Packages
fnord
0.9.40
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/git_cli/default.ex
defmodule GitCli.Default do
@moduledoc """
Real implementation of the `GitCli` behaviour: thin wrappers over
`System.cmd("git", ...)`. See `GitCli` for the public contract and
per-function documentation.
Calls to public siblings go back through the `GitCli` facade rather
than locally, so a test double installed on the `:git_cli` Globals
key intercepts nested calls, not just top-level entry points.
"""
@behaviour GitCli
@impl GitCli
def is_git_repo? do
case System.cmd("git", ["rev-parse", "--is-inside-work-tree"],
cd: effective_git_dir(),
stderr_to_stdout: true
) do
{"true\n", 0} -> true
_ -> false
end
end
@impl GitCli
def is_git_repo_at?(nil), do: false
def is_git_repo_at?(path) when is_binary(path) do
case System.cmd("git", ["rev-parse", "--is-inside-work-tree"],
cd: path,
stderr_to_stdout: true
) do
{"true\n", 0} -> true
_ -> false
end
end
@impl GitCli
def repo_root() do
git = System.find_executable("git")
if git do
case System.cmd(git, ["rev-parse", "--show-toplevel"],
cd: effective_git_dir(),
stderr_to_stdout: true
) do
{out, 0} -> String.trim(out)
_ -> nil
end
else
nil
end
end
@impl GitCli
def repo_root_at(path) when is_binary(path) do
case System.cmd("git", ["rev-parse", "--show-toplevel"],
cd: path,
stderr_to_stdout: true
) do
{out, 0} -> {:ok, String.trim(out)}
_ -> {:error, :not_a_repo}
end
end
@impl GitCli
def is_worktree? do
GitCli.is_git_repo?()
end
@impl GitCli
def worktree_root() do
if GitCli.is_worktree?() do
case System.cmd("git", ["rev-parse", "--show-toplevel"],
cd: effective_git_dir(),
stderr_to_stdout: true
) do
{result, 0} -> String.trim(result)
_ -> nil
end
else
nil
end
end
# Branch reporting follows the same effective directory semantics as the
# other repo and worktree helpers in this module.
@impl GitCli
def current_branch() do
git = System.find_executable("git")
if git do
branch_name(git, effective_git_dir())
else
nil
end
end
@impl GitCli
def branch_upstream(root, branch) when is_binary(root) and is_binary(branch) do
case System.cmd("git", ["rev-parse", "--abbrev-ref", "#{branch}@{upstream}"],
cd: root,
stderr_to_stdout: true
) do
{out, 0} ->
upstream = String.trim(out)
case upstream do
"" -> nil
value -> value
end
_ ->
nil
end
end
def branch_upstream(_root, _branch), do: nil
@spec branch_name(String.t(), String.t()) :: String.t() | nil
defp branch_name(git, dir) do
case System.cmd(git, ["rev-parse", "--abbrev-ref", "HEAD"],
cd: dir,
stderr_to_stdout: true
) do
{out, 0} ->
case String.trim(out) do
"HEAD" ->
case System.cmd(git, ["rev-parse", "--short", "HEAD"],
cd: dir,
stderr_to_stdout: true
) do
{sha, 0} -> "@" <> String.trim(sha)
_ -> nil
end
branch ->
branch
end
_ ->
nil
end
end
@impl GitCli
def git_info() do
git = System.find_executable("git")
if git do
root =
case System.cmd(git, ["rev-parse", "--show-toplevel"],
cd: effective_git_dir(),
stderr_to_stdout: true
) do
{out, 0} -> String.trim(out)
_ -> nil
end
branch =
if root do
branch_name(git, effective_git_dir())
end
if root && branch do
"""
You are working in a git repository.
The current branch is `#{branch}`.
The git root is `#{root}`.
"""
else
"Note: this project is not under git version control."
end
else
"Note: git executable not found on PATH."
end
end
@spec effective_git_dir() :: String.t()
defp effective_git_dir() do
Settings.get_project_root_override() || File.cwd!()
end
@impl GitCli
def ignored_files(nil), do: %{}
def ignored_files(root) when is_binary(root) do
case System.cmd("git", ["ls-files", "--others", "--ignored", "--exclude-standard"],
cd: root,
stderr_to_stdout: true
) do
{out, 0} ->
out
|> String.split("\n", trim: true)
|> Enum.map(&Path.absname(&1, root))
|> Map.new(&{&1, true})
_ ->
%{}
end
end
@impl GitCli
def default_branch(nil), do: nil
def default_branch(root) when is_binary(root) do
key = {__MODULE__, :default_branch, root}
case :persistent_term.get(key, :miss) do
:miss ->
result = resolve_default_branch(root)
:persistent_term.put(key, result)
result
cached ->
cached
end
end
# Forks 2-4 git subprocesses (is_git_repo_at? + symbolic-ref, then
# rev-parse probes for main/master). Called per file under the index
# scan's async_stream via Source.hash and Source.exists?, so without
# the persistent_term wrapper above this is O(N) fork/exec round
# trips per pass - minutes of scan time on a thousand-file repo.
# Cached for the BEAM's lifetime: the branch tip doesn't advance
# during a single fnord invocation and the mode decision must stay
# stable across a scan.
defp resolve_default_branch(root) do
cond do
not GitCli.is_git_repo_at?(root) -> nil
branch = remote_head(root) -> branch
branch_exists?(root, "main") -> "main"
branch_exists?(root, "master") -> "master"
true -> nil
end
end
defp remote_head(root) do
case System.cmd("git", ["symbolic-ref", "--quiet", "refs/remotes/origin/HEAD"],
cd: root,
stderr_to_stdout: true
) do
{out, 0} ->
case out |> String.trim() |> String.replace_prefix("refs/remotes/origin/", "") do
"" -> nil
branch -> branch
end
_ ->
nil
end
end
defp branch_exists?(root, branch) do
case System.cmd("git", ["rev-parse", "--verify", "--quiet", "refs/heads/#{branch}"],
cd: root,
stderr_to_stdout: true
) do
{_, 0} -> true
_ -> false
end
end
@impl GitCli
def ls_tree(root, branch) when is_binary(root) and is_binary(branch) do
case System.cmd(
"git",
["ls-tree", "-r", "--full-tree", "--format=%(objectname)\t%(path)", branch],
cd: root,
stderr_to_stdout: true
) do
{out, 0} ->
entries =
out
|> String.split("\n", trim: true)
|> Enum.flat_map(fn line ->
case String.split(line, "\t", parts: 2) do
[sha, path] when byte_size(sha) > 0 and byte_size(path) > 0 -> [{sha, path}]
_ -> []
end
end)
{:ok, entries}
{err, _} ->
{:error, String.trim(err)}
end
end
@impl GitCli
def show_blob(root, branch, rel_path)
when is_binary(root) and is_binary(branch) and is_binary(rel_path) do
case System.cmd("git", ["show", "#{branch}:#{rel_path}"], cd: root, stderr_to_stdout: false) do
{out, 0} -> {:ok, out}
{err, _} -> {:error, String.trim(err)}
end
end
@impl GitCli
def commit_shas(root, ref) when is_binary(root) and is_binary(ref) do
case System.cmd("git", ["rev-list", ref],
cd: root,
stderr_to_stdout: true
) do
{out, 0} -> {:ok, String.split(String.trim(out), "\n", trim: true)}
{err, status} -> {:error, {status, err}}
end
end
# The \x1f (ASCII unit separator) field delimiter is chosen as the byte
# least likely to appear in commit text; a literal \x1f in a subject or
# body still breaks the parse, which surfaces as :invalid_commit_metadata.
@impl GitCli
def commit_meta(root, sha) when is_binary(root) and is_binary(sha) do
case System.cmd(
"git",
[
"show",
"--quiet",
"--format=%H\x1f%P\x1f%an\x1f%at\x1f%s\x1f%b",
sha
],
cd: root,
stderr_to_stdout: true
) do
{out, 0} ->
case String.trim(out) do
"" ->
{:error, :empty_commit_output}
line ->
case String.split(line, "\x1f") do
[commit_sha, parents, author, committed_at, subject, body] ->
{:ok,
%{
sha: commit_sha,
parent_shas: String.split(parents, " ", trim: true),
author: author,
committed_at: committed_at,
subject: subject,
body: body
}}
_ ->
{:error, :invalid_commit_metadata}
end
end
{error, status} ->
{:error, {status, error}}
end
end
@impl GitCli
def commit_numstat(root, sha) when is_binary(root) and is_binary(sha) do
case System.cmd(
"git",
["show", "--numstat", "--format=", sha],
cd: root,
stderr_to_stdout: true
) do
{out, 0} ->
{files, stats} =
out
|> String.split("\n", trim: true)
|> Enum.reduce({[], []}, fn line, {files, stats} ->
cond do
line == "" ->
{files, stats}
String.contains?(line, "\t") ->
case String.split(line, "\t") do
[adds, dels, path] ->
{additions, deletions} = parse_numstat_counts(adds, dels)
{[path | files],
[%{file: path, additions: additions, deletions: deletions} | stats]}
_ ->
{files, stats}
end
true ->
{files, stats}
end
end)
{:ok, {Enum.reverse(Enum.uniq(files)), Enum.reverse(stats)}}
{error, status} ->
{:error, {status, error}}
end
end
@impl GitCli
def status_short(root) when is_binary(root) do
case System.cmd("git", ["status", "--short", "--untracked-files=all"],
cd: root,
stderr_to_stdout: true
) do
{out, 0} -> {:ok, String.split(out, "\n", trim: true)}
{error, status} -> {:error, {status, error}}
end
end
@impl GitCli
def primary_root_at(dir) when is_binary(dir) do
case System.cmd("git", ["rev-parse", "--is-inside-work-tree"],
cd: dir,
stderr_to_stdout: true
) do
{"true\n", 0} ->
case System.cmd("git", ["rev-parse", "--git-common-dir"],
cd: dir,
stderr_to_stdout: true
) do
# --git-common-dir answers with the primary clone's .git directory;
# its parent is the primary root. Git may answer relative to `dir`
# (a bare ".git" when dir IS the primary root), so expand against
# dir to keep the contract absolute.
{out, 0} -> out |> String.trim() |> Path.dirname() |> Path.expand(dir)
_ -> nil
end
_ ->
nil
end
end
@impl GitCli
def merge_base(root, ref_a, ref_b) do
run_git(root, ["merge-base", ref_a, ref_b])
end
@impl GitCli
def diff_stat(root, range) do
run_git(root, ["diff", "--stat", range])
end
@impl GitCli
def log_oneline(root, range) do
run_git(root, ["log", "--oneline", range])
end
@impl GitCli
def verify_commit(root, ref) do
run_git(root, ["rev-parse", "--verify", "--quiet", ref <> "^{commit}"])
end
@impl GitCli
def fetch_ref(root, remote, ref) do
run_git(root, ["fetch", remote, ref])
end
# Shared runner for the review ops: trimmed stdout on success, bare :error
# on any failure (see the facade docs for why these don't return reasons).
@spec run_git(String.t(), [String.t()]) :: {:ok, String.t()} | :error
defp run_git(root, args) do
case System.cmd("git", args, cd: root, stderr_to_stdout: true) do
{out, 0} -> {:ok, String.trim(out)}
_ -> :error
end
end
@spec parse_numstat_counts(String.t(), String.t()) ::
{non_neg_integer(), non_neg_integer()}
defp parse_numstat_counts(adds, dels) do
{parse_numstat_int(adds), parse_numstat_int(dels)}
end
# Numstat reports "-" for binary files; treat anything non-numeric as 0.
@spec parse_numstat_int(String.t()) :: non_neg_integer()
defp parse_numstat_int(value) do
case Integer.parse(value) do
{count, _} when count >= 0 -> count
_ -> 0
end
end
end