"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 (\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 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 User Merginal_BranchList nnoremap R :call merginal#tryRefreshBranchListBuffer(0) autocmd User Merginal_BranchList nnoremap C :call checkoutBranchUnderCursor() autocmd User Merginal_BranchList nnoremap cc :call checkoutBranchUnderCursor() autocmd User Merginal_BranchList nnoremap ct :call trackBranchUnderCursor(0) autocmd User Merginal_BranchList nnoremap cT :call trackBranchUnderCursor(1) autocmd User Merginal_BranchList nnoremap A :call promptToCreateNewBranch() autocmd User Merginal_BranchList nnoremap aa :call promptToCreateNewBranch() autocmd User Merginal_BranchList nnoremap D :call deleteBranchUnderCursor() autocmd User Merginal_BranchList nnoremap dd :call deleteBranchUnderCursor() autocmd User Merginal_BranchList nnoremap M :call mergeBranchUnderCursor() autocmd User Merginal_BranchList nnoremap mm :call mergeBranchUnderCursor() autocmd User Merginal_BranchList nnoremap mf :call mergeBranchUnderCursorUsingFugitive() autocmd User Merginal_BranchList nnoremap rb :call rebaseBranchUnderCursor() autocmd User Merginal_BranchList nnoremap ps :call remoteActionForBranchUnderCursor('push',0) autocmd User Merginal_BranchList nnoremap pS :call remoteActionForBranchUnderCursor('push',1) autocmd User Merginal_BranchList nnoremap pl :call remoteActionForBranchUnderCursor('pull',0) autocmd User Merginal_BranchList nnoremap pf :call remoteActionForBranchUnderCursor('fetch',0) autocmd User Merginal_BranchList nnoremap gd :call diffWithBranchUnderCursor() autocmd User Merginal_BranchList nnoremap rn :call renameBranchUnderCursor() 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: normal ggdG "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 R :call merginal#tryRefreshMergeConflictsBuffer(0) autocmd User Merginal_MergeConflicts nnoremap :call openMergeConflictUnderCursor() autocmd User Merginal_MergeConflicts nnoremap A :call addConflictedFileToStagingArea() autocmd User Merginal_MergeConflicts nnoremap aa :call addConflictedFileToStagingArea() 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: normal ggdG "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 R :call merginal#tryRefreshDiffFilesBuffer() autocmd User Merginal_DiffFiles nnoremap :call openDiffFileUnderCursor() autocmd User Merginal_DiffFiles nnoremap ds :call openDiffFileUnderCursorAndDiff('s') autocmd User Merginal_DiffFiles nnoremap dv :call openDiffFileUnderCursorAndDiff('v') autocmd User Merginal_DiffFiles nnoremap co :call checkoutDiffFileUnderCursor() 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 branch list buffer - refresh it! function! merginal#tryRefreshDiffFilesBuffer() if 'Merginal:Diff'==bufname('') let l:diffBranch=b:merginal_diffBranch let l:diffFiles=merginal#runGitCommandInTreeReturnResultLines(b:merginal_repo,'diff --name-status '.shellescape(l:diffBranch.handle)) let l:currentLine=line('.') setlocal modifiable "Clear the buffer: normal ggdG "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:diffBranch=b:merginal_diffBranch "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:diffBranch.handle) endif endfunction "Checks out the file from the other branch 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 echo merginal#runGitCommandInTreeReturnResult(b:merginal_repo,'--no-pager checkout '.shellescape(b:merginal_diffBranch.handle) \.' -- '.shellescape(l:diffFile.fileFullPath)) call merginal#reloadBuffers() call merginal#tryRefreshDiffFilesBuffer() else echo ' ' echo 'File checkout canceled by the 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 R :call merginal#tryRefreshRebaseConflictsBuffer(0) autocmd User Merginal_RebaseConflicts nnoremap :call openMergeConflictUnderCursor() autocmd User Merginal_RebaseConflicts nnoremap A :call addConflictedFileToStagingArea() autocmd User Merginal_RebaseConflicts nnoremap aa :call addConflictedFileToStagingArea() autocmd User Merginal_RebaseConflicts nnoremap ra :call rebaseAction('abort') autocmd User Merginal_RebaseConflicts nnoremap rs :call rebaseAction('skip') autocmd User Merginal_RebaseConflicts nnoremap rc :call rebaseAction('continue') 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('') echo merginal#runGitCommandInTreeReturnResult(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 autocmd User Merginal_RebaseAmend nnoremap R :call merginal#tryRefreshRebaseAmendBuffer() autocmd User Merginal_RebaseAmend nnoremap ra :call rebaseAction('abort') autocmd User Merginal_RebaseAmend nnoremap rs :call rebaseAction('skip') autocmd User Merginal_RebaseAmend nnoremap rc :call rebaseAction('continue') autocmd User Merginal_RebaseAmend nnoremap gd :call diffWithBranchUnderCursor() function! merginal#tryRefreshRebaseAmendBuffer() if 'Merginal:RebaseAmend'==bufname('') "let l:gitStatusOutput=split(merginal#system(b:merginal_repo.git_command('status','--all')),'\r\n\|\n\|\r') 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: normal ggdG "Write the new buffer lines: call setline(1,l:newBufferLines) "call setline(1,l:branchList) setlocal nomodifiable endif return 0 endfunction