30

I want to ignore certain files within a branch without having to rely on a tracked .gitignore file that will be overwritten during merges with other branches.

I’ve closely followed a Stack Overflow answer along with the linked blog post, but my repo doesn’t seem to be recognizing the specified excludesfile in my .git/config. The Git documentation (git-config, gitignore) doesn’t seem to show how to specify and address an excludes file for a specific branch.

My .git/config looks like this:

[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
        precomposeunicode = true
        excludesfile = +/info/exclude

[branch "myspecialbranch"]
        excludesfile = +/info/exclude_specialbranch

My .git/info/exclude file looks like this (by default, I didn’t touch it):

# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store

I don’t have a .gitignore file in my “specialbranch”.

If I try to ignore a file like info.php, my .git/info/exclude_specialbranch file looks like this:

info.php

… but it doesn’t ignore the file.

If I run git status --ignored, it only lists the .DS_Store file from the default exclude file.

However, if I add info.php to my .git/info/exclude file:

# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store
info.php

it ignores the file perfectly.

Note that it still works without the line excludesfile = +/info/exclude under [core] in .git/config. Git seems to know where the system-wide exclude is without me having to tell it.

I have a feeling that .git/config isn’t recognizing the address of my custom excludes file:

excludesfile = +/info/exclude_specialbranch

… most likely because of the use of + to describe the location of the .git directory.

What’s the correct method for addressing custom exclude files in the (more recent) versions of Git?

I’m running OSX 10.9.5 and Git version 2.2.1.

Community
  • 1
  • 1
goredwards
  • 2,486
  • 2
  • 30
  • 40

2 Answers2

40

Git does not support per-branch excludes files

You’re trying to achieve something that Git does not support. The blog post is the original source of this hoax that the Stack Overflow answer only parroted. As noted in comments under that answer, even the original blog post contains a discussion that brings out that the solution does not work and it links to a newer blog post that mentions that even the author is unable to reproduce the behavior.

Why it does not work? If you read man gitignore and man git-config, you’ll find just core.excludesfile referenced. No branch.<name>.excludesfile there. The core.excludesfile is meant to enable you to exclude e.g. Vim .swp files or other temporary stuff your software uses.

core.excludesfile

In addition to .gitignore (per-directory) and .git/info/exclude, Git looks into this file for patterns of files which are not meant to be tracked. "~/" is expanded to the value of $HOME and "~user/" to the specified user’s home directory. Its default value is $XDG_CONFIG_HOME/git/ignore. If $XDG_CONFIG_HOME is either not set or empty, $HOME/.config/git/ignore is used instead. See gitignore(5).

Workaround

I believe that the best approximation of a per-branch excludes file is achieved using the post-checkout hook and that rewrites the contents of .gitignore in the working tree.*

Each branch would have e.g. a .gitignores directory with files named after the corresponding branches. Then there would be a .gitignores/__default file that would be used by default. The .gitignore would be excluded by all the excludes files and would be created by the post-checkout hook as a copy of the corresponding file in .gitignores.

If you don’t want to track the excludes files, you may do the same with .git/info/exclude file as a copy of .git/info/excludes/__default etc.

*: Side note: I used to recommend making .gitignore a symlink updated by the hook to point to the right content, but .gitignore cannot be symlink since Git v2.32.0. Now, when editing the .gitignore, the changes are lost when switching branches, instead of being made to the branch's custom excludes file.

Palec
  • 12,743
  • 8
  • 69
  • 138
  • If you want more info on the work-around, feel free to ping me via @Palec in the comments here. – Palec Apr 11 '15 at 22:54
  • ha thanks - i just finished reading the entire http://git-scm.com/docs/git-config (!) and was wondering why i didn't see a branch.excludesfile - i'll check out your work-around. – goredwards Apr 11 '15 at 23:40
  • I checked the history of git-config and gitignore manpages and they never mentioned `branch..excludesfile`. – Palec Apr 12 '15 at 02:03
  • ok thanks - so it looks like it was _never_ possible... good to know. – goredwards Apr 12 '15 at 02:47
  • 3
    Actually, I’m not 100% sure yet that it never worked. I’m just sure it was never documented. What puzzles me it the high number of upvotes on the answer you tried to follow. I downvoted it, upvoted the comment that says it doesn’t work, and I recommend anyone who reads this to do the same. – Palec Apr 12 '15 at 09:48
  • `git stash -u -a` seems to be a usable workaround for me, but it's tedious when the directories to ignore contain some gigabytes. – Murphy Sep 01 '16 at 15:09
  • I need the same functionality, but for separate `changelog-[branch].log`. @Palec, I have never created a post-checkout hook. And from your answer it is not clear if the process you propose, is automated by `git` itself or we need to use a script (like in the [other](https://stackoverflow.com/a/48696220/3408342))? I need this to be automated in both Linux and Windows (as we used both of these OSes for development). – tukusejssirs Nov 12 '19 at 11:50
  • 2
    Hooks are separate executables or scripts, Git does not create them. I'll probably never write such a script as I don't need that functionality and it's not trivial. If you encounter any issues with implementing it yourself and don't find an answer, you may post a new question about it. @tukusejssirs – Palec Nov 12 '19 at 13:02
  • Someone wrote this post-checkout hook: https://gist.github.com/wizioo/c89847c7894ede628071 I have not tried this myself. – Joe Casadonte Jan 16 '21 at 14:54
  • @Palec About the high number of upvotes - I upvoted it as it seemed like a simple and easy-to-understand solution to my problem - then when I tried to remove my upvote, I was told that my vote was locked in. Maybe clicking on the "up" arrow a second time doesn't remove the vote, and I'm not the only one who tried to un-upvote. – AJM Oct 28 '22 at 13:43
  • I think the proposed workaround doesn't work any more. After 2.32, git no longer allows .gitignore to be a symlink (https://github.com/git/git/blob/master/Documentation/RelNotes/2.32.0.txt) – Jasancos Dec 02 '22 at 11:34
  • 1
    Thanks for the heads up, @Jasancos! Updated my answer so that the workaround works even after the change. By using a copy instead of a symlink, it loses some of the convenience, but it should work. – Palec Dec 02 '22 at 12:07
3

Here's a script I wrote to do this:

#!/bin/bash                                                                      

# This is designed to allow per-branch un-ignoring of certain files.
# Use case: Compiled CSS files on master to push to server.
# A special .gitignore file will be present on master at
# {$gitignorePath}/{$disabledMasterGitignoreName} and that file should 
# enable the css files using the ! flag. @https://git-scm.com/docs/gitignore

# When the branch specified by script parameters
# is checked out, {$gitignorePath}/{$disabledMasterGitignoreName} is 
# copied to .gitignore. 
# On other branches this gitignore file is named $disabledMasterGitignoreName, versioned,
# and {$gitignorePath}.gitignore will be deleted. Note, you must ignore 
# {$gitignorePath}.gitignore from your main .gitignore file
#
# To enable put this file in your path and call this script
# in .git/hooks/post-checkout with pass a list of single-space-separated branches that
# will enable the branch-specific .gitignore.

# One caveat is that you can only merge into the branch containing the .gitignore
# file. Otherwise you'll end up re-committing the files. This is fine if you are
# using gitflow and `master` contains your special .gitigore using the ! syntax
# that is un-ignoring files.
#
# Background: @http://stackoverflow.com/questions/29579546/git-excludesfile-for-a-branch

set -e                                                                           

gitignorePath='docroot/sites/all/themes'
disabledMasterGitignoreName='.gitignore_master--disabled'
#branchWithGitignoreEnabled='master'

branch=$(git rev-parse --abbrev-ref HEAD)

gitignoreRoot="$(git rev-parse --show-toplevel)/${gitignorePath}"

if [ -f "${gitignorePath}/.gitignore" ]
then
    masterGitignoreExists=true
fi

if [ -f "${gitignorePath}/${disabledMasterGitignoreName}" ]
then
    disabledMasterGitignoreExists=true
fi

IFS=' ' read -a params <<< "$@"

if [[ " ${params[@]} " =~ " ${branch} " ]]
then
  if [ $disabledMasterGitignoreExists ]
  then
    cp -f "${gitignoreRoot}/${disabledMasterGitignoreName}" "${gitignoreRoot}/.gitignore"
    echo "Enabled ${gitignorePath}/.gitignore"
  fi
elif [ $masterGitignoreExists ]
then
    rm -f "${gitignorePath}/.gitignore"
    if [ masterGitignoreExists ]
    then
      echo "Disabled ${gitignorePath}/.gitignore"
    fi
fi
Jeremy John
  • 13,686
  • 2
  • 16
  • 16