Skip to content
This repository was archived by the owner on Oct 8, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions lib/xgit/core/dir_cache.ex
Original file line number Diff line number Diff line change
Expand Up @@ -309,4 +309,71 @@ defmodule Xgit.Core.DirCache do
:gt -> [new_head | combine_entries(existing_entries, new_tail)]
end
end

@typedoc ~S"""
An entry for the `remove` option for `remove_entries/3`.
"""
@type entry_to_remove :: {path :: [byte], stage :: 0..3 | :all}

@doc ~S"""
Returns a dir cache that has some directory entries removed.

## Parameters

`entries_to_remove` is a list of `{path, stage}` tuples identifying tuples to be removed.

* `path` should be a byte list for the path.
* `stage` should be 0..3 or `:all`, meaning any entry that matches the path,
regardless of stage, should be removed.

## Return Value

`{:ok, dir_cache}` where `dir_cache` is the original `dir_cache` with any matching
entries removed.

`{:error, :invalid_dir_cache}` if the original `dir_cache` was invalid.

`{:error, :invalid_entries}` if one or more of the entries is invalid.
"""
@spec remove_entries(dir_cache :: t, entries_to_remove :: [entry_to_remove]) ::
{:ok, t} | {:error, :invalid_dir_cache | :invalid_entries}
def remove_entries(%__MODULE__{entries: existing_entries} = dir_cache, entries_to_remove)
when is_list(entries_to_remove) do
with {:dir_cache_valid?, true} <- {:dir_cache_valid?, valid?(dir_cache)},
{:entries_valid?, true} <-
{:entries_valid?, Enum.all?(entries_to_remove, &valid_remove_entry?/1)} do
updated_entries = remove_matching_entries(existing_entries, Enum.sort(entries_to_remove))
{:ok, %{dir_cache | entry_count: Enum.count(updated_entries), entries: updated_entries}}
else
{:dir_cache_valid?, _} -> {:error, :invalid_dir_cache}
{:entries_valid?, _} -> {:error, :invalid_entries}
end
end

defp valid_remove_entry?({path, :all}) when is_list(path), do: true

defp valid_remove_entry?({path, stage})
when is_list(path) and is_integer(stage) and stage >= 0 and stage <= 3,
do: true

defp valid_remove_entry?(_), do: false

defp remove_matching_entries(sorted_existing_entries, sorted_entries_to_remove)

defp remove_matching_entries([], _sorted_entries_to_remove), do: []
defp remove_matching_entries(sorted_existing_entries, []), do: sorted_existing_entries

defp remove_matching_entries([%__MODULE__.Entry{name: path} | existing_tail], [
{path, :all} | remove_tail
]),
do:
remove_matching_entries(Enum.drop_while(existing_tail, &(&1.name == path)), remove_tail)

defp remove_matching_entries([%__MODULE__.Entry{name: path, stage: stage} | existing_tail], [
{path, stage} | remove_tail
]),
do: remove_matching_entries(existing_tail, remove_tail)

defp remove_matching_entries([existing_head | existing_tail], sorted_entries_to_remove),
do: [existing_head | remove_matching_entries(existing_tail, sorted_entries_to_remove)]
end
102 changes: 102 additions & 0 deletions test/xgit/core/dir_cache_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,106 @@ defmodule Xgit.Core.DirCacheTest do
end
end
end

describe "remove_entries/2" do
test "happy path: removing from an empty list" do
assert {:ok,
%DirCache{
version: 2,
entry_count: 0,
entries: []
}} = DirCache.remove_entries(DirCache.empty(), [{'hello.txt', 0}])
end

test "happy path: removing something that doesn't exist from a non-empty list" do
assert {:ok, @valid} = DirCache.remove_entries(@valid, [{'goodbye.txt', 0}])
end

test "happy path: ignores mismatch on stage" do
assert {:ok, @valid} = DirCache.remove_entries(@valid, [{'hello.txt', 1}])
end

test "happy path: removes only item in list" do
assert {:ok,
%DirCache{
version: 2,
entry_count: 0,
entries: []
}} = DirCache.remove_entries(@valid, [{'hello.txt', 0}])
end

test "happy path: removes all matching entries via stage :all" do
assert {:ok,
%DirCache{
version: 2,
entry_count: 0,
entries: []
}} =
DirCache.remove_entries(
%DirCache{
version: 2,
entry_count: 3,
entries: [
Map.put(@valid_entry, :stage, 0),
Map.put(@valid_entry, :stage, 1),
Map.put(@valid_entry, :stage, 3)
]
},
[{'hello.txt', :all}]
)
end

test "happy path: removes only matching entries via name" do
assert {:ok, @valid} =
DirCache.remove_entries(
%DirCache{
version: 2,
entry_count: 2,
entries: [Map.put(@valid_entry, :name, 'abc.txt'), @valid_entry]
},
[{'abc.txt', 0}]
)
end

test "happy path: sorts list of items to remove" do
assert {:ok, @valid} =
DirCache.remove_entries(
%DirCache{
version: 2,
entry_count: 3,
entries: [
@valid_entry,
Map.put(@valid_entry, :name, 'other.txt'),
Map.put(@valid_entry, :name, 'xgit.txt')
]
},
[{'xgit.txt', 0}, {'other.txt', 0}]
)
end

test "{:error, :invalid_dir_cache}" do
assert {:error, :invalid_dir_cache} =
DirCache.remove_entries(Map.put(@valid, :entry_count, 999), [
{'xgit.txt', 0},
{'other.txt', 0}
])
end

test "{:error, :invalid_entries}" do
assert {:error, :invalid_entries} =
DirCache.remove_entries(DirCache.empty(), [{'hello.txt', 7}])
end

test "FunctionClauseError: not a DirCache" do
assert_raise FunctionClauseError, fn ->
DirCache.remove_entries("trust me, it's a DirCache", [{'hello.txt', 0}])
end
end

test "FunctionClauseError: not a list of entries" do
assert_raise FunctionClauseError, fn ->
DirCache.remove_entries(DirCache.empty(), {'hello.txt', 0})
end
end
end
end