This file lets you specify that git should "ignore" the diff from certain files. For instance, Node projects have a `package-lock.json` that is pure noise from a Git standpoint (it's just massive amounts of diff specifying specific versions of libraries, and the real human-readable version is in a separate `package.json` file).
With `.gitattributes` in the root of your project, you can just add a line:
`package-lock.json -diff`
Now, that file will still get staged/committed (which you want) ... but when you `git diff` you won't see the massive amounts of pointless diff in that file.
It shouldn't be noise. Don't update it if you're not intentionally trying to, otherwise you're exposing yourself to supply-chain risk for no reason. If you are regularly getting unexpected `package-lock.json` changes then you are doing something wrong.
I check the diff for uv.lock (Python counterpart of package-lock.json) every time I merge a PR. It is important to know which direct or transient dependencies have been updated. We don't blindly bump all dependencies to the latest versions (you shouldn't either).
pipdeptree --freeze
to see this clearly uv pip treeFor critical files like package-lock.json I'd also expect developers to explain why a library was added or a version was changed and the impact of the version change. The lack of such basic hygiene is why supply chain attacks are so common these days.
You could argue "those scripts are dumb then! outta my way!", but then you shouldn't be using a CLI for whatever it is you're trying to do. If you insist, you can just grep or use the --stat option.
We already know the git CLI has plenty of antifeatures like this. It is up to the devs how they want to proceed, but it doesn't change the fact that hiding things is a footgun.
I guess it's the norm in the software industry, but that's slightly irresponsible.
I could just not use those deps, but then I won’t be able to build anything interesting. The software industry has historically relied on being a high-trust society; I don’t know what will happen if that is changing.
Rewriting every dep with Fable for every project, maybe.
Every dependency adds risk, the goal is to minimise them. If you include a dependency for something that you would code in 20 lines, you should at least wonder if it's worth it or not. If that dependency pulls 5 transitive dependencies itself, probably you should go for the 20 lines.
As you say, sometimes it's impossible to track because there are so many dependencies (thanks to modern package managers that make it so easy). But at least you should see that your dependencies are completely out of control. If you ship an app where 95% of the code comes from dependencies you have never seen, you may as well have vibe-coded the app.
> The software industry has historically relied on being a high-trust society; I don’t know what will happen if that is changing.
I very much disagree with that. Most software is bad and shouldn't be trusted.
Even if that's true, you definitely do not want to attempt merge two lock files, and using the .gitattributes file to set the merge strategy is a good idea!
config.json merge=oursMaybe they do — I am not too deep in JS ecosystem — but that should be the basis of a true SBoM (generated, static artifact tied to a release build) and reproducible builds (able to regenerate byte-for-byte identical artifacts from actual source of truth which is your package.json).
I get what you're saying about it being line noise but when you need it you need it!
We've adapted: - our CI and git hooks so that our dependency or .lock files are visible when they change, and error if they change inconsistently - and our team procedures to confine dependency updates to dedicated commits
The idea being that when you see one of those "messy" .lock file changes...you were expecting it. If you see one and are annoyed by it (like OP) that's actually a waving red flag that a dependency changed.
> Use -diff to completely hide the internal file content during a diff. Git will only report `Binary files differ` if the file changes.
Same like you would binary files. It's still good advice to actually review the lockfile changes at some point.
You can also apparently write transformers to make it more human readable.
Every major npm attack I can think of essentially follows the pattern of "version X.Y.Z is secretly evil". How does seeing package@X.Y.Z in your lockfile alert you to that?
My general rule is that in-repo .gitignore should only be used for repo-specific things (build outputs, dependency folders, ...) and most user tools should be in their own user config.
In the future, I think I might just be less nice about it. I dunno.
You cant preempt every file or folder, but its almost no effort to catch the obvious ones.
You cant prompt every file or folder, but its almost no effort to catch the obvious ones.
It’s one of several tools a project can use to ensure quality, alongside eg linters and formatters. Automating those (in this case by defaulting to the expected outcome) reduces friction on basically every operation anyone might do in a project, in any context.
Through the lens of kindness, it benefits you as well as your team… and ultimately everyone else downstream, since you’re all not wasting time and cognitive load on trivially preventable mistakes.
Emphasis mine. This is the exact mindset I'm referring to, and when applied generally to files in the repo, will bite at some point. Even if you're lucky and it's unimportant/internal enough not to bite users, it will bite contributors. Luckily none of us would be discourteous enough to do this while contributing to another's repo.
In a perfect world everyone would add .DS_Store to their global ignore list but since this is not default it necessitates defensive measures.
If you don't add it to .gitignore then I can guarantee at some point a .DS_Store file will be committed to the repo, because the behavior is broken by default.
There is no meaningful penalty for it to be not up-to-date. There is only a benefit for people who come in when it's already configured, as they don't need to configure anything anymore.
(I say that but I'm using a global ignore too for eg ai configuration like skills as I like to half-ass them before discarding again)
I can set a creation script or volume to restore/persist configs if I must avoid gitignore. However, that's an extra script or devcontainer mounts config over a gitignore line.
---
That said, the easier change is still a one/two line bind mount that trying to exhaustively list ignored directories for every IDE or tool under the sun.
Corollary: My computer does make other kinds of files, and you’d never know it.
Of course, I could have used .git/info/exclude for that, and not risked accidentally adding my `scratch` directory with `git add -A` or something. So I (re-)learned something (which I'd known about but forgotten) today.
But as a reminder to anyone else who had forgotten this: .gitignore files are processed throughout the repo, not just at the top level. You can sprinkle them throughout the repo structure for finer-grained control, which may come in handy in some circumstances.
my dotfiles are a lot smaller at the root level taking advantage of the ~/.config/ for a lot more things.
the git exclude isn't used as much because it doesn't get committed to the repository so you'd have to recreate it each time you wanted to use it. that doesn't mean they're bad just why they are not used.
i find this better than putting all of ~/.config in git, since i don't necessarily want everything there to be version controlled.
video i learned this from: https://youtu.be/y6XCebnB9gs
gnu stow: https://www.gnu.org/software/stow/
attic
That way you can just create an attic directory in any project where you can keep random stuff that should never be committed. I’ve yet to find a repo which actually has such a directory checker in.Let's say you have a directory like attic; you can put inside a `attic/.gitignore`:
/**
& then that directory (and anything in it) is ignored, including the ignore file itself.I usually name my version of this directory the single character U+1F4A9, which HN refuses to permit me to put in a comment ;)
aux
and I hide it by putting a .gitignore in it that just contains am asterisk (*), nothing else, that way it ignores itself and anything in it.hasn't tripped me up (yet)
> For example, if you’re on macOS, adding .DS_Store here would be ideal.
As long as every Mac user on your project does. If you have more than one, it may be better off taken out of everyone's hands.
This file on my newer Mac is dated 2 days before I ordered it, and I don't remember setting any of this up, so I assume it came like this out of the box. I can't remember the dates for my older Mac, but I assume it's the same thing - and the macOS versions suggest that the default setup might have been like this for a while now.
So, perhaps the days of having to add .DS_Store/ to your .gitignore file are over!
Not only do people think that, they also think that every pet tool that every pet user might decide to use should also end up cluttering up .gitignores for every project on earth. Worse, these people have created whole templates for this, so they can start a new project with ignores for dozens of tools they don't even use. 9 out of 10 times, this includes a broken ignore for Vim swap files.
I think these people are crazy, and like you suggest, tooling that is particular to you should go in the user's ignore, and tooling particular to the project should go into the repo's ignore.
But I wouldn't want to deny anyone an opportunity to regularly rehash a narrow tribal complaint in the comments on a pull request. Yeesh.
They already answered your situation in their post.
https://en.wikipedia.org/wiki/Narcissism_of_small_difference...
Me, I am pragmatic. I have set this in my local config and I've added it to my repos to be certain. Because it's ten bytes.
I assume that’s why some open source maintainers don’t bother either - if you haven’t even looked at your diff before submitting then why should they?
Machine-wide configuration is called "system" in git, and generally lives under "/etc".
git-home/
company-project/ <-- git repo with main checked out
ephemera/ <-- my private repo
my-data-script.py
work/ <-- gitignored
company-project-feature-X/ <-- worktree on feature-X branch
company-project-feature-Y/ <-- worktree on feature-Y branch
This way, too, I can easily use the same ephemera scripts across multiple branches, or even multiple repos, concurrently.Debian build tooling places build artifacts in the parent directory on the assumption that this is acceptable, but it then surprises people since it's not the norm anywhere else.
Perhaps this ship has sailed. But I think it's worth pointing out that if you have an option, don't design things that place things inside the source tree if you can avoid it.
assume = update-index --assume-unchanged
unassume = update-index --no-assume-unchanged
assumed = "!git ls-files -v | grep ^h | cut -c 3-"
unassumeall = "!git assumed | xargs git update-index --no-assume-unchanged"
assumeall = "!git st -s | awk {'print $2'} | xargs git assume" > The ignore file lives in your machine’s home directory in ~/.config/git/ignore. Whatever filenames are added to this file are ignored globally at a machine-level.
The wording here is slightly wrong: ~/.config/git/ignore will ignore files per-user on the machine, not "at a machine level". And it's not "your machine's home directory", it's your user's home directory on that machine. Any other users on the same machine will not see this. Git calls this "global", as in "global for the user".Git config does also have a --system option which modifies the system-level config file, /etc/gitignore. You could probably ignore stuff at the system/machine level (hint: you don't want to), with this. I'd do something like:
$ sudo git config --system core.excludesFile /etc/gitignore
$ sudo touch /etc/gitignore
Note however that user config will override this, so any user who has a core.excludesFile setting will not also look at your system level excludes file. Which is a pretty big caveat.I just realized that I never even „asked“ myself if there might exist a better way than to clutter .gitignore with all kinds of specific excluded only relevant to me. I just accepted the world as it appeared to me…
And Today, it got s little bit better :-)
I see version control somewhat similarly as I see traffic laws. Sure, they could work in entirely different ways, Germans like their autobahns, but breaks to the norm in an otherwise planar field are rarely arguable for.
Ok, sometimes a more vivid and visually explanatory style would help, but here still Google is your friend for individual concepts.
One of the best resources there is. git is a hell of a tool. It looks simple but is so beautifully versatile without being complex or not deductive.
Asking aLlm is the new google
git is a hell of a tool. It looks simple but is so beautifully versatile without being complex
without being complex
Uh, what? Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 10 threads
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 1.43 KiB | 1.43 MiB/s, done.
Total 8 (delta 7), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (7/7), completed with 7 local objects.
don't you understand?!https://git-man-page-generator.lokaltog.net/
And each time someone quotes one there is a chance that an LLM will be trained on it.
I just can't ever be confident the command I write will do the thing I expect it to...
you may have a personal notes.txt file in a repository that you don’t want to check into git but you also don’t want to add to .gitignore because it’s unique to your workflow.
The exclude file lives in the .git directory of every Git repository but changes to it are not checked into Git
Wtf. I've always wanted this, and it was right there.Haha, same! I was literally looking for this feature last week without realising.
What could be more git- the problem is rarely that it can't do something, but the ergonomics of discovering how to make it work correctly.
git update-index --[no]-skip-worktree
for files that are already tracked. This can be useful for some local experimentation... it's just a bit annoying to use because it's not really surfaced anywhere by git (kinda). You need to remember that you set it; otherwise other operations like checkouts may be blocked.I'll fix it as soon as I'm in front of a computer. Happy Midsummer!
This never be considered as a solution. It only works on that PC, when working with a team, this approach is wrong in so many levels.
I host my own Forgejo server/repos, it is just me but .gitignore just makes more sense. It is on the root of the project, and I only have one file to manage. No matter that PC/device I am using, they are automatically covered.
So each person putting .vscode or .idea in their local config's ignore actually makes sense. (Relative to the nonsensical parent requirement... of course).
Set a global hooks dir, and then block binary files in pre-commit by using file or checking the git index
git config --global core.hooksPath ~/.config/git/hooks
Or block large changes, because binary mods are often larger than a real diff. [includeIf "hasconfig:remote.*.url:git@git.company.com:*/**"]
path = /home/dir/per/company/config
allows for remote specific configs, overriding say email or other required options depending on where you send contributions - without having to have per repo configworks for dir too
[includeIf "gitdir:/home/user/src/work1/"]
Git is REAL bitch about exact syntax here; the first snippet won't work with just :*, it needs :/* ; the second won't work without trailing slash