diff --git a/.gitignore b/.gitignore
index fbe0c19..ec87170 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,6 @@ npm-debug.log
/priv/git/
/.env
+
+/priv/repo/dumps/
+/priv/static/
diff --git a/lib/kmxgit/git_manager.ex b/lib/kmxgit/git_manager.ex
index 07a1a6e..f7b5c5f 100644
--- a/lib/kmxgit/git_manager.ex
+++ b/lib/kmxgit/git_manager.ex
@@ -147,4 +147,20 @@ defmodule Kmxgit.GitManager do
"#{@git_root}/#{dir}/"
|> rm_rf()
end
+
+ def fork(from, to) do
+ dir_from = git_dir(from)
+ dir_to = git_dir(to)
+ if File.exists?(dir_to) do
+ {:error, "file exists"}
+ else
+ dir = Path.dirname(dir_to)
+ :ok = File.mkdir_p(dir)
+ {out, status} = System.cmd("cp", ["-PRp", dir_from, dir_to], stderr_to_stdout: true)
+ case status do
+ 0 -> :ok
+ _ -> {:error, out}
+ end
+ end
+ end
end
diff --git a/lib/kmxgit/repository_manager.ex b/lib/kmxgit/repository_manager.ex
index a3fd2ea..74a807c 100644
--- a/lib/kmxgit/repository_manager.ex
+++ b/lib/kmxgit/repository_manager.ex
@@ -49,6 +49,14 @@ defmodule Kmxgit.RepositoryManager do
|> Repo.insert()
end
+ def fork_repository(repo, owner, slug) do
+ params = %{description: repo.description,
+ slug: slug}
+ %Repository{}
+ |> Repository.owner_changeset(params, owner)
+ |> Repo.insert()
+ end
+
def update_repository(repository, attrs = %{"owner_slug" => owner_slug}) do
if owner_slug && owner_slug != "" do
if slug = SlugManager.get_slug(owner_slug) do
diff --git a/lib/kmxgit/repository_manager/repository.ex b/lib/kmxgit/repository_manager/repository.ex
index 49644d5..48bfc99 100644
--- a/lib/kmxgit/repository_manager/repository.ex
+++ b/lib/kmxgit/repository_manager/repository.ex
@@ -10,6 +10,7 @@ defmodule Kmxgit.RepositoryManager.Repository do
schema "repositories" do
field :deploy_keys, :string
field :description, :string
+ field :fork_to, :string, virtual: true
belongs_to :organisation, Organisation, on_replace: :nilify
field :owner_slug, :string, virtual: true
field :slug, :string
diff --git a/lib/kmxgit_web/controllers/repository_controller.ex b/lib/kmxgit_web/controllers/repository_controller.ex
index e26d4ee..67385dd 100644
--- a/lib/kmxgit_web/controllers/repository_controller.ex
+++ b/lib/kmxgit_web/controllers/repository_controller.ex
@@ -50,7 +50,7 @@ defmodule KmxgitWeb.RepositoryController do
create_repo(conn, params["repository"], user)
else
org = slug.organisation
- if org && org.users |> Enum.find(& &1.id == current_user.id) do
+ if org && Organisation.owner?(org, current_user) do
create_repo(conn, params["repository"], org)
else
not_found(conn)
@@ -98,7 +98,7 @@ defmodule KmxgitWeb.RepositoryController do
slug = chunks |> Enum.at(0) |> Enum.join("/")
{branch, path} = get_branch_and_path(chunks)
repo = RepositoryManager.get_repository_by_owner_and_slug(params["owner"], slug)
- if repo && Enum.find(Repository.members(repo), fn u -> u.id == current_user.id end) do
+ if repo && Repository.member?(repo, current_user) do
org = repo.organisation
user = repo.user
git = setup_git(repo, branch || "master", path, conn)
@@ -430,5 +430,127 @@ defmodule KmxgitWeb.RepositoryController do
defp assign_current_organisation(conn, org) do
assign(conn, :current_organisation, org)
end
-
+
+ def fork(conn, params) do
+ current_user = conn.assigns.current_user
+ slug = Enum.join(params["slug"], "/")
+ repo = RepositoryManager.get_repository_by_owner_and_slug(params["owner"], slug)
+ if repo && Repository.member?(repo, current_user) do
+ org = repo.organisation
+ changeset = RepositoryManager.change_repository(repo)
+ conn
+ |> assign(:action, Routes.repository_path(conn, :fork_post, params["owner"], Repository.splat(repo)))
+ |> assign(:changeset, changeset)
+ |> assign_current_organisation(org)
+ |> assign(:current_repository, repo)
+ |> assign(:repo, repo)
+ |> render("fork.html")
+ else
+ not_found(conn)
+ end
+ end
+
+ def fork_post(conn, params) do
+ current_user = conn.assigns.current_user
+ slug = Enum.join(params["slug"], "/")
+ repo = RepositoryManager.get_repository_by_owner_and_slug(params["owner"], slug)
+ if repo && Repository.member?(repo, current_user) do
+ IO.inspect(params)
+ fork_to = params["repository"]["fork_to"]
+ slug = String.split(fork_to, "/") |> Enum.at(0) |> SlugManager.get_slug()
+ if slug do
+ user = slug.user
+ if user do
+ if user.id == current_user.id do
+ fork_repo(conn, params["repository"], user, repo)
+ else
+ changeset = repo
+ |> Repository.changeset(params["repository"])
+ |> Ecto.Changeset.add_error(:fork_to, "you cannot fork to another user")
+ |> changeset_put_action(:fork)
+ conn
+ |> assign(:action, Routes.repository_path(conn, :fork_post, Repository.owner_slug(repo), Repository.splat(repo)))
+ |> assign(:changeset, changeset)
+ |> assign_current_organisation(repo.organisation)
+ |> assign(:current_repository, repo)
+ |> assign(:repo, repo)
+ |> render("fork.html")
+ end
+ else
+ %Organisation{} = org = slug.organisation
+ if Organisation.owner?(org, current_user) do
+ fork_repo(conn, params["repository"], org, repo)
+ else
+ changeset = repo
+ |> Repository.changeset(params["repository"])
+ |> Ecto.Changeset.add_error(:fork_to, "you don't have the permission to fork to this organisation")
+ |> changeset_put_action(:fork)
+ conn
+ |> assign(:action, Routes.repository_path(conn, :fork_post, Repository.owner_slug(repo), Repository.splat(repo)))
+ |> assign(:changeset, changeset)
+ |> assign_current_organisation(repo.organisation)
+ |> assign(:current_repository, repo)
+ |> assign(:repo, repo)
+ |> render("fork.html")
+ end
+ end
+ else
+ changeset = repo
+ |> Repository.changeset(params["repository"])
+ |> Ecto.Changeset.add_error(:fork_to, "owner was not found")
+ |> changeset_put_action(:fork)
+ conn
+ |> assign(:action, Routes.repository_path(conn, :fork_post, Repository.owner_slug(repo), Repository.splat(repo)))
+ |> assign(:changeset, changeset)
+ |> assign_current_organisation(repo.organisation)
+ |> assign(:current_repository, repo)
+ |> assign(:repo, repo)
+ |> render("fork.html")
+ end
+ else
+ not_found(conn)
+ end
+ end
+
+ defp fork_repo(conn, params, owner, origin) do
+ [_ | slug] = String.split(params["fork_to"], "/")
+ slug = Enum.join(slug, "/")
+ case Repo.transaction(fn ->
+ case RepositoryManager.fork_repository(origin, owner, slug) do
+ {:ok, repo} ->
+ case GitManager.fork(Repository.full_slug(origin), Repository.full_slug(repo)) do
+ :ok -> repo
+ {:error, e} ->
+ repo
+ |> Repository.changeset(params)
+ |> Ecto.Changeset.add_error(:fork_to, e)
+ |> changeset_put_action(:fork)
+ |> Repo.rollback
+ end
+ {:error, changeset} ->
+ Repo.rollback(changeset)
+ end
+ end) do
+ {:ok, repo} ->
+ case GitManager.update_auth() do
+ :ok -> nil
+ error -> IO.inspect(error)
+ end
+ 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, :fork_post, Repository.owner_slug(origin), Repository.splat(origin)))
+ |> assign(:changeset, changeset)
+ |> assign_current_organisation(origin.organisation)
+ |> assign(:current_repository, origin)
+ |> assign(:repo, origin)
+ |> render("fork.html")
+ end
+ end
+
+ defp changeset_put_action(changeset, action) do
+ %Ecto.Changeset{changeset | action: action}
+ end
end
diff --git a/lib/kmxgit_web/router.ex b/lib/kmxgit_web/router.ex
index f080c36..0e8c6ce 100644
--- a/lib/kmxgit_web/router.ex
+++ b/lib/kmxgit_web/router.ex
@@ -97,6 +97,11 @@ defmodule KmxgitWeb.Router do
delete "/repository/:owner/*slug", RepositoryController, :delete
end
+ scope "/_fork/" do
+ get "/:owner/*slug", RepositoryController, :fork
+ post "/:owner/*slug", RepositoryController, :fork_post
+ end
+
scope "/_admin", Admin, as: "admin" do
pipe_through :admin
get "/", DashboardController, :index
diff --git a/lib/kmxgit_web/templates/repository/fork.html.heex b/lib/kmxgit_web/templates/repository/fork.html.heex
new file mode 100644
index 0000000..7b6a642
--- /dev/null
+++ b/lib/kmxgit_web/templates/repository/fork.html.heex
@@ -0,0 +1,26 @@
+<div class="container-fluid">
+ <h1>
+ <%= gettext "Fork repository %{repo}", repo: Repository.full_slug(@repo) %>
+ </h1>
+
+ <%= form_for @changeset, @action, [method: :post], fn f -> %>
+ <%= if @changeset.action do %>
+ <div class="alert alert-danger">
+ <p>Invalid parameters</p>
+ </div>
+ <% end %>
+
+ <div class="mb-3">
+ <%= label f, :fork_to, class: "form-label" %>
+ <%= text_input f, :fork_to, class: "form-control" %>
+ <%= error_tag f, :fork_to %>
+ </div>
+
+ <div class="mb-3">
+ <%= link gettext("Cancel"), to: Routes.repository_path(@conn, :show, Repository.owner_slug(@current_repository), Repository.splat(@current_repository)), class: "btn btn-secondary" %>
+ <%= submit gettext("Submit"), class: "btn btn-primary" %>
+ </div>
+
+ <% end %>
+
+</div>
diff --git a/lib/kmxgit_web/templates/repository/show.html.heex b/lib/kmxgit_web/templates/repository/show.html.heex
index 05d712a..f495367 100644
--- a/lib/kmxgit_web/templates/repository/show.html.heex
+++ b/lib/kmxgit_web/templates/repository/show.html.heex
@@ -7,9 +7,10 @@
<div class="col col-12 col-sm-4">
<%= if Repository.owner?(@repo, @current_user) do %>
<%= link gettext("Edit"),
- to: Routes.repository_path(@conn, :edit, Repository.owner_slug(@repo), String.split(@repo.slug, "/")),
+ to: Routes.repository_path(@conn, :edit, Repository.owner_slug(@repo), Repository.splat(@repo)),
class: "btn btn-primary" %>
<% end %>
+ <%= link gettext("Fork"), to: Routes.repository_path(@conn, :fork, Repository.owner_slug(@repo), Repository.splat(@repo)), class: "btn btn-primary" %>
</div>
</div>
diff --git a/priv/repo/dumps/.keep b/priv/repo/dumps/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/priv/repo/dumps/.keep