· 11 min read

The Only Git Article You'd Ever Need - 2/4

Deeper Into GitHub Collaboration

Now that we’ve covered the foundations of Git and have a basic idea of how we can collaborate with others in a common repository, let’s dive deeper into this “collaboration” aspect.

Professional Git Workflows: Collaboration Done Right

This is where Git transforms from a personal productivity tool into a professional collaboration system. These workflows separate developers who can work effectively in teams from those who create merge conflicts and break production deployments.

Branching: The Most Important Thing You Will Ever Learn

If you take one thing, and only one thing, from these 3 Git articles, let it be this: Branching. This isn’t optional. This isn’t a “nice-to-have.” This is how actual, sane people work.

Why Branch?

Typically, ‘main’/‘master’/‘production’ branch is the one that’s taken as the deployable, stable version of your application. The output of the code in this branch is what your end users interact with. Working directly on this branch is dangerous and can lead to unstable releases and broken features reaching users.

Actual developers never commit incomplete or experimental work directly to main. Instead, they use feature branches to isolate development work, ensuring the main branch remains stable and deployable at all times.

Every new feature, bug fix, experiment, or idea gets its own dedicated branch. This isolation allows you to make radical changes, test different approaches, and work through complex problems without affecting the stable main codebase. If your experimental work doesn’t pan out, you can simply delete the branch without any impact on production code.

git branch <name> & git switch <name> (or git checkout -b <name>): Creating and Switching Branches

Think of your local repository as a branching tree structure. The main branch represents the trunk, and when you create a new branch, you’re creating a new development path that diverges from that main line.

  • git branch <name>: This command creates a new branch. It’s like telling Git, “Hey, make me a new timeline called <name>, based on where I currently am.” It doesn’t switch you to it.

    # You're on main (check with git branch)
    git branch feature/user-profile
    # Now if you run 'git branch', you'll see both main and feature/user-profile, but you would still be on the main branch
    
  • git switch <name>: This command switches your working directory and Git’s HEAD pointer to that specified branch. It’s like jumping into that new parallel universe. Your files will literally change to reflect the state of that branch.

    git switch feature/user-profile
    # You'll see: "Switched to branch 'feature/user-profile'"
    
  • git checkout -b <name>: This is the lazy (efficient actually) way to do both in one go. It creates a new branch and immediately switches you to it. Use this because we both know we’re lazy.

    git checkout -b bugfix/login-page-ui
    # You'll see: "Switched to a new branch 'bugfix/login-page-ui'"
    

    Now, you can happily code away on your new branch, making commits, breaking things, and no one on main will be any the wiser. Unless you push it and they see it, but we’ll get to that.

How Many Branches? As Many as You Need

Seriously. A branch is cheap. Make one for everything.

  • Feature Branches: feature/new-dashboard-widget, feature/oauth-integration
  • Bugfix Branches: bugfix/critical-crash-on-prod, bugfix/typo-in-footer
  • Chore Branches: chore/update-dependencies, chore/refactor-old-module
  • Experimental Branches: experimental/ai-powered-toaster-oven (just don’t merge this one into main after your all-nighter).

Each branch should ideally represent a single, isolated piece of work. If you find yourself working on three unrelated things on one branch, you’re doing it wrong. Stop. Go back and branch again.

When to Delete These Branches?

