"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