Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,15 @@ jobs:
- name: Check out repository code
uses: actions/checkout@v4

# The neovim PPA has no build for every Ubuntu ubuntu-latest moves to:
# use the pinned release like the Test job
- name: Install vim and neovim
run: |
sudo add-apt-repository ppa:neovim-ppa/stable -y
sudo apt-get update
sudo apt-get -y install vim neovim
sudo apt-get -y install vim
curl -fsSL https://github.com/neovim/neovim/releases/download/v0.11.6/nvim-linux-x86_64.tar.gz \
| sudo tar -xz -C /opt
echo /opt/nvim-linux-x86_64/bin >> "$GITHUB_PATH"

- name: Clone vim-test
run: |
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ let g:vim_project_local_config = {
| <kbd>s</kbd> | shelve: save the changes as a patch and revert them |
| <kbd>S</kbd> | unshelve a shelf or one of its files |
| <kbd>u</kbd> / <kbd>p</kbd> / <kbd>P</kbd> | pull, push, pull then push, in the background |
| <kbd>A</kbd> | abort the merge, rebase, cherry-pick or revert in progress |

Merge conflicts: a pull or branch merge that stops on conflicts opens the Unmerged changelist. <kbd>Enter</kbd> on a file opens a three-pane merge window, yours | result | theirs, with each conflict colored by state. Press <kbd>&lt;</kbd> yours, <kbd>&gt;</kbd> theirs, <kbd>=</kbd> both, <kbd>e</kbd> edit, <kbd>u</kbd> undo, <kbd>]c</kbd> / <kbd>[c</kbd> move, <kbd>q</kbd> to accept (write and `git add`) once all are resolved, <kbd>Q</kbd> to discard. After the last file it offers to commit the merge or continue the rebase.

### Git log, file history, branches and tags

Expand All @@ -209,7 +212,7 @@ let g:vim_project_local_config = {

- `:ProjectGitLog` searches commits by message, author or hash, with the changed files of the selected one beside it.
- `:ProjectGitFileHistory` shows the commits of the current file, or of the selected lines, with their diffs.
- `:ProjectGitBranch` switches, creates and merges branches.
- `:ProjectGitBranch` switches, creates and merges branches. It fetches remotes in the background and refreshes the list when done.
- `:ProjectGitTag` checks out and creates tags.

| List | Key | Action |
Expand Down
16 changes: 16 additions & 0 deletions autoload/project/config.vim
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ let s:default = {
\'new_project_base': '',
\'new_tasks_post_cmd': '',
\'commit_message': '',
\'git_branch_fetch': 1,
\'find_replace_separator': ' => ',
\'debug': 0,
\}
Expand Down Expand Up @@ -105,6 +106,20 @@ let s:default.git_local_changes_mappings = {
\'force_push': '<NOP>',
\'shelf': 's',
\'unshelf': 'S',
\'abort_merge': 'A',
\}
let s:default.git_merge_mappings = {
\'take_yours': '<',
\'take_theirs': '>',
\'take_both': '=',
\'undo': 'u',
\'edit': 'e',
\'next': ']c',
\'previous': '[c',
\'all_yours': 'L',
\'all_theirs': 'R',
\'finish': 'q',
\'quit': 'Q',
\}
let s:default.file_open_types = {
\'': 'edit',
Expand All @@ -126,6 +141,7 @@ let s:merge_keys = [
\'git_diff_mappings',
\'git_changes_mappings',
\'git_local_changes_mappings',
\'git_merge_mappings',
\]

" Options a project's init.vim may set. Lists and dicts extend the global
Expand Down
23 changes: 17 additions & 6 deletions autoload/project/git.vim
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ let s:changed_files = []
let s:untracked_files = []
let s:staged_files = []
let s:unmerged_files = []
" Statuses of git status --porcelain for unmerged paths
let s:unmerged_status = ['DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU']
let s:commit_files = []
let s:file_regexp = '|\zs.*\ze|'
let s:directory_regexp = '| \zs.*$'
Expand Down Expand Up @@ -899,6 +901,11 @@ function! s:OpenFolderOrFile()
return
endif

if index(s:unmerged_files, 'U '.file) != -1
call project#git_merge#Open(file)
return
endif

let diff_file = s:TryGetDiffFile(file)
wincmd k
if empty(diff_file)
Expand Down Expand Up @@ -1647,7 +1654,12 @@ function! s:ParseGitStatus()
let status = strpart(line, 0, 2)
let filepath = strpart(line, 3)

if status[0] != ' ' && status[0] != '?' && status[0] != 'U' " Staged files
if index(s:unmerged_status, status) != -1 " Merge conflicts
call add(s:unmerged_files, 'U '.filepath)
continue
endif

if status[0] != ' ' && status[0] != '?' " Staged files
call add(s:staged_files, status[0].' '.filepath)
endif

Expand All @@ -1658,10 +1670,6 @@ function! s:ParseGitStatus()
if status == '??' " Untracked files
call add(s:untracked_files, '? '.filepath)
endif

if status == 'UU' " Merge conflicts
call add(s:unmerged_files, 'U '.filepath)
endif
endfor
endfunction

Expand Down Expand Up @@ -1795,6 +1803,7 @@ function! s:SetupChangelistBuffer()
call s:AddVisualMapping(mappings.shelf, '<SID>ShelfFile()')
call s:AddMapping(mappings.unshelf, '<SID>UnshelfFile()')
call s:AddVisualMapping(mappings.unshelf, '<SID>UnshelfFile()')
call s:AddMapping(mappings.abort_merge, 'project#git_merge#Abort()')

hi default DiffBufferModify ctermfg=3 guifg=#b58900
hi default DiffBufferAdd ctermfg=2 guifg=#719e07
Expand Down Expand Up @@ -2093,7 +2102,9 @@ function! s:OnGitJobDone(cmd, done_message, OnSuccess, ctx, status)
let s:git_job = v:null
let lines = filter(copy(a:ctx.lines), '!empty(v:val)')
if a:status != 0
call project#WarnCommandOutput(a:cmd, lines)
if !project#git_merge#ShowConflicts()
call project#WarnCommandOutput(a:cmd, lines)
endif
return
endif

Expand Down
95 changes: 92 additions & 3 deletions autoload/project/git_branch.vim
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ function! s:Highlight(state)
endfunction

function! s:Init(input)
let s:refs = s:GetRefs()
let s:list = s:LoadBranches()
call s:Update(a:input)
call s:StartFetch()
endfunction

function! s:LoadBranches()
let format = join(['%(HEAD)', '%(refname:short)', '%(upstream:short)',
\'%(contents:subject)', '%(authorname)', '%(committerdate:relative)'], s:splitter)
let cmd = 'git branch -a --format='.shellescape(format)
Expand All @@ -36,8 +43,87 @@ function! s:Init(input)
endif
call insert(branches, s:CreateItem(' ', s:item_splitter))
call insert(branches, s:CreateItem('+', s:item_new_branch))
let s:list = s:GetTabulatedList(branches)
call s:Update(a:input)
return s:GetTabulatedList(branches)
endfunction

" ---------------------------------------------------------------- fetch

" Remote branches are only as new as the last fetch: fetch in the background
" each time the list opens, then show them again if it is still open. git
" cannot ask for credentials without a terminal, so it fails instead
let s:fetch_job = v:null
let s:refs = []

function! s:StartFetch()
if !project#config#Get('git_branch_fetch') || s:fetch_job isnot v:null
\|| !(exists('*job_start') || exists('*jobstart'))
\|| empty(project#RunShellCmd('git remote', 0))
return
endif

let ctx = {'lines': []}
let cmd = [&shell, &shellcmdflag, 'git fetch --all --prune --quiet']
let env = {'GIT_TERMINAL_PROMPT': '0'}
if exists('*job_start')
let s:fetch_job = job_start(cmd, {
\'cwd': $vim_project,
\'env': env,
\'in_io': 'null',
\'out_cb': {_, line -> add(ctx.lines, line)},
\'err_cb': {_, line -> add(ctx.lines, line)},
\'exit_cb': function('s:OnVimFetchExit', [ctx]),
\})
else
let s:fetch_job = jobstart(cmd, {
\'cwd': $vim_project,
\'env': env,
\'stdin': 'null',
\'stdout_buffered': v:true,
\'stderr_buffered': v:true,
\'on_stdout': {_, data, __ -> extend(ctx.lines, data)},
\'on_stderr': {_, data, __ -> extend(ctx.lines, data)},
\'on_exit': {_, status, __ -> s:OnFetched(status, ctx)},
\})
endif
endfunction

" exit_cb may run before the output was read
function! s:OnVimFetchExit(ctx, job, status)
call timer_start(10, {-> s:OnFetched(a:status, a:ctx)})
endfunction

function! s:OnFetched(status, ctx)
let s:fetch_job = v:null
if !project#list#IsOpen('GIT_BRANCH')
return
endif
if a:status != 0
let lines = filter(copy(a:ctx.lines), '!empty(v:val)')
call project#list#SetInfoOnClose('git fetch failed: '.get(lines, 0, 'exit '.a:status))
return
endif

" Redraw only when a branch moved, not to flash the list for nothing
let refs = s:GetRefs()
if refs == s:refs
return
endif
let list = s:LoadBranches()
" Two items are New Branch and the splitter: keep the list on an error
if len(list) <= 2
return
endif
let s:refs = refs
let s:list = list
call s:Update(project#list#State().user_input)
call project#list#RedrawInputLine()
endfunction

" Branches and the commits they point to, to tell whether a fetch changed any
function! s:GetRefs()
let format = shellescape('%(refname) %(objectname) %(upstream)')
let cmd = 'git for-each-ref --format='.format.' refs/heads refs/remotes'
return project#RunShellCmd(cmd, 0)
endfunction

function! s:Update(input)
Expand Down Expand Up @@ -111,8 +197,11 @@ function! s:MergeBranch(state)

let cmd = 'git merge '.shellescape(name)

call project#RunShellCmd(cmd)
let output = project#RunShellCmd(cmd, 0)
if v:shell_error
" Once the list is closed
call timer_start(0, {-> project#git_merge#ShowConflicts()
\|| project#WarnCommandOutput(cmd, output)})
return 'close'
endif
let a:state.offset = 0
Expand Down
Loading
Loading