clink/git.lua
2022-02-28 20:15:23 +01:00

993 lines
30 KiB
Lua

-- preamble: common routines
local path = require('path')
local git = require('gitutil')
local matchers = require('matchers')
local w = require('tables').wrap
local clink_version = require('clink_version')
local color = require('color')
local parser = clink.arg.new_parser
if clink_version.supports_color_settings then
settings.add('color.git.star', 'bright green', 'Color for preferred branch completions')
end
---
-- Lists remote branches based on packed-refs file from git directory
-- @param string [dir] Directory where to search file for
-- @return table List of remote branches
local function list_packed_refs(dir)
local result = w()
local git_dir = dir or git.get_git_common_dir()
if not git_dir then return result end
local packed_refs_file = io.open(git_dir..'/packed-refs')
if packed_refs_file == nil then return {} end
for line in packed_refs_file:lines() do
-- SHA is 40 char length + 1 char for space
if #line > 41 then
local match = line:sub(41):match('refs/remotes/(.*)')
if match then table.insert(result, match) end
end
end
packed_refs_file:close()
return result
end
local function list_remote_branches(dir)
local git_dir = dir or git.get_git_common_dir()
if not git_dir then return w() end
return w(path.list_files(git_dir..'/refs/remotes', '/*',
--[[recursive=]]true, --[[reverse_separator=]]true))
:concat(list_packed_refs(git_dir))
:sort():dedupe()
end
---
-- Lists local branches for git repo in git_dir directory.
--
-- @param string [dir] Git directory, where to search for remote branches
-- @return table List of branches.
local function list_local_branches(dir)
local git_dir = dir or git.get_git_common_dir()
if not git_dir then return w() end
local result = w(path.list_files(git_dir..'/refs/heads', '/*',
--[[recursive=]]true, --[[reverse_separator=]]true))
return result
end
local branches = function (token)
local git_dir = git.get_git_common_dir()
if not git_dir then return w() end
return list_local_branches(git_dir)
:filter(function(branch)
return clink.is_match(token, branch)
end)
end
local function alias(token)
local res = w()
-- Try to resolve .git directory location
local git_dir = git.get_git_dir()
if git_dir == nil then return res end
local f = io.popen("git config --get-regexp alias 2>nul")
if f == nil then return {} end
for line in f:lines() do
local s = line:find(" ", 1, true)
local alias_name = line:sub(7, s - 1)
local start = alias_name:find(token, 1, true)
if start and start == 1 then
table.insert(res, alias_name)
end
end
f:close()
return res
end
local function remotes(token) -- luacheck: no unused args
local result = w()
local git_dir = git.get_git_common_dir()
if not git_dir then return result end
local git_config = io.open(git_dir..'/config')
-- if there is no gitconfig file (WAT?!), return empty list
if git_config == nil then return result end
for line in git_config:lines() do
local remote = line:match('%[remote "(.*)"%]')
if (remote) then
table.insert(result, remote)
end
end
git_config:close()
return result
end
local function local_or_remote_branches(token)
-- Try to resolve .git directory location
local git_dir = git.get_git_common_dir()
if not git_dir then return w() end
return list_local_branches(git_dir)
:concat(list_remote_branches(git_dir))
:filter(function(branch)
return clink.is_match(token, branch)
end)
end
local function checkout_spec_generator(token)
local files = matchers.files(token)
:filter(function(file)
return path.is_real_dir(file)
end)
local git_dir = git.get_git_common_dir()
local local_branches = branches(token)
local remote_branches = list_remote_branches(git_dir)
:filter(function(branch)
return clink.is_match(token, branch)
end)
local predicted_branches = list_remote_branches(git_dir)
:map(function (remote_branch)
return remote_branch:match('.-/(.+)')
end)
:filter(function(branch)
return branch
and clink.is_match(token, branch)
-- Filter out those predictions which are already exists as local branches
and not local_branches:contains(branch)
end)
if (#local_branches + #remote_branches + #predicted_branches) == 0 then return files end
-- if there is any refspec that matches token then:
-- * disable readline's filename completion, otherwise we'll get a list of these specs
-- threaten as list of files (without 'path' part), ie. 'some_branch' instead of 'my_remote/some_branch'
-- * create display filter for completion table to append path separator to each directory entry
-- since it is not added automatically by readline (see previous point)
clink.matches_are_files(0)
clink.match_display_filter = function ()
local star = '*'
if clink_version.supports_query_rl_var and rl.isvariabletrue('colored-stats') then
star = color.get_clink_color('color.git.star')..star..color.get_clink_color('color.filtered')
end
return files:map(function(file)
return clink.is_dir(file) and file..'\\' or file
end)
:concat(local_branches)
:concat(predicted_branches:map(function(branch) return star..branch end))
:concat(remote_branches)
end
return files
:concat(local_branches)
:concat(predicted_branches)
:concat(remote_branches)
end
local function push_branch_spec(token)
local git_dir = git.get_git_common_dir()
if not git_dir then return w() end
local plus_prefix = token:sub(0, 1) == '+'
-- cut out leading '+' symbol as it is a part of branch spec
local branch_spec = plus_prefix and token:sub(2) or token
-- check if there a local/remote branch separator
local s, e = branch_spec:find(':')
-- starting from here we have 2 options:
-- * if there is no branch separator complete word with local branches
if not s then
local b = branches(branch_spec)
-- setup display filter to prevent display '+' symbol in completion list
clink.match_display_filter = function ()
return b
end
return b:map(function(branch)
-- append '+' to results if it was specified
return plus_prefix and '+'..branch or branch
end)
else
-- * if there is ':' separator then we need to complete remote branch
local local_branch_spec = branch_spec:sub(1, s - 1)
local remote_branch_spec = branch_spec:sub(e + 1)
-- TODO: show remote branches only for remote that has been specified as previous argument
local b = w(clink.find_dirs(git_dir..'/refs/remotes/*'))
:filter(function(remote) return path.is_real_dir(remote) end)
:reduce({}, function(result, remote)
return w(path.list_files(git_dir..'/refs/remotes/'..remote, '/*',
--[[recursive=]]true, --[[reverse_separator=]]true))
:filter(function(remote_branch)
return clink.is_match(remote_branch_spec, remote_branch)
end)
:concat(result)
end)
-- setup display filter to prevent display '+' symbol in completion list
clink.match_display_filter = function ()
return b
end
return b:map(function(branch)
return (plus_prefix and '+'..local_branch_spec or local_branch_spec)..':'..branch
end)
end
end
local stashes = function(token) -- luacheck: no unused args
local git_dir = git.get_git_dir()
if not git_dir then return w() end
local stash_file = io.open(git_dir..'/logs/refs/stash')
-- if there is no stash file, return empty list
if stash_file == nil then return w() end
local stashes = {}
-- make a dictionary of stash time and stash comment to
-- be able to sort stashes by date/time created
for stash in stash_file:lines() do
local stash_time, stash_name = stash:match('(%d%d%d%d%d%d%d%d%d%d) [+-]%d%d%d%d%s+(.*)')
if (stash_name and stash_name) then
stashes[stash_time] = stash_name
end
end
stash_file:close()
-- get times for available stashes into separate table and sort it
-- from newest to oldest. This is required because of stash@{0}
-- represents _latest_ stash, not the last one in file
local stash_times = {}
for k in pairs(stashes) do
table.insert(stash_times, k)
end
table.sort(stash_times, function (a, b)
return a > b
end)
-- generate matches and match filter table
local ret = {}
local ret_filter = {}
for i,v in ipairs(stash_times) do
local match = "stash@{"..(i-1).."}"
table.insert(ret, match)
if clink_version.supports_display_filter_description then
-- Clink now has a richer match interface. By returning a table,
-- the script is able to provide the stash name separately from the
-- description. If the script does so, then the popup completion
-- window is able to show the stash name plus a dimmed description,
-- but only insert the stash name.
table.insert(ret_filter, { match=match, type="word", description=stashes[v] })
else
table.insert(ret_filter, match.." "..stashes[v])
end
end
local function filter()
return ret_filter
end
if clink_version.supports_display_filter_description then
clink.ondisplaymatches(filter)
else
clink.match_display_filter = filter
end
return ret
end
local color_opts = parser({"true", "false", "always"})
local git_options = {
"core.editor",
"core.pager",
"core.excludesfile",
"core.autocrlf"..parser({"true", "false", "input"}),
"core.trustctime"..parser({"true", "false"}),
"core.whitespace"..parser({
"cr-at-eol",
"-cr-at-eol",
"indent-with-non-tab",
"-indent-with-non-tab",
"space-before-tab",
"-space-before-tab",
"trailing-space",
"-trailing-space"
}),
"commit.template",
"color.ui"..color_opts, "color.*"..color_opts, "color.branch"..color_opts,
"color.diff"..color_opts, "color.interactive"..color_opts, "color.status"..color_opts,
"help.autocorrect",
"merge.tool", "mergetool.*.cmd", "mergetool.trustExitCode"..parser({"true", "false"}), "diff.external",
"user.name", "user.email", "user.signingkey",
}
local config_parser = parser(
"--system", "--global", "--local", "--file"..parser({matchers.files}),
"--int", "--bool", "--path",
"-z", "--null",
"--add",
"--replace-all",
"--get", "--get-all", "--get-regexp", "--get-urlmatch",
"--unset", "--unset-all",
"--rename-section", "--remove-section",
"-l", "--list",
"--get-color", "--get-colorbool",
"-e", "--edit",
{git_options}
)
local merge_recursive_options = parser({
"ours",
"theirs",
"renormalize",
"no-renormalize",
"diff-algorithm="..parser({
"patience",
"minimal",
"histogram",
"myers"
}),
"patience",
"ignore-space-change",
"ignore-all-space",
"ignore-space-at-eol",
"rename-threshold=",
-- "subtree="..parser(),
"subtree"
})
local merge_strategies = parser({
"resolve",
"recursive",
"ours",
"octopus",
"subtree"
})
local cleanup_options = parser({
"strip",
"whitespace",
"verbatim",
"scissors",
"default"
})
local git_parser = parser(
{
{alias},
"add" .. parser({matchers.files},
"-n", "--dry-run",
"-v", "--verbose",
"-f", "--force",
"-i", "--interactive",
"-p", "--patch",
"-e", "--edit",
"-u", "--update",
"-A", "--all",
"--no-all",
"--ignore-removal",
"--no-ignore-removal",
"-N", "--intent-to-add",
"--refresh",
"--ignore-errors",
"--ignore-missing"
),
"add--interactive",
"am",
"annotate" .. parser({matchers.files},
"-b",
"--root",
"--show-stats",
"-L",
"-l",
"-t",
"-S",
"--reverse",
"-p",
"--porcelain",
"--line-porcelain",
"--incremental",
"--encoding=",
"--contents",
"--date",
"-M",
"-C",
"-h"
),
"apply" .. parser(
"--stat",
"--numstat",
"--summary",
"--check",
"--index",
"--cached",
"-3", "--3way",
"--build-fake-ancestor=",
"-R", "--reverse",
"--reject",
"-z",
"-p",
"-C",
"--unidiff-zero",
"--apply",
"--no-add",
"--allow-binary-replacement", "--binary",
"--exclude=",
"--include=",
"--ignore-space-change", "--ignore-whitespace",
"--whitespace=",
"--inaccurate-eof",
"-v", "--verbose",
"--recount",
"--directory="
),
"archive",
"bisect",
"bisect--helper",
"blame",
"branch" .. parser(
"-v", "--verbose",
"-q", "--quiet",
"-t", "--track",
"--set-upstream",
"-u", "--set-upstream-to",
"--unset-upstream",
"--color",
"-r", "--remotes",
"--contains" ,
"--abbrev",
"-a", "--all",
"-d" .. parser({branches}):loop(1),
"--delete" .. parser({branches}):loop(1),
"-D" .. parser({branches}):loop(1),
"-m", "--move",
"-M",
"--list",
"-l", "--create-reflog",
"--edit-description",
"-f", "--force",
"--no-merged",
"--merged",
"--column"
),
"bundle",
"cat-file",
"check-attr",
"check-ignore",
"check-mailmap",
"check-ref-format",
"checkout" .. parser({checkout_spec_generator},
"-q", "--quiet",
"-b",
"-B",
"-l",
"--detach",
"-t", "--track",
"--orphan",
"-2", "--ours",
"-3", "--theirs",
"-f", "--force",
"-m", "--merge",
"--overwrite-ignore",
"--conflict",
"-p", "--patch",
"--ignore-skip-worktree-bits"
),
"checkout-index",
"cherry",
"cherry-pick"..parser(
"-e", "--edit",
"-m", "--mainline ",
"-n", "--no-commit",
"-r",
"-x",
"--ff",
"-s", "-S", "--gpg-sign",
"--allow-empty",
"--allow-empty-message",
"--keep-redundant-commits",
"--strategy"..parser({merge_strategies}),
"-X"..parser({merge_recursive_options}),
"--strategy-option"..parser({merge_recursive_options}),
"--continue",
"--quit",
"--abort"
),
"citool",
"clean",
"clone" .. parser(
"--template",
"-l", "--local",
"-s", "--shared",
"--no-hardlinks",
"-q", "--quiet",
"-n", "--no-checkout",
"--bare",
"--mirror",
"-o", "--origin",
"-b", "--branch",
"-u", "--upload-pack",
"--reference",
"--dissociate",
"--separate-git-dir",
"--depth",
"--single-branch", "--no-single-branch",
"--no-tags",
"--recurse-submodules", "--shallow-submodules", "--no-shallow-submodules",
"--jobs"
),
"column",
"commit" .. parser(
"-a", "--all",
"-p", "--patch",
"-C", "--reuse-message=",
"-c", "--reedit-message=",
"--fixup=",
"--squash=",
"--reset-author",
"--short",
"--branch",
"--porcelain",
"--long",
"-z",
"--null",
"-F", "--file=",
"--author=",
"--date=",
"-m", "--message=",
"-t", "--template=",
"-s", "--signoff",
"-n", "--no-verify",
"--allow-empty",
"--allow-empty-message",
"--cleanup"..cleanup_options,
"-e", "--edit",
"--no-edit",
"--amend",
"--no-post-rewrite",
"-i", "--include",
"-o", "--only",
"-u", "--untracked-files", "--untracked-files=", -- .. parser({"no", "normal", "all"}),
"-v", "--verbose",
"-q", "--quiet",
"--dry-run",
"--status",
"--no-status",
"-S", "--gpg-sign", "--gpg-sign=",
"--"
),
"commit-tree",
"config"..config_parser,
"count-objects",
"credential",
"credential-store",
"credential-wincred",
"daemon",
"describe",
"diff" .. parser({local_or_remote_branches, matchers.files}),
"diff-files",
"diff-index",
"diff-tree",
"difftool"..parser(
"-d", "--dir-diff",
"-y", "--no-prompt", "--prompt",
"-t", "--tool=" -- TODO: complete tool (take from config)
),
"difftool--helper",
"fast-export",
"fast-import",
"fetch" .. parser({remotes},
"--all",
"--prune",
"--tags"
),
"fetch-pack",
"filter-branch",
"fmt-merge-msg",
"for-each-ref",
"format-patch",
"fsck",
"fsck-objects",
"gc",
"get-tar-commit-id",
"grep",
"gui",
"gui--askpass",
"gui--askyesno",
"gui.tcl",
"hash-object",
"help",
"http-backend",
"http-fetch",
"http-push",
"imap-send",
"index-pack",
"init",
"init-db",
"log",
"lost-found",
"ls-files",
"ls-remote",
"ls-tree",
"mailinfo",
"mailsplit",
"merge" .. parser({branches},
"--commit", "--no-commit",
"--edit", "-e", "--no-edit",
"--ff", "--no-ff", "--ff-only",
"--log", "--no-log",
"--stat", "-n", "--no-stat",
"--squash", "--no-squash",
"-s" .. merge_strategies,
"--strategy" .. merge_strategies,
"-X" .. merge_recursive_options,
"--strategy-option" .. merge_recursive_options,
"--verify-signatures", "--no-verify-signatures",
"-q", "--quiet", "-v", "--verbose",
"--progress", "--no-progress",
"-S", "--gpg-sign",
"-m",
"--rerere-autoupdate", "--no-rerere-autoupdate",
"--abort"
),
"merge-base",
"merge-file",
"merge-index",
"merge-octopus",
"merge-one-file",
"merge-ours",
"merge-recursive",
"merge-resolve",
"merge-subtree",
"merge-tree",
"mergetool",
"mergetool--lib",
"mktag",
"mktree",
"mv",
"name-rev",
"notes",
"p4",
"pack-objects",
"pack-redundant",
"pack-refs",
"parse-remote",
"patch-id",
"peek-remote",
"prune",
"prune-packed",
"pull" .. parser(
{remotes}, {branches},
"-q", "--quiet",
"-v", "--verbose",
"--recurse-submodules", --[no-]recurse-submodules[=yes|on-demand|no]
"--no-recurse-submodules",
"--commit", "--no-commit",
"-e", "--edit", "--no-edit",
"--ff", "--no-ff", "--ff-only",
"--log", "--no-log",
"--stat", "-n", "--no-stat",
"--squash", "--no-squash",
"-s"..merge_strategies,
"--strategy"..merge_strategies,
"-X"..merge_recursive_options,
"--strategy-option"..merge_recursive_options,
"--verify-signatures", "--no-verify-signatures",
"--summary", "--no-summary",
"-r", "--rebase", "--no-rebase",
"--all",
"-a", "--append",
"--depth", "--unshallow", "--update-shallow",
"-f", "--force",
"-k", "--keep",
"--no-tags",
"-u", "--update-head-ok",
"--upload-pack",
"--progress"
),
"push" .. parser(
{remotes},
{push_branch_spec},
"-v", "--verbose",
"-q", "--quiet",
"--repo",
"--all",
"--mirror",
"--delete",
"--tags",
"-n", "--dry-run",
"--porcelain",
"-f", "--force",
"--force-with-lease",
"--recurse-submodules",
"--thin",
"--receive-pack",
"--exec",
"-u", "--set-upstream",
"--progress",
"--prune",
"--no-verify",
"--follow-tags"
),
"quiltimport",
"read-tree",
"rebase" .. parser({local_or_remote_branches}, {branches},
"-i", "--interactive",
"--onto" .. parser({branches}),
"--continue",
"--abort",
"--keep-empty",
"--skip",
"--edit-todo",
"-m", "--merge",
"-s" .. merge_strategies,
"--strategy"..merge_strategies,
"-X" .. merge_recursive_options,
"--strategy-option"..merge_recursive_options,
"-S", "--gpg-sign",
"-q", "--quiet",
"-v", "--verbose",
"--stat", "-n", "--no-stat",
"--no-verify", "--verify",
"-C",
"-f", "--force-rebase",
"--fork-point", "--no-fork-point",
"--ignore-whitespace", "--whitespace",
"--committer-date-is-author-date", "--ignore-date",
"-i", "--interactive",
"-p", "--preserve-merges",
"-x", "--exec",
"--root",
"--autosquash", "--no-autosquash",
"--autostash", "--no-autostash",
"--no-ff"
),
"receive-pack",
"reflog",
"remote"..parser({
"add" ..parser(
"-t"..parser({branches}),
"-m",
"-f",
"--mirror",
"--tags", "--no-tags"
),
"rename"..parser({remotes}),
"remove"..parser({remotes}),
"rm"..parser({remotes}),
"set-head"..parser({remotes}, {branches},
"-a", "--auto",
"-d", "--delete"
),
"set-branches"..parser("--add", {remotes}, {branches}),
"set-url"..parser(
"--add"..parser("--push", {remotes}),
"--delete"..parser("--push", {remotes})
),
"get-url"..parser({remotes}, "--push", "--all"),
"show"..parser("-n", {remotes}),
"prune"..parser("-n", "--dry-run", {remotes}),
"update"..parser({remotes}, "-p", "--prune")
}, "-v", "--verbose"),
"remote-ext",
"remote-fd",
"remote-ftp",
"remote-ftps",
"remote-hg",
"remote-http",
"remote-https",
"remote-testsvn",
"repack",
"replace",
"repo-config",
"request-pull",
"rerere",
-- TODO: Add commit completions
"reset"..parser({local_or_remote_branches},
"-q",
"-p", "--patch",
"--soft", "--mixed", "--hard",
"--merge", "--keep"
),
"restore"..parser({matchers.files},
"-s", "--source",
"-p", "--patch",
"-W", "--worktree",
"-S", "--staged",
"-q", "--quiet",
"--progress", "--no-progress",
"--ours", "--theirs",
"-m", "--merge",
"--conflict",
"--ignore-unmerged",
"--ignore-skip-worktree-bits",
"--overlay", "--no-overlay"
),
"rev-list",
"rev-parse",
"revert"..parser(
"-e", "--edit",
"-m", "--mainline",
"--no-edit",
"--cleanup"..cleanup_options,
"-n", "--no-commit",
"-S", "--gpg-sign",
"--no-gpg-sign",
"-s", "--signoff",
"--strategy"..merge_strategies,
"-X"..merge_recursive_options,
"--strategy-option"..merge_recursive_options,
"--rerere-autoupdate",
"--no-rerere-autoupdate",
"--continue",
"--skip",
"--quit",
"--abort"
),
"rm",
"send-email",
"send-pack",
"sh-i18n",
"sh-i18n--envsubst",
"sh-setup",
"shortlog",
"show",
"show-branch",
"show-index",
"show-ref",
"stage",
"stash"..parser({
"list", -- TODO: The command takes options applicable to the git log
-- command to control what is shown and how it's done
"show"..parser({stashes}),
"drop"..parser({stashes}, "-q", "--quiet"),
"pop"..parser({stashes}, "--index", "-q", "--quiet"),
"apply"..parser({stashes}, "--index", "-q", "--quiet"),
"branch"..parser({branches}, {stashes}),
"save"..parser(
"-p", "--patch",
"-k", "--no-keep-index", "--keep-index",
"-q", "--quiet",
"-u", "--include-untracked",
"-a", "--all"
),
"clear"
}),
"status",
"stripspace",
"submodule"..parser({
"add",
"init",
"deinit",
"foreach",
"status"..parser("--cached", "--recursive"),
"summary",
"sync",
"update"
}, '--quiet'),
"subtree",
"switch"..parser({local_or_remote_branches},
"-c", "-C", "--create",
"--force-create",
"-d", "--detach",
"--guess", "--no-guess",
"-f", "--force", "--discard-changes",
"-m", "--merge",
"--conflict",
"-q", "--quiet",
"--progress", "--no-progress",
"-t", "--track",
"--no-track",
"--orphan",
"--ignore-other-worktrees",
"--recurse-submodules", "--no-recurse-submodules"
),
"svn"..parser({
"init"..parser("-T", "--trunk", "-t", "--tags", "-b", "--branches", "-s", "--stdlayout",
"--no-metadata", "--use-svm-props", "--use-svnsync-props", "--rewrite-root",
"--rewrite-uuid", "--username", "--prefix"..parser({"origin"}), "--ignore-paths",
"--include-paths", "--no-minimize-url"),
"fetch"..parser({remotes}, "--localtime", "--parent", "--ignore-paths", "--include-paths",
"--log-window-size"),
"clone"..parser("-T", "--trunk", "-t", "--tags", "-b", "--branches", "-s", "--stdlayout",
"--no-metadata", "--use-svm-props", "--use-svnsync-props", "--rewrite-root",
"--rewrite-uuid", "--username", "--prefix"..parser({"origin"}), "--ignore-paths",
"--include-paths", "--no-minimize-url", "--preserve-empty-dirs",
"--placeholder-filename"),
"rebase"..parser({local_or_remote_branches}, {branches}),
"dcommit"..parser("--no-rebase", "--commit-url", "--mergeinfo", "--interactive"),
"branch"..parser("-m","--message","-t", "--tags", "-d", "--destination",
"--username", "--commit-url", "--parents"),
"log"..parser("-r", "--revision", "-v", "--verbose", "--limit",
"--incremental", "--show-commit", "--oneline"),
"find-rev"..parser("--before", "--after"),
"reset"..parser("-r", "--revision", "-p", "--parent"),
"tag",
"blame",
"set-tree",
"create-ignore",
"show-ignore",
"mkdirs",
"commit-diff",
"info",
"proplist",
"propget",
"show-externals",
"gc"
}),
"symbolic-ref",
"tag",
"tar-tree",
"unpack-file",
"unpack-objects",
"update-index",
"update-ref",
"update-server-info",
"upload-archive",
"upload-pack",
"var",
"verify-pack",
"verify-tag",
"web--browse",
"whatchanged",
"worktree"..parser({
"add"..parser(
{matchers.dirs},
{branches},
"-f", "--force",
"--detach",
"--checkout",
"--lock",
"-b"..parser({branches})
),
"list"..parser("--porcelain"),
"lock"..parser("--reason"),
"move",
"prune"..parser(
"-n", "--dry-run",
"-v", "--verbose",
"--expire"
),
"remove"..parser("-f"),
"unlock"
}),
"write-tree",
},
"--version",
"--help",
"-c",
"--exec-path",
"--html-path",
"--man-path",
"--info-path",
"-p", "--paginate", "--no-pager",
"--no-replace-objects",
"--bare",
"--git-dir=",
"--work-tree=",
"--namespace="
)
clink.arg.register_parser("git", git_parser)