Edit

kmx.io/kmxgit/lib/kmxgit_web/controllers/repository_controller.ex

Branch :

  • lib/kmxgit_web/controllers/repository_controller.ex
  • ## kmxgit
    ## Copyright 2022 kmx.io <contact@kmx.io>
    ##
    ## Permission is hereby granted to use this software granted
    ## the above copyright notice and this permission paragraph
    ## are included in all copies and substantial portions of this
    ## software.
    ##
    ## THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
    ## PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
    ## AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
    ## THIS SOFTWARE.
    
    defmodule KmxgitWeb.RepositoryController do
      use KmxgitWeb, :controller
      require Logger
    
      alias Ecto.Changeset
      alias Kmxgit.Git
      alias Kmxgit.GitAuth
      alias Kmxgit.GitManager
      alias Kmxgit.OrganisationManager.Organisation
      alias Kmxgit.RepositoryManager
      alias Kmxgit.RepositoryManager.Repository
      alias Kmxgit.SlugManager
      alias Kmxgit.UserManager
      alias Kmxgit.UserManager.User
      alias Kmxgit.Repo
      alias KmxgitWeb.OpParams
    
      def add_user(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.owner?(repo, current_user) do
          org = repo.organisation
          conn
          |> assign(:action, Routes.repository_path(conn, :add_user_post, params["owner"], Repository.splat(repo)))
          |> assign_current_organisation(org)
          |> assign(:current_repository, repo)
          |> assign(:repo, repo)
          |> render("add_user.html")
        else
          not_found(conn)
        end
      end
    
      def add_user_post(conn, params) do
        current_user = conn.assigns.current_user
        login = params["repository"]["login"]
        slug = Enum.join(params["slug"], "/")
        repo = RepositoryManager.get_repository_by_owner_and_slug(params["owner"], slug)
        if repo && Repository.owner?(repo, current_user) do
          org = repo.organisation
          case RepositoryManager.add_member(repo, login) do
            {:ok, repo} ->
              GitAuth.update()
              conn
              |> redirect(to: Routes.repository_path(conn, :show, params["owner"], Repository.splat(repo)))
            {:error, _} ->
              conn
              |> assign(:action, Routes.repository_path(conn, :add_user_post, params["owner"], Repository.splat(repo)))
              |> assign_current_organisation(org)
              |> assign(:current_repository, repo)
              |> assign(:repo, repo)
              |> render("add_user.html")
          end
        else
          not_found(conn)
        end
      end
    
      def create(conn, params) do
        current_user = conn.assigns.current_user
        slug = SlugManager.get_slug(params["owner"])
        if !slug do
          not_found(conn)
        else
          user = slug.user
          if user && user.id == current_user.id do
            create_repo(conn, params["repository"], user)
          else
            org = slug.organisation
            if org && Organisation.owner?(org, current_user) do
              create_repo(conn, params["repository"], org)
            else
              not_found(conn)
            end
          end
        end
      end
    
      def edit(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.owner?(repo, current_user) do
          org = repo.organisation
          changeset = RepositoryManager.change_repository(repo)
          public_access = GitManager.public_access?(Repository.full_slug(repo))
          conn
          |> assign(:action, Routes.repository_path(conn, :update, params["owner"], Repository.splat(repo)))
          |> assign(:changeset, changeset)
          |> assign_current_organisation(org)
          |> assign(:current_repository, repo)
          |> assign(:public_access, public_access)
          |> assign(:repo, repo)
          |> render("edit.html")
        else
          not_found(conn)
        end
      end
    
      def update(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.owner?(repo, current_user) do
          tr = Repo.transaction(fn ->
            case RepositoryManager.update_repository(repo, params["repository"]) do
              {:ok, repo1} ->
                s = Repository.full_slug(repo)
                s1 = Repository.full_slug(repo1)
                if s != s1 do
                  case GitManager.rename(s, s1) do
                    :ok -> repo1
                    {:error, err} -> Repo.rollback(err)
                  end
                else
                  repo1
                end
              {:error, changeset} -> Repo.rollback(changeset)
            end
          end)
          case tr do
            {:ok, repo1} ->
              GitAuth.update()
              :ok = GitManager.public_access(Repository.full_slug(repo1), repo1.public_access)
              conn
              |> redirect(to: Routes.repository_path(conn, :show, Repository.owner_slug(repo1), Repository.splat(repo1)))
            {:error, changeset} ->
              conn
              |> assign(:action, Routes.repository_path(conn, :update, params["owner"], Repository.splat(repo)))
              |> assign(:changeset, changeset)
              |> assign_current_organisation(repo.organisation)
              |> assign(:current_repository, repo)
              |> assign(:repo, repo)
              |> render("edit.html")
          end
        else
          not_found(conn)
        end
      end
    
      def delete(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.owner?(repo, current_user) do
          case Repo.transaction(fn ->
                case RepositoryManager.delete_repository(repo) do
                  {:ok, _} -> :ok = GitManager.delete(Repository.full_slug(repo))
                  {:error, changeset} -> Repo.rollback changeset
                end
              end) do
            {:ok, _} ->
              GitAuth.update()
              conn
              |> redirect(to: Routes.slug_path(conn, :show, params["owner"]))
            {:error, _changeset} ->
              conn
              |> redirect(to: Routes.slug_path(conn, :edit, params["owner"]))
          end
        else
          not_found(conn)
        end
      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
          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
    
      def new(conn, params) do
        action = Routes.repository_path(conn, :create, params["owner"])
        current_user = conn.assigns.current_user
        slug = SlugManager.get_slug(params["owner"])
        if !slug do
          not_found(conn)
        else
          if slug.user && slug.user.id == current_user.id do
            changeset = RepositoryManager.change_repository(slug.user)
            conn
            |> assign(:action, action)
            |> assign(:changeset, changeset)
            |> assign(:owner, slug.user)
            |> assign(:public_access, false)
            |> render("new.html")
          else
            org = slug.organisation
            if org && Organisation.owner?(org, current_user) do
              changeset = RepositoryManager.change_repository(org)
              conn
              |> assign(:action, action)
              |> assign(:changeset, changeset)
              |> assign(:current_organisation, org)
              |> assign(:owner, org)
              |> assign(:public_access, false)
              |> render("new.html")
            else
              not_found(conn)
            end
          end
        end
      end
    
      def remove_user(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.owner?(repo, current_user) do
          org = repo.organisation
          conn
          |> assign(:action, Routes.repository_path(conn, :remove_user_post, params["owner"], Repository.splat(repo)))
          |> assign_current_organisation(org)
          |> assign(:current_repository, repo)
          |> assign(:repo, repo)
          |> render("remove_user.html")
        else
          not_found(conn)
        end
      end
    
      def remove_user_post(conn, params) do
        current_user = conn.assigns.current_user
        login = params["repository"]["login"]
        slug = Enum.join(params["slug"], "/")
        repo = RepositoryManager.get_repository_by_owner_and_slug(params["owner"], slug)
        if repo && Repository.owner?(repo, current_user) do
          org = repo.organisation
          case RepositoryManager.remove_member(repo, login) do
            {:ok, repo} ->
              GitAuth.update()
              conn
              |> redirect(to: Routes.repository_path(conn, :show, params["owner"], Repository.splat(repo)))
            {:error, _} ->
              conn
              |> assign(:action, Routes.repository_path(conn, :remove_user_post, params["owner"], Repository.splat(repo)))
              |> assign_current_organisation(org)
              |> assign(:current_repository, repo)
              |> assign(:repo, repo)
              |> render("remove_user.html")
          end
        else
          not_found(conn)
        end
      end
    
      def show(conn, params) do
        current_user = conn.assigns[:current_user]
        chunks = params["slug"] |> chunk_path()
        slug = chunks |> Enum.at(0) |> Enum.join("/")
        if conn.request_path == "/#{params["owner"]}/#{slug}" do
          redirect conn, to: conn.request_path <> "/"
        else
          op = get_op(chunks)
          op_params = get_op_params(op, chunks)
          repo = RepositoryManager.get_repository_by_owner_and_slug(params["owner"], slug)
          if (repo) do
            public_access = GitManager.public_access?(Repository.full_slug(repo))
            if op_params && repo && (public_access || Repository.member?(repo, current_user)) do
              org = repo.organisation
              user = repo.user
              git = git_setup(repo, conn, op, op_params)
              first_tree = Enum.find_value(git.trees,
                fn {:branch, "master", _} -> "master"
                  _ -> false
                end) ||
                case Enum.at(git.trees, 0) do
                  {_, first_tree, _} -> first_tree
                  nil -> nil
                end
              tree1 = op_params.tree || first_tree
              op_params = %OpParams{op_params | tree: tree1, git: git, org: org, repo: repo, user: user}
              if git.valid do
                show_op(conn, op || :tree, op_params)
              else
                IO.inspect(:invalid_git)
                not_found(conn)
              end
            else
              IO.inspect(:no_repo)
              not_found(conn)
            end
          else
            not_found(conn)
          end
        end
      end
    
      defp assign_current_organisation(conn, nil), do: conn
      defp assign_current_organisation(conn, %User{}), do: conn
      defp assign_current_organisation(conn, org = %Organisation{}) do
        assign(conn, :current_organisation, org)
      end
    
      defp changeset_put_action(changeset, action) do
        %Ecto.Changeset{changeset | action: action}
      end
    
      defp chunk_path(path) do
        chunk_path(path, [[]])
      end
    
      defp chunk_path([], acc) do
        acc
        |> Enum.reverse()
        |> Enum.map(&Enum.reverse/1)
      end
      defp 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
    
      defp ci_put_content(ci = %{path: path}) do
        if (File.dir?(path)) do
          ci
        else
          case File.read(path) do
            {:ok, content} ->
              {type, ext} = case Regex.run(~r/[.]([^.]+)$/, path) do
                              [_, ext] -> {mime_type(content, ext), ext}
                              _ -> {mime_type(content), nil}
                            end
              filename = filename(path)
              line_numbers = line_numbers(content)
              markdown_html = if ext && String.match?(ext, ~r/md/i) do
                Markdown.to_html!(content)
              end
              lang = lang(ext, filename)
              %{ci | content: content, content_lang: lang, content_type: type, filename: filename, line_numbers: line_numbers, markdown_html: markdown_html}
            _ ->
              ci
          end
        end
      end
    
      defp ci_put_dir(ci = %{path: path}) do
        if (File.dir?(path)) do
          case File.ls(path) do
            {:ok, files} ->
              files1 = files |> Enum.sort() |> Enum.map(fn file ->
                if File.dir?("#{path}/#{file}"), do: "#{file}/", else: file
              end)
              %{ci | files: files1}
            _ ->
              ci
          end
        else
          ci
        end
      end
    
      defp create_repo(conn, params, owner) do
        case Repo.transaction(fn ->
              case RepositoryManager.create_repository(params, owner) do
                {:ok, repo} ->
                  case Git.create(Repository.full_slug(repo)) do
                    :ok -> repo
                    {:error, e} ->
                      repo
                      |> Repository.changeset(params)
                      |> Ecto.Changeset.add_error(:git, e)
                      |> Repo.rollback
                  end
                {:error, changeset} ->
                  Repo.rollback(changeset)
              end
            end) do
          {:ok, repo} ->
            GitAuth.update()
            :ok = GitManager.public_access(Repository.full_slug(repo), repo.public_access)
            conn
            |> redirect(to: Routes.repository_path(conn, :show, owner.slug_, Repository.splat(repo)))
          {:error, changeset} ->
            IO.inspect changeset
            conn
            |> assign(:action, Routes.repository_path(conn, :create, owner.slug_))
            |> assign(:changeset, changeset)
            |> assign_current_organisation(owner)
            |> assign(:owner, owner)
            |> assign(:public_access, Changeset.get_field(changeset, :public_access, false))
            |> render("new.html")
        end
      end
    
      defp filename(path) do
        path
        |> String.split("/")
        |> List.last()
      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} ->
            GitAuth.update()
            conn
            |> redirect(to: Routes.repository_path(conn, :show, owner.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 get_op(chunks) do
        if path = chunks |> Enum.at(1) do
          case path |> Enum.at(0) do
            "_blob" -> :blob
            "_ci" -> :ci
            "_commit" -> :commit
    #        "_diff" -> :diff
            "_log" -> :log
            "_tag" -> :tag
            "_tree" -> :tree
            x ->
              IO.puts "Unknown operation #{x}"
              :unknown
          end
        end
      end
    
      defp get_op_params(nil, _) do
        %OpParams{}
      end
      defp get_op_params(:diff, chunks) do
        path = chunks |> Enum.at(1)
        case path |> Enum.split(3) do
          {[_, from, to], _} -> %OpParams{from: from, to: to}
          _ -> nil
        end
      end
      defp get_op_params(:ci, chunks) do
        path = chunks |> Enum.at(1)
        {_, rest} = chunks |> Enum.split(2)
        rest1 = rest |> Enum.map(fn x ->
          Enum.join(x, "/")
        end)
        case path |> Enum.split(1) do
          {_, path1} ->
            path2 = (path1 ++ rest1)
            |> Enum.reject(&(!&1 || &1 == ""))
            |> Enum.join("/")
            path3 = if path2 != "", do: path2
            %OpParams{path: "/#{path3}"}
          _ ->
            nil
        end
      end
      defp get_op_params(_, chunks) do
        path = chunks |> Enum.at(1)
        {_, rest} = chunks |> Enum.split(2)
        rest1 = rest |> Enum.map(fn x ->
          Enum.join(x, "/")
        end)
        case path |> Enum.split(2) do
          {[_, tree], path1} ->
            path2 = (path1 ++ rest1)
            |> Enum.reject(&(!&1 || &1 == ""))
            |> Enum.join("/")
            path3 = if path2 != "", do: path2
            %OpParams{tree: tree, path: path3}
          _ ->
            nil
        end
      end
    
      defp git_add_user_email(git, email) do
        if Enum.find(git.users_email, & &1 == email) do
          git
        else
          %{git | users_email: [email | git.users_email]}
        end
      end
    
      defp git_add_user_emails(git, []) do
        git
      end
      defp git_add_user_emails(git, [email | rest]) do
        git_add_user_emails(git_add_user_email(git, email), rest)
      end
    
      defp git_put_avatars(git = %{valid: true}) do
        users_by_email = UserManager.get_users_by_email(Enum.uniq(git.users_email))
        %{git | users_by_email: users_by_email}
      end
      defp git_put_avatars(git) do
        git
      end
    
      defp git_put_branches(git = %{valid: true}, repo, conn, op, path) do
        slug = Repository.full_slug(repo)
        case Git.branches(slug) do
          {:ok, branches} ->
            branch_trees = branches
            |> Enum.map(fn branch ->
              url = Routes.repository_path(conn, :show, Repository.owner_slug(repo), Repository.splat(repo) ++ ["_#{op || :tree}", branch] ++ (if path, do: String.split(path, "/"), else: []))
              {:branch, branch, url}
            end)
            %{git | trees: git.trees ++ branch_trees}
          {:error, status} ->
            IO.inspect({:git_put_branches, slug, status})
            %{git | status: "#{git.status}\ngit_put_branches: #{status}", valid: false}
        end
      end
      defp git_put_branches(git, _, _, _, _) do
        git
      end
    
      defp git_put_files(git, _, nil, _, _) do
        git
      end
      defp git_put_files(git = %{valid: true}, repo, tree, subdir, conn) do
        slug = Repository.full_slug(repo)
        case Git.files(slug, tree, subdir || "") do
          {:ok, []} -> git
          {:ok, files} ->
            files = files
            |> Enum.map(fn f = %{url: url} ->
              %{f | url: Routes.repository_path(conn, :show, Repository.owner_slug(repo), Repository.splat(repo) ++ ["_tree", tree | String.split(url, "/")])}
            end)
            %{git | files: files}
          {:error, status} ->
            IO.inspect({:git_put_files, slug, status})
            %{git | status: "#{git.status}\ngit_put_files: #{status}", valid: false}
        end
      end
      defp git_put_files(git, _, _, _, _) do
        git
      end
    
      defp git_put_log(git, repo, tree, path) do
        slug = Repository.full_slug(repo)
        log = case Git.log(slug, tree, path || "") do
                {:ok, log} -> Enum.map log, fn log1 ->
                    ci_status_path = "priv/ci/#{Repository.full_slug(repo)}/ci/status/rbpkg_ci.#{repo.slug}.commit_#{log1.hash}.status"
                    if File.exists?(ci_status_path) do
                      {:ok, ci_status} = File.read(ci_status_path)
                      %{log1 | ci_status: String.trim(ci_status)}
                    else
                      log1
                    end
                  end
                {:error, reason} ->
                  Logger.error(inspect(reason))
                  nil
              end
        if log do
          emails = Enum.map(log, & &1.author_email) |> Enum.uniq()
          %{git | log: log}
          |> git_add_user_emails(emails)
        else
          git
        end
      end
    
      defp git_put_content(git = %{files: [%{name: name, sha1: sha1, type: :blob}], valid: true}, repo, path) do
        if (path == name) do
          #IO.inspect({:git_put_content, git, repo, path})
          case Git.content(Repository.full_slug(repo), sha1) do
            {:ok, content} ->
              {type, ext} = case Regex.run(~r/[.]([^.]+)$/, path) do
                              [_, ext] -> {mime_type(content, ext), ext}
                              _ -> {mime_type(content), nil}
                            end
              filename = filename(name)
              #content_html = Pygmentize.html(content, filename)
              line_numbers = line_numbers(content)
              markdown_html = if ext && String.match?(ext, ~r/md/i) do
                Markdown.to_html!(content)
              end
              lang = lang(ext, filename)
              #IO.inspect(path: path, name: name, type: type)
              %{git | content: content, content_lang: lang, content_type: type, filename: filename, line_numbers: line_numbers, markdown_html: markdown_html}
            {:error, error} -> %{git | status: "#{git.status}\ngit_put_content: #{error}"}
          end
        else
          git
        end
      end
      defp git_put_content(git, _, _) do
        git
      end
    
      defp git_put_log1(git = %{valid: true}, repo, tree, path) do
        slug = Repository.full_slug(repo)
        log1 = case Git.log(slug, tree, path || "", 0, 1) do
                 {:ok, [commit]} ->
                   ci_status_path = "priv/ci/#{Repository.full_slug(repo)}/ci/status/rbpkg_ci.#{repo.slug}.commit_#{commit.hash}.status"
                   IO.inspect(ci_status_path)
                   if File.exists?(ci_status_path) do
                     {:ok, ci_status} = File.read(ci_status_path)
                     %{commit | ci_status: String.trim(ci_status)}
                   else
                     commit
                   end
                 {:ok, _result} ->
                   #IO.inspect({:log1, result})
                   nil
                 {:error, err} ->
                   IO.inspect(err)
                   nil
               end
        if log1 do
          %{git | log1: log1}
          |> git_add_user_email(log1.author_email)
        else
          git
        end
      end
      defp git_put_log1(git, _, _, _) do
        git
      end
    
      defp git_put_readme(git = %{files: files, valid: true}, repo) do
        readme = Enum.map(files, fn f ->
          if String.match?(f.name, ~r/^readme\.md$/i) do
            {:ok, content} = Git.content(Repository.full_slug(repo), f.sha1)
            %{html: Markdown.to_html!(content),
              name: f.name,
              txt: content}
          else
            if String.match?(f.name, ~r/^readme(.txt)?$/i) do
              {:ok, content} = Git.content(Repository.full_slug(repo), f.sha1)
              %{html: nil,
                name: f.name,
                txt: content}
            end
          end
        end)
        |> Enum.filter(& &1)
        %{git | readme: readme}
      end
      defp git_put_readme(git, _) do
        git
      end
    
      defp git_put_release(git, _, nil, _) do
        git
      end
      defp git_put_release(git = %{valid: true}, repo, tree, conn) do
        case Git.files("#{Repository.full_slug(repo)}/release", "master", "#{tree}") do
          {:ok, []} ->
            git
          {:ok, files} ->
            release = files
            |> Enum.map(fn f = %{url: url} ->
              %{f | url: Routes.repository_path(conn, :show, Repository.owner_slug(repo), Repository.splat(repo) ++ ["release", "_blob", "master" | String.split(url, "/")])}
            end)
            %{git | release: release}
          {:error, _e} ->
            #IO.inspect("git_put_release: Git.files error: #{e}") 
            git
        end
      end
      defp git_put_release(git, _, _, _) do
        git
      end
    
      defp git_put_tags(git = %{valid: true}, repo, conn, op, path) do
        case Git.tags(Repository.full_slug(repo)) do
          {:ok, tags} ->
            tag_trees = tags
            |> Enum.map(fn tag ->
              url = Routes.repository_path(conn, :show, Repository.owner_slug(repo), Repository.splat(repo) ++ ["_#{op || :tree}", tag] ++ (if path, do: String.split(path, "/"), else: []))
              {:tag, tag, url}
            end)
            |> Enum.reverse()
            %{git | tags: tags, trees: git.trees ++ tag_trees}
          {:error, status} -> %{git | status: status, valid: false}
        end
      end
      defp git_put_tags(git, _, _, _, _) do
        git
      end
    
      defp git_put_commit(git = %{valid: true}, repo, conn, op, tree, path) do
        if Enum.find(git.trees, fn {_, id, _} -> id == tree end) do
          git
        else
          url = Routes.repository_path(conn, :show, Repository.owner_slug(repo), Repository.splat(repo) ++ ["_#{op || :tree}", tree] ++ (if path, do: String.split(path, "/"), else: []))
          %{git | trees: [{:commit, tree, url} | git.trees]}
        end
      end
      defp git_put_commit(git, _, _, _, _, _) do
        git
      end
    
      defp git_setup(repo, conn, op, op_params) do
        %{trees: [],
          content: nil,
          content_lang: nil,
          content_html: nil,
          content_type: nil,
          filename: nil,
          files: [],
          line_numbers: nil,
          log: nil,
          log1: nil,
          markdown_html: nil,
          path: op_params.path,
          readme: [],
          release: nil,
          status: "",
          tags: [],
          users_by_email: %{},
          users_email: [],
          valid: true}
          |> git_put_branches(repo, conn, op, op_params.path)
      end
    
      @lang_ext %{
        "ex" => "elixir",
        "exs" => "elixir",
        "h" => "c"
      }
    
      defp lang(nil, filename) do
        if String.match?(filename, ~r/Makefile$/i) do
          "makefile"
        else
          "shell"
        end
      end
      defp lang("", filename), do: lang(nil, filename)
      defp lang(ext, filename) do
        case Regex.run(~r/(.*)~$/, ext) do
          [_, ext1] -> lang(ext1, filename)
          _ ->
            ext2 = String.replace(ext, ~r/[^A-Za-z0-9]/, "_")
            @lang_ext[ext2] || ext2
        end
      end
    
      defp line_numbers(string) do
        string
        |> String.split("\n")
        |> tl()
        |> Enum.with_index()
        |> Enum.map(fn {_, i} -> "#{i + 1}" end)
      end
    
      defp mime_type(content, ext \\ nil) do
        if String.valid?(content) do
          "text/plain"
        else
          MIME.type(ext)
        end
      end
      
      defp show_op(conn, :blob, %{git: git, path: path, repo: repo, tree: tree}) do
        git = git
        |> git_put_files(repo, tree, path, conn)
        |> git_put_content(repo, path)
        if (git.content) do
          conn
          |> put_resp_content_type("application/octet-stream")
          |> put_resp_header("Content-Disposition", "attachment; filename=#{git.filename |> URI.encode()}")
          |> resp(200, git.content)
        else
          IO.inspect(:no_content)
          not_found(conn)
        end
      end
      defp show_op(conn, :ci, %{org: org, path: path, repo: repo, user: user}) do
        if String.match?(path, ~r{(^|/)[.][.](/|$)}) do
          not_found(conn)
        else
            ci = %{content: nil,
                   content_lang: nil,
                   content_type: nil,
                   filename: nil,
                   files: nil,
                   line_numbers: nil,
                   markdown_html: nil,
                   path: "priv/ci/#{Repository.full_slug(repo)}/#{path}"}
                   |> ci_put_content()
                   |> ci_put_dir()
            if ci.content == nil && ci.files == nil do
              not_found(conn)
            else
              if Path.extname(path) == ".log" || ci.content_type && String.match?(ci.content_type, ~r(^image/)) do
                conn
                |> put_resp_content_type(ci.content_type)
                |> resp(200, ci.content)
              else
                if ci.content_lang == "html" do
                  conn
                  |> put_resp_content_type("text/html")
                  |> resp(200, ci.content)
                else
                  conn
                  |> assign(:ci, ci)
                  |> assign_current_organisation(org)
                  |> assign(:current_repository, repo)
                  |> assign(:owner, org || user)
                  |> assign(:path, path)
                  |> assign(:repo, repo)
                  |> render("ci.html")
                end
              end
            end
        end
      end
      defp show_op(conn, op = :commit, %{git: git, org: org, path: path, repo: repo, tree: tree}) do
        public_access = GitManager.public_access?(Repository.full_slug(repo))
        git = git
        |> git_put_log1(repo, tree, path)
        |> git_put_commit(repo, conn, op, tree, path)
        |> git_put_avatars()
        #IO.inspect(git)
        if git.log1.hash do
          diff = case Git.diff(Repository.full_slug(repo), "#{git.log1.hash}~1", git.log1.hash) do
                   {:ok, diff} -> diff
                   _ -> nil
                 end
          diff_line_numbers = diff && line_numbers(diff)
        else
          diff = ""
          diff_line_numbers = line_numbers(diff)
        end
        conn
        |> assign(:commit, git.log1)
        |> assign_current_organisation(org)
        |> assign(:current_repository, repo)
        |> assign(:diff, diff)
        |> assign(:diff_line_numbers, diff_line_numbers)
        |> assign(:git, git)
        |> assign(:path, path)
        |> assign(:public_access, public_access)
        |> assign(:repo, repo)
        |> render("commit.html")
      end
      defp show_op(conn, :diff, %{from: from, org: org, path: path, repo: repo, to: to}) do
        case Git.diff(Repository.full_slug(repo), from, to) do
          {:ok, diff} ->
            #diff_html = Pygmentize.html(diff, "diff.patch")
            diff_line_numbers = line_numbers(diff)
            #IO.inspect(from: from, to: to, diff: diff, html: diff_html, line_numbers: diff_line_numbers)
            conn
            |> assign_current_organisation(org)
            |> assign(:current_repository, repo)
            |> assign(:diff, diff)
            |> assign(:diff_from, from)
            |> assign(:diff_line_numbers, diff_line_numbers)
            |> assign(:diff_to, to)
            |> assign(:path, path)
            |> assign(:repo, repo)
            |> render("diff.html")
          {:error, e} ->
            IO.inspect(e)
            not_found(conn)
        end
      end
      defp show_op(conn, :log, %{tree: tree, git: git, org: org, path: path, repo: repo}) do
        public_access = GitManager.public_access?(Repository.full_slug(repo))
        git = git
        |> git_put_log(repo, tree, path)
        |> git_put_avatars()
    
        #IO.inspect([:log, tree: tree, git: git, path: path, log: log])
        if git.log do
          conn
          |> assign(:tree, tree)
          |> assign(:tree_url, Routes.repository_path(conn, :show, Repository.owner_slug(repo), Repository.splat(repo, ["_log", tree] ++ (if path, do: String.split(path, "/"), else: []))))
          |> assign_current_organisation(org)
          |> assign(:current_repository, repo)
          |> assign(:git, git)
          |> assign(:log, git.log)
          |> assign(:path, path)
          |> assign(:public_access, public_access)
          |> assign(:repo, repo)
          |> render("log.html")
        else
          not_found(conn)
        end
      end
      defp show_op(conn, op = :tag, %{tree: tree, git: git, org: org, path: path, repo: repo}) do
        git = git
        |> git_put_log1(repo, tree, path)
        |> git_put_release(repo, tree, conn)
        |> git_put_tags(repo, conn, op, nil)
        |> git_put_avatars()
        public_access = GitManager.public_access?(Repository.full_slug(repo))
        tag = Enum.find(git.tags, fn tag -> tag == tree end)
        if git.log1 && tag do
          conn
          |> assign_current_organisation(org)
          |> assign(:current_repository, repo)
          |> assign(:git, git)
          |> assign(:path, nil)
          |> assign(:repo, repo)
          |> assign(:public_access, public_access)
          |> assign(:tag, tag)
          |> render("tag.html")
        else
          not_found(conn)
        end
      end
      defp show_op(conn, op = :tree, %{tree: tree, git: git, org: org, path: path, repo: repo, user: user}) do
        public_access = GitManager.public_access?(Repository.full_slug(repo))
        git = git
        |> git_put_files(repo, tree, path, conn)
        |> git_put_release(repo, tree, conn)
        |> git_put_content(repo, path)
        |> git_put_readme(repo)
        |> git_put_log1(repo, tree, path)
        |> git_put_tags(repo, conn, op, path)
        |> git_put_avatars()
        if (git.content == nil) && ! String.match?(conn.request_path, ~r(/$)) do
          redirect conn, to: conn.request_path <> "/"
        else
          conn
          |> assign_current_organisation(org)
          |> assign(:current_repository, repo)
          |> assign(:disk_usage, Repository.disk_usage(repo))
          |> assign(:git, git)
          |> assign(:members, Repository.members(repo))
          |> assign(:owner, org || user)
          |> assign(:path, path)
          |> assign(:public_access, public_access)
          |> assign(:repo, repo)
          |> assign(:tree, tree)
          |> assign(:tree_url, tree && Routes.repository_path(conn, :show, Repository.owner_slug(repo), Repository.splat(repo, ["_tree", tree] ++ (if path, do: String.split(path, "/"), else: []))))
          |> render("show.html")
        end
      end
      defp show_op(conn, _, _) do
        not_found(conn)
      end
    end