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

Commit

Permalink
Implement basic case of Xgit.Repository.OnDisk.init/1. (#4)
Browse files Browse the repository at this point in the history
* Implement basic case of Xgit.Repository.OnDisk.init/1.

* Build server and my dev machine use different git versions.

Looks like this is only different in the sample files and config file. Force those to a standard.

* Looks like .git/info/exclude wobbles between versions, too.
  • Loading branch information
scouten committed Jul 12, 2019
1 parent 4b7f354 commit 6ca963a
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 0 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
124 changes: 124 additions & 0 deletions lib/xgit/repository/on_disk/init.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
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!(~s"""
[core]
\trepositoryformatversion = 0
\tfilemode = true
\tbare = false
\tlogallrefupdates = true
""")
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
73 changes: 73 additions & 0 deletions test/support/git_init_test_case.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
defmodule Xgit.GitInitTestCase do
@moduledoc false
# Creates a temporary directory containing an
# initialized, but otherwise empty git repo and
# adjacent space for an Xgit-created git repo.

use ExUnit.CaseTemplate

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

git_init_and_standardize(ref)

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

defp git_init_and_standardize(git_dir) do
git_dir
|> git_init()
|> remove_sample_hooks()
|> rewrite_config()
|> rewrite_info_exclude()
end

defp git_init(git_dir) do
{_, 0} = System.cmd("git", ["init", git_dir])
git_dir
end

defp remove_sample_hooks(git_dir) do
hooks_dir = Path.join(git_dir, ".git/hooks")

hooks_dir
|> File.ls!()
|> Enum.filter(&String.ends_with?(&1, ".sample"))
|> Enum.each(&File.rm!(Path.join(hooks_dir, &1)))

git_dir
end

defp rewrite_config(git_dir) do
git_dir
|> Path.join(".git/config")
|> File.write!(~s"""
[core]
\trepositoryformatversion = 0
\tfilemode = true
\tbare = false
\tlogallrefupdates = true
""")

git_dir
end

defp rewrite_info_exclude(git_dir) do
git_dir
|> Path.join(".git/info/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
""")

git_dir
end
end
28 changes: 28 additions & 0 deletions test/xgit/repository/on_disk/init_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule Xgit.Repository.OnDisk.InitTest do
use Xgit.GitInitTestCase, async: true

alias Xgit.Repository.OnDisk

import FolderDiff

describe "init/1" do
test "happy path matches command-line git", %{ref: ref, xgit: xgit} do
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 6ca963a

Please sign in to comment.