Repository: nosarthur/gita

Issue:
track repo type 
Currently the repos are parsed into {name: path}. To support the workspace, we need to know which repos are the 'main' repos, which contains

- a local `.gita/` config that overrides the global config
- other repos (optional)

We can add a field in the `repo_path` config file, and parse the repos to something like {name: (path, type)}

The main repo is 'main' for both global and local config

Discussion:

Patch:
diff --git a/README.md b/README.md
index a583e4d..2670ee0 100644
--- a/README.md
+++ b/README.md
@@ -180,6 +180,29 @@ For example,
 
 ## <a name='custom'></a> Customization
 
+### define main repos and shadow the global configuration setting with local setting
+
+The so-called main repos contain `.gita` folder to save local configurations.
+When executing `gita` commands within/relative to a main repo, local configurations
+are used. To add a main repo, run
+
+```
+gita add -m main-repo-path
+```
+
+Subordinate repos are added recursively. It should work best for the following
+project structure
+
+```
+main-repo
+├── sub-repo1
+│   └── sub-sub-repo
+├── sub-repo2
+└── sub-repo3
+```
+
+On the other hand, the `group` works best for independent but related repos.
+
 ### add user-defined sub-command using yaml file
 
 Custom delegating sub-commands can be defined in `$XDG_CONFIG_HOME/gita/cmds.yml`
diff --git a/gita/__main__.py b/gita/__main__.py
index 02f3830..270c15a 100644
--- a/gita/__main__.py
+++ b/gita/__main__.py
@@ -31,10 +31,10 @@ def f_add(args: argparse.Namespace):
     paths = args.paths
     if args.main:
         # add to global and tag as main
-        main_repos = utils.add_repos(repos, paths, repo_type=1)
-        # add sub-repo recursively and save to local config_dir
-        for main_repo, main_path in main_repos.items():
-            print('locally,', main_repo)
+        main_repos = utils.add_repos(repos, paths, repo_type='m')
+        # add sub-repo recursively and save to local config
+        for main_path, name, _ in main_repos:
+            print('locally,', name)
             sub_paths = Path(main_path).glob('**')
             utils.add_repos({}, sub_paths, root=main_path)
     else:
@@ -91,7 +91,10 @@ def f_clone(args: argparse.Namespace):
 
 def f_freeze(_):
     repos = utils.get_repos()
-    for name, path in repos.items():
+    for name, prop in repos.items():
+        path = prop['path']
+        # TODO: What do we do with main repos? Maybe give an option to print
+        #       their sub-repos too.
         url = ''
         cp = subprocess.run(['git', 'remote', '-v'], cwd=path, capture_output=True)
         lines = cp.stdout.decode('utf-8').split('\n')
@@ -120,7 +123,7 @@ def f_ll(args: argparse.Namespace):
 def f_ls(args: argparse.Namespace):
     repos = utils.get_repos()
     if args.repo:  # one repo, show its path
-        print(repos[args.repo])
+        print(repos[args.repo]['path'])
     else:  # show names of all repos
         print(' '.join(repos))
 
@@ -196,12 +199,23 @@ def f_rm(args: argparse.Namespace):
     """
     Unregister repo(s) from gita
     """
-    path_file = common.get_config_fname('repo_path')
+    path_file = common.get_config_fname('repos.csv')
     if os.path.isfile(path_file):
         repos = utils.get_repos()
+        main_paths = [prop['path'] for prop in repos.values() if prop['type'] == 'm']
+        # TODO: add test case to delete main repo from main repo
+        #       only local setting should be affected instead of the global one
         for repo in args.repo:
             del repos[repo]
-        utils.write_to_repo_file(repos, 'w')
+        data = [(prop['path'], name, prop['type']) for name, prop in repos.items()]
+        # If cwd is relative to any main repo, write to local config
+        cwd = os.getcwd()
+        for p in main_paths:
+            if utils.is_relative_to(cwd, p):
+                utils.write_to_repo_file(data, 'w', p)
+                break
+        else:  # global config
+            utils.write_to_repo_file(data, 'w')
 
 
 def f_git_cmd(args: argparse.Namespace):
@@ -225,7 +239,8 @@ def f_git_cmd(args: argparse.Namespace):
         repos = chosen
     cmds = ['git'] + args.cmd
     if len(repos) == 1 or cmds[1] in args.async_blacklist:
-        for path in repos.values():
+        for prop in repos.values():
+            path = prop['path']
             print(path)
             subprocess.run(cmds, cwd=path)
     else:  # run concurrent subprocesses
@@ -233,7 +248,7 @@ def f_git_cmd(args: argparse.Namespace):
         # Here we shut off any user input in the async execution, and re-run
         # the failed ones synchronously.
         errors = utils.exec_async_tasks(
-            utils.run_async(repo_name, path, cmds) for repo_name, path in repos.items())
+            utils.run_async(repo_name, prop['path'], cmds) for repo_name, prop in repos.items())
         for path in errors:
             if path:
                 print(path)
@@ -268,9 +283,9 @@ def f_shell(args):
                     chosen[r] = repos[r]
         repos = chosen
     cmds = ' '.join(args.man[i:])  # join the shell command into a single string
-    for name, path in repos.items():
+    for name, prop in repos.items():
         # TODO: pull this out as a function
