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

Commit

Permalink
Merge bbd05d1 into 4b7f354
Browse files Browse the repository at this point in the history
  • Loading branch information
scouten committed Jul 12, 2019
2 parents 4b7f354 + bbd05d1 commit df366da
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 3 deletions.
35 changes: 35 additions & 0 deletions lib/xgit/repository/on_disk.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule Xgit.Repository.OnDisk do
@moduledoc ~S"""
Implementation of `Xgit.Repository` that stores content on the local file system.
**IMPORTANT NOTE:** This is intended as a reference implementation largely
for testing purposes and may not necessarily handle all of the edge cases that
the traditional `git` command-line interface will handle.
That said, it does intentionally use the same `.git` folder format as command-line
git so that results may be compared for similar operations.
"""

@doc ~S"""
Creates a new, empty git repository on the local file system.
Analogous to [`git init`](https://git-scm.com/docs/git-init).
## Options
* `:work_dir` (required): Top-level working directory. A `.git` directory is
created inside this directory.
## Return Value
`:ok`
## Errors
Will raise `ArgumentError` if options are incomplete or incorrect.
Will raise `File.Error` or similar if unable to create the directory.
"""
# @spec init([work_dir: String.t]) :: :ok
defdelegate init(opts), to: Xgit.Repository.OnDisk.Init
end
126 changes: 126 additions & 0 deletions lib/xgit/repository/on_disk/init.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
defmodule Xgit.Repository.OnDisk.Init do
@moduledoc false
# Implements Xgit.Repository.OnDisk.init/1.

def init(opts) when is_list(opts) do
work_dir = Keyword.get(opts, :work_dir)

unless is_binary(work_dir) do
raise ArgumentError, "Xgit.Repository.OnDisk.init/1: :work_dir must be a file path"
end

work_dir
|> assert_not_exists!()
|> create_empty_repo!()

:ok
end

defp assert_not_exists!(path) do
if File.exists?(path) do
raise ArgumentError,
"Xgit.Repository.OnDisk.init/1: :work_dir must be a directory that doesn't already exist"
else
path
end
end

defp create_empty_repo!(path) do
File.mkdir_p!(path)

path
|> Path.join(".git")
|> create_git_dir!()
end

defp create_git_dir!(git_dir) do
create_branches_dir!(git_dir)
create_config!(git_dir)
create_description!(git_dir)
create_head!(git_dir)
create_hooks_dir!(git_dir)
create_info_dir!(git_dir)
create_objects_dir!(git_dir)
create_refs_dir!(git_dir)
end

defp create_branches_dir!(git_dir) do
git_dir
|> Path.join("branches")
|> File.mkdir_p!()
end

defp create_config!(git_dir) do
git_dir
|> Path.join("config")
|> File.write!(
"[core]\n" <>
"\trepositoryformatversion = 0\n" <>
"\tfilemode = true\n" <>
"\tbare = false\n" <>
"\tlogallrefupdates = true\n" <>
"\tignorecase = true\n" <>
"\tprecomposeunicode = true\n"
)
end

defp create_description!(git_dir) do
git_dir
|> Path.join("description")
|> File.write!("Unnamed repository; edit this file 'description' to name the repository.\n")
end

defp create_head!(git_dir) do
git_dir
|> Path.join("HEAD")
|> File.write!("ref: refs/heads/master\n")
end

defp create_hooks_dir!(git_dir) do
git_dir
|> Path.join("hooks")
|> File.mkdir_p!()

# NOTE: Intentionally not including the sample files.
end

defp create_info_dir!(git_dir) do
info_dir = Path.join(git_dir, "info")
File.mkdir_p!(info_dir)

info_dir
|> Path.join("exclude")
|> File.write!(~S"""
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store
""")
end

defp create_objects_dir!(git_dir) do
git_dir
|> Path.join("objects/info")
|> File.mkdir_p!()

git_dir
|> Path.join("objects/pack")
|> File.mkdir_p!()
end

defp create_refs_dir!(git_dir) do
refs_dir = Path.join(git_dir, "refs")
File.mkdir_p!(refs_dir)

refs_dir
|> Path.join("heads")
|> File.mkdir_p!()

refs_dir
|> Path.join("tags")
|> File.mkdir_p!()
end
end
8 changes: 5 additions & 3 deletions test/support/folder_diff.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ defmodule FolderDiff do
import ExUnit.Assertions

def assert_folders_are_equal(folder1, folder2) do
files1 = folder1 |> File.ls!() |> Enum.sort()
files2 = folder2 |> File.ls!() |> Enum.sort()
unless String.ends_with?(folder1, ".git/hooks") and String.ends_with?(folder2, ".git/hooks") do
files1 = folder1 |> File.ls!() |> Enum.sort()
files2 = folder2 |> File.ls!() |> Enum.sort()

assert_folders_are_equal(folder1, folder2, files1, files2)
assert_folders_are_equal(folder1, folder2, files1, files2)
end
end

defp assert_folders_are_equal(folder1, folder2, [file1 | files1], [file2 | files2]) do
Expand Down
41 changes: 41 additions & 0 deletions test/xgit/repository/on_disk/init_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
defmodule Xgit.Repository.OnDisk.InitTest do
use ExUnit.Case

alias Xgit.Repository.OnDisk

import FolderDiff

setup do
Temp.track!()
tmp = Temp.mkdir!()
ref = Path.join(tmp, "ref")
xgit = Path.join(tmp, "xgit")

{:ok, ref: ref, xgit: xgit}
end

describe "init/1" do
test "happy path matches command-line git", %{ref: ref, xgit: xgit} do
File.mkdir_p!(ref)
{_, 0} = System.cmd("git", ["init", "."], cd: ref)

assert :ok = OnDisk.init(work_dir: xgit)

assert_folders_are_equal(ref, xgit)
end

test "error: no work_dir" do
assert_raise ArgumentError, fn ->
OnDisk.init([])
end
end

test "error: work dir exists already", %{xgit: xgit} do
File.mkdir_p!(xgit)

assert_raise ArgumentError, fn ->
OnDisk.init(work_dir: xgit)
end
end
end
end

0 comments on commit df366da

Please sign in to comment.