Packages

Elixir implementation of the Git object storage, but with the goal to implement the same semantic with other storage and topics

Current section

Files

Jump to
gitex lib gitex.ex
Raw

lib/gitex.ex

defmodule Gitex do
@type hash :: {:hash,String.t}
@type refpath:: {:refpath,String.t}
@type tag :: {:tag,String.t}
@type branch :: {:branch,String.t}
@type remote :: {:remote,remote::String.t,:head | branch::String.t}
@type ref :: :head | tag | branch | remote
@moduledoc """
Git API to a repo which is a struct implementing Gitex.Codec and Gitex.Backend
"""
@doc "get the decoded GIT object associated with a given hash"
def object(repo,hash), do:
Gitex.Codec.decode(repo,hash,Gitex.Backend.get_obj(repo,hash))
@doc """
get the decoded GIT object from a fuzzy reference : can be
either a `ref` or a binary which will be tested for
each reference type in this order : branch,tag,remote
"""
def get(repo,ref), do:
object(repo,fuzzy_ref(repo,ref))
@doc "from a reference, use a path to get the wanted object"
def get(repo,%{}=obj,path) , do:
get_path(repo,obj,path)
def get(repo,ref,path), do:
get_path(repo,get(repo,ref),path)
@doc "lazily stream parents of a reference, sorted by date"
def history(repo,ref) do
Stream.resource(fn-> {object(repo,fuzzy_ref(repo,ref)),[]} end,fn
:nomore->{:halt,nil}
{current,others}->
to_compare = Enum.concat(Enum.map(parent_list(current),&object(repo,&1)) ,others)
nexts = to_compare |> Enum.uniq(& &1.hash) |> Enum.sort_by(& &1.committer.utc_time) |> Enum.reverse
case nexts do
[next|others]-> {[current],{next,others}}
[]-> {[current],:nomore}
end
end,fn _->:done end)
end
@doc """
take a commit stream (from `history/2`) and lazily
add an index of the current branch to ease visualization and tree drawing
"""
def align_history(history) do
Stream.transform(history,{1,HashDict.new}, fn commit, {nextlevel,levels}=acc->
level = Dict.get(levels,commit.hash,0)
acc = case parent_list(commit) do
[head|tail]->
levels = Dict.update(levels,head,level,&min(&1,level))
Enum.reduce(tail,{nextlevel,levels},fn h,{nextlevel,levels}->
levels[h] && {nextlevel,levels} || {nextlevel+1,Dict.put(levels,h,nextlevel)}
end)
[]->acc
end
{[{level,commit}],acc}
end)
end
defp parent_list(%{parent: parents}) when is_list(parents), do: Enum.reverse(parents)
defp parent_list(%{parent: parent}), do: [parent]
defp parent_list(_), do: []
defp refpath(:head), do: "HEAD"
defp refpath({:branch,ref}), do: "refs/heads/#{ref}"
defp refpath({:tag,ref}), do: "refs/tags/#{ref}"
defp refpath({:remote,remote,:head}), do: "refs/remotes/#{remote}/HEAD"
defp refpath({:remote,remote,ref}), do: "refs/remotes/#{remote}/#{ref}"
defp fuzzy_ref(repo,ref) when is_atom(ref) or is_tuple(ref), do:
Gitex.Backend.resolve_ref(repo,refpath(ref))
defp fuzzy_ref(repo,ref) when is_binary(ref) do
Gitex.Backend.resolve_ref(repo,refpath({:branch,ref}))
|| Gitex.Backend.resolve_ref(repo,refpath({:tag,ref}))
|| case String.split(ref,"/") do
[remote,ref]->Gitex.Backend.resolve_ref(repo,refpath({:remote,remote,ref}))
[remote]->Gitex.Backend.resolve_ref(repo,refpath({:remote,remote,:head}))
end
|| ref
end
defp get_path(repo,%{tree: tree},path), do: get_path(repo,object(repo,tree),path)
defp get_path(repo,%{object: ref},path), do: get_path(repo,object(repo,ref),path)
defp get_path(_repo,obj,"/"), do: obj
defp get_path(repo,tree,path) when is_list(tree) do
{name,subpath} = case String.split(String.strip(path,?/),"/", parts: 2) do
[head,tail] -> {head,tail}
[head] -> {head,nil}
end
if (elem=Enum.find(tree,& &1.name == name)) do
obj = object(repo,elem.ref)
if subpath, do: get_path(repo,obj,subpath), else: obj
end
end
defp get_path(_,_,_), do: nil
end