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

Commit

Permalink
Implement Xgit.Core.Object.to_object/1. (#184)
Browse files Browse the repository at this point in the history
* Implement Xgit.Core.Object.to_object/1.

* Silently de-duplicate parents list.

* Raise ArgumentError if commit struct is invalid.
  • Loading branch information
scouten committed Oct 1, 2019
1 parent 0340677 commit 13122c5
Show file tree
Hide file tree
Showing 2 changed files with 355 additions and 0 deletions.
45 changes: 45 additions & 0 deletions lib/xgit/core/commit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule Xgit.Core.Commit do
@moduledoc ~S"""
Represents a git `commit` object in memory.
"""
alias Xgit.Core.Object
alias Xgit.Core.ObjectId
alias Xgit.Core.PersonIdent

Expand Down Expand Up @@ -51,4 +52,48 @@ defmodule Xgit.Core.Commit do
end

def valid?(_), do: cover(false)

@doc ~S"""
Renders this commit structure into a corresponding `Xgit.Core.Object`.
If duplicate parents are detected, they will be silently de-duplicated.
If the commit structure is not valid, will raise `ArgumentError`.
"""
@spec to_object(commit :: t) :: Object.t()
def to_object(commit)

def to_object(
%__MODULE__{
tree: tree,
parents: parents,
author: %PersonIdent{} = author,
committer: %PersonIdent{} = committer,
message: message
} = commit
) do
unless valid?(commit) do
raise ArgumentError, "Xgit.Core.Commit.to_object/1: commit is not valid"
end

rendered_parents =
parents
|> Enum.uniq()
|> Enum.flat_map(&'parent #{&1}\n')

rendered_commit =
'tree #{tree}\n' ++
rendered_parents ++
'author #{PersonIdent.to_external_string(author)}\n' ++
'committer #{PersonIdent.to_external_string(committer)}\n' ++
'\n' ++
message

%Object{
type: :commit,
content: rendered_commit,
size: Enum.count(rendered_commit),
id: ObjectId.calculate_id(rendered_commit, :commit)
}
end
end
310 changes: 310 additions & 0 deletions test/xgit/core/commit_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@ defmodule Xgit.Core.CommitTest do
use ExUnit.Case, async: true

alias Xgit.Core.Commit
alias Xgit.Core.Object
alias Xgit.Core.PersonIdent
alias Xgit.GitInitTestCase
alias Xgit.Repository
alias Xgit.Repository.OnDisk

import FolderDiff

@valid_pi %PersonIdent{
name: "A. U. Thor",
email: "author@example.com",
when: 1_142_878_501_000,
tz_offset: 150
}

@invalid_pi %PersonIdent{
name: :bogus,
Expand Down Expand Up @@ -199,4 +212,301 @@ defmodule Xgit.Core.CommitTest do
|> PersonIdent.from_byte_list()
end
end

describe "to_object/1" do
test "empty tree" do
assert_same_output(
fn _git_dir -> [] end,
fn tree_id, [] ->
%Commit{
tree: tree_id,
author: @valid_pi,
committer: @valid_pi,
message: 'x\n'
}
end
)
end

test "tree with two entries" do
assert_same_output(
fn git_dir ->
{_output, 0} =
System.cmd(
"git",
[
"update-index",
"--add",
"--cacheinfo",
"100644",
"7919e8900c3af541535472aebd56d44222b7b3a3",
"hello.txt"
],
cd: git_dir
)

{_output, 0} =
System.cmd(
"git",
[
"update-index",
"--add",
"--cacheinfo",
"100755",
"4a43a489f107e7ece679950f53567c648038449a",
"xyzzy.sh"
],
cd: git_dir
)

[]
end,
fn tree_id, [] ->
%Commit{
tree: tree_id,
author: @valid_pi,
committer: @valid_pi,
message: 'x\n'
}
end
)
end

test "tree with two entries and one parent" do
assert_same_output(
fn git_dir ->
{empty_tree_id_str, 0} =
System.cmd(
"git",
[
"write-tree"
],
cd: git_dir
)

empty_tree_id = String.trim(empty_tree_id_str)

env = [
{"GIT_AUTHOR_DATE", "1142878449 +0230"},
{"GIT_COMMITTER_DATE", "1142878449 +0230"},
{"GIT_AUTHOR_EMAIL", "author@example.com"},
{"GIT_COMMITTER_EMAIL", "author@example.com"},
{"GIT_AUTHOR_NAME", "A. U. Thor"},
{"GIT_COMMITTER_NAME", "A. U. Thor"}
]

{empty_commit_id_str, 0} =
System.cmd(
"git",
[
"commit-tree",
"-m",
"empty",
empty_tree_id
],
cd: git_dir,
env: env
)

empty_commit_id = String.trim(empty_commit_id_str)

{_output, 0} =
System.cmd(
"git",
[
"update-index",
"--add",
"--cacheinfo",
"100644",
"7919e8900c3af541535472aebd56d44222b7b3a3",
"hello.txt"
],
cd: git_dir
)

{_output, 0} =
System.cmd(
"git",
[
"update-index",
"--add",
"--cacheinfo",
"100755",
"4a43a489f107e7ece679950f53567c648038449a",
"xyzzy.sh"
],
cd: git_dir
)

[empty_commit_id]
end,
fn tree_id, parents ->
%Commit{
tree: tree_id,
parents: parents,
author: @valid_pi,
committer: @valid_pi,
message: 'x\n'
}
end
)
end

test "deduplicates and warns on duplicate parent" do
assert_same_output(
fn git_dir ->
{empty_tree_id_str, 0} =
System.cmd(
"git",
[
"write-tree"
],
cd: git_dir
)

empty_tree_id = String.trim(empty_tree_id_str)

env = [
{"GIT_AUTHOR_DATE", "1142878449 +0230"},
{"GIT_COMMITTER_DATE", "1142878449 +0230"},
{"GIT_AUTHOR_EMAIL", "author@example.com"},
{"GIT_COMMITTER_EMAIL", "author@example.com"},
{"GIT_AUTHOR_NAME", "A. U. Thor"},
{"GIT_COMMITTER_NAME", "A. U. Thor"}
]

{empty_commit_id_str, 0} =
System.cmd(
"git",
[
"commit-tree",
"-m",
"empty",
empty_tree_id
],
cd: git_dir,
env: env
)

empty_commit_id = String.trim(empty_commit_id_str)

{_output, 0} =
System.cmd(
"git",
[
"update-index",
"--add",
"--cacheinfo",
"100644",
"7919e8900c3af541535472aebd56d44222b7b3a3",
"hello.txt"
],
cd: git_dir
)

{_output, 0} =
System.cmd(
"git",
[
"update-index",
"--add",
"--cacheinfo",
"100755",
"4a43a489f107e7ece679950f53567c648038449a",
"xyzzy.sh"
],
cd: git_dir
)

[empty_commit_id, empty_commit_id]
end,
fn tree_id, parents ->
%Commit{
tree: tree_id,
parents: parents,
author: @valid_pi,
committer: @valid_pi,
message: 'x\n'
}
end
)
end

test "raises ArgumentError if commit is invalid" do
assert_raise ArgumentError, "Xgit.Core.Commit.to_object/1: commit is not valid", fn ->
Commit.to_object(%Commit{
tree: "be9bfa841874ccc9f2ef7c48d0c76226f89b7189",
author: @invalid_pi,
committer: pi("<> 0 +0000"),
message: 'x'
})
end
end

defp assert_same_output(write_tree_fn, xgit_fn, opts \\ []) do
author_date = Keyword.get(opts, :author_date, "1142878501 +0230")
committer_date = Keyword.get(opts, :committer_date, "1142878501 +0230")

author_name = Keyword.get(opts, :author_name, "A. U. Thor")
committer_name = Keyword.get(opts, :committer_name, "A. U. Thor")

author_email = Keyword.get(opts, :author_email, "author@example.com")
committer_email = Keyword.get(opts, :committer_email, "author@example.com")

message = Keyword.get(opts, :message, "x")

env = [
{"GIT_AUTHOR_DATE", author_date},
{"GIT_COMMITTER_DATE", committer_date},
{"GIT_AUTHOR_EMAIL", author_email},
{"GIT_COMMITTER_EMAIL", committer_email},
{"GIT_AUTHOR_NAME", author_name},
{"GIT_COMMITTER_NAME", committer_name}
]

{:ok, ref: ref, xgit: xgit} = GitInitTestCase.setup_git_repo()

ref_parents = write_tree_fn.(ref)

{output, 0} = System.cmd("git", ["write-tree", "--missing-ok"], cd: ref)
tree_content_id = String.trim(output)

{output, 0} =
System.cmd(
"git",
["commit-tree", tree_content_id, "-m", message] ++
Enum.flat_map(Enum.uniq(ref_parents), &["-p", &1]),
cd: ref,
env: env
)

ref_commit_id = String.trim(output)

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

parents = write_tree_fn.(xgit)
assert parents == ref_parents

xgit_commit_object =
tree_content_id
|> xgit_fn.(parents)
|> Commit.to_object()

assert Object.valid?(xgit_commit_object)
assert :ok = Object.check(xgit_commit_object)

assert xgit_commit_object.id == ref_commit_id

{output, 0} = System.cmd("git", ["write-tree", "--missing-ok"], cd: xgit)
assert tree_content_id == String.trim(output)

:ok = Repository.put_loose_object(repo, xgit_commit_object)

assert_folders_are_equal(
Path.join([ref, ".git", "objects"]),
Path.join([xgit, ".git", "objects"])
)
end
end
end

0 comments on commit 13122c5

Please sign in to comment.