Top 20 Best Practices for Git Repository Management
20 Git repository management best practices: root files, .gitattributes, roles, CODEOWNERS, protection, secrets, Git LFS, maintenance and archiving.
Good Git repository management means treating each repository as a maintained asset: standard root files, a clean .gitignore and .gitattributes, least-privilege access with code owners, protected branches and tags, no secrets or large binaries in history, consistent commits and tags, routine maintenance, and a plan for backups and archiving.
This is the hub for our Git series and sits under the CI/CD pillar. It covers the repository as a whole: how it is set up, who can change it, what must never go into it, and how to keep it fast and recoverable. Three focused guides go deeper on topics this page only summarises: Git commit messages, Git branching strategies and version control in DevOps. Commands are explained in 25 Git commands every DevOps engineer must know. Facts and limits below were checked against git-scm.com and the GitHub and GitLab documentation in November 2026.
Key takeaways
- Every repository should ship with a README, .gitignore, .gitattributes, CODEOWNERS and contribution notes from the first commit.
- Grant access to teams, not individuals, and give each the lowest role that lets them work.
- Protect the default branch and release tags so history cannot be rewritten or bypassed.
- Secrets that reach Git history must be rotated first; rewriting history comes second.
- GitHub warns on files over 50 MiB and blocks files over 100 MiB. Use Git LFS or an artifact store for large binaries.
- Schedule maintenance, mirror important repositories, and archive the ones nobody uses.
The 20 practices at a glance
| Area | Practices | Where to go deeper |
|---|---|---|
| Setup and structure | 1 to 5: root files, .gitignore, .gitattributes, repository layout, default branch | This page |
| Access and ownership | 6 to 9: roles, CODEOWNERS, branch and tag protection, authentication | Branching guide (protection) |
| Security | 10 to 12: secrets, signing, security features | DevOps guide |
| History and content | 13 to 17: commit standards, branch hygiene, shared history, large files, tags | Commit message guide |
| Maintenance and lifecycle | 18 to 20: housekeeping, history cleanup, backup and archiving | This page |
Setup and structure
1. Start every repository with the same root files
A newcomer should understand a repository from its root directory. Agree on a standard set and put it in a template repository so new projects start complete:
| File | Purpose |
|---|---|
README.md | What the project does, how to build, test and run it, who owns it |
.gitignore | Files Git should never track (build output, local config, secrets) |
.gitattributes | Line endings, diff and merge behaviour, Git LFS patterns |
CODEOWNERS | Who must review changes to which paths |
CONTRIBUTING.md | Branch model, commit format, review rules |
SECURITY.md | How to report a vulnerability |
LICENSE | Terms of use, required for open source |
| Issue and PR/MR templates | Consistent bug reports and review checklists |
Both GitHub and GitLab support template repositories or project templates, which saves copying these files by hand.
2. Write the .gitignore before the first commit
Ignoring a file after it has been committed does not remove it from history. Add language and tool patterns (for example node_modules/, *.tfstate, .terraform/, .env, target/) on day one. Keep personal editor files in your global ignore file rather than the shared one. If something slipped in, stop tracking it without deleting your local copy:
echo ".env" >> .gitignore
git rm --cached .env
git commit -m "Stop tracking local .env file"
If that file held a secret, removing it from tracking is not enough. See practice 10.
3. Normalise line endings and file handling with .gitattributes
Teams that mix Windows, macOS and Linux often see whole-file diffs caused by line endings, or shell scripts that fail because of carriage returns. A short .gitattributes fixes this for everyone, regardless of personal settings:
# Let Git normalise text files
* text=auto
# Scripts must keep LF endings
*.sh text eol=lf
# Windows batch files need CRLF
*.bat text eol=crlf
# Treat these as binary: no diff, no merge
*.png binary
*.zip binary
4. Choose monorepo or polyrepo on purpose
A monorepo keeps many projects in one repository, which makes cross-project changes atomic and tooling consistent, but needs build tooling that only tests what changed. Polyrepos give clear ownership and independent pipelines, but cross-repository changes need coordination and versioned dependencies. Neither is right in general. Decide per product, write down why, and design CI for that choice. Our post on monorepo and polyrepo trade-offs compares them in detail; for large monorepos, sparse checkout and partial clone keep local copies manageable.
5. Standardise the default branch name
Tools, scripts and protection rules all assume a default branch name. GitHub and GitLab create new repositories with main, and you can make local git init match:
git config --global init.defaultBranch main
If older repositories still use master, either rename them in one planned change or document the exception. On GitHub, renaming a branch through the web UI retargets open pull requests and redirects links to the old name; update CI triggers and scripts at the same time. A mix of names across repositories causes broken pipeline triggers.
Access and ownership
6. Grant least-privilege roles to teams
GitHub organisation repositories have five roles: Read, Triage, Write, Maintain and Admin. GitLab's default roles include Guest, Reporter, Developer, Maintainer and Owner, plus specialised roles such as Planner. Most engineers need Write (GitHub) or Developer (GitLab). Keep Admin or Owner for a small group, and give access through teams or groups so that joining or leaving a team updates permissions everywhere. Review access on a schedule, for example every quarter, and remove departed users and unused deploy keys.
7. Route reviews with CODEOWNERS
A CODEOWNERS file maps paths to the people or teams who must review changes. On GitHub it can live in .github/, the repository root or docs/ (searched in that order), and the last matching pattern wins:
# Default owners
* @acme/platform-team
# Infrastructure changes need the infra team
/terraform/ @acme/infra-team
# Pipeline definitions need DevOps review
/.github/workflows/ @acme/devops-team
GitHub ignores CODEOWNERS files of 3 MB or more. Combine CODEOWNERS with a protection rule that requires code owner review, otherwise owners are only suggested reviewers. GitLab supports a CODEOWNERS file too, and code owner approval on protected branches is a Premium feature.
8. Protect the default branch and release tags
Require pull or merge requests, passing status checks and at least one approval on the default branch, and block force pushes and deletion. Protect tag patterns such as v* so released versions cannot be moved or removed. On GitHub, rulesets can apply the same rules across many repositories in an organisation; on GitLab, Premium supports group-level protected branches. The branching strategies guide lists the exact settings on each platform.
9. Authenticate with SSH keys or tokens, never passwords
GitHub has removed password authentication for Git operations; over HTTPS you use a personal access token or a credential helper such as Git Credential Manager. Prefer fine-grained tokens limited to specific repositories and an expiry date. For automation, use deploy keys scoped to one repository or the CI system's own short-lived job token rather than a person's credentials.
Security
10. Keep secrets out, and rotate first if one gets in
A credential pushed to a remote repository should be treated as compromised, even if you delete the commit minutes later: clones, forks and caches may already hold it. The order is fixed:
- Revoke and rotate the credential.
- Check logs for misuse.
- Only then, if needed, remove it from history (practice 19).
Prevention is cheaper. GitHub's push protection blocks pushes that contain detected secrets; user push protection is on by default for pushes to public repositories, and repository-level push protection must be enabled by an administrator. GitLab Premium push rules include "Prevent pushing secret files", and GitLab also offers secret detection. Add a local pre-commit scanner as a first line of defence. Read best practices for storing secrets in Git repositories for vault and encryption options.
11. Sign commits and tags where provenance matters
Git can sign commits and tags with GPG, SSH or X.509 keys (git commit -S, git tag -s). GitHub's "Require signed commits" rule and GitLab Premium's "Reject unsigned commits" push rule turn signing into a requirement. Start with release tags and protected branches, where knowing who produced a change matters most, before requiring it everywhere.
# Sign with an existing SSH key (Git 2.34 or later)
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
12. Turn on the platform's repository security features
Both platforms can scan dependencies, code and secrets inside the repository. On GitHub that means Dependabot alerts and updates, secret scanning and code scanning; on GitLab it means dependency scanning, SAST and secret detection in pipelines. Availability depends on the plan and on whether the repository is public, so check your plan, then switch on everything you are entitled to and route the alerts to the owning team, not a shared inbox.
History and content
13. Agree on a commit standard
Repositories with consistent commits are easier to search, review and release from. At minimum: a short imperative subject, a blank line, a body that explains why, and one logical change per commit. If you generate changelogs, add Conventional Commits and lint it in CI. All of this is covered in our commit message guide.
14. Document the branch model and delete merged branches
Write the branching strategy in CONTRIBUTING.md, allow only one merge method, and turn on automatic deletion of branches after merge. Run git fetch --prune (or set git config --global fetch.prune true) so deleted remote branches disappear locally too. A repository with hundreds of stale branches hides the few that matter. Choosing the model itself is covered in the branching guide.
15. Never rewrite shared history
Rebasing, amending and force pushing are fine on your own unmerged branch. On the default branch, release branches or any branch others have pulled, they break everyone's clones. Undo a bad change on a shared branch with git revert, which adds a new commit. When you must force push your own branch, use git push --force-with-lease, which refuses to overwrite commits you have not seen.
16. Keep large binaries out of normal Git history
Git stores every version of every file, so a 200 MB binary committed ten times stays in every clone forever. GitHub warns on files over 50 MiB, blocks files over 100 MiB, and recommends keeping repositories ideally under 1 GB and strongly under 5 GB. Options for large files:
- Build output and packages: publish to an artifact repository or container registry, not Git.
- Design files, media, datasets that must be versioned: use Git LFS, which stores pointers in Git and the content elsewhere.
git lfs install
git lfs track "*.psd"
git add .gitattributes
git commit -m "Track Photoshop files with Git LFS"
Server-side limits help: GitHub rulesets can restrict file size and file extensions on push, and GitLab Premium push rules include a maximum file size.
17. Tag releases with Semantic Versioning
Use annotated tags (git tag -a v2.4.0 -m "Release 2.4.0") for every release so each tag records its author, date and message. Follow Semantic Versioning so a version number tells users whether an upgrade is safe, and publish release notes on the platform's Releases page. Tags are also the cleanest trigger for release pipelines.
Maintenance and lifecycle
18. Schedule repository maintenance
Large or busy repositories slow down as loose objects and pack files accumulate. On developer machines and long-lived build agents, git maintenance start registers the repository and schedules background tasks. With the default incremental strategy, Git updates the commit-graph and prefetches from remotes hourly, packs loose objects and runs an incremental repack daily, and packs refs weekly.
git maintenance start # register and schedule background maintenance
git maintenance run --task=gc # one-off full garbage collection
git count-objects -vH # check repository size
On the server side, GitHub manages storage for you; on self-managed GitLab, administrators configure housekeeping. Watch repository size over time and investigate sudden jumps.
19. Use git filter-repo, with a plan, when history must change
Sometimes history must be rewritten: a leaked secret after rotation, a huge file that should never have been committed, or splitting a directory into its own repository. The git-filter-branch manual itself recommends using an alternative such as git filter-repo, because filter-branch has safety and performance problems. Rewriting changes every later commit ID, so plan it:
- Announce a freeze and merge or close open pull requests.
- Run the rewrite on a fresh mirror clone and verify the result.
- Force push, then ask everyone to re-clone rather than pull.
- Ask the hosting platform's support to purge cached views if sensitive data was involved.
20. Back up, mirror and archive
A hosted platform is not a backup plan on its own: an accidental deletion, a compromised admin account or an expired subscription can still remove access. For important repositories, keep an independent mirror (git clone --mirror plus scheduled git remote update), or use your platform's export and backup features, and test a restore. When a project ends, archive the repository instead of deleting it. On GitHub, an archived repository becomes read-only for everyone and can be unarchived later; GitLab offers a similar archive option. Add a note to the README pointing to any replacement.
A quarterly repository health check
| Check | Healthy sign | Command or setting |
|---|---|---|
| Access | No departed users, no unused deploy keys | Repository or project members page |
| Protection | Default branch and v* tags protected | Branch rules or rulesets |
| Stale branches | Only active work remains | git branch -r --merged origin/main |
| Size | No unexplained growth | git count-objects -vH |
| Secrets | No open secret scanning alerts | Security tab or pipeline reports |
| Ownership | CODEOWNERS teams still exist | CODEOWNERS errors view on GitHub |
| Backups | Last restore test succeeded | Mirror job logs |
Setting up a new repository in the right order
Most repository problems start in the first hour, when someone pushes code before the guard rails exist. This order avoids the usual clean-up work later:
- Create the repository from your organisation's template so the root files from practice 1 are already there.
- Fill in the .gitignore and .gitattributes for the language and tools before adding any code.
- Add teams with the right roles (practice 6) and write CODEOWNERS (practice 7).
- Turn on branch and tag protection or apply the organisation ruleset (practice 8), and choose the single allowed merge method.
- Enable secret push protection and the other security features your plan includes (practices 10 and 12).
- Add a minimal CI pipeline that runs on every pull or merge request, and make its checks required.
- Push the first real code through a pull request, so the process is tested from day one.
# Local defaults worth setting once per machine
git config --global init.defaultBranch main
git config --global fetch.prune true
git config --global pull.rebase true
git config --global push.autoSetupRemote true
pull.rebase keeps local feature branches linear when you pull, and push.autoSetupRemote sets the upstream automatically on the first push of a new branch, so git push works without extra flags.
Common repository mistakes and how to fix them
| Mistake | What goes wrong | Fix |
|---|---|---|
Committing .env or cloud keys | Credentials exposed in every clone | Rotate, then clean history; add push protection |
| Admin rights for the whole team | Anyone can disable protection or delete the repository | Write or Developer for most, Admin or Owner for a few |
| No protection on release tags | A tag is moved and nobody knows what shipped | Protect v* tags; use annotated tags |
| Build artifacts committed | Slow clones and huge diffs | Ignore them, publish to an artifact repository |
Force push to main | Lost commits and broken clones | Block force pushes; undo with git revert |
| Hundreds of stale branches | Real work is hard to find | Auto-delete after merge; prune regularly |
| Ownership only in people's heads | Reviews go to whoever is online | CODEOWNERS with teams, not individuals |
| Deleting finished projects | History lost for audits and reuse | Archive instead |
Each of these is cheaper to prevent with a setting than to clean up afterwards, which is why the setup order above puts protection and scanning before the first line of code.
Build these habits with guided practice
Repository settings are easier to learn on a real project where you protect branches, configure CODEOWNERS and wire pipelines to tags. Our GitLab training and DevOps training cover this hands-on, in Kharadi, Pune or live online across India. To test yourself, work through our Git and GitHub interview questions.
Frequently asked questions
What files should every Git repository have?
At minimum a README, .gitignore, .gitattributes, CODEOWNERS and CONTRIBUTING notes, plus a LICENSE for open source and a SECURITY.md for vulnerability reports. Keep them in a template repository so new projects start complete.
How do I remove a file from Git without deleting it locally?
Add it to .gitignore, then run git rm --cached <file> and commit. The file stays on disk but is no longer tracked. Earlier commits still contain it, so rotate any secret it held.
What should I do if I committed a password or API key?
Revoke and rotate the credential immediately, then check for misuse. Removing it from history with git filter-repo is a follow-up step, because clones and forks may already contain the old commits.
How large can a Git repository be on GitHub?
GitHub warns on files over 50 MiB, blocks files over 100 MiB, and recommends repositories ideally under 1 GB and strongly under 5 GB. Use Git LFS or an artifact repository for large binaries.
Should we use a monorepo or many repositories?
It depends on coupling and tooling. Monorepos make cross-project changes atomic but need selective builds. Polyrepos give clear ownership but need versioned dependencies. Decide per product and record the reason.
What does CODEOWNERS do?
It maps file paths to the people or teams responsible for them, so they are requested as reviewers automatically. With a protection rule that requires code owner review, their approval becomes mandatory.
Is git filter-branch still recommended?
No. The Git manual for filter-branch warns about safety and performance problems and recommends an alternative such as git filter-repo for rewriting history.
Should I delete or archive old repositories?
Archive them. Archiving keeps history available for audits and reference while making the repository read-only. Delete only when you are sure nothing depends on it and any retention rules allow it.
References
- Git documentation: gitignore
- Git documentation: gitattributes
- Git documentation: git-maintenance
- Git documentation: git-filter-branch (warning and alternatives)
- Git LFS
- GitHub Docs: Repository roles for an organization
- GitHub Docs: About code owners
- GitHub Docs: Available rules for rulesets
- GitHub Docs: About large files on GitHub
- GitHub Docs: About push protection
- GitHub Docs: Archiving repositories
- GitLab Docs: Roles and permissions
- GitLab Docs: Protected branches
- GitLab Docs: Push rules
- Semantic Versioning 2.0.0
What's Your Reaction?
Like
1
Dislike
0
Love
0
Funny
0
Angry
0
Sad
0
Wow
0