Using any cli tool from Elixir is easy, thanks to the System.cmd/3
function.
System.cmd("git", args, stderr_to_stdout: true)
The return of System.cmd/3
is a tuple with the output and the exit code.
Elixir has trained me to prefer an ok/error tuple. So, my first instinct was to
wrap and transform it. In addition to that, provide an actual function
git/2
that runs git commands instead of passing about a string.
defmodule Kit.Cmd do
@moduledoc """
Provides a set of functions for interacting with the command line.
"""
defp run_cmd(name, args, opts) do
case System.cmd(name, args, opts) do
{output, 0} -> {:ok, output}
{output, exit_code} -> {:error, {exit_code, output}}
end
end
def git(args, opts \\ [])
def git(args, opts) when is_list(args) do
run_cmd("git", args, opts)
end
def git(subcmd, opts) when is_binary(subcmd) do
git([subcmd], opts)
end
end
From here, you have the full power of Git at your disposal - which is a lot. Entire libraries can and have been written around specific use cases because now it all comes down to properly parsing the output. If you have something specific in mind it can be fun at this point to wander over to hex.pm and search packages related to git. It’s like shopping for nerds.
I want to demonstrate two examples of questionable uses. First up, get the last modified date time of a file according to your git history. Without getting into the intricacies of the git cli, this might look something like this:
git log -1 --format=%ct -- mix.exs
1747449071
There are usually more than one way to do the something in git’s myriad of
commands but we are going to run with that. Turning the cli args into an array
gives us a handy way to run the command in our git
function.
["log", "-1", "--format=%ct", "--", "mix.exs"]
Expanding on that, lets create a last_modified/1
function that takes a path
and returns the unix timestamp, all parsed to something usable.
defmodule Kit.Cmd.Git do
@moduledoc """
Provides Git-related commands.
"""
import Kit.Cmd, only: [git: 2]
def last_modified(path) do
args = ["log", "-1", "--format=%ct", "--", path]
case git(args, stderr_to_stdout: true) do
{:ok, ts} ->
{
:ok,
ts
|> String.trim()
|> String.to_integer()
|> DateTime.from_unix!()
|> DateTime.to_naive()
}
{:error, _} ->
{:error, nil}
end
end
def last_modified!(path) do
case last_modified(path) do
{:ok, ts} -> ts
{:error, _} -> raise "Failed to get last modified time for #{path}"
end
end
end
Sweet. What you can do with git is quite vast. I will try to write up another use case in another post.