diff --git a/lib/kmxgit/git_manager.ex b/lib/kmxgit/git_manager.ex
new file mode 100644
index 0000000..daff2d6
--- /dev/null
+++ b/lib/kmxgit/git_manager.ex
@@ -0,0 +1,96 @@
+defmodule Kmxgit.GitManager do
+
+ @git_root "priv/git"
+
+ def git_dir(repo) do
+ "#{@git_root}/#{repo}.git"
+ end
+
+ def status(repo) do
+ dir = git_dir(repo)
+ {out, status} = System.cmd("git", ["-C", dir, "status"])
+ case status do
+ 0 -> {:ok, out}
+ _ -> {:error, out}
+ end
+ end
+
+ def branches(repo) do
+ dir = git_dir(repo)
+ {out, status} = System.cmd("git", ["branch", "-a"])
+ IO.inspect {out, status}
+ case status do
+ 0 ->
+ b = out
+ |> String.split("\n")
+ |> filter_branches()
+ |> Enum.reject(&(!&1 || &1 == ""))
+ {:ok, b}
+ _ -> {:error, out}
+ end
+ end
+
+ defp filter_branches(lines) do
+ filter_branches(lines, [], nil)
+ end
+
+ defp filter_branches([], acc, nil) do
+ Enum.reverse(acc)
+ end
+ defp filter_branches([], acc, main) do
+ [main | Enum.reverse(acc)]
+ end
+ defp filter_branches([line | rest], acc, main) do
+ case Regex.run(~r/^(\* )? *([^ ].*)$/, line) do
+ [_, "* ", branch] -> filter_branches(rest, acc, branch)
+ [_, _, branch] -> filter_branches(rest, [branch | acc], main)
+ _ -> filter_branches(rest, acc, main)
+ end
+ end
+
+ def content(repo, branch, path) do
+ dir = git_dir(repo)
+ path = if path == "" do "." else path end
+ {out, status} = System.cmd("git", ["-C", dir, "cat-file", "blob", path])
+ IO.inspect {out, status}
+ case status do
+ 0 -> {:ok, out}
+ _ -> {:error, out}
+ end
+ end
+
+ def files(repo, tree, path, parent \\ ".") do
+ dir = git_dir(repo)
+ path1 = if path == "" do "." else path end
+ {out, status} = System.cmd("git", ["-C", dir, "ls-tree", tree, path1])
+ case status do
+ 0 ->
+ list = out
+ |> String.split("\n")
+ |> Enum.reject(&(&1 == ""))
+ |> Enum.map(fn line ->
+ [stat, name] = String.split(line, "\t")
+ [mode, type, sha1] = String.split(stat, " ")
+ url = "#{parent}/#{name}"
+ %{mode: mode, name: name, sha1: sha1, type: type, url: url}
+ end)
+ case list do
+ [%{name: ^path1, sha1: sha1, type: "tree"}] ->
+ files(repo, sha1, "", "#{parent}/#{path1}")
+ _ -> {:ok, list}
+ end
+ _ -> {:error, String.split(out, "\n")}
+ end
+ end
+
+ def create(repo) do
+ dir = "#{@git_root}/#{Path.dirname(repo)}"
+ name = "#{Path.basename(repo)}.git"
+ :ok = File.mkdir_p(dir)
+ {out, status} = System.cmd("git", ["-C", dir, "init", "--bare", name])
+ case status do
+ 0 -> {:ok, out}
+ _ -> {:error, out}
+ end
+ end
+end
diff --git a/lib/kmxgit/repository_manager.ex b/lib/kmxgit/repository_manager.ex
index acb4547..752538d 100644
--- a/lib/kmxgit/repository_manager.ex
+++ b/lib/kmxgit/repository_manager.ex
@@ -19,19 +19,19 @@ defmodule Kmxgit.RepositoryManager do
Repository.changeset(repository, %{})
end
- def create_repository(owner), do: create_repository(owner, %{})
-
- def create_repository(user = %User{}, attrs) do
- %Repository{}
- |> Repository.changeset(attrs)
- |> Ecto.Changeset.put_assoc(:user, user)
- |> Repo.insert()
+ defp put_owner(changeset, owner) do
+ case owner do
+ %User{} ->
+ Ecto.Changeset.put_assoc(changeset, :user, owner)
+ %Organisation{} ->
+ Ecto.Changeset.put_assoc(changeset, :organisation, owner)
+ end
end
- def create_repository(org = %Organisation{}, attrs) do
+ def create_repository(owner, attrs \\ %{}) do
%Repository{}
|> Repository.changeset(attrs)
- |> Ecto.Changeset.put_assoc(:organisation, org)
+ |> put_owner(owner)
|> Repo.insert()
end
diff --git a/lib/kmxgit/repository_manager/repository.ex b/lib/kmxgit/repository_manager/repository.ex
index cdfa9c8..a065901 100644
--- a/lib/kmxgit/repository_manager/repository.ex
+++ b/lib/kmxgit/repository_manager/repository.ex
@@ -20,7 +20,7 @@ defmodule Kmxgit.RepositoryManager.Repository do
|> cast(attrs, [:description, :slug])
|> validate_required([:slug])
|> validate_format(:slug, ~r|^[A-Za-z][-_+.0-9A-Za-z]{1,64}(/[A-Za-z][-_+.0-9A-Za-z]{1,64})*$|)
- |> unique_constraint(:_lower_slug)
+ |> unique_constraint(:slug, name: "repositories__lower_slug_index")
|> Markdown.validate_markdown(:description)
end
diff --git a/lib/kmxgit/slug_manager/slug.ex b/lib/kmxgit/slug_manager/slug.ex
index 32f42d9..521df40 100644
--- a/lib/kmxgit/slug_manager/slug.ex
+++ b/lib/kmxgit/slug_manager/slug.ex
@@ -17,6 +17,6 @@ defmodule Kmxgit.SlugManager.Slug do
|> cast(attrs, [:slug])
|> validate_required([:slug])
|> validate_format(:slug, ~r/^[A-Za-z][-_+.0-9A-Za-z]{1,64}$/)
- |> unique_constraint(:_lower_slug)
+ |> unique_constraint(:slug, name: "slugs__lower_slug_index")
end
end
diff --git a/lib/kmxgit/user_manager/user.ex b/lib/kmxgit/user_manager/user.ex
index 3aeb0a7..69683d1 100644
--- a/lib/kmxgit/user_manager/user.ex
+++ b/lib/kmxgit/user_manager/user.ex
@@ -31,7 +31,7 @@ defmodule Kmxgit.UserManager.User do
|> cast_assoc(:slug)
|> validate_required([:deploy_only, :email, :encrypted_password, :is_admin, :slug])
|> validate_format(:email, ~r/^[-_+.0-9A-Za-z]+@([-_0-9A-Za-z]+[.])+[A-Za-z]+$/)
- |> unique_constraint(:_lower_email)
+ |> unique_constraint(:email, name: "users__lower_email_index")
|> Markdown.validate_markdown(:description)
end
diff --git a/lib/kmxgit_web/controllers/repository_controller.ex b/lib/kmxgit_web/controllers/repository_controller.ex
index 5bf4e60..5c875ff 100644
--- a/lib/kmxgit_web/controllers/repository_controller.ex
+++ b/lib/kmxgit_web/controllers/repository_controller.ex
@@ -1,6 +1,7 @@
defmodule KmxgitWeb.RepositoryController do
use KmxgitWeb, :controller
+ alias Kmxgit.GitManager
alias Kmxgit.RepositoryManager
alias Kmxgit.RepositoryManager.Repository
alias Kmxgit.SlugManager
@@ -68,14 +69,24 @@ defmodule KmxgitWeb.RepositoryController do
defp create_repo(conn, owner, params) do
case Repo.transaction(fn ->
case RepositoryManager.create_repository(owner, params) do
- {:ok, repo} -> repo
- {:error, changeset} -> Repo.rollback changeset
+ {:ok, repo} ->
+ case GitManager.create(Repository.full_slug(repo)) do
+ {:ok, _} -> repo
+ {:error, e} ->
+ repo
+ |> Repository.changeset(%{})
+ |> Ecto.Changeset.add_error(:git, e)
+ |> Repo.rollback
+ end
+ {:error, changeset} ->
+ Repo.rollback(changeset)
end
end) do
{:ok, repo} ->
conn
|> redirect(to: Routes.repository_path(conn, :show, owner.slug.slug, Repository.splat(repo)))
{:error, changeset} ->
+ IO.inspect changeset
conn
|> assign(:action, Routes.repository_path(conn, :create, owner.slug.slug))
|> assign(:changeset, changeset)
@@ -91,24 +102,130 @@ defmodule KmxgitWeb.RepositoryController do
|> render(:"404")
end
+ def chunk_path(path) do
+ chunk_path(path, [[]])
+ end
+
+ def chunk_path([], acc) do
+ acc
+ |> Enum.reverse()
+ |> Enum.map(&Enum.reverse/1)
+ end
+ def chunk_path([first | rest], acc = [acc_first | acc_rest]) do
+ if Regex.match?(~r/_/, first) do
+ chunk_path(rest, [[first] | acc])
+ else
+ chunk_path(rest, [[first | acc_first] | acc_rest])
+ end
+ end
+
def show(conn, params) do
- slug = Enum.join(params["slug"], "/")
+ path = params["slug"] |> chunk_path()
+ slug = path |> Enum.at(0) |> Enum.join("/")
+ {branch, path1} = if (path1 = path |> Enum.at(1)) && (path1 |> Enum.at(0)) == "_branch" do
+ {_, rest} = path |> Enum.split(2)
+ rest1 = rest |> Enum.map(fn x ->
+ Enum.join(x, "/")
+ end)
+ {_, path2} = path1 |> Enum.split(2)
+ {path1 |> Enum.at(1),
+ path2 ++ rest1 |> Enum.reject(&(!&1 || &1 == "")) |> Enum.join("/")}
+ else
+ {"master", ""}
+ end
+ IO.inspect([path: path, slug: slug, branch: branch, path1: path1])
repo = RepositoryManager.get_repository_by_owner_and_slug(params["owner"], slug)
if repo do
org = repo.organisation
user = repo.user
- conn
- |> assign_current_organisation(org)
- |> assign(:current_repository, repo)
- |> assign(:repo, repo)
- |> assign(:members, Repository.members(repo))
- |> assign(:owner, org || user)
- |> render("show.html")
+ git = %{branches: [], content: nil, files: [], status: "", valid: true}
+ |> git_put_branches(repo, conn)
+ |> git_put_files(repo, branch, path1, conn)
+ |> git_put_content(repo, branch, path1)
+ IO.inspect(git)
+ if git.valid do
+ conn
+ |> assign(:branch, branch)
+ |> assign(:branch_url, branch_url(git.branches, branch))
+ |> assign_current_organisation(org)
+ |> assign(:current_repository, repo)
+ |> assign(:git, git)
+ |> assign(:repo, repo)
+ |> assign(:members, Repository.members(repo))
+ |> assign(:owner, org || user)
+ |> assign(:path, path1)
+ |> render("show.html")
+ else
+ not_found(conn)
+ end
else
not_found(conn)
end
end
+ defp branch_url([{b, url} | rest], branch) do
+ if b == branch do
+ url
+ else
+ branch_url(rest, branch)
+ end
+ end
+
+ defp git_put_branches(git = %{valid: true}, repo, conn) do
+ case GitManager.branches(Repository.full_slug(repo)) do
+ {:ok, branches} ->
+ branches = branches
+ |> Enum.map(fn b ->
+ url = Routes.repository_path(conn, :show, Repository.owner_slug(repo), Repository.splat(repo) ++ ["_branch", b])
+ {b, url}
+ end)
+ %{git | branches: branches}
+ {:error, status} -> %{git | status: status, valid: false}
+ end
+ end
+ defp git_put_branches(git, _, _) do
+ git
+ end
+
+ defp git_put_status(git = %{content: nil, valid: true}, repo) do
+ case GitManager.status(Repository.full_slug(repo)) do
+ {:ok, status} -> %{git | status: status}
+ {:error, status} -> %{git | status: status, valid: false}
+ end
+ end
+
+ defp git_put_status(git, _) do
+ git
+ end
+
+ defp git_put_files(git = %{valid: true}, repo, branch, subdir, conn) do
+ case GitManager.files(Repository.full_slug(repo), branch, subdir) do
+ {:ok, []} -> %{git | valid: false}
+ {:ok, files} ->
+ files = files
+ |> Enum.map(fn f = %{url: url} ->
+ %{f | url: Routes.repository_path(conn, :show, Repository.owner_slug(repo), Repository.splat(repo) ++ ["_branch", branch | String.split(url, "/")])}
+ end)
+ %{git | files: files}
+ {:error, status} -> %{git | status: "#{git.status}\n#{status}", valid: false}
+ end
+ end
+ defp git_put_files(git, _, _, _, _) do
+ git
+ end
+
+ defp git_put_content(git = %{files: [%{name: name, type: _type, sha1: sha1}], valid: true}, repo, branch, path) do
+ IO.inspect(git)
+ case GitManager.content(Repository.full_slug(repo), branch, sha1) do
+ {:ok, content} -> %{git | content: content}
+ {:error, error} -> %{git | status: error}
+ end
+ end
+
+ defp git_put_content(git, _, _, _) do
+ git
+ end
+
def edit(conn, params) do
current_user = conn.assigns.current_user
slug = Enum.join(params["slug"], "/")
diff --git a/lib/kmxgit_web/templates/repository/show.html.heex b/lib/kmxgit_web/templates/repository/show.html.heex
index 4e3f81b..95854a5 100644
--- a/lib/kmxgit_web/templates/repository/show.html.heex
+++ b/lib/kmxgit_web/templates/repository/show.html.heex
@@ -14,6 +14,29 @@
<div class="row">
<div class="col col-12 col-md-7">
<hr/>
+ <%= gettext("Branch") %>
+ <%= select :repository, :branch, @git.branches, selected: @branch_url, onchange: "javascript:document.location = this.value;" %>
+ <h2><%= @path %></h2>
+ <%= if @git.content do %>
+ <hr/>
+ <h2><%= gettext("Content") %></h2>
+ <pre><%= @git.content %></pre>
+ <% else %>
+ <hr/>
+ <h2><%= gettext("Files") %></h2>
+ <ul>
+ <%= for file <- @git.files do %>
+ <li>
+ <%= case file.type do %>
+ <% "blob" -> %>
+ <%= link file.name, to: file.url %>
+ <% "tree" -> %>
+ <%= link "#{file.name}/", to: file.url %>
+ <% end %>
+ </li>
+ <% end %>
+ </ul>
+ <% end %>
</div>
<div class="col col-12 col-md-4">
<hr/>
diff --git a/lib/kmxgit_web/templates/user/edit.html.heex b/lib/kmxgit_web/templates/user/edit.html.heex
index 59b3966..7ddb7d5 100644
--- a/lib/kmxgit_web/templates/user/edit.html.heex
+++ b/lib/kmxgit_web/templates/user/edit.html.heex
@@ -5,38 +5,45 @@
<div class="mb-3">
<%= label f, :name, class: "form-label" %>
<%= text_input f, :name, class: "form-control" %>
+ <%= error_tag f, :name %>
</div>
<div class="mb-3">
<%= label f, :email, class: "form-label" %>
<%= text_input f, :email, class: "form-control" %>
+ <%= error_tag f, :email %>
</div>
<%= inputs_for f, :slug, fn ff -> %>
<div class="mb-3">
<%= label ff, :slug, gettext("Login"), class: "form-label" %>
<%= text_input ff, :slug, class: "form-control" %>
+ <%= error_tag ff, :slug %>
</div>
<% end %>
<div class="mb-3">
<%= label f, :description, class: "form-label" %>
<%= textarea f, :description, class: "form-control" %>
+ <%= error_tag f, :description %>
</div>
<div class="mb-3">
<%= label f, :ssh_keys, gettext("SSH keys"), class: "form-label" %>
<%= textarea f, :ssh_keys, class: "form-control" %>
+ <%= error_tag f, :ssh_keys %>
</div>
<div class="mb-3">
<%= label f, :password, class: "form-label" %>
<%= password_input f, :password, class: "form-control" %>
+ <%= error_tag f, :password %>
</div>
<div class="mb-3">
<%= label f, :password_confirmation, class: "form-label" %>
<%= password_input f, :password_confirmation, class: "form-control" %>
+ <%= error_tag f, :password_confirmation %>
</div>
<div class="mb-3 form-check">