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

Commit

Permalink
Implement Xgit.Repository.has_all_object_ids?/2. (#157)
Browse files Browse the repository at this point in the history
  • Loading branch information
scouten committed Sep 6, 2019
1 parent c1e1160 commit 9fe3dcf
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 0 deletions.
31 changes: 31 additions & 0 deletions lib/xgit/repository.ex
Expand Up @@ -111,6 +111,29 @@ defmodule Xgit.Repository do
when is_pid(repository) and is_pid(working_tree),
do: GenServer.call(repository, {:set_default_working_tree, working_tree})

@doc ~S"""
Returns `true` if all objects in the list are present in the object dictionary.
This limit is not enforced, but it's recommended to query for no more than ~100 object
IDs at a time.
"""
@spec has_all_object_ids?(repository :: t, object_ids :: [ObjectId.t()]) :: boolean
def has_all_object_ids?(repository, object_ids) when is_pid(repository) and is_list(object_ids),
do: GenServer.call(repository, {:has_all_object_ids?, object_ids})

@doc ~S"""
Checks for presence of multiple object Ids.
Called when `has_all_object_ids?/2` is called.
## Return Value
Should return `{:ok, has_all_object_ids?, state}` where `has_all_object_ids?` is `true`
if all object IDs can be found in the object dictionary; `false` otherwise.
"""
@callback handle_has_all_object_ids?(state :: any, object_ids :: [ObjectId.t()]) ::
{:ok, has_all_object_ids? :: boolean, state :: any}

@typedoc ~S"""
Error codes that can be returned by `get_object/2`.
"""
Expand Down Expand Up @@ -205,6 +228,9 @@ defmodule Xgit.Repository do
def handle_call({:set_default_working_tree, _working_tree}, _from, state),
do: {:reply, :error, state}

def handle_call({:has_all_object_ids?, object_ids}, _from, state),
do: delegate_boolean_call_to(state, :handle_has_all_object_ids?, [object_ids])

def handle_call({:get_object, object_id}, _from, state),
do: delegate_call_to(state, :handle_get_object, [object_id])

Expand All @@ -224,6 +250,11 @@ defmodule Xgit.Repository do
end
end

defp delegate_boolean_call_to(state, function, args) do
{:reply, {:ok, response}, state} = delegate_call_to(state, function, args)
cover {:reply, response, state}
end

defmacro __using__(opts) do
quote location: :keep, bind_quoted: [opts: opts] do
use GenServer, opts
Expand Down
6 changes: 6 additions & 0 deletions lib/xgit/repository/in_memory.ex
Expand Up @@ -30,6 +30,12 @@ defmodule Xgit.Repository.InMemory do
@impl true
def init(opts) when is_list(opts), do: cover({:ok, %{loose_objects: %{}}})

@impl true
def handle_has_all_object_ids?(%{loose_objects: objects} = state, object_ids) do
has_all_objects? = Enum.all?(object_ids, fn object_id -> Map.has_key?(objects, object_id) end)
cover {:ok, has_all_objects?, state}
end

@impl true
def handle_get_object(%{loose_objects: objects} = state, object_id) do
# Currently only checks for loose objects.
Expand Down
4 changes: 4 additions & 0 deletions lib/xgit/repository/on_disk.ex
Expand Up @@ -86,6 +86,10 @@ defmodule Xgit.Repository.OnDisk do
@spec create(work_dir :: Path.t()) :: :ok | {:error, :work_dir_must_not_exist}
defdelegate create(work_dir), to: Xgit.Repository.OnDisk.Create

@impl true
defdelegate handle_has_all_object_ids?(state, object_ids),
to: Xgit.Repository.OnDisk.HasAllObjectIds

@impl true
defdelegate handle_get_object(state, object_id),
to: Xgit.Repository.OnDisk.GetObject
Expand Down
30 changes: 30 additions & 0 deletions lib/xgit/repository/on_disk/has_all_object_ids.ex
@@ -0,0 +1,30 @@
defmodule Xgit.Repository.OnDisk.HasAllObjectIds do
@moduledoc false
# Implements Xgit.Repository.OnDisk.handle_has_all_objects?/2.

import Xgit.Util.ForceCoverage

alias Xgit.Core.ObjectId

@spec handle_has_all_object_ids?(state :: any, object_ids :: [ObjectId.t()]) ::
{:ok, has_all_object_ids? :: boolean, state :: any}
| {:error, reason :: any, state :: any}
def handle_has_all_object_ids?(%{git_dir: git_dir} = state, object_ids) do
has_all_object_ids? =
Enum.all?(object_ids, fn object_id -> has_object_id?(git_dir, object_id) end)

cover {:ok, has_all_object_ids?, state}
end

defp has_object_id?(git_dir, object_id) do
loose_object_path =
Path.join([
git_dir,
"objects",
String.slice(object_id, 0, 2),
String.slice(object_id, 2, 38)
])

File.regular?(loose_object_path)
end
end
60 changes: 60 additions & 0 deletions test/xgit/repository/in_memory/has_all_object_ids_test.exs
@@ -0,0 +1,60 @@
defmodule Xgit.Repository.InMemory.HasAllObjectIdsTest do
use ExUnit.Case, async: true

alias Xgit.Core.Object
alias Xgit.Repository
alias Xgit.Repository.InMemory

describe "has_all_object_ids?/2" do
@test_content 'test content\n'
@test_content_id "d670460b4b4aece5915caf5c68d12f560a9fe3e4"

setup do
assert {:ok, repo} = InMemory.start_link()

object = %Object{type: :blob, content: @test_content, size: 13, id: @test_content_id}
assert :ok = Repository.put_loose_object(repo, object)

# Yes, the hash is wrong, but we'll ignore that for now.
object = %Object{
type: :blob,
content: @test_content,
size: 15,
id: "c1e116090ad56f172370351ab3f773eb0f1fe89e"
}

assert :ok = Repository.put_loose_object(repo, object)

{:ok, repo: repo}
end

test "happy path: zero object IDs", %{repo: repo} do
assert Repository.has_all_object_ids?(repo, [])
end

test "happy path: one object ID", %{repo: repo} do
assert Repository.has_all_object_ids?(repo, [@test_content_id])
end

test "happy path: two object IDs", %{repo: repo} do
assert Repository.has_all_object_ids?(repo, [
@test_content_id,
"c1e116090ad56f172370351ab3f773eb0f1fe89e"
])
end

test "happy path: partial match", %{repo: repo} do
refute Repository.has_all_object_ids?(repo, [
@test_content_id,
"b9e3a9e3ea7dde01d652f899a783b75a1518564c"
])
end

test "happy path: no match", %{repo: repo} do
refute Repository.has_all_object_ids?(repo, [
@test_content_id,
"6ee878a55ed36e2cda2c68452d2336ce3bd692d1"
])
end
end
end
64 changes: 64 additions & 0 deletions test/xgit/repository/on_disk/has_all_object_ids_test.exs
@@ -0,0 +1,64 @@
defmodule Xgit.Repository.OnDisk.HasAllObjectIdsTest do
use Xgit.GitInitTestCase, async: true

alias Xgit.Core.Object
alias Xgit.Repository
alias Xgit.Repository.OnDisk

describe "has_all_object_ids?/2" do
@test_content 'test content\n'
@test_content_id "d670460b4b4aece5915caf5c68d12f560a9fe3e4"

setup %{xgit: xgit} do
assert :ok = OnDisk.create(xgit)
assert {:ok, repo} = OnDisk.start_link(work_dir: xgit)

object = %Object{type: :blob, content: @test_content, size: 13, id: @test_content_id}
assert :ok = Repository.put_loose_object(repo, object)

# Yes, the hash is wrong, but we'll ignore that for now.
object = %Object{
type: :blob,
content: @test_content,
size: 15,
id: "c1e116090ad56f172370351ab3f773eb0f1fe89e"
}

assert :ok = Repository.put_loose_object(repo, object)

{:ok, repo: repo}
end

test "happy path: zero object IDs", %{repo: repo} do
assert true == Repository.has_all_object_ids?(repo, [])
end

test "happy path: one object ID", %{repo: repo} do
assert true == Repository.has_all_object_ids?(repo, [@test_content_id])
end

test "happy path: two object IDs", %{repo: repo} do
assert true ==
Repository.has_all_object_ids?(repo, [
@test_content_id,
"c1e116090ad56f172370351ab3f773eb0f1fe89e"
])
end

test "happy path: partial match", %{repo: repo} do
assert false ==
Repository.has_all_object_ids?(repo, [
@test_content_id,
"b9e3a9e3ea7dde01d652f899a783b75a1518564c"
])
end

test "happy path: no match", %{repo: repo} do
assert false ==
Repository.has_all_object_ids?(repo, [
@test_content_id,
"6ee878a55ed36e2cda2c68452d2336ce3bd692d1"
])
end
end
end

0 comments on commit 9fe3dcf

Please sign in to comment.