Commit 970b811c40f6c3943888b133c0783d44dc52ccb0

Thomas de Grivel 2021-12-07T11:44:43

fork without tracking

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