Skip to main content

Git Collaboration

Git Distributed Model Git’s distributed nature makes team collaboration seamless. Learn to work with remote repositories, pull requests, and code review workflows.

Remote Repositories

A remote is a version of your repository hosted elsewhere (GitHub, GitLab, Bitbucket). Think of it as a shared bulletin board — everyone on the team can post their changes and read everyone else’s. Your local repo is your private workspace; the remote is where work becomes visible to the team.

Viewing Remotes

Adding Remotes


Cloning Repositories


Pushing Changes

Never force push to shared branches! It rewrites history and can cause problems for teammates.Safe alternative:

Pulling Changes

Pull vs Fetch

Practical tip: Many experienced developers prefer git pull --rebase over a plain git pull. Instead of creating a merge commit every time you sync with main, rebase replays your local commits on top of the remote changes — keeping a cleaner, linear history. You can make this the default: git config --global pull.rebase true.

Working with Forks

Forking Workflow

1

Fork on GitHub

Click “Fork” button on the repository
2

Clone your fork

3

Add upstream remote

4

Keep fork updated


Pull Requests (PRs)

Pull requests are how you propose changes to a repository.

Creating a Pull Request

PR Best Practices

Small PRs

Easier to review, faster to merge

Clear Description

Explain what and why, not just how

Link Issues

Reference related issues: “Fixes #123”

Update Branch

Keep PR branch updated with main

PR Template Example


Code Review

As a Reviewer

Good Review Comments:
  • ✅ “Consider using a Map here for O(1) lookup”
  • ✅ “This could cause a race condition if…”
  • ✅ “Great solution! Minor: variable name could be more descriptive”
Bad Review Comments:
  • ❌ “This is wrong”
  • ❌ “Why did you do it this way?”
  • ❌ “I would have done it differently”

As an Author


Collaboration Workflows

Centralized Workflow

Best for: Small teams, simple projects

Feature Branch Workflow

Best for: Most teams, medium to large projects

Forking Workflow

Best for: Open source projects, external contributors

Handling Common Scenarios

Syncing Fork with Upstream

Updating PR Branch

Resolving PR Conflicts


SSH vs HTTPS

HTTPS

Pros: Easy setup, works everywhere
Cons: Need to enter credentials (or use credential helper)

SSH

Pros: No password needed, more secure
Cons: Requires SSH key setup

Setting Up SSH


Tags and Releases

Creating Tags

Semantic Versioning

Semantic versioning (SemVer) communicates the nature of changes to consumers of your software. It is a contract: “I promise this version is safe to upgrade to (or not) based on the version number.”
Real-world example: When a library bumps from v2.3.1 to v3.0.0, your automated dependency updater (Dependabot, Renovate) will flag it as a major change requiring manual review. A bump from v2.3.1 to v2.4.0 is usually auto-merged. This works because the community trusts SemVer as a reliable signal — which is why violating it (shipping breaking changes in a minor version) is considered a serious breach of trust in the open source world.

Practical Examples

Example 1: Contributing to Open Source

Example 2: Team Feature Development


Troubleshooting

”Your branch is behind ‘origin/main’"

"Your branch has diverged"

"Permission denied (publickey)"

"Failed to push some refs”


Best Practices

Always get latest changes before pushing
  • One feature/fix per PR
  • Easier to review
  • Faster to merge
  • What changed
  • Why it changed
  • How to test
  • Screenshots if UI changes
  • Be constructive
  • Ask questions
  • Suggest improvements
  • Approve when ready

Key Takeaways

  • Remotes connect local and hosted repositories
  • Pull requests enable code review
  • Forks allow external contributions
  • SSH keys simplify authentication
  • Good collaboration requires communication

Interview Deep-Dive

Strong Answer:
  • The blast radius: the remote main branch now points to an older commit. The 5 overwritten commits are no longer reachable from any remote ref. Developers who already pulled those commits have them locally, but their next git pull will see a diverged history. If anyone had branched off the lost commits, their branches now have orphaned parents that do not exist on the remote.
  • Immediate recovery: any developer who has the original main (before the force push) can restore it. They run git log origin/main to confirm they still have the old tip (they might need to check their reflog with git reflog show origin/main). Then they push the correct history back: git push origin main (or --force-with-lease if the remote has already been updated by others).
  • If no developer has the original commits locally, check the hosting platform. GitHub retains force-pushed commits for at least 90 days — you can find the old tip SHA in the repository’s events audit log or by using the GitHub API to inspect the push event. With the SHA, you can reset main to the correct commit.
  • Downstream cleanup: all three developers should git fetch origin and then git reset --hard origin/main on their local main (or git pull --rebase). Developers with feature branches based on the lost commits should rebase onto the restored main.
  • Prevention: enable branch protection on main. In GitHub, this means requiring PR reviews and disabling force pushes entirely. In GitLab, protect the branch at the project settings level. This is the single most important repository configuration for any team.