-        got = subprocess.run(cmds, cwd=path, check=True, shell=True,
+        got = subprocess.run(cmds, cwd=prop['path'], check=True, shell=True,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.STDOUT)
         print(utils.format_output(got.stdout.decode(), name))
diff --git a/gita/common.py b/gita/common.py
index de8e4b8..abbef5f 100644
--- a/gita/common.py
+++ b/gita/common.py
@@ -1,7 +1,4 @@
 import os
-from pathlib import Path
-from functools import lru_cache
-from typing import Dict
 
 
 def get_config_dir(root=None) -> str:
diff --git a/gita/utils.py b/gita/utils.py
index 4c0bf15..b6bd545 100644
--- a/gita/utils.py
+++ b/gita/utils.py
@@ -1,10 +1,11 @@
 import os
-import yaml
+import yaml  # TODO: to be removed
+import csv
 import asyncio
 import platform
 from functools import lru_cache, partial
 from pathlib import Path
-from typing import List, Dict, Coroutine, Union, Iterator
+from typing import List, Dict, Coroutine, Union, Iterator, Tuple
 
 from . import info
 from . import common
@@ -21,31 +22,23 @@ def is_relative_to(kid: str, parent: str) -> bool:
 @lru_cache()
 def get_repos(root=None) -> Dict[str, str]:
     """
-    Return a `dict` of repo name to repo absolute path
+    Return a `dict` of repo name to repo absolute path and repo type
 
-    @param config_dir: If None, use either global or local config depending on cwd.
+    @param root: Use local config if set. If None, use either global or local
+                 config depending on cwd.
     """
-    path_file = common.get_config_fname('repo_path', root)
+    path_file = common.get_config_fname('repos.csv', root)
     repos = {}
-    # Each line is a repo path and repo name separated by ,
     if os.path.isfile(path_file) and os.stat(path_file).st_size > 0:
         with open(path_file) as f:
-            for line in f:
-                line = line.rstrip()
-                if not line:  # blank line
-                    continue
-                path, name = line.split(',')
-                if not is_git(path):
-                    continue
-                if name not in repos:
-                    repos[name] = path
-                else:  # repo name collision for different paths: include parent path name
-                    par_name = os.path.basename(os.path.dirname(path))
-                    repos[os.path.join(par_name, name)] = path
+            rows = csv.DictReader(f, ['path', 'name', 'type'])  # it's actually a reader
+            repos = {r['name']: {'path': r['path'], 'type': r['type']}
+                        for r in rows if is_git(r['path'])}
     if root is None:  # detect if inside a main path
         cwd = os.getcwd()
-        for _, path in repos.items():
-            if is_relative_to(cwd, path):
+        for prop in repos.values():
+            path = prop['path']
+            if prop['type'] == 'm' and is_relative_to(cwd, path):
                 return get_repos(path)
     return repos
 
@@ -106,16 +99,29 @@ def is_git(path: str) -> bool:
     return os.path.exists(loc)
 
 
-def rename_repo(repos: Dict[str, str], repo: str, new_name: str):
+def rename_repo(repos: Dict[str, Dict[str, str]], repo: str, new_name: str):
     """
     Write new repo name to file
     """
     # FIXME: We should check the new name is not in use
-    path = repos[repo]
+    prop = repos[repo]
     del repos[repo]
-    repos[new_name] = path
-    write_to_repo_file(repos, 'w')
-    # update groups
+    repos[new_name] = prop
+    data = [(prop['path'], name, prop['type']) for name, prop in repos.items()]
+    # write to local config if inside a main path
+    main_paths = (d[0] for d in data if d[2] == 'm')
+    cwd = os.getcwd()
+    is_local_config = True
+    for p in main_paths:
+        if is_relative_to(cwd, p):
+            write_to_repo_file(data, 'w', p)
+            break
+    else:  # global config
+        write_to_repo_file(data, 'w')
+        is_local_config = False
+    # update groups only when outside any main repos
+    if is_local_config:
+        return
     groups = get_groups()
     for g, members in groups.items():
         if repo in members:
@@ -125,14 +131,15 @@ def rename_repo(repos: Dict[str, str], repo: str, new_name: str):
     write_to_groups_file(groups, 'w')
 
 
-def write_to_repo_file(repos: Dict[str, str], mode: str, root=None):
+def write_to_repo_file(repos: List[Tuple[str]], mode: str, root=None):
     """
+    @param repos: each repo is (path, name, type)
     """
-    data = ''.join(f'{path},{name}\n' for name, path in repos.items())
-    fname = common.get_config_fname('repo_path', root)
+    fname = common.get_config_fname('repos.csv', root)
     os.makedirs(os.path.dirname(fname), exist_ok=True)
     with open(fname, mode) as f:
-        f.write(data)
+        writer = csv.writer(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
+        writer.writerows(repos)
 
 
 def write_to_groups_file(groups: Dict[str, List[str]], mode: str):
@@ -148,22 +155,49 @@ def write_to_groups_file(groups: Dict[str, List[str]], mode: str):
             yaml.dump(groups, f, default_flow_style=None)
 
 
-def add_repos(repos: Dict[str, str], new_paths: List[str],
-        repo_type=0, root=None) -> Dict[str, str]:
+def _make_name(path: str, repos: Dict[str, Dict[str, str]]) -> str:
+    """
+    Given a new repo `path`, create a repo name. By default, basename is used.
+    If name collision exists, further include parent path name.
+
+    @param path: It should not be in `repos`
+    """
+    name = os.path.basename(os.path.normpath(path))
+    if name in repos:
+        par_name = os.path.basename(os.path.dirname(path))
+        return os.path.join(par_name, name)
+    return name
+
+
+def _get_repo_type(path, repo_type, root) -> str:
+    """
+
+    """
+    if repo_type != '':  # explicitly set
+        return repo_type
+    if root == path:  # main repo
+        return 'm'
+    return ''
+
+
+def add_repos(repos: Dict[str, Dict[str, str]], new_paths: List[str],
+        repo_type='', root=None) -> List[Tuple]:
     """
     Write new repo paths to file; return the added repos.
 
     @param repos: name -> path
     """
-    existing_paths = set(repos.values())
-    new_paths = set(os.path.abspath(p) for p in new_paths if is_git(p))
+    existing_paths = {prop['path'] for prop in repos.values()}
+    new_paths = {os.path.abspath(p) for p in new_paths if is_git(p)}
     new_paths = new_paths - existing_paths
     new_repos = {}
     if new_paths:
         print(f"Found {len(new_paths)} new repo(s).")
-        new_repos = {
-                os.path.basename(os.path.normpath(path)): path
-                for path in new_paths}
+        new_repos = [(path, _make_name(path, repos),
+                        _get_repo_type(path, repo_type, root))
+                     for path in new_paths]
+        # When root is not None, we could optionally set its type to 'm', i.e.,
+        # main repo.
         write_to_repo_file(new_repos, 'a+', root)
     else:
         print('No new repos found!')
@@ -226,7 +260,7 @@ def exec_async_tasks(tasks: List[Coroutine]) -> List[Union[None, str]]:
     return errors
 
 
-def describe(repos: Dict[str, str], no_colors: bool=False) -> str:
+def describe(repos: Dict[str, Dict[str, str]], no_colors: bool=False) -> str:
     """
     Return the status of all repos
     """
@@ -240,7 +274,7 @@ def describe(repos: Dict[str, str], no_colors: bool=False) -> str:
         funcs[idx] = partial(get_repo_status, no_colors=True)
 
     for name in sorted(repos):
-        path = repos[name]
+        path = repos[name]['path']
         info_items = ' '.join(f(path) for f in funcs)
         yield f'{name:<{name_width}}{info_items}'
 
diff --git a/work.vim b/work.vim
new file mode 100644
index 0000000..25ad90f
--- /dev/null
+++ b/work.vim
@@ -0,0 +1,766 @@
+let SessionLoad = 1
+if &cp | set nocp | endif
+let s:cpo_save=&cpo
+set cpo&vim
+inoremap <silent> <Plug>(-fzf-complete-finish) l
+inoremap <silent> <Plug>CocRefresh =coc#_complete()
+inoremap <silent> <Plug>(fzf-maps-i) :call fzf#vim#maps('i', 0)
+inoremap <expr> <Plug>(fzf-complete-buffer-line) fzf#vim#complete#buffer_line()
+inoremap <expr> <Plug>(fzf-complete-line) fzf#vim#complete#line()
+inoremap <expr> <Plug>(fzf-complete-file-ag) fzf#vim#complete#path('ag -l -g ""')
+inoremap <expr> <Plug>(fzf-complete-file) fzf#vim#complete#path("find . -path '*/\.*' -prune -o -type f -print -o -type l -print | sed 's:^..::'")
+inoremap <expr> <Plug>(fzf-complete-path) fzf#vim#complete#path("find . -path '*/\.*' -prune -o -print | sed '1d;s:^..::'")
+inoremap <expr> <Plug>(fzf-complete-word) fzf#vim#complete#word()
+inoremap <silent> <SNR>20_AutoPairsReturn =AutoPairsReturn()
+inoremap <silent> <expr> <C-Space> coc#refresh()
+inoremap <expr> <S-Tab> pumvisible() ? "\" : "\"
+map! <D-v> *
+nnoremap * *``
+nmap <silent> ,ig <Plug>IndentGuidesToggle
+noremap ,4 4gt
+noremap ,3 3gt
+noremap ,2 2gt
+noremap ,1 1gt
+nmap ,d :GitGutterFold
+nmap ,r :Rg!
+nmap ,b :Buffer
+nmap ,l :Lines!
+nmap ,w :BLines
+nmap ,o :Files!
+nmap ,f :GFiles!
+nmap ,a :CtrlSF -R ""<Left>
+nmap ,t :TagbarToggle
+noremap ,  :noh:call clearmatches()
+vnoremap ,s :sort
+nnoremap ,s :w
+noremap ,e :qa! " Quit all windows
+noremap ,q :q " Quit current windows
+vnoremap < <gv
+vnoremap > >gv
+nnoremap N Nzzzv
+vmap gx <Plug>NetrwBrowseXVis
+nmap gx <Plug>NetrwBrowseX
+nmap g> <Plug>(swap-next)
+nmap g< <Plug>(swap-prev)
+xmap gs <Plug>(swap-interactive)
+nmap gs <Plug>(swap-interactive)
+nmap <silent> gr <Plug>(coc-references)
+nmap <silent> gi <Plug>(coc-implementation)
+nmap <silent> gy <Plug>(coc-type-definition)
+nmap <silent> gd <Plug>(coc-definition)
+nnoremap n nzzzv
+nnoremap <silent> <Plug>(-fzf-complete-finish) a
+nnoremap <Plug>(-fzf-:) :
+nnoremap <Plug>(-fzf-/) /
+nnoremap <Plug>(-fzf-vim-do) :execute g:__fzf_command
+vnoremap <silent> <Plug>NetrwBrowseXVis :call netrw#BrowseXVis()
+nnoremap <silent> <Plug>NetrwBrowseX :call netrw#BrowseX(netrw#GX(),netrw#CheckIfRemote(netrw#GX()))
+onoremap <silent> <Plug>(coc-classobj-a) :call coc#rpc#request('selectSymbolRange', [v:false, '', ['Interface', 'Struct', 'Class']])
+onoremap <silent> <Plug>(coc-classobj-i) :call coc#rpc#request('selectSymbolRange', [v:true, '', ['Interface', 'Struct', 'Class']])
+vnoremap <silent> <Plug>(coc-classobj-a) :call coc#rpc#request('selectSymbolRange', [v:false, visualmode(), ['Interface', 'Struct', 'Class']])
+vnoremap <silent> <Plug>(coc-classobj-i) :call coc#rpc#request('selectSymbolRange', [v:true, visualmode(), ['Interface', 'Struct', 'Class']])
+onoremap <silent> <Plug>(coc-funcobj-a) :call coc#rpc#request('selectSymbolRange', [v:false, '', ['Method', 'Function']])
+onoremap <silent> <Plug>(coc-funcobj-i) :call coc#rpc#request('selectSymbolRange', [v:true, '', ['Method', 'Function']])
+vnoremap <silent> <Plug>(coc-funcobj-a) :call coc#rpc#request('selectSymbolRange', [v:false, visualmode(), ['Method', 'Function']])
+vnoremap <silent> <Plug>(coc-funcobj-i) :call coc#rpc#request('selectSymbolRange', [v:true, visualmode(), ['Method', 'Function']])
+nnoremap <silent> <Plug>(coc-cursors-position) :call coc#rpc#request('cursorsSelect', [bufnr('%'), 'position', 'n'])
+nnoremap <silent> <Plug>(coc-cursors-word) :call coc#rpc#request('cursorsSelect', [bufnr('%'), 'word', 'n'])
+vnoremap <silent> <Plug>(coc-cursors-range) :call coc#rpc#request('cursorsSelect', [bufnr('%'), 'range', visualmode()])
+nnoremap <silent> <Plug>(coc-refactor) :call       CocActionAsync('refactor')
+nnoremap <silent> <Plug>(coc-command-repeat) :call       CocAction('repeatCommand')
+nnoremap <silent> <Plug>(coc-float-jump) :call       coc#float#jump()
+nnoremap <silent> <Plug>(coc-float-hide) :call       coc#float#close_all()
+nnoremap <silent> <Plug>(coc-fix-current) :call       CocActionAsync('doQuickfix')
+nnoremap <silent> <Plug>(coc-openlink) :call       CocActionAsync('openLink')
+nnoremap <silent> <Plug>(coc-references-used) :call       CocActionAsync('jumpUsed')
+nnoremap <silent> <Plug>(coc-references) :call       CocActionAsync('jumpReferences')
+nnoremap <silent> <Plug>(coc-type-definition) :call       CocActionAsync('jumpTypeDefinition')
+nnoremap <silent> <Plug>(coc-implementation) :call       CocActionAsync('jumpImplementation')
+nnoremap <silent> <Plug>(coc-declaration) :call       CocActionAsync('jumpDeclaration')
+nnoremap <silent> <Plug>(coc-definition) :call       CocActionAsync('jumpDefinition')
+nnoremap <silent> <Plug>(coc-diagnostic-prev-error) :call       CocActionAsync('diagnosticPrevious', 'error')
+nnoremap <silent> <Plug>(coc-diagnostic-next-error) :call       CocActionAsync('diagnosticNext',     'error')
+nnoremap <silent> <Plug>(coc-diagnostic-prev) :call       CocActionAsync('diagnosticPrevious')
+nnoremap <silent> <Plug>(coc-diagnostic-next) :call       CocActionAsync('diagnosticNext')
+nnoremap <silent> <Plug>(coc-diagnostic-info) :call       CocActionAsync('diagnosticInfo')
+nnoremap <silent> <Plug>(coc-format) :call       CocActionAsync('format')
+nnoremap <silent> <Plug>(coc-rename) :call       CocActionAsync('rename')
+nnoremap <Plug>(coc-codeaction-cursor) :call       CocActionAsync('codeAction',         'cursor')
+nnoremap <Plug>(coc-codeaction-line) :call       CocActionAsync('codeAction',         'line')
+nnoremap <Plug>(coc-codeaction) :call       CocActionAsync('codeAction',         '')
+vnoremap <silent> <Plug>(coc-codeaction-selected) :call       CocActionAsync('codeAction',         visualmode())
+vnoremap <silent> <Plug>(coc-format-selected) :call       CocActionAsync('formatSelected',     visualmode())
+nnoremap <Plug>(coc-codelens-action) :call       CocActionAsync('codeLensAction')
+nnoremap <Plug>(coc-range-select) :call       CocActionAsync('rangeSelect',     '', v:true)
+vnoremap <silent> <Plug>(coc-range-select-backward) :call       CocActionAsync('rangeSelect',     visualmode(), v:false)
+vnoremap <silent> <Plug>(coc-range-select) :call       CocActionAsync('rangeSelect',     visualmode(), v:true)
+noremap <silent> <Plug>(swap-textobject-a) :call swap#textobj#select('a')
+noremap <silent> <Plug>(swap-textobject-i) :call swap#textobj#select('i')
+nnoremap <silent> <Plug>(swap-next) :call swap#prerequisite('n', repeat([['#', '#+1']], v:count1))g@l
+nnoremap <silent> <Plug>(swap-prev) :call swap#prerequisite('n', repeat([['#', '#-1']], v:count1))g@l
+xnoremap <silent> <Plug>(swap-interactive) :call swap#prerequisite('x')gvg@
+nnoremap <silent> <Plug>(swap-interactive) :call swap#prerequisite('n')g@l
+onoremap <silent> <Plug>(fzf-maps-o) :call fzf#vim#maps('o', 0)
+xnoremap <silent> <Plug>(fzf-maps-x) :call fzf#vim#maps('x', 0)
+nnoremap <silent> <Plug>(fzf-maps-n) :call fzf#vim#maps('n', 0)
+tnoremap <silent> <Plug>(fzf-normal) 
+tnoremap <silent> <Plug>(fzf-insert) i
+nnoremap <silent> <Plug>(fzf-normal) <Nop>
+nnoremap <silent> <Plug>(fzf-insert) i
+nnoremap <Plug>CtrlSFQuickfixPrompt :CtrlSFQuickfix 
+nnoremap <Plug>CtrlSFPrompt :CtrlSF 
+nnoremap <silent> <Plug>GitGutterPreviewHunk :call gitgutter#utility#warn('Please change your map <Plug>GitGutterPreviewHunk to <Plug>(GitGutterPreviewHunk)')
+nnoremap <silent> <Plug>(GitGutterPreviewHunk) :GitGutterPreviewHunk
+nnoremap <silent> <Plug>GitGutterUndoHunk :call gitgutter#utility#warn('Please change your map <Plug>GitGutterUndoHunk to <Plug>(GitGutterUndoHunk)')
+nnoremap <silent> <Plug>(GitGutterUndoHunk) :GitGutterUndoHunk
+nnoremap <silent> <Plug>GitGutterStageHunk :call gitgutter#utility#warn('Please change your map <Plug>GitGutterStageHunk to <Plug>(GitGutterStageHunk)')
+nnoremap <silent> <Plug>(GitGutterStageHunk) :GitGutterStageHunk
+xnoremap <silent> <Plug>GitGutterStageHunk :call gitgutter#utility#warn('Please change your map <Plug>GitGutterStageHunk to <Plug>(GitGutterStageHunk)')
+xnoremap <silent> <Plug>(GitGutterStageHunk) :GitGutterStageHunk
+nnoremap <silent> <expr> <Plug>GitGutterPrevHunk &diff ? '[c' : ":\call gitgutter#utility#warn('Please change your map \<Plug>GitGutterPrevHunk to \<Plug>(GitGutterPrevHunk)')\"
+nnoremap <silent> <expr> <Plug>(GitGutterPrevHunk) &diff ? '[c' : ":\execute v:count1 . 'GitGutterPrevHunk'\"
+nnoremap <silent> <expr> <Plug>GitGutterNextHunk &diff ? ']c' : ":\call gitgutter#utility#warn('Please change your map \<Plug>GitGutterNextHunk to \<Plug>(GitGutterNextHunk)')\"
+nnoremap <silent> <expr> <Plug>(GitGutterNextHunk) &diff ? ']c' : ":\execute v:count1 . 'GitGutterNextHunk'\"
+xnoremap <silent> <Plug>(GitGutterTextObjectOuterVisual) :call gitgutter#hunk#text_object(0)
+xnoremap <silent> <Plug>(GitGutterTextObjectInnerVisual) :call gitgutter#hunk#text_object(1)
+onoremap <silent> <Plug>(GitGutterTextObjectOuterPending) :call gitgutter#hunk#text_object(0)
+onoremap <silent> <Plug>(GitGutterTextObjectInnerPending) :call gitgutter#hunk#text_object(1)
+vmap <BS> "-d
+vmap <D-x> "*d
+vmap <D-c> "*y
+vmap <D-v> "-d"*P
+nmap <D-v> "*P
+inoremap <expr>  complete_info()["selected"] != "-1" ? "\" : "\u\"
+inoremap ,s :w
+let &cpo=s:cpo_save
+unlet s:cpo_save
+set autoindent
+set background=dark
+set backspace=2
+set clipboard=unnamed
+set expandtab
+set fileencodings=ucs-bom,utf-8,default,latin1
+set helplang=en
+set hlsearch
+set ignorecase
+set laststatus=2
+set modelines=0
+set path=.,/usr/include,,,**
+set runtimepath=~/.vim,~/.vim/plugged/vim-gitgutter/,~/.vim/plugged/ctrlsf.vim/,~/.vim/plugged/lightline.vim/,~/.vim/plugged/auto-pairs/,~/.vim/plugged/fzf/,~/.vim/plugged/fzf.vim/,~/.vim/plugged/goyo.vim/,~/.vim/plugged/gv.vim/,~/.vim/plugged/seoul256.vim/,~/.vim/plugged/vim-swap/,~/.vim/plugged/tagbar/,~/.vim/plugged/coc.nvim/,~/.vim/plugged/vim-fugitive/,~/.vim/plugged/vim-indent-guides/,/usr/share/vim/vimfiles,/usr/share/vim/vim82,/usr/share/vim/vimfiles/after,~/.vim/plugged/ctrlsf.vim/after,~/.vim/after
+set shiftwidth=4
+set smartcase
+set noswapfile
+set tabline=%!lightline#tabline()
+set tabstop=4
+set title
+set updatetime=100
+set wildignore=*.pyc
+set wildmenu
+set wildmode=longest:list,full
+set window=0
+set nowritebackup
+let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0
+let v:this_session=expand("<sfile>:p")
+silent only
+silent tabonly
+cd ~/src/gita
+if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == ''
+  let s:wipebuf = bufnr('%')
+endif
+set shortmess=aoO
+argglobal
+%argdel
+$argadd gita/__main__.py
+set stal=2
+tabnew
+tabrewind
+edit gita/utils.py
+set splitbelow splitright
+wincmd _ | wincmd |
+vsplit
+1wincmd h
+wincmd w
+set nosplitbelow
+set nosplitright
+wincmd t
+set winminheight=0
+set winheight=1
+set winminwidth=0
+set winwidth=1
+exe 'vert 1resize ' . ((&columns * 94 + 94) / 188)
+exe 'vert 2resize ' . ((&columns * 93 + 94) / 188)
+argglobal
+let s:cpo_save=&cpo
+set cpo&vim
+inoremap <buffer> <silent> <M-n> :call AutoPairsJump()a
+inoremap <buffer> <silent> <expr> <M-p> AutoPairsToggle()
+inoremap <buffer> <silent> <M-b> =AutoPairsBackInsert()
+inoremap <buffer> <silent> <C-W> =AutoPairsFastWrap()
+inoremap <buffer> <silent> <C-H> =AutoPairsDelete()
+inoremap <buffer> <silent> <BS> =AutoPairsDelete()
+inoremap <buffer> <silent> <M-'> =AutoPairsMoveCharacter('''')
+inoremap <buffer> <silent> <M-"> =AutoPairsMoveCharacter('"')
+inoremap <buffer> <silent> <M-}> =AutoPairsMoveCharacter('}')
+inoremap <buffer> <silent> <M-{> =AutoPairsMoveCharacter('{')
+inoremap <buffer> <silent> <M-]> =AutoPairsMoveCharacter(']')
+inoremap <buffer> <silent> <M-[> =AutoPairsMoveCharacter('[')
+inoremap <buffer> <silent> <M-)> =AutoPairsMoveCharacter(')')
+inoremap <buffer> <silent> <M-(> =AutoPairsMoveCharacter('(')
+nmap <buffer> ,hp <Plug>(GitGutterPreviewHunk)
+nmap <buffer> ,hu <Plug>(GitGutterUndoHunk)
+nmap <buffer> ,hs <Plug>(GitGutterStageHunk)
+xmap <buffer> ,hs <Plug>(GitGutterStageHunk)
+inoremap <buffer> <silent> § =AutoPairsMoveCharacter('''')
+inoremap <buffer> <silent> ¢ =AutoPairsMoveCharacter('"')
+inoremap <buffer> <silent> © =AutoPairsMoveCharacter(')')
+inoremap <buffer> <silent> ¨ =AutoPairsMoveCharacter('(')
+inoremap <buffer> <silent> î :call AutoPairsJump()a
+inoremap <buffer> <silent> <expr> ð AutoPairsToggle()
+inoremap <buffer> <silent> â =AutoPairsBackInsert()
+inoremap <buffer> <silent> ý =AutoPairsMoveCharacter('}')
+inoremap <buffer> <silent> û =AutoPairsMoveCharacter('{')
+inoremap <buffer> <silent> Ý =AutoPairsMoveCharacter(']')
+inoremap <buffer> <silent> Û =AutoPairsMoveCharacter('[')
+nmap <buffer> [c <Plug>(GitGutterPrevHunk)
+nmap <buffer> ]c <Plug>(GitGutterNextHunk)
+xmap <buffer> ac <Plug>(GitGutterTextObjectOuterVisual)
+omap <buffer> ac <Plug>(GitGutterTextObjectOuterPending)
+xmap <buffer> ic <Plug>(GitGutterTextObjectInnerVisual)
+omap <buffer> ic <Plug>(GitGutterTextObjectInnerPending)
+noremap <buffer> <silent> <M-n> :call AutoPairsJump()
+noremap <buffer> <silent> <M-p> :call AutoPairsToggle()
+inoremap <buffer> <silent>  =AutoPairsDelete()
+inoremap <buffer> <silent>  =AutoPairsFastWrap()
+inoremap <buffer> <silent>   =AutoPairsSpace()
+inoremap <buffer> <silent> " =AutoPairsInsert('"')
+inoremap <buffer> <silent> ' =AutoPairsInsert('''')
+inoremap <buffer> <silent> ( =AutoPairsInsert('(')
+inoremap <buffer> <silent> ) =AutoPairsInsert(')')
+noremap <buffer> <silent> î :call AutoPairsJump()
+noremap <buffer> <silent> ð :call AutoPairsToggle()
+inoremap <buffer> <silent> [ =AutoPairsInsert('[')
+inoremap <buffer> <silent> ] =AutoPairsInsert(']')
+inoremap <buffer> <silent> ` =AutoPairsInsert('`')
+inoremap <buffer> <silent> { =AutoPairsInsert('{')
+inoremap <buffer> <silent> } =AutoPairsInsert('}')
+let &cpo=s:cpo_save
+unlet s:cpo_save
+setlocal autoindent
+setlocal backupcopy=
+setlocal nobinary
+setlocal nobreakindent
+setlocal breakindentopt=
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),0],:,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+set colorcolumn=80
+setlocal colorcolumn=80
+setlocal comments=b:#,fb:-
+setlocal commentstring=#\ %s
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal cryptmethod=
+setlocal nocursorbind
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal cursorlineopt=both
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'python'
+setlocal filetype=python
+endif
+setlocal fixendofline
+setlocal foldcolumn=0
+set nofoldenable
+setlocal nofoldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+set foldmethod=indent
+setlocal foldmethod=indent
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal formatprg=
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=-1
+setlocal include=^\\s*\\(from\\|import\\)
+setlocal includeexpr=substitute(substitute(substitute(v:fname,b:grandparent_match,b:grandparent_sub,''),b:parent_match,b:parent_sub,''),b:child_match,b:child_sub,'g')
+setlocal indentexpr=GetPythonIndent(v:lnum)
+setlocal indentkeys=0{,0},0),0],:,!^F,o,O,e,<:>,=elif,=except
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=pydoc
+setlocal nolinebreak
+setlocal nolisp
+setlocal lispwords=
+setlocal nolist
+setlocal makeencoding=
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=bin,octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=pythoncomplete#Complete
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norelativenumber
+setlocal noscrollbind
+setlocal scrolloff=-1
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal showbreak=
+setlocal sidescrolloff=-1
+setlocal signcolumn=auto
+setlocal nosmartindent
+setlocal softtabstop=4
+set spell
+setlocal spell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\	\ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=%{lightline#link()}%#LightlineLeft_active_0#%(\ %{lightline#mode()}\ %)%{(&paste)?\"|\":\"\"}%(\ %{&paste?\"PASTE\":\"\"}\ %)%#LightlineLeft_active_0_1#%#LightlineLeft_active_1#%(\ %R\ %)%{(&readonly)&&(1||(&modified||!&modifiable))?\"|\":\"\"}%(\ %t\ %)%{(&modified||!&modifiable)?\"|\":\"\"}%(\ %M\ %)%#LightlineLeft_active_1_2#%#LightlineMiddle_active#%=%#LightlineRight_active_2_3#%#LightlineRight_active_2#%(\ %{&ff}\ %)%{1||1?\"|\":\"\"}%(\ %{&fenc!=#\"\"?&fenc:&enc}\ %)%{1?\"|\":\"\"}%(\ %{&ft!=#\"\"?&ft:\"no\ ft\"}\ %)%#LightlineRight_active_1_2#%#LightlineRight_active_1#%(\ %3p%%\ %)%#LightlineRight_active_0_1#%#LightlineRight_active_0#%(\ %3l:%-2c\ %)
+setlocal suffixesadd=.py
+setlocal noswapfile
+setlocal synmaxcol=3000
+if &syntax != 'python'
+setlocal syntax=python
+endif
+setlocal tabstop=8
+setlocal tagcase=
+setlocal tagfunc=
+setlocal tags=
+setlocal termwinkey=
+setlocal termwinscroll=10000
+setlocal termwinsize=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal noundofile
+setlocal undolevels=-123456
+setlocal wincolor=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+set nowrap
+setlocal nowrap
+setlocal wrapmargin=0
+let s:l = 83 - ((21 * winheight(0) + 21) / 43)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+83
+normal! 05|
+wincmd w
+argglobal
+if bufexists("gita/__main__.py") | buffer gita/__main__.py | else | edit gita/__main__.py | endif
+let s:cpo_save=&cpo
+set cpo&vim
+inoremap <buffer> <silent> <M-n> :call AutoPairsJump()a
+inoremap <buffer> <silent> <expr> <M-p> AutoPairsToggle()
+inoremap <buffer> <silent> <M-b> =AutoPairsBackInsert()
+inoremap <buffer> <silent> <C-W> =AutoPairsFastWrap()
+inoremap <buffer> <silent> <C-H> =AutoPairsDelete()
+inoremap <buffer> <silent> <BS> =AutoPairsDelete()
+inoremap <buffer> <silent> <M-'> =AutoPairsMoveCharacter('''')
+inoremap <buffer> <silent> <M-"> =AutoPairsMoveCharacter('"')
+inoremap <buffer> <silent> <M-}> =AutoPairsMoveCharacter('}')
+inoremap <buffer> <silent> <M-{> =AutoPairsMoveCharacter('{')
+inoremap <buffer> <silent> <M-]> =AutoPairsMoveCharacter(']')
+inoremap <buffer> <silent> <M-[> =AutoPairsMoveCharacter('[')
+inoremap <buffer> <silent> <M-)> =AutoPairsMoveCharacter(')')
+inoremap <buffer> <silent> <M-(> =AutoPairsMoveCharacter('(')
+nmap <buffer> ,hp <Plug>(GitGutterPreviewHunk)
+nmap <buffer> ,hu <Plug>(GitGutterUndoHunk)
+nmap <buffer> ,hs <Plug>(GitGutterStageHunk)
+xmap <buffer> ,hs <Plug>(GitGutterStageHunk)
+inoremap <buffer> <silent> § =AutoPairsMoveCharacter('''')
+inoremap <buffer> <silent> ¢ =AutoPairsMoveCharacter('"')
+inoremap <buffer> <silent> © =AutoPairsMoveCharacter(')')
+inoremap <buffer> <silent> ¨ =AutoPairsMoveCharacter('(')
+inoremap <buffer> <silent> î :call AutoPairsJump()a
+inoremap <buffer> <silent> <expr> ð AutoPairsToggle()
+inoremap <buffer> <silent> â =AutoPairsBackInsert()
+inoremap <buffer> <silent> ý =AutoPairsMoveCharacter('}')
+inoremap <buffer> <silent> û =AutoPairsMoveCharacter('{')
+inoremap <buffer> <silent> Ý =AutoPairsMoveCharacter(']')
+inoremap <buffer> <silent> Û =AutoPairsMoveCharacter('[')
+nmap <buffer> [c <Plug>(GitGutterPrevHunk)
+nmap <buffer> ]c <Plug>(GitGutterNextHunk)
+xmap <buffer> ac <Plug>(GitGutterTextObjectOuterVisual)
+omap <buffer> ac <Plug>(GitGutterTextObjectOuterPending)
+xmap <buffer> ic <Plug>(GitGutterTextObjectInnerVisual)
+omap <buffer> ic <Plug>(GitGutterTextObjectInnerPending)
+noremap <buffer> <silent> <M-n> :call AutoPairsJump()
+noremap <buffer> <silent> <M-p> :call AutoPairsToggle()
+inoremap <buffer> <silent>  =AutoPairsDelete()
+inoremap <buffer> <silent>  =AutoPairsFastWrap()
+inoremap <buffer> <silent>   =AutoPairsSpace()
+inoremap <buffer> <silent> " =AutoPairsInsert('"')
+inoremap <buffer> <silent> ' =AutoPairsInsert('''')
+inoremap <buffer> <silent> ( =AutoPairsInsert('(')
+inoremap <buffer> <silent> ) =AutoPairsInsert(')')
+noremap <buffer> <silent> î :call AutoPairsJump()
+noremap <buffer> <silent> ð :call AutoPairsToggle()
+inoremap <buffer> <silent> [ =AutoPairsInsert('[')
+inoremap <buffer> <silent> ] =AutoPairsInsert(']')
+inoremap <buffer> <silent> ` =AutoPairsInsert('`')
+inoremap <buffer> <silent> { =AutoPairsInsert('{')
+inoremap <buffer> <silent> } =AutoPairsInsert('}')
+let &cpo=s:cpo_save
+unlet s:cpo_save
+setlocal autoindent
+setlocal backupcopy=
+setlocal nobinary
+setlocal nobreakindent
+setlocal breakindentopt=
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),0],:,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+set colorcolumn=0
+setlocal colorcolumn=0
+setlocal comments=b:#,fb:-
+setlocal commentstring=#\ %s
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal cryptmethod=
+setlocal nocursorbind
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal cursorlineopt=both
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'python'
+setlocal filetype=python
+endif
+setlocal fixendofline
+setlocal foldcolumn=0
+set nofoldenable
+setlocal nofoldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+set foldmethod=indent
+setlocal foldmethod=indent
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal formatprg=
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=-1
+setlocal include=^\\s*\\(from\\|import\\)
+setlocal includeexpr=substitute(substitute(substitute(v:fname,b:grandparent_match,b:grandparent_sub,''),b:parent_match,b:parent_sub,''),b:child_match,b:child_sub,'g')
+setlocal indentexpr=GetPythonIndent(v:lnum)
+setlocal indentkeys=0{,0},0),0],:,!^F,o,O,e,<:>,=elif,=except
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=pydoc
+setlocal nolinebreak
+setlocal nolisp
+setlocal lispwords=
+setlocal nolist
+setlocal makeencoding=
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=bin,octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=pythoncomplete#Complete
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norelativenumber
+setlocal noscrollbind
+setlocal scrolloff=-1
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal showbreak=
+setlocal sidescrolloff=-1
+setlocal signcolumn=auto
+setlocal nosmartindent
+setlocal softtabstop=4
+set spell
+setlocal spell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\	\ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=%{lightline#link()}%#LightlineLeft_inactive_0#%(\ %t\ %)%#LightlineLeft_inactive_0_1#%#LightlineMiddle_inactive#%=%#LightlineRight_inactive_1_2#%#LightlineRight_inactive_1#%(\ %3p%%\ %)%#LightlineRight_inactive_0_1#%#LightlineRight_inactive_0#%(\ %3l:%-2c\ %)
+setlocal suffixesadd=.py
+setlocal noswapfile
+setlocal synmaxcol=3000
+if &syntax != 'python'
+setlocal syntax=python
+endif
+setlocal tabstop=8
+setlocal tagcase=
+setlocal tagfunc=
+setlocal tags=
+setlocal termwinkey=
+setlocal termwinscroll=10000
+setlocal termwinsize=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal noundofile
+setlocal undolevels=-123456
+setlocal wincolor=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+set nowrap
+setlocal nowrap
+setlocal wrapmargin=0
+let s:l = 34 - ((32 * winheight(0) + 21) / 43)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+34
+normal! 09|
+wincmd w
+exe 'vert 1resize ' . ((&columns * 94 + 94) / 188)
+exe 'vert 2resize ' . ((&columns * 93 + 94) / 188)
+tabnext
+edit gita/common.py
+set splitbelow splitright
+set nosplitbelow
+set nosplitright
+wincmd t
+set winminheight=0
+set winheight=1
+set winminwidth=0
+set winwidth=1
+argglobal
+let s:cpo_save=&cpo
+set cpo&vim
+inoremap <buffer> <silent> <M-n> :call AutoPairsJump()a
+inoremap <buffer> <silent> <expr> <M-p> AutoPairsToggle()
+inoremap <buffer> <silent> <M-b> =AutoPairsBackInsert()
+inoremap <buffer> <silent> <C-W> =AutoPairsFastWrap()
+inoremap <buffer> <silent> <C-H> =AutoPairsDelete()
+inoremap <buffer> <silent> <BS> =AutoPairsDelete()
+inoremap <buffer> <silent> <M-'> =AutoPairsMoveCharacter('''')
+inoremap <buffer> <silent> <M-"> =AutoPairsMoveCharacter('"')
+inoremap <buffer> <silent> <M-}> =AutoPairsMoveCharacter('}')
+inoremap <buffer> <silent> <M-{> =AutoPairsMoveCharacter('{')
+inoremap <buffer> <silent> <M-]> =AutoPairsMoveCharacter(']')
+inoremap <buffer> <silent> <M-[> =AutoPairsMoveCharacter('[')
+inoremap <buffer> <silent> <M-)> =AutoPairsMoveCharacter(')')
+inoremap <buffer> <silent> <M-(> =AutoPairsMoveCharacter('(')
+nmap <buffer> ,hp <Plug>(GitGutterPreviewHunk)
+nmap <buffer> ,hu <Plug>(GitGutterUndoHunk)
+nmap <buffer> ,hs <Plug>(GitGutterStageHunk)
+xmap <buffer> ,hs <Plug>(GitGutterStageHunk)
+inoremap <buffer> <silent> § =AutoPairsMoveCharacter('''')
+inoremap <buffer> <silent> ¢ =AutoPairsMoveCharacter('"')
+inoremap <buffer> <silent> © =AutoPairsMoveCharacter(')')
+inoremap <buffer> <silent> ¨ =AutoPairsMoveCharacter('(')
+inoremap <buffer> <silent> î :call AutoPairsJump()a
+inoremap <buffer> <silent> <expr> ð AutoPairsToggle()
+inoremap <buffer> <silent> â =AutoPairsBackInsert()
+inoremap <buffer> <silent> ý =AutoPairsMoveCharacter('}')
+inoremap <buffer> <silent> û =AutoPairsMoveCharacter('{')
+inoremap <buffer> <silent> Ý =AutoPairsMoveCharacter(']')
+inoremap <buffer> <silent> Û =AutoPairsMoveCharacter('[')
+nmap <buffer> [c <Plug>(GitGutterPrevHunk)
+nmap <buffer> ]c <Plug>(GitGutterNextHunk)
+xmap <buffer> ac <Plug>(GitGutterTextObjectOuterVisual)
+omap <buffer> ac <Plug>(GitGutterTextObjectOuterPending)
+xmap <buffer> ic <Plug>(GitGutterTextObjectInnerVisual)
+omap <buffer> ic <Plug>(GitGutterTextObjectInnerPending)
+noremap <buffer> <silent> <M-n> :call AutoPairsJump()
+noremap <buffer> <silent> <M-p> :call AutoPairsToggle()
+inoremap <buffer> <silent>  =AutoPairsDelete()
+inoremap <buffer> <silent>  =AutoPairsFastWrap()
+inoremap <buffer> <silent>   =AutoPairsSpace()
+inoremap <buffer> <silent> " =AutoPairsInsert('"')
+inoremap <buffer> <silent> ' =AutoPairsInsert('''')
+inoremap <buffer> <silent> ( =AutoPairsInsert('(')
+inoremap <buffer> <silent> ) =AutoPairsInsert(')')
+noremap <buffer> <silent> î :call AutoPairsJump()
+noremap <buffer> <silent> ð :call AutoPairsToggle()
+inoremap <buffer> <silent> [ =AutoPairsInsert('[')
+inoremap <buffer> <silent> ] =AutoPairsInsert(']')
+inoremap <buffer> <silent> ` =AutoPairsInsert('`')
+inoremap <buffer> <silent> { =AutoPairsInsert('{')
+inoremap <buffer> <silent> } =AutoPairsInsert('}')
+let &cpo=s:cpo_save
+unlet s:cpo_save
+setlocal autoindent
+setlocal backupcopy=
+setlocal nobinary
+setlocal nobreakindent
+setlocal breakindentopt=
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),0],:,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+set colorcolumn=0
+setlocal colorcolumn=0
+setlocal comments=b:#,fb:-
+setlocal commentstring=#\ %s
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal cryptmethod=
+setlocal nocursorbind
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal cursorlineopt=both
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'python'
+setlocal filetype=python
+endif
+setlocal fixendofline
+setlocal foldcolumn=0
+set nofoldenable
+setlocal nofoldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+set foldmethod=indent
+setlocal foldmethod=indent
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal formatprg=
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=-1
+setlocal include=^\\s*\\(from\\|import\\)
+setlocal includeexpr=substitute(substitute(substitute(v:fname,b:grandparent_match,b:grandparent_sub,''),b:parent_match,b:parent_sub,''),b:child_match,b:child_sub,'g')
+setlocal indentexpr=GetPythonIndent(v:lnum)
+setlocal indentkeys=0{,0},0),0],:,!^F,o,O,e,<:>,=elif,=except
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=pydoc
+setlocal nolinebreak
+setlocal nolisp
+setlocal lispwords=
+setlocal nolist
+setlocal makeencoding=
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=bin,octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=pythoncomplete#Complete
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norelativenumber
+setlocal noscrollbind
+setlocal scrolloff=-1
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal showbreak=
+setlocal sidescrolloff=-1
+setlocal signcolumn=auto
+setlocal nosmartindent
+setlocal softtabstop=4
+set spell
+setlocal spell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\	\ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=%{lightline#link()}%#LightlineLeft_active_0#%(\ %{lightline#mode()}\ %)%{(&paste)?\"|\":\"\"}%(\ %{&paste?\"PASTE\":\"\"}\ %)%#LightlineLeft_active_0_1#%#LightlineLeft_active_1#%(\ %R\ %)%{(&readonly)&&(1||(&modified||!&modifiable))?\"|\":\"\"}%(\ %t\ %)%{(&modified||!&modifiable)?\"|\":\"\"}%(\ %M\ %)%#LightlineLeft_active_1_2#%#LightlineMiddle_active#%=%#LightlineRight_active_2_3#%#LightlineRight_active_2#%(\ %{&ff}\ %)%{1||1?\"|\":\"\"}%(\ %{&fenc!=#\"\"?&fenc:&enc}\ %)%{1?\"|\":\"\"}%(\ %{&ft!=#\"\"?&ft:\"no\ ft\"}\ %)%#LightlineRight_active_1_2#%#LightlineRight_active_1#%(\ %3p%%\ %)%#LightlineRight_active_0_1#%#LightlineRight_active_0#%(\ %3l:%-2c\ %)
+setlocal suffixesadd=.py
+setlocal noswapfile
+setlocal synmaxcol=3000
+if &syntax != 'python'
+setlocal syntax=python
+endif
+setlocal tabstop=8
+setlocal tagcase=
+setlocal tagfunc=
+setlocal tags=
+setlocal termwinkey=
+setlocal termwinscroll=10000
+setlocal termwinsize=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal noundofile
+setlocal undolevels=-123456
+setlocal wincolor=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+set nowrap
+setlocal nowrap
+setlocal wrapmargin=0
+let s:l = 2 - ((1 * winheight(0) + 21) / 43)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+2
+normal! 0
+tabnext 1
+set stal=1
+badd +34 gita/__main__.py
+badd +0 gita/utils.py
+badd +0 gita/common.py
+if exists('s:wipebuf') && len(win_findbuf(s:wipebuf)) == 0
+  silent exe 'bwipe ' . s:wipebuf
+endif
+unlet! s:wipebuf
+set winheight=1 winwidth=20 shortmess=filnxtToOS
+set winminheight=1 winminwidth=1
+let s:sx = expand("<sfile>:p:r")."x.vim"
+if filereadable(s:sx)
+  exe "source " . fnameescape(s:sx)
+endif
+let &so = s:so_save | let &siso = s:siso_save
+nohlsearch
+doautoall SessionLoadPost
+unlet SessionLoad
+" vim: set ft=vim :


Test patch:
diff --git a/tests/test_main.py b/tests/test_main.py
index f8ec9ad..b77c07b 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -12,6 +12,25 @@ from conftest import (
 )
 
 
+class TestAdd:
+
+    @pytest.mark.parametrize('input, expected', [
+        (['add', '.'], ''),
+        (['add', '-m', '.'], 'm'),
+        ])
+    @patch('gita.common.get_config_fname')
+    def testAdd(self, mock_path_fname, tmp_path, input, expected):
+        def side_effect(input, _=None):
+            return tmp_path / f'{input}.txt'
+        mock_path_fname.side_effect = side_effect
+        utils.get_repos.cache_clear()
+        __main__.main(input)
+        utils.get_repos.cache_clear()
+        got = utils.get_repos()
+        assert len(got) == 1
+        assert got['gita']['type'] == expected
+
+
 class TestLsLl:
     @patch('gita.common.get_config_fname')
     def testLl(self, mock_path_fname, capfd, tmp_path):
@@ -21,8 +40,8 @@ class TestLsLl:
         # avoid modifying the local configuration
         def side_effect(input, _=None):
             return tmp_path / f'{input}.txt'
-        #mock_path_fname.return_value = tmp_path / 'path_config.txt'
         mock_path_fname.side_effect = side_effect
+        utils.get_repos.cache_clear()
         __main__.main(['add', '.'])
         out, err = capfd.readouterr()
         assert err == ''
@@ -52,11 +71,11 @@ class TestLsLl:
         __main__.main(['ls', 'gita'])
         out, err = capfd.readouterr()
         assert err == ''
-        assert out.strip() == utils.get_repos()['gita']
+        assert out.strip() == utils.get_repos()['gita']['path']
 
     def testLs(self, monkeypatch, capfd):
         monkeypatch.setattr(utils, 'get_repos',
-                lambda: {'repo1': '/a/', 'repo2': '/b/'})
+                lambda: {'repo1': {'path': '/a/'}, 'repo2': {'path': '/b/'}})
         monkeypatch.setattr(utils, 'describe', lambda x: x)
         __main__.main(['ls'])
         out, err = capfd.readouterr()
@@ -72,7 +91,7 @@ class TestLsLl:
          "repo1 cmaster dsu\x1b[0m msg \nrepo2 cmaster dsu\x1b[0m msg \nxxx   cmaster dsu\x1b[0m msg \n"),
         (PATH_FNAME_EMPTY, ""),
         (PATH_FNAME_CLASH,
-         "repo1   cmaster dsu\x1b[0m msg \nrepo2   cmaster dsu\x1b[0m msg \nx/repo1 cmaster dsu\x1b[0m msg \n"
+         "repo1 cmaster dsu\x1b[0m msg \nrepo2 cmaster dsu\x1b[0m msg \n"
          ),
     ])
     @patch('gita.utils.is_git', return_value=True)
@@ -84,7 +103,7 @@ class TestLsLl:
     def testWithPathFiles(self, mock_path_fname, _0, _1, _2, _3, _4, path_fname,
                           expected, capfd):
         def side_effect(input, _=None):
-            if input == 'repo_path':
+            if input == 'repos.csv':
                 return path_fname
             return f'/{input}'
         mock_path_fname.side_effect = side_effect
@@ -97,7 +116,7 @@ class TestLsLl:
 
 
 @patch('subprocess.run')
-@patch('gita.utils.get_repos', return_value={'repo1': '/a/', 'repo2': '/b/'})
+@patch('gita.utils.get_repos', return_value={'repo1': {'path': '/a/'}, 'repo2': {'path': '/b/'}})
 def test_freeze(_, mock_run, capfd):
     __main__.main(['freeze'])
     assert mock_run.call_count == 2
@@ -108,13 +127,13 @@ def test_freeze(_, mock_run, capfd):
 
 @patch('os.path.isfile', return_value=True)
 @patch('gita.common.get_config_fname', return_value='some path')
-@patch('gita.utils.get_repos', return_value={'repo1': '/a/', 'repo2': '/b/'})
+@patch('gita.utils.get_repos', return_value={'repo1': {'path': '/a/', 'type': None}, 'repo2': {'path': '/b/', 'type': None}})
 @patch('gita.utils.write_to_repo_file')
 def test_rm(mock_write, *_):
     args = argparse.Namespace()
     args.repo = ['repo1']
     __main__.f_rm(args)
-    mock_write.assert_called_once_with({'repo2': '/b/'}, 'w')
+    mock_write.assert_called_once_with([('/b/', 'repo2', None)], 'w')
 
 
 def test_not_add():
@@ -122,7 +141,7 @@ def test_not_add():
     __main__.main(['add', '/home/some/repo/'])
 
 
-@patch('gita.utils.get_repos', return_value={'repo2': '/d/efg'})
+@patch('gita.utils.get_repos', return_value={'repo2': {'path': '/d/efg'}})
 @patch('subprocess.run')
 def test_fetch(mock_run, *_):
     __main__.main(['fetch'])
@@ -131,8 +150,8 @@ def test_fetch(mock_run, *_):
 
 @patch(
     'gita.utils.get_repos', return_value={
-        'repo1': '/a/bc',
-        'repo2': '/d/efg'
+        'repo1': {'path': '/a/bc'},
+        'repo2': {'path': '/d/efg'}
     })
 @patch('gita.utils.run_async', new=async_mock())
 @patch('subprocess.run')
@@ -150,7 +169,7 @@ def test_async_fetch(*_):
     'diff --name-only --staged',
     "commit -am 'lala kaka'",
 ])
-@patch('gita.utils.get_repos', return_value={'repo7': 'path7'})
+@patch('gita.utils.get_repos', return_value={'repo7': {'path': 'path7'}})
 @patch('subprocess.run')
 def test_superman(mock_run, _, input):
     mock_run.reset_mock()
@@ -164,7 +183,7 @@ def test_superman(mock_run, _, input):
     'diff --name-only --staged',
     "commit -am 'lala kaka'",
 ])
-@patch('gita.utils.get_repos', return_value={'repo7': 'path7'})
+@patch('gita.utils.get_repos', return_value={'repo7': {'path': 'path7'}})
 @patch('subprocess.run')
 def test_shell(mock_run, _, input):
     mock_run.reset_mock()
@@ -320,8 +339,9 @@ def test_rename(mock_rename, _, __):
     args = ['rename', 'repo1', 'abc']
     __main__.main(args)
     mock_rename.assert_called_once_with(
-        {'repo1': '/a/bcd/repo1', 'repo2': '/e/fgh/repo2',
-            'xxx': '/a/b/c/repo3'},
+        {'repo1': {'path': '/a/bcd/repo1', 'type': None},
+            'xxx': {'path': '/a/b/c/repo3', 'type': None},
+            'repo2': {'path': '/e/fgh/repo2', 'type': None}},
         'repo1', 'abc')
 
 
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 690bac3..3072804 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -9,9 +9,9 @@ from conftest import (
 
 
 @pytest.mark.parametrize('test_input, diff_return, expected', [
-    ([{'abc': '/root/repo/'}, False], True, 'abc \x1b[31mrepo *+_  \x1b[0m msg xx'),
-    ([{'abc': '/root/repo/'}, True], True, 'abc repo *+_   msg xx'),
-    ([{'repo': '/root/repo2/'}, False], False, 'repo \x1b[32mrepo _    \x1b[0m msg xx'),
+    ([{'abc': {'path': '/root/repo/'}}, False], True, 'abc \x1b[31mrepo *+_  \x1b[0m msg xx'),
+    ([{'abc': {'path': '/root/repo/'}}, True], True, 'abc repo *+_   msg xx'),
+    ([{'repo': {'path': '/root/repo2/'}}, False], False, 'repo \x1b[32mrepo _    \x1b[0m msg xx'),
 ])
 def test_describe(test_input, diff_return, expected, monkeypatch):
     monkeypatch.setattr(info, 'get_head', lambda x: 'repo')
@@ -27,15 +27,14 @@ def test_describe(test_input, diff_return, expected, monkeypatch):
 
 @pytest.mark.parametrize('path_fname, expected', [
     (PATH_FNAME, {
-        'repo1': '/a/bcd/repo1',
-        'repo2': '/e/fgh/repo2',
-        'xxx': '/a/b/c/repo3',
+        'repo1': {'path': '/a/bcd/repo1', 'type': None},
+        'repo2': {'path': '/e/fgh/repo2', 'type': None},
+        'xxx': {'path': '/a/b/c/repo3', 'type': None},
     }),
     (PATH_FNAME_EMPTY, {}),
     (PATH_FNAME_CLASH, {
-        'repo1': '/a/bcd/repo1',
-        'repo2': '/e/fgh/repo2',
-        'x/repo1': '/root/x/repo1'
+        'repo2': {'path': '/e/fgh/repo2', 'type': None},
+        'repo1': {'path': '/root/x/repo1', 'type': None}
     }),
 ])
 @patch('gita.utils.is_git', return_value=True)
@@ -79,25 +78,26 @@ def test_custom_push_cmd(*_):
 @pytest.mark.parametrize(
     'path_input, expected',
     [
-        (['/home/some/repo/'], '/home/some/repo,repo\n'),  # add one new
+        (['/home/some/repo/'], '/home/some/repo,some/repo,\r\n'),  # add one new
         (['/home/some/repo1', '/repo2'],
-            {'/repo2,repo2\n/home/some/repo1,repo1\n',  # add two new
-            '/home/some/repo1,repo1\n/repo2,repo2\n'}),  # add two new
+            {'/repo2,repo2,\r\n',  # add two new
+            '/home/some/repo1,repo1,\r\n'}),  # add two new
         (['/home/some/repo1', '/nos/repo'],
-         '/home/some/repo1,repo1\n'),  # add one old one new
+         '/home/some/repo1,repo1,\r\n'),  # add one old one new
     ])
 @patch('os.makedirs')
 @patch('gita.utils.is_git', return_value=True)
 def test_add_repos(_0, _1, path_input, expected, monkeypatch):
     monkeypatch.setenv('XDG_CONFIG_HOME', '/config')
     with patch('builtins.open', mock_open()) as mock_file:
-        utils.add_repos({'repo': '/nos/repo'}, path_input)
-    mock_file.assert_called_with('/config/gita/repo_path', 'a+')
+        utils.add_repos({'repo': {'path': '/nos/repo'}}, path_input)
+    mock_file.assert_called_with('/config/gita/repos.csv', 'a+')
     handle = mock_file()
     if type(expected) == str:
         handle.write.assert_called_once_with(expected)
     else:
-        handle.write.assert_called_once()
+        # the write order is random
+        assert handle.write.call_count == 2
         args, kwargs = handle.write.call_args
         assert args[0] in expected
         assert not kwargs
@@ -105,8 +105,10 @@ def test_add_repos(_0, _1, path_input, expected, monkeypatch):
 
 @patch('gita.utils.write_to_repo_file')
 def test_rename_repo(mock_write):
-    utils.rename_repo({'r1': '/a/b', 'r2': '/c/c'}, 'r2', 'xxx')
-    mock_write.assert_called_once_with({'r1': '/a/b', 'xxx': '/c/c'}, 'w')
+    utils.rename_repo({'r1': {'path': '/a/b', 'type': None},
+        'r2': {'path': '/c/c', 'type': None}}, 'r2', 'xxx')
+    mock_write.assert_called_once_with(
+            [('/a/b', 'r1', None), ('/c/c', 'xxx', None)], 'w')
 
 
 def test_async_output(capfd):