├── README.md ├── demo.gif ├── lua └── git_fastfix │ └── init.lua └── spec ├── demo.git ├── COMMIT_EDITMSG ├── HEAD ├── config ├── description ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── fsmonitor-watchman.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ ├── pre-receive.sample │ ├── prepare-commit-msg.sample │ └── update.sample ├── index ├── info │ └── exclude ├── logs │ ├── HEAD │ └── refs │ │ └── heads │ │ └── master ├── objects │ ├── 23 │ │ └── 4496b1caf2c7682b8441f9b866a7e2420d9748 │ ├── 73 │ │ └── 3ac0e7f136f3ad803c1f26778b67543b2c310f │ ├── 9c │ │ └── 59e24b8393179a5d712de4f990178df5734d99 │ ├── c9 │ │ └── a08ee6d6a92881d20b0f5aebec4066e4d99987 │ ├── cb │ │ └── 66df7d1ee3d0af286ca70340ea9c85d90b913b │ ├── cd │ │ └── 7e3cfe5de93688525b197954f266a2d13e8e31 │ ├── e0 │ │ └── 19be006cf33489e2d0177a3837a2384eddebc5 │ ├── f3 │ │ └── f7b0af0ed7cc50ee09ec28be7e9dbd32041664 │ └── f5 │ │ └── 78d53291854de1ec19903109bc9d32fbe2b5c0 └── refs │ └── heads │ └── master ├── demo ├── first_commit.txt ├── second_commit.txt └── third_commit.txt └── git_fastfix_spec.lua /README.md: -------------------------------------------------------------------------------- 1 | # Git FastFix 2 | 3 | This is a Neovim plugin for applying "fast git fixups"(using UI) to the current development branch. 4 | 5 | ![demo](demo.gif) 6 | 7 | Under the hood `git commit --fixup` and `git rebase -i --autosquash --autostash` are used, see git manual for the more information. 8 | ## Installation 9 | 10 | Add plugin to the runtime path 11 | 12 | ### Vim-Plug 13 | 14 | ```VimL 15 | Plug 'dm1try/git_fastfix' 16 | 17 | ``` 18 | 19 | load lua module and map `git_fastfix.open()` 20 | ```VimL 21 | lua git_fastfix = require('git_fastfix') 22 | nn gf :lua git_fastfix.open() 23 | ``` 24 | 25 | ## Usage 26 | 27 | Open `git_fastfix` wizard window(`:lua git_fastfix.open()`). 28 | Chooze git hunks for the patch. 29 | 30 | To fixup and rebase the changes press `` in the window with git logs. 31 | To fixup only use ``. 32 | 33 | ## Development 34 | 35 | Install neovim from [source](https://github.com/neovim/neovim#install-from-source). 36 | Run the tests from neovim source directory. Example: 37 | ``` 38 | cd ~/projects/neovim 39 | TEST_FILE=~/projects/git_fastfix/spec/git_fastfix_spec.lua make functionaltest 40 | ``` 41 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dm1try/git_fastfix/2c00dc8633a2f51e56594ff317e621174c977f95/demo.gif -------------------------------------------------------------------------------- /lua/git_fastfix/init.lua: -------------------------------------------------------------------------------- 1 | local nvim_win_set_config = vim.api.nvim_win_set_config 2 | local nvim_open_win = vim.api.nvim_open_win 3 | local nvim_win_set_buf = vim.api.nvim_win_set_buf 4 | local nvim_create_buf = vim.api.nvim_create_buf 5 | local nvim_call_function = vim.api.nvim_call_function 6 | local nvim_set_current_win = vim.api.nvim_set_current_win 7 | local nvim_command = vim.api.nvim_command 8 | local nvim_buf_get_name = vim.api.nvim_buf_get_name 9 | local nvim_buf_set_keymap = vim.api.nvim_buf_set_keymap 10 | local nvim_buf_set_lines = vim.api.nvim_buf_set_lines 11 | local nvim_get_current_line = vim.api.nvim_get_current_line 12 | local split = vim.split 13 | 14 | local function nvim_shell_call(command) 15 | return nvim_call_function('system', { command }) 16 | end 17 | 18 | local function nvim_term_open(command) 19 | return nvim_call_function('termopen', { command }) 20 | end 21 | 22 | function _fixup_to_commit(do_rebase) 23 | local current_line = nvim_get_current_line() 24 | local commit_hash = split(current_line, ' ')[1] 25 | 26 | nvim_shell_call('git commit --fixup ' .. commit_hash) 27 | local notification = 'Fixup applied to ' .. current_line 28 | 29 | if do_rebase then 30 | nvim_shell_call('GIT_EDITOR=true git rebase -i --autosquash --autostash ' .. commit_hash .. '^') 31 | notification = 'Patch applied to ' .. current_line 32 | end 33 | 34 | nvim_command('echo ' .. string.format("%q", notification)) 35 | nvim_command("close") 36 | end 37 | 38 | function _show_git_log() 39 | local buf = nvim_create_buf(false, true) 40 | local git_log_output = nvim_shell_call('git log -n 30 --oneline') 41 | local git_logs = split(git_log_output, "\n") 42 | 43 | nvim_buf_set_lines(buf, 0, -1, true, git_logs) 44 | nvim_win_set_buf(0, buf) 45 | nvim_buf_set_keymap(buf, 'n', '' ,":lua if type(git_fastfix) == 'table' then git_fastfix._fixup_to_commit(true) else _fixup_to_commit(true) end ",{}) 46 | nvim_buf_set_keymap(buf, 'n', '' ,":lua if type(git_fastfix) == 'table' then git_fastfix._fixup_to_commit(false) else _fixup_to_commit(false) end ",{}) 47 | end 48 | 49 | local function open() 50 | local source_code_filename = nvim_buf_get_name(0) 51 | 52 | local buf = nvim_create_buf(false, true) 53 | local opts = { 54 | relative='cursor', 55 | width=80, height=20, 56 | col=0, row=1, anchor='NW', style='minimal' 57 | } 58 | local win = nvim_open_win(buf, 0, opts) 59 | nvim_set_current_win(win) 60 | nvim_term_open('git add --patch ' .. source_code_filename) 61 | nvim_command('startinsert') 62 | nvim_command(":autocmd TermClose :lua if type(git_fastfix) == 'table' then git_fastfix._show_git_log() else _show_git_log() end") 63 | end 64 | 65 | function OpenGitFastFixWindow() 66 | nvim_command('echom "using OpenGitFastFixWindow() was deprecated, use git_fastfix.open(); See README for more info"') 67 | open() 68 | end 69 | 70 | return { 71 | open = open, 72 | _show_git_log = _show_git_log, 73 | _fixup_to_commit = _fixup_to_commit 74 | } 75 | -------------------------------------------------------------------------------- /spec/demo.git/COMMIT_EDITMSG: -------------------------------------------------------------------------------- 1 | third commit 2 | -------------------------------------------------------------------------------- /spec/demo.git/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /spec/demo.git/config: -------------------------------------------------------------------------------- 1 | [core] 2 | repositoryformatversion = 0 3 | filemode = true 4 | bare = false 5 | logallrefupdates = true 6 | ignorecase = true 7 | precomposeunicode = true 8 | -------------------------------------------------------------------------------- /spec/demo.git/description: -------------------------------------------------------------------------------- 1 | Unnamed repository; edit this file 'description' to name the repository. 2 | -------------------------------------------------------------------------------- /spec/demo.git/hooks/applypatch-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message taken by 4 | # applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. The hook is 8 | # allowed to edit the commit message file. 9 | # 10 | # To enable this hook, rename this file to "applypatch-msg". 11 | 12 | . git-sh-setup 13 | commitmsg="$(git rev-parse --git-path hooks/commit-msg)" 14 | test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} 15 | : 16 | -------------------------------------------------------------------------------- /spec/demo.git/hooks/commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message. 4 | # Called by "git commit" with one argument, the name of the file 5 | # that has the commit message. The hook should exit with non-zero 6 | # status after issuing an appropriate message if it wants to stop the 7 | # commit. The hook is allowed to edit the commit message file. 8 | # 9 | # To enable this hook, rename this file to "commit-msg". 10 | 11 | # Uncomment the below to add a Signed-off-by line to the message. 12 | # Doing this in a hook is a bad idea in general, but the prepare-commit-msg 13 | # hook is more suited to it. 14 | # 15 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 16 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 17 | 18 | # This example catches duplicate Signed-off-by lines. 19 | 20 | test "" = "$(grep '^Signed-off-by: ' "$1" | 21 | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { 22 | echo >&2 Duplicate Signed-off-by lines. 23 | exit 1 24 | } 25 | -------------------------------------------------------------------------------- /spec/demo.git/hooks/fsmonitor-watchman.sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use IPC::Open2; 6 | 7 | # An example hook script to integrate Watchman 8 | # (https://facebook.github.io/watchman/) with git to speed up detecting 9 | # new and modified files. 10 | # 11 | # The hook is passed a version (currently 1) and a time in nanoseconds 12 | # formatted as a string and outputs to stdout all files that have been 13 | # modified since the given time. Paths must be relative to the root of 14 | # the working tree and separated by a single NUL. 15 | # 16 | # To enable this hook, rename this file to "query-watchman" and set 17 | # 'git config core.fsmonitor .git/hooks/query-watchman' 18 | # 19 | my ($version, $time) = @ARGV; 20 | 21 | # Check the hook interface version 22 | 23 | if ($version == 1) { 24 | # convert nanoseconds to seconds 25 | $time = int $time / 1000000000; 26 | } else { 27 | die "Unsupported query-fsmonitor hook version '$version'.\n" . 28 | "Falling back to scanning...\n"; 29 | } 30 | 31 | my $git_work_tree; 32 | if ($^O =~ 'msys' || $^O =~ 'cygwin') { 33 | $git_work_tree = Win32::GetCwd(); 34 | $git_work_tree =~ tr/\\/\//; 35 | } else { 36 | require Cwd; 37 | $git_work_tree = Cwd::cwd(); 38 | } 39 | 40 | my $retry = 1; 41 | 42 | launch_watchman(); 43 | 44 | sub launch_watchman { 45 | 46 | my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') 47 | or die "open2() failed: $!\n" . 48 | "Falling back to scanning...\n"; 49 | 50 | # In the query expression below we're asking for names of files that 51 | # changed since $time but were not transient (ie created after 52 | # $time but no longer exist). 53 | # 54 | # To accomplish this, we're using the "since" generator to use the 55 | # recency index to select candidate nodes and "fields" to limit the 56 | # output to file names only. Then we're using the "expression" term to 57 | # further constrain the results. 58 | # 59 | # The category of transient files that we want to ignore will have a 60 | # creation clock (cclock) newer than $time_t value and will also not 61 | # currently exist. 62 | 63 | my $query = <<" END"; 64 | ["query", "$git_work_tree", { 65 | "since": $time, 66 | "fields": ["name"], 67 | "expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]] 68 | }] 69 | END 70 | 71 | print CHLD_IN $query; 72 | close CHLD_IN; 73 | my $response = do {local $/; }; 74 | 75 | die "Watchman: command returned no output.\n" . 76 | "Falling back to scanning...\n" if $response eq ""; 77 | die "Watchman: command returned invalid output: $response\n" . 78 | "Falling back to scanning...\n" unless $response =~ /^\{/; 79 | 80 | my $json_pkg; 81 | eval { 82 | require JSON::XS; 83 | $json_pkg = "JSON::XS"; 84 | 1; 85 | } or do { 86 | require JSON::PP; 87 | $json_pkg = "JSON::PP"; 88 | }; 89 | 90 | my $o = $json_pkg->new->utf8->decode($response); 91 | 92 | if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) { 93 | print STDERR "Adding '$git_work_tree' to watchman's watch list.\n"; 94 | $retry--; 95 | qx/watchman watch "$git_work_tree"/; 96 | die "Failed to make watchman watch '$git_work_tree'.\n" . 97 | "Falling back to scanning...\n" if $? != 0; 98 | 99 | # Watchman will always return all files on the first query so 100 | # return the fast "everything is dirty" flag to git and do the 101 | # Watchman query just to get it over with now so we won't pay 102 | # the cost in git to look up each individual file. 103 | print "/\0"; 104 | eval { launch_watchman() }; 105 | exit 0; 106 | } 107 | 108 | die "Watchman: $o->{error}.\n" . 109 | "Falling back to scanning...\n" if $o->{error}; 110 | 111 | binmode STDOUT, ":utf8"; 112 | local $, = "\0"; 113 | print @{$o->{files}}; 114 | } 115 | -------------------------------------------------------------------------------- /spec/demo.git/hooks/post-update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare a packed repository for use over 4 | # dumb transports. 5 | # 6 | # To enable this hook, rename this file to "post-update". 7 | 8 | exec git update-server-info 9 | -------------------------------------------------------------------------------- /spec/demo.git/hooks/pre-applypatch.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed 4 | # by applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. 8 | # 9 | # To enable this hook, rename this file to "pre-applypatch". 10 | 11 | . git-sh-setup 12 | precommit="$(git rev-parse --git-path hooks/pre-commit)" 13 | test -x "$precommit" && exec "$precommit" ${1+"$@"} 14 | : 15 | -------------------------------------------------------------------------------- /spec/demo.git/hooks/pre-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | if git rev-parse --verify HEAD >/dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=$(git hash-object -t tree /dev/null) 16 | fi 17 | 18 | # If you want to allow non-ASCII filenames set this variable to true. 19 | allownonascii=$(git config --bool hooks.allownonascii) 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | # Cross platform projects tend to avoid non-ASCII filenames; prevent 25 | # them from being added to the repository. We exploit the fact that the 26 | # printable range starts at the space character and ends with tilde. 27 | if [ "$allownonascii" != "true" ] && 28 | # Note that the use of brackets around a tr range is ok here, (it's 29 | # even required, for portability to Solaris 10's /usr/bin/tr), since 30 | # the square bracket bytes happen to fall in the designated range. 31 | test $(git diff --cached --name-only --diff-filter=A -z $against | 32 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 33 | then 34 | cat <<\EOF 35 | Error: Attempt to add a non-ASCII file name. 36 | 37 | This can cause problems if you want to work with people on other platforms. 38 | 39 | To be portable it is advisable to rename the file. 40 | 41 | If you know what you are doing you can disable this check using: 42 | 43 | git config hooks.allownonascii true 44 | EOF 45 | exit 1 46 | fi 47 | 48 | # If there are whitespace errors, print the offending file names and fail. 49 | exec git diff-index --check --cached $against -- 50 | -------------------------------------------------------------------------------- /spec/demo.git/hooks/pre-push.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # An example hook script to verify what is about to be pushed. Called by "git 4 | # push" after it has checked the remote status, but before anything has been 5 | # pushed. If this script exits with a non-zero status nothing will be pushed. 6 | # 7 | # This hook is called with the following parameters: 8 | # 9 | # $1 -- Name of the remote to which the push is being done 10 | # $2 -- URL to which the push is being done 11 | # 12 | # If pushing without using a named remote those arguments will be equal. 13 | # 14 | # Information about the commits which are being pushed is supplied as lines to 15 | # the standard input in the form: 16 | # 17 | # 18 | # 19 | # This sample shows how to prevent push of commits where the log message starts 20 | # with "WIP" (work in progress). 21 | 22 | remote="$1" 23 | url="$2" 24 | 25 | z40=0000000000000000000000000000000000000000 26 | 27 | while read local_ref local_sha remote_ref remote_sha 28 | do 29 | if [ "$local_sha" = $z40 ] 30 | then 31 | # Handle delete 32 | : 33 | else 34 | if [ "$remote_sha" = $z40 ] 35 | then 36 | # New branch, examine all commits 37 | range="$local_sha" 38 | else 39 | # Update to existing branch, examine new commits 40 | range="$remote_sha..$local_sha" 41 | fi 42 | 43 | # Check for WIP commit 44 | commit=`git rev-list -n 1 --grep '^WIP' "$range"` 45 | if [ -n "$commit" ] 46 | then 47 | echo >&2 "Found WIP commit in $local_ref, not pushing" 48 | exit 1 49 | fi 50 | fi 51 | done 52 | 53 | exit 0 54 | -------------------------------------------------------------------------------- /spec/demo.git/hooks/pre-rebase.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2006, 2008 Junio C Hamano 4 | # 5 | # The "pre-rebase" hook is run just before "git rebase" starts doing 6 | # its job, and can prevent the command from running by exiting with 7 | # non-zero status. 8 | # 9 | # The hook is called with the following parameters: 10 | # 11 | # $1 -- the upstream the series was forked from. 12 | # $2 -- the branch being rebased (or empty when rebasing the current branch). 13 | # 14 | # This sample shows how to prevent topic branches that are already 15 | # merged to 'next' branch from getting rebased, because allowing it 16 | # would result in rebasing already published history. 17 | 18 | publish=next 19 | basebranch="$1" 20 | if test "$#" = 2 21 | then 22 | topic="refs/heads/$2" 23 | else 24 | topic=`git symbolic-ref HEAD` || 25 | exit 0 ;# we do not interrupt rebasing detached HEAD 26 | fi 27 | 28 | case "$topic" in 29 | refs/heads/??/*) 30 | ;; 31 | *) 32 | exit 0 ;# we do not interrupt others. 33 | ;; 34 | esac 35 | 36 | # Now we are dealing with a topic branch being rebased 37 | # on top of master. Is it OK to rebase it? 38 | 39 | # Does the topic really exist? 40 | git show-ref -q "$topic" || { 41 | echo >&2 "No such branch $topic" 42 | exit 1 43 | } 44 | 45 | # Is topic fully merged to master? 46 | not_in_master=`git rev-list --pretty=oneline ^master "$topic"` 47 | if test -z "$not_in_master" 48 | then 49 | echo >&2 "$topic is fully merged to master; better remove it." 50 | exit 1 ;# we could allow it, but there is no point. 51 | fi 52 | 53 | # Is topic ever merged to next? If so you should not be rebasing it. 54 | only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` 55 | only_next_2=`git rev-list ^master ${publish} | sort` 56 | if test "$only_next_1" = "$only_next_2" 57 | then 58 | not_in_topic=`git rev-list "^$topic" master` 59 | if test -z "$not_in_topic" 60 | then 61 | echo >&2 "$topic is already up to date with master" 62 | exit 1 ;# we could allow it, but there is no point. 63 | else 64 | exit 0 65 | fi 66 | else 67 | not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` 68 | /usr/bin/perl -e ' 69 | my $topic = $ARGV[0]; 70 | my $msg = "* $topic has commits already merged to public branch:\n"; 71 | my (%not_in_next) = map { 72 | /^([0-9a-f]+) /; 73 | ($1 => 1); 74 | } split(/\n/, $ARGV[1]); 75 | for my $elem (map { 76 | /^([0-9a-f]+) (.*)$/; 77 | [$1 => $2]; 78 | } split(/\n/, $ARGV[2])) { 79 | if (!exists $not_in_next{$elem->[0]}) { 80 | if ($msg) { 81 | print STDERR $msg; 82 | undef $msg; 83 | } 84 | print STDERR " $elem->[1]\n"; 85 | } 86 | } 87 | ' "$topic" "$not_in_next" "$not_in_master" 88 | exit 1 89 | fi 90 | 91 | <<\DOC_END 92 | 93 | This sample hook safeguards topic branches that have been 94 | published from being rewound. 95 | 96 | The workflow assumed here is: 97 | 98 | * Once a topic branch forks from "master", "master" is never 99 | merged into it again (either directly or indirectly). 100 | 101 | * Once a topic branch is fully cooked and merged into "master", 102 | it is deleted. If you need to build on top of it to correct 103 | earlier mistakes, a new topic branch is created by forking at 104 | the tip of the "master". This is not strictly necessary, but 105 | it makes it easier to keep your history simple. 106 | 107 | * Whenever you need to test or publish your changes to topic 108 | branches, merge them into "next" branch. 109 | 110 | The script, being an example, hardcodes the publish branch name 111 | to be "next", but it is trivial to make it configurable via 112 | $GIT_DIR/config mechanism. 113 | 114 | With this workflow, you would want to know: 115 | 116 | (1) ... if a topic branch has ever been merged to "next". Young 117 | topic branches can have stupid mistakes you would rather 118 | clean up before publishing, and things that have not been 119 | merged into other branches can be easily rebased without 120 | affecting other people. But once it is published, you would 121 | not want to rewind it. 122 | 123 | (2) ... if a topic branch has been fully merged to "master". 124 | Then you can delete it. More importantly, you should not 125 | build on top of it -- other people may already want to 126 | change things related to the topic as patches against your 127 | "master", so if you need further changes, it is better to 128 | fork the topic (perhaps with the same name) afresh from the 129 | tip of "master". 130 | 131 | Let's look at this example: 132 | 133 | o---o---o---o---o---o---o---o---o---o "next" 134 | / / / / 135 | / a---a---b A / / 136 | / / / / 137 | / / c---c---c---c B / 138 | / / / \ / 139 | / / / b---b C \ / 140 | / / / / \ / 141 | ---o---o---o---o---o---o---o---o---o---o---o "master" 142 | 143 | 144 | A, B and C are topic branches. 145 | 146 | * A has one fix since it was merged up to "next". 147 | 148 | * B has finished. It has been fully merged up to "master" and "next", 149 | and is ready to be deleted. 150 | 151 | * C has not merged to "next" at all. 152 | 153 | We would want to allow C to be rebased, refuse A, and encourage 154 | B to be deleted. 155 | 156 | To compute (1): 157 | 158 | git rev-list ^master ^topic next 159 | git rev-list ^master next 160 | 161 | if these match, topic has not merged in next at all. 162 | 163 | To compute (2): 164 | 165 | git rev-list master..topic 166 | 167 | if this is empty, it is fully merged to "master". 168 | 169 | DOC_END 170 | -------------------------------------------------------------------------------- /spec/demo.git/hooks/pre-receive.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to make use of push options. 4 | # The example simply echoes all push options that start with 'echoback=' 5 | # and rejects all pushes when the "reject" push option is used. 6 | # 7 | # To enable this hook, rename this file to "pre-receive". 8 | 9 | if test -n "$GIT_PUSH_OPTION_COUNT" 10 | then 11 | i=0 12 | while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" 13 | do 14 | eval "value=\$GIT_PUSH_OPTION_$i" 15 | case "$value" in 16 | echoback=*) 17 | echo "echo from the pre-receive-hook: ${value#*=}" >&2 18 | ;; 19 | reject) 20 | exit 1 21 | esac 22 | i=$((i + 1)) 23 | done 24 | fi 25 | -------------------------------------------------------------------------------- /spec/demo.git/hooks/prepare-commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare the commit log message. 4 | # Called by "git commit" with the name of the file that has the 5 | # commit message, followed by the description of the commit 6 | # message's source. The hook's purpose is to edit the commit 7 | # message file. If the hook fails with a non-zero status, 8 | # the commit is aborted. 9 | # 10 | # To enable this hook, rename this file to "prepare-commit-msg". 11 | 12 | # This hook includes three examples. The first one removes the 13 | # "# Please enter the commit message..." help message. 14 | # 15 | # The second includes the output of "git diff --name-status -r" 16 | # into the message, just before the "git status" output. It is 17 | # commented because it doesn't cope with --amend or with squashed 18 | # commits. 19 | # 20 | # The third example adds a Signed-off-by line to the message, that can 21 | # still be edited. This is rarely a good idea. 22 | 23 | COMMIT_MSG_FILE=$1 24 | COMMIT_SOURCE=$2 25 | SHA1=$3 26 | 27 | /usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" 28 | 29 | # case "$COMMIT_SOURCE,$SHA1" in 30 | # ,|template,) 31 | # /usr/bin/perl -i.bak -pe ' 32 | # print "\n" . `git diff --cached --name-status -r` 33 | # if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; 34 | # *) ;; 35 | # esac 36 | 37 | # SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 38 | # git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" 39 | # if test -z "$COMMIT_SOURCE" 40 | # then 41 | # /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" 42 | # fi 43 | -------------------------------------------------------------------------------- /spec/demo.git/hooks/update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to block unannotated tags from entering. 4 | # Called by "git receive-pack" with arguments: refname sha1-old sha1-new 5 | # 6 | # To enable this hook, rename this file to "update". 7 | # 8 | # Config 9 | # ------ 10 | # hooks.allowunannotated 11 | # This boolean sets whether unannotated tags will be allowed into the 12 | # repository. By default they won't be. 13 | # hooks.allowdeletetag 14 | # This boolean sets whether deleting tags will be allowed in the 15 | # repository. By default they won't be. 16 | # hooks.allowmodifytag 17 | # This boolean sets whether a tag may be modified after creation. By default 18 | # it won't be. 19 | # hooks.allowdeletebranch 20 | # This boolean sets whether deleting branches will be allowed in the 21 | # repository. By default they won't be. 22 | # hooks.denycreatebranch 23 | # This boolean sets whether remotely creating branches will be denied 24 | # in the repository. By default this is allowed. 25 | # 26 | 27 | # --- Command line 28 | refname="$1" 29 | oldrev="$2" 30 | newrev="$3" 31 | 32 | # --- Safety check 33 | if [ -z "$GIT_DIR" ]; then 34 | echo "Don't run this script from the command line." >&2 35 | echo " (if you want, you could supply GIT_DIR then run" >&2 36 | echo " $0 )" >&2 37 | exit 1 38 | fi 39 | 40 | if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then 41 | echo "usage: $0 " >&2 42 | exit 1 43 | fi 44 | 45 | # --- Config 46 | allowunannotated=$(git config --bool hooks.allowunannotated) 47 | allowdeletebranch=$(git config --bool hooks.allowdeletebranch) 48 | denycreatebranch=$(git config --bool hooks.denycreatebranch) 49 | allowdeletetag=$(git config --bool hooks.allowdeletetag) 50 | allowmodifytag=$(git config --bool hooks.allowmodifytag) 51 | 52 | # check for no description 53 | projectdesc=$(sed -e '1q' "$GIT_DIR/description") 54 | case "$projectdesc" in 55 | "Unnamed repository"* | "") 56 | echo "*** Project description file hasn't been set" >&2 57 | exit 1 58 | ;; 59 | esac 60 | 61 | # --- Check types 62 | # if $newrev is 0000...0000, it's a commit to delete a ref. 63 | zero="0000000000000000000000000000000000000000" 64 | if [ "$newrev" = "$zero" ]; then 65 | newrev_type=delete 66 | else 67 | newrev_type=$(git cat-file -t $newrev) 68 | fi 69 | 70 | case "$refname","$newrev_type" in 71 | refs/tags/*,commit) 72 | # un-annotated tag 73 | short_refname=${refname##refs/tags/} 74 | if [ "$allowunannotated" != "true" ]; then 75 | echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 76 | echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 77 | exit 1 78 | fi 79 | ;; 80 | refs/tags/*,delete) 81 | # delete tag 82 | if [ "$allowdeletetag" != "true" ]; then 83 | echo "*** Deleting a tag is not allowed in this repository" >&2 84 | exit 1 85 | fi 86 | ;; 87 | refs/tags/*,tag) 88 | # annotated tag 89 | if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 90 | then 91 | echo "*** Tag '$refname' already exists." >&2 92 | echo "*** Modifying a tag is not allowed in this repository." >&2 93 | exit 1 94 | fi 95 | ;; 96 | refs/heads/*,commit) 97 | # branch 98 | if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then 99 | echo "*** Creating a branch is not allowed in this repository" >&2 100 | exit 1 101 | fi 102 | ;; 103 | refs/heads/*,delete) 104 | # delete branch 105 | if [ "$allowdeletebranch" != "true" ]; then 106 | echo "*** Deleting a branch is not allowed in this repository" >&2 107 | exit 1 108 | fi 109 | ;; 110 | refs/remotes/*,commit) 111 | # tracking branch 112 | ;; 113 | refs/remotes/*,delete) 114 | # delete tracking branch 115 | if [ "$allowdeletebranch" != "true" ]; then 116 | echo "*** Deleting a tracking branch is not allowed in this repository" >&2 117 | exit 1 118 | fi 119 | ;; 120 | *) 121 | # Anything else (is there anything else?) 122 | echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 123 | exit 1 124 | ;; 125 | esac 126 | 127 | # --- Finished 128 | exit 0 129 | -------------------------------------------------------------------------------- /spec/demo.git/index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dm1try/git_fastfix/2c00dc8633a2f51e56594ff317e621174c977f95/spec/demo.git/index -------------------------------------------------------------------------------- /spec/demo.git/info/exclude: -------------------------------------------------------------------------------- 1 | # git ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | # *~ 7 | -------------------------------------------------------------------------------- /spec/demo.git/logs/HEAD: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 f3f7b0af0ed7cc50ee09ec28be7e9dbd32041664 dm1try 1586092372 +0300 commit (initial): first commit 2 | f3f7b0af0ed7cc50ee09ec28be7e9dbd32041664 cd7e3cfe5de93688525b197954f266a2d13e8e31 dm1try 1586092396 +0300 commit: second commit 3 | cd7e3cfe5de93688525b197954f266a2d13e8e31 c9a08ee6d6a92881d20b0f5aebec4066e4d99987 dm1try 1586092421 +0300 commit: third commit 4 | -------------------------------------------------------------------------------- /spec/demo.git/logs/refs/heads/master: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 f3f7b0af0ed7cc50ee09ec28be7e9dbd32041664 dm1try 1586092372 +0300 commit (initial): first commit 2 | f3f7b0af0ed7cc50ee09ec28be7e9dbd32041664 cd7e3cfe5de93688525b197954f266a2d13e8e31 dm1try 1586092396 +0300 commit: second commit 3 | cd7e3cfe5de93688525b197954f266a2d13e8e31 c9a08ee6d6a92881d20b0f5aebec4066e4d99987 dm1try 1586092421 +0300 commit: third commit 4 | -------------------------------------------------------------------------------- /spec/demo.git/objects/23/4496b1caf2c7682b8441f9b866a7e2420d9748: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dm1try/git_fastfix/2c00dc8633a2f51e56594ff317e621174c977f95/spec/demo.git/objects/23/4496b1caf2c7682b8441f9b866a7e2420d9748 -------------------------------------------------------------------------------- /spec/demo.git/objects/73/3ac0e7f136f3ad803c1f26778b67543b2c310f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dm1try/git_fastfix/2c00dc8633a2f51e56594ff317e621174c977f95/spec/demo.git/objects/73/3ac0e7f136f3ad803c1f26778b67543b2c310f -------------------------------------------------------------------------------- /spec/demo.git/objects/9c/59e24b8393179a5d712de4f990178df5734d99: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dm1try/git_fastfix/2c00dc8633a2f51e56594ff317e621174c977f95/spec/demo.git/objects/9c/59e24b8393179a5d712de4f990178df5734d99 -------------------------------------------------------------------------------- /spec/demo.git/objects/c9/a08ee6d6a92881d20b0f5aebec4066e4d99987: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dm1try/git_fastfix/2c00dc8633a2f51e56594ff317e621174c977f95/spec/demo.git/objects/c9/a08ee6d6a92881d20b0f5aebec4066e4d99987 -------------------------------------------------------------------------------- /spec/demo.git/objects/cb/66df7d1ee3d0af286ca70340ea9c85d90b913b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dm1try/git_fastfix/2c00dc8633a2f51e56594ff317e621174c977f95/spec/demo.git/objects/cb/66df7d1ee3d0af286ca70340ea9c85d90b913b -------------------------------------------------------------------------------- /spec/demo.git/objects/cd/7e3cfe5de93688525b197954f266a2d13e8e31: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dm1try/git_fastfix/2c00dc8633a2f51e56594ff317e621174c977f95/spec/demo.git/objects/cd/7e3cfe5de93688525b197954f266a2d13e8e31 -------------------------------------------------------------------------------- /spec/demo.git/objects/e0/19be006cf33489e2d0177a3837a2384eddebc5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dm1try/git_fastfix/2c00dc8633a2f51e56594ff317e621174c977f95/spec/demo.git/objects/e0/19be006cf33489e2d0177a3837a2384eddebc5 -------------------------------------------------------------------------------- /spec/demo.git/objects/f3/f7b0af0ed7cc50ee09ec28be7e9dbd32041664: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dm1try/git_fastfix/2c00dc8633a2f51e56594ff317e621174c977f95/spec/demo.git/objects/f3/f7b0af0ed7cc50ee09ec28be7e9dbd32041664 -------------------------------------------------------------------------------- /spec/demo.git/objects/f5/78d53291854de1ec19903109bc9d32fbe2b5c0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dm1try/git_fastfix/2c00dc8633a2f51e56594ff317e621174c977f95/spec/demo.git/objects/f5/78d53291854de1ec19903109bc9d32fbe2b5c0 -------------------------------------------------------------------------------- /spec/demo.git/refs/heads/master: -------------------------------------------------------------------------------- 1 | c9a08ee6d6a92881d20b0f5aebec4066e4d99987 2 | -------------------------------------------------------------------------------- /spec/demo/first_commit.txt: -------------------------------------------------------------------------------- 1 | first 2 | -------------------------------------------------------------------------------- /spec/demo/second_commit.txt: -------------------------------------------------------------------------------- 1 | second 2 | -------------------------------------------------------------------------------- /spec/demo/third_commit.txt: -------------------------------------------------------------------------------- 1 | third 2 | -------------------------------------------------------------------------------- /spec/git_fastfix_spec.lua: -------------------------------------------------------------------------------- 1 | local helpers = require('test.functional.helpers')(after_each) 2 | local Screen = require('test.functional.ui.screen') 3 | local nvim = helpers.nvim 4 | local clear, command, feed = helpers.clear, helpers.command, helpers.feed 5 | local eq = helpers.eq 6 | 7 | describe('git_fastfix', function() 8 | local screen 9 | local screen_width = 100 10 | 11 | local plugin_dir = os.getenv("TEST_FILE"):match("(.*)/spec") 12 | local spec_dir = plugin_dir .. "/spec" 13 | local demo_path = spec_dir .. '/demo' 14 | 15 | before_each(function() 16 | clear() 17 | screen = Screen.new(screen_width, 10) 18 | screen:attach() 19 | 20 | local demo_git_path = spec_dir .. '/demo.git' 21 | command('!cp -R ' .. demo_git_path .. ' ' .. demo_path .. '/.git') 22 | feed('') 23 | command('!cd ' .. demo_path .. ' && git reset --hard HEAD') 24 | feed('') 25 | command('set rtp+=' .. plugin_dir) 26 | command('lua git_fastfix = require("git_fastfix")') 27 | end) 28 | 29 | after_each(function() 30 | command('!rm -rf ' .. demo_path .. '/.git') 31 | feed('') 32 | screen:detach() 33 | end) 34 | 35 | local function go_to_demo_project() 36 | command('cd ' .. demo_path) 37 | end 38 | 39 | local function update_file_from_second_commit() 40 | command('e second_commit.txt') 41 | feed('iupdated :w') 42 | end 43 | 44 | local function open_fixup_window() 45 | command('lua git_fastfix.open()') 46 | end 47 | 48 | local function expect_git_patch_window() 49 | screen:expect([[ 50 | updateddiff --git a/second_commit.txt b/second_commit.txt | 51 | ~ index e019be0..9dd24c0 100644 | 52 | ~ --- a/second_commit.txt | 53 | ~ +++ b/second_commit.txt | 54 | ~ @@ -1 +1 @@ | 55 | ~ -second | 56 | ~ +updated second | 57 | ~ Stage this hunk [y,n,q,a,d,e,?]? | 58 | ~ | 59 | -- TERMINAL -- | 60 | ]]) 61 | end 62 | 63 | local function expect_git_log_window() 64 | screen:expect([[ 65 | updated^c9a08ee third commit | 66 | ~ cd7e3cf second commit | 67 | ~ f3f7b0a first commit | 68 | ~ | 69 | ~ | 70 | ~ | 71 | ~ | 72 | ~ | 73 | ~ | 74 | | 75 | ]]) 76 | end 77 | 78 | local function rebase_second_commit() 79 | feed('j') 80 | end 81 | 82 | local function fixup_second_commit() 83 | feed('j\\') 84 | end 85 | 86 | local function expect_notification_about_applied_patch() 87 | screen:expect([[ 88 | updated^ second | 89 | ~ | 90 | ~ | 91 | ~ | 92 | ~ | 93 | ~ | 94 | ~ | 95 | ~ | 96 | ~ | 97 | Patch applied to cd7e3cf second commit | 98 | ]]) 99 | end 100 | 101 | local function expect_notification_about_applied_fixup() 102 | screen:expect([[ 103 | updated^ second | 104 | ~ | 105 | ~ | 106 | ~ | 107 | ~ | 108 | ~ | 109 | ~ | 110 | ~ | 111 | ~ | 112 | Fixup applied to cd7e3cf second commit | 113 | ]]) 114 | end 115 | 116 | it('applies git "fixup" and "rebase" for the choosen commit', function() 117 | go_to_demo_project() 118 | update_file_from_second_commit() 119 | 120 | open_fixup_window() 121 | 122 | expect_git_patch_window() 123 | feed('y') -- confirm git hunk 124 | 125 | expect_git_log_window() 126 | rebase_second_commit() 127 | expect_notification_about_applied_patch() 128 | end) 129 | 130 | it('applies only a "fixup" for the choosen commit', function() 131 | go_to_demo_project() 132 | update_file_from_second_commit() 133 | 134 | open_fixup_window() 135 | 136 | expect_git_patch_window() 137 | feed('y') -- confirm git hunk 138 | 139 | expect_git_log_window() 140 | fixup_second_commit() 141 | expect_notification_about_applied_fixup() 142 | end) 143 | end) 144 | --------------------------------------------------------------------------------