diff --git a/vimfiles/GetLatest/GetLatestVimScripts.dat b/vimfiles/GetLatest/GetLatestVimScripts.dat index 8c32e6f..7d485fb 100644 --- a/vimfiles/GetLatest/GetLatestVimScripts.dat +++ b/vimfiles/GetLatest/GetLatestVimScripts.dat @@ -40,3 +40,4 @@ ScriptID SourceID Filename 3849 22637 git-time-lapse 4932 22924 diffchar.vim 4955 22916 Merginal +3574 16307 gitv: gitk for Vim diff --git a/vimfiles/doc/gitv.txt b/vimfiles/doc/gitv.txt new file mode 100644 index 0000000..9506cf8 --- /dev/null +++ b/vimfiles/doc/gitv.txt @@ -0,0 +1,482 @@ +gitv -- gitk for vim. + +AUTHOR: Greg Sexton *gitv-author* +WEBSITE: http://www.gregsexton.org/portfolio/gitv/ +LICENSE: Same terms as Vim itself (see :help license). +NOTES: Much of the credit for gitv goes to Tim Pope and the fugitive plugin + where this plugin either uses functionality directly or was inspired + heavily. + +gitv *gitv* + +1. Introduction |gitv-introduction| +2. Installation |gitv-installation| +3. Usage |gitv-usage| +4. Configuration Options |gitv-config-options| +5. Changelog |gitv-changelog| +6. Misc |gitv-misc| + +============================================================================== +1. Introduction *gitv-introduction* + +|gitv| is a 'gitk clone' plugin for the text editor Vim. The goal is to give +you a similar set of functionality as a repository viewer. Using this plugin +you can view a repository's history including branching and merging, you can +see which commits refs point to. You can quickly and easily view what changed +to which files and when. You can perform arbitrary diffs (using Vim's +excellent built in diff functionality) and you can easily check out whole +commits and branches or just individual files if need be. + +Throw in the fact that it is running in Vim and you get for free: the ability +to move over repository history quickly and precisely using Vim's built in +movement operators. You get excellent code syntax highlighting due to Vim's +built in ability. You can open up all sorts of repository views in multiple +windows and position them exactly how you like. You can take advantage of +Vim's registers to copy multiple fragments of code from previous commits. The +list goes on. + +This plugin is an extension of the |fugitive| plugin. + +I hope you like it! + +============================================================================== +2. Installation *gitv-installation* + +Install in ~/.vim, or in ~\vimfiles if you're on Windows. This plugin should +be fully pathogen compatible if you want to install it this way. + +|gitv| was developed against Vim 7.3 but earlier versions of Vim should work. +Vim 7.2+ is recommended as it ships with syntax highlighting for many Git file +types. You will also need the |fugitive| plugin installed and working for +|gitv| to work. + +============================================================================== +3. Usage *gitv-usage* + +|gitv| defines the following command. + +:[range]Gitv[!] [args] + + Invoking this command on a buffer that belongs to a git + repository causes the gitv browser to open. '!' causes gitv + to open in file mode rather than browser mode. Any [args] + supplied are passed on to the gitv viewer and can be used to + narrow the commits that are shown. If this command is run + on a buffer not belonging to a git repository a message + stating 'Not a git repository.' is displayed. + + When used with a [range] this has no effect in browser mode. + In file mode it narrows the commits shown to only those + affecting lines in the range. See section 3.8 for more + details. + +The following abbreviation is also defined so that you can type :gitv without +capitalisation. The abbreviation is defined in such a way that this +substitution is only performed when 'gitv' is the first word on the command +line. +> + cabbrev gitv Gitv +< + +3.1 Browser mode + +|gitv| has two distinct modes. Browser mode and file mode. The browser mode is +opened in a new tab and allows the repository history to be viewed for all +files. This is activated by running :Gitv without a '!'. + +In this mode you can view the entire repository history and see which files +were changed with each commit. This mode tries to closely resemble gitk. + +3.2 File mode + +File mode is opened in a |preview-window| above the buffer you are currently in. +This view is tailored to act on the buffer that :Gitv! was run from, the +"focused" file. Whilst in this mode all actions performed are specific to the +single focused file. The browser only shows commits where the focused file +changed. Selecting a commit, views the focused file as it was in that commit. +Performing a check out only checks the focused file out as it was in the +commit. And so on. See 3.4 for the differences. + +3.3 Arguments + +You can pass arguments to the :Gitv command. These allow you to filter and +narrow the commits shown. Essentially, gitv is a glorified 'git log' wrapper +and so any flag that 'git log' accepts so will gitv. gitv does not inspect the +arguments passed on to 'git log' and so may not work if they don't make sense. + +Without any arguments gitv behaves just like gitk and git log without +arguments. It will display the commit history for the currently checked out +branch. + +Here are some particularly useful examples of arguments that could be +passed to :Gitv. For more info see 'git help log' and in particular the +section: "Commit Limiting". + + Flag Description ~ + + --all View repository history for all refs. + + .. Show only commits between the named two commits. When + either or is omitted, it defaults to + HEAD, i.e. the tip of the current branch. For a more + complete list of ways to spell and , + see gitrevisions(7). + + --merges View only merge commits. + + -S Look for differences that introduce or remove an + instance of . Note that this is different than + the string simply appearing in diff output; see the + pickaxe entry in gitdiffcore(7) for more details. + + -G Look for differences whose added or removed line + matches the given . + +3.4 Key mappings. + +This is a list of key mappings that will work only in the gitv browser window. +Where appropriate the differences in action is described for the two modes. + + Mode Map Description~ + + normal Opens a commit. In browser mode this will show the + commit header information including the commit + message. It will also display a full diff showing all the + changes to files. + + Tip: if you press on anything sensible you can + view the expected output. For example pressing on + the line beginning a file diff, it will display the + diff using Vim's built in diff viewing capability. + Pressing on the tree sha will list all the files + in the commit and pressing on one of these will + show that file as it was in the commit. And so on. + + Pressing on the line "-- Load More --" will load + |g:Gitv_CommitStep| more commits. + + In file mode this will open the focused file as it was + in the currently selected diff. This allows you to + easily go back in time and look at the focused file. + + Pressing on the top line in file mode opens the + current working copy of the focused file. + + normal o The same as but opens in a new |split|. + normal O The same as but opens in a new |tab|. + normal s The same as but opens in a new |vsplit|. + + normal This performs the same thing as in browser mode. + In file mode it opens the commit details rather than + the focused file. + + normal q Quits gitv. In browser mode this will close the entire + tab. In file mode this closes only the |preview-window|. + Note: in browser mode this will close the tab + regardless of any windows you may have opened as well + as the gitv windows. + + normal u Forces an update of the content of the browser window. + + normal co Performs a 'git checkout' of the commit the cursor is + on. In both modes this will present you with a choice + of whether you would like to checkout the sha or any + ref that might point to this commit. + + File mode differs in that it doesn't check out the + entire commit but just the focused file in that + commit. + + Tip: in gVim this will present you with a pop up dialog. + You can make this a text choice by performing ':set + guioptions+=c.' + + normal D Performs a diff using Vim's built in diff viewing + capabilities. This does nothing in browser mode. In + file mode it will diff the current file with the + focused file in the commit under the cursor. + + visual D In visual mode this performs a diff against the file + in the commit at the top of the selection against the + file in the commit at the bottom of the selection. The + newest file is always on the right. + + normal S This works for both browser and file mode. It opens a + diffstat of everything that has changed since the + commit under the cursor. + + visual S In visual mode this works in exactly the same way as + normal mode. However, it only shows what has changed in + the range of commits that are highlighted. + + visual m Merges the commit in either the top or bottom line of + the selection in to the commit specified by the other + end of the selection. This uses prompts to guide you + through the merging and should be fairly intuitive. It + will also confirm if you wish to perform a + fast-forward merge or not. + + gitv doesn't allow you to merge arbitrary commits. The + lines at the top and bottom of the selection must + contain refs in the form of tags, remotes or local + branches. + + normal git Enters command mode with ":Git " already typed for + you. Just a convenient shortcut for executing git + commands and watching them affect the repository. + +Here is a list of extra key mappings that can be used to efficiently move +around a repository history in the browser window. + + Mode Map Description~ + + normal x Jumps the cursor forward to the next branching point + in the history. + + normal X Jumps the cursor backward to the previous branching + point in the history. + + normal r Jumps the cursor forward to the next ref in the + history. + + normal R Jumps the cursor backward to the previous ref in the + history. + + normal P Jumps the cursor to the commit referenced by HEAD. + +3.5 Commands + +Running the |:Git| command in the commit browser window, in either mode, will +cause the |:Git| command to be run as expected but the commit history will +automatically update to reflect any changes too. + +3.6 Windows + +In browser mode, two windows are opened initially. The "browser window" that +displays the commit history and the "preview window" that shows the currently +selected commit. + +In file mode, a |preview-window| is opened above the current file. This is a +browser window filtered to show commits only affecting the focused file. The +window holding the focused file acts as the preview window in this mode. + +NOTE: In both modes the buffer in the preview window is wiped after replacing +it. This is to stop the quick build up of fugitive buffers. A buffer will not +be wiped if it contains unsaved changes. Buffers are not wiped when opening a +commit in a split, vsplit or a new tab. + +When opening a commit, the window it will be opened in is determined by simple +rules. If in browser mode and the layout is vertical it will open in the +window to the immediate right (exactly as if you performed l). If in a +horizontal layout, it will be opened in the window immediately below (exactly +as if you performed j). If in file mode, it will open exactly like +browser mode split horizontally. NOTE: It is for this reason that you should +not move the browser window as it will cause commits to be opened in +unexpected places. + +3.7 Folding + +Folding of branches is supported in the browser window. Initially all folds +are always open and will open on a reload. You can collapse any branch by +using Vim's built in fold operators. See |folding| for more details. + +3.8 Using with a range of lines + +If a range is passed to the :Gitv command it will have no effect in browser +mode. In file mode however, only commits that affect at least one line in the +range will be displayed. This is useful, for example, to view the commit +history for a function and all of the changes made to it. + +If a range of less than two lines is used then gitv opens in regular file +mode. + +Selecting a commit by pressing on it will show the file as it was in that +commit with all lines not in the range folded away. This allows you to quickly +and easily move from commit to commit viewing the evolution of the range of +lines. Pressing will show you the commit details so that you can see +any other changes it may have made. + +The range that Git looks at is specified by the first and last line in the +range passed to :Gitv. These lines are escaped appropriately and passed to Git +to use as a regular expression. If you wish to modify either regex delimiter +this can be done by pressing on the appropriate line near the top of the +file mode buffer. Vim will prompt you to modify the regex. If you press enter +without modification, nothing happens. If you modify the regex (Git supports +POSIX regular expressions) gitv will automatically update. + +You may also pass arguments to the :Gitv! command when using a range. This +filters the commits that are to be compared for changes in the range. For +example you could pass the name of a branch to only look at changes in commits +on that branch. + +Note: This feature requires the bash shell to be installed on your system and +in your path. This should already be the case for the majority of users. + +Note: Using a range searches back through the commit history looking at each +section of lines for where changes occurred. It will only look at the last +g:Gitv_CommitStep number, if you don't get any matches you could try pressing + on the 'load more' line. This command may be slow if you choose a large +range and have a large g:Gitv_CommitStep. + +============================================================================== +4. Configuration Options *gitv-config-options* + +You can set the following options in your .vimrc to override the values used +by |gitv|. The defaults are shown. + +4.1 Commit Step + +This is the number of commits to show each time. When pressing on +"-- Load More --", the number of extra commits loaded is g:Gitv_CommitStep. +The default is a screen's worth of lines. This should be set to an integer +number. Setting this to a value _really_ high will load the entire repo in one +go. +> + g:Gitv_CommitStep = &lines +< + +4.2 Open Horizontal + +This is the default layout to use in browser mode. If set to 0 then browser +mode will open in a vertical split. If set to 1 then browser mode will open in +a horizontal split. If set to 'auto' then browser mode will open in a vertical +split unless the content fits better in a horizontal split, in which case it +will open horizontally. + +The commit browser width and height is automatically sized to best fit the +content in all modes and settings. +> + g:Gitv_OpenHorizontal = 0 +< + +4.3 Git Executable + +This is the name of the git executable to use to run commands. This should be +a string. +> + g:Gitv_GitExecutable = 'git' +< +4.4 Wipe All on Close + +This option should be set to either 0 (to disable) or 1 (to enable). If set to +1 then on closing the browser mode by using the q key all buffers displayed in +a window in the tab will be wiped before the tab is closed. This option allows +you to limit the number of fugitive buffers that accumulate in the use of gitv. + +NOTE: This will not wipe any buffer with unsaved content. It will however +mercilessly wipe all buffers in the tab regardless of the file they hold. +> + g:Gitv_WipeAllOnClose = 0 +< + +4.5 Wrap Lines + +If set to 1 then line wrapping is enabled. This is useful if you have +occasional very long commit messages. +> + g:Gitv_WrapLines = 0 +< + +4.6 Truncate Commit Subjects + +If set to 1 then commit subject truncation is enabled. This will truncate +commit subjects, where necessary, so that the whole line will fit in one +screen width. If this is set, then automatically switching to a horizontal +layout will no longer work as commits will be truncated to always fit in a +vertical split. NOTE: It is possible that this can truncate any refs pointing +at a commit. In this situation it will not be possible to check out any of +these refs. This is due to gitv being unable to recognise that they are refs. +> + g:Gitv_TruncateCommitSubjects = 0 +< + +4.7 Open Preview On Launch + +If set to 1 then the preview window is displayed when launching gitv in +browser mode. If set to 0 then no preview window is displayed until a commit +is opened. This option has no effect in file mode. +> + g:Gitv_OpenPreviewOnLaunch = 1 +< + +4.8 Prompt to Delete Merge Branch + +If set to 1 then, when performing a merge using gitv, you will be prompted if +you wish to delete the topic branch. If you often merge a branch into another +and rarely wish to delete branches, set this to 0. The default is 0. +> + let g:Gitv_PromptToDeleteMergeBranch = 0 +< +============================================================================== +5. Changelog *gitv-changelog* + +1.1 Nodes displaying local changes are inserted above HEAD ref. + Added range feature. + Merging branches is possible in gitv. + strwidth bugfix (thanks Adam Reeve). + Prevent bdelete errors (thanks pydave). + +1.0 First release. I hope you enjoy gitv! + +============================================================================== +6. Misc *gitv-misc* + +6.1 Tips and tricks + +I use the following mappings to make working with |gitv| easier. +> + nmap gv :Gitv --all + nmap gV :Gitv! --all + vmap gV :Gitv! --all +< +The vmap equivalent for file mode is to make dealing with ranges easier. It +allows you to visually select a range of lines to view the commit history for +that section of the file. + +The following abbreviation makes running arbitrary git commands much easier. +> + cabbrev git Git +< + +The function: 'Gitv_OpenGitCommand(command, windowCmd)' is provided to allow +the more advanced user to create their own commands. This function will +execute the git command provided in the new window created using windowCmd. + +By using this function you get for free: the buffer set up for read only git +output, including syntax highlighting and many other tailored options. You +also get mappings for 'u' to update the output and 'q' to easily close the +window. + +Here is an example of getting diff output both cached and not, in a vertical +and horizontal split respectively. +> + call Gitv_OpenGitCommand("diff --no-color --cached", 'vnew') + call Gitv_OpenGitCommand("diff --no-color", 'new') +< + +I like my diff colors to be green for added lines and red for removed lines, +just like in the shell. Adding this to your vimrc will accomplish this. +> + highlight diffAdded guifg=#00bf00 + highlight diffRemoved guifg=#bf0000 +< + +I highly recommend adding to your vimrc: +> + set lazyredraw +< + +This stops Vim from redrawing the screen during complex operations and results +in much smoother looking plugins. + + +6.2 Bugs, issues, features and contributing. + +There are no known bugs. Hopefully there are not too many unknown. Please see +below to help. + +Bugs, suggestions, pull requests and patches are all very welcome. If you find +issues with |gitv| please add them to the issues page on the github project. +Anything else, feel free to email me: gregsexton@gmail.com. + + vim:tw=78:ts=8:ft=help:norl: diff --git a/vimfiles/doc/tags b/vimfiles/doc/tags index 6b7fb68..b506eb3 100644 --- a/vimfiles/doc/tags +++ b/vimfiles/doc/tags @@ -1853,6 +1853,14 @@ getscript-data pi_getscript.txt /*getscript-data* getscript-history pi_getscript.txt /*getscript-history* getscript-plugins pi_getscript.txt /*getscript-plugins* getscript-start pi_getscript.txt /*getscript-start* +gitv gitv.txt /*gitv* +gitv-author gitv.txt /*gitv-author* +gitv-changelog gitv.txt /*gitv-changelog* +gitv-config-options gitv.txt /*gitv-config-options* +gitv-installation gitv.txt /*gitv-installation* +gitv-introduction gitv.txt /*gitv-introduction* +gitv-misc gitv.txt /*gitv-misc* +gitv-usage gitv.txt /*gitv-usage* global_markfilelist pi_netrw.txt /*global_markfilelist* glvs pi_getscript.txt /*glvs* glvs-alg pi_getscript.txt /*glvs-alg* @@ -1943,6 +1951,7 @@ matchit.vim matchit.txt /*matchit.vim* merginal merginal.txt /*merginal* merginal-branch-list merginal.txt /*merginal-branch-list* merginal-diff-files merginal.txt /*merginal-diff-files* +merginal-history-log merginal.txt /*merginal-history-log* merginal-merge-conflicts merginal.txt /*merginal-merge-conflicts* merginal-rebase-amend merginal.txt /*merginal-rebase-amend* merginal-rebase-conflicts merginal.txt /*merginal-rebase-conflicts* diff --git a/vimfiles/ftplugin/gitv.vim b/vimfiles/ftplugin/gitv.vim new file mode 100644 index 0000000..90fa45c --- /dev/null +++ b/vimfiles/ftplugin/gitv.vim @@ -0,0 +1,48 @@ +"AUTHOR: Greg Sexton +"WEBSITE: http://www.gregsexton.org/portfolio/gitv/ +"LICENSE: Same terms as Vim itself (see :help license). +"NOTES: Much of the credit for gitv goes to Tim Pope and the fugitive plugin +" where this plugin either uses functionality directly or was inspired heavily. + +"enabling these next lines breaks settings when reloading the buffer +"if exists("b:did_ftplugin") | finish | endif +"let b:did_ftplugin = 1 + +let s:cpo_save = &cpo +set cpo&vim + +setlocal fdm=expr + +fu! Foldlevelforbranch() "{{{ + let line = getline(v:lnum) + + if line == "-- Load More --" + return 0 + endif + if line =~ "^-- \\[.*\\] --$" + return 0 + endif + + let line = substitute(line, "\\s", "", "g") + let level = match(line, "*") + 1 + return level == 0 ? -1 : level +endfu "}}} +setlocal foldexpr=Foldlevelforbranch() + +fu! BranchFoldText() "{{{ + "get first non-blank line + let fs = v:foldstart + while getline(fs) =~ '^\s*$' | let fs = nextnonblank(fs + 1) + endwhile + if fs > v:foldend + let line = getline(v:foldstart) + else + let line = getline(fs) + endif + return line +endf "}}} +setlocal foldtext=BranchFoldText() +setlocal foldlevel=99 + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/vimfiles/plugin/gitv.vim b/vimfiles/plugin/gitv.vim new file mode 100644 index 0000000..c94506e --- /dev/null +++ b/vimfiles/plugin/gitv.vim @@ -0,0 +1,1075 @@ +"AUTHOR: Greg Sexton +"WEBSITE: http://www.gregsexton.org/portfolio/gitv/ +"LICENSE: Same terms as Vim itself (see :help license). +"NOTES: Much of the credit for gitv goes to Tim Pope and the fugitive plugin +" where this plugin either uses functionality directly or was inspired heavily. + +if exists("g:loaded_gitv") || v:version < 700 + finish +endif +let g:loaded_gitv = 1 + +let s:savecpo = &cpo +set cpo&vim + +"configurable options: +"g:Gitv_CommitStep - int +"g:Gitv_OpenHorizontal - {0,1,'AUTO'} +"g:Gitv_GitExecutable - string +"g:Gitv_WipeAllOnClose - int +"g:Gitv_WrapLines - {0,1} +"g:Gitv_TruncateCommitSubjects - {0,1} +"g:Gitv_OpenPreviewOnLaunch - {0,1} +"g:Gitv_PromptToDeleteMergeBranch - {0,1} + +if !exists("g:Gitv_CommitStep") + let g:Gitv_CommitStep = &lines +endif + +if !exists('g:Gitv_GitExecutable') + let g:Gitv_GitExecutable = 'git' +endif + +if !exists('g:Gitv_WipeAllOnClose') + let g:Gitv_WipeAllOnClose = 0 "default for safety +endif + +if !exists('g:Gitv_WrapLines') + let g:Gitv_WrapLines = 0 +endif + +if !exists('g:Gitv_TruncateCommitSubjects') + let g:Gitv_TruncateCommitSubjects = 0 +endif + +if !exists('g:Gitv_OpenPreviewOnLaunch') + let g:Gitv_OpenPreviewOnLaunch = 1 +endif + +if !exists('g:Gitv_PromptToDeleteMergeBranch') + let g:Gitv_PromptToDeleteMergeBranch = 0 +endif + +"this counts up each time gitv is opened to ensure a unique file name +let g:Gitv_InstanceCounter = 0 + +let s:localUncommitedMsg = 'Local uncommitted changes, not checked in to index.' +let s:localCommitedMsg = 'Local changes checked in to index but not committed.' + +command! -nargs=* -range -bang Gitv call s:OpenGitv(shellescape(), 0, , ) +cabbrev gitv =(getcmdtype()==':' && getcmdpos()==1 ? 'Gitv' : 'gitv') + +"Public API:"{{{ +fu! Gitv_OpenGitCommand(command, windowCmd, ...) "{{{ + "returns 1 if command succeeded with output + "optional arg is a flag, if present runs command verbatim + + "this function is not limited to script scope as is useful for running other commands. + "e.g call Gitv_OpenGitCommand("diff --no-color", 'vnew') is useful for getting an overall git diff. + + let [result, finalCmd] = s:RunGitCommand(a:command, a:0) + + if type(result) == type(0) + return 0 + endif + if type(result) == type("") && result == "" + echom "No output." + return 0 + else + if a:windowCmd == '' + silent setlocal modifiable + silent setlocal noreadonly + 1,$ d + else + let goBackTo = winnr() + let dir = s:GetRepoDir() + let workingDir = fnamemodify(dir,':h') + let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' + let bufferDir = getcwd() + let tempSplitBelow = &splitbelow + let tempSplitRight = &splitright + try + set nosplitbelow + set nosplitright + execute cd.'`=workingDir`' + exec a:windowCmd + let newWindow = winnr() + finally + exec goBackTo . 'wincmd w' + execute cd.'`=bufferDir`' + if exists('newWindow') + exec newWindow . 'wincmd w' + endif + exec 'set '. (tempSplitBelow ? '' : 'no') . 'splitbelow' + exec 'set '. (tempSplitRight ? '' : 'no') . 'splitright' + endtry + endif + if !(&modifiable) + return 0 + endif + let b:Git_Command = finalCmd + silent setlocal ft=git + silent setlocal buftype=nofile + silent setlocal nobuflisted + silent setlocal noswapfile + silent setlocal bufhidden=wipe + silent setlocal nonumber + if g:Gitv_WrapLines + silent setlocal wrap + else + silent setlocal nowrap + endif + silent setlocal fdm=syntax + silent setlocal foldlevel=0 + nmap q :q! + nmap u :if exists('b:Git_Command')call Gitv_OpenGitCommand(b:Git_Command, '', 1)endif + call append(0, split(result, '\n')) "system converts eols to \n regardless of os. + silent setlocal nomodifiable + silent setlocal readonly + 1 + return 1 + endif +endf "}}} }}} +"General Git Functions: "{{{ +fu! s:RunGitCommand(command, verbatim) "{{{ + "if verbatim returns result of system command, else + "switches to the buffer repository before running the command and switches back after. + if !a:verbatim + "switches to the buffer repository before running the command and switches back after. + let cmd = g:Gitv_GitExecutable.' --git-dir="{DIR}" '. a:command + let [result, finalCmd] = s:RunCommandRelativeToGitRepo(cmd) + else + let result = system(a:command) + let finalCmd = a:command + endif + return [result, finalCmd] +endfu "}}} +fu! s:RunCommandRelativeToGitRepo(command) "{{{ + "this runs the command verbatim but first changing to the root git dir + "it also replaces any occurance of '{DIR}' in the command with the root git dir. + let dir = s:GetRepoDir() + let workingDir = fnamemodify(dir,':h') + if workingDir == '' + return 0 + endif + + let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' + let bufferDir = getcwd() + try + execute cd.'`=workingDir`' + let finalCmd = substitute(a:command, '{DIR}', dir, 'g') + let result = system(finalCmd) + finally + execute cd.'`=bufferDir`' + endtry + return [result, finalCmd] +endfu "}}} +fu! s:GetRepoDir() "{{{ + let dir = fugitive#buffer().repo().dir() + if dir == '' + echom "No git repository could be found." + endif + return dir +endfu "}}} }}} +"Open And Update Gitv:"{{{ +fu! s:OpenGitv(extraArgs, fileMode, rangeStart, rangeEnd) "{{{ + let sanatizedArgs = a:extraArgs == "''" ? '' : a:extraArgs + let sanatizedArgs = sanatizedArgs == '""' ? '' : sanatizedArgs + let g:Gitv_InstanceCounter += 1 + if !s:IsCompatible() "this outputs specific errors + return + endif + try + if a:fileMode + call s:OpenFileMode(sanatizedArgs, a:rangeStart, a:rangeEnd) + else + call s:OpenBrowserMode(sanatizedArgs) + endif + catch /not a git repository/ + echom 'Not a git repository.' + return + endtry +endf "}}} +fu! s:IsCompatible() "{{{ + if !exists('g:loaded_fugitive') + echoerr "gitv requires the fugitive plugin to be installed." + endif + return exists('g:loaded_fugitive') +endfu "}}} +fu! s:OpenBrowserMode(extraArgs) "{{{ + "this throws an exception if not a git repo which is caught immediately + let fubuffer = fugitive#buffer() + silent Gtabedit HEAD + + if s:IsHorizontal() + let direction = 'new gitv'.'-'.g:Gitv_InstanceCounter + else + let direction = 'vnew gitv'.'-'.g:Gitv_InstanceCounter + endif + if !s:LoadGitv(direction, 0, g:Gitv_CommitStep, a:extraArgs, '', []) + return 0 + endif + call s:SetupBufferCommands(0) + "open the first commit + if g:Gitv_OpenPreviewOnLaunch + silent call s:OpenGitvCommit("Gedit", 0) + else + call s:MoveIntoPreviewAndExecute('bdelete', 0) + endif +endf "}}} +fu! s:OpenFileMode(extraArgs, rangeStart, rangeEnd) "{{{ + let relPath = fugitive#buffer().path() + pclose! + let range = a:rangeStart != a:rangeEnd ? s:GetRegexRange(a:rangeStart, a:rangeEnd) : [] + if !s:LoadGitv(&previewheight . "new gitv".'-'.g:Gitv_InstanceCounter, 0, g:Gitv_CommitStep, a:extraArgs, relPath, range) + return 0 + endif + set previewwindow + set winfixheight + let b:Gitv_FileMode = 1 + let b:Gitv_FileModeRelPath = relPath + let b:Gitv_FileModeRange = range + call s:SetupBufferCommands(1) +endf "}}} +fu! s:LoadGitv(direction, reload, commitCount, extraArgs, filePath, range) "{{{ + if a:reload + let jumpTo = line('.') "this is for repositioning the cursor after reload + endif + + "precondition: a:range should be of the form [a, b] or [] + " where a,b are integers && a 0 + let git = g:Gitv_GitExecutable + let cmd = 'for hash in ' . join(a:hashes, " ") . '; ' + let cmd .= "do " + let cmd .= git.' --git-dir="{DIR}" log --no-color --decorate=full --pretty=format:"%d %s__SEP__%ar__SEP__%an__SEP__[%h]%n" --graph -1 ${hash}; ' + let cmd .= 'done' + let finalCmd = "bash -c " . shellescape(cmd) + + let [result, cmd] = s:RunCommandRelativeToGitRepo(finalCmd) + return split(result, '\n') + else + return "" + endif +endfu "}}} +fu! s:GetRegexRange(rangeStart, rangeEnd) "{{{ + let rangeS = getline(a:rangeStart) + let rangeS = escape(rangeS, '.^$*\/[]') + let rangeS = matchstr(rangeS, '\v^\s*\zs.{-}\ze\s*$') "trim whitespace + let rangeE = getline(a:rangeEnd) + let rangeE = escape(rangeE, '.^$*\/[]') + let rangeE = matchstr(rangeE, '\v^\s*\zs.{-}\ze\s*$') "trim whitespace + let rangeS = rangeS =~ '^\s*$' ? '^[:blank:]*$' : rangeS + let rangeE = rangeE =~ '^\s*$' ? '^[:blank:]*$' : rangeE + return ['/'.rangeS.'/', '/'.rangeE.'/'] +endfu "}}} }}} +fu! s:SetupBuffer(commitCount, extraArgs, filePath, range) "{{{ + silent set filetype=gitv + let b:Gitv_CommitCount = a:commitCount + let b:Gitv_ExtraArgs = a:extraArgs + silent setlocal modifiable + silent setlocal noreadonly + silent %s/refs\/tags\//t:/ge + silent %s/refs\/remotes\//r:/ge + silent %s/refs\/heads\///ge + silent %call s:Align("__SEP__", a:filePath) + silent %s/\s\+$//e + call s:AddLoadMore() + call s:AddLocalNodes(a:filePath) + call s:AddFileModeSpecific(a:filePath, a:range, a:commitCount) + silent setlocal nomodifiable + silent setlocal readonly + silent setlocal cursorline +endf "}}} +fu! s:AddLocalNodes(filePath) "{{{ + let suffix = a:filePath == '' ? '' : ' -- '.a:filePath + let gitCmd = "diff --no-color" . suffix + let [result, cmd] = s:RunGitCommand(gitCmd, 0) + let headLine = search('^\(\(|\|\/\|\\\|\*\)\s\?\)*\s*([^)]*HEAD', 'cnw') + let headLine = headLine == 0 ? 1 : headLine + if result != "" + let line = s:AlignWithRefs(headLine, s:localUncommitedMsg) + call append(headLine-1, substitute(line, '*', '=', '')) + let headLine += 1 + endif + let gitCmd = "diff --no-color --cached" . suffix + let [result, cmd] = s:RunGitCommand(gitCmd, 0) + if result != "" + let line = s:AlignWithRefs(headLine, s:localCommitedMsg) + call append(headLine-1, substitute(line, '*', '+', '')) + endif +endfu +fu! s:AlignWithRefs(targetLine, targetStr) + "returns the targetStr prefixed with enough whitespace to align with + "the first asterisk on targetLine + if a:targetLine == 0 + return '* '.a:targetStr + endif + let line = getline(a:targetLine) + let idx = stridx(line, '(') + if idx == -1 + return '* '.a:targetStr + endif + return strpart(line, 0, idx) . a:targetStr +endfu "}}} +fu! s:AddLoadMore() "{{{ + call append(line('$'), '-- Load More --') +endfu "}}} +fu! s:AddFileModeSpecific(filePath, range, commitCount) "{{{ + if a:filePath != '' + call append(0, '-- ['.a:filePath.'] --') + if a:range != [] + call append(1, '-- Showing (up to '.a:commitCount.') commits affecting lines in the range:') + call append(2, '-- ' . a:range[0]) + call append(3, '-- ' . a:range[1]) + endif + endif +endfu "}}} +fu! s:SetupMappings() "{{{ + "operations + nmap :call OpenGitvCommit("Gedit", 0) + nmap o :call OpenGitvCommit("Gsplit", 0) + nmap O :call OpenGitvCommit("Gtabedit", 0) + nmap s :call OpenGitvCommit("Gvsplit", 0) + "force opening the fugitive buffer for the commit + nmap :call OpenGitvCommit("Gedit", 1) + + nmap q :call CloseGitv() + nmap u :call LoadGitv('', 1, b:Gitv_CommitCount, b:Gitv_ExtraArgs, GetRelativeFilePath(), GetRange()) + nmap co :call CheckOutGitvCommit() + + nmap D :call DiffGitvCommit() + vmap D :call DiffGitvCommit() + + nmap S :call StatGitvCommit() + vmap S :call StatGitvCommit() + + vmap m :call MergeBranches() + + "movement + nmap x :call JumpToBranch(0) + nmap X :call JumpToBranch(1) + nmap r :call JumpToRef(0) + nmap R :call JumpToRef(1) + nmap P :call JumpToHead() + + "misc + nmap git :Git +endf "}}} +fu! s:SetupBufferCommands(fileMode) "{{{ + silent command! -buffer -nargs=* -complete=customlist,s:fugitive_GitComplete Git call MoveIntoPreviewAndExecute("unsilent Git ",1)|normal u +endfu "}}} +fu! s:ResizeWindow(fileMode) "{{{ + if a:fileMode "window height determined by &previewheight + return + endif + if !s:IsHorizontal() + "size window based on longest line + let longest = max(map(range(1, line('$')), "virtcol([v:val, '$'])")) + if longest > &columns/2 + "potentially auto change to horizontal + if s:AutoHorizontal() + "switching to horizontal + let b:Gitv_AutoHorizontal=1 + wincmd K + call s:ResizeWindow(a:fileMode) + return + else + let longest = &columns/2 + endif + endif + exec "vertical resize " . longest + else + "size window based on num lines + call s:ResizeHorizontal() + endif +endf "}}} }}} +"Utilities:"{{{ +fu! s:GetGitvSha(lineNumber) "{{{ + let l = getline(a:lineNumber) + let sha = matchstr(l, "\\[\\zs[0-9a-f]\\{7}\\ze\\]$") + return sha +endf "}}} +fu! s:GetGitvRefs(line) "{{{ + let l = getline(a:line) + let refstr = matchstr(l, "^\\(\\(|\\|\\/\\|\\\\\\|\\*\\)\\s\\?\\)*\\s\\+(\\zs.\\{-}\\ze)") + let refs = split(refstr, ', ') + return refs +endf "}}} +fu! s:GetConfirmString(list, ...) "{{{ {{{ + "returns a string to be used with confirm out of the choices in a:list + "any extra arguments are appended to the list of choices + "attempts to assign unique shortcut keys to every choice + "NOTE: choices must not be single letters and duplicates will be removed. + let totalList = a:list + a:000 + let G = s:ConfirmStringBipartiteGraph(totalList) + let matches = s:MaxBipartiteMatching(G) + let choices = [] + for choice in totalList + let shortcutChar = get(matches, choice, '') + if shortcutChar != '' + call add(choices, substitute(choice, '\c'.shortcutChar, '\&\0', '')) + endif + endfor + return join(choices, "\n") +endfu "}}} +"Max Bipartite Matching Functions: "{{{ +let s:SOURCE_NODE = '__SOURCE__' +let s:SINK_NODE = '__SINK__' +fu! s:ConfirmStringBipartiteGraph(list) "{{{ + let G = {} + let G[s:SOURCE_NODE] = {} + for word in a:list + let G[word] = {} + let G[s:SOURCE_NODE][word] = 1 + for i in range(len(word)) + let char = tolower(word[i]) + let G[word][char] = 1 + if !has_key(G, char) | let G[char] = {} | endif + let G[char][s:SINK_NODE] = 1 + endfor + endfor + return G +endfu "}}} +fu! s:MaxBipartiteMatching(G) "{{{ + let f = s:InitialiseFlow(a:G) + let path = s:GetPathInResidual(a:G, f, s:SOURCE_NODE, s:SINK_NODE) + while path != [] + let pathCost = 100000 "max path cost should be 1 so this is effectively infinite + for [u, v] in s:Partition(path) + let pathCost = min([pathCost, s:GetEdge(a:G, u, v) - s:GetEdge(f, u, v)]) + endfor + for [u, v] in s:Partition(path) + let f[u][v] = s:GetEdge(f, u, v) + pathCost + let f[v][u] = -s:GetEdge(f, u, v) + endfor + let path = s:GetPathInResidual(a:G, f, s:SOURCE_NODE, s:SINK_NODE) + endwhile + "f holds max flow for each edge, due to construction: include edge iff flow is 1 + let returnDict = {} + for n1 in keys(f) + for [n2, val] in items(f[n1]) + if val == 1 + let returnDict[n1] = n2 + endif + endfor + endfor + return returnDict +endfu "}}} +fu! s:InitialiseFlow(G) "{{{ + let f = {} + for u in keys(a:G) + let f[u] = {} + for v in keys(a:G[u]) + let f[u][v] = 0 + if !has_key(f, v) | let f[v] = {} | endif + let f[v][u] = 0 + endfor + endfor + return f +endfu "}}} +fu! s:GetPathInResidual(G, f, s, t) "{{{ + "setup residual network + let Gf = deepcopy(a:f, 1) + for u in keys(a:f) + for v in keys(a:f[u]) + let Gf[u][v] = s:GetEdge(a:G, u, v) - a:f[u][v] + endfor + endfor + return s:BFS(Gf, a:s, a:t) +endfu "}}} +fu! s:Partition(path) "{{{ + "returns a list of [u,v] for the path + if len(a:path) < 2 | return a:path | endif + let parts = [] + for i in range(len(a:path)-1) + let parts = add(parts, [a:path[i], a:path[i+1]]) + endfor + return parts +endfu "}}} +fu! s:BFS(G, s, t) "{{{ + "BFS for t from s -- returns path + return s:BFSHelp(a:G, a:s, a:t, [], [], {}) +endfu "}}} +fu! s:BFSHelp(G, s, t, q, acc, visited) "{{{ + if a:s == a:t + return a:acc + [a:t] + endif + let a:visited[a:s] = 1 + let children = s:GetEdges(a:G, a:s) + call filter(children, '!get(a:visited, v:val, 0)') + if empty(a:q) && empty(children) | return [] | endif + + let newq = empty(children) ? a:q : a:q + [[a:acc+[a:s], children]] + let newAcc = a:acc + if type(newq[0]) == type([]) + let newAcc = newq[0][0] + let newq = newq[0][1] + newq[1:] + endif + return s:BFSHelp(a:G, newq[0], a:t, newq[1:], newAcc, a:visited) +endfu "}}} +fu! s:GetEdge(G, u, v) "{{{ + "returns 0 if edge does not exist + return get(get(a:G, a:u, {}), a:v, 0) +endfu "}}} +fu! s:GetEdges(G, u) "{{{ + let e = [] + for k in keys(get(a:G, a:u, {})) + let e += a:G[a:u][k] > 0 ? [k] : [] + endfor + return e +endfu "}}} }}} }}} +fu! s:RecordBufferExecAndWipe(cmd, wipe) "{{{ + "this should be used to replace the buffer in a window + let buf = bufnr('%') + exec a:cmd + if a:wipe + "safe guard against wiping out buffer you're in + if bufnr('%') != buf && bufexists(buf) + " ignore errors from bdelete -- the user won't care if it's + " already deleted + exec 'silent! bdelete ' . buf + endif + endif +endfu "}}} +fu! s:MoveIntoPreviewAndExecute(cmd, tryToOpenNewWin) "{{{ + if winnr("$") == 1 "is the only window + call s:AttemptToCreateAPreviewWindow(a:tryToOpenNewWin, a:cmd) + return + endif + let horiz = s:IsHorizontal() + let filem = s:IsFileMode() + let currentWin = winnr() + + if horiz || filem + wincmd j + else + wincmd l + endif + + if currentWin == winnr() "haven't moved anywhere + call s:AttemptToCreateAPreviewWindow(a:tryToOpenNewWin, a:cmd) + return + endif + + silent exec a:cmd + if horiz || filem + wincmd k + else + wincmd h + endif +endfu "}}} +fu! s:AttemptToCreateAPreviewWindow(shouldAttempt, cmd) "{{{ + if a:shouldAttempt + call s:CreateNewPreviewWindow() + call s:MoveIntoPreviewAndExecute(a:cmd, 0) + else + echoerr "No preview window detected." + endif +endfu "}}} +fu! s:CreateNewPreviewWindow() "{{{ + "this should not be called by anything other than AttemptToCreateAPreviewWindow + let horiz = s:IsHorizontal() + let filem = s:IsFileMode() + if horiz || filem + Gsplit HEAD + else + Gvsplit HEAD + endif + wincmd x +endfu "}}} +fu! s:IsHorizontal() "{{{ + "NOTE: this can only tell you if horizontal while cursor in browser window + let horizGlobal = exists('g:Gitv_OpenHorizontal') && g:Gitv_OpenHorizontal == 1 + let horizBuffer = exists('b:Gitv_AutoHorizontal') && b:Gitv_AutoHorizontal == 1 + return horizGlobal || horizBuffer +endf "}}} +fu! s:AutoHorizontal() "{{{ + return exists('g:Gitv_OpenHorizontal') && + \ type(g:Gitv_OpenHorizontal) == type("") && + \ g:Gitv_OpenHorizontal ==? 'auto' +endf "}}} +fu! s:IsFileMode() "{{{ + return exists('b:Gitv_FileMode') && b:Gitv_FileMode == 1 +endf "}}} +fu! s:ResizeHorizontal() "{{{ + let lines = line('$') + if lines > (&lines/2)-2 + let lines = (&lines/2)-2 + endif + exec "resize " . lines +endf "}}} +fu! s:GetRelativeFilePath() "{{{ + return exists('b:Gitv_FileModeRelPath') ? b:Gitv_FileModeRelPath : '' +endf "}}} +fu! s:GetRange() "{{{ + return exists('b:Gitv_FileModeRange') ? b:Gitv_FileModeRange : [] +endfu "}}} +fu! s:SetRange(idx, value) "{{{ + "idx - {0,1}, 0 for beginning, 1 for end. + let b:Gitv_FileModeRange[a:idx] = a:value +endfu "}}} +fu! s:FoldToRevealOnlyRange(rangeStart, rangeEnd) "{{{ + setlocal foldmethod=manual + normal zE + let rangeS = '/'.escape(matchstr(a:rangeStart, '/\zs.*\ze/'), '~[]/\.^$*').'/' + let rangeE = '/'.escape(matchstr(a:rangeEnd, '/\zs.*\ze/'), '~[]/\.^$*').'/' + exec '1,'.rangeS.'-1fold' + exec rangeE.'+1,$fold' +endfu "}}} +fu! s:OpenRelativeFilePath(sha, geditForm) "{{{ + let relPath = s:GetRelativeFilePath() + if relPath == '' + return + endif + let cmd = a:geditForm . " " . a:sha . ":" . relPath + let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(a:geditForm=='Gedit').')' + call s:MoveIntoPreviewAndExecute(cmd, 1) + let range = s:GetRange() + if range != [] + let rangeS = escape(range[0], '"') + let rangeE = escape(range[1], '"') + call s:MoveIntoPreviewAndExecute('call s:FoldToRevealOnlyRange("'.rangeS.'", "'.rangeE.'")', 0) + endif +endf "}}} }}} +"Mapped Functions:"{{{ +"Operations: "{{{ +fu! s:OpenGitvCommit(geditForm, forceOpenFugitive) "{{{ + if getline('.') == "-- Load More --" + call s:LoadGitv('', 1, b:Gitv_CommitCount+g:Gitv_CommitStep, b:Gitv_ExtraArgs, s:GetRelativeFilePath(), s:GetRange()) + return + endif + if s:IsFileMode() && getline('.') =~ "^-- \\[.*\\] --$" + call s:OpenWorkingCopy(a:geditForm) + return + endif + if getline('.') =~ s:localUncommitedMsg.'$' + call s:OpenWorkingDiff(a:geditForm, 0) + return + endif + if getline('.') =~ s:localCommitedMsg.'$' + call s:OpenWorkingDiff(a:geditForm, 1) + return + endif + if s:IsFileMode() && getline('.') =~ '^-- /.*/$' + if s:EditRange(matchstr(getline('.'), '^-- /\zs.*\ze/$')) + normal u + endif + return + endif + let sha = s:GetGitvSha(line('.')) + if sha == "" + return + endif + if s:IsFileMode() && !a:forceOpenFugitive + call s:OpenRelativeFilePath(sha, a:geditForm) + else + let cmd = a:geditForm . " " . sha + let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(a:geditForm=='Gedit').')' + call s:MoveIntoPreviewAndExecute(cmd, 1) + call s:MoveIntoPreviewAndExecute('setlocal fdm=syntax', 0) + endif +endf +fu! s:OpenWorkingCopy(geditForm) + let fp = s:GetRelativeFilePath() + let form = a:geditForm[1:] "strip off the leading 'G' + let cmd = form . " " . fugitive#buffer().repo().tree() . "/" . fp + let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(form=='edit').')' + call s:MoveIntoPreviewAndExecute(cmd, 1) +endfu +fu! s:OpenWorkingDiff(geditForm, staged) + let winCmd = a:geditForm[1:] == 'edit' ? '' : a:geditForm[1:] + if s:IsFileMode() + let fp = s:GetRelativeFilePath() + let suffix = ' -- '.fp + let g:Gitv_InstanceCounter += 1 + let winCmd = 'new gitv'.'-'.g:Gitv_InstanceCounter + else + let suffix = '' + endif + if a:staged + let cmd = 'call Gitv_OpenGitCommand(\"diff --no-color --cached'.suffix.'\", \"'.winCmd.'\")' + else + let cmd = 'call Gitv_OpenGitCommand(\"diff --no-color'.suffix.'\", \"'.winCmd.'\")' + endif + let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(winCmd=='').')' + call s:MoveIntoPreviewAndExecute(cmd, 1) +endfu +fu! s:EditRange(rangeDelimiter) + let range = s:GetRange() + let rangeDelimWithSlashes = '/'.a:rangeDelimiter.'/' + let idx = rangeDelimWithSlashes == range[0] ? 0 : rangeDelimWithSlashes == range[1] ? 1 : -1 + if idx == -1 + return 0 + endif + let value = input("Enter new range regex: ", a:rangeDelimiter) + let value = '/'.value.'/' + if value == range[idx] + return 0 "no need to update + endif + call s:SetRange(idx, value) + return 1 +endfu "}}} +fu! s:CheckOutGitvCommit() "{{{ + let allrefs = s:GetGitvRefs('.') + let sha = s:GetGitvSha(line('.')) + if sha == "" + return + endif + let refs = allrefs + [sha] + let refstr = s:GetConfirmString(refs, 'Cancel') + let choice = confirm("Checkout commit:", refstr) + if choice == 0 + return + endif + let choice = get(refs, choice-1, "") + if choice == "" + return + endif + let choice = substitute(choice, "^t:", "", "") + let choice = substitute(choice, "^r:", "", "") + if s:IsFileMode() + let relPath = s:GetRelativeFilePath() + let choice .= " -- " . relPath + endif + exec "Git checkout " . choice +endf "}}} +fu! s:CloseGitv() "{{{ + if s:IsFileMode() + q + else + if g:Gitv_WipeAllOnClose + silent windo setlocal bufhidden=wipe + endif + let moveLeft = tabpagenr() == tabpagenr('$') ? 0 : 1 + tabc + if moveLeft && tabpagenr() != 1 + tabp + endif + endif +endf "}}} +fu! s:DiffGitvCommit() range "{{{ + if !s:IsFileMode() + echom "Diffing is not possible in browser mode." + return + endif + let shafirst = s:GetGitvSha(a:firstline) + let shalast = s:GetGitvSha(a:lastline) + if shafirst == "" || shalast == "" + return + endif + if a:firstline != a:lastline + call s:OpenRelativeFilePath(shafirst, "Gedit") + endif + call s:MoveIntoPreviewAndExecute("Gdiff " . shalast, a:firstline != a:lastline) +endf "}}} +fu! s:MergeBranches() range "{{{ + if a:firstline == a:lastline + echom 'Already up to date.' + return + endif + let refs = s:GetGitvRefs(a:firstline) + let refs += s:GetGitvRefs(a:lastline) + call filter(refs, 'v:val !=? "HEAD"') + if len(refs) < 2 + echom 'Not enough refs found to perform a merge.' + return + endif + let target = confirm("Choose target branch to merge into:", s:GetConfirmString(refs, "Cancel")) + if target == 0 || get(refs, target-1, '')=='' | return | endif + let target = remove(refs, target-1) + let target = substitute(target, "^[tr]:", "", "") + + let merge = confirm("Choose branch to merge in to '".target."':", s:GetConfirmString(refs, "Cancel")) + if merge == 0 || get(refs, merge-1, '')==''| return | endif + let merge = refs[merge-1] + let merge = substitute(merge, "^[tr]:", "", "") + + let choices = "&Yes\n&No\n&Cancel" + let ff = confirm("Use fast-forward, if possible, to merge '". merge . "' in to '" . target ."'?", choices) + if ff == 0 || ff == 3 | return | endif + let ff = ff == 1 ? ff : 0 + + if ff + echom "Merging '" . merge . "' in to '" . target . "' with fast-forward." + else + echom "Merging '" . merge . "' in to '" . target . "' without fast-forward." + endif + call s:PerformMerge(target, merge, ff) +endfu +fu! s:PerformMerge(target, mergeBranch, ff) abort + exec 'Git checkout ' . a:target + exec 'Git merge ' . (a:ff ? '--ff ' : '--no-ff ') . a:mergeBranch + + if g:Gitv_PromptToDeleteMergeBranch + let choices = "&Yes\n&No\n&Cancel" + let delBranch = confirm("Delete merge branch: '" . a:mergeBranch . "'?", choices) + if delBranch == 0 || delBranch == 3 | return | endif + let delBranch = delBranch == 1 ? delBranch : 0 + if delBranch + exec 'Git branch -d ' . a:mergeBranch + endif + endif +endfu "}}} +fu! s:StatGitvCommit() range "{{{ + let shafirst = s:GetGitvSha(a:firstline) + let shalast = s:GetGitvSha(a:lastline) + if shafirst == "" || shalast == "" + return + endif + let cmd = 'diff --no-color '.shafirst + if shafirst != shalast + let cmd .= ' '.shalast + endif + let cmd .= ' --stat' + let cmd = "call s:SetupStatBuffer('".cmd."')" + if s:IsFileMode() + exec cmd + else + call s:MoveIntoPreviewAndExecute(cmd, 1) + endif +endf +fu! s:SetupStatBuffer(cmd) + silent let res = Gitv_OpenGitCommand(a:cmd, s:IsFileMode()?'vnew':'') + if res + silent set filetype=gitv + endif +endfu "}}} }}} +"Movement: "{{{ +fu! s:JumpToBranch(backward) "{{{ + if a:backward + silent! ?|/\||\\?-1 + else + silent! /|\\\||\//+1 + endif +endf "}}} +fu! s:JumpToRef(backward) "{{{ + if a:backward + silent! ?^\(\(|\|\/\|\\\|\*\)\s\=\)\+\s\+\zs( + else + silent! /^\(\(|\|\/\|\\\|\*\)\s\?\)\+\s\+\zs(/ + endif +endf "}}} +fu! s:JumpToHead() "{{{ + silent! /^\(\(|\|\/\|\\\|\*\)\s\?\)\+\s\+\zs(HEAD/ +endf "}}} +"}}} }}} +"Align And Truncate Functions: "{{{ +if exists("*strwidth") "{{{ + "introduced in Vim 7.3 + fu! s:StringWidth(string) + return strwidth(a:string) + endfu +else + fu! s:StringWidth(string) + return len(split(a:string,'\zs')) + endfu +end "}}} +fu! s:Align(seperator, filePath) range "{{{ + let lines = getline(a:firstline, a:lastline) + call map(lines, 'split(v:val, a:seperator)') + + let newlines = copy(lines) + call filter(newlines, 'len(v:val)>1') + let maxLens = s:MaxLengths(newlines) + + let newlines = [] + for tokens in lines + if len(tokens)>1 + let newline = [] + for i in range(len(tokens)) + let token = tokens[i] + call add(newline, token . repeat(' ', maxLens[i]-s:StringWidth(token)+1)) + endfor + call add(newlines, newline) + else + call add(newlines, tokens) + endif + endfor + + if g:Gitv_TruncateCommitSubjects + call s:TruncateLines(newlines, a:filePath) + endif + + call map(newlines, "join(v:val)") + call setline(a:firstline, newlines) +endfu "}}} +fu! s:TruncateLines(lines, filePath) "{{{ + "truncates the commit subject for any line > &columns + call map(a:lines, "s:TruncateHelp(v:val, a:filePath)") +endfu "}}} +fu! s:TruncateHelp(line, filePath) "{{{ + let length = s:StringWidth(join(a:line)) + let maxWidth = s:IsHorizontal() ? &columns : &columns/2 + let maxWidth = a:filePath != '' ? winwidth(0) : maxWidth + if length > maxWidth + let delta = length - maxWidth + "offset = 3 for the elipsis and 1 for truncation + let offset = 3 + 1 + if a:line[0][-(delta + offset + 1):] =~ "^\\s\\+$" + let extension = " " + else + let extension = "..." + endif + let a:line[0] = a:line[0][:-(delta + offset)] . extension + endif + return a:line +endfu "}}} +fu! s:MaxLengths(colls) "{{{ + "precondition: coll is a list of lists of strings -- should be rectangular + "returns a list of maximum string lengths + let lengths = [] + for x in a:colls + for y in range(len(x)) + let length = s:StringWidth(x[y]) + if length > get(lengths, y, 0) + if len(lengths)-1 < y + call add(lengths, length) + else + let lengths[y] = length + endif + endif + endfor + endfor + return lengths +endfu "}}} }}} +"Fugitive Functions: "{{{ +"These functions are lifted directly from fugitive and modified only to work with gitv. +function! s:fugitive_sub(str,pat,rep) abort "{{{ + return substitute(a:str,'\v\C'.a:pat,a:rep,'') +endfunction "}}} +function! s:fugitive_GitComplete(A,L,P) abort "{{{ + if !exists('s:exec_path') + let s:exec_path = s:fugitive_sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','') + endif + let cmds = map(split(glob(s:exec_path.'/git-*'),"\n"),'s:fugitive_sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")') + if a:L =~ ' [[:alnum:]-]\+ ' + return fugitive#buffer().repo().superglob(a:A) + elseif a:A == '' + return cmds + else + return filter(cmds,'v:val[0 : strlen(a:A)-1] ==# a:A') + endif +endfunction "}}} }}} + +let &cpo = s:savecpo +unlet s:savecpo + + " vim:fdm=marker diff --git a/vimfiles/syntax/gitv.vim b/vimfiles/syntax/gitv.vim new file mode 100644 index 0000000..ed261e4 --- /dev/null +++ b/vimfiles/syntax/gitv.vim @@ -0,0 +1,98 @@ +" Vim syntax file +" Language: Custom git log output +" Maintainer: Greg Sexton +" Last Change: 2011-04-08 +" + +if exists("b:current_syntax") + finish +endif + +"set conceallevel=2 +"set concealcursor=n + +syn match gitvSubject /.*/ + +syn match gitvDate /\(\d\+ years\?, \)\?\d\+ \%(second\|seconds\|minute\|minutes\|hour\|hours\|day\|days\|week\|weeks\|month\|months\|year\) ago/ contained containedin=gitvSubject +syn match gitvHash /\[[0-9a-f]\{7}\]$/ contained containedin=gitvSubject + +syn match gitvGraphEdge9 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge0,gitvRef,gitvSubject skipwhite +syn match gitvGraphEdge8 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge9,gitvRef,gitvSubject skipwhite +syn match gitvGraphEdge7 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge8,gitvRef,gitvSubject skipwhite +syn match gitvGraphEdge6 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge7,gitvRef,gitvSubject skipwhite +syn match gitvGraphEdge5 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge6,gitvRef,gitvSubject skipwhite +syn match gitvGraphEdge4 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge5,gitvRef,gitvSubject skipwhite +syn match gitvGraphEdge3 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge4,gitvRef,gitvSubject skipwhite +syn match gitvGraphEdge2 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge3,gitvRef,gitvSubject skipwhite +syn match gitvGraphEdge1 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge2,gitvRef,gitvSubject skipwhite +syn match gitvGraphEdge0 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge1,gitvRef,gitvSubject skipwhite +syn match gitvGraphEdgeH /_/ contained containedin=gitvGraphEdge0,gitvGraphEdge1,gitvGraphEdge2,gitvGraphEdge3,gitvGraphEdge4,gitvGraphEdge5,gitvGraphEdge6,gitvGraphEdge7,gitvGraphEdge8,gitvGraphEdge9 + +syn match gitvRef /\s*(.\{-})/ nextgroup=gitvSubject skipwhite +syn match gitvRefTag /t:\zs.\{-}\ze\(, \|)\)/ contained containedin=gitvRef +syn match gitvRefRemote /r:\zs.\{-}\ze\(, \|)\)/ contained containedin=gitvRef +syn match gitvRefHead /HEAD/ contained containedin=gitvRef + +syn match gitvLoadMore /^-- Load More --$/ +syn match gitvWorkingCopy /^-- \[.*\] --$/ + +syn match gitvRange /^-- Showing .* in the range:$/ +syn match gitvRangeFromTo /^-- \/.*\/$/ + +syn match gitvLocalUncommit /Local uncommitted changes, not checked in to index\.$/ contained containedin=gitvSubject +syn match gitvLocalCommited /Local changes checked in to index but not committed\.$/ contained containedin=gitvSubject +syn match gitvLocalCommitedNode /+/ contained containedin=gitvGraphEdge0,gitvGraphEdge1,gitvGraphEdge2,gitvGraphEdge3,gitvGraphEdge4,gitvGraphEdge5,gitvGraphEdge6,gitvGraphEdge7,gitvGraphEdge8,gitvGraphEdge9 +syn match gitvLocalUncommitNode /=/ contained containedin=gitvGraphEdge0,gitvGraphEdge1,gitvGraphEdge2,gitvGraphEdge3,gitvGraphEdge4,gitvGraphEdge5,gitvGraphEdge6,gitvGraphEdge7,gitvGraphEdge8,gitvGraphEdge9 + +syn match gitvAddedMarks /|\s\+\d\+ \zs+*-*\ze$/ contained containedin=gitvSubject +syn match gitvAddedMarks /|\s\+Bin \zs\d\+ -> \d\+\ze bytes$/ contained containedin=gitvSubject +syn match gitvRemovedMarks /-*$/ contained containedin=gitvAddedMarks +syn match gitvRemovedMarks /\d\+\ze ->/ contained containedin=gitvAddedMarks +syn match gitvSeperatorMarks /\s\+->\s\+/ contained containedin=gitvAddedMarks + +hi def link gitvHash Number +hi def link gitvRef Directory +hi def link gitvRefTag String +hi def link gitvRefRemote Statement +hi def link gitvRefHead Special +hi def link gitvDate Statement +hi def link gitvSubject Normal +hi def link gitvLoadMore Question +hi def link gitvWorkingCopy Question +hi def link gitvRange ModeMsg +hi def link gitvRangeFromTo Function + +hi def link gitvAddedMarks diffAdded +hi def link gitvRemovedMarks diffRemoved +hi def link gitvSeperatorMarks Normal + +hi def link gitvGraphEdge0 Delimiter + +if &background == "dark" + highlight default gitvGraphEdge1 ctermfg=magenta guifg=green1 + highlight default gitvGraphEdge2 ctermfg=green guifg=yellow1 + highlight default gitvGraphEdge3 ctermfg=yellow guifg=orange1 + highlight default gitvGraphEdge4 ctermfg=cyan guifg=greenyellow + highlight default gitvGraphEdge5 ctermfg=red guifg=springgreen1 + highlight default gitvGraphEdge6 ctermfg=yellow guifg=cyan1 + highlight default gitvGraphEdge7 ctermfg=green guifg=slateblue1 + highlight default gitvGraphEdge8 ctermfg=cyan guifg=magenta1 + highlight default gitvGraphEdge9 ctermfg=magenta guifg=purple1 +else + highlight default gitvGraphEdge1 ctermfg=darkyellow guifg=orangered3 + highlight default gitvGraphEdge2 ctermfg=darkgreen guifg=orange2 + highlight default gitvGraphEdge3 ctermfg=blue guifg=yellow3 + highlight default gitvGraphEdge4 ctermfg=darkmagenta guifg=olivedrab4 + highlight default gitvGraphEdge5 ctermfg=red guifg=green4 + highlight default gitvGraphEdge6 ctermfg=darkyellow guifg=paleturquoise3 + highlight default gitvGraphEdge7 ctermfg=darkgreen guifg=deepskyblue4 + highlight default gitvGraphEdge8 ctermfg=blue guifg=darkslateblue + highlight default gitvGraphEdge9 ctermfg=darkmagenta guifg=darkviolet +endif + +highlight default gitvLocalCommitedNode ctermfg=green guifg=green +highlight default gitvLocalUncommitNode ctermfg=red guifg=red +highlight default gitvLocalCommited gui=bold +highlight default gitvLocalUncommit gui=bold + +let b:current_syntax = "gitv"