Once a branch’s changes are successfully integrated into the main branch (via a merge, which we’ll cover next), that feature branch has served its purpose. There’s almost never a point in keeping a cluttered list of these old branches. It adds overhead and makes git branch output look like a tangled mess. Clean up after yourself.

  • git branch -d <name> (Safe Delete Local Branch): This command deletes a local branch only if it has been fully merged into your current branch. Git won’t let you accidentally lose work.

    # switch back to main
    git checkout main
    git branch -d feature/user-profile
    # If not fully merged, Git will complain: "The branch 'feature/user-profile' is not fully merged. If you still want to delete it, read below."
    
  • git branch -D <name> (Force Delete Local Branch): Use the uppercase -D to force delete a branch, even if it hasn’t been merged. Only use this if you are absolutely, 100%, undeniably sure you don’t need the changes on that branch. This is the “I know what I’m doing and I accept the consequences” flag.

    git branch -D experimental/trash-fire-idea
    # poof. gone. like a dad gone out to buy some milk.
    
  • git push origin --delete <name> (Delete Remote Branch): Deleting a local branch doesn’t delete it from GitHub. You need to explicitly tell the remote to remove it.

    git push origin --delete feature/user-profile
    # or the shorter syntax:
    git push origin :feature/user-profile
    

    This ensures your remote repository stays clean and manageable.

The Unification: merge and Pull Requests (PRs)

You’ve spent days (or minutes, you sloppy vibecoder) playing around on your feature branch, making buggy, broken commits. Now what? You are an intern. You’re destined to break things in production. So, naturally, you want those buggy commits/changes in the main branch so they can eventually be deployed and seen by the your users. This is where Pull Requests (PRs) come into play.

What’s a Pull Request (PR)?

A Pull Request isn’t a Git command; it’s a GitHub (or GitLab/Bitbucket) feature. It’s you submitting your work for review. It’s you begging like a slave to your team (or whoever reviews it) saying:

“Hey, I’ve done some work on feature/broken-thing. It works (I am lying), pwease accept it into the main branch 👉🏻👈🏿.r”

This is the core of modern, sane collaboration:

  1. Code Review: Others examine your changes. They check for bugs, performance issues, general sanity, AI slop, yada yada.
  2. Discussion: Comments, suggestions, and nitpicks fly back and forth. You fix things, they approve.
  3. CI/CD Integration: Automated tests run. Linters check your code. Build pipelines kick off. If everything passes, green checkmarks. If not, red X’s and your destiny changes from making buggy changes to the inevitable public execution.
  4. Merge: Once approved and all checks pass, your branch is merged.

A PR is not just about merging code; it’s about quality control, knowledge sharing, and avoiding disasters.

git pull / git fetch: How to Get the Latest Changes Without Screwing Up

Before you even think about starting new work or merging your branch, you need to update your local repository with the latest changes from the remote. Someone might have pushed new stuff to main while you were taking your beauty nap.

  • git fetch: This command downloads new commits and branches from the remote repository but does not integrate them into your current working branch. It merely updates your local copy of the remote’s branches (e.g., origin/main). It’s like checking the mail without opening any letters.

    git fetch origin
    # You'll see: "remote: Counting objects... Unpacking objects... From github.com:username/repo-name * [new branch] main -> origin/main"
    # Your local 'main' branch is still behind, but your 'origin/main' reference is up-to-date.
    

    You can then use git log origin/main to see what changes are waiting for you, or git diff main origin/main to see the exact differences.

  • git pull: This command is essentially git fetch followed by git merge. It downloads the latest changes from the remote and immediately tries to merge them into your current branch. This is the common shortcut, but it can lead to immediate merge conflicts if you’re not careful.

    # Make sure you're on the branch you want to update (e.g., main)
    git checkout main
    git pull origin main
    # Or, if you used 'git push -u origin main' before:
    git pull
    

    Best Practice:

    1. Start new work by fetching and pulling main into your local main branch:

      git checkout main
      git pull origin main
      
    2. Then, create your new feature branch from the updated main:

      git checkout -b feature/my-new-thing
      
    3. Before submitting a PR for your feature branch, update it with the latest main changes:

      # from your feature or whatever other branch
      git pull origin main # This pulls origin/main into your feature branch, often resulting in a merge
      

This prevents you from building a feature on stale code, minimizing painful conflicts later.

git merge <branch-name>: The Command That Joins Two Timelines

