Make direnv work with sapling
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 detail.
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 executes 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. And 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 changes. And what if we want to make changes 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 automatically load. 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
Also published at OliverNguyen.io/sapling.direnv
Want to Connect?
Connect with me twitter.com/@_OliverNguyen. I share information about
software development, JavaScript, Go, and other interesting things I learn.