"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 "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 q q 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',[]) autocmd User Merginal_BranchList nnoremap pS :call remoteActionForBranchUnderCursor('push',['--force']) autocmd User Merginal_BranchList nnoremap pl :call remoteActionForBranchUnderCursor('pull',[]) autocmd User Merginal_BranchList nnoremap pr :call remoteActionForBranchUnderCursor('pull',['--rebase']) autocmd User Merginal_BranchList nnoremap pf :call remoteActionForBranchUnderCursor('fetch',[]) autocmd User Merginal_BranchList nnoremap gd :call diffWithBranchUnderCursor() autocmd User Merginal_BranchList nnoremap rn :call renameBranchUnderCursor() autocmd User Merginal_BranchList nnoremap gl :call historyLogForBranchUnderCursor() 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 q q autocmd User Merginal_MergeConflicts nnoremap 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: 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 q q autocmd User Merginal_DiffFiles nnoremap 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 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 q q 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('') 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 q q 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() autocmd User Merginal_RebaseAmend nnoremap gl :call historyLogForBranchUnderCursor() 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 q q autocmd User Merginal_HistoryLog nnoremap R :call merginal#tryRefreshHistoryLogBuffer() autocmd User Merginal_HistoryLog nnoremap :call moveToNextOrPreviousCommit(-1) autocmd User Merginal_HistoryLog nnoremap :call moveToNextOrPreviousCommit(1) autocmd User Merginal_HistoryLog nnoremap S :call printCommitUnderCurosr('fuller') autocmd User Merginal_HistoryLog nnoremap ss :call printCommitUnderCurosr('fuller') autocmd User Merginal_HistoryLog nnoremap C :call checkoutCommitUnderCurosr() autocmd User Merginal_HistoryLog nnoremap cc :call checkoutCommitUnderCurosr() autocmd User Merginal_HistoryLog nnoremap gd :call diffWithCommitUnderCursor() 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