Make sapling work with direnv
In the last post, I shared my experience with Sapling, Meta’s new Git client. It requires some workaround with direnv. This post explains the change in details.
At work, we are using direnv for quickly managing environment variables on our repository. When we cd
into a directory, or a subtree, it automatically detects the file .envrc
in the current directory (or the nearest parent directory), loads environment variables, and execute predefined commands.
Our typical .envrc:
export PROJECT_ROOT=$(git rev-parse --show-toplevel)
export REL_PATH_FROM_PROJECT_ROOT=$(git rev-parse --show-prefix)
PATH_add $(pwd)
With this file, when we cd
to a subtree, it will:
- Set
PROJECT_ROOT
to the root of the git repository, of course, our monorepo. - Set
REL_PATH_FROM_PROJECT_ROOT
to the relative path from the root of the git repository. - Add the current directory to
PATH
. It allows executing commands in the current directory by typingcommand
instead of./command
. We usually define arun
command, to define some common commands for the current directory.
It works well. Until one day, I want to use sapling!
Make sapling work with direnv
The above .envrc
uses git rev-parse --show-top-level
to get the root of the git repository. Sapling is a new Git client, it works with Git repositories, but it’s not Git. It doesn’t have git rev-parse
command. So, we need to find a way to make it work with direnv.
Hence, I come up with this function:
sl_project_root() {
local pdir=./; # project directory
while [[ ${#pdir} -lt 30 ]] && ! ls "${pdir}.sl" >/dev/null 2>&1; do
pdir="${pdir}../"
done
if [[ ${#pdir} -lt 30 ]] ; then
# shellcheck disable=SC2155
export PROJECT_ROOT=$(realpath "${pdir}") ;
# shellcheck disable=SC2155
export REL_PATH_FROM_PROJECT_ROOT=\
$(python -c "import os; print(os.path.relpath(\"$(pwd)\",\"${pdir}\"))")
fi
}
if [[ -z "${PROJECT_ROOT}" ]]; then
sl_project_root
fi
The first part is to find the nearest .sl
directory, which is where sapling stores its local data. When found, the PROJECT_ROOT
will be set to the parent directory of .sl
.
The second part empowers Python to calculate the relative path from the root of the git repository, with os.path.relpath()
. Python is pre-installed on Mac and most Linux distributions, so it’s a good choice.
Put all things together
Another problem is that the above snippet is a bit long. And we have many .envrc
files around the monorepo. When the .envrc
file is changed, we need to update all of them. Other people is asked to run direnv allow
again in every directory with .envrc
. It’s the policy of direnv to make sure that the user is aware of the change. And what if we want to make change to the snippet in the future? We need to update all of them and make other people annoyed again.
Let’s fix it. So we’ll create a new file called .project_root.sh
with the above snippet. And people who want to use sapling just need to add the soft-link of the file to their $PATH
. Something like this:
# assume that ~/bin is included in $PATH
ln -s /path/to/repo/.project_root.sh ~/bin/.project_root.sh
Finally, we can update the .envrc
file to:
export PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
export REL_PATH_FROM_PROJECT_ROOT=$(git rev-parse --show-prefix 2>/dev/null)
git rev-parse 2>/dev/null || source .project_root.sh
PATH_add $(pwd)
The next time .project_root.sh
get updated, people don’t need to do anything as it will be automatically loaded. And only people with sapling will be affected. Other people don’t need to add the soft-link. Of course, it’s required that we trust the .project_root.sh
file. But it’s our repository anyway.
Read more: My first impressions of Sapling — Meta’s new Git client
Let's stay connected!
Author
I'm Oliver Nguyen. A software maker working mostly in Go and JavaScript. I enjoy learning and seeing a better version of myself each day. Occasionally spin off new open source projects. Share knowledge and thoughts during my journey. Connect with me on , , , , or subscribe to my posts.