Gitignore, Main Branch Protection, and Team Git Best Practices
These three topics are closely related to keeping a shared Git repository secure, stable, and easy for a development team to maintain.
1. Does adding a file to .gitignore remove it from Git history?
What is it?
No. Adding a file to .gitignore does not remove it from Git history.
.gitignore tells Git which untracked files or folders should normally not be added to version control.
For example:
.env
appsettings.Production.json
bin/
obj/
If .env has never been committed, Git will normally ignore it.
But if the file has already been committed, simply adding it to .gitignore does not:
- Stop Git from tracking the already tracked file.
- Delete it from previous commits.
- Remove sensitive information from Git history.
Simple Example
Suppose:
Commit A
|
|-- appsettings.Production.json
| Password=Secret123
|
Commit B
|
|-- Added file to .gitignore
The old version still exists:
Commit A → Secret still exists
Commit B → File added to .gitignore
Someone with access to the repository history could potentially inspect the earlier commit.
If the file is already tracked
Suppose you have:
appsettings.Production.json
but Git is already tracking the file.
You can remove it from the Git index while keeping your local copy:
git rm --cached appsettings.Production.json
Then commit:
git add .gitignore
git commit -m "Stop tracking production settings"
git push
Now:
Local file
↓
Still exists
Git current version
↓
No longer tracks it
However:
Previous Git commits
↓
May still contain the file
If it contained a real secret, rotate/revoke the secret and consider history cleanup as discussed previously.
Key Points
.gitignoreprimarily affects untracked files.- It doesn't automatically untrack files already committed.
- Use
git rm --cached <file>to stop tracking an already tracked file while retaining the local file. - It doesn't remove the file from previous commits.
- If credentials were exposed, removing the file is not sufficient; rotate/revoke the credentials.
Interview Answer
No. Adding a file to
.gitignoredoes not remove it from Git history..gitignoremainly prevents untracked files from being added. If a file is already tracked, I can usegit rm --cachedto stop tracking it, but previous commits can still contain the file. If it contained a secret, I would rotate the secret and clean the repository history if required.
2. How do you protect the main branch?
What is it?
Protecting main means configuring repository policies so developers cannot make uncontrolled changes to the production-critical branch.
Instead of:
Developer
|
| git push
v
main
the team normally follows:
Developer
↓
Feature Branch
↓
Pull Request
↓
Build / Tests
↓
Code Review
↓
Approval
↓
main
This is primarily a Git hosting/platform feature, not a feature of the core Git command itself.
For example:
- GitHub → branch protection/rulesets
- Azure DevOps → branch policies
Simple Example
Developer creates a feature branch:
git switch main
git pull origin main
git switch -c feature/login
Makes changes:
git add .
git commit -m "Add login functionality"
Pushes:
git push -u origin feature/login
Instead of directly pushing:
git push origin main
the developer creates a Pull Request:
feature/login
↓
Pull Request
↓
Required Reviewers
↓
Build Validation
↓
Tests
↓
Approval
↓
main
Important main Branch Protections
1. Require Pull Requests
Prevent normal development changes from going directly into main.
feature/login
↓
PR
↓
main
This gives the team an opportunity to review the change.
2. Require Reviewers
For example:
Developer creates PR
↓
Reviewer reviews code
↓
Approval
↓
Merge allowed
Teams can require one or more approvals depending on their process.
3. Require Successful CI Checks
Before merging:
Pull Request
↓
Build
↓
Unit Tests
↓
Code Quality / Security Checks
↓
Success
↓
Merge
If the build fails:
Build failed
↓
Merge blocked
This prevents obviously broken code from entering main.
4. Restrict Direct Pushes
Developers should generally not bypass the Pull Request process:
Developer
X
|
v
main
Instead:
Developer
↓
Branch
↓
PR
↓
main
5. Restrict Force Push
This command can rewrite shared history:
git push --force
On an important shared branch such as main, force pushing should normally be disabled or tightly restricted.
6. Control Branch Deletion
Accidental deletion of important branches should also be restricted.
GitHub vs Azure DevOps
The terminology differs slightly.
| GitHub | Azure DevOps |
|---|---|
| Branch protection/rulesets | Branch policies |
| Pull Request | Pull Request |
| Required reviews | Minimum number of reviewers / reviewer policies |
| Required status checks | Build validation/status policies |
| Restrict pushes | Branch permissions/policies |
The exact options vary by platform and organization configuration.
Practical Team Workflow
A common protected-branch workflow is:
main
|
+------ feature/login
|
Development
|
Commit
|
Push
|
v
Pull Request
|
+-------+-------+
| |
Code Review CI Pipeline
| |
Approval Build + Tests
| |
+-------+-------+
|
v
Merge
|
v
main
Key Points
- Protect
mainfrom uncontrolled direct changes. - Require Pull Requests.
- Require appropriate reviewer approval.
- Require successful build/test checks.
- Restrict force pushes.
- Use branch permissions appropriately.
- GitHub provides branch protection/rulesets.
- Azure DevOps provides branch policies and permissions.
Interview Answer
I protect the
mainbranch by preventing normal direct pushes and requiring developers to work through feature branches and Pull Requests. I configure required reviewers, successful CI/build validation and other status checks before merging. I also restrict force pushes and branch deletion. In GitHub this is handled through branch protection or rulesets, while in Azure DevOps we use branch policies and permissions.
3. Important Git Best Practices for Development Teams
What is it?
Git best practices are conventions that help a team maintain:
- Clean history
- Stable branches
- Easier collaboration
- Better code review
- Fewer merge conflicts
- Secure repositories
- Reliable releases
1. Use a Defined Branching Strategy
The whole team should follow the same strategy.
Examples:
GitFlow
GitHub Flow
Trunk-Based Development
Avoid developers independently inventing different branching processes.
2. Protect main
Don't allow normal development to happen directly on main.
Prefer:
main
|
+---- feature/login
|
+---- feature/payment
Then use Pull Requests.
3. Keep Branches Focused
A branch should ideally address one feature, bug, or task.
Good:
feature/login
bugfix/cart-total
hotfix/payment-error
Avoid one branch containing many unrelated changes.
This makes PRs easier to review and reduces merge complexity.
4. Keep Branches Short-Lived
Long-lived branches tend to diverge from the shared branch:
main A---B---C---D---E---F
\
feature X-------------Y
The longer they diverge, the greater the potential integration difficulty.
Prefer frequent synchronization and smaller changes.
5. Pull/Fetch Recent Changes Regularly
Before starting or integrating work, synchronize appropriately with the remote repository.
For example:
git switch main
git pull origin main
Then create the new branch:
git switch -c feature/login
While working, keep aware of changes made by other developers.
6. Make Small, Logical Commits
Avoid one huge commit containing unrelated work.
Bad:
"Changed many things"
Better:
Add login validation
Add JWT authentication
Handle invalid credentials
Add authentication unit tests
Each commit should represent a meaningful logical change.
7. Write Meaningful Commit Messages
Avoid:
git commit -m "changes"
git commit -m "fix"
git commit -m "test"
git commit -m "final"
Prefer:
git commit -m "Add validation for login request"
or:
git commit -m "Fix null reference in order service"
A reviewer should be able to understand the purpose of the commit.
8. Use Pull Requests and Code Reviews
Instead of:
Developer → main
prefer:
Developer
↓
Feature Branch
↓
Pull Request
↓
Review
↓
CI
↓
main
Pull Requests provide a controlled place for discussion, review, and automated validation.
9. Resolve Conflicts Carefully
Don't blindly accept:
Accept Current
or:
Accept Incoming
Understand both changes before resolving the conflict.
After resolution:
git add <resolved-file>
Then complete the merge/rebase operation as appropriate.
Most importantly, test the resulting code.
A conflict-free merge does not automatically mean the application behaves correctly.
10. Never Commit Secrets
Never intentionally commit:
Passwords
API keys
PATs
Private keys
JWT signing secrets
Credential-bearing connection strings
Use:
Environment variables
.NET User Secrets
Azure Key Vault
CI/CD secret variables
11. Use .gitignore Properly
Don't commit generated/local files unnecessarily.
For a .NET application, common examples include:
bin/
obj/
.vs/
The repository should contain source and necessary configuration—not every file generated on a developer's machine.
12. Be Careful With History-Rewriting Commands
Commands such as:
git reset --hard
git rebase
git push --force
can cause lost work or collaboration problems when used incorrectly.
Particularly avoid rewriting commits that other developers are already depending on unless the team has explicitly coordinated it.
For force pushes where rewriting is intentional, --force-with-lease is generally safer than plain --force because it provides additional protection against overwriting unexpected remote changes.
13. Delete Merged Feature Branches
After:
feature/login
↓
PR
↓
main
the feature branch is usually no longer required.
Local:
git branch -d feature/login
Remote:
git push origin --delete feature/login
This keeps the repository easier to navigate.
14. Tag Important Releases
For example:
git tag v2.0.0
git push origin v2.0.0
Then:
main
|
+-- v1.0.0
|
+-- v1.1.0
|
+-- v2.0.0
Tags make it easier to identify the exact commit associated with a release.
15. Use CI/CD Validation
A strong workflow combines Git with automated validation:
Developer
↓
Commit
↓
Push
↓
Pull Request
↓
CI Pipeline
|
+-- Restore dependencies
+-- Build
+-- Unit tests
+-- Static/security checks
|
↓
Review / Approval
↓
Merge
This prevents many problems from reaching the protected branch.
Team Git Workflow Example
A practical workflow might be:
1. Update main
↓
2. Create feature branch
↓
3. Make small changes
↓
4. Commit with meaningful messages
↓
5. Synchronize with latest team changes as needed
↓
6. Push feature branch
↓
7. Create Pull Request
↓
8. CI build + tests
↓
9. Code review
↓
10. Resolve comments/conflicts
↓
11. Merge into protected main
↓
12. Delete feature branch
↓
13. Deploy through CI/CD
Key Points
For quick interview revision, remember these:
- Follow a clear branching strategy.
- Protect
main. - Use short-lived, focused branches.
- Use Pull Requests and code reviews.
- Keep commits small and logical.
- Write meaningful commit messages.
- Synchronize regularly to reduce integration problems.
- Never commit secrets.
- Configure
.gitignorecorrectly. - Test carefully after resolving conflicts.
- Be cautious with
reset --hard,rebase, and force push. - Delete merged feature branches when no longer needed.
- Tag releases when appropriate.
- Use automated CI/build/test checks.
- Keep the shared branch stable and deployable according to the team's release model.
Interview Answer
Adding a file to
.gitignoredoes not remove it from Git history. If it is already tracked, I can usegit rm --cachedto stop tracking it, but earlier commits still contain the historical version.For the
mainbranch, I normally protect it by requiring Pull Requests, code reviews, successful CI/build checks, and appropriate approvals while restricting direct pushes, force pushes, and deletion.In a development team, I follow a defined branching strategy, use short-lived focused branches, make small logical commits with meaningful messages, regularly synchronize changes, use Pull Requests and code reviews, never commit secrets, and use CI/CD validation. I am also careful with history-rewriting commands such as rebase, reset, and force push, especially on shared branches.