217

How can binary files be ignored in git using the .gitignore file?

Example:

$ g++ hello.c -o hello

The "hello" file is a binary file. Can git ignore this file ?

belteshazzar
  • 2,163
  • 2
  • 21
  • 30
JuanPablo
  • 23,792
  • 39
  • 118
  • 164
  • 1
    possible duplicate of [How do I add files without dots in them (all extension-less files) to the gitignore file?](http://stackoverflow.com/questions/19023550/how-do-i-add-files-without-dots-in-them-all-extension-less-files-to-the-gitign) – Mad Physicist Jul 09 '15 at 21:13
  • 7
    I'm very surprised such an old and important question has no proper answer. I'm even more surprised that the answer is a straight forward `[^\.]*`. – TamaMcGlinn Oct 03 '18 at 15:13
  • this doesn't work – timotheecour Apr 23 '20 at 06:36
  • 1
    You should probably build to seperate directory than your source files and gitignore the build directory. – George Jul 16 '21 at 08:51

18 Answers18

205
# Ignore all
*

# Unignore all with extensions
!*.*

# Unignore all dirs
!*/

### Above combination will ignore all files without extension ###

# Ignore files with extension `.class` & `.sm`
*.class
*.sm

# Ignore `bin` dir
bin/
# or
*/bin/*

# Unignore all `.jar` in `bin` dir
!*/bin/*.jar

# Ignore all `library.jar` in `bin` dir
*/bin/library.jar

# Ignore a file with extension
relative/path/to/dir/filename.extension

# Ignore a file without extension
relative/path/to/dir/anotherfile
VenomVendor
  • 15,064
  • 13
  • 65
  • 96
  • 1
    This solution works like a charm! I don't understand why by unignore all dirs "!*/", it can also unignore subdir's files with an extension? (e.g. aaa/bbb.c) but still ignore subdir's file without extensions. (e.g. aaa/ccc) – dragonxlwang Nov 14 '15 at 21:21
  • 4
    Seems that this method does not work as expected after I found that when there is multiple layers of directory... – dragonxlwang Nov 14 '15 at 22:02
  • 1
    @dragonxlwang I'm curious where would this not work? The accepted solution here https://stackoverflow.com/a/19023985/1426932 is slightly different, and uses `!/**/` instead of `!*/` ; which one is correct? /cc @VonC – timotheecour Jan 25 '19 at 10:17
  • It is fairly common in Unix culture to name shell scripts as well as binary executables without extensions, in which case this solution will cause scripts to be ignored. A better idea is to just add binary executables to .gitignore manually whenever they are added to the project - it typically doesn't happen all that often. If that is too cumbersome, then the makefile solution suggested by vedantk is better. – Peter Helfer Feb 14 '19 at 19:32
  • 7
    This will ignore makefile – VMatrix1900 Feb 13 '20 at 01:36
  • this will make a global gitignore useless – timotheecour Apr 23 '20 at 06:36
  • this is what I do. this way I don't remove all files without extensions since there can be those that I don't want to remove. https://stackoverflow.com/a/70531507/7277094 – Mahi Jan 25 '22 at 08:12
  • I've already seen this.. https://gist.github.com/chichunchen/970a7e97c74a253a4503 Also is there a way to have it only once in the project root and not in every folder? – tuxErrante Apr 14 '22 at 13:55
49

Add something like

*.o

in the .gitignore file and place it at the root of your repo ( or you can place in any sub directory you want - it will apply from that level on ) and check it in.

Edit:

For binaries with no extension, you are better off placing them in bin/ or some other folder. Afterall there is no ignore based on content-type.

You can try

*
!*.*

but that is not foolproof.

manojlds
  • 290,304
  • 63
  • 469
  • 417
  • 1
    Added edit. Is there any reason you don't want your binary to have an extension – manojlds Apr 19 '11 at 03:20
  • 12
    Executables often do not have extensions. I'm trying to do the same thing here for files created by `gcc` passing `-o $@`. – Nathan Lilienthal Sep 11 '13 at 16:27
  • `*` ignores everything even directories, so `!*.*` will not unignore within directories, so add `!*/` to .gitignore in order to add directories, then `!*.* will also unignore files in directories with extensions – Isaac Weingarten Dec 14 '21 at 21:10
38

To append all executables to your .gitignore (which you probably mean by "binary file" judging from your question), you can use

find . -executable -type f >>.gitignore

If you don't care about ordering of lines in your .gitignore, you could also update your .gitignore with the following command which also removes duplicates and keeps alphabetic ordering intact.

T=$(mktemp); (cat .gitignore; find . -executable -type f | sed -e 's%^\./%%') | sort | uniq >$T; mv $T .gitignore

Note, that you cannot pipe output directly to .gitignore, because that would truncate the file before cat opens it for reading. Also, you might want to add \! -regex '.*/.*/.*' as an option to find if you do not want to include executable files in subdirectories.

icks
  • 481
  • 4
  • 4
  • A possible update to this answer could be `find . -executable -type f ! -name "*.*"` to only get the executables without an extension. – kellekai Jan 04 '22 at 08:00
27

Binary files are often without extensions. If this is your case try this:

*
!/**/
!*.*

REF: https://stackoverflow.com/a/19023985/1060487

mattdlockyer
  • 6,984
  • 4
  • 40
  • 44
27

Your best bet with binaries is to either give them an extension that you can easily filter out with a standard pattern, or put them into directories that you can filter out at the directory level.

The extension suggestion is more applicable in Windows, because extensions are standard and basically required, but in Unix, you may or may not use extensions on your executable binaries. In this case, you can put them in a bin/ folder, and add bin/ to your .gitignore.

In your very specific, small-scope example, you can just put hello in your .gitignore.

Andy White
  • 86,444
  • 48
  • 176
  • 211
16

You may try in your .gitignore:

*
!*.c

This approach has many disadvantages, but it's acceptable for small projects.

Andrei Beliankou
  • 684
  • 7
  • 17
  • 5
    It would be nice if you least list the major disadvantages – pjvds Apr 01 '14 at 16:18
  • 1
    The obvious disadvantage is the order of allow-deny rules, the proper way is to ignore only undesired files, not to disallow all and then to include only wished files. – Andrei Beliankou Apr 03 '14 at 15:00
12

If you're using a makefile, you could try modifying your make rules to append the names of new binaries to your .gitignore file.

Here's an example Makefile for a small Haskell project;

all: $(patsubst %.hs, %, $(wildcard *.hs))

%: %.hs
    ghc $^
    grep -xq "$@" .gitignore || echo $@ >> .gitignore

This makefile defines a rule for creating executables out of Haskell code. After ghc is invoked, we check the .gitignore to see if the binary is already in it. If it isn't, we append the name of the binary to the file.

vedantk
  • 356
  • 3
  • 5
11

The most specific gitignore line is normally the best, so that you don't accidentally hide files from git, which causes hard-to-diagnose problems when someone else checks out your commit. Hence, I recommend specifically gitignoring only the file called hello in the root of your directory. To do that, add:

/hello

to the .gitignore file in the root of your repository. (It is also possible to have .gitignore files elsewhere in the repository, and this makes sense when your git repository contains multiple projects, which you might want to move around later, or split off into their own repository one day.)

However, if you really want to ignore all files that have no extension, you could use:

/[^\.]*

or even less specific:

[^\.]*

Explanation:

/  starting with / means "only in the root of the repository"
[] encloses a character class, e.g. [a-zA-Z] means "any letter".
^  means "not"
\. means a literal dot - without the backslash . means "any character"
*  means "any number of these characters"
TamaMcGlinn
  • 2,840
  • 23
  • 34
  • 1
    This solutions also ignores files with extensions like `test.txt` – Viesturs Jun 15 '21 at 09:09
  • ^ means beginning of the line in unix. Is it different in gitignore? – George Jul 16 '21 at 09:02
  • yes, there's no 'beginning of line' really, since this is a list of file/directory paths. '/' is kind of the equivalent, being the path of the .gitignore file in question. Note that you can have a .gitignore file in how ever many directories inside a git repo you want, and this is also a good idea, since moving that directory will no longer require modification of other .gitignores. – TamaMcGlinn Jul 16 '21 at 09:10
  • I agree with @Viesturs - this doesn't really work. It excludes all files except those that start with a period. A true one-line regex answer would probably require a negative lookahead. But since the answers from VenomVendor and mattdlockyer work just fine, I personally don't feel like devoting the effort to figuring that out! – sfarbota Jul 03 '22 at 23:07
  • And @George - `^` normally means beginning of line in regular expressions, but when it's inside of `[square brackets]`, it means "not" instead (similar to a `!` symbol in many programming languages). – sfarbota Jul 03 '22 at 23:10
6

Here's another solution using file. This way executable scripts will not end up in gitignore. You may need to change how the output from file is interpreted to match your system. One could then set up a pre-commit hook to call this script each time you commit.

import subprocess, os

git_root = subprocess.check_output(['git', 'root']).decode("UTF-8").strip()
exes = []
cut = len(git_root)

for root, dirnames, filenames in os.walk(git_root+"/src/"):
  for fname in filenames:
    f = os.path.join(root,fname)
    if not os.access(f,os.X_OK):
      continue

    ft = subprocess.check_output(['file', f]).decode("UTF-8")

    if 'ELF' in ft and 'executable' in ft:
      exes.append(f[cut:])

gifiles = [ str.strip(a) for a in open(git_root + "/.gitignore").readlines() ]
gitignore=frozenset(exes+gifiles)

with open(git_root+"/.gitignore", "w") as g:
  for a in sorted(gitignore):
    print(a, file=g)
Tyler Earnest
  • 61
  • 1
  • 2
  • I made a similar script, and posted on a duplicate question: http://stackoverflow.com/a/28258619/218294 Your code is nicer :) mine probably runs faster as it runs "file" only once, or a few times (using xargs). – Sam Watkins Feb 01 '15 at 01:59
6

If you follow these commands on your .gitignore file and files still seems to appear you may want to try:

git rm --cached FILENAME

After that, add your .gitignore, commit and push. Took me 40 minutes to understand that, hope this helps to newbies like me

Shaun Peretz
  • 73
  • 2
  • 6
4

A way to also ignore in some subdir, not only in a root:

# Ignore everything in a root
/*
# But not files with extension located in a root
!/*.*
# And not my subdir (by name)
!/subdir/
# Ignore everything inside my subdir on any level below
/subdir/**/*
# A bit of magic, removing last slash or changing combination with previous line
# fails everything. Though very possibly it just says not to ignore sub-sub-dirs.
!/subdir/**/
# ...Also excluding (grand-)children files having extension on any level
# below subdir
!/subdir/**/*.*

Or, if you want to include only some specific types of files:

/*
!/*.c
!/*.h
!/subdir/
/subdir/**/*
!/subdir/**/
!/subdir/**/*.c
!/subdir/**/*.h

Seems it may even also work like for every new subdirectory if you want!:

/*
!/*.c
!/*.h
!/*/
/*/**/*
!/*/**/
!/*/**/*.c
!/*/**/*.h

Leading slashes are important only in first two lines and optional in other. Tailing slash in !/*/ and !/subdir/ is also optional, but only in this line.

shaman.sir
  • 3,198
  • 3
  • 28
  • 36
2

Building on VenomVendors answer

# Ignore all
*

# Unignore all files with extensions recursively
!**/*.*

# Unignore Makefiles recursively
!**/Makefile

# other .gitignore rules...
Eero Aaltonen
  • 4,239
  • 1
  • 29
  • 41
2

Old thread, but still relevant. I changed the makefile so the resulting binary file after linking has the name [filname].bin instead of only [filname]. Then I added *.bin files in the gitignore.
This routine fulfill my needs.

1

Just add hello or /hello to your .gitignore. Either works.

Travis Reeder
  • 38,611
  • 12
  • 87
  • 87
1

The .gitignore mechanism works only based on file names, not on file contents. Being a binary file is a property of the content, hence you can't ask git ignore binary files directly, but only to ignore them by name (and as other suggested, you can either add all binary file names to your .gitignore or use an appropriate naming convention).

The fact that .gitignore works on file names is an important property performance-wise: Git only needs to list files, but not to open and read them to know which files to ignore. In other words, Git would be terribly slow if you could ask it to ignore files based on their contents.

Matthieu Moy
  • 15,151
  • 5
  • 38
  • 65
1

I don't know any other solution but adding them one by one to .gitignore.

A crude way to test is to grep the file command's output:

find . \( ! -regex '.*/\..*' \) -type f | xargs -n 1 file | egrep "ASCII|text"

EDIT

Why don't you simply name you executable hello.bin?

muhuk
  • 15,777
  • 9
  • 59
  • 98
0

I created a .gitignore file with two entries in GOPATH directory.

/bin
/pkg

It ignore all the compiled developments, currently.

gszecsenyi
  • 93
  • 9
0

.gitignore uses glob programming to filter files, at least on Linux.

I am about to give a coding talk at a Meetup and, in preparation, I made a directory with several subdirectories that are named according to the order I want to present them: 01_subject1, 02_subject2, 03_subject3. Each subdirectory contains a source file with a language-dependent extension that compiles to an executable file whose name matches the source file name without the extension according to common practice.

I exclude the compiled files in the numeral-prefixed directories with the following .gitignore line:

[0-9][0-9]_*/[!\.]*

According to my understanding of the documentation, it shouldn't work. Having the trailing asterisk should fail because it should match any number of unspecified characters, including the '.' + extension. Omitting the trailing asterisk should fail (and does) because [!\.] matches only a single non-period character. However, I added the trailing asterisk, as I would for a regular expression, and it works. By work, I mean that git notices changes to the source file, but not the existence or changes to the compiled files.

chuckj
  • 135
  • 1
  • 12