Introduction
I keep my fish config synced between multiple machines. The configuration is stored in a GitHub repository, the concept known as dotfiles.
Problem
Fish shell lets you create a function on the fly and save it using one command (funcsave), which makes it available across other sessions (including future ones).
The functions defined this way are stored in ~/.config/fish/functions/.
For fish, I symlink the whole ~/.config/fish/ to ~/.dotfiles/fish/, which by default would result in all my funcsaved functions being seen by git.
This is not desirable since my machines have always ended up with some custom functions that I didn’t want synced.
Solution
I recently came up with a solution which is as easy to use as the built-in funcsave.
In fact, it’s based on the function -> funcsave workflow.
First, I added ~/.dotfiles/fish/functions/ to my .gitignore.
This way functions I define on the fly aren’t seen by git by default.
Then, I instructed fish to look for functions in an additional place: ~/.config/fish/stored-functions/.
I only did this for interactive sessions since I never use fish for scripting.
For convenience, I also created ~/.config/fish/stored-completions/ so I could have intelligent auto-completion for some of the saved functions.
# ~/.config/fish/config.fish
if status is-interactive
# Commands to run in interactive sessions can go here
set --prepend fish_function_path ~/.config/fish/stored-functions
set --prepend fish_complete_path ~/.config/fish/stored-completions
end
Next, I created a funcstore function.
The concept is to enhance the established function -> funcsave workflow in fish with a new final step: marking a function as one I want stored across machines.
It moves an existing fish function defined on the fly to the newly created ~/.config/fish/stored-functions/ directory.
With the function -> funcsave -> funcstore workflow, only selected functions are visible by git instead of this being the case for all functions by default.
# ~/.config/fish/stored-functions/funcstore.fish
function funcstore
if test (count $argv) -ne 1
echo "Usage: funcstore <function_name>"
echo "Moves a fish function to the stored-functions directory."
return 1
end
set fname $argv[1]
set src_path ~/.config/fish/functions/$fname.fish
set dest_path ~/.config/fish/stored-functions/$fname.fish
if not test -f $src_path
echo "Error: Function '$fname' does not exist in ~/.config/fish/functions/"
return 1
end
mv $src_path $dest_path
echo "funcstore: moved $src_path to $dest_path"
end
Finally, I created a completion function that suggests the functions I might want to store upon pressing tab when my prompt says funcstore.
# ~/.config/fish/stored-completions/funcstore.fish
function __fish_complete_funcstore
set -l func_dir ~/.config/fish/functions/
for file in $func_dir*.fish
echo (basename $file .fish)
end
end
complete -c funcstore -f -a "(__fish_complete_funcstore)" -d "Move a function to stored-functions"
Example
Let’s say I wrote a useful function that lets me SSH to a machine and create (or attach to an existing) tmux session with fish as $SHELL.
I called it ssht.
See how the workflow looked like:
> function ssht
ssh -t $argv "env SHELL=`which fish` tmux new-session -A -s mbiel"
end
> # The above created the function visible in this shell session only
> funcsave ssht
funcsave: wrote /home/mbiel/.config/fish/functions/ssht.fish
> # Now the function has been persisted on disk and is available in other sessions on this machine,
> # but still invisible to the git repository keeping track of my ~/.config/fish/
> # This is great if I don't want to track it with my other dotfiles
> funcstore ssht
funcstore: moved /home/mbiel/.config/fish/functions/ssht.fish to /home/mbiel/.config/fish/stored-functions/ssht.fish
> # Finally, the file with function definition has been moved to a directory which is not ignored by git
> # and I can stage and commit it.