Skip to content
This repository has been archived by the owner on Oct 8, 2020. It is now read-only.

Commit

Permalink
Merge 62fadb9 into 6da8d94
Browse files Browse the repository at this point in the history
  • Loading branch information
scouten committed Jul 13, 2019
2 parents 6da8d94 + 62fadb9 commit 65f674b
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 0 deletions.
95 changes: 95 additions & 0 deletions lib/xgit/repository.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
defmodule Xgit.Repository do
@moduledoc ~S"""
Represents an abstract git repository.
## Design Goals
Xgit intends to allow repositories to be stored in multiple different mechanisms.
While it includes built-in support for local on-disk repositories
(see `Xgit.Repository.OnDisk`), you could envision repositories stored entirely
in memory, or on a remote file system or database.
In Elixir/OTP terms, a repository is both a `GenServer` and a behaviour module.
In git terms, a repository (and thus any module that implements the behaiviours
defined here) is primary a **plumbing** module.
The git **porcelain** implementations are largely implemented in a way that is
intended to be agnostic with regard to storage. They are largely implemented
in separate "command" modules. (See `Xgit.Command.*`, though no such module
exists as of yet.)
## Implementing a Storage Architecture
To define a new mechanism for storing a git repo, start by creating a new module
that `use`s this module and implements the required callbacks. Consider the
information stored in a typical `.git` directory in a local repository. You will
be designing an alternative to that storage mechanism.
"""

use GenServer

require Logger

@type t :: pid

@doc """
Starts a `Repository` process linked to the current process.
You should not invoke this function directly unless you are implementing a
new storage implementation module that implements this behaviour.
## Parameters
`module` is the name of a module that implements the callbacks defined in this module.
`init_arg` is passed to the `init/1` function of `module`.
`options` are passed to `GenServer.start_link/3`.
## Return Value
See `GenServer.start_link/3`.
"""
@spec start_link(module :: module, init_arg :: term, GenServer.options()) ::
GenServer.on_start()
def start_link(module, init_arg, options) when is_atom(module) and is_list(options),
do: GenServer.start_link(__MODULE__, {module, init_arg}, options)

@impl true
def init({mod, mod_init_arg}) do
case mod.init(mod_init_arg) do
{:ok, state} -> {:ok, {mod, state}}
{:stop, reason} -> {:stop, reason}
end
end

@doc ~S"""
Returns `true` if the argument is a PID representing a valid `Repository` process.
"""
@spec valid?(repository :: term) :: boolean
def valid?(repository) when is_pid(repository),
do:
Process.alive?(repository) &&
GenServer.call(repository, :valid_repository?) == :valid_repository

def valid?(_), do: false

@impl true
def handle_call(:valid_repository?, _from, state), do: {:reply, :valid_repository, state}

def handle_call(message, _from, state) do
Logger.warn("Repository received unrecognized call #{inspect(message)}")
{:reply, {:error, :unknown_message}, state}
end

defmacro __using__(opts) do
quote location: :keep, bind_quoted: [opts: opts] do
use GenServer, opts

alias Xgit.Repository

# @behaviour Repository (not yet, but it will be)
end
end
end
29 changes: 29 additions & 0 deletions lib/xgit/repository/on_disk.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,35 @@ defmodule Xgit.Repository.OnDisk do
git so that results may be compared for similar operations.
"""

use Xgit.Repository

@doc ~S"""
Start an on-disk git repository.
## Options
* **TBD**
Any other options are passed through to `GenServer.start_link/3`.
## Return Value
See `GenServer.start_link/3`.
Use the functions in `Xgit.Repository` to interact with this repository process.
"""
@spec start_link(opts :: Keyword.t()) :: GenServer.on_start()
def start_link(opts \\ []), do: Repository.start_link(__MODULE__, opts, opts)

@impl GenServer
def init(opts) when is_list(opts) do
with work_dir when is_binary(work_dir) <- Keyword.get(opts, :work_dir) do
{:ok, %{work_dir: work_dir}}
else
_ -> {:stop, :missing_arguments}
end
end

@doc ~S"""
Creates a new, empty git repository on the local file system.
Expand Down
33 changes: 33 additions & 0 deletions test/xgit/repository/on_disk_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule Xgit.Repository.OnDiskTest do
use Xgit.GitInitTestCase, async: true

alias Xgit.Repository
alias Xgit.Repository.OnDisk

import ExUnit.CaptureLog

describe "start_link/1" do
test "happy path: starts and is valid", %{xgit: xgit} do
assert :ok = OnDisk.create(work_dir: xgit)

assert {:ok, repo} = OnDisk.start_link(work_dir: xgit)
assert is_pid(repo)

assert Repository.valid?(repo)
end

test "handles unknown message", %{xgit: xgit} do
assert :ok = OnDisk.create(work_dir: xgit)
assert {:ok, repo} = OnDisk.start_link(work_dir: xgit)

assert capture_log(fn ->
assert {:error, :unknown_message} = GenServer.call(repo, :random_unknown_message)
end) =~ "Repository received unrecognized call :random_unknown_message"
end

test "error: missing work_dir" do
Process.flag(:trap_exit, true)
assert {:error, :missing_arguments} = OnDisk.start_link([])
end
end
end

0 comments on commit 65f674b

Please sign in to comment.