defmodule GitStreet do @moduledoc """ Documentation for GitStreet. """ @doc """ Entrypoint for further working with existing git repository. ## Examples iex> GitStreet.open %GitStreet.Repo{path: "/current/directory/path/.git"} iex> GitStreet.open("/user/repo/path") %GitStreet.Repo{path: "/user/repo/path/.git"} """ @spec open(Path.t()) :: %GitStreet.Repo{} | {:error, binary} def open, do: open(File.cwd!) def open(path) when is_binary(path) do if File.exists?("#{path}/.git"), do: %GitStreet.Repo{path: path}, else: {:error, "invalid path"} end def open(_path), do: {:error, "invalid argument"} @doc """ Shows the commit logs. You can set `limit` option and function returns limited output of commits. ## Examples iex> GitStreet.log(%GitStreet.Repo{path: "/user/repo/path"}) [%{ author_name: "Author Name", author_email: "author.name@gmail.com", created_at: "Tue Feb 4 14:13:37 2020 +0200", hash: "fa6ff4e6b12b90b035671d1eb44a7762191c175c", message: "commit message" }, %{ .... }] iex> GitStreet.log(%GitStreet.Repo{path: "/user/repo/path"}, limit: 1) [%{ author_name: "Author Name", author_email: "author.name@gmail.com", created_at: "Tue Feb 4 14:13:37 2020 +0200", hash: "fa6ff4e6b12b90b035671d1eb44a7762191c175c", message: "commit message" }] """ @spec log(%GitStreet.Repo{}, keyword) :: list(map) def log(repository, options \\ []) def log(%GitStreet.Repo{path: path}, options) do limit = Keyword.get(options, :limit, -1) {string_of_commits, _} = System.cmd("git", ["log", "-n #{limit}", "--pretty=%H /-/ %an /-/ %ae /-/ %at /-/ %s"], cd: path) string_of_commits |> String.split("\n", trim: true) |> Enum.map(fn commit -> [hash, name, email, date, message] = String.split(commit, " /-/ ") {microseconds, _} = Integer.parse(date) %{hash: hash, author_name: name, author_email: email, created_at: DateTime.from_unix!(microseconds), message: message} end) end def log(_path, _options), do: {:error, "invalid argument"} @doc """ Clone a repository into a new directory. You can set `path` option and set the name of a new directory to clone into. ## Examples iex> GitStreet.clone("git@provider.com:repo_name.git") %GitStreet.Repo{path: "/current/directory/path/repo_name/.git"} iex> GitStreet.clone("git@provider.com:repo_name.git", path: "/users/directory/path") %GitStreet.Repo{path: "/users/directory/path/repo_name/.git"} iex> GitStreet.clone("git@provider.com:repo_name.git", path: "/users/directory/path", folder_name: "some_folder") %GitStreet.Repo{path: "/users/directory/path/some_folder/.git"} """ @spec clone(String.t, keyword) :: String.t def clone(link, options \\ []) def clone(link, options) when is_binary(link) do project_name = Regex.run(~r/^(https|git)(:\/\/|@)([^\/:]+)[\/:]([^\/:]+)\/(.+).git$/, link) |> List.last path = Keyword.get(options, :path, File.cwd!) folder_name = Keyword.get(options, :folder_name, project_name) project_path = "#{path}/#{folder_name}" System.cmd("git", ["clone", link, project_path, "-q"]) %GitStreet.Repo{path: project_path} end def clone(_link, _options), do: {:error, "invalid argument"} @doc """ List of branches ## Examples iex> GitStreet.branch(%GitStreet.Repo{path: "/user/repo/path"}) ["master", "release", "feature/123"] """ @spec branch(%GitStreet.Repo{}) :: list def branch(%GitStreet.Repo{path: path}) do System.cmd("git", ["remote", "set-head", "origin", "-d"], cd: path) {string_of_branches, _} = System.cmd("git", ["branch", "-rl"], cd: path) string_of_branches |> String.split("\n", trim: true) |> Enum.map(fn branch -> [_, branch_name] = Regex.run(~r/origin\/(.*)/, branch) branch_name end) end def branch(_path), do: {:error, "invalid argument"} @doc """ Switch branches or restore working tree files ## Examples iex> GitStreet.checkout(%GitStreet.Repo{path: "/user/repo/path"}, "my_branch") {:ok, "Switched to branch my_branch"} iex> GitStreet.checkout(%GitStreet.Repo{path: "/user/repo/path"}, "nonexistent_branch") {:ok, "Couldn`t switch to nonexistent_branch"} """ @spec checkout(%GitStreet.Repo{}, String.t) :: tuple def checkout(%GitStreet.Repo{path: path}, branch_name) when is_binary(branch_name) do {result, _} = System.cmd("git", ["checkout", branch_name], stderr_to_stdout: true, cd: path) case String.match?(result, ~r/^error/) do true -> {:error, "Couldn`t switch to #{branch_name}"} false -> {:ok, "Switched to branch #{branch_name}"} end end def checkout(_path, _branch_name), do: {:error, "invalid argument"} @doc """ List of branches ## Examples iex> GitStreet.ls_remote("git@gitlab.com:codealchemyco/git_street.git") {:ok, [%{hash: "81499657cd4b154ef17e9684fcc596d408d26457", reference: "refs/heads/master"}]} iex> GitStreet.ls_remote("invalid_url") {:error, "Not found remote repository"} """ @spec ls_remote(String.t) :: tuple def ls_remote(url) when is_binary(url) do {result, code} = System.cmd("git", ["ls-remote", "--exit-code", "-h", url]) if code == 0, do: {:ok, list_references(result)}, else: {:error, "Not found remote repository"} end def ls_remote(_url), do: {:error, "invalid argument"} defp list_references(result) do result |> String.split("\n", trim: true) |> Enum.map(fn record -> [hash, reference] = String.split(record, "\t") %{hash: hash, reference: reference} end) end end