# `Exgit.Transport.HTTP`
[🔗](https://github.com/ivarvong/exgit/blob/v0.1.0/lib/exgit/transport/http.ex#L1)

`Exgit.Transport` implementation for git smart-HTTP v2.

Speaks git protocol v2 over HTTPS using `Req` as the HTTP client.
Implements `capabilities/1`, `ls_refs/2`, `fetch/3`, and
`push/4`; supports `want-ref`, `filter`, `sideband-all`, and
`symrefs` extensions.

## Credentials

The `:auth` field accepts either a bare auth tuple (auto-wrapped
in `%Exgit.Credentials{}` with host-binding) or an explicit
`%Exgit.Credentials{}` struct. Host-bound credentials refuse to
emit auth headers when the URL host doesn't match the bound
pattern, defending against cross-origin leaks through redirects
or user-supplied URLs.

## TLS

`verify_tls: true` (default) uses `:public_key.cacerts_get/0` and
enables hostname verification via `:ssl.pkix_verify_hostname/3`.

## Redirects

Disabled by default to prevent any chance of credential leaks
through an unexpected redirect target. Opt-in via
`redirect: :same_origin` or `redirect: :follow`; host-binding on
`:auth` remains in force either way.

# `auth`

```elixir
@type auth() :: auth_value() | Exgit.Credentials.t()
```

# `auth_value`

```elixir
@type auth_value() ::
  nil
  | {:basic, String.t(), String.t()}
  | {:bearer, String.t()}
  | {:header, String.t(), String.t()}
  | {:callback, (String.t() -&gt; [{String.t(), String.t()}])}
```

# `t`

```elixir
@type t() :: %Exgit.Transport.HTTP{
  auth: auth(),
  capabilities_cache: term(),
  connect_options: term(),
  connect_timeout: term(),
  max_refs: term(),
  receive_timeout: term(),
  redirect: term(),
  url: String.t(),
  user_agent: String.t(),
  verify_tls: term()
}
```

# `auth_headers_for`

```elixir
@spec auth_headers_for(t(), String.t()) :: [{String.t(), String.t()}]
```

Compute the auth headers for a specific request URL. Exposed for
testing; production call-sites use `request_opts/5` or `do_request/5`.

This is the enforcement point for credential host-binding: a
`%Exgit.Credentials{}` with a non-matching host pattern returns `[]`
regardless of what the caller thought they attached.

# `capabilities`

```elixir
@spec capabilities(t()) :: {:ok, map()} | {:error, term()}
```

Discover the server's protocol-v2 capability advertisement.

Performs the `info/refs?service=git-upload-pack` discovery GET and
parses the advertisement into a map keyed by capability name
(string keys; well-known entries like `:version` get structured
values). Returns `{:error, :server_does_not_support_v2}` for
protocol-v1-only servers, `{:error, {:malformed_response, reason}}`
when the body is not valid pkt-line framing.

A struct carrying a memoized `capabilities_cache` (see
`capabilities_cached/1`) returns the cached result without HTTP.

# `capabilities_cached`

```elixir
@spec capabilities_cached(t()) :: {term(), t()}
```

Return `{capabilities, updated_transport}` so the caller can thread
the transport forward and avoid re-discovering on every call.

The default fetch/ls-refs paths still accept a non-cached transport
(to preserve backward compatibility with struct-sharing callers);
`capabilities_cached/1` is the opt-in path for workflows that make
many small fetches against the same transport.

# `fetch`

```elixir
@spec fetch(t(), [binary()], keyword()) :: {:ok, binary(), map()} | {:error, term()}
```

Fetch a pack for the given `wants` (20-byte SHAs) via the
protocol-v2 `fetch` command.

Returns `{:ok, pack_bytes, summary}`. With `:object_store` the
pack is stream-parsed straight into the store — `pack_bytes` is
`<<>>` and `summary` carries `:objects` and the updated `:store`.
Without it, `pack_bytes` is the raw pack for the caller to parse.
Sideband channel-3 server messages surface as
`{:error, {:server_error, msg}}`.

## Options

  * `:haves` — SHAs the client already has, for negotiation.
  * `:depth` — shallow-clone depth (`deepen`).
  * `:filter` — partial-clone filter spec (e.g. `"blob:none"`).
  * `:object_store` — an `Exgit.ObjectStore` to stream objects
    into as they arrive (bounded memory on multi-GB packs).
  * `:sideband`, `:thin_pack`, `:ofs_delta` — explicit feature
    overrides; by default they're negotiated from the server's
    advertised capabilities.

# `ls_refs`

```elixir
@spec ls_refs(
  t(),
  keyword()
) ::
  {:ok, [Exgit.Transport.ref_entry()], Exgit.Transport.ls_refs_meta()}
  | {:error, term()}
```

List the remote's refs via the protocol-v2 `ls-refs` command.

Returns `{:ok, refs, meta}` where `refs` is a list of
`{ref_name, sha}` 2-tuples and `meta` carries protocol-v2
side-channel data (`:head` symref target, `:peeled` tag targets) —
see `t:Exgit.Transport.ls_refs_meta/0`. Invalid ref names from the
server are dropped (with `[:exgit, :security, :ref_rejected]`
telemetry); more than `max_refs` refs aborts with
`{:error, {:too_many_refs, cap}}`.

## Options

  * `:prefix` — ref-prefix filter or list of filters
    (e.g. `"refs/heads/"`); default `[]` (all refs).
  * `:symrefs` — ask for symref targets, revealing where HEAD
    points (default `true`).
  * `:peeled` — ask for `peeled:<sha>` attributes on annotated
    tags (default `true`).

# `new`

```elixir
@spec new(
  String.t(),
  keyword()
) :: t()
```

Build a transport for the git smart-HTTP remote at `url`.

## Options

  * `:auth` — credentials; a bare auth tuple (auto-wrapped in a
    host-bound `%Exgit.Credentials{}`) or an explicit
    `%Exgit.Credentials{}` struct. See the module doc.
  * `:user_agent` — the `user-agent` header value.
  * `:connect_timeout` — connect timeout in milliseconds
    (default 10_000).
  * `:receive_timeout` — response-body timeout in milliseconds
    (default 300_000); `:infinity` disables.
  * `:verify_tls` — TLS peer verification (default `true`).
  * `:connect_options` — extra transport options merged into the
    library's TLS defaults (custom CA bundles, mTLS, SNI).
  * `:redirect` — redirect policy: `false` (default),
    `:same_origin`, or `:follow`.
  * `:max_refs` — cap on refs accepted from a single ls-refs
    response (default 1000000). `ls_refs/2` aborts with
    `{:error, {:too_many_refs, cap}}` when a server exceeds it —
    a backstop against a hostile server streaming unbounded refs
    into client memory.

# `push`

```elixir
@spec push(t(), [Exgit.Transport.ref_update()], binary(), keyword()) ::
  {:ok, %{ref_results: [{String.t(), :ok | :error}]}} | {:error, term()}
```

Push ref `updates` and the accompanying `pack_bytes` via
`git-receive-pack`.

Each update is a `{ref, old_sha, new_sha}` 3-tuple (20-byte SHAs;
`nil` means the all-zero SHA, i.e. create/delete). Returns
`{:ok, %{ref_results: [{ref, :ok | :error}]}}` parsed from the
server's report-status response, or
`{:error, {:malformed_response, reason}}` when the report is not
valid pkt-line framing.

# `request_opts`

```elixir
@spec request_opts(
  t(),
  atom(),
  String.t(),
  [{String.t(), String.t()}],
  binary() | nil
) :: keyword()
```

Build the keyword list we'd pass to `Req.request/1` for the given
request. Exposed publicly for test introspection; production code
goes through `do_request/5`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
