Presubmit.Git (presubmit v0.2.2)

Copy Markdown View Source

Thin adapter over the git plumbing commands presubmit relies on.

Every function takes the repository path first and returns tagged tuples; run!/2 is the only place a shell command is executed. Keeping the surface this small is what lets the rest of the library be tested against in-memory trees.

Summary

Types

Metadata for a single commit as read by commit_info/2.

One record from git diff-tree --raw.

Functions

Reads many blobs of tree in one process: git archive of the given paths, extracted in memory. Every path must exist in the tree (git refuses a pathspec that matches nothing); blobs excluded by export-ignore are simply absent from the result.

Reads commit metadata and the raw message body for rev.

Reads a git configuration value as git itself would resolve it, or nil.

Diffs two trees with rename detection, returning one entry per changed path.

Whether anything on disk differs from HEAD: staged or unstaged edits, or untracked files that are not ignored.

Returns the object id of the empty tree for this repository's hash algorithm.

Lists every blob path in tree.

Returns true when rev names an object that exists locally.

Reads the blob at path in tree.

Resolves rev to a full object id.

Runs git with args inside repo.

Runs git with args inside repo, raising Presubmit.GitError on a non-zero exit.

Returns true when sha is a shallow-clone boundary: a commit whose real parents were cut off by --depth. Git reports such commits as parentless, so this is the only way to tell them apart from a genuine root commit.

Writes the current index to a tree object and returns its id.

Writes the working directory to a tree object and returns its id, without touching the repository's own index.

Types

commit_info()

@type commit_info() :: %{
  sha: oid(),
  tree: oid(),
  parents: [oid()],
  author: %{name: String.t(), email: String.t(), date: DateTime.t()},
  committer: %{name: String.t(), email: String.t(), date: DateTime.t()},
  body: String.t()
}

Metadata for a single commit as read by commit_info/2.

oid()

@type oid() :: String.t()

raw_entry()

@type raw_entry() :: %{
  status: :added | :modified | :deleted | :renamed | :copied | :type_changed,
  old_path: String.t() | nil,
  new_path: String.t() | nil,
  old_mode: String.t(),
  new_mode: String.t(),
  score: non_neg_integer() | nil
}

One record from git diff-tree --raw.

score is the similarity percentage for renames and copies, nil otherwise.

repo()

@type repo() :: Path.t()

Functions

archive(repo, tree, paths)

@spec archive(repo(), oid(), [String.t()]) ::
  {:ok, %{optional(String.t()) => binary()}} | {:error, Presubmit.GitError.t()}

Reads many blobs of tree in one process: git archive of the given paths, extracted in memory. Every path must exist in the tree (git refuses a pathspec that matches nothing); blobs excluded by export-ignore are simply absent from the result.

commit_info(repo, rev)

@spec commit_info(repo(), String.t()) ::
  {:ok, commit_info()} | {:error, Presubmit.GitError.t()}

Reads commit metadata and the raw message body for rev.

config(repo, key)

@spec config(repo(), String.t()) :: String.t() | nil

Reads a git configuration value as git itself would resolve it, or nil.

diff_trees(repo, before, after_tree)

@spec diff_trees(repo(), oid(), oid()) ::
  {:ok, [raw_entry()]} | {:error, Presubmit.GitError.t()}

Diffs two trees with rename detection, returning one entry per changed path.

dirty?(repo)

@spec dirty?(repo()) :: boolean()

Whether anything on disk differs from HEAD: staged or unstaged edits, or untracked files that are not ignored.

empty_tree(repo)

@spec empty_tree(repo()) :: oid()

Returns the object id of the empty tree for this repository's hash algorithm.

ls_tree(repo, tree)

@spec ls_tree(repo(), oid()) :: {:ok, [String.t()]} | {:error, Presubmit.GitError.t()}

Lists every blob path in tree.

object_present?(repo, rev)

@spec object_present?(repo(), String.t()) :: boolean()

Returns true when rev names an object that exists locally.

read_blob(repo, tree, path)

@spec read_blob(repo(), oid(), String.t()) :: {:ok, binary()} | :error

Reads the blob at path in tree.

rev_parse(repo, rev)

@spec rev_parse(repo(), String.t()) :: {:ok, oid()} | {:error, Presubmit.GitError.t()}

Resolves rev to a full object id.

run(repo, args, env \\ [])

@spec run(repo(), [String.t()], [{String.t(), String.t()}]) ::
  {:ok, binary()} | {:error, Presubmit.GitError.t()}

Runs git with args inside repo.

run!(repo, args)

@spec run!(repo(), [String.t()]) :: binary()

Runs git with args inside repo, raising Presubmit.GitError on a non-zero exit.

The user's git configuration is honoured (so safe.directory, credential and transport settings apply); every plumbing call passes the flags that make its output independent of configuration (-z, --no-color, explicit --format, --no-show-signature).

shallow_boundary?(repo, sha)

@spec shallow_boundary?(repo(), oid()) :: boolean()

Returns true when sha is a shallow-clone boundary: a commit whose real parents were cut off by --depth. Git reports such commits as parentless, so this is the only way to tell them apart from a genuine root commit.

write_index_tree(repo)

@spec write_index_tree(repo()) :: {:ok, oid()} | {:error, Presubmit.GitError.t()}

Writes the current index to a tree object and returns its id.

This is how the staged change set gets a tree that the rest of the pipeline can treat exactly like a commit's tree. Writing a tree object never touches refs or the working directory.

write_worktree_tree(repo)

@spec write_worktree_tree(repo()) :: {:ok, oid()} | {:error, Presubmit.GitError.t()}

Writes the working directory to a tree object and returns its id, without touching the repository's own index.

A temporary index is seeded from HEAD (or left empty on an unborn branch), everything on disk is added to it honouring .gitignore, and the result is written as a tree. Only loose objects are created; git gc reclaims them.