vimsuite/vimfiles/autoload/merginal.vim
2015-02-19 12:55:54 +01:00

1238 lines
45 KiB
VimL
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"Use vimproc if available under windows to prevent opening a console window
function! merginal#system(command,...)
if has('win32') && exists(':VimProcBang') "We don't need vimproc when we use linux
if empty(a:000)
return vimproc#system(a:command)
else
return vimproc#system(a:command,a:000[0])
endif
else
if empty(a:000)
return system(a:command)
else
return system(a:command,a:000[0])
endif
endif
endfunction
"Opens a file that belongs to a repo in a window that already belongs to that
"repo. Creates a new window if can't find suitable window.
function! merginal#openFileDecidedWindow(repo,fileName)
"We have to check with bufexists, because bufnr also match prefixes of the
"file name
let l:fileBuffer=-1
if bufexists(a:fileName)
let l:fileBuffer=bufnr(a:fileName)
endif
"We have to check with bufloaded, because bufwinnr also matches closed
"windows...
let l:windowToOpenIn=-1
if bufloaded(l:fileBuffer)
let l:windowToOpenIn=bufwinnr(l:fileBuffer)
endif
"If we found an open window with the correct file, we jump to it
if -1<l:windowToOpenIn
execute l:windowToOpenIn.'wincmd w'
else
"Check if the previous window can be used
let l:previousWindow=winnr('#')
if s:isWindowADisposableWindowOfRepo(l:previousWindow,a:repo)
execute winnr('#').'wincmd w'
else
"If the previous window can't be used, check if any open
"window can be used
let l:windowsToOpenTheFileIn=merginal#getListOfDisposableWindowsOfRepo(a:repo)
if empty(l:windowsToOpenTheFileIn)
"If no open window can be used, open a new Vim window
new
else
execute l:windowsToOpenTheFileIn[0].'wincmd w'
endif
endif
if -1<l:fileBuffer
"If the buffer is already open, jump to it
execute 'buffer '.l:fileBuffer
else
"Otherwise, load it
execute 'edit '.fnameescape(a:fileName)
endif
endif
diffoff "Just in case...
endfunction
"Check if the current window is modifiable, saved, and belongs to the repo
function! s:isCurrentWindowADisposableWindowOfRepo(repo)
if !&modifiable
return 0
endif
if &modified
return 0
endif
try
return a:repo==fugitive#repo()
catch
return 0
endtry
endfunction
"Calls s:isCurrentWindowADisposableWindowOfRepo with a window number
function! s:isWindowADisposableWindowOfRepo(winnr,repo)
let l:currentWindow=winnr()
try
execute a:winnr.'wincmd w'
return s:isCurrentWindowADisposableWindowOfRepo(a:repo)
finally
execute l:currentWindow.'wincmd w'
endtry
endfunction
"Get a list of windows that yield true with s:isWindowADisposableWindowOfRepo
function! merginal#getListOfDisposableWindowsOfRepo(repo)
let l:result=[]
let l:currentWindow=winnr()
windo if s:isCurrentWindowADisposableWindowOfRepo(a:repo) | call add(l:result,winnr()) | endif
execute l:currentWindow.'wincmd w'
return l:result
endfunction
"Get the repository that belongs to a window
function! s:getRepoOfWindow(winnr)
"Ignore bad window numbers
if a:winnr<=0
return {}
endif
let l:currentWindow=winnr()
try
execute a:winnr.'wincmd w'
return fugitive#repo()
catch
return {}
finally
execute l:currentWindow.'wincmd w'
endtry
endfunction
"Reload the buffers
function! merginal#reloadBuffers()
let l:currentWindow=winnr()
try
silent windo if ''==&buftype
\| edit
\| endif
catch
"do nothing
endtry
execute l:currentWindow.'wincmd w'
endfunction
"Exactly what it says on tin
function! merginal#runGitCommandInTreeReturnResult(repo,command)
let l:dir=getcwd()
execute 'cd '.fnameescape(a:repo.tree())
try
return merginal#system(a:repo.git_command().' '.a:command)
finally
execute 'cd '.fnameescape(l:dir)
endtry
endfunction
"Like merginal#runGitCommandInTreeReturnResult but split result to lines
function! merginal#runGitCommandInTreeReturnResultLines(repo,command)
return split(merginal#runGitCommandInTreeReturnResult(a:repo, a:command),
\ '\r\n\|\n\|\r')
endfunction
function! s:cleanup_term_codes(s)
let s = substitute(a:s, '\t', ' ', 'g')
" Remove terminal escape codes for colors (based on
" www.commandlinefu.com/commands/view/3584/).
let s = substitute(s, '\v\[([0-9]{1,3}(;[0-9]{1,3})?)?[m|K]', '', 'g')
return s
endfunction
function! merginal#runGitCommandInTreeEcho(repo,command)
let l:lines = merginal#runGitCommandInTreeReturnResultLines(a:repo, a:command)
if len(l:lines) == 1
" Output a single/empty line to make Vim wait for Enter.
echo ' '
endif
for l:line in l:lines
echo "[output]" s:cleanup_term_codes(l:line)
endfor
endfunction
"Returns 1 if there was a merginal bufffer to close
function! merginal#closeMerginalBuffer()
let l:merginalWindowNumber=bufwinnr('Merginal:')
if 0<=l:merginalWindowNumber
let l:currentWindow=winnr()
try
execute l:merginalWindowNumber.'wincmd w'
wincmd q
"If the current window is after the merginal window, closing the
"merginal window will decrease the current window's nubmer.
if l:merginalWindowNumber<l:currentWindow
let l:currentWindow=l:currentWindow-1
endif
return 1
finally
execute l:currentWindow.'wincmd w'
endtry
end
return 0
endfunction
"Returns 1 if a new buffer was opened, 0 if it already existed
function! merginal#openTuiBuffer(bufferName,inWindow)
let l:repo=fugitive#repo()
let l:tuiBufferWindow=bufwinnr(bufnr(a:bufferName))
if -1<l:tuiBufferWindow "Jump to the already open buffer
execute l:tuiBufferWindow.'wincmd w'
else "Open a new buffer
if merginal#isMerginalWindow(a:inWindow)
execute a:inWindow.'wincmd w'
enew
else
40vnew
endif
setlocal buftype=nofile
setlocal bufhidden=wipe
setlocal nomodifiable
setlocal winfixwidth
setlocal winfixheight
setlocal nonumber
setlocal norelativenumber
execute 'silent file '.a:bufferName
call fugitive#detect(l:repo.dir())
endif
"At any rate, reassign the active repository
let b:merginal_repo=l:repo
let b:headerLinesCount=0
"Check and return if a new buffer was created
return -1==l:tuiBufferWindow
endfunction
"Check if a window belongs to Merginal
function! merginal#isMerginalWindow(winnr)
if a:winnr<=0
return 0
endif
let l:buffer=winbufnr(a:winnr)
if l:buffer<=0
return 0
endif
"check for the merginal repo buffer variable
return !empty(getbufvar(l:buffer,'merginal_repo'))
endfunction
"For the branch in the specified line, retrieve:
" - type: 'local', 'remote' or 'detached'
" - isCurrent, isLocal, isRemote, isDetached
" - remote: the name of the remote or '' for local branches
" - name: the name of the branch, without the remote
" - handle: the named used for referring the branch in git commands
function! merginal#branchDetails(lineNumber)
if !exists('b:merginal_repo')
throw 'Unable to get branch details outside the merginal window'
endif
if line(a:lineNumber)<=b:headerLinesCount
throw 'Unable to get branch details for the header of the merginal window'
endif
let l:line=getline(a:lineNumber)
let l:result={}
"Check if this branch is the currently selected one
let l:result.isCurrent=('*'==l:line[0])
let l:line=l:line[2:]
let l:detachedMatch=matchlist(l:line,'\v^\(detached from ([^/]+)%(/(.*))?\)$')
if !empty(l:detachedMatch)
let l:result.type='detached'
let l:result.isLocal=0
let l:result.isRemote=0
let l:result.isDetached=1
let l:result.remote=l:detachedMatch[1]
let l:result.name=l:detachedMatch[2]
if empty(l:detachedMatch[2])
let l:result.handle=l:detachedMatch[1]
else
let l:result.handle=l:detachedMatch[1].'/'.l:detachedMatch[2]
endif
return l:result
endif
let l:remoteMatch=matchlist(l:line,'\v^remotes/([^/]+)%(/(\S*))%( \-\> (\S+))?$')
if !empty(l:remoteMatch)
let l:result.type='remote'
let l:result.isLocal=0
let l:result.isRemote=1
let l:result.isDetached=0
let l:result.remote=l:remoteMatch[1]
let l:result.name=l:remoteMatch[2]
if empty(l:remoteMatch[2])
let l:result.handle=l:remoteMatch[1]
else
let l:result.handle=l:remoteMatch[1].'/'.l:remoteMatch[2]
endif
return l:result
endif
let l:result.type='local'
let l:result.isLocal=1
let l:result.isRemote=0
let l:result.isDetached=0
let l:result.remote=''
let l:result.name=l:line
let l:result.handle=l:line
return l:result
endfunction
"For the file in the specified line, retrieve:
" - name: the name of the file
function! merginal#fileDetails(lineNumber)
if !exists('b:merginal_repo')
throw 'Unable to get file details outside the merginal window'
endif
if line(a:lineNumber)<=b:headerLinesCount
throw 'Unable to get branch details for the header of the merginal window'
endif
let l:line=getline(a:lineNumber)
let l:result={}
let l:result.name=l:line
return l:result
endfunction
"For the commit in the specified line, retrieve it's hash
function! merginal#commitHash(lineNumber)
if !exists('b:merginal_repo')
throw 'Unable to get commit details outside the merginal window'
endif
if line(a:lineNumber)<=b:headerLinesCount
throw 'Unable to get commit details for the header of the merginal window'
endif
"echo a:lineNumber
if type(0) == type(a:lineNumber)
let l:lineNumber = a:lineNumber
else
let l:lineNumber = line(a:lineNumber)
endif
while b:headerLinesCount < l:lineNumber && !empty(getline(l:lineNumber))
let l:lineNumber -= 1
endwhile
let l:lineNumber += 1
return split(getline(l:lineNumber))[0]
endfunction
"For the commit in the specified line, retrieve:
" - fullHash
" - authorName
" - timestamp
" - subject
" - body
function! merginal#commitDetails(lineNumber)
let l:commitHash = merginal#commitHash(a:lineNumber)
let l:entryFormat = join(['%H', '%aN', '%aE', '%ai', '%B'], '%x01')
let l:commitLines = split(
\ merginal#system(b:merginal_repo.git_command('--no-pager', 'log', '-1', '--format='.l:entryFormat, l:commitHash)),
\ '\%x01')
let l:result = {}
let l:result.fullHash = l:commitLines[0]
let l:result.authorName = l:commitLines[1]
let l:result.authorEmail = l:commitLines[2]
let l:result.timestamp = l:commitLines[3]
let l:commitMessage = split(l:commitLines[4], '\r\n\|\n\|\r')
let l:result.subject = l:commitMessage[0]
let l:result.body = join(l:commitMessage[2 :], "\n")
return l:result
endfunction
function! merginal#getLocalBranchNamesThatTrackARemoteBranch(remoteBranchName)
"Get verbose list of branches
let l:branchList=split(merginal#system(b:merginal_repo.git_command('branch','-vv')),'\r\n\|\n\|\r')
"Filter for branches that track our remote
let l:checkIfTrackingRegex='\V['.escape(a:remoteBranchName,'\').'\[\]:]'
let l:branchList=filter(l:branchList,'v:val=~l:checkIfTrackingRegex')
"Extract the branch name from the matching lines
"let l:extractBranchNameRegex='\v^\*?\s*(\S+)'
"let l:branchList=map(l:branchList,'matchlist(v:val,l:extractBranchNameRegex)[1]')
let l:extractBranchNameRegex='\v^\*?\s*\zs\S+'
let l:branchList=map(l:branchList,'matchstr(v:val,l:extractBranchNameRegex)')
return l:branchList
endfunction
function! merginal#getRemoteBranchTrackedByLocalBranch(localBranchName)
let l:result=merginal#system(b:merginal_repo.git_command('branch','--list',a:localBranchName,'-vv'))
echo l:result
return matchstr(l:result,'\v\[\zs[^\[\]:]*\ze[\]:]')
endfunction
"Check if the current buffer's repo is in rebase mode
function! merginal#isRebaseMode()
return isdirectory(fugitive#repo().dir('rebase-apply'))
endfunction
"Check if the current buffer's repo is in rebase amend mode
function! merginal#isRebaseAmendMode()
return isdirectory(fugitive#repo().dir('rebase-merge'))
endfunction
"Check if the current buffer's repo is in merge mode
function! merginal#isMergeMode()
"Use glob() to check for file existence
return !empty(glob(fugitive#repo().dir('MERGE_MODE')))
endfunction
"Open the branch list buffer for controlling buffers
function! merginal#openBranchListBuffer(...)
if merginal#openTuiBuffer('Merginal:Branches',get(a:000,1,bufwinnr('Merginal:')))
doautocmd User Merginal_BranchList
endif
"At any rate, refresh the buffer:
call merginal#tryRefreshBranchListBuffer(1)
endfunction
augroup merginal
autocmd!
autocmd User Merginal_BranchList nnoremap <buffer> q <C-w>q
autocmd User Merginal_BranchList nnoremap <buffer> R :call merginal#tryRefreshBranchListBuffer(0)<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> C :call <SID>checkoutBranchUnderCursor()<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> cc :call <SID>checkoutBranchUnderCursor()<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> ct :call <SID>trackBranchUnderCursor(0)<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> cT :call <SID>trackBranchUnderCursor(1)<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> A :call <SID>promptToCreateNewBranch()<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> aa :call <SID>promptToCreateNewBranch()<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> D :call <SID>deleteBranchUnderCursor()<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> dd :call <SID>deleteBranchUnderCursor()<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> M :call <SID>mergeBranchUnderCursor()<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> mm :call <SID>mergeBranchUnderCursor()<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> mf :call <SID>mergeBranchUnderCursorUsingFugitive()<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> rb :call <SID>rebaseBranchUnderCursor()<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> ps :call <SID>remoteActionForBranchUnderCursor('push',[])<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> pS :call <SID>remoteActionForBranchUnderCursor('push',['--force'])<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> pl :call <SID>remoteActionForBranchUnderCursor('pull',[])<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> pr :call <SID>remoteActionForBranchUnderCursor('pull',['--rebase'])<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> pf :call <SID>remoteActionForBranchUnderCursor('fetch',[])<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> gd :call <SID>diffWithBranchUnderCursor()<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> rn :call <SID>renameBranchUnderCursor()<Cr>
autocmd User Merginal_BranchList nnoremap <buffer> gl :call <SID>historyLogForBranchUnderCursor()<Cr>
augroup END
"If the current buffer is a branch list buffer - refresh it!
function! merginal#tryRefreshBranchListBuffer(jumpToCurrentBranch)
if 'Merginal:Branches'==bufname('')
let l:branchList=split(merginal#system(b:merginal_repo.git_command('branch','--all')),'\r\n\|\n\|\r')
let l:currentLine=line('.')
setlocal modifiable
"Clear the buffer:
silent normal! gg"_dG
"Write the branch list:
call setline(1,l:branchList)
setlocal nomodifiable
if a:jumpToCurrentBranch
"Find the current branch's index
let l:currentBranchIndex=-1
for i in range(len(l:branchList))
if '*'==l:branchList[i][0]
let l:currentBranchIndex=i
break
endif
endfor
if -1<l:currentBranchIndex
"Jump to the current branch's line
execute l:currentBranchIndex+1
endif
else
execute l:currentLine
endif
endif
endfunction
"Exactly what it says on tin
function! s:checkoutBranchUnderCursor()
if 'Merginal:Branches'==bufname('')
let l:branch=merginal#branchDetails('.')
call merginal#runGitCommandInTreeEcho(b:merginal_repo,'--no-pager checkout '.shellescape(l:branch.handle))
call merginal#reloadBuffers()
call merginal#tryRefreshBranchListBuffer(0)
endif
endfunction
"Track what it says on tin
function! s:trackBranchUnderCursor(promptForName)
if 'Merginal:Branches'==bufname('')
let l:branch=merginal#branchDetails('.')
if !l:branch.isRemote
throw 'Can not track - branch is not remote'
endif
let l:newBranchName=l:branch.name
if a:promptForName
let l:newBranchName=input('Track `'.l:branch.handle.'` as: ',l:newBranchName)
if empty(l:newBranchName)
echo ' '
echom 'Branch tracking canceled by user.'
return
endif
endif
call merginal#runGitCommandInTreeEcho(b:merginal_repo,'--no-pager checkout -b '.shellescape(l:newBranchName).' --track '.shellescape(l:branch.handle))
call merginal#reloadBuffers()
call merginal#tryRefreshBranchListBuffer(0)
endif
endfunction
"Uses the current branch as the source
function! s:promptToCreateNewBranch()
if 'Merginal:Branches'==bufname('')
let l:newBranchName=input('Branch `'.b:merginal_repo.head().'` to: ')
if empty(l:newBranchName)
echo ' '
echom 'Branch creation canceled by user.'
return
endif
call merginal#runGitCommandInTreeEcho(b:merginal_repo,'--no-pager checkout -b '.shellescape(l:newBranchName))
call merginal#reloadBuffers()
call merginal#tryRefreshBranchListBuffer(1)
endif
endfunction
"Verifies the decision
function! s:deleteBranchUnderCursor()
if 'Merginal:Branches'==bufname('')
let l:branch=merginal#branchDetails('.')
let l:answer=0
if l:branch.isLocal
let l:answer='yes'==input('Delete branch `'.l:branch.handle.'`? (type "yes" to confirm) ')
elseif l:branch.isRemote
"Deleting remote branches needs a special warning
let l:answer='yes-remote'==input('Delete remote(!) branch `'.l:branch.handle.'`? (type "yes-remote" to confirm) ')
endif
if l:answer
if l:branch.isLocal
call merginal#runGitCommandInTreeEcho(b:merginal_repo,'--no-pager branch -D '.shellescape(l:branch.handle))
else
execute '!'.b:merginal_repo.git_command('push').' '.shellescape(l:branch.remote).' --delete '.shellescape(l:branch.name)
endif
call merginal#reloadBuffers()
call merginal#tryRefreshBranchListBuffer(0)
else
echo ' '
echom 'Branch deletion canceled by user.'
endif
endif
endfunction
"If there are merge conflicts, opens the merge conflicts buffer
function! s:mergeBranchUnderCursor()
if 'Merginal:Branches'==bufname('')
let l:branch=merginal#branchDetails('.')
call merginal#runGitCommandInTreeEcho(b:merginal_repo,'merge --no-commit '.shellescape(l:branch.handle))
call merginal#reloadBuffers()
if v:shell_error
call merginal#openMergeConflictsBuffer(winnr())
elseif merginal#isMergeMode()
"If we are in merge mode without a shell error, that means there
"are not conflicts and the user can be prompted to enter a merge
"message.
Gstatus
call merginal#closeMerginalBuffer()
endif
endif
endfunction
"Use Fugitive's :Gmerge. It was added to Fugitive after I implemented
"Merginal's merge, and I don't want to remove it since it can still more
"comfortable for some.
function! s:mergeBranchUnderCursorUsingFugitive()
if 'Merginal:Branches'==bufname('')
let l:branch=merginal#branchDetails('.')
execute ':Gmerge '.l:branchName.handle
endif
endfunction
"If there are rebase conflicts, opens the rebase conflicts buffer
function! s:rebaseBranchUnderCursor()
if 'Merginal:Branches'==bufname('')
let l:branch=merginal#branchDetails('.')
call merginal#runGitCommandInTreeEcho(b:merginal_repo,'rebase '.shellescape(l:branch.handle))
call merginal#reloadBuffers()
if v:shell_error
call merginal#openRebaseConflictsBuffer(winnr())
endif
endif
endfunction
"Run various remote actions
function! s:remoteActionForBranchUnderCursor(remoteAction,flags)
if 'Merginal:Branches'==bufname('')
let l:branch=merginal#branchDetails('.')
if l:branch.isLocal
let l:remotes=merginal#runGitCommandInTreeReturnResultLines(b:merginal_repo,'remote')
if empty(l:remotes)
throw 'Can not '.a:remoteAction.' - no remotes defined'
endif
let l:chosenRemoteIndex=0
if 1<len(l:remotes)
let l:listForInputlist=map(copy(l:remotes),'v:key+1.") ".v:val')
"Choose the correct text accoring to the action:
if 'push'==a:remoteAction
call insert(l:listForInputlist,'Choose remote to '.a:remoteAction.' `'.l:branch.handle.'` to:')
else
call insert(l:listForInputlist,'Choose remote to '.a:remoteAction.' `'.l:branch.handle.'` from:')
endif
let l:chosenRemoteIndex=inputlist(l:listForInputlist)
"Check that the chosen index is in range
if l:chosenRemoteIndex<=0 || len(l:remotes)<l:chosenRemoteIndex
return
endif
let l:chosenRemoteIndex=l:chosenRemoteIndex-1
endif
let l:localBranchName=l:branch.name
let l:chosenRemote=l:remotes[l:chosenRemoteIndex]
let l:remoteBranchNameCanadidate=merginal#getRemoteBranchTrackedByLocalBranch(l:branch.name)
echo ' '
if !empty(l:remoteBranchNameCanadidate)
"Check that this is the same remote:
if l:remoteBranchNameCanadidate=~'\V\^'.escape(l:chosenRemote,'\').'/'
"Remote the remote repository name
let l:remoteBranchName=l:remoteBranchNameCanadidate[len(l:chosenRemote)+1:(-1)]
endif
endif
elseif l:branch.isRemote
let l:chosenRemote=l:branch.remote
if 'push'==a:remoteAction
"For push, we want to specify the remote branch name
let l:remoteBranchName=l:branch.name
let l:locals=merginal#getLocalBranchNamesThatTrackARemoteBranch(l:branch.handle)
if empty(l:locals)
let l:localBranchName=l:branch.name
elseif 1==len(l:locals)
let l:localBranchName=l:locals[0]
else
let l:listForInputlist=map(copy(l:locals),'v:key+1.") ".v:val')
call insert(l:listForInputlist,'Choose local branch to push `'.l:branch.handle.'` from:')
let l:chosenLocalIndex=inputlist(l:listForInputlist)
"Check that the chosen index is in range
if l:chosenLocalIndex<=0 || len(l:locals)<l:chosenLocalIndex
return
endif
let l:localBranchName=l:locals[l:chosenLocalIndex-1]
endif
else
"For pull and fetch, git automatically resolves the tracking
"branch based on the remote branch.
let l:localBranchName=l:branch.name
endif
endif
if exists('l:remoteBranchName') && empty(l:remoteBranchName)
unlet l:remoteBranchName
endif
let l:gitCommandWithArgs=[a:remoteAction]
for l:flag in a:flags
call add(l:gitCommandWithArgs,l:flag)
endfor
let l:reloadBuffers=0
"Pulling requires the --no-commit flag
if 'pull'==a:remoteAction
if exists('l:remoteBranchName')
let l:remoteBranchNameAsPrefix=shellescape(l:remoteBranchName).':'
else
let l:remoteBranchNameAsPrefix=''
endif
let l:remoteBranchEscapedName=l:remoteBranchNameAsPrefix.shellescape(l:localBranchName)
call add(l:gitCommandWithArgs,'--no-commit')
let l:reloadBuffers=1
elseif 'push'==a:remoteAction
if exists('l:remoteBranchName')
let l:remoteBranchNameAsSuffix=':'.shellescape(l:remoteBranchName)
else
let l:remoteBranchNameAsSuffix=''
endif
let l:remoteBranchEscapedName=shellescape(l:localBranchName).l:remoteBranchNameAsSuffix
elseif 'fetch'==a:remoteAction
if exists('l:remoteBranchName')
let l:targetBranchName=l:remoteBranchName
else
let l:targetBranchName=l:localBranchName
endif
let l:remoteBranchEscapedName=shellescape(l:targetBranchName)
execute '!'.b:merginal_repo.git_command(a:remoteAction).' '.shellescape(l:chosenRemote).' '.shellescape(l:targetBranchName)
endif
execute '!'.call(b:merginal_repo.git_command,l:gitCommandWithArgs,b:merginal_repo).' '.shellescape(l:chosenRemote).' '.l:remoteBranchEscapedName
if l:reloadBuffers
call merginal#reloadBuffers()
endif
call merginal#tryRefreshBranchListBuffer(0)
endif
endfunction
"Opens the diff files buffer
function! s:diffWithBranchUnderCursor()
if 'Merginal:Branches'==bufname('')
\|| 'Merginal:RebaseAmend'==bufname('')
let l:branch=merginal#branchDetails('.')
call merginal#openDiffFilesBuffer(l:branch.handle)
endif
endfunction
"Prompts for a new name to the branch and renames it
function! s:renameBranchUnderCursor()
if 'Merginal:Branches'==bufname('')
let l:branch=merginal#branchDetails('.')
if !l:branch.isLocal
throw 'Can not rename - not a local branch'
endif
let l:newName=input('Rename `'.l:branch.handle.'` to: ',l:branch.name)
echo ' '
if empty(l:newName)
echom 'Branch rename canceled by user.'
return
elseif l:newName==l:branch.name
echom 'Branch name was not modified.'
return
endif
let l:gitCommand=b:merginal_repo.git_command('branch','-m',l:branch.name,l:newName)
let l:result=merginal#system(l:gitCommand)
echo l:result
call merginal#tryRefreshBranchListBuffer(0)
endif
endfunction
"Opens the history log buffer
function! s:historyLogForBranchUnderCursor()
if 'Merginal:Branches'==bufname('')
\|| 'Merginal:RebaseAmend'==bufname('')
let l:branch=merginal#branchDetails('.')
call merginal#openHistoryLogBuffer(l:branch)
endif
endfunction
"Open the merge conflicts buffer for resolving merge conflicts
function! merginal#openMergeConflictsBuffer(...)
let l:currentFile=expand('%:~:.')
if merginal#openTuiBuffer('Merginal:Conflicts',get(a:000,1,bufwinnr('Merginal:')))
doautocmd User Merginal_MergeConflicts
endif
"At any rate, refresh the buffer:
call merginal#tryRefreshMergeConflictsBuffer(l:currentFile)
endfunction
augroup merginal
autocmd User Merginal_MergeConflicts nnoremap <buffer> q <C-w>q
autocmd User Merginal_MergeConflicts nnoremap <buffer> R :call merginal#tryRefreshMergeConflictsBuffer(0)<Cr>
autocmd User Merginal_MergeConflicts nnoremap <buffer> <Cr> :call <SID>openMergeConflictUnderCursor()<Cr>
autocmd User Merginal_MergeConflicts nnoremap <buffer> A :call <SID>addConflictedFileToStagingArea()<Cr>
autocmd User Merginal_MergeConflicts nnoremap <buffer> aa :call <SID>addConflictedFileToStagingArea()<Cr>
augroup END
"Returns 1 if all merges are done
function! s:refreshConflictsBuffer(fileToJumpTo,headerLines)
"Get the list of unmerged files:
let l:conflicts=split(merginal#system(b:merginal_repo.git_command('ls-files','--unmerged')),'\r\n\|\n\|\r')
"Split by tab - the first part is info, the second is the file name
let l:conflicts=map(l:conflicts,'split(v:val,"\t")')
"Only take the stage 1 files - stage 2 and 3 are the same files with
"different hash, and we don't care about the hash here
let l:conflicts=filter(l:conflicts,'v:val[0] =~ "\\v 1$"')
"Take the file name - we no longer care about the info
let l:conflicts=map(l:conflicts,'v:val[1]')
"If the working copy is not the current dir, we can get wrong paths.
"We need to resulve that:
let l:conflicts=map(l:conflicts,'b:merginal_repo.tree(v:val)')
"Make the paths as short as possible:
let l:conflicts=map(l:conflicts,'fnamemodify(v:val,":~:.")')
let l:currentLine=line('.')-b:headerLinesCount
setlocal modifiable
"Clear the buffer:
silent normal! gg"_dG
"Write the branch list:
call setline(1,a:headerLines+l:conflicts)
let b:headerLinesCount=len(a:headerLines)
let l:currentLine=l:currentLine+b:headerLinesCount
setlocal nomodifiable
if empty(l:conflicts)
return 1
endif
if empty(a:fileToJumpTo)
if 0<l:currentLine
execute l:currentLine
endif
else
let l:lineNumber=search('\V\^'+escape(a:fileToJumpTo,'\')+'\$','cnw')
if 0<l:lineNumber
execute l:lineNumber
else
execute l:currentLine
endif
endif
return 0
endfunction
"Returns 1 if all merges are done
function! merginal#tryRefreshMergeConflictsBuffer(fileToJumpTo)
if 'Merginal:Conflicts'==bufname('')
return s:refreshConflictsBuffer(a:fileToJumpTo,[])
endif
return 0
endfunction
"Exactly what it says on tin
function! s:openMergeConflictUnderCursor()
if 'Merginal:Conflicts'==bufname('')
\|| 'Merginal:Rebase'==bufname('')
let l:file=merginal#fileDetails('.')
if empty(l:file.name)
return
endif
call merginal#openFileDecidedWindow(b:merginal_repo,l:file.name)
endif
endfunction
"If that was the last merge conflict, automatically opens Fugitive's status
"buffer
function! s:addConflictedFileToStagingArea()
if 'Merginal:Conflicts'==bufname('')
\|| 'Merginal:Rebase'==bufname('')
let l:file=merginal#fileDetails('.')
if empty(l:file.name)
return
endif
call merginal#runGitCommandInTreeEcho(b:merginal_repo,'--no-pager add '.shellescape(fnamemodify(l:file.name,':p')))
if 'Merginal:Conflicts'==bufname('')
if merginal#tryRefreshMergeConflictsBuffer(0)
"If this returns 1, that means this is the last branch, and we
"should open gufitive's status window
let l:mergeConflictsBuffer=bufnr('')
Gstatus
let l:gitStatusBuffer=bufnr('')
execute bufwinnr(l:mergeConflictsBuffer).'wincmd w'
wincmd q
execute bufwinnr(l:gitStatusBuffer).'wincmd w'
endif
else
if merginal#tryRefreshRebaseConflictsBuffer(0)
echo 'Added the last file of this patch.'
echo 'Continue to the next patch (y/N)?'
let l:answer=getchar()
if char2nr('y')==l:answer || char2nr('Y')==l:answer
call s:rebaseAction('continue')
endif
endif
endif
endif
endfunction
"Open the diff files buffer for diffing agains another branch/commit
function! merginal#openDiffFilesBuffer(diffTarget,...)
if merginal#openTuiBuffer('Merginal:Diff',get(a:000,1,bufwinnr('Merginal:')))
doautocmd User Merginal_DiffFiles
endif
let b:merginal_diffTarget=a:diffTarget
"At any rate, refresh the buffer:
call merginal#tryRefreshDiffFilesBuffer()
endfunction
augroup merginal
autocmd User Merginal_DiffFiles nnoremap <buffer> q <C-w>q
autocmd User Merginal_DiffFiles nnoremap <buffer> R :call merginal#tryRefreshDiffFilesBuffer()<Cr>
autocmd User Merginal_DiffFiles nnoremap <buffer> <Cr> :call <SID>openDiffFileUnderCursor()<Cr>
autocmd User Merginal_DiffFiles nnoremap <buffer> ds :call <SID>openDiffFileUnderCursorAndDiff('s')<Cr>
autocmd User Merginal_DiffFiles nnoremap <buffer> dv :call <SID>openDiffFileUnderCursorAndDiff('v')<Cr>
autocmd User Merginal_DiffFiles nnoremap <buffer> co :call <SID>checkoutDiffFileUnderCursor()<Cr>
augroup END
"For the diff file in the specified line, retrieve:
" - type: 'added', 'deleted' or 'modified'
" - isAdded, isDeleted, isModified
" - fileInTree: the path of the file relative to the repo
" - fileFullPath: the full path to the file
function! merginal#diffFileDetails(lineNumber)
if !exists('b:merginal_repo')
throw 'Unable to get diff file details outside the merginal window'
endif
let l:line=getline(a:lineNumber)
let l:result={}
let l:matches=matchlist(l:line,'\v([ADM])\t(.*)$')
if empty(l:matches)
throw 'Unable to get diff files details for `'.l:line.'`'
endif
let l:result.isAdded=0
let l:result.isDeleted=0
let l:result.isModified=0
if 'A'==l:matches[1]
let l:result.type='added'
let l:result.isAdded=1
elseif 'D'==l:matches[1]
let l:result.type='deleted'
let l:result.isDeleted=1
else
let l:result.type='modified'
let l:result.isModified=1
endif
let l:result.fileInTree=l:matches[2]
let l:result.fileFullPath=b:merginal_repo.tree(l:matches[2])
return l:result
endfunction
"If the current buffer is a diff files buffer - refresh it!
function! merginal#tryRefreshDiffFilesBuffer()
if 'Merginal:Diff'==bufname('')
let l:diffTarget=b:merginal_diffTarget
let l:diffFiles=merginal#runGitCommandInTreeReturnResultLines(b:merginal_repo,'diff --name-status '.shellescape(l:diffTarget))
let l:currentLine=line('.')
setlocal modifiable
"Clear the buffer:
silent normal! gg"_dG
"Write the diff files list:
call setline(1,l:diffFiles)
setlocal nomodifiable
execute l:currentLine
endif
endfunction
"Exactly what it says on tin
function! s:openDiffFileUnderCursor()
if 'Merginal:Diff'==bufname('')
let l:diffFile=merginal#diffFileDetails('.')
if l:diffFile.isDeleted
throw 'File does not exist in current buffer'
endif
call merginal#openFileDecidedWindow(b:merginal_repo,l:diffFile.fileFullPath)
endif
endfunction
"Exactly what it says on tin
function! s:openDiffFileUnderCursorAndDiff(diffType)
if a:diffType!='s' && a:diffType!='v'
throw 'Bad diff type'
endif
if 'Merginal:Diff'==bufname('')
let l:diffFile=merginal#diffFileDetails('.')
if l:diffFile.isAdded
throw 'File does not exist in other buffer'
endif
let l:repo=b:merginal_repo
let l:diffTarget=b:merginal_diffTarget
"Close currently open git diffs
let l:currentWindowBuffer=winbufnr('.')
try
windo if 'blob'==get(b:,'fugitive_type','') && exists('w:fugitive_diff_restore')
\| bdelete
\| endif
catch
"do nothing
finally
execute bufwinnr(l:currentWindowBuffer).'wincmd w'
endtry
call merginal#openFileDecidedWindow(l:repo,l:diffFile.fileFullPath)
execute ':G'.a:diffType.'diff '.fnameescape(l:diffTarget)
endif
endfunction
"Checks out the file from the diff target to the current branch
function! s:checkoutDiffFileUnderCursor()
if 'Merginal:Diff'==bufname('')
let l:diffFile=merginal#diffFileDetails('.')
if l:diffFile.isAdded
throw 'File does not exist in diffed buffer'
endif
let l:answer=1
if !empty(glob(l:diffFile.fileFullPath))
let l:answer='yes'==input('Override `'.l:diffFile.fileInTree.'`? (type "yes" to confirm) ')
endif
if l:answer
call merginal#runGitCommandInTreeEcho(b:merginal_repo,'--no-pager checkout '.shellescape(b:merginal_diffTarget)
\.' -- '.shellescape(l:diffFile.fileFullPath))
call merginal#reloadBuffers()
call merginal#tryRefreshDiffFilesBuffer()
else
echo
echom 'File checkout canceled by user.'
endif
endif
endfunction
"Open the rebase conflicts buffer for resolving rebase conflicts
function! merginal#openRebaseConflictsBuffer(...)
let l:currentFile=expand('%:~:.')
if merginal#openTuiBuffer('Merginal:Rebase',get(a:000,1,bufwinnr('Merginal:')))
doautocmd User Merginal_RebaseConflicts
endif
"At any rate, refresh the buffer:
call merginal#tryRefreshRebaseConflictsBuffer(l:currentFile)
endfunction
augroup merginal
autocmd User Merginal_RebaseConflicts nnoremap <buffer> q <C-w>q
autocmd User Merginal_RebaseConflicts nnoremap <buffer> R :call merginal#tryRefreshRebaseConflictsBuffer(0)<Cr>
autocmd User Merginal_RebaseConflicts nnoremap <buffer> <Cr> :call <SID>openMergeConflictUnderCursor()<Cr>
autocmd User Merginal_RebaseConflicts nnoremap <buffer> A :call <SID>addConflictedFileToStagingArea()<Cr>
autocmd User Merginal_RebaseConflicts nnoremap <buffer> aa :call <SID>addConflictedFileToStagingArea()<Cr>
autocmd User Merginal_RebaseConflicts nnoremap <buffer> ra :call <SID>rebaseAction('abort')<Cr>
autocmd User Merginal_RebaseConflicts nnoremap <buffer> rs :call <SID>rebaseAction('skip')<Cr>
autocmd User Merginal_RebaseConflicts nnoremap <buffer> rc :call <SID>rebaseAction('continue')<Cr>
augroup END
"Returns 1 if all rebase conflicts are done
function! merginal#tryRefreshRebaseConflictsBuffer(fileToJumpTo)
if 'Merginal:Rebase'==bufname('')
let l:currentCommitMessageLines=readfile(b:merginal_repo.dir('rebase-apply','msg-clean'))
call insert(l:currentCommitMessageLines,'=== Reapplying: ===')
call add(l:currentCommitMessageLines,'===================')
call add(l:currentCommitMessageLines,'')
return s:refreshConflictsBuffer(a:fileToJumpTo,l:currentCommitMessageLines)
endif
return 0
endfunction
"Run various rebase actions
function! s:rebaseAction(remoteAction)
if 'Merginal:Rebase'==bufname('')
\|| 'Merginal:RebaseAmend'==bufname('')
call merginal#runGitCommandInTreeEcho(b:merginal_repo,'--no-pager rebase --'.a:remoteAction)
call merginal#reloadBuffers()
if merginal#isRebaseMode()
call merginal#tryRefreshRebaseConflictsBuffer(0)
elseif merginal#isRebaseAmendMode()
call merginal#tryRefreshRebaseAmendBuffer()
else
"If we finished rebasing - close the rebase conflicts buffer
wincmd q
endif
endif
endfunction
"Open the rebase amend buffer
function! merginal#openRebaseAmendBuffer(...)
let l:currentFile=expand('%:~:.')
if merginal#openTuiBuffer('Merginal:RebaseAmend',get(a:000,1,bufwinnr('Merginal:')))
doautocmd User Merginal_RebaseAmend
endif
"At any rate, refresh the buffer:
call merginal#tryRefreshRebaseAmendBuffer()
endfunction
augroup merginal
autocmd User Merginal_RebaseAmend nnoremap <buffer> q <C-w>q
autocmd User Merginal_RebaseAmend nnoremap <buffer> R :call merginal#tryRefreshRebaseAmendBuffer()<Cr>
autocmd User Merginal_RebaseAmend nnoremap <buffer> ra :call <SID>rebaseAction('abort')<Cr>
autocmd User Merginal_RebaseAmend nnoremap <buffer> rs :call <SID>rebaseAction('skip')<Cr>
autocmd User Merginal_RebaseAmend nnoremap <buffer> rc :call <SID>rebaseAction('continue')<Cr>
autocmd User Merginal_RebaseAmend nnoremap <buffer> gd :call <SID>diffWithBranchUnderCursor()<Cr>
autocmd User Merginal_RebaseAmend nnoremap <buffer> gl :call <SID>historyLogForBranchUnderCursor()<Cr>
augroup END
function! merginal#tryRefreshRebaseAmendBuffer()
if 'Merginal:RebaseAmend'==bufname('')
let l:currentLine=line('.')
let l:newBufferLines=[]
let l:amendedCommit=readfile(b:merginal_repo.dir('rebase-merge','amend'))
let l:amendedCommitShort=merginal#system(b:merginal_repo.git_command('rev-parse','--short',l:amendedCommit[0]))
let l:amendedCommitShort=substitute(l:amendedCommitShort,'\v[\r\n]','','g')
let l:amendedCommitMessage=readfile(b:merginal_repo.dir('rebase-merge','message'))
call add(l:newBufferLines,'=== Amending '.l:amendedCommitShort.' ===')
let l:newBufferLines+=l:amendedCommitMessage
call add(l:newBufferLines,repeat('=',len(l:newBufferLines[0])))
call add(l:newBufferLines,'')
let b:headerLinesCount=len(l:newBufferLines)+1
let l:branchList=split(merginal#system(b:merginal_repo.git_command('branch','--all')),'\r\n\|\n\|\r')
"The first line is a reminder that we are rebasing
"call remove(l:branchList,0)
let l:newBufferLines+=l:branchList
setlocal modifiable
"Clear the buffer:
silent normal! gg"_dG
"Write the new buffer lines:
call setline(1,l:newBufferLines)
"call setline(1,l:branchList)
setlocal nomodifiable
endif
return 0
endfunction
"Open the history log buffer
function! merginal#openHistoryLogBuffer(logBranch,...)
let l:currentFile=expand('%:~:.')
if merginal#openTuiBuffer('Merginal:HistoryLog',get(a:000,1,bufwinnr('Merginal:')))
doautocmd User Merginal_HistoryLog
endif
let b:merginal_branch=a:logBranch
"At any rate, refresh the buffer:
call merginal#tryRefreshHistoryLogBuffer()
endfunction
augroup merginal
autocmd User Merginal_HistoryLog nnoremap <buffer> q <C-w>q
autocmd User Merginal_HistoryLog nnoremap <buffer> R :call merginal#tryRefreshHistoryLogBuffer()<Cr>
autocmd User Merginal_HistoryLog nnoremap <buffer> <C-p> :call <SID>moveToNextOrPreviousCommit(-1)<Cr>
autocmd User Merginal_HistoryLog nnoremap <buffer> <C-n> :call <SID>moveToNextOrPreviousCommit(1)<Cr>
autocmd User Merginal_HistoryLog nnoremap <buffer> S :call <SID>printCommitUnderCurosr('fuller')<Cr>
autocmd User Merginal_HistoryLog nnoremap <buffer> ss :call <SID>printCommitUnderCurosr('fuller')<Cr>
autocmd User Merginal_HistoryLog nnoremap <buffer> C :call <SID>checkoutCommitUnderCurosr()<Cr>
autocmd User Merginal_HistoryLog nnoremap <buffer> cc :call <SID>checkoutCommitUnderCurosr()<Cr>
autocmd User Merginal_HistoryLog nnoremap <buffer> gd :call <SID>diffWithCommitUnderCursor()<Cr>
augroup END
function! merginal#tryRefreshHistoryLogBuffer()
if 'Merginal:HistoryLog'==bufname('')
let l:entryFormat = '%h %aN%n%ai%n%s%n'
let l:logLines = split(
\ merginal#system(b:merginal_repo.git_command(
\ '--no-pager', 'log', '--format='.l:entryFormat, b:merginal_branch.handle)),
\ '\r\n\|\n\|\r')
if empty(l:logLines[len(l:logLines) - 1])
call remove(l:logLines, len(l:logLines) - 1)
endif
let l:currentLine=line('.')
setlocal modifiable
"Clear the buffer:
silent normal! gg"_dG
"Write the log lines:
call setline(1,l:logLines)
setlocal nomodifiable
execute l:currentLine
endif
return 0
endfunction
"Exactly what it says on tin
function! s:printCommitUnderCurosr(format)
if 'Merginal:HistoryLog'==bufname('')
let l:commitHash = merginal#commitHash('.')
"Not using merginal#runGitCommandInTreeEcho because we are insterested
"in the result as more than just git command output. Also - using
"git-log with -1 instead of git-show because for some reason git-show
"ignores the --format flag...
echo merginal#system(b:merginal_repo.git_command('--no-pager', 'log', '-1', '--format='.a:format, l:commitHash))
endif
endfunction
"Exactly what it says on tin
function! s:checkoutCommitUnderCurosr()
if 'Merginal:HistoryLog'==bufname('')
let l:commitHash = merginal#commitHash('.')
call merginal#runGitCommandInTreeEcho(b:merginal_repo,'--no-pager checkout '.shellescape(l:commitHash))
call merginal#reloadBuffers()
call merginal#tryRefreshBranchListBuffer(0)
endif
endfunction
"Opens the diff files buffer
function! s:diffWithCommitUnderCursor()
if 'Merginal:HistoryLog'==bufname('')
let l:commitHash = merginal#commitHash('.')
call merginal#openDiffFilesBuffer(l:commitHash)
endif
endfunction
function! s:moveToNextOrPreviousCommit(direction)
if 'Merginal:HistoryLog'==bufname('')
let l:line = line('.')
"Find the first line of the current commit
while !empty(getline(l:line - 1))
let l:line -= 1
endwhile
"Find the first line of the next/prev commit
let l:line += a:direction
while !empty(getline(l:line - 1))
let l:line += a:direction
endwhile
if l:line <= 0 || line('$') <= l:line
"We reached past the first/last commit - go back!
let l:line -= a:direction
while !empty(getline(l:line - 1))
let l:line -= a:direction
endwhile
endif
execute l:line
endif
endfunction