We should stop project-root pollution

· 12min · Giovanni Carvalho

Not too long ago people realized that enough is enough and that programs should not be writing stuff directly to $HOME as they please. The battle is far from over, as many programs still hard-code their settings path, but the XDG Base Directory Spec has gained enough support to at least enable tools that automate the cleanup process.

What about our repositories, though?

A quick glance at BigQuery's public datasets for GitHub data shows that many files at the project root are just configuration for some vendor or tool:

/* most common files at the root of GitHub repositories */
SELECT
  path,
  COUNT(*) as count
FROM `bigquery-public-data.github_repos.sample_files`
GROUP BY path
-- only top-level files, i.e. paths which don't contain slashes
HAVING path NOT LIKE '%/%'
ORDER BY count DESC
LIMIT 1000
pathcount
README.md147824
.gitignore138027
LICENSE122677
.travis.yml52847
package.json37508
LICENSE.txt15942
Makefile14351
CHANGELOG.md13315
setup.py12636
LICENSE.md12480
.gitattributes12360
CONTRIBUTING.md10582
.editorconfig10164
index.js10125
Gemfile9997
Rakefile9503
bower.json9297
composer.json8382
COPYING7956
.npmignore7770
MANIFEST.in7237
README.rst7099
.jshintrc6922
build.gradle6846
index.html6824

(Remaining 975 rows at the bottom of the article.)

Some of those are understandably relevant to occupy real estate at the project root (README, LICENSE, CHANGELOG).

But many are just tool-specific configuration files, most of which rarely gets touched.

"Why do you care?"

Hey, I'm not the only one. I care because it gets noisy and cluttered. It makes even simple tasks, like finding the main package declaration, become frustrating for users who aren't familiar with the language.

The project root is like a living room, and we should keep it tidy to make guests feel comfortable.

