Current section

Files

Jump to
livebook lib livebook file_system git.ex
Raw

lib/livebook/file_system/git.ex

defmodule Livebook.FileSystem.Git do
use Ecto.Schema
import Ecto.Changeset
alias Livebook.FileSystem
# File system backed by an Git repository.
@type t :: %__MODULE__{
id: String.t(),
repo_url: String.t(),
branch: String.t(),
key: String.t(),
external_id: String.t() | nil,
hub_id: String.t()
}
embedded_schema do
field :repo_url, :string
field :branch, :string
field :key, :string, redact: true
field :external_id, :string
field :hub_id, :string
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking file system changes.
"""
@spec change_file_system(t(), map()) :: Ecto.Changeset.t()
def change_file_system(git, attrs \\ %{}) do
changeset(git, attrs)
end
defp changeset(git, attrs) do
git
|> cast(attrs, [:repo_url, :branch, :key, :external_id, :hub_id])
|> validate_format(:repo_url, ~r/^git@[\w\.\-]+:(?:v\d+\/)?[\w\.\-\/]+\.git$/,
message: "must be a valid repo URL"
)
|> validate_required([:repo_url, :branch, :key, :hub_id])
|> put_id()
end
defp put_id(changeset) do
hub_id = get_field(changeset, :hub_id)
repo_url = get_field(changeset, :repo_url)
if get_field(changeset, :id) do
changeset
else
put_change(changeset, :id, FileSystem.Utils.id("git", hub_id, repo_url))
end
end
@doc false
def git_dir(%__MODULE__{id: id}), do: Path.join(Livebook.Config.tmp_path(), id)
@doc false
def key_path(%__MODULE__{id: id}), do: Path.join(Livebook.Config.tmp_path(), "#{id}_key")
end
defimpl Livebook.FileSystem, for: Livebook.FileSystem.Git do
alias Livebook.FileSystem
alias Livebook.FileSystem.Git
def type(_file_system) do
:global
end
def default_path(_file_system) do
"/"
end
def list(file_system, path, _recursive) do
with {:ok, []} <- Git.Client.list_files(file_system, path) do
FileSystem.Utils.posix_error(:enoent)
end
end
def read(file_system, path) do
Git.Client.read_file(file_system, path)
end
def write(_file_system, _path, _content), do: raise("not implemented")
def access(_file_system, _path) do
{:ok, :read}
end
def create_dir(_file_system, _path), do: raise("not implemented")
def remove(_file_system, _path), do: raise("not implemented")
def copy(_file_system, _source_path, _destination_path), do: raise("not implemented")
def rename(_file_system, _source_path, _destination_path), do: raise("not implemented")
def etag_for(file_system, path) do
FileSystem.Utils.assert_regular_path!(path)
Git.Client.etag(file_system, path)
end
def exists?(file_system, path) do
Git.Client.exists?(file_system, path)
end
def resolve_path(_file_system, dir_path, subject) do
FileSystem.Utils.resolve_unix_like_path(dir_path, subject)
end
def write_stream_init(_file_system, _path, _opts), do: raise("not implemented")
def write_stream_chunk(_file_system, _state, _chunk), do: raise("not implemented")
def write_stream_finish(_file_system, _state), do: raise("not implemented")
def write_stream_halt(_file_system, _state), do: raise("not implemented")
def read_stream_into(file_system, path, collectable) do
try do
Git.Client.stream_file(file_system, path, collectable)
rescue
error in File.Error -> FileSystem.Utils.posix_error(error.reason)
end
end
def load(file_system, %{"hub_id" => _} = fields) do
load(file_system, %{
id: fields["id"],
repo_url: fields["repo_url"],
branch: fields["branch"],
key: fields["key"],
external_id: fields["external_id"],
hub_id: fields["hub_id"]
})
end
def load(file_system, fields) do
%{
file_system
| id: fields.id,
repo_url: fields.repo_url,
branch: fields.branch,
key: fields.key,
external_id: fields.external_id,
hub_id: fields.hub_id
}
end
def dump(file_system) do
file_system
|> Map.from_struct()
|> Map.take([:id, :repo_url, :branch, :key, :external_id, :hub_id])
end
def external_metadata(file_system) do
%{name: file_system.repo_url, error_field: "repo_url"}
end
def mount(file_system) do
if mounted?(file_system) do
FileSystem.Git.Client.fetch(file_system)
else
FileSystem.Git.Client.init(file_system)
end
end
def unmount(file_system) do
path = FileSystem.Git.git_dir(file_system)
key_path = FileSystem.Git.key_path(file_system)
if File.exists?(key_path) do
File.rm!(key_path)
end
case File.rm_rf(path) do
{:ok, _} -> :ok
{:error, reason, _file} -> FileSystem.Utils.posix_error(reason)
end
end
defp mounted?(file_system) do
file_system
|> FileSystem.Git.git_dir()
|> File.exists?()
end
end