This is where the parallel universe you created comes crashing back into the main timeline. git merge takes all the commits from the specified branch and integrates them into your current branch.

How It Works

  • Fast-Forward Merge: If your current branch (main) hasn’t had any new commits since you created your feature branch, Git just “fast-forwards” the main branch pointer to the latest commit of your feature branch. It’s a clean, linear history. No new commit is created.
  • Three-Way Merge (Merge Commit): If both your current branch (main) and your feature branch have diverged (i.e., new commits have been added to main since you created your feature branch, and you have commits on your feature branch), Git creates a new commit called a “merge commit.” This merge commit has two parents (the latest commit from main and the latest commit from your feature branch), effectively bringing both histories together.

Usage: Let’s say you’re on main and want to bring in changes from feature/user-profile:

# First, ensure your main branch is up-to-date
git switch main
git pull origin main

# then, merge your feature branch into main
git merge feature/user-profile

Output (successful merge):

Updating a0b1c2d..f1d3a5b
Fast-forward
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
# Or for a three-way merge:
Merge branch 'feature/user-profile'
# Conflicts might happen here!

The Inevitable Pain: Merge Conflicts

This section is dedicated to the moment Git throws its hands up in the air, looks you dead in the eye, and screams, “YOU and someone ELSE changed the SAME LINE in the SAME FILE. I’m not resolving this. YOU FIX IT!”

A merge conflict occurs when Git cannot automatically reconcile differences between two converging branches. This usually happens when:

  • Two different branches modify the exact same lines in the same file.
  • One branch deletes a file, and another branch modifies it.
  • Two branches add a file with the same name, but different content, in the same location.

When a merge conflict happens, Git pauses the merge process. Your terminal will look something like this:

Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

And if you run git status, you’ll see:

On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified: index.html

no changes added to commit (use "git add" and/or "git commit -a")

Now, go open the conflicted file (index.html in this example). It will look like a crime scene.

The Ugly Markers of Doom: Git inserts special markers into the file to show you where the conflict is:

<<<<<<< HEAD
<p>This is my useless new paragraph.</p>
=======
<p>This is someone else's better paragraph.</p>
>>>>>>> feature/user-profile
  • <<<<<<< HEAD: Marks the beginning of the conflicting change from your current branch (where HEAD is pointing, usually main).
  • =======: This is the separator. Everything above it is from HEAD, everything below it is from the other branch.
  • >>>>>>> feature/user-profile: Marks the end of the conflicting change from the other branch you’re trying to merge (feature/user-profile).

How to Resolve This Hellish Mess

  1. Manually edit the file(s): Delete the <<<<<<<, =======, and >>>>>>> markers. Then, combine or choose the lines of code that you actually want to keep.

    • Option 1: Keep only your changes:

      <p>This is my useless new paragraph.</p>
      
    • Option 2: Keep only their changes:

      <p>This is someone else's better paragraph.</p>
      
    • Option 3: Combine both:

      <p>
        This is my useless new paragraph and their better different paragraph
        combined, making it all useless now.
      </p>
      
  2. Tell Git you’ve resolved the conflict:

    git add index.html
    

    This stages the merged content. Try git status and read what it is displaying now.

  3. Commit the resolution:

    git commit -m "Merge branch 'feature/user-profile' into main"
    

    This creates the merge commit and finalises the integration of the two branches.

This is a rite of passage. Every developer, at some point, will face the <<<<<<< HEAD monster. Learn from it. And for the love of all, test your code after resolving conflicts. You don’t want to merge a broken mess into main.


You’ve mastered the fundamentals. You can add, commit, push, and resolve merge conflicts. This completes all your foundation for Git/GitHub and ways to collaborate with people.

Go forth. Branch. Merge. And may your conflicts be minimal, and your resolutions swift. Try things out yourself, not just when I mention them. Learn to break stuff. Make yourself uncomfortable, that’s how you learn.

See you in the next part, stranger :)