I’m terrified to contemplate the amount of time I spend “increasing my productivity” rather than doing real work. 🙃
Dev experience is like… everything. Dev experience is IMO basically the same thing as “software engineering”. Or something. That’s a hill to die on for another time.
So I made a git cd
command to jump to repos…
Anyway, benefit from my shame and misery, with this handy shortcut. You can paste this or your own custom version into your ~/.zshrc. Jump below if you hate learning and just want the goods. 😁
The rest of this post documents what I did in case you want to customize.
git cd
First, this has to run as a function in your current shell. You can’t change cd
the current shell by calling a child script, as it runs in its own shell.
So the general structure of this is to front git
with a function.
# Where you want to complete to
project_dirs=("$HOME/src" "$HOME/ws")
git() {
if [[ $1 == "cd" ]]; then
# cd to subdirectory of project_dirs
else
# Fwd to git if not cd
command git "$@"
fi
end
And then we just plugin the cd magic:
project_dirs=("$HOME/src" "$HOME/ws")
git() {
if [[ $1 == "cd" ]]; then
for dir in "${project_dirs[@]}"; do
full_dir="$dir/$2/"
for repo_dir in $dir/*/; do
if [[ "$full_dir" == "$repo_dir" ]]; then
cd "$repo_dir" > /dev/null 2>&1
if [[ $? == 0 ]]; then
return 0
fi
fi
done
done
echo "No repo found matching $2"
return 1
else
command git "$@"
fi
}
This is the strictest possible matching.
$> ls -d ~/ws/*
dotfiles
hello-ltr
splainer_search
softwaredoug-blog
$> git cd dotfiles # works
$> git cd hello-ltr # works
$> git cd helloltr # DOES NOT work (no fuzzy match on `hello-ltr`)
$> git cd softwaredoug # DOES NOT work (no prefix match on `softwaredoug-blog` repo))
Fuzzier matching - ignore dashes, underscores, lowercase, etc
I like to remove dashes, underscores, and whitespace for matching. You can do this with zsh parameter substitution.
All the “squished” lines and checking are added. The param substitution syntax is pretty obscure. But you can see the - _
below and add your own characters in case you want to squish away them as well.
project_dirs=("$HOME/src" "$HOME/ws")
git() {
if [[ $1 == "cd" ]]; then
for dir in "${project_dirs[@]}"; do
squished_arg="$dir/$2/"
squished_arg="${(L)squished_arg//[- _]/}" # REMOVE -,_ and whitespace, lowercase (from arg)
squished_arg="${squished_arg%?}" # Remove trailing /
for repo_dir in $dir/*/; do
squished="${(L)repo_dir//[- _]/}" # REMOVE -,_ and whitespace, lowercase (from dir we're checking)
squished="${squished%?}" # Remove trailing /
if [[ "$squished" == "$squished_arg" ]]; then
cd "$repo_dir" > /dev/null 2>&1
if [[ $? == 0 ]]; then
return 0
fi
fi
done
done
echo "No repo found matching $2"
return 1
else
command git "$@"
fi
}
Trying it out…
$> ls -d ~/ws/*
dotfiles
hello-ltr
splainer_search
softwaredoug-blog
$> git cd dotfiles # works
$> git cd hello-ltr # works
$> git cd helloltr # works! (now we fuzzy match on `hello-ltr`)
$> git cd softwaredoug # DOES NOT work (no prefix match on `softwaredoug-blog` repo))
Adding in prefix matching
We can next add wildcard matching on the prefix as our final case. This is just an extra check after the exact match, using the * syntax for prefix checking.
project_dirs=("$HOME/src" "$HOME/ws")
git_cd_prefix() {
if [[ $1 == "cd" ]]; then
for dir in "${project_dirs[@]}"; do
squished_arg="$dir/$2/"
squished_arg="${(L)squished_arg//[- _]/}"
squished_arg="${squished_arg%?}"
for repo_dir in $dir/*/; do
squished="${(L)repo_dir//[- _]/}"
squished="${squished%?}"
if [[ "$squished" == "$squished_arg" ]]; then
cd "$repo_dir" > /dev/null 2>&1
if [[ $? == 0 ]]; then
return 0
fi
fi
# ADDED * checking
if [[ "$squished" == "$squished_arg"* ]]; then
cd "$repo_dir" > /dev/null 2>&1
if [[ $? == 0 ]]; then
return 0
fi
fi
done
done
echo "No repo found matching $2"
return 1
else
command git "$@"
fi
}
Now all our cases should work:
$> git cd dotfiles # works
$> git cd hello-ltr # works
$> git cd helloltr # works! (now we fuzzy match on `hello-ltr`)
$> git cd softwaredoug # works! (now matches on softwaredoug*)
Adding in tab completion
Finally, we can complete git cd
with a little bit of tab completion magic. This just loads the list of directories in these repos for later completion.
project_dirs=("$HOME/src" "$HOME/ws")
repos=()
all_repos() {
for dir in "${project_dirs[@]}"; do
for repo_dir in $dir/*/; do
echo `basename "$repo_dir"`
done
done
}
repos=($(all_repos))
_git_cd_completion() {
if [[ "$words[2]" == "cd" ]]; then
repos=($(all_repos)) # <- if you want to update the list on every completion
_arguments '*:repository:($(echo ${^repos}))'
else
_git
fi
}
compdef _git_cd_completion git cd
The whole shebang
Add all this to your .zshrc for nice cd
experience for your repos:
#git wrapper function to cd to root of repo
# git cd <repo> # exact repo name
# git cd <re...> # or prefix
project_dirs=("$HOME/src" "$HOME/ws")
repos=()
all_repos() {
for dir in "${project_dirs[@]}"; do
for repo_dir in $dir/*/; do
echo `basename "$repo_dir"`
done
done
}
repos=($(all_repos))
_git_cd_completion() {
if [[ "$words[2]" == "cd" ]]; then
repos=($(all_repos)) # <- if you want to update the list on every completion
_arguments '*:repository:($(echo ${^repos}))'
else
_git
fi
}
compdef _git_cd_completion git cd
git() {
if [[ $1 == "cd" ]]; then
for dir in "${project_dirs[@]}"; do
squished_arg="$dir/$2/"
squished_arg="${(L)squished_arg//[- _]/}"
squished_arg="${squished_arg%?}"
for repo_dir in $dir/*/; do
squished="${(L)repo_dir//[- _]/}"
squished="${squished%?}"
if [[ "$squished" == "$squished_arg" ]]; then
cd "$repo_dir" > /dev/null 2>&1
if [[ $? == 0 ]]; then
return 0
fi
fi
if [[ "$squished" == "$squished_arg"* ]]; then
cd "$repo_dir" > /dev/null 2>&1
if [[ $? == 0 ]]; then
return 0
fi
fi
done
done
echo "No repo found matching $2"
return 1
else
command git "$@"
fi
}
If you like this, check out my other git shortcuts that make my life oh so easier! 😁
Have Fun! -softwaredoug