Version controlling your .Rprofile, .gitconfig and other dotfiles.

Dotfiles are an important part of coding on Linux and macOS. In my work, I find myself not only working on my macOS laptop but on several Linux servers. Each of these requires dotfiles to configure my R (.Rprofile), git (.gitconfig and .gitignore_global), ssh (.ssh/config but not key files), vim (.vimrc) and shell (.zshrc, .bashrc, .bash_profile, etc.).

My current .Rprofile is focused on package development. It loads devtools automatically when using R interactively, and give defaults when creating new R packages:

if (interactive()) {
  suppressMessages(require(devtools))
}

options(
  usethis.full_name = "Rick M Tankard",
  usethis.description = list(
    `Authors@R` = 'person("Rick M", "Tankard", 
     email = "rickmtankard@gmail.com", role = c("aut", "cre"),
  comment = c(ORCID = "0000-0002-8847-9401"))',
  License = "MIT + file LICENSE",
  Version = "0.0.0.9000"
  ),
  usethis.protocol  = "ssh"
)

Many R users and package developers use git for version control. Why not extend that to your dotfiles? There are several motivations for version controlling dotfiles with git:

  • Backup. My shell dotfiles have been curated over many years of work and between institutions; I wouldn’t want to lose them because I change organisation.
  • Sync. There are settings I want to use regardless of the host device. Git helps to keep them in sync with a push command on one device, and a pull command on the others. Platform and host-specific commands are available in several dotfiles including .Rprofile. For others you can have specific dotfiles for each device.
  • Version control. It’s good to track what’s changed and makes it easier to diagnose problems as they arise.

Setup

Choose a location to store the git repository for your dotfiles. This doesn’t need to be, and probably shouldn’t be your home directory. I like to keep all my GitHub repositories in one place, with the end of the path matching the GitHub URL. For me, this directory is:

cd ~/Documents/git/github/trickytank/dotfiles

Create a git repository called dotfiles. You can do this on the terminal with the bash or zsh shell:

# Replace <PATH> with the location you wish to store the dotfiles git repository.
DOTFILESDIR=<PATH>/dotfiles
mkdir -p "$DOTFILESDIR"
cd "$DOTFILESDIR"
git init

Version controlling dotfiles

I suggest you create directories in this repository to correspond to hosts and host groups. My dotfiles repository has directories for specific hosts (mac3570 and hypatia), server groups such as supercomputers at Pawsey Supercomputing Centre and common dotfiles (server_common).

Here we are going to version control your .Rprofile. If you use multiple hosts, you can define host-specific R code to run (later in this post), so we can create our R profile into a common directory. For ease of use, I drop the dot from the dotfile name for version control

mkdir common

The following command will help you put your .Rprofile in the git repository, or create a file if Rprofile does not exist.

if test -f ~/.Rprofile; then
  if ! test -f ~/Rprofile_backup; then
    cp -n ~/.Rprofile ~/Rprofile_backup # create a backup copy of your R profile
    mv ~/.Rprofile common/Rprofile
    ln -s "$(pwd)/common/Rprofile" ~/.Rprofile
  else 
    echo "Error: ~/Rprofile_backup already exists"
  fi
else 
    touch common/Rprofile
    ln -s "$(pwd)/common/Rprofile" ~/.Rprofile
fi

If you already had an .Rprofile, this also creates a copy of your original .Rprofile to ~/Rprofile_backup (assuming it didn’t already exist). You may delete this backup after the tutorial.

You should check that common/Rprofile has been created. This command will show files in the common directory:

ls common

If so, it is time to add and commit your .Rprofile:

git add common/Rprofile
git commit -m "Added .Rprofile"

Next, create a new repository on GitHub or Gitlab and follow the command line instructions to push an existing repository from the command line.

Now, whenever you make changes to your .Rprofile (either by editing ~/.Rprofile or the file in your repository), you should commit the change and push. (edit <PATH_TO_REPOSITORY> and <SOMETHING>)

cd <PATH_TO_REPOSITORY>
git add common/Rprofile
git commit -m "Added <SOMETHING> to .Rprofile"
git push

Using your .Rprofile on another machine

This section describes how to use the same .Rprofile across multiple hosts or a new machine.

From GitHub or Gitlab, copy the clone URL. On GitHub you press the green ‘Code’ button and copy the HTTPS or SSH string. Pass this onto git clone, for example:

cd <DIRECTORY_TO_HOLD_dotfiles>
git clone https://github.com/<username>/dotfiles.git
cd dotfiles

You might already have an ~/.Rprofile on this machine. If so, merge the contents of this file with the git repository version. You may find the section Useful patterns for your .Rprofile later in the post handy when you want host-specific R commands.

if ! test -f ~/Rprofile_backup; then
  if test -f ~/.Rprofile; then
    mv ~/.Rprofile ~/Rprofile_backup
  fi
  ln -s "$(pwd)/common/Rprofile" ~/.Rprofile
else 
  echo "Error: ~/Rprofile_backup already exists"
fi

This once again creates the file ~/Rprofile_backup (and does nothing if it's already there), then creates a soft link ~/.Rprofile to the repository version of that file.

Now whenever change to your dotfiles on one machine, run git push in the dotfiles repository on that machine, and run git pull on every other machine.

Congratulations, your .Rprofile is version controlled and in sync across the devices you use.

Subsequent dotfiles can be added by moving the dotfile to your git repository, soft-linking to the file using an absolute path, and committing the dotfile, followed by a git push (and git pull on other machines).

Useful patterns for your .Rprofile

It may not be suitable for you to have the same R setup commands on all devices. You can selectively run commands on particular hosts by it’s “nodename” given by as.character(Sys.info()["nodename"]) or platform. For example, I could have commands specific to Pawsey’s zeus server by using:

if(as.character(Sys.info()["nodename"]) == "zeus") {
  # Insert hostname "zeus" specific startup code here
}

Or could specify multiple servers with:

if(as.character(Sys.info()["nodename"]) %in% c("zeus", "magnus", "linux500")) {
  # Insert specific startup code here for the given devices
}

You may also want commands specific to a platform such as Linux:

if(Sys.info()['sysname'] == "Linux") {
  # Insert Linux specific startup code here
}

For macOS, replace ‘Linux’ with ‘Darwin’.

Final words

Using version control on my dotfiles is something I’ve done for years and has allowed me to move between laptops and bring my precious dotfiles with me. In the past I’ve broken my dotfiles accidentally; version control made it easy to see what I’d changed on GitHub and fix the issue. I hope this can be useful to other R users out there too!

Dr. Rick Tankard
Dr. Rick Tankard
Freelance Statistician, Bioinformatician and Data Scientist

I am a Bioinformatician and Statistician based in Australia, working at WEHI.

Related