Remaining 975 rows
pathcount
README6738
requirements.txt6676
pom.xml6269
.gitmodules6251
gradlew6073
gradlew.bat5926
settings.gradle5785
AUTHORS5679
Gruntfile.js5146
gulpfile.js5127
.eslintrc4882
setup.cfg4670
gradle.properties4310
Dockerfile3808
readme.md3716
.babelrc3586
Gemfile.lock3529
tox.ini3528
CMakeLists.txt3480
.rspec3135
appveyor.yml2988
.bowerrc2810
configure.ac2578
phpunit.xml.dist2490
ChangeLog2489
Makefile.am2446
TODO2441
INSTALL2337
license.txt2332
.project2202
NEWS2045
karma.conf.js2043
Procfile2016
webpack.config.js2008
composer.lock1959
circle.yml1958
phpunit.xml1955
CHANGELOG1931
Vagrantfile1914
.coveragerc1857
VERSION1852
NOTICE1847
README.markdown1805
.eslintignore1792
test.js1630
index.php1620
main.go1615
autogen.sh1535
build.xml1495
screenshot.png1479
build.sh1470
README.txt1359
.rubocop.yml1354
.classpath1344
server.js1338
.jscsrc1330
MIT-LICENSE1321
.ruby-version1303
LICENCE1286
LICENSE-MIT1272
build.sbt1264
Podfile1228
configure1209
.scrutinizer.yml1197
app.js1178
config.ru1177
TODO.md1168
Cargo.toml1164
favicon.ico1145
Readme.md1123
Setup.hs1119
.codeclimate.yml1110
.dockerignore1096
_config.yml1071
docker-compose.yml1058
license1049
project.clj1044
.mailmap1026
CODE_OF_CONDUCT.md1024
Guardfile1018
CHANGES.md999
CHANGES996
CONTRIBUTING.rst980
Podfile.lock972
tsconfig.json964
manage.py959
.coveralls.yml937
CNAME936
CONTRIBUTORS865
.DS_Store860
.jshintignore859
__init__.py831
License.txt821
AndroidManifest.xml799
install.sh786
.htaccess784
CREDITS777
MAINTAINERS777
.gitreview760
.watchmanconfig748
Makefile.in731
README.rdoc731
readme.txt726
mix.exs716
project.properties705
.ember-cli699
doc.go691
style.css676
test-requirements.txt660
stack.yaml648
.hgignore646
testem.json633
ember-cli-build.js627
AUTHORS.rst619
app.json614
changelog.md613
makefile613
CHANGES.txt612
.yardopts609
manifest.json602
license.md594
Doxyfile592
config.js590
main.js580
logo.png574
NOTICE.txt572
HISTORY.md567
mix.lock566
install-sh549
MIT-LICENSE.txt547
rebar.config546
icon.png540
History.md538
package.js534
typings.json533
CHANGELOG.rst531
COPYRIGHT530
INSTALL.md511
.eslintrc.json503
REQUIRE500
_Pods.xcodeproj494
COPYING.txt490
CHANGES.rst489
Package.swift486
changelog.txt480
component.json479
tslint.json478
runtests.py471
.clang-format466
.ruby-gemset460
mkdocs.yml457
HISTORY.rst450
AUTHORS.md450
MANIFEST449
robots.txt449
CONTRIBUTORS.md448
aclocal.m4448
proguard-project.txt447
config.h.in446
.document443
config.json442
test.sh441
main.cpp440
CHANGELOG.txt435
main.py434
.php_cs434
requirements-dev.txt432
build.cmd427
THANKS424
run.sh423
Gruntfile.coffee414
.env.example397
TODO.txt396
test.py395
demo.gif394
feed.xml393
gruntfile.js393
contributing.md388
config.guess387
config.sub384
.flowconfig377
Cartfile.resolved376
Kconfig376
missing375
404.html375
REPORTING-BUGS374
UNLICENSE373
Kbuild372
plugin.xml370
main.c370
build.bat369
Cartfile360
LICENCE.txt358
cli.js351
npm-shrinkwrap.json349
example.js348
release.sh347
AUTHORS.txt346
COPYING.LESSER340
ISSUE_TEMPLATE.md340
LICENSE-APACHE339
gulpfile.babel.js337
.nvmrc337
README.org337
init.rb333
deploy.sh331
Changelog.md327
.testr.conf327
config.go324
README.adoc324
binding.gyp322
coffeelint.json321
depcomp321
DESCRIPTION312
wercker.yml307
.hgtags307
pytest.ini306
bootstrap.sh304
CONTRIBUTING302
License.md299
tests.py298
version.sbt295
.hound.yml294
config.py294
demo.html292
util.go291
LICENSE.TXT289
Changelog287
ChangeLog.md286
build.properties286
HACKING285
metadata.json282
.eslintrc.js280
NEWS.md276
checkstyle.xml274
server.php273
dev-requirements.txt272
.versions271
artisan271
ic_launcher-web.png269
PATENTS268
README.textile268
bootstrap268
setup.sh266
example_test.go266
configure.in264
fabfile.py263
HACKING.rst262
client.go261
readme.markdown261
.rubocop_todo.yml260
config.rb260
NAMESPACE256
tsd.json255
License255
BUGS254
Appraisals254
server.go254
functions.php253
LICENCE.md252
acinclude.m4250
config.xml249
Cargo.lock249
babel.cfg247
test.html246
.styleci.yml246
RELEASE_NOTES.md245
utils.go245
global.json244
testem.js243
Gulpfile.js240
start.sh239
pylintrc239
.pylintrc235
pubspec.yaml235
messages.json229
about.md228
Capfile228
Berksfile227
.dir-locals.el226
.bumpversion.cfg225
jsconfig.json225
.drone.yml225
Info.plist224
run_tests.sh224
COPYING.LIB223
INSTALL.txt222
ROADMAP.md221
compile219
build.fsx218
Main.sublime-menu217
.jscs.json216
phpspec.yml216
.csslintrc216
.yo-rc.json216
metadata.rb216
LICENSE-2.0.txt213
ez_setup.py212
run.py211
.env210
.kitchen.yml210
rebar210
.npmrc208
header.php207
protractor.conf.js207
build.js205
glide.yaml204
footer.php204
favicon.png203
build201
example.html200
version.go199
.Rbuildignore197
example.py197
Android.mk196
keywords.txt196
index.md195
wscript194
CONDUCT.md193
elm-package.json193
SUMMARY.md193
.jsbeautifyrc192
library.properties191
config.h189
about.html188
todo.txt188
app.py188
Cakefile186
ltmain.sh184
API.md184
version.txt183
paket.dependencies182
app.yaml182
.cproject180
requirements-test.txt180
ansible.cfg180
.gitlab-ci.yml179
gulpfile.coffee179
.pydevproject178
paket.lock177
settings.py176
utils.py175
.istanbul.yml174
manifest.yml174
mkinstalldirs173
errors.go173
book.json173
Setup.lhs172
index.coffee171
main.m171
opam168
config.php168
_tags168
config168
codecov.yml168
.ghci167
glide.lock166
main_test.go166
params.json165
index.ios.js165
package.xml165
build.ps1164
screenshot.jpg164
Cartfile.private164
README.MD164
phpcs.xml164
RELEASE.md164
FAQ.md163
.vimrc162
History.txt161
packages.config160
util.h159
.rvmrc159
runtime.txt158
sitemap.xml157
RELEASING.md156
local.properties155
version155
AndroidKernel.mk153
SConstruct152
.zuul.yml151
LICENSE.rst151
search.php151
runtests.sh151
init.lua151
404.php151
util.c151
update.sh151
changelog149
CONTRIBUTORS.txt149
ReadMe.md148
chefignore147
install147
lint.xml147
build.py146
build.rs146
page.php145
.arcconfig144
requirements_dev.txt144
.scss-lint.yml143
COPYRIGHT.txt143
server.py142
conftest.py141
README.mdown140
scalastyle-config.xml140
logo.svg140
Doxyfile.in139
Changes138
Default.sublime-commands137
.appveyor.yml137
Screenshot.png137
haxelib.json137
index.android.js136
single.php136
browser.js136
UPGRADE.md135
.merlin135
proguard.cfg135
error.go134
myocamlbuild.ml134
.swiftlint.yml134
clean.sh134
openstack-common.conf133
HACKING.md131
_oasis131
Default (Windows).sublime-keymap130
example.png130
addon.xml130
.slather.yml130
Default (OSX).sublime-keymap129
comments.php129
install.php128
CHANGELOG.markdown128
TESTING.md128
README.mkd127
run127
.reviewboardrc127
webpack.config.dev.js126
index.css126
utils.js126
version.sh126
common.h126
ChangeLog.txt126
CREDITS.md126
Brocfile.js125
test125
index.hbs124
uninstall.php124
.cvsignore124
Default (Linux).sublime-keymap123
shard.yml123
test_requirements.txt123
background.js123
.buildpacks123
bootstrap.php122
FAQ122
PULL_REQUEST_TEMPLATE.md122
.landscape.yaml121
autoload.php120
tasks.py120
preview.png120
nginx.conf120
GNUmakefile119
Contributing.md119
utils.h119
default.hbs118
sbt118
HISTORY118
rebar.lock118
post.hbs118
sidebar.php117
.vscodeignore117
maven_push.gradle117
Default-568h@2x.png117
versioneer.py117
webpack.config.prod.js116
screenshot.gif116
.fixtures.yml116
ionic.project116
.eslintrc.yml115
404.md115
run_tests.py115
styles.css114
contributors.txt114
types.go114
.nojekyll114
setup.ml112
travis.sh112
licence.txt112
composer.phar111
.tern-project111
TODO.rst111
.verb.md111
versions.json110
README.asciidoc110
client_test.go110
logger.go110
humans.txt110
Cask109
Module.php109
archive.php108
webpack.config.production.js108
login.php108
VERSION.txt108
.simplecov108
npm-debug.log108
bootstrap.py107
todo.md106
example.php105
web.config105
atom.xml104
.swift-version104
_config.php103
manifest.mf103
nodemon.json102
default.nix102
.csscomb.json102
codeception.yml101
version.h101
rollup.config.js100
config.m4100
CTestConfig.cmake100
context.go100
box.json100
codereview.settings99
util_test.go99
index.less99
mvnw99
DOCUMENTATION.md98
wsgi.py97
tests.webpack.js97
history.md97
config.yaml97
.stylelintrc96
NOTES96
make.bat96
make.sh96
popup.html95
log.go95
run-tests.sh95
modman95
config.h.cmake95
test.c94
.bithoundrc94
README.html94
api.go93
uninstall.sh93
Makefile.PL93
requirements.php92
config.mk92
chrome.manifest92
RELEASENOTES.md92
MAINTAINERS.md92
BUILD.md92
options.html92
.landscape.yml92
CREDITS.txt91
install.rb91
plugin.yml91
compile.sh90
parser.go90
yii.bat90
Manifest.txt90
Readme.markdown89
.env.sample89
RELEASE89
utils_test.go88
util.py88
util.js88
config.rpath88
Program.cs87
NOTICE.md87
PKG-INFO87
import-summary.txt87
Makefile.common87
CHANGELOG.rdoc87
config.ini87
demo.js86
erlang.mk86
NOTES.md86
readme.rst86
logout.php86
.gitconfig86
.slugignore86
code_of_conduct.md86
.cfignore86
Readme.txt85
buildout.cfg85
package.lisp85
page.hbs85
script.js85
.autotest85
system.properties84
package.sh84
UPGRADING.md84
example.gif84
main.lua84
.bzrignore83
config_test.go83
proguard-rules.pro83
init.sh83
config.yml82
phpunit.php82
client.js82
hosts81
response.go81
app.rb81
nbactions.xml80
ant.properties80
webpack.production.config.js80
sache.json80
ABOUT-NLS80
notes.txt80
request.go79
.ycm_extra_conf.py79
firebase.json79
tests.js79
jsdoc.json79
main.css79
.coffeelintignore79
apple-touch-icon.png78
CONTRIB.md78
webpack.config.development.js78
docker-entrypoint.sh78
esdoc.json77
appinfo.json77
build.boot77
message.go76
utils.c76
demo.png76
install.rdf76
handler.go76
supervisord.conf75
mainwindow.h75
webpack.config.babel.js75
DEVELOPMENT.md75
urls.py75
yuidoc.json75
Changelog.txt75
LICENSE-MIT.txt75
BUILDING.md74
GPL-LICENSE.txt74
publish.sh74
git.mk74
screenshot-1.png74
Settings.StyleCop74
webpack.config.base.js73
rebar.config.script73
index.d.ts73
test.lua73
conn.go73
Config.in73
ReleaseNotes.md73
version.py73
mainwindow.cpp73
http.go73
tags.html73
entrypoint.sh73
.no-sublime-package73
fanart.jpg72
test.php72
activator.properties72
bench_test.go72
.pre-commit-config.yaml72
commands.go72
mvnw.cmd72
screenshot2.png72
code-of-conduct.md71
CONTRIBUTE.md71
requirements_test.txt71
resources.qrc71
launch.sh71
NuGet.Config71
Icon.png71
router.go71
dub.json71
tag.hbs71
LICENSE.MIT71
NuGet.config70
ivy.xml70
main.h70
screenshot-2.png70
schema.sql69
OSSMETADATA69
rules.mk69
common.mk69
dev_requirements.txt69
INSTALL.rst69
KEYS69
theme.toml69
shell.nix68
init.php68
cover.jpg68
server_test.go67
conf.py67
helpers.go67
options.js67
rustfmt.toml66
cmake_uninstall.cmake.in66
stamp-h.in66
LICENSE.GPL66
vimrc66
init66
PKGBUILD66
resource.h66
BUILD65
session.go65
nb-configuration.xml65
bin.js65
.ackrc65
auth.go65
waf65
CHANGE.md64
RELEASE-NOTES.md64
options.go64
TODO.org64
mainwindow.ui64
.gemtest64
blueprints.yaml64
sonar-project.properties64
event.go64
models.py64
Main.hs64
.sass-lint.yml63
packages.json63
install.py63
.gdbinit63
.get_maintainer.ignore63
searchform.php63
CONTRIBUTORS.rst63
types.h63
findbugs-exclude.xml63
configure.sh63
log.c63
admin.php63
behat.yml62
.travis.sh62
default.ps162
DISCLAIMER62
common.go62
.drone.sec62
contact.html62
yii62
screen.png62
test.yml62
Thorfile61
coverage.sh61
MANIFEST.SKIP61
Context.sublime-menu61
uninstall.rb61
.analysis_options61
BSDmakefile61
webpack.prod.config.js61
karma.config.js61
library.json61
grunt.js61
.python-version61
default.py61
DESIGN.md61
App.config60
cron.php60
default.properties60
api.php60
OWNERS60
debug.h60
LICENSE.MD60
site.yml60
data.json60
screenshot1.png60
settings.xml60
NEWS.txt60
.pep859
manifest59
metadata.yaml59
plugin.json59
install.bat59
author.hbs59
readme.html58
release-notes.md58
config.c58
app.config58
.goxc.json57
template.html57
provision.sh57
.semver57
Default.sublime-keymap57
cache.go57
phpdoc.dist.xml57
config.w3257
Jenkinsfile57
wallaby.js56
index.yaml56
.bashrc56
control56
log.h56
db.go56
index.rst56
node.go56
run.bat56
setup.rb56
connection.go56
other-requirements.txt55
start.js55
xmlrpc.php55
test.txt55
file.go55
environment_config.yaml55
rebar355
.remarkrc54
shippable.yml54
LICENSE.LGPL54
routes.js54
info.plist54
todo54
DCO54
.pullapprove.yml54
test_settings.py54
parser_test.go54
demo.py54
cucumber.yml53
.buckconfig53
LICENSE.html53
CONTRIBUTING.markdown53
config.lua53
pre-commit53
icon.ico53
api.js53
Messages.sh53
.directory53
brunch-config.js53
init.bat53
README.TXT53
issue_template.md53
apiary.apib53
clean.bat53
META53
debug.go53
fig.yml53
ReadMe.txt52
THANKS.md52
README_CN.md52
LICENSE-GPL52
compat.h52
COPYING.LGPL52
command.go52
.buildpath52
pavement.py52
DESCRIPTION.rst52
content.php52
benchmark.js52
reader.go52
.kitchen.docker.yml51
karma-e2e.conf.js51
linter.py51
notes.md51
uncrustify.cfg51
licence51
template.go51
writer.go51
docs51
context_test.go51
DEPS51
deploy51
config.toml51
COPYING.DOC50
.sublimelinterrc50
stats.go50
update.php50
common.py50
preview.gif50
pre_build_hook50
cleanup.sh50
RELEASE-NOTES.txt50
CONTRIBUTION.md50
settings.json50
releases.json50
RELEASE_NOTES50
Web.config49
Rakefile.rb49
nuget.config49
Procfile.dev49
rakefile.rb49
list.h49
cli.go49
stdeb.cfg49
config.json.example49
devServer.js49
HEADER.txt49
ofxaddons_thumbnail.png48
build.cake48
.rultor.yml48
environment.yml48
extension.js48
build.config48
changes.txt48
.cocoadocs.yml48
.checkignore47
index.ts47
.checkstyle47
version.properties47
install.js47
newrelic.js47
MAINTAINERS.toml47
test-main.js47
LICENSE.BSD47
crossdomain.xml47
Changes.md47
release47
gradle-mvn-push.gradle46
icon.svg46
COPYING.MIT46
screenshot-3.png46
ReleaseNotes.txt46
.node-version46
requirements.pip46
store.go46
SECURITY.md46
webpack.dev.config.js46
background.html46
browserconfig.xml46
.godir45
readthedocs.yml45
feeds.conf.default45
register.php45
.isort.cfg45
constants.go45
ylwrap45
Local.testsettings45
README.md~44
WORKSPACE44
generate.sh44
SETUP.md44
model.py44
header.txt44
bundle.js44
GruntFile.js44
settings.php44
common.c44
benchmark_test.go44
.agignore44
.deployment44
env.sh44
apple-touch-icon-precomposed.png44
header.png44
distribute_setup.py44
version.c44
common.js44
Plugin.php44
router_test.go44
handlers.go44
parse.go43
typedoc.json43
HISTORY.txt43
run.js43
phpmd.xml43
filter.go43
USAGE.md43
cookiecutter.json43
oca_dependencies.txt43
.envrc43
build.proj43
VERSION.yml43
smart.json43
requirements-tests.txt43
Brewfile42
CommonAssemblyInfo.cs42
gitconfig42
proxy.go42
authors.txt42
ISSUE_TEMPLATE42
.tm_properties42
test.cpp42
debug.c42
premake4.lua42
jquery.js42