let s:File = vital#gina#import('System.File') let s:Path = vital#gina#import('System.Filepath') function! gina#action#index#define(binder) abort call a:binder.define('index:add', function('s:on_add'), { \ 'hidden': 1, \ 'description': 'Add changes to an index', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': {}, \}) call a:binder.define('index:add:force', function('s:on_add'), { \ 'hidden': 1, \ 'description': 'Add changes to an index', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': { 'force': 1 }, \}) call a:binder.define('index:add:intent-to-add', function('s:on_add'), { \ 'hidden': 1, \ 'description': 'Intent to add changes to an index', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': { 'intent-to-add': 1 }, \}) call a:binder.define('index:rm', function('s:on_rm'), { \ 'hidden': 1, \ 'description': 'Remove files from a working tree and from an index', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': {}, \}) call a:binder.define('index:rm:force', function('s:on_rm'), { \ 'hidden': 1, \ 'description': 'Remove files from a working tree and from an index', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': { 'force': 1 }, \}) call a:binder.define('index:rm:cached', function('s:on_rm'), { \ 'hidden': 1, \ 'description': 'Remove files from an index but a working tree', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': { 'cached': 1 }, \}) call a:binder.define('index:reset', function('s:on_reset'), { \ 'hidden': 1, \ 'description': 'Reset changes on an index', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': {}, \}) call a:binder.define('index:stage', function('s:on_stage'), { \ 'description': 'Stage changes to an index', \ 'mapping_mode': 'nv', \ 'requirements': ['path', 'sign'], \ 'options': {}, \}) call a:binder.define('index:stage:force', function('s:on_stage'), { \ 'hidden': 1, \ 'description': 'Stage changes to an index', \ 'mapping_mode': 'nv', \ 'requirements': ['path', 'sign'], \ 'options': { 'force': 1 }, \}) call a:binder.define('index:unstage', function('s:on_unstage'), { \ 'description': 'Unstage changes from an index', \ 'mapping_mode': 'nv', \ 'requirements': ['path', 'sign'], \ 'options': {}, \}) call a:binder.define('index:toggle', function('s:on_toggle'), { \ 'description': 'Toggle stage/unstage of changes in an index', \ 'mapping_mode': 'nv', \ 'requirements': ['path', 'sign'], \ 'options': {}, \}) call a:binder.define('index:checkout', function('s:on_checkout'), { \ 'description': 'Checkout a content from an index', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': {}, \}) call a:binder.define('index:checkout:force', function('s:on_checkout'), { \ 'hidden': 1, \ 'description': 'Checkout a content from an index', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': { 'force': 1 }, \}) call a:binder.define('index:checkout:HEAD', function('s:on_checkout'), { \ 'hidden': 1, \ 'description': 'Checkout a content from a HEAD', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': { 'rev': 'HEAD' }, \}) call a:binder.define('index:checkout:HEAD:force', function('s:on_checkout'), { \ 'hidden': 1, \ 'description': 'Checkout a content from a HEAD', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': { 'rev': 'HEAD', 'force': 1 }, \}) call a:binder.define('index:checkout:origin', function('s:on_checkout'), { \ 'hidden': 1, \ 'description': 'Checkout a content from an origin/HEAD', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': { 'rev': 'origin/HEAD' }, \}) call a:binder.define('index:checkout:origin:force', function('s:on_checkout'), { \ 'hidden': 1, \ 'description': 'Checkout a content from an origin/HEAD', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': { 'rev': 'origin/HEAD', 'force': 1 }, \}) call a:binder.define('index:checkout:ours', function('s:on_checkout'), { \ 'hidden': 1, \ 'description': 'Checkout a content from local (ours) during merge', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': { 'ours': 1 }, \}) call a:binder.define('index:checkout:theirs', function('s:on_checkout'), { \ 'hidden': 1, \ 'description': 'Checkout a content from remote (theirs) during merge', \ 'mapping_mode': 'nv', \ 'requirements': ['path'], \ 'options': { 'theirs': 1 }, \}) call a:binder.define('index:discard', function('s:on_discard'), { \ 'description': 'Discard changes on a working tree', \ 'mapping_mode': 'nv', \ 'requirements': ['path', 'sign'], \ 'options': {}, \}) call a:binder.define('index:discard:force', function('s:on_discard'), { \ 'hidden': 1, \ 'description': 'Discard changes on a working tree', \ 'mapping_mode': 'nv', \ 'requirements': ['path', 'sign'], \ 'options': { 'force': 1 }, \}) endfunction function! s:on_add(candidates, options) abort if empty(a:candidates) return endif let options = extend({ \ 'force': 0, \ 'intent-to-add': 0, \}, a:options) let pathlist = map(copy(a:candidates), 'v:val.path') execute printf( \ '%s Gina add --ignore-errors %s %s -- %s', \ options.mods, \ options.force ? '--force' : '', \ options['intent-to-add'] ? '--intent-to-add' : '', \ gina#util#shellescape(pathlist), \) endfunction function! s:on_rm(candidates, options) abort if empty(a:candidates) return endif let options = extend({ \ 'cached': 0, \ 'force': 0, \}, a:options) let pathlist = map(copy(a:candidates), 'v:val.path') execute printf( \ '%s Gina rm --quiet --ignore-unmatch %s %s -- %s', \ options.mods, \ options.force ? '--force' : '', \ options.cached ? '--cached' : '', \ gina#util#shellescape(pathlist), \) endfunction function! s:on_reset(candidates, options) abort if empty(a:candidates) return endif let options = extend({}, a:options) let pathlist = map(copy(a:candidates), 'v:val.path') execute printf( \ '%s Gina reset --quiet -- %s', \ options.mods, \ gina#util#shellescape(pathlist), \) endfunction function! s:on_stage(candidates, options) abort dict if empty(a:candidates) return endif let options = extend({ \ 'force': 0, \}, a:options) let rm_candidates = [] let add_candidates = [] for candidate in a:candidates if candidate.sign =~# '^.D$' call add(rm_candidates, candidate) elseif candidate.sign !~# '^. $' call add(add_candidates, candidate) endif endfor if options.force call self.call(options.mods . 'index:add:force', add_candidates) if !empty(rm_candidates) call gina#process#wait() endif call self.call(options.mods . 'index:rm:force', rm_candidates) else call self.call(options.mods . 'index:add', add_candidates) if !empty(rm_candidates) call gina#process#wait() endif call self.call(options.mods . 'index:rm', rm_candidates) endif endfunction function! s:on_unstage(candidates, options) abort dict let options = extend({}, a:options) call self.call(options.mods . 'index:reset', a:candidates) endfunction function! s:on_toggle(candidates, options) abort dict if empty(a:candidates) return endif let options = extend({}, a:options) let stage_candidates = [] let unstage_candidates = [] for candidate in a:candidates if candidate.sign =~# '^\%(??\|!!\|.\w\)$' call add(stage_candidates, candidate) elseif candidate.sign =~# '^\w.$' call add(unstage_candidates, candidate) endif endfor call self.call(options.mods . 'index:stage', stage_candidates) if !empty(unstage_candidates) call gina#process#wait() endif call self.call(options.mods . 'index:unstage', unstage_candidates) endfunction function! s:on_checkout(candidates, options) abort if empty(a:candidates) return endif let options = extend({ \ 'force': 0, \ 'ours': 0, \ 'theirs': 0, \ 'rev': '', \}, a:options) let pathlist = map(copy(a:candidates), 'v:val.path') execute printf( \ '%s Gina! checkout --quiet %s %s %s %s -- %s', \ options.mods, \ options.force ? '--force' : '', \ options.ours ? '--ours' : '', \ options.theirs ? '--theirs' : '', \ gina#util#shellescape(options.rev), \ gina#util#shellescape(pathlist), \) endfunction function! s:on_discard(candidates, options) abort dict if empty(a:candidates) return endif let git = gina#core#get_or_fail() let options = extend({ \ 'force': 0, \}, a:options) let delete_candidates = [] let checkout_candidates = [] for candidate in a:candidates if candidate.sign =~# '^\%(??\|!!\)$' call add(delete_candidates, candidate) else call add(checkout_candidates, candidate) endif endfor if !options.force call gina#core#console#warn(join([ \ 'A discard action will discard all local changes on the working ', \ 'tree and the operation is irreversible, mean that you have no ', \ 'chance to revert the operation.', \], "\n")) call gina#core#console#info( \ 'This operation will be performed to the following candidates:' \) for candidate in extend(copy(delete_candidates), checkout_candidates) call gina#core#console#echo('- ' . s:Path.realpath(candidate.path)) endfor if !gina#core#console#confirm('Are you sure to discard the changes?', 'n') return endif endif " delete untracked files for candidate in delete_candidates let abspath = s:Path.realpath(gina#core#repo#abspath(git, candidate.path)) if isdirectory(abspath) if g:gina#action#index#discard_directories call s:File.rmdir(abspath, 'r') else call gina#core#console#info(printf( \ '"%s" is directory. While g:gina#action#index#discard_directories is not set, it is not removed.', \ candidate.path, \)) endif elseif filewritable(abspath) call delete(abspath) endif endfor call self.call(options.mods . 'index:checkout:HEAD:force', checkout_candidates) if !empty(delete_candidates) && empty(checkout_candidates) call gina#core#emitter#emit('modified:delay') endif endfunction " Config --------------------------------------------------------------------- call gina#config(expand(''), { \ 'discard_directories': 0, \})