Packages
livebook
0.19.3
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook/file_system/git/client.ex
defmodule Livebook.FileSystem.Git.Client do
alias Livebook.FileSystem
@doc """
Clones the given repository to your local file system.
"""
@spec init(FileSystem.Git.t()) :: :ok | {:error, FileSystem.error()}
def init(%FileSystem.Git{} = file_system) do
case fetch_repository(file_system) do
{:ok, _} ->
:ok
{:error, _reason} ->
git_dir = FileSystem.Git.git_dir(file_system)
with_ssh_key_file(file_system, fn key_path ->
with {:ok, _} <- clone(git_dir, file_system.repo_url, key_path) do
fetch(git_dir, file_system.branch, key_path)
end
end)
end
end
@doc """
Returns a list of files from given repository and given path.
"""
@spec list_files(FileSystem.Git.t(), String.t()) ::
{:ok, list(String.t())} | {:error, FileSystem.error()}
def list_files(%FileSystem.Git{} = file_system, path) do
path = relative_path(path)
with {:ok, git_dir} <- fetch_repository(file_system) do
ls_tree(git_dir, file_system.branch, path)
end
end
@doc """
Returns the content of the given file from given repository.
"""
@spec read_file(FileSystem.Git.t(), String.t()) ::
{:ok, String.t()} | {:error, FileSystem.error()}
def read_file(%FileSystem.Git{} = file_system, path) do
path = relative_path(path)
with {:ok, git_dir} <- fetch_repository(file_system) do
git(git_dir, ["show", "#{file_system.branch}:#{path}"])
end
end
@doc """
Streams the content of the given file from given repository.
"""
@spec stream_file(FileSystem.Git.t(), String.t(), Collectable.t()) ::
{:ok, String.t()} | {:error, FileSystem.error()}
def stream_file(%FileSystem.Git{} = file_system, path, collectable) do
path = relative_path(path)
with {:ok, git_dir} <- fetch_repository(file_system) do
git(git_dir, ["show", "#{file_system.branch}:#{path}"], into: collectable)
end
end
@doc """
Returns the ETag of the given file from given repository.
"""
@spec etag(FileSystem.Git.t(), String.t()) :: {:ok, String.t()} | {:error, FileSystem.error()}
def etag(%FileSystem.Git{} = file_system, path) do
path = relative_path(path)
with {:ok, git_dir} <- fetch_repository(file_system) do
rev_parse(git_dir, file_system.branch, path)
end
end
@doc """
Returns if the given path exists in given repository.
"""
@spec exists?(FileSystem.Git.t(), String.t()) :: {:ok, boolean()} | {:error, FileSystem.error()}
def exists?(%FileSystem.Git{} = file_system, path) do
with {:ok, keys} <- list_files(file_system, path) do
{:ok, keys != []}
end
end
@doc """
Fetches the repository with latest changes from branch.
"""
@spec fetch(FileSystem.Git.t()) :: :ok | {:error, FileSystem.error()}
def fetch(%FileSystem.Git{} = file_system) do
with {:ok, git_dir} <- fetch_repository(file_system) do
with_ssh_key_file(file_system, &fetch(git_dir, file_system.branch, &1))
end
end
@ls_tree_format "--format=%(objecttype) %(path)"
defp ls_tree(git_dir, branch, path) do
with {:ok, result} <- git(git_dir, ["ls-tree", @ls_tree_format, branch, path]) do
{:ok,
result
|> String.split("\n", trim: true)
|> Enum.map(&normalize_dir_path/1)}
end
end
defp rev_parse(git_dir, branch, path) do
with {:ok, etag} <- git(git_dir, ["rev-parse", "#{branch}:#{path}"]) do
{:ok, String.trim(etag)}
end
end
defp clone(git_dir, repo_url, key_path) do
with {:ok, _ssh} <- fetch_executable("ssh") do
git(git_dir, ["clone", "--bare", "--depth=1", repo_url, git_dir], env_opts(key_path))
end
end
defp fetch(git_dir, branch, key_path) do
with {:ok, _ssh} <- fetch_executable("ssh"),
{:ok, _} <-
git(git_dir, ["fetch", "origin", "#{branch}:#{branch}"], env_opts(key_path)) do
:ok
end
end
defp git(git_dir, args, opts \\ []) do
with {:ok, git} <- fetch_executable("git") do
case System.cmd(git, args, cmd_opts(git_dir, opts)) do
{result, 0} -> {:ok, result}
{error, _} -> {:error, String.trim(error)}
end
end
end
@cmd_opts [stderr_to_stdout: true]
defp cmd_opts(git_dir, opts) do
opts = Keyword.merge(@cmd_opts, opts)
if File.exists?(git_dir) do
Keyword.merge(opts, cd: git_dir)
else
opts
end
end
defp env_opts(key_path) do
[env: %{"GIT_SSH_COMMAND" => "ssh -o StrictHostKeyChecking=no -i '#{key_path}'"}]
end
defp fetch_repository(file_system) do
git_dir = FileSystem.Git.git_dir(file_system)
if File.exists?(git_dir) do
{:ok, git_dir}
else
{:error, "repository not found"}
end
end
defp with_ssh_key_file(file_system, fun) when is_function(fun, 1) do
File.mkdir_p!(Livebook.Config.tmp_path())
key_path = FileSystem.Git.key_path(file_system)
ssh_key =
file_system.key
|> String.replace(
~r/ (?!(?:OPENSSH |RSA |EC |DSA |PRIVATE )?(?:PRIVATE )?KEY-----)/,
"\n"
)
|> :public_key.pem_decode()
|> :public_key.pem_encode()
File.write!(key_path, ssh_key)
File.chmod!(key_path, 0o600)
result = fun.(key_path)
if File.exists?(key_path) do
File.rm!(key_path)
end
result
end
defp normalize_dir_path(<<"blob ", path::binary>>), do: "/" <> path
defp normalize_dir_path(<<"tree ", path::binary>>), do: "/" <> path <> "/"
defp relative_path("/"), do: "."
defp relative_path("/" <> path), do: path
defp relative_path(path), do: path
defp fetch_executable(program) do
if bin = System.find_executable(program) do
{:ok, bin}
else
{:error, "'#{program}' executable not found"}
end
end
end