diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..bdb0cabc8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index 4e6a72782..09d71b878 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,8 @@ doc/release-docs/ src/builtin-config.c src/tig tags -test/test-graph +test/tools/test-graph +test/tmp tig-*.tar.gz tig-*.tar.gz.md5 tig.spec diff --git a/.travis.yml b/.travis.yml index 6e185cb8d..405d06d5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ compiler: - gcc env: - - TEST=autoconf VERBOSE=1 + - TEST=autoconf VERBOSE=1 TEST_SHELL=bash - TEST=make VERBOSE=@ before_install: sudo apt-get update -qq @@ -15,4 +15,4 @@ install: sudo apt-get install -qq --no-install-recommends asciidoc xmlto docbook script: - if [ "$TEST" = autoconf ]; then ./autogen.sh && ./configure; fi - if [ "$TEST" = make ]; then cp contrib/config.make .; fi - - make V=$VERBOSE DESTDIR=/tmp all test install install-doc + - make V=$VERBOSE DESTDIR=/tmp TEST_SHELL=$TEST_SHELL all test install install-doc diff --git a/Makefile b/Makefile index 6d684b7b4..cc829e57a 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # The last tagged version. Can be overridden either by the version from # git or from the value of the DIST_VERSION environment variable. -VERSION = 2.0.1 +VERSION = 2.0.3 all: @@ -41,7 +41,7 @@ LDLIBS ?= -lcurses CFLAGS ?= -Wall -O2 DFLAGS = -g -DDEBUG -Werror -O0 EXE = src/tig -TOOLS = test/test-graph tools/doc-gen +TOOLS = test/tools/test-graph tools/doc-gen TXTDOC = doc/tig.1.adoc doc/tigrc.5.adoc doc/manual.adoc NEWS.adoc README.adoc INSTALL.adoc MANDOC = doc/tig.1 doc/tigrc.5 doc/tigmanual.7 HTMLDOC = doc/tig.1.html doc/tigrc.5.html doc/manual.html README.html INSTALL.html NEWS.html @@ -103,7 +103,7 @@ install-release-doc-html: install-doc: install-doc-man install-doc-html install-release-doc: install-release-doc-man install-release-doc-html -clean: +clean: clean-test $(RM) -r $(TARNAME) *.spec tig-*.tar.gz tig-*.tar.gz.md5 .deps $(RM) $(EXE) $(TOOLS) $(OBJS) core doc/*.xml src/builtin-config.c @@ -153,16 +153,26 @@ dist: configure tig.spec rpm: dist rpmbuild -ta $(TARNAME).tar.gz -test: $(TOOLS) - test/unit-test-graph.sh - test/builtin-config.sh +TESTS = $(sort $(shell find test -type f -name '*-test')) + +clean-test: + $(Q)$(RM) -r test/tmp + +test: clean-test $(TESTS) + $(QUIET_SUMMARY)test/tools/show-results.sh + +export TEST_OPTS ?= $(V:1=no-indent) + +$(TESTS): PATH := $(CURDIR)/test/tools:$(CURDIR)/src:$(PATH) +$(TESTS): $(EXE) test/tools/test-graph + $(QUIET_TEST)$(TEST_SHELL) $@ # Other autoconf-related rules are hidden in config.make.in so that # they don't confuse Make when we aren't actually using ./configure configure: configure.ac acinclude.m4 tools/*.m4 ./autogen.sh -.PHONY: all all-debug doc doc-man doc-html install install-doc \ +.PHONY: all all-debug doc doc-man doc-html install install-doc $(TESTS) \ install-doc-man install-doc-html clean spell-check dist rpm test ifdef NO_MKSTEMPS @@ -222,8 +232,8 @@ TIG_OBJS = \ src/tig: $(TIG_OBJS) -TEST_GRAPH_OBJS = test/test-graph.o src/string.o src/util.o src/io.o src/graph.o $(COMPAT_OBJS) -test/test-graph: $(TEST_GRAPH_OBJS) +TEST_GRAPH_OBJS = test/tools/test-graph.o src/string.o src/util.o src/io.o src/graph.o $(COMPAT_OBJS) +test/tools/test-graph: $(TEST_GRAPH_OBJS) DOC_GEN_OBJS = tools/doc-gen.o src/string.o src/types.o src/util.o src/request.o tools/doc-gen: $(DOC_GEN_OBJS) @@ -317,6 +327,8 @@ QUIET_DB2PDF = $(Q:@=@echo ' DB2PDF '$@;) # tools/install.sh will print 'file -> $install_dir/file' QUIET_INSTALL = $(Q:@=@printf ' INSTALL ';) QUIET_INSTALL_EACH = $(Q:@=printf ' INSTALL ';) +QUIET_TEST = $(Q:@=@echo ' TEST '$@;) +QUIET_SUMMARY = $(Q:@=@printf ' SUMMARY ';) export V endif diff --git a/NEWS.adoc b/NEWS.adoc index 82d9d9be0..678cea883 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -4,11 +4,75 @@ Release notes master ------ +Improvements: + + - Add move-half-page-up and move-half-page-down actions. (GH #323) + - Preserve the cursor position when changing the diff context. + +Bug fixes: + + - Update manual to reflect default keybinding changes. (GH #325) + - Fix graph support for `--first-parent`. (GH #326) + - Fix off-by-one error when opening editor from the grep view. + +tig-2.0.3 +--------- + +Improvements: + + - Add `:save-display ` prompt command to save the current display. + - Add `:script ` prompt command for scripting the Tig UI. + - Add test framework and convert existing tests to use it. + - Add command-line option for starting in refs view: `tig refs`. (GH #309) + - Make blame commit ID colors stable across reloads. (GH #303) + - Increase blame ID and graph rendering color palette to 14 colors. + - New setting 'split-view-width' controls the width for vertical splits. It + takes the width of the right-most view either as a number or a percentage. + - Expose settings holding command line argument lists: `file-args`, `rev-args`, + and `cmdline-args`. They are mainly intended for testing purposes but also + allows to change the filtering arguments dynamically. (GH #306) + - Add `log-options` setting for specifying default log view options. + Example: `set log-options = --pretty=fuller`. + - Use option specific view flags to reload view after `:set` commands. + +Bug fixes: + + - Refresh the current view when returning from an external command and + `refresh-mode=after-command`. (GH #289) + - Fix readline completion. + - Fix '/' to `find-next` when readline support is enabled. (GH #302) + - Fix readline prompt to correctly handle UTF-8 characters. + - Add warnings for more obsolete actions and colors. + - Fix passing of commit IDS via stdin to the main view. + - Fix commit title overflow drawing for multibyte text. (GH #307) + - Fix installation directory permissions. + - Handle binary files matches reported by git-grep. + - Toggling of "args"-typed options without any arguments will clear the current + arguments. Example: `:toggle blame-options`. + - Detect custom `pretty.format` settings that break the log view and fallback + to use the `medium` format. (GH #225) + - Fix invocation of git-diff for the blame view's line tracking. (GH #316) + - Fix blame completion of directory names. (GH #317) + - Fix display of conflicts in the main view when 'show-changes' is enabled. + - Fix off-by-one error when displaying line numbers in the grep view. + - When showing the commit graph ensure that either topo, date or author-date + commit order is used. (Debian #757692) (GH #238) + +tig-2.0.2 +--------- + +Improvements: + + - Use git-status for diffing the index. + - Group toggle options together in the help view. + Bug fixes: - Fix refs, main and grep loading when 'gui.encoding' is set. (GH #287) - Ignore 'gui.encoding' and 'i18n.commitencoding' when set to 'UTF-8'. - Add work-around for missing strndup() on Mac OS X v10.6. (GH #286) + - Fix spurious abbreviation of author names. (GH #288) + - Don't show empty action groups in the help view. tig-2.0.1 --------- diff --git a/contrib/tig-completion.bash b/contrib/tig-completion.bash index 8b4adc514..67303ec0a 100755 --- a/contrib/tig-completion.bash +++ b/contrib/tig-completion.bash @@ -186,22 +186,40 @@ _tig_options () _tig_blame () { - local reply="" ref=HEAD cur="${COMP_WORDS[COMP_CWORD]}" + local reply="" ref=HEAD cur="${COMP_WORDS[COMP_CWORD]}" p="" + local pfx=$(git rev-parse --show-prefix 2>/dev/null) if test "$COMP_CWORD" -lt 3; then - reply="$(__tig_refs)" + _tigcomp "$(__tig_refs)" else ref="${COMP_WORDS[2]}" fi - reply="$reply $(git --git-dir="$(__tigdir)" ls-tree "$ref" \ - | sed '/^100... blob /s,^.* ,, - /^040000 tree /{ - s,^.* ,, - s,$,/, - } - s/^.* //')" - _tigcomp "$reply" + case "$cur" in + */) p=${cur%/} ;; + */*) p=${cur%/*} ;; + *) p= ;; + esac + + i=${#COMPREPLY[@]} + local IFS=$'\n' + for c in $(git --git-dir="$(__tigdir)" ls-tree "$ref:$pfx$p" 2>/dev/null | + sed -n '/^100... blob /{ + s,^.* ,, + s,$, , + p + } + /^040000 tree /{ + s,^.* ,, + s,$,/, + p + }') + do + c="${p:+$p/}$c" + if [[ "$c" == "$cur"* ]]; then + COMPREPLY[i++]=$c + fi + done } _tig_show () diff --git a/doc/manual.adoc b/doc/manual.adoc index d88e48f21..2a5187d07 100644 --- a/doc/manual.adoc +++ b/doc/manual.adoc @@ -88,7 +88,7 @@ from an external command, most often 'git log', 'git diff', or 'git show'. The main view:: Is the default view, and it shows a one line summary of each commit - in the chosen list of revisions. The summary includes commit date, + in the chosen list of revisions. The summary includes author date, author, and the first line of the log message. Additionally, any repository references, such as tags, will be shown. @@ -301,11 +301,12 @@ View Switching |p |Switch to pager view. |t |Switch to (directory) tree view. |f |Switch to (file) blob view. -|B |Switch to blame view. -|H |Switch to refs view. +|g |Switch to grep view. +|b |Switch to blame view. +|r |Switch to refs view. |y |Switch to stash view. |h |Switch to help view -|S |Switch to status view +|s |Switch to status view |c |Switch to stage view |============================================================================= @@ -384,8 +385,8 @@ Scrolling |Key |Action |Insert |Scroll view one line up. |Delete |Scroll view one line down. -|w |Scroll view one page up. -|s |Scroll view one page down. +|ScrBack|Scroll view one page up. +|ScrFwd |Scroll view one page down. |Left |Scroll view one column left. |Right |Scroll view one column right. |\| |Scroll view to the first column. @@ -412,16 +413,16 @@ Misc |============================================================================= |Key |Action |Q |Quit. -|r |Redraw screen. +| |Redraw screen. |z |Stop all background loading. This can be useful if you use Tig in a repository with a long history without limiting the revision log. |v |Show version. |o |Open option menu -|. |Toggle line numbers on/off. +|# |Toggle line numbers on/off. |D |Toggle date display on/off/short/relative/local. |A |Toggle author display on/off/abbreviated/email/email user name. -|g |Toggle revision graph visualization on/off. +|G |Toggle revision graph visualization on/off. |~ |Toggle (line) graphics mode |F |Toggle reference display on/off (tag and branch names). |W |Toggle ignoring whitespace on/off for diffs diff --git a/doc/tig.1.adoc b/doc/tig.1.adoc index e09661191..f94f45bea 100644 --- a/doc/tig.1.adoc +++ b/doc/tig.1.adoc @@ -16,6 +16,7 @@ tig log [options] [revisions] [--] [paths] tig show [options] [revisions] [--] [paths] tig blame [options] [rev] [--] path tig grep [options] [pattern] +tig refs tig stash tig status tig < [Git command output] @@ -52,6 +53,9 @@ status:: log:: Start up in log view, displaying git-log(1) output. +refs:: + Start up in refs view. + stash:: Start up in stash view. @@ -167,6 +171,11 @@ Grep all files for lines containing `DEFINE_ENUM`: $ tig grep -p DEFINE_ENUM ----------------------------------------------------------------------------- +Show references (branches, remotes and tags): +----------------------------------------------------------------------------- +$ tig refs +----------------------------------------------------------------------------- + ENVIRONMENT VARIABLES --------------------- @@ -201,6 +210,16 @@ TIG_DIFF_OPTS:: TIG_TRACE:: Path for trace file where information about Git commands are logged. +TIG_SCRIPT:: + Path to script that should be executed automatically on startup. If this + environment variable is defined to the empty string, the script is read + from stdin. The script is interpreted line-by-line and can contain + prompt commands and key mappings. + +TIG_NO_DISPLAY:: + Open Tig without rendering anything to the terminal. This force Ncurses + to write to /dev/null. The main use is for automated testing of Tig. + FILES ----- '~/.tigrc':: diff --git a/doc/tigrc.5.adoc b/doc/tigrc.5.adoc index ae498b878..64b99b64b 100644 --- a/doc/tigrc.5.adoc +++ b/doc/tigrc.5.adoc @@ -38,8 +38,9 @@ one of the Git configuration files, which are read by Tig on startup. See 'git-config(1)' for which files to use. The following example show the basic syntax to use for settings, bindings and colors. +// TEST: gitconfig -------------------------------------------------------------------------- -[tig] show-rev-graph = true +[tig] show-changes = true [tig "color"] cursor = yellow red bold [tig "bind"] generic = P parent -------------------------------------------------------------------------- @@ -83,6 +84,7 @@ is: Examples: +// TEST: tigrc -------------------------------------------------------------------------- set commit-order = topo # Order commits topologically set git-colors = no # Do not read Git's color settings. @@ -103,6 +105,7 @@ set blame-view = \ Or in the Git configuration files: +// TEST: gitconfig -------------------------------------------------------------------------- [tig] line-graphics = no # Disable graphics characters @@ -203,11 +206,19 @@ The following variables can be set: 'split-view-height' (mixed):: - Height of the lower view in a split view. Can be specified either as - the number of rows, e.g. '5', or as a percentage of the view height, - e.g. '80%', where the maximum is 100%. It is always ensured that the - smaller of the views is at least four rows high. The default is a view - height of '66%'. + The height of the bottom view in a horizontally split display. Can be + specified either as the number of rows, e.g. '5', or as a percentage of + the view height, e.g. '80%', where the maximum is 100%. It is always + ensured that the smaller of the views is at least four rows high. The + default is '67%'. + +'split-view-width' (mixed):: + + Width of the right-most view in a vertically split display. Can be + specified either as the number of column, e.g. '5', or as a percentage + of the view width, e.g. '80%', where the maximum is 100%. It is always + ensured that the smaller of the views is at least four columns wide. The + default is '50%'. 'status-untracked-dirs' (bool):: @@ -234,6 +245,8 @@ The following variables can be set: Commit ordering using the default (chronological reverse) order, topological order, date order or reverse order. The default order is used when the option is set to false, and topo order when set to true. + Note that topological order is automatically used in the main view when + the commit graph is enabled and the commit order is set to the default. 'ignore-case' (bool):: @@ -283,6 +296,21 @@ The following variables can be set: Interval in seconds between view refresh update checks when 'refresh-mode' is set to 'periodic'. +'file-args' (args):: + + Command line arguments referring to files. These are filtered using + `git-rev-parse(1)`. + +'rev-args' (args):: + + Command line arguments referring to revisions. These are filtered using + `git-rev-parse(1)`. + +'cmdline-args' (args):: + + All remaining command line arguments that are not either filtered into + 'file-args' or 'rev-args'. + View settings ~~~~~~~~~~~~~ @@ -300,6 +328,7 @@ enum value. For example, `file-name` will automatically have its 'display' setting resolve to 'auto'. Examples: +// TEST: tigrc -------------------------------------------------------------------------- # Enable both ID and line numbers in the blame view set blame-view = date:default author:full file-name:auto id:yes,color \ @@ -335,9 +364,9 @@ author:: - 'display' (mixed) [full|abbreviated|email|email-user|]: How to display author names. If set to "abbreviated" author initials will be shown. - - 'width' (int): Width of the column. When set to 5 or below, the author - name will be abbreviated to the author's initials. When set to zero, - the width is automatically sized to fit the content. + - 'width' (int): Width of the column. When set to a value between 1 and + 10, the author name will be abbreviated to the author's initials. + When set to zero, the width is automatically sized to fit the content. commit-title:: - 'graph' (bool): Whether to show revision graph in the main view on @@ -425,10 +454,10 @@ key map. The syntax is: *bind* 'keymap' 'key' 'action' Examples: - +// TEST: tigrc -------------------------------------------------------------------------- # Add keybinding to quickly jump to the next diff chunk in the stage view -bind stage Enter :/^@@ +bind stage :/^@@ # Disable the default mapping for running git-gc bind generic G none @@ -444,7 +473,7 @@ bind generic ø @sh -c "printf '%s' %(commit) | pbcopy" -------------------------------------------------------------------------- Or in the Git configuration files: - +// TEST: gitconfig -------------------------------------------------------------------------- [tig "bind"] # 'unbind' the default quit key binding @@ -485,9 +514,9 @@ combos consisting of an initial `Escape` key followed by a normal key value can be bound using `key`. Examples: - +// TEST: tigrc -------------------------------------------------------------------------- -bind main R reload +bind main R refresh bind main next bind main scroll-page-down bind main o options @@ -554,7 +583,7 @@ following variable names, which are substituted before commands are run: |============================================================================= Examples: - +// TEST: tigrc -------------------------------------------------------------------------- # Save save the current commit as a patch file when the user selects a # commit in the main view and presses 'S'. @@ -572,6 +601,7 @@ expansion of environment variables and process control, this can be achieved by using a shell command: .Configure a binding to copy the current commit ID to the clipboard. +// TEST: tigrc -------------------------------------------------------------------------- bind generic I @sh -c "echo -n %(commit) | xclip -selection c" -------------------------------------------------------------------------- @@ -581,6 +611,7 @@ following example entries can be put in either the .gitconfig or .git/config file: .Git configuration which binds Tig keys to Git command aliases. +// TEST: gitconfig -------------------------------------------------------------------------- [alias] gitk-bg = !"gitk HEAD --not $(git rev-parse --remotes) &" @@ -599,6 +630,7 @@ and act similar to commands run via Tig's prompt. Valid internal commands are configuration file options (as described in this document) and pager view commands. Examples: +// TEST: tigrc -------------------------------------------------------------------------- # Reload ~/.tigrc when 'S' is pressed bind generic S :source .tigrc @@ -613,7 +645,7 @@ bind generic W :!git reflog bind stage 2 :?^@@ bind stage D :/^diff --(git|cc) -bind main I :toggle show-id # Show/hide the ID column +bind main I :toggle id # Show/hide the ID column bind diff D :toggle diff-options --minimal # Use minimal diff algorithm bind diff [ :toggle diff-context -3 # Decrease context (-U arg) bind diff ] :toggle diff-context +3 # Increase context @@ -687,8 +719,10 @@ Cursor navigation |============================================================================= |move-up |Move cursor one line up |move-down |Move cursor one line down -|move-page-down |Move cursor one page down |move-page-up |Move cursor one page up +|move-page-down |Move cursor one page down +|move-half-page-up |Move cursor half a page up +|move-half-page-down |Move cursor half a page down |move-first-line |Move cursor to first line |move-last-line |Move cursor to last line |============================================================================= @@ -755,7 +789,7 @@ given as the last parameter. The syntax is: *color* 'area' 'fgcolor' 'bgcolor' '[attributes]' Examples: - +// TEST: tigrc ------------------------------------------------------------------------------ # Override the default terminal colors to white on black. color default white black @@ -769,7 +803,7 @@ color tree.date black cyan bold -------------------------------------------------------------------------- Or in the Git configuration files: - +// TEST: gitconfig -------------------------------------------------------------------------- [tig "color"] # A strange looking cursor line @@ -837,7 +871,7 @@ setting the *default* color option. sections in the help view. |line-number |Line numbers. |id |The commit ID. -|date |The commit date. +|date |The author date. |author |The commit author. |mode |The file mode holding the permissions and type. |overflow |Title text overflow. @@ -850,9 +884,11 @@ setting the *default* color option. [frame="none",grid="none",cols="25 0 && string_format(msgbuf, "%.*s", msglen, msgstart)) { const char *msg = msgbuf; @@ -271,9 +271,13 @@ format_append_arg(struct format_context *format, const char ***dst_argv, const c format->bufpos = 0; while (arg) { - char *var = strstr(arg, "%("); - int len = var ? var - arg : strlen(arg); - char *next = var ? strchr(var, ')') + 1 : NULL; + const char *var = strstr(arg, "%("); + const char *closing = var ? strchr(var, ')') : NULL; + const char *next = closing ? closing + 1 : NULL; + const int len = var ? var - arg : strlen(arg); + + if (var && !closing) + return FALSE; if (len && !string_format_from(format->buf, &format->bufpos, "%.*s", len, arg)) return FALSE; @@ -319,7 +323,7 @@ argv_format(struct argv_env *argv_env, const char ***dst_argv, const char *src_a const char *arg = src_argv[argc]; if (!strcmp(arg, "%(fileargs)")) { - if (file_filter && !argv_append_array(dst_argv, opt_file_argv)) + if (file_filter && !argv_append_array(dst_argv, opt_file_args)) break; } else if (!strcmp(arg, "%(diffargs)")) { @@ -330,13 +334,17 @@ argv_format(struct argv_env *argv_env, const char ***dst_argv, const char *src_a if (!format_append_argv(&format, dst_argv, opt_blame_options)) break; + } else if (!strcmp(arg, "%(logargs)")) { + if (!format_append_argv(&format, dst_argv, opt_log_options)) + break; + } else if (!strcmp(arg, "%(cmdlineargs)")) { - if (!format_append_argv(&format, dst_argv, opt_cmdline_argv)) + if (!format_append_argv(&format, dst_argv, opt_cmdline_args)) break; } else if (!strcmp(arg, "%(revargs)") || (first && !strcmp(arg, "%(commit)"))) { - if (!argv_append_array(dst_argv, opt_rev_argv)) + if (!argv_append_array(dst_argv, opt_rev_args)) break; } else if (!format_append_arg(&format, dst_argv, arg)) { diff --git a/src/blame.c b/src/blame.c index ec975d5af..9f3a82401 100644 --- a/src/blame.c +++ b/src/blame.c @@ -93,17 +93,17 @@ blame_open(struct view *view, enum open_flags flags) if (is_initial_view(view)) { /* Finish validating and setting up blame options */ - if (!opt_file_argv || opt_file_argv[1] || (opt_rev_argv && opt_rev_argv[1])) + if (!opt_file_args || opt_file_args[1] || (opt_rev_args && opt_rev_args[1])) usage("Invalid number of options to blame"); - if (opt_rev_argv) { - string_ncopy(view->env->ref, opt_rev_argv[0], strlen(opt_rev_argv[0])); + if (opt_rev_args) { + string_ncopy(view->env->ref, opt_rev_args[0], strlen(opt_rev_args[0])); } - string_ncopy(view->env->file, opt_file_argv[0], strlen(opt_file_argv[0])); + string_ncopy(view->env->file, opt_file_args[0], strlen(opt_file_args[0])); - opt_blame_options = opt_cmdline_argv; - opt_cmdline_argv = NULL; + opt_blame_options = opt_cmdline_args; + opt_cmdline_args = NULL; } if (!view->env->file[0]) { @@ -339,7 +339,7 @@ setup_blame_parent_line(struct view *view, struct blame *blame) char from[SIZEOF_REF + SIZEOF_STR]; char to[SIZEOF_REF + SIZEOF_STR]; const char *diff_tree_argv[] = { - "git", "diff", encoding_arg, "--no-textconv", "--no-extdiff", + "git", "diff", encoding_arg, "--no-textconv", "--no-ext-diff", "--no-color", "-U0", from, to, "--", NULL }; struct io io; @@ -433,7 +433,7 @@ blame_request(struct view *view, enum request request, struct line *line) switch (request) { case REQ_VIEW_BLAME: case REQ_PARENT: - if (!check_blame_commit(blame, TRUE)) + if (!check_blame_commit(blame, request == REQ_VIEW_BLAME)) break; blame_go_forward(view, blame, request == REQ_PARENT); break; diff --git a/src/diff.c b/src/diff.c index 42cd35e14..fa9d61309 100644 --- a/src/diff.c +++ b/src/diff.c @@ -32,6 +32,8 @@ diff_open(struct view *view, enum open_flags flags) "--", "%(fileargs)", NULL }; + diff_save_line(view, view->private, flags); + return begin_update(view, NULL, diff_argv, flags); } @@ -164,6 +166,61 @@ diff_common_enter(struct view *view, enum request request, struct line *line) } } +void +diff_save_line(struct view *view, struct diff_state *state, enum open_flags flags) +{ + if (flags & OPEN_RELOAD) { + struct line *line = &view->line[view->pos.lineno]; + const char *file = view_has_line(view, line) ? diff_get_pathname(view, line) : NULL; + + if (file) { + state->file = get_path(file); + state->lineno = diff_get_lineno(view, line); + state->pos = view->pos; + } + } +} + +void +diff_restore_line(struct view *view, struct diff_state *state) +{ + struct line *line = &view->line[view->lines - 1]; + + if (!state->file) + return; + + while ((line = find_prev_line_by_type(view, line, LINE_DIFF_HEADER))) { + const char *file = diff_get_pathname(view, line); + + if (file && !strcmp(file, state->file)) + break; + line--; + } + + state->file = NULL; + + if (!line) + return; + + while ((line = find_next_line_by_type(view, line, LINE_DIFF_CHUNK))) { + unsigned int lineno = diff_get_lineno(view, line); + + for (line++; view_has_line(view, line) && line->type != LINE_DIFF_CHUNK; line++) { + if (lineno == state->lineno) { + unsigned long lineno = line - view->line; + unsigned long offset = lineno - (state->pos.lineno - state->pos.offset); + + goto_view_line(view, offset, lineno); + redraw_view(view); + return; + } + if (line->type != LINE_DIFF_DEL && + line->type != LINE_DIFF_DEL2) + lineno++; + } + } +} + static bool diff_read(struct view *view, struct buffer *buf) { @@ -171,9 +228,9 @@ diff_read(struct view *view, struct buffer *buf) if (!buf) { /* Fall back to retry if no diff will be shown. */ - if (view->lines == 0 && opt_file_argv) { + if (view->lines == 0 && opt_file_args) { int pos = argv_size(view->argv) - - argv_size(opt_file_argv) - 1; + - argv_size(opt_file_args) - 1; if (pos > 0 && !strcmp(view->argv[pos], "--")) { for (; view->argv[pos]; pos++) { @@ -187,6 +244,9 @@ diff_read(struct view *view, struct buffer *buf) return FALSE; } } + + diff_restore_line(view, state); + return TRUE; } @@ -411,6 +471,7 @@ diff_select(struct view *view, struct line *line) if (file) { string_format(view->ref, "Changes to '%s'", file); string_format(view->env->file, "%s", file); + view->env->lineno = diff_get_lineno(view, line); view->env->blob[0] = 0; } else { string_ncopy(view->ref, view->ops->id, strlen(view->ops->id)); diff --git a/src/display.c b/src/display.c index df04b87df..398bbe25d 100644 --- a/src/display.c +++ b/src/display.c @@ -28,24 +28,32 @@ static WINDOW *display_win[2]; static WINDOW *display_title[2]; static WINDOW *display_sep; -FILE *opt_tty; +static FILE *opt_tty; bool -open_external_viewer(const char *argv[], const char *dir, bool confirm, bool refresh, const char *notice) +open_external_viewer(const char *argv[], const char *dir, bool silent, bool confirm, bool refresh, const char *notice) { bool ok; - def_prog_mode(); /* save current tty modes */ - endwin(); /* restore original tty modes */ - ok = io_run_fg(argv, dir); - if (confirm || !ok) { - if (!ok && *notice) - fprintf(stderr, "%s", notice); - fprintf(stderr, "Press Enter to continue"); - getc(opt_tty); + if (silent) { + ok = io_run_bg(argv); + + } else { + def_prog_mode(); /* save current tty modes */ + endwin(); /* restore original tty modes */ + ok = io_run_fg(argv, dir); + if (confirm || !ok) { + if (!ok && *notice) + fprintf(stderr, "%s", notice); + if (!is_script_executing()) { + fprintf(stderr, "Press Enter to continue"); + getc(opt_tty); + } + } + reset_prog_mode(); } - reset_prog_mode(); - if (watch_update(WATCH_EVENT_AFTER_EXTERNAL) && refresh) { + + if (watch_update(WATCH_EVENT_AFTER_COMMAND) && refresh) { struct view *view; int i; @@ -94,7 +102,7 @@ open_editor(const char *file, unsigned int lineno) if (lineno && opt_editor_line_number && string_format(lineno_cmd, "+%u", lineno)) editor_argv[argc++] = lineno_cmd; editor_argv[argc] = file; - if (!open_external_viewer(editor_argv, repo.cdup, FALSE, TRUE, EDITOR_LINENO_MSG)) + if (!open_external_viewer(editor_argv, repo.cdup, FALSE, FALSE, TRUE, EDITOR_LINENO_MSG)) opt_editor_line_number = FALSE; } @@ -113,7 +121,7 @@ static void apply_vertical_split(struct view *base, struct view *view) { view->height = base->height; - view->width = apply_step(VSPLIT_SCALE, base->width); + view->width = apply_step(opt_split_view_width, base->width); view->width = MAX(view->width, MIN_VIEW_WIDTH); view->width = MIN(view->width, base->width - MIN_VIEW_WIDTH); base->width -= view->width; @@ -243,6 +251,70 @@ redraw_display(bool clear) redraw_display_separator(clear); } +static bool +save_window_line(FILE *file, WINDOW *win, int y, char *buf, size_t bufsize) +{ + int read = mvwinnstr(win, y, 0, buf, bufsize); + + return read == ERR ? FALSE : fprintf(file, "%s\n", buf) == read + 1; +} + +static bool +save_window_vline(FILE *file, WINDOW *left, WINDOW *right, int y, char *buf, size_t bufsize) +{ + int read1 = mvwinnstr(left, y, 0, buf, bufsize); + int read2 = read1 == ERR ? ERR : mvwinnstr(right, y, 0, buf + read1 + 1, bufsize - read1 - 1); + + if (read2 == ERR) + return FALSE; + buf[read1] = '|'; + + return fprintf(file, "%s\n", buf) == read1 + 1 + read2 + 1; +} + +bool +save_display(const char *path) +{ + int i, width; + char *line; + FILE *file = fopen(path, "w"); + bool ok = TRUE; + struct view *view = display[0]; + + if (!file) + return FALSE; + + getmaxyx(stdscr, i, width); + line = malloc(width + 1); + if (!line) { + fclose(file); + return FALSE; + } + + if (view->width < width && display[1]) { + struct view *left = display[0], + *right = display[1]; + + for (i = 0; ok && i < left->height; i++) + ok = save_window_vline(file, left->win, right->win, i, line, width); + if (ok) + ok = save_window_vline(file, left->title, right->title, 0, line, width); + } else { + int j; + + foreach_displayed_view (view, j) { + for (i = 0; ok && i < view->height; i++) + ok = save_window_line(file, view->win, i, line, width); + if (ok) + ok = save_window_line(file, view->title, 0, line, width); + } + } + + free(line); + fclose(file); + return ok; +} + /* * Status management */ @@ -327,6 +399,7 @@ done_display(void) void init_display(void) { + bool no_display = !!getenv("TIG_NO_DISPLAY"); const char *term; int x, y; @@ -336,15 +409,18 @@ init_display(void) die("Failed to register done_display"); /* Initialize the curses library */ - if (isatty(STDIN_FILENO)) { + if (!no_display && isatty(STDIN_FILENO)) { cursed = !!initscr(); opt_tty = stdin; } else { /* Leave stdin and stdout alone when acting as a pager. */ + FILE *out_tty; + opt_tty = fopen("/dev/tty", "r+"); - if (!opt_tty) + out_tty = no_display ? fopen("/dev/null", "w+") : opt_tty; + if (!opt_tty || !out_tty) die("Failed to open /dev/tty"); - cursed = !!newterm(NULL, opt_tty, opt_tty); + cursed = !!newterm(NULL, out_tty, opt_tty); } if (!cursed) @@ -355,8 +431,7 @@ init_display(void) noecho(); /* Don't echo input */ leaveok(stdscr, FALSE); - if (has_colors()) - init_colors(); + init_colors(); getmaxyx(stdscr, y, x); status_win = newwin(1, x, y - 1, 0); @@ -376,10 +451,11 @@ init_display(void) term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM"); if (term && !strcmp(term, "gnome-terminal")) { - /* In the gnome-terminal-emulator, the message from - * scrolling up one line when impossible followed by - * scrolling down one line causes corruption of the - * status line. This is fixed by calling wclear. */ + /* In the gnome-terminal-emulator, the warning message + * shown when scrolling up one line while the cursor is + * on the first line followed by scrolling down one line + * corrupts the status line. This is fixed by calling + * wclear. */ use_scroll_status_wclear = TRUE; use_scroll_redrawwin = FALSE; @@ -396,6 +472,71 @@ init_display(void) } } +static struct io script_io = { -1 }; + +bool +open_script(const char *path) +{ + return io_open(&script_io, "%s", path); +} + +bool +is_script_executing(void) +{ + return script_io.pipe != -1; +} + +static bool +read_script(struct key *key) +{ + static struct buffer input_buffer; + static const char *line = ""; + enum status_code code; + + if (!line || !*line) { + if (input_buffer.data && *input_buffer.data == ':') { + line = ""; + memset(&input_buffer, 0, sizeof(input_buffer)); + + } else if (!io_get(&script_io, &input_buffer, '\n', TRUE)) { + io_done(&script_io); + return FALSE; + } else { + line = input_buffer.data; + } + } + + code = get_key_value(&line, key); + if (code != SUCCESS) + die("Error reading script: %s", get_status_message(code)); + return TRUE; +} + +int +get_input_char(void) +{ + if (is_script_executing()) { + static struct key key; + static int bytes_pos; + + if (!key.modifiers.multibytes || bytes_pos >= strlen(key.data.bytes)) { + if (!read_script(&key)) + return 0; + bytes_pos = 0; + } + + if (!key.modifiers.multibytes) { + if (key.data.value < 128) + return key.data.value; + die("Only ASCII control characters can be used in prompts: %d", key.data.value); + } + + return key.data.bytes[bytes_pos++]; + } + + return getc(opt_tty); +} + int get_input(int prompt_position, struct key *key, bool modifiers) { @@ -436,15 +577,23 @@ get_input(int prompt_position, struct key *key, bool modifiers) } else { view = display[current_view]; getbegyx(view->win, cursor_y, cursor_x); - cursor_x = view->width - 1; + cursor_x += view->width - 1; cursor_y += view->pos.lineno - view->pos.offset; } setsyx(cursor_y, cursor_x); - /* Refresh, accept single keystroke of input */ - doupdate(); - wtimeout(status_win, delay); - key_value = wgetch(status_win); + if (is_script_executing()) { + /* Wait for the current command to complete. */ + if (delay == 0 || !read_script(key)) + continue; + return key->modifiers.multibytes ? OK : key->data.value; + + } else { + /* Refresh, accept single keystroke of input */ + doupdate(); + wtimeout(status_win, delay); + key_value = wgetch(status_win); + } /* wgetch() with nodelay() enabled returns ERR when * there's no input. */ @@ -498,7 +647,6 @@ get_input(int prompt_position, struct key *key, bool modifiers) key->data.bytes[0] = key_value; key_length = utf8_char_length(key->data.bytes); - nodelay(status_win, TRUE); for (pos = 1; pos < key_length && pos < sizeof(key->data.bytes) - 1; pos++) { key->data.bytes[pos] = wgetch(status_win); } diff --git a/src/draw.c b/src/draw.c index 0bfcc9538..897646d19 100644 --- a/src/draw.c +++ b/src/draw.c @@ -16,6 +16,23 @@ #include "tig/draw.h" #include "tig/options.h" +static const enum line_type palette_colors[] = { + LINE_PALETTE_0, + LINE_PALETTE_1, + LINE_PALETTE_2, + LINE_PALETTE_3, + LINE_PALETTE_4, + LINE_PALETTE_5, + LINE_PALETTE_6, + LINE_PALETTE_7, + LINE_PALETTE_8, + LINE_PALETTE_9, + LINE_PALETTE_10, + LINE_PALETTE_11, + LINE_PALETTE_12, + LINE_PALETTE_13, +}; + /* * View drawing. */ @@ -116,12 +133,15 @@ draw_text_overflow(struct view *view, const char *text, enum line_type type, if (on) { int overflow = overflow_length + offset; int max = MIN(VIEW_MAX_LEN(view), overflow); - int len = strlen(text); + const char *tmp = text; + int text_width = 0; + int trimmed = FALSE; + size_t len = utf8_length(&tmp, 0, &text_width, max, &trimmed, FALSE, 1); - if (draw_text_expanded(view, type, text, max, max < overflow)) + if (draw_text_expanded(view, type, text, text_width, max < overflow)) return TRUE; - text = len > overflow ? text + overflow : ""; + text += len; type = LINE_OVERFLOW; } @@ -220,22 +240,16 @@ draw_author(struct view *view, struct view_column *column, const struct ident *a static bool draw_id(struct view *view, struct view_column *column, const char *id) { - static const enum line_type colors[] = { - LINE_PALETTE_0, - LINE_PALETTE_1, - LINE_PALETTE_2, - LINE_PALETTE_3, - LINE_PALETTE_4, - LINE_PALETTE_5, - LINE_PALETTE_6, - }; enum line_type type = LINE_ID; if (!column->opt.id.display) return FALSE; - if (column->opt.id.color) - type = colors[((long) id) % ARRAY_SIZE(colors)]; + if (column->opt.id.color && id) { + hashval_t color = iterative_hash(id, SIZEOF_REV - 1, 0); + + type = palette_colors[color % ARRAY_SIZE(palette_colors)]; + } return draw_field(view, type, id, column->width, ALIGN_LEFT, FALSE); } @@ -357,22 +371,12 @@ draw_status(struct view *view, struct view_column *column, * Revision graph */ -static const enum line_type graph_colors[] = { - LINE_PALETTE_0, - LINE_PALETTE_1, - LINE_PALETTE_2, - LINE_PALETTE_3, - LINE_PALETTE_4, - LINE_PALETTE_5, - LINE_PALETTE_6, -}; - static enum line_type get_graph_color(struct graph_symbol *symbol) { if (symbol->commit) return LINE_GRAPH_COMMIT; - assert(symbol->color < ARRAY_SIZE(graph_colors)); - return graph_colors[symbol->color]; + assert(symbol->color < ARRAY_SIZE(palette_colors)); + return palette_colors[symbol->color]; } static bool diff --git a/src/grep.c b/src/grep.c index 1d7b97638..5e671cc29 100644 --- a/src/grep.c +++ b/src/grep.c @@ -32,8 +32,6 @@ struct grep_state { bool no_file_group; }; -#define grep_view_lineno(grep) ((grep)->lineno > 0 ? (grep)->lineno - 1 : 0) - static struct grep_line * grep_get_line(const struct line *line) { @@ -60,7 +58,7 @@ grep_get_column_data(struct view *view, const struct line *line, struct view_col return TRUE; } - if (*grep->file && !grep->lineno) { + if (*grep->file && !*grep->text) { static struct view_column file_name_column; file_name_column.type = VIEW_COLUMN_FILE_NAME; @@ -116,8 +114,10 @@ open_grep_view(struct view *prev) if ((!prev && is_initial_view(view)) || (view->lines && !in_grep_view)) { open_view(prev, view, OPEN_DEFAULT); } else { - if (grep_prompt()) + if (grep_prompt()) { + clear_position(&view->pos); open_view(prev, view, OPEN_RELOAD); + } } } @@ -128,8 +128,8 @@ grep_open(struct view *view, enum open_flags flags) const char **argv = NULL; if (is_initial_view(view)) { - grep_argv = opt_cmdline_argv; - opt_cmdline_argv = NULL; + grep_argv = opt_cmdline_args; + opt_cmdline_args = NULL; } if (!argv_append_array(&argv, grep_args) || @@ -162,14 +162,16 @@ grep_request(struct view *view, enum request request, struct line *line) return REQ_NONE; if (file_view->parent == view && file_view->prev == view && state->last_file == grep->file && view_is_displayed(file_view)) { - if (grep_view_lineno(grep)) - select_view_line(file_view, grep_view_lineno(grep)); + if (*grep->text) { + select_view_line(file_view, grep->lineno); + update_view_title(file_view); + } } else { const char *file_argv[] = { repo.cdup, grep->file, NULL }; clear_position(&file_view->pos); - view->env->lineno = grep_view_lineno(grep); + view->env->lineno = grep->lineno; view->env->blob[0] = 0; open_argv(view, file_view, file_argv, repo.cdup, OPEN_SPLIT | OPEN_RELOAD); } @@ -179,12 +181,12 @@ grep_request(struct view *view, enum request request, struct line *line) case REQ_EDIT: if (!*grep->file) return request; - open_editor(grep->file, grep->lineno); + open_editor(grep->file, grep->lineno + 1); return REQ_NONE; case REQ_VIEW_BLAME: view->env->ref[0] = 0; - view->env->lineno = grep_view_lineno(grep); + view->env->lineno = grep->lineno; return request; default: @@ -204,6 +206,10 @@ grep_read(struct view *view, struct buffer *buf) if (!buf) { state->last_file = NULL; + if (!view->lines) { + view->ref[0] = 0; + report("No matches found"); + } return TRUE; } @@ -213,8 +219,14 @@ grep_read(struct view *view, struct buffer *buf) lineno = io_memchr(buf, buf->data, 0); text = io_memchr(buf, lineno, 0); + /* + * No data indicates binary file matches, e.g.: + * > git grep vertical- -- test + * test/graph/20-tig-all-long-test:● │ Add "auto" vertical-split + * Binary file test/graph/20-tig-all-long-test.in matches + */ if (!lineno || !text) - return FALSE; + return TRUE; textlen = strlen(text); @@ -232,6 +244,8 @@ grep_read(struct view *view, struct buffer *buf) grep->file = file; grep->lineno = atoi(lineno); + if (grep->lineno > 0) + grep->lineno -= 1; strncpy(grep->text, text, textlen); grep->text[textlen] = 0; view_column_info_update(view, line); diff --git a/src/keys.c b/src/keys.c index 1b77c5af7..e0bb6266f 100644 --- a/src/keys.c +++ b/src/keys.c @@ -16,7 +16,6 @@ #include "tig/argv.h" #include "tig/io.h" #include "tig/keys.h" -#include "tig/util.h" struct keybinding { enum request request; diff --git a/src/line.c b/src/line.c index 9e7319ad5..cc001ab3f 100644 --- a/src/line.c +++ b/src/line.c @@ -188,6 +188,13 @@ init_colors(void) int default_fg = rule->info.fg; enum line_type type; + /* XXX: Even if the terminal does not support colors (e.g. + * TERM=dumb) init_colors() must ensure that the built-in rules + * have been initialized. This is done by the above call to + * find_line_rule(). */ + if (!has_colors()) + return; + start_color(); if (assume_default_colors(default_fg, default_bg) == ERR) { diff --git a/src/log.c b/src/log.c index 79f247981..1606f876e 100644 --- a/src/log.c +++ b/src/log.c @@ -65,8 +65,8 @@ log_open(struct view *view, enum open_flags flags) { const char *log_argv[] = { "git", "log", encoding_arg, commit_order_arg(), "--cc", - "--stat", "%(cmdlineargs)", "%(revargs)", "--no-color", - "--", "%(fileargs)", NULL + "--stat", "%(logargs)", "%(cmdlineargs)", "%(revargs)", + "--no-color", "--", "%(fileargs)", NULL }; return begin_update(view, NULL, log_argv, flags); diff --git a/src/main.c b/src/main.c index 61e942a62..cc3d4d09b 100644 --- a/src/main.c +++ b/src/main.c @@ -166,6 +166,9 @@ main_check_argv(struct view *view, const char *argv[]) continue; } + if (!strcmp(arg, "--first-parent")) + state->first_parent = TRUE; + if (!argv_parse_rev_flag(arg, &rev_flags)) continue; @@ -181,33 +184,44 @@ main_check_argv(struct view *view, const char *argv[]) return with_reflog; } +static bool +main_with_graph(struct view *view, enum open_flags flags) +{ + struct view_column *column = get_view_column(view, VIEW_COLUMN_COMMIT_TITLE); + + if (open_in_pager_mode(flags)) + return FALSE; + + return column && column->opt.commit_title.graph && + opt_commit_order != COMMIT_ORDER_REVERSE; +} + static bool main_open(struct view *view, enum open_flags flags) { + bool with_graph = main_with_graph(view, flags); const char *pretty_custom_argv[] = { - GIT_MAIN_LOG_CUSTOM(encoding_arg, commit_order_arg(), "%(cmdlineargs)", "%(revargs)", "%(fileargs)") + GIT_MAIN_LOG_CUSTOM(encoding_arg, commit_order_arg_with_graph(with_graph), + "%(cmdlineargs)", "%(revargs)", "%(fileargs)") }; const char *pretty_raw_argv[] = { - GIT_MAIN_LOG_RAW(encoding_arg, commit_order_arg(), "%(cmdlineargs)", "%(revargs)", "%(fileargs)") + GIT_MAIN_LOG_RAW(encoding_arg, commit_order_arg_with_graph(with_graph), + "%(cmdlineargs)", "%(revargs)", "%(fileargs)") }; struct main_state *state = view->private; const char **main_argv = pretty_custom_argv; - struct view_column *column; enum watch_trigger changes_triggers = WATCH_NONE; if (opt_show_changes && repo.is_inside_work_tree) changes_triggers |= WATCH_INDEX; - column = get_view_column(view, VIEW_COLUMN_COMMIT_TITLE); - state->with_graph = column && column->opt.commit_title.graph && - opt_commit_order != COMMIT_ORDER_REVERSE; + state->with_graph = with_graph; - if (opt_rev_argv && main_check_argv(view, opt_rev_argv)) + if (opt_rev_args && main_check_argv(view, opt_rev_args)) main_argv = pretty_raw_argv; if (open_in_pager_mode(flags)) { changes_triggers = WATCH_NONE; - state->with_graph = FALSE; } /* This calls reset_view() so must be before adding changes commits. */ @@ -357,9 +371,21 @@ main_read(struct view *view, struct buffer *buf) } main_flush_commit(view, commit); - main_register_commit(view, &state->current, line, is_boundary); author = io_memchr(buf, line, 0); + + if (state->first_parent) { + char *parent = strchr(line, ' '); + char *parent_end = parent ? strchr(parent + 1, ' ') : NULL; + + if (parent_end) + *parent_end = 0; + + io_trace("[parent] %s\n", line); + } + + main_register_commit(view, &state->current, line, is_boundary); + if (author) { char *title = io_memchr(buf, author, 0); diff --git a/src/options.c b/src/options.c index baaf33578..a87a4a92d 100644 --- a/src/options.c +++ b/src/options.c @@ -128,6 +128,20 @@ commit_order_arg() return commit_order_arg_map[opt_commit_order].name; } +const char * +commit_order_arg_with_graph(bool with_graph) +{ + enum commit_order commit_order = opt_commit_order; + + if (with_graph && + commit_order != COMMIT_ORDER_TOPO && + commit_order != COMMIT_ORDER_DATE && + commit_order != COMMIT_ORDER_AUTHOR_DATE) + commit_order = COMMIT_ORDER_TOPO; + + return commit_order_arg_map[commit_order].name; +} + /* Use --show-notes to support Git >= 1.7.6 */ #define NOTES_ARG "--show-notes" #define NOTES_EQ_ARG NOTES_ARG "=" @@ -224,7 +238,12 @@ static const struct enum_map_entry attr_map[] = { enum status_code parse_step(double *opt, const char *arg) { - *opt = atoi(arg); + int value = atoi(arg); + + if (!value && !isdigit(*arg)) + return error("Invalid double or percentage"); + + *opt = value; if (!strchr(arg, '%')) return SUCCESS; @@ -259,10 +278,12 @@ set_color(int *color, const char *name) { if (map_enum(color, color_map, name)) return TRUE; + /* Git expects a plain int w/o prefix, however, color is + * the preferred Tig color notation. */ if (!prefixcmp(name, "color")) - return parse_int(color, name + 5, 0, 255) == SUCCESS; - /* Used when reading git colors. Git expects a plain int w/o prefix. */ - return parse_int(color, name, 0, 255) == SUCCESS; + name += 5; + return string_isnumber(name) && + parse_int(color, name, 0, 255) == SUCCESS; } #define is_quoted(c) ((c) == '"' || (c) == '\'') @@ -341,6 +362,7 @@ option_color_command(int argc, const char *argv[]) { "diff-tree", "'diff-tree '" }, { "filename", "file" }, { "help-keymap", "help.section" }, + { "main-revgraph", "" }, { "pp-adate", "'AuthorDate: '" }, { "pp-author", "'Author: '" }, { "pp-cdate", "'CommitDate: '" }, @@ -359,6 +381,8 @@ option_color_command(int argc, const char *argv[]) index = find_remapped(obsolete, ARRAY_SIZE(obsolete), rule.name); if (index != -1) { + if (!*obsolete[index][1]) + return error("%s is obsolete", argv[0]); /* Keep the initial prefix if defined. */ code = parse_color_name(obsolete[index][1], &rule, prefix ? NULL : &prefix); if (code != SUCCESS) @@ -402,18 +426,23 @@ parse_bool(bool *opt, const char *arg) } static enum status_code -parse_enum(unsigned int *opt, const char *arg, const struct enum_map *map) +parse_enum(const char *name, unsigned int *opt, const char *arg, + const struct enum_map *map) { bool is_true; + enum status_code code; assert(map->size > 1); if (map_enum_do(map->entries, map->size, (int *) opt, arg)) return SUCCESS; - parse_bool(&is_true, arg); + code = parse_bool(&is_true, arg); *opt = is_true ? map->entries[1].value : map->entries[0].value; - return SUCCESS; + if (code == SUCCESS) + return code; + return error("'%s' is not a valid value for %s; using %s", + arg, name, enum_name(map->entries[*opt].name)); } static enum status_code @@ -494,7 +523,7 @@ parse_option(struct option_info *option, const char *prefix, const char *arg) const char *type = option->type + STRING_SIZE("enum "); const struct enum_map *map = find_enum_map(type); - return parse_enum(option->value, arg, map); + return parse_enum(name, option->value, arg, map); } if (!strcmp(option->type, "int")) { @@ -663,10 +692,12 @@ option_bind_command(int argc, const char *argv[]) static const char *toggles[][2] = { { "diff-context-down", "diff-context" }, { "diff-context-up", "diff-context" }, + { "stage-next", ":/^@@" }, { "toggle-author", "author" }, { "toggle-changes", "show-changes" }, { "toggle-commit-order", "show-commit-order" }, { "toggle-date", "date" }, + { "toggle-files", "file-filter" }, { "toggle-file-filter", "file-filter" }, { "toggle-file-size", "file-size" }, { "toggle-filename", "filename" }, @@ -676,6 +707,7 @@ option_bind_command(int argc, const char *argv[]) { "toggle-lineno", "line-number" }, { "toggle-refs", "commit-title-refs" }, { "toggle-rev-graph", "commit-title-graph" }, + { "toggle-show-changes", "show-changes" }, { "toggle-sort-field", "sort-field" }, { "toggle-sort-order", "sort-order" }, { "toggle-title-overflow", "commit-title-overflow" }, @@ -698,12 +730,16 @@ option_bind_command(int argc, const char *argv[]) const char *action = toggles[alias][0]; const char *arg = prefixcmp(action, "diff-context-") ? NULL : (strstr(action, "-down") ? "-1" : "+1"); - const char *toggle[] = { ":toggle", toggles[alias][1], arg, NULL}; - enum status_code code = add_run_request(keymap, key, keys, toggle); + const char *mapped = toggles[alias][1]; + const char *toggle[] = { ":toggle", mapped, arg, NULL}; + const char *other[] = { mapped, NULL }; + const char **prompt = *mapped == ':' ? other : toggle; + enum status_code code = add_run_request(keymap, key, keys, prompt); if (code == SUCCESS) - code = error("%s has been replaced by `:toggle %s%s%s'", - action, toggles[alias][1], + code = error("%s has been replaced by `%s%s%s%s'", + action, prompt == other ? mapped : ":toggle ", + prompt == other ? "" : mapped, arg ? " " : "", arg ? arg : ""); return code; } @@ -854,7 +890,7 @@ load_options(void) if (!io_from_string(&io, builtin_config)) die("Failed to get built-in config"); - if (!io_load_span(&io, " \t", &config.lineno, read_option, &config) == ERR || config.errors == TRUE) + if (io_load_span(&io, " \t", &config.lineno, read_option, &config) == ERR || config.errors == TRUE) die("Error in built-in config"); } @@ -1053,6 +1089,10 @@ read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen else if (!strcmp(name, "diff.context")) { if (!find_option_info_by_value(&opt_diff_context)->seen) opt_diff_context = -atoi(value); + + } else if (!strcmp(name, "format.pretty")) { + if (!prefixcmp(value, "format:") && strstr(value, "%C(")) + argv_append(&opt_log_options, "--pretty=medium"); } return OK; diff --git a/src/parse.c b/src/parse.c index 524de1ace..eb1666d64 100644 --- a/src/parse.c +++ b/src/parse.c @@ -12,7 +12,6 @@ */ #include "tig/tig.h" -#include "tig/util.h" #include "tig/parse.h" size_t diff --git a/src/prompt.c b/src/prompt.c index 22af8da9b..0d32dca13 100644 --- a/src/prompt.c +++ b/src/prompt.c @@ -178,11 +178,7 @@ read_prompt_incremental(const char *prompt, bool edit_mode, input_handler handle static void readline_display(void) { - wmove(status_win, 0, 0); - waddstr(status_win, rl_display_prompt); - waddstr(status_win, rl_line_buffer); - wclrtoeol(status_win); - wmove(status_win, 0, strlen(rl_display_prompt) + rl_point); + update_status("%s%s", rl_display_prompt, rl_line_buffer); wrefresh(status_win); } @@ -233,6 +229,7 @@ readline_action_generator(const char *text, int state) "bind", "set", "toggle", + "save-display", #define REQ_GROUP(help) #define REQ_(req, help) #req REQ_INFO, @@ -359,7 +356,7 @@ readline_toggle_generator(const char *text, int state) static int readline_getc(FILE *stream) { - return getc(opt_tty); + return get_input_char(); } static char ** @@ -449,7 +446,13 @@ read_prompt(const char *prompt) } line = readline(prompt); - if (line && *line) + + if (line && !*line) { + free(line); + line = NULL; + } + + if (line) add_history(line); return line; @@ -538,12 +541,17 @@ struct prompt_toggle { void *opt; }; +static struct prompt_toggle option_toggles[] = { +#define DEFINE_OPTION_TOGGLES(name, type, flags) { #name, #type, flags, &opt_ ## name }, + OPTION_INFO(DEFINE_OPTION_TOGGLES) +}; + static bool find_arg(const char *argv[], const char *arg) { int i; - for (i = 0; argv[i]; i++) + for (i = 0; argv && argv[i]; i++) if (!strcmp(argv[i], arg)) return TRUE; return FALSE; @@ -625,6 +633,12 @@ prompt_toggle_option(struct view *view, const char *argv[], const char *prefix, bool found = TRUE; int i; + if (argv_size(argv) <= 2) { + argv_free(*opt); + (*opt)[0] = NULL; + return SUCCESS; + } + for (i = 2; argv[i]; i++) { if (!find_arg(*opt, argv[i])) { found = FALSE; @@ -689,10 +703,6 @@ find_prompt_toggle(struct prompt_toggle toggles[], size_t toggles_size, static enum status_code prompt_toggle(struct view *view, const char *argv[], enum view_flag *flags) { - struct prompt_toggle option_toggles[] = { -#define DEFINE_OPTION_TOGGLES(name, type, flags) { #name, #type, flags, &opt_ ## name }, - OPTION_INFO(DEFINE_OPTION_TOGGLES) - }; const char *option = argv[1]; size_t optionlen = option ? strlen(option) : 0; struct prompt_toggle *toggle; @@ -742,6 +752,25 @@ prompt_toggle(struct view *view, const char *argv[], enum view_flag *flags) return error("`:toggle %s` not supported", option); } +static void +prompt_update_display(enum view_flag flags) +{ + struct view *view; + int i; + + if (flags & VIEW_RESET_DISPLAY) { + resize_display(); + redraw_display(TRUE); + } + + foreach_displayed_view(view, i) { + if (view_has_flags(view, flags) && view_can_refresh(view)) + reload_view(view); + else + redraw_view(view); + } +} + enum request run_prompt_command(struct view *view, const char *argv[]) { @@ -755,7 +784,9 @@ run_prompt_command(struct view *view, const char *argv[]) if (string_isnumber(cmd)) { int lineno = view->pos.lineno + 1; - if (parse_int(&lineno, cmd, 1, view->lines + 1) == SUCCESS) { + if (parse_int(&lineno, cmd, 0, view->lines + 1) == SUCCESS) { + if (!lineno) + lineno = 1; select_view_line(view, lineno - 1); report_clear(); } else { @@ -821,34 +852,40 @@ run_prompt_command(struct view *view, const char *argv[]) open_pager_view(view, OPEN_PREPARED | OPEN_WITH_STDERR); } + } else if (!strcmp(cmd, "save-display")) { + const char *path = argv[1] ? argv[1] : "tig-display.txt"; + + if (!save_display(path)) + report("Failed to save screen to %s", path); + else + report("Saved screen to %s", path); + } else if (!strcmp(cmd, "toggle")) { enum view_flag flags = VIEW_NO_FLAGS; enum status_code code = prompt_toggle(view, argv, &flags); const char *action = get_status_message(code); - int i; if (code != SUCCESS) { report("%s", action); return REQ_NONE; } - if (flags & VIEW_RESET_DISPLAY) { - resize_display(); - redraw_display(TRUE); - } - - foreach_displayed_view(view, i) { - if (view_has_flags(view, flags) && view_can_refresh(view)) - reload_view(view); - else - redraw_view(view); - } + prompt_update_display(flags); if (*action) report("%s", action); + } else if (!strcmp(cmd, "script")) { + if (is_script_executing()) { + report("Scripts cannot be run from scripts"); + } else if (!open_script(argv[1])) { + report("Failed to open %s", argv[1]); + } + } else { struct key key = {}; + enum status_code code; + enum view_flag flags = VIEW_NO_FLAGS; /* Try : */ key.modifiers.multibytes = 1; @@ -862,14 +899,33 @@ run_prompt_command(struct view *view, const char *argv[]) if (request != REQ_UNKNOWN) return request; - if (set_option(argv[0], argv_size(argv + 1), &argv[1]) == SUCCESS) { + code = set_option(argv[0], argv_size(argv + 1), &argv[1]); + if (code != SUCCESS) { + report("%s", get_status_message(code)); + return REQ_NONE; + } + + if (!strcmp(cmd, "set")) { + struct prompt_toggle *toggle; + + toggle = find_prompt_toggle(option_toggles, ARRAY_SIZE(option_toggles), + "", argv[1], strlen(argv[1])); + + if (toggle) + flags = toggle->flags; + } + + if (flags) { + prompt_update_display(flags); + + } else { request = view_can_refresh(view) ? REQ_REFRESH : REQ_SCREEN_REDRAW; if (!strcmp(cmd, "color")) init_colors(); resize_display(); redraw_display(TRUE); } - return request; + } return REQ_NONE; } diff --git a/src/refdb.c b/src/refdb.c index d162b5079..8fd380506 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -13,7 +13,6 @@ #include "tig/tig.h" #include "tig/argv.h" -#include "tig/util.h" #include "tig/io.h" #include "tig/watch.h" #include "tig/options.h" @@ -299,7 +298,7 @@ ref_update_env(struct argv_env *env, const struct ref *ref, bool clear) string_copy_rev(env->commit, ref->id); if (ref_is_tag(ref)) { - string_copy_rev(env->tag, ref->name); + string_ncopy(env->tag, ref->name, strlen(ref->name)); } else if (ref_is_remote(ref)) { const char *sep = strchr(ref->name, '/'); @@ -307,10 +306,10 @@ ref_update_env(struct argv_env *env, const struct ref *ref, bool clear) if (!sep) return; string_ncopy(env->remote, ref->name, sep - ref->name); - string_copy_rev(env->branch, sep + 1); + string_ncopy(env->branch, sep + 1, strlen(sep + 1)); } else if (ref->type == REFERENCE_BRANCH) { - string_copy_rev(env->branch, ref->name); + string_ncopy(env->branch, ref->name, strlen(ref->name)); } } diff --git a/src/refs.c b/src/refs.c index b24712812..c30d36373 100644 --- a/src/refs.c +++ b/src/refs.c @@ -175,7 +175,7 @@ refs_select(struct view *view, struct line *line) } string_copy_rev(view->ref, reference->ref->id); string_copy_rev(view->env->head, reference->ref->id); - string_copy_rev(view->env->ref, reference->ref->name); + string_ncopy(view->env->ref, reference->ref->name, strlen(reference->ref->name)); ref_update_env(view->env, reference->ref, TRUE); } diff --git a/src/repo.c b/src/repo.c index 6ccf64d88..967c3b4d8 100644 --- a/src/repo.c +++ b/src/repo.c @@ -127,7 +127,8 @@ index_diff(struct index_diff *diff, bool untracked, bool count_all) while (io_get(&io, &buf, 0, TRUE) && (ok = buf.size > 3)) { if (buf.data[0] == '?') diff->untracked++; - else if (buf.data[0] != ' ') + /* Ignore staged but unmerged entries. */ + else if (buf.data[0] != ' ' && buf.data[0] != 'U') diff->staged++; if (buf.data[1] != ' ') diff->unstaged++; diff --git a/src/stage.c b/src/stage.c index 2a9050f82..bfdbbfb4e 100644 --- a/src/stage.c +++ b/src/stage.c @@ -475,6 +475,7 @@ stage_open(struct view *view, enum open_flags flags) }; static const char *file_argv[] = { repo.cdup, stage_status.new.name, NULL }; const char **argv = NULL; + struct stage_state *state = view->private; if (!stage_line_type) { report("No stage content, press %s to open the status view and choose file", @@ -517,6 +518,9 @@ stage_open(struct view *view, enum open_flags flags) return FALSE; } + if (stage_line_type != LINE_STAT_UNTRACKED) + diff_save_line(view, &state->diff, flags); + view->vid[0] = 0; view->dir = repo.cdup; return begin_update(view, NULL, NULL, flags); @@ -535,6 +539,9 @@ stage_read(struct view *view, struct buffer *buf) return TRUE; } + if (!buf) + diff_restore_line(view, &state->diff); + if (buf && diff_common_read(view, buf->data, &state->diff)) return TRUE; diff --git a/src/status.c b/src/status.c index 194e88d68..442f4648c 100644 --- a/src/status.c +++ b/src/status.c @@ -578,7 +578,7 @@ open_mergetool(const char *file) { const char *mergetool_argv[] = { "git", "mergetool", file, NULL }; - open_external_viewer(mergetool_argv, repo.cdup, TRUE, TRUE, ""); + open_external_viewer(mergetool_argv, repo.cdup, FALSE, TRUE, TRUE, ""); } static enum request diff --git a/src/tig.c b/src/tig.c index d003900db..f9771e12c 100644 --- a/src/tig.c +++ b/src/tig.c @@ -160,12 +160,9 @@ open_run_request(struct view *view, enum request request) } } - if (confirmed && argv_remove_quotes(argv)) { - if (req->flags.silent) - io_run_bg(argv); - else - open_external_viewer(argv, NULL, !req->flags.exit, FALSE, ""); - } + if (confirmed && argv_remove_quotes(argv)) + open_external_viewer(argv, NULL, req->flags.silent, + !req->flags.exit, FALSE, ""); } if (argv) @@ -215,6 +212,8 @@ view_driver(struct view *view, enum request request) case REQ_MOVE_DOWN: case REQ_MOVE_PAGE_UP: case REQ_MOVE_PAGE_DOWN: + case REQ_MOVE_HALF_PAGE_UP: + case REQ_MOVE_HALF_PAGE_DOWN: case REQ_MOVE_FIRST_LINE: case REQ_MOVE_LAST_LINE: move_view(view, request); @@ -294,7 +293,7 @@ view_driver(struct view *view, enum request request) case REQ_VIEW_NEXT: { int nviews = displayed_views(); - int next_view = (current_view + 1) % nviews; + int next_view = nviews ? (current_view + 1) % nviews : current_view; if (next_view == current_view) { report("Only one view is displayed"); @@ -397,6 +396,7 @@ static const char usage_string[] = " or: tig show [options] [revs] [--] [paths]\n" " or: tig blame [options] [rev] [--] path\n" " or: tig grep [options] [pattern]\n" +" or: tig refs\n" " or: tig stash\n" " or: tig status\n" " or: tig < [git command output]\n" @@ -443,11 +443,11 @@ filter_options(const char *argv[], bool rev_parse) update_options_from_argv(argv); if (!rev_parse) { - opt_cmdline_argv = argv; + opt_cmdline_args = argv; return; } - filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv); + filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv); filter_rev_parse(&flags, "--flags", "--no-revs", argv); if (flags) { @@ -455,17 +455,17 @@ filter_options(const char *argv[], bool rev_parse) const char *flag = flags[next]; if (argv_parse_rev_flag(flag, NULL)) - argv_append(&opt_rev_argv, flag); + argv_append(&opt_rev_args, flag); else flags[flags_pos++] = flag; } flags[flags_pos] = NULL; - opt_cmdline_argv = flags; + opt_cmdline_args = flags; } - filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv); + filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv); } static enum request @@ -503,6 +503,9 @@ parse_options(int argc, const char *argv[], bool pager_mode) } else if (!strcmp(subcommand, "stash")) { request = REQ_VIEW_STASH; + } else if (!strcmp(subcommand, "refs")) { + request = REQ_VIEW_REFS; + } else { subcommand = NULL; } @@ -548,16 +551,16 @@ open_pager_mode(enum request request) { if (request == REQ_VIEW_PAGER) { /* Detect if the user requested the main view. */ - if (argv_contains(opt_rev_argv, "--stdin")) { + if (argv_contains(opt_rev_args, "--stdin")) { open_main_view(NULL, OPEN_FORWARD_STDIN); - } else if (argv_contains(opt_cmdline_argv, "--pretty=raw")) { + } else if (argv_contains(opt_cmdline_args, "--pretty=raw")) { open_main_view(NULL, OPEN_STDIN); } else { open_pager_view(NULL, OPEN_STDIN); } } else if (request == REQ_VIEW_DIFF) { - if (argv_contains(opt_rev_argv, "--stdin")) + if (argv_contains(opt_rev_args, "--stdin")) open_diff_view(NULL, OPEN_FORWARD_STDIN); else open_diff_view(NULL, OPEN_STDIN); @@ -682,6 +685,12 @@ main(int argc, const char *argv[]) if (pager_mode) request = open_pager_mode(request); + if (getenv("TIG_SCRIPT")) { + const char *script_command[] = { "script", getenv("TIG_SCRIPT"), NULL }; + + run_prompt_command(NULL, script_command); + } + while (view_driver(display[current_view], request)) { struct key key; int key_value = get_input(0, &key, TRUE); diff --git a/src/view.c b/src/view.c index 64af9ab26..a0ad792bf 100644 --- a/src/view.c +++ b/src/view.c @@ -190,6 +190,16 @@ move_view(struct view *view, enum request request) ? view->lines - view->pos.lineno - 1 : view->height; break; + case REQ_MOVE_HALF_PAGE_UP: + steps = view->height / 2 > view->pos.lineno + ? -view->pos.lineno : -(view->height / 2); + break; + + case REQ_MOVE_HALF_PAGE_DOWN: + steps = view->pos.lineno + view->height / 2 >= view->lines + ? view->lines - view->pos.lineno - 1 : view->height / 2; + break; + case REQ_MOVE_UP: case REQ_PREVIOUS: steps = -1; @@ -483,6 +493,9 @@ reset_view(struct view *view) reset_matches(view); view->prev_pos = view->pos; + /* A view without a previous view is the first view */ + if (!view->prev && !view->lines && view->prev_pos.lineno == 0) + view->prev_pos.lineno = view->env->lineno; clear_position(&view->pos); if (view->columns) @@ -498,12 +511,6 @@ reset_view(struct view *view) static bool restore_view_position(struct view *view) { - /* A view without a previous view is the first view */ - if (!view->prev && view->env->lineno && view->env->lineno <= view->lines) { - select_view_line(view, view->env->lineno); - view->env->lineno = 0; - } - /* Ensure that the view position is in a valid state. */ if (!check_position(&view->prev_pos) || (view->pipe && view->lines <= view->prev_pos.lineno)) @@ -564,9 +571,9 @@ begin_update(struct view *view, const char *dir, const char **argv, enum open_fl { bool extra = !!(flags & (OPEN_EXTRA)); bool refresh = flags & (OPEN_REFRESH | OPEN_PREPARED | OPEN_STDIN); - int forward_stdin = (flags & OPEN_FORWARD_STDIN) ? IO_RD_FORWARD_STDIN : 0; - int with_stderr = (flags & OPEN_WITH_STDERR) ? IO_RD_WITH_STDERR : 0; - int io_flags = forward_stdin | with_stderr; + enum io_flags forward_stdin = (flags & OPEN_FORWARD_STDIN) ? IO_RD_FORWARD_STDIN : 0; + enum io_flags with_stderr = (flags & OPEN_WITH_STDERR) ? IO_RD_WITH_STDERR : 0; + enum io_flags io_flags = forward_stdin | with_stderr; if (view_no_refresh(view, flags)) return TRUE; @@ -814,7 +821,8 @@ load_view(struct view *view, struct view *prev, enum open_flags flags) /* Clear the old view and let the incremental updating refill * the screen. */ werase(view->win); - if (!(flags & (OPEN_RELOAD | OPEN_REFRESH))) + /* Do not clear the position if it is the first view. */ + if (view->prev && !(flags & (OPEN_RELOAD | OPEN_REFRESH))) clear_position(&view->prev_pos); report_clear(); } else if (view_is_displayed(view)) { @@ -869,76 +877,109 @@ open_argv(struct view *prev, struct view *view, const char *argv[], const char * static struct view *sorting_view; -#define sort_order_reverse(state, result) \ - ((state)->reverse ? -(result) : (result)) - -#define sort_order(state, cmp, o1, o2) \ - sort_order_reverse(state, (!(o1) || !(o2)) ? !!(o2) - !!(o1) : cmp(o1, o2)) +#define apply_comparator(cmp, o1, o2) \ + (!(o1) || !(o2)) ? !!(o2) - !!(o1) : cmp(o1, o2) #define number_compare(size1, size2) (*(size1) - *(size2)) #define mode_is_dir(mode) ((mode) && S_ISDIR(*(mode))) static int -sort_view_compare(const void *l1, const void *l2) +compare_view_column(enum view_column_type column, bool use_file_mode, + const struct line *line1, struct view_column_data *column_data1, + const struct line *line2, struct view_column_data *column_data2) { - const struct line *line1 = l1; - const struct line *line2 = l2; - struct view_column_data column_data1 = {}; - struct view_column_data column_data2 = {}; - struct sort_state *sort = &sorting_view->sort; - - if (!sorting_view->ops->get_column_data(sorting_view, line1, &column_data1)) - return -1; - else if (!sorting_view->ops->get_column_data(sorting_view, line2, &column_data2)) - return 1; - - switch (get_sort_field(sorting_view)) { + switch (column) { case VIEW_COLUMN_AUTHOR: - return sort_order(sort, ident_compare, column_data1.author, column_data2.author); + return apply_comparator(ident_compare, column_data1->author, column_data2->author); case VIEW_COLUMN_DATE: - return sort_order(sort, timecmp, column_data1.date, column_data2.date); + return apply_comparator(timecmp, column_data1->date, column_data2->date); case VIEW_COLUMN_ID: - if (column_data1.reflog && column_data2.reflog) - return sort_order(sort, strcmp, column_data1.reflog, column_data2.reflog); - return sort_order(sort, strcmp, column_data1.id, column_data2.id); + if (column_data1->reflog && column_data2->reflog) + return apply_comparator(strcmp, column_data1->reflog, column_data2->reflog); + return apply_comparator(strcmp, column_data1->id, column_data2->id); case VIEW_COLUMN_FILE_NAME: - if (mode_is_dir(column_data1.mode) != mode_is_dir(column_data2.mode)) - return sort_order_reverse(sort, mode_is_dir(column_data1.mode) ? -1 : 1); - return sort_order(sort, strcmp, column_data1.file_name, column_data2.file_name); + if (use_file_mode && mode_is_dir(column_data1->mode) != mode_is_dir(column_data2->mode)) + return mode_is_dir(column_data1->mode) ? -1 : 1; + return apply_comparator(strcmp, column_data1->file_name, column_data2->file_name); case VIEW_COLUMN_FILE_SIZE: - return sort_order(sort, number_compare, column_data1.file_size, column_data2.file_size); + return apply_comparator(number_compare, column_data1->file_size, column_data2->file_size); case VIEW_COLUMN_LINE_NUMBER: - return sort_order_reverse(sort, line1->lineno - line2->lineno); + return line1->lineno - line2->lineno; case VIEW_COLUMN_MODE: - return sort_order(sort, number_compare, column_data1.mode, column_data2.mode); + return apply_comparator(number_compare, column_data1->mode, column_data2->mode); case VIEW_COLUMN_REF: - return sort_order(sort, ref_compare, column_data1.ref, column_data2.ref); + return apply_comparator(ref_compare, column_data1->ref, column_data2->ref); case VIEW_COLUMN_COMMIT_TITLE: - return sort_order(sort, strcmp, column_data1.commit_title, column_data2.commit_title); + return apply_comparator(strcmp, column_data1->commit_title, column_data2->commit_title); case VIEW_COLUMN_SECTION: - return sort_order(sort, strcmp, column_data1.section->opt.section.text, - column_data2.section->opt.section.text); + return apply_comparator(strcmp, column_data1->section->opt.section.text, + column_data2->section->opt.section.text); case VIEW_COLUMN_STATUS: - return sort_order(sort, number_compare, column_data1.status, column_data2.status); + return apply_comparator(number_compare, column_data1->status, column_data2->status); case VIEW_COLUMN_TEXT: - return sort_order(sort, strcmp, column_data1.text, column_data2.text); + return apply_comparator(strcmp, column_data1->text, column_data2->text); } return 0; } +static enum view_column_type view_column_order[] = { + VIEW_COLUMN_FILE_NAME, + VIEW_COLUMN_STATUS, + VIEW_COLUMN_MODE, + VIEW_COLUMN_FILE_SIZE, + VIEW_COLUMN_DATE, + VIEW_COLUMN_AUTHOR, + VIEW_COLUMN_COMMIT_TITLE, + VIEW_COLUMN_LINE_NUMBER, + VIEW_COLUMN_SECTION, + VIEW_COLUMN_TEXT, + VIEW_COLUMN_REF, + VIEW_COLUMN_ID, +}; + +static int +sort_view_compare(const void *l1, const void *l2) +{ + const struct line *line1 = l1; + const struct line *line2 = l2; + struct view_column_data column_data1 = {}; + struct view_column_data column_data2 = {}; + struct sort_state *sort = &sorting_view->sort; + enum view_column_type column = get_sort_field(sorting_view); + int cmp; + int i; + + if (!sorting_view->ops->get_column_data(sorting_view, line1, &column_data1)) + return -1; + else if (!sorting_view->ops->get_column_data(sorting_view, line2, &column_data2)) + return 1; + + cmp = compare_view_column(column, TRUE, line1, &column_data1, line2, &column_data2); + + /* Ensure stable sorting by ordering ordering by the other + * columns if the selected column values are equal. */ + for (i = 0; !cmp && i < ARRAY_SIZE(view_column_order); i++) + if (column != view_column_order[i]) + cmp = compare_view_column(view_column_order[i], FALSE, + line1, &column_data1, + line2, &column_data2); + + return sort->reverse ? -cmp : cmp; +} + void sort_view(struct view *view, bool change_field) { diff --git a/src/watch.c b/src/watch.c index 751ee1441..5a55f0530 100644 --- a/src/watch.c +++ b/src/watch.c @@ -106,7 +106,7 @@ watch_index_handler(struct watch_handler *handler, enum watch_event event, enum enum watch_trigger changed = WATCH_NONE; struct index_diff diff; - if (event == WATCH_EVENT_AFTER_EXTERNAL) + if (event == WATCH_EVENT_AFTER_COMMAND) return check_file_mtime(&handler->last_modified, "%s/index", repo.git_dir) ? check : WATCH_NONE; @@ -139,7 +139,7 @@ static enum watch_trigger watch_refs_handler(struct watch_handler *handler, enum watch_event event, enum watch_trigger check) { - if (event == WATCH_EVENT_AFTER_EXTERNAL) + if (event == WATCH_EVENT_AFTER_COMMAND) load_refs(TRUE); return WATCH_NONE; @@ -157,7 +157,7 @@ watch_no_refresh(enum watch_event event) { return opt_refresh_mode == REFRESH_MODE_MANUAL || (opt_refresh_mode == REFRESH_MODE_AFTER_COMMAND && - event != WATCH_EVENT_AFTER_EXTERNAL); + event != WATCH_EVENT_AFTER_COMMAND); } static void @@ -172,31 +172,48 @@ watch_apply_changes(struct watch *source, enum watch_event event, for (watch = watches; watch; watch = watch->next) { enum watch_trigger triggers = changed & watch->triggers; - if (source == watch) + if (source == watch) { source->state |= triggers; - else if (triggers) - watch->changed |= triggers; + continue; + } + + if (event == WATCH_EVENT_AFTER_COMMAND) { + watch->state = WATCH_NONE; + triggers = watch->triggers; + } + + watch->changed |= triggers; } } void watch_apply(struct watch *source, enum watch_trigger changed) { - return watch_apply_changes(source, WATCH_EVENT_LOAD, changed); + watch_apply_changes(source, WATCH_EVENT_LOAD, changed); } static enum watch_trigger watch_update_event(enum watch_event event, enum watch_trigger trigger, enum watch_trigger changed) { + time_t timestamp = 0; int i; if (watch_no_refresh(event)) return WATCH_NONE; - for (i = 0; trigger && i < ARRAY_SIZE(watch_handlers); i++) { + if (event == WATCH_EVENT_AFTER_COMMAND) + timestamp = time(NULL); + + for (i = 0; i < ARRAY_SIZE(watch_handlers); i++) { struct watch_handler *handler = &watch_handlers[i]; + if (event == WATCH_EVENT_AFTER_COMMAND) { + changed = handler->triggers; + handler->last_modified = timestamp; + continue; + } + if (*repo.git_dir && (trigger & handler->triggers) && (changed | handler->triggers) != changed) diff --git a/test/README.adoc b/test/README.adoc new file mode 100644 index 000000000..b45404b00 --- /dev/null +++ b/test/README.adoc @@ -0,0 +1,39 @@ +Test Overview +============= + +All tests can be run with `make test`. This will run all scripts that +end with `-test` in the `test` folder and summarize the test results +using the script `test/tools/show-results.sh`. + +To run individual tests, use `make ` e.g. `make +test/tigrc/parse-test`. Alternatively, tests can be run directly via the +test scripts as long as `PATH` is set to include the directories `src/` +and `test/tools`. The latter directory is where the test helper +libraries are located, the most important of which is `libtest.sh`. + +Options +------- + +Tests can be configured by setting the `TEST_OPTS` environment variable. +The variable should contain a space-separated list of options. The +following options are supported: + +verbose:: + + Whether to print individual test results even when all + assertions passed. The default is to not results for passed + tests. + +no-indent:: + + Do not indent test output. This is automatically set depending + on whether `V=1` was passed to `make` to show verbose output. + +debugger=:: + + Invoke tig via a debugger, for example `debugger=lldb`. Remember + to recompile using `make clean all-debug` to expose all symbols. + +trace:: + + Show trace information. diff --git a/test/blame/default-test b/test/blame/default-test new file mode 100755 index 000000000..2823baddc --- /dev/null +++ b/test/blame/default-test @@ -0,0 +1,155 @@ +#!/bin/sh + +. libtest.sh +. libgit.sh + +tigrc < +AuthorDate: Tue Oct 29 18:46:52 2013 +0100 +Commit: Sébastien Doeraene +CommitDate: Tue Oct 29 18:46:52 2013 +0100 + + Update for new groupId and package structure of Scala.js. +--- + project/Build.scala | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/project/Build.scala b/project/Build.scala +index 560bca1..1713681 100644 +--- a/project/Build.scala ++++ b/project/Build.scala +@@ -1,7 +1,7 @@ + import sbt._ + import Keys._ +[diff] 74537d9b257954056d3caa19eb3837500aded883 - line 1 of 24 75% +EOF + +assert_equals 'blame-with-diff-no-file-filter.screen' < +AuthorDate: Tue Oct 29 18:46:52 2013 +0100 +Commit: Sébastien Doeraene +CommitDate: Tue Oct 29 18:46:52 2013 +0100 + + Update for new groupId and package structure of Scala.js. +--- + project/Build.scala | 2 +- + project/build.sbt | 2 +- + tracer/App.scala | 4 ++-- + tracer/Engine.scala | 2 +- + tracer/JSTypes.scala | 2 +- + 5 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/project/Build.scala b/project/Build.scala +index 560bca1..1713681 100644 +--- a/project/Build.scala +[diff] 74537d9b257954056d3caa19eb3837500aded883 - line 1 of 76 23% +EOF + +assert_equals 'blame-parent-of-74537d9.screen' <&1 | grep -q "tig: Error in built-in config" -if [ $? == 0 ] -then - echo "not ok - Errors reported in built-in config" - result=$(($result+1)) -else - echo "ok - Built-in config loaded" -fi - -exit $result diff --git a/test/diff/diff-context-test b/test/diff/diff-context-test new file mode 100755 index 000000000..e1ca216ae --- /dev/null +++ b/test/diff/diff-context-test @@ -0,0 +1,213 @@ +#!/bin/sh + +. libtest.sh +. libgit.sh + +steps ' + :save-display diff-default.screen + + :21 + ] + :save-display diff-u4.screen + + ] + :save-display diff-u5.screen + + :toggle diff-context +5 + :save-display diff-u10.screen + + [ + [ + :save-display diff-u8.screen + + :0 + :set diff-context = 3 + :view-main + :view-diff + :save-display diff-default-from-main.screen + + :21 + ] + :save-display diff-u4-from-main.screen + + ] + :save-display diff-u5-from-main.screen + + :toggle diff-context +5 + :save-display diff-u10-from-main.screen + + [ + [ + :save-display diff-u8-from-main.screen +' + +in_work_dir create_repo_from_tgz "$base_dir/files/scala-js-benchmarks.tgz" + +test_tig show master^ + +assert_equals 'diff-default.screen' < +AuthorDate: Sat Mar 1 15:59:02 2014 -0500 +Commit: Jonas Fonseca +CommitDate: Sat Mar 1 15:59:02 2014 -0500 + + Add type parameter for js.Dynamic +--- + common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo +index 65f914a..3aa4320 100644 +--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala ++++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala +@@ -15,7 +15,7 @@ object Benchmark { + val benchmarks = js.Array[Benchmark]() + val benchmarkApps = js.Array[BenchmarkApp]() + +- val global = js.Dynamic.global.asInstanceOf[js.Dictionary] ++ val global = js.Dynamic.global.asInstanceOf[js.Dictionary[js.Any]] + global("runScalaJSBenchmarks") = runBenchmarks _ + global("initScalaJSBenchmarkApps") = initBenchmarkApps _ + + + + + +[diff] a1dcf1aaa11470978db1d5d8bcf9e16201eb70ff - line 1 of 24 100% +EOF + +assert_equals 'diff-u4.screen' < +AuthorDate: Sat Mar 1 15:59:02 2014 -0500 +Commit: Jonas Fonseca +CommitDate: Sat Mar 1 15:59:02 2014 -0500 + + Add type parameter for js.Dynamic +--- + common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo +index 65f914a..3aa4320 100644 +--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala ++++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala +@@ -14,9 +14,9 @@ import scala.scalajs.js + object Benchmark { + val benchmarks = js.Array[Benchmark]() + val benchmarkApps = js.Array[BenchmarkApp]() + +- val global = js.Dynamic.global.asInstanceOf[js.Dictionary] ++ val global = js.Dynamic.global.asInstanceOf[js.Dictionary[js.Any]] + global("runScalaJSBenchmarks") = runBenchmarks _ + global("initScalaJSBenchmarkApps") = initBenchmarkApps _ + + def add(benchmark: Benchmark) { + + +[diff] Changes to 'common/src/main/scala/org/scalajs/benchmark/Benchmark.sca100% +EOF + +assert_equals 'diff-u5.screen' < +AuthorDate: Sat Mar 1 15:59:02 2014 -0500 +Commit: Jonas Fonseca +CommitDate: Sat Mar 1 15:59:02 2014 -0500 + + Add type parameter for js.Dynamic +--- + common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo +index 65f914a..3aa4320 100644 +--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala ++++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala +@@ -13,11 +13,11 @@ import scala.scalajs.js + + object Benchmark { + val benchmarks = js.Array[Benchmark]() + val benchmarkApps = js.Array[BenchmarkApp]() + +- val global = js.Dynamic.global.asInstanceOf[js.Dictionary] ++ val global = js.Dynamic.global.asInstanceOf[js.Dictionary[js.Any]] + global("runScalaJSBenchmarks") = runBenchmarks _ + global("initScalaJSBenchmarkApps") = initBenchmarkApps _ + + def add(benchmark: Benchmark) { + benchmarks.push(benchmark) + +[diff] Changes to 'common/src/main/scala/org/scalajs/benchmark/Benchmark.sca100% +EOF + +assert_equals 'diff-u10.screen' < 1381189194 -0400 committer Jonas Fonseca 1381189194 -0400 Fix and improve inter-document linking - +EOF + +assert_equals stdout <