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.

Dec 16, 2025 - 12:55
Nov 7, 2026 - 05:16
 0  409
Top 20 Best Practices for Git Repository Management

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

AreaPracticesWhere to go deeper
Setup and structure1 to 5: root files, .gitignore, .gitattributes, repository layout, default branchThis page
Access and ownership6 to 9: roles, CODEOWNERS, branch and tag protection, authenticationBranching guide (protection)
Security10 to 12: secrets, signing, security featuresDevOps guide
History and content13 to 17: commit standards, branch hygiene, shared history, large files, tagsCommit message guide
Maintenance and lifecycle18 to 20: housekeeping, history cleanup, backup and archivingThis 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:

FilePurpose
README.mdWhat the project does, how to build, test and run it, who owns it
.gitignoreFiles Git should never track (build output, local config, secrets)
.gitattributesLine endings, diff and merge behaviour, Git LFS patterns
CODEOWNERSWho must review changes to which paths
CONTRIBUTING.mdBranch model, commit format, review rules
SECURITY.mdHow to report a vulnerability
LICENSETerms of use, required for open source
Issue and PR/MR templatesConsistent 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:

  1. Revoke and rotate the credential.
  2. Check logs for misuse.
  3. 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:

  1. Announce a freeze and merge or close open pull requests.
  2. Run the rewrite on a fresh mirror clone and verify the result.
  3. Force push, then ask everyone to re-clone rather than pull.
  4. 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

CheckHealthy signCommand or setting
AccessNo departed users, no unused deploy keysRepository or project members page
ProtectionDefault branch and v* tags protectedBranch rules or rulesets
Stale branchesOnly active work remainsgit branch -r --merged origin/main
SizeNo unexplained growthgit count-objects -vH
SecretsNo open secret scanning alertsSecurity tab or pipeline reports
OwnershipCODEOWNERS teams still existCODEOWNERS errors view on GitHub
BackupsLast restore test succeededMirror 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:

  1. Create the repository from your organisation's template so the root files from practice 1 are already there.
  2. Fill in the .gitignore and .gitattributes for the language and tools before adding any code.
  3. Add teams with the right roles (practice 6) and write CODEOWNERS (practice 7).
  4. Turn on branch and tag protection or apply the organisation ruleset (practice 8), and choose the single allowed merge method.
  5. Enable secret push protection and the other security features your plan includes (practices 10 and 12).
  6. Add a minimal CI pipeline that runs on every pull or merge request, and make its checks required.
  7. 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

MistakeWhat goes wrongFix
Committing .env or cloud keysCredentials exposed in every cloneRotate, then clean history; add push protection
Admin rights for the whole teamAnyone can disable protection or delete the repositoryWrite or Developer for most, Admin or Owner for a few
No protection on release tagsA tag is moved and nobody knows what shippedProtect v* tags; use annotated tags
Build artifacts committedSlow clones and huge diffsIgnore them, publish to an artifact repository
Force push to mainLost commits and broken clonesBlock force pushes; undo with git revert
Hundreds of stale branchesReal work is hard to findAuto-delete after merge; prune regularly
Ownership only in people's headsReviews go to whoever is onlineCODEOWNERS with teams, not individuals
Deleting finished projectsHistory lost for audits and reuseArchive 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

What's Your Reaction?

Like Like 1
Dislike Dislike 0
Love Love 0
Funny Funny 0
Angry Angry 0
Sad Sad 0
Wow Wow 0
Mridul I am a passionate technology enthusiast with a strong focus on DevOps, Cloud Computing, and Cybersecurity. Through my blogs at DevOps Training Institute, I aim to simplify complex concepts and share practical insights for learners and professionals. My goal is to empower readers with knowledge, hands-on tips, and industry best practices to stay ahead in the ever-evolving world of DevOps.