Onboarding with Git

Problem

Large software projects can be tricky for newcomers. They often involve following lengthy articles and making numerous configuration changes to files in the codebase. As the code evolves, these articles may go out of date, resulting in frustration for anyone joining the project.

After making these configuration changes, the developer's working directory may be littered with modified files that must not be committed to the repository. This makes every commit and push a tiresome and risky affair, with the developer forced to stage each file and hunk manually by picking them out from a sea of configuration changes. Luckily, Git offers a solution to both of these problems.

Solution

Ignoring single files

Git offers a way to selectively ignore tracked files using the command:

git update-index --skip-worktree path/to/file

This command lets you modify a tracked file without reporting the changes through git status. Thus, if the only modified files are those marked with --skip-worktree, then git status reports that there are no local changes. This behaviour is also consistent with other Git commands such as git rebase or git checkout; these commands will not affect any files ignored using --skip-worktree.

Ignoring tracked files through --skip-worktree means that you can now use operations such as git add * without accidentally committing local configuration changes. However, it also means that you cannot commit any changes to a configuration file that should be committed. To commit changes to a configuration file, you will need to undo the --skip-worktree operation using the command:

git update-index --no-skip-worktree path/to/file

Ignoring multiple files

Typing git update-index --skip-worktree path/to/file for every single configuration file can get pretty exhausting. Fortunately, this can be resolved by chaining some Bash commands:

git ls-files -m | xargs -d "\n" git update-index --skip-worktree

The git ls-files -m command emits the list of modified files in your working tree. Each line emitted from git ls-files -m is then passed in as an argument by xargs to git update-index --skip-worktree. Due to the line endings emitted by git ls-files -m, the xargs command is configured to look for newlines ("\n") instead of the traditional null byte.

You can undo this operation using the following command:

git ls-files -v | grep ^S | cut -c3- | xargs -d "\n" git update-index --no-skip-worktree

This git ls-files -v command prints every file in the Git repository with a prefix indicating its status. Any file ignored using --skip-worktree is prefixed with S. Thus, grep ^S returns only the files marked with --skip-worktree. The cut -c3- command removes the S prefix, leaving us with a list of files that are passed into xargs -d "\n" git update-index --no-skip-worktree. This final command iterates over each file, marking each one with the --no-skip-worktree flag to allow Git to track local changes to these files.

Switching branches

When checking out different commits or branches, there is the possibility that one of your configuration files has been updated by some other commit. If this happens, Git will abort the checkout operation with the following message:

error: Your local changes to the following files would be overwritten by checkout:
      /path/to/foo
      /path/to/bar
Please commit your changes or stash them before you switch branches.
Aborting

If this occurs, use the following strategy to switch to the new branch:

Here is the above list translated into Git commands:

<ensure no local changes are present>
git ls-files -v | grep ^S | cut -c3- | xargs -d "\n" git update-index --no-skip-worktree
git stash
git checkout new-branch
git stash pop
<fix conflicts>
git ls-files -m | xargs -d "\n" git update-index --skip-worktree

Sharing configurations

The use of --skip-worktree makes it easy to generate configuration diffs that can be shared with other team members. Consider a new team member cloning the repository. You can generate a configuration for them using the following commands:

<ensure no local changes are present>
git fetch origin main
git checkout origin/main
git ls-files -v | grep ^S | cut -c3- | xargs -d "\n" git update-index --no-skip-worktree
git diff > newConfig.diff
git ls-files -m | xargs -d "\n" git update-index --skip-worktree
<edit newConfig.diff to change names, etc.>

Once newConfig.diff is ready, you can send this file to the new team member. They may then configure their repository using the following commands:

git apply newConfig.diff
git ls-files -m | xargs -d "\n" git update-index --skip-worktree

This technique for generating diffs also comes in handy when generating backup configuration files for yourself. If you ever lose or corrupt your configuration, you can restore your configuration using git apply config.diff.