Follow-up: How do you educate the team without shaming the junior developer?I frame it as a systems problem, not a people problem. The question is not “why did they force push” but “why does our repository allow force pushes to main?” I enable branch protection, do a brief team demo of --force-with-lease vs --force, and add a hook or CI check that rejects force pushes to protected branches. The incident becomes a learning moment that results in a concrete process improvement, not blame.
Strong Answer:
  • As a maintainer, I have two options. First, I ask the contributor to resolve the conflicts themselves. This is the preferred approach because it keeps the contributor engaged and they understand their code best. I leave a PR comment: “This PR has conflicts with main. Could you rebase onto the latest main?” Most active contributors will do this within a day.
  • Second, if the contributor is unresponsive or unfamiliar with conflict resolution, I can resolve it myself. I fetch their branch: git fetch origin pull/123/head:pr-123, check it out, rebase it onto main (git rebase main), resolve conflicts, and push to the contributor’s fork (if they enabled “allow edits from maintainers”) or to a new branch and update the PR.
  • A third, more common pattern in larger projects: I use GitHub’s “Update branch” button (which merges main into the PR branch) or the CLI equivalent. This creates a merge commit on the PR branch, which is less clean but avoids the contributor needing to force-push.
  • The key consideration is the project’s merge strategy. If the project uses squash merge (most open-source projects do), the intermediate conflict resolution commits do not matter because they will be squashed into one commit on merge. If the project uses rebase-and-merge, the commit history should be clean.
Follow-up: The contributor accidentally committed sensitive credentials in an early commit. The PR has 10 commits. How do you handle this before merging?I immediately ask the contributor to rotate the credential, regardless of whether we can clean the history. Then I ask them to do an interactive rebase (git rebase -i) to remove or amend the commit that introduced the credential, and force-push the cleaned branch. I verify the cleaned PR no longer contains the credential with a search through the diff. If the PR was open for any length of time, I assume the credential was scraped by bots — GitHub public repos are actively scanned. The credential must be rotated regardless of whether we successfully clean the Git history.
Strong Answer:
  • git pull is syntactic sugar for git fetch + git merge (or git fetch + git rebase if configured with pull.rebase=true). Functionally, they produce the same result. The difference is control and visibility.
  • git fetch + manual merge gives you an inspection step. After fetching, you can run git log origin/main to see what your teammates pushed, git diff main..origin/main to review the actual changes, and then decide how to integrate. This is valuable when working on a shared branch where unexpected changes could conflict with your in-progress work.
  • git pull is more convenient for routine syncing. If you are on a personal feature branch and just want to stay up to date with main, git pull --rebase origin main is a single command that fetches and replays your commits on top.
  • In my workflow, I use git fetch + manual inspection when integrating from a branch I do not control (e.g., syncing a fork with upstream, or pulling from a coworker’s branch). I use git pull --rebase for routine updates to my own feature branches from main. I configure pull.rebase=true globally so that accidental git pull without flags does not create unnecessary merge commits.
  • The most important thing is understanding that git pull can create merge commits that pollute your branch history. Teams that care about clean history should default to git pull --rebase and only use merge pulls when they intentionally want to record a merge point.
Follow-up: You run ‘git pull —rebase’ and hit a conflict on every single commit in your branch. What is happening and how do you handle it?During rebase, Git replays each of your commits one at a time on top of the updated base. If the base changed significantly (e.g., a large refactor on main), each replayed commit can conflict with the new base. The worst case is resolving the same conflict repeatedly across commits. The fix is git rerere (reuse recorded resolution): enable it with git config rerere.enabled true. Once you resolve a conflict, rerere records the resolution. If the same conflict pattern appears in subsequent commits, Git applies the recorded resolution automatically. For a branch with many commits, I would also consider squashing into fewer commits before rebasing to reduce the number of potential conflict points.

Next: Git Advanced Topics →