Welcome to Chapter 16! So far, we’ve explored the fundamental building blocks of Git, delved into its internals, mastered branching, and understood how to interact with remote repositories like GitHub. Now, it’s time to put all that knowledge to the ultimate test: building a collaborative application in a simulated team environment. This chapter is where theory meets practice, allowing you to experience the full power of Git and GitHub in a real-world scenario.

This chapter is designed to be highly hands-on. You’ll play the role of multiple developers, tackling common challenges like merge conflicts, coordinating features, and maintaining a clean project history. We’ll reinforce best practices for team workflows, code reviews, and troubleshooting, ensuring you’re well-equipped for your next collaborative project. Get ready to build, solve, and learn by doing!

Before we dive in, make sure you’re comfortable with:

  • Git Basics: Committing, pushing, pulling, cloning.
  • Branching & Merging: Creating branches, switching between them, and merging changes.
  • Remote Repositories: Working with GitHub (or a similar platform), understanding origins, and pull requests.
  • Text Editor: Having a good text editor (like VS Code, Sublime Text, or similar) ready to modify code files.

Ready to start building? Let’s go!

Setting Up Our Collaborative Project

In a real-world scenario, a team would decide on a project, its initial structure, and the tools they’ll use. For our purposes, we’ll simulate building a very simple “Collaborative Note-Taking App.” This app will have a frontend part (a basic HTML page) and a backend part (a placeholder Node.js server).

We’ll use GitHub as our central collaboration hub, which is a modern industry standard. If you don’t have a GitHub account, please create one now.

Step 1: Create a New Repository on GitHub

The first step for any new team project is to create a shared repository.

  1. Go to GitHub: Open your web browser and navigate to github.com.
  2. Create New Repository: Click the + icon in the top right corner and select New repository.
  3. Repository Details:
    • Repository name: collaborative-notes-app
    • Description (Optional): “A simple app to demonstrate Git collaboration.”
    • Public/Private: Choose Public for this exercise.
    • Initialize this repository with: Check Add a README file. This gives us a starting point.
    • Add .gitignore: Select Node from the dropdown (since our backend will be Node.js). This automatically adds common files to ignore.
    • Choose a license: Select MIT License.
  4. Create Repository: Click the Create repository button.

Congratulations! You’ve just set up the foundation for your collaborative project. You should see your new repository with a README.md and a .gitignore file.

Step 2: Clone the Repository Locally

Now, let’s get a local copy of this repository on your machine. This will be your “main” development environment.

  1. Copy Clone URL: On your GitHub repository page, click the green Code button and copy the HTTPS URL.

  2. Open Terminal/Git Bash: Navigate to a directory where you want to store your projects.

  3. Clone the Repository: Run the git clone command, pasting the URL you copied.

    git clone https://github.com/YOUR_USERNAME/collaborative-notes-app.git
    
    • Explanation: git clone downloads a copy of the remote repository to your local machine, creating a new directory with the repository’s name. It also automatically sets up a remote named origin pointing to the GitHub repository.
  4. Navigate into the Project Directory:

    cd collaborative-notes-app
    
  5. Verify Status: Check the status of your local repository.

    git status
    
    • Expected Output: You should see On branch main and Your branch is up to date with 'origin/main'. This confirms you’re on the main branch and everything is synced.

Simulating Team Development: Alice (Frontend) and Bob (Backend)

In a real team, different developers work on different parts of the application. We’ll simulate this by having you, the user, play two roles: “Alice” (Frontend Developer) and “Bob” (Backend Developer).

Scenario 1: Alice Starts the Frontend

Alice’s task is to create the basic HTML structure for the frontend.

  1. Create Alice’s Feature Branch: It’s a best practice for developers to work on feature branches, not directly on main.

    git switch -c feature/alice-frontend-ui
    
    • Explanation: git switch -c creates a new branch and immediately switches you to it. Our new branch is named feature/alice-frontend-ui, clearly indicating its purpose and who’s working on it.
  2. Create Frontend Directory and File: Now, let’s add the frontend structure.

    mkdir frontend
    # For Windows users, use 'type nul > frontend/index.html'
    # For macOS/Linux users:
    touch frontend/index.html
    
    • Explanation: We’re creating a frontend directory and an index.html file inside it.
  3. Add Basic HTML Content: Open frontend/index.html in your text editor and add the following simple HTML:

    <!-- frontend/index.html -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Collaborative Notes - Frontend</title>
    </head>
    <body>
        <h1>Welcome to Collaborative Notes!</h1>
        <p>This is the initial frontend UI.</p>
        <div id="notes-container">
            <!-- Notes will be loaded here -->
        </div>
    </body>
    </html>
    
    • Explanation: A very basic HTML structure to represent our frontend.
  4. Stage and Commit Alice’s Changes:

    git add .
    git commit -m "feat(frontend): Initial frontend UI setup"
    
    • Explanation: git add . stages all new and modified files in the current directory. git commit -m "..." saves these staged changes to your local branch history with a descriptive message.
  5. Push Alice’s Branch to GitHub:

    git push -u origin feature/alice-frontend-ui
    
    • Explanation: git push -u origin feature/alice-frontend-ui pushes your local branch to the remote origin (GitHub) and sets it up so that future git push and git pull commands for this branch will automatically track the remote branch.
  6. Create a Pull Request (PR) on GitHub (Alice):

    • Go back to your GitHub repository page. You should see a banner indicating that feature/alice-frontend-ui was recently pushed.
    • Click the Compare & pull request button.
    • Title: “feat(frontend): Initial frontend UI setup”
    • Description: “Adds the basic HTML structure for the frontend application.”
    • Click Create pull request.
    • Simulate Review: In a real team, others would review this. For now, you can click Merge pull request and Confirm merge.
    • Clean Up (Optional but Recommended): Click Delete branch after merging. This removes the feature branch from GitHub, keeping your repository tidy.
  7. Sync Local main Branch: Now that Alice’s changes are merged into main on GitHub, your local main branch is out of date.

    git switch main
    git pull origin main
    
    • Explanation: We switch back to main and then git pull origin main fetches the latest changes from the remote main branch and integrates them into your local main branch.

Scenario 2: Bob Starts the Backend

Now, let’s switch hats and act as Bob, the backend developer. Bob’s task is to set up a placeholder for the backend API.

  1. Create Bob’s Feature Branch:

    git switch -c feature/bob-backend-api
    
  2. Create Backend Directory and File:

    mkdir backend
    # For Windows users, use 'type nul > backend/server.js'
    # For macOS/Linux users:
    touch backend/server.js
    
  3. Add Basic Node.js Content: Open backend/server.js in your text editor and add the following (minimalist) Node.js code:

    // backend/server.js
    const http = require('http');
    
    const hostname = '127.0.0.1';
    const port = 3000;
    
    const server = http.createServer((req, res) => {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/plain');
      res.end('Hello from the Backend API!\n');
    });
    
    server.listen(port, hostname, () => {
      console.log(`Server running at http://${hostname}:${port}/`);
    });
    
    • Explanation: A very basic HTTP server that responds with a simple message.
  4. Stage and Commit Bob’s Changes:

    git add .
    git commit -m "feat(backend): Initial backend API setup"
    
  5. Push Bob’s Branch to GitHub:

    git push -u origin feature/bob-backend-api
    
  6. Create a Pull Request (PR) on GitHub (Bob):

    • Go to GitHub.
    • Click Compare & pull request.
    • Title: “feat(backend): Initial backend API setup”
    • Description: “Adds a basic Node.js server placeholder for the API.”
    • Click Create pull request.
    • Simulate Review & Merge: Click Merge pull request and Confirm merge.
    • Clean Up: Click Delete branch.
  7. Sync Local main Branch:

    git switch main
    git pull origin main
    
    • Observation: Now your local main branch has both Alice’s frontend and Bob’s backend directories. Great job collaborating!

Scenario 3: Introducing and Resolving a Merge Conflict

This is where real-world collaboration gets interesting! Imagine both Alice and Bob need to update a shared file, like the README.md, at roughly the same time.

  1. Alice Updates README.md (on a new branch):

    • Switch to a new branch for Alice’s change:

      git switch -c feature/alice-update-readme
      
    • Open README.md in your text editor.

    • Add a line under the “Collaborative Notes - Frontend” title, for example, about the frontend tech stack.

      # collaborative-notes-app
      
      A simple app to demonstrate Git collaboration.
      
      ## Frontend
      - Built with plain HTML/CSS/JS.
      
      ## Backend
      
    • Stage and Commit Alice’s change:

      git add README.md
      git commit -m "docs(readme): Add frontend tech stack details"
      
    • Push Alice’s branch:

      git push -u origin feature/alice-update-readme
      
    • Go to GitHub, create a PR for feature/alice-update-readme, and merge it into main. Delete the branch.

    • Important: Switch back to main and pull the latest changes:

      git switch main
      git pull origin main
      
  2. Bob Updates README.md (on a different new branch, without pulling Alice’s change first):

    • Crucial Step: We need to simulate Bob not having Alice’s latest changes yet. For this, let’s create a new branch from the current (already updated) main, but then we’ll pretend Bob started his work before Alice merged her PR.

    • For simplicity, let’s just create a new branch for Bob’s change:

      git switch -c feature/bob-update-readme
      

      Self-correction: To guarantee a conflict, Bob’s branch should ideally be based on an older state of main. However, in a real scenario, Bob might just start a branch, and Alice merges before Bob finishes. The easiest way to simulate this for learning is for you to make conflicting changes on a new branch.

    • Open README.md in your text editor.

    • Now, Bob wants to add information about the backend, in the same area where Alice added her frontend info. Let’s make it conflict directly on the same line.

    • Modify README.md to look like this (notice the conflicting line):

      # collaborative-notes-app
      
      A simple app to demonstrate Git collaboration.
      
      ## Frontend
      - Initial UI developed.
      
      ## Backend
      - Built with Node.js and Express. (This line conflicts with Alice's update!)
      
      • Trainer Note: I deliberately changed the line - Built with plain HTML/CSS/JS. from Alice’s commit to - Initial UI developed. and then added Bob’s line. If Alice’s line was - Built with plain HTML/CSS/JS. and Bob added his line below it, there would be no conflict. The goal is to make them edit the same conceptual area or even the same line for a clear conflict. Let’s make it simpler and have them both modify the “Frontend” section, or both add a general “Technologies” section.
    • Let’s simplify the conflict for clarity:

      • Alice modified the “Frontend” bullet point.
      • Bob will modify the “Frontend” bullet point too.

      Alice’s README.md (after her merge):

      # collaborative-notes-app
      
      A simple app to demonstrate Git collaboration.
      
      ## Frontend
      - Built with plain HTML/CSS/JS.
      
      ## Backend
      

      Bob’s README.md (on feature/bob-update-readme): Open README.md and change the frontend line again:

      # collaborative-notes-app
      
      A simple app to demonstrate Git collaboration.
      
      ## Frontend
      - Our beautiful user interface.
      
      ## Backend
      
    • Stage and Commit Bob’s change:

      git add README.md
      git commit -m "docs(readme): Update frontend description"
      
    • Push Bob’s branch:

      git push -u origin feature/bob-update-readme
      
    • Go to GitHub and create a PR for feature/bob-update-readme.

    • Observe: GitHub will now likely tell you “This branch has conflicts that must be resolved.” This is because Alice’s change to main affected the same line Bob changed.

  3. Resolving the Merge Conflict Locally (Bob’s perspective):

    • GitHub won’t let you merge Bob’s PR directly. Bob needs to pull the latest changes from main into his feature branch, resolve the conflict locally, and then push the resolved branch.

    • Make sure you are on Bob’s branch (feature/bob-update-readme):

      git switch feature/bob-update-readme
      
    • Pull the latest changes from main into Bob’s branch:

      git pull origin main
      
    • Expected Output: Git will tell you there’s a merge conflict!

      Auto-merging README.md
      CONFLICT (content): Merge conflict in README.md
      Automatic merge failed; fix conflicts and then commit the result.
      
    • Open README.md: Your text editor will now show conflict markers:

      # collaborative-notes-app
      
      A simple app to demonstrate Git collaboration.
      
      ## Frontend
      <<<<<<< HEAD
      - Our beautiful user interface.
      =======
      - Built with plain HTML/CSS/JS.
      >>>>>>> origin/main
      
      ## Backend
      
      • Explanation:
        • <<<<<<< HEAD: Marks the beginning of the conflicting changes from your current branch (HEAD, which is feature/bob-update-readme).
        • =======: Separates the changes from your current branch and the incoming branch.
        • >>>>>>> origin/main: Marks the end of the conflicting changes from the origin/main branch.
    • Manually Resolve the Conflict: You need to decide which changes to keep, or how to combine them. Let’s combine both descriptions. Edit README.md to look like this, removing all conflict markers:

      # collaborative-notes-app
      
      A simple app to demonstrate Git collaboration.
      
      ## Frontend
      - Our beautiful user interface, built with plain HTML/CSS/JS.
      
      ## Backend
      
    • Stage the Resolved File: After editing, tell Git the conflict is resolved.

      git add README.md
      
    • Commit the Merge Resolution: Git will provide a default merge commit message. You can accept it or modify it.

      git commit
      
      • Note: This will open your default text editor (like Vim or Nano) with a pre-filled message. Save and exit the editor (e.g., in Vim, type :wq and press Enter).
    • Push the Resolved Branch: Now that the conflict is resolved locally, push the updated feature branch.

      git push origin feature/bob-update-readme
      
    • Complete the PR on GitHub: Go back to Bob’s Pull Request on GitHub. The “This branch has conflicts…” message should now be gone, and you can click Merge pull request and Confirm merge. Delete the branch afterwards.

    • Sync main again:

      git switch main
      git pull origin main
      
      • Observation: Your README.md on main should now reflect the combined, resolved changes. You’ve successfully navigated a merge conflict!

Advanced Collaboration: Rebasing for a Clean History

While merging is straightforward, sometimes teams prefer a linear, cleaner project history. This is where git rebase comes in. Rebasing rewrites commit history, moving your branch’s commits to appear after the latest commits on the target branch.

When to Rebase?

  • Before merging a feature branch: To ensure your feature branch is up-to-date with main and has a clean, linear history before being merged.
  • To squash commits: Combine multiple small commits into a single, more meaningful one.
  • To clean up local commits: Before pushing to a remote, you might reorder, edit, or squash commits on your local feature branch.

Scenario 4: Alice Rebases Her Branch

Let’s say Alice is working on a new feature, and main has evolved significantly. She wants to incorporate main’s latest changes without creating a merge commit on her feature branch.

  1. Create Alice’s New Feature Branch:

    git switch main
    git pull origin main # Ensure main is up-to-date
    git switch -c feature/alice-new-feature
    
  2. Alice Makes Some Changes:

    • Create a new file frontend/styles.css:

      /* frontend/styles.css */
      body {
          font-family: sans-serif;
          background-color: #f4f4f4;
      }
      h1 {
          color: #333;
      }
      
    • Update frontend/index.html to link the stylesheet:

      <!-- frontend/index.html -->
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Collaborative Notes - Frontend</title>
          <link rel="stylesheet" href="styles.css"> <!-- Added this line -->
      </head>
      <body>
          <h1>Welcome to Collaborative Notes!</h1>
          <p>This is the initial frontend UI.</p>
          <div id="notes-container">
              <!-- Notes will be loaded here -->
          </div>
      </body>
      </html>
      
    • Commit these changes:

      git add .
      git commit -m "feat(frontend): Add basic styling"
      
  3. Simulate main Branch Evolution: While Alice was working, imagine another developer (Bob, or someone else) merged a small change into main. Let’s simulate this directly on main for simplicity (in a real scenario, this would be another PR).

    • Switch to main:

      git switch main
      
    • Make a small, non-conflicting change (e.g., update .gitignore):

      echo "temp_files/" >> .gitignore
      
    • Commit and push to main:

      git add .gitignore
      git commit -m "chore: Add temp_files/ to gitignore"
      git push origin main
      
  4. Alice Rebases Her Feature Branch: Now, Alice wants to bring her feature/alice-new-feature branch up-to-date with main using rebase.

    • Switch back to Alice’s feature branch:

      git switch feature/alice-new-feature
      
    • Start the rebase:

      git rebase main
      
      • Explanation: git rebase main tells Git to take all the commits on your current branch (feature/alice-new-feature) that are not on main, put them aside temporarily, fast-forward feature/alice-new-feature to main’s latest commit, and then re-apply your temporary commits on top of main’s latest.

      • Expected Output: If there are no conflicts, Git will simply re-apply your commit(s) and tell you it’s successful.

        Successfully rebased and updated refs/heads/feature/alice-new-feature.
        
      • Observation: Check git log. You’ll see that Alice’s commit “feat(frontend): Add basic styling” now appears after the chore: Add temp_files/ to gitignore commit from main. The history is linear!

  5. Pushing After Rebase (Force Push Warning!):

    • If you’ve rebased commits that were already pushed to a remote branch, you’ve rewritten history. A normal git push will be rejected. You’ll need to git push --force-with-lease (or git push -f).

    • CRITICAL WARNING: Never force push to shared branches like main or branches other people are actively working on without extreme caution and coordination. Force pushing can overwrite others’ work. For your own feature branch that only you are using, it’s generally safe.

    • In our scenario, Alice’s branch was already pushed. So, to update the remote:

      git push --force-with-lease origin feature/alice-new-feature
      
      • Explanation: --force-with-lease is safer than --force because it will only force push if the remote branch hasn’t been updated by someone else since you last pulled. This prevents accidentally overwriting someone else’s recent work.
    • Now, Alice can open a PR for feature/alice-new-feature. Since the history is linear, it will likely be a “fast-forward” merge on GitHub, meaning no new merge commit is needed.

Rebasing with Conflicts

Just like merging, rebasing can also lead to conflicts. The process for resolving them is similar, but you continue the rebase instead of committing a merge.

  1. If a conflict occurs during git rebase main:
    • Git will pause and tell you about the conflict.
    • You’ll see conflict markers in the affected files, just like with a merge conflict.
    • Resolve the conflicts manually in your editor.
    • git add the resolved files.
    • Instead of git commit, you use git rebase --continue: This tells Git you’ve resolved the current conflict and to continue applying the remaining commits in the rebase sequence.
    • If you want to stop the rebase entirely: git rebase --abort.

Troubleshooting: Detached HEAD State

A “detached HEAD” state occurs when your HEAD (which points to your current commit) is pointing directly to a commit, rather than to a branch name. This often happens if you git checkout a specific commit hash, or a tag.

  1. How it happens:

    git log --oneline
    # Copy a commit hash, e.g., 'abcdef1 Initial commit'
    git checkout abcdef1
    
    • Expected Output: Git will warn you: You are in 'detached HEAD' state.
  2. Why it’s a problem: If you make new commits in a detached HEAD state, those commits won’t belong to any branch. If you then switch to another branch, those “orphaned” commits might be lost unless you explicitly create a branch for them.

  3. How to recover:

    • Option A: Create a new branch from the detached HEAD: This is the most common and safest way to preserve any changes you might have made.

      git switch -c my-new-branch
      
      • Explanation: This creates a new branch named my-new-branch at the commit your HEAD was pointing to, and then switches you to that new branch. Now your work is safe on a named branch.
    • Option B: Go back to a branch: If you didn’t make any changes and just want to return to normal development.

      git switch main
      
      • Explanation: This simply moves your HEAD back to the main branch. Any commits you might have made in the detached state would be orphaned and subject to garbage collection unless referenced by reflog.
    • Important: If you made commits in a detached HEAD state and forgot to create a branch, you can often find them using git reflog and then create a branch from the commit hash you find there.

      git reflog
      # Find your commit hash from the reflog output
      git branch recovery-branch <commit-hash>
      git switch recovery-branch
      

Mini-Challenge: Enhance the Backend with a Conflict

It’s your turn to put your conflict resolution and branching skills to the test!

Challenge:

  1. Ensure your local main branch is up-to-date (git switch main && git pull origin main).
  2. Create a new feature branch called feature/add-api-endpoint.
  3. In backend/server.js, add a new route or modify an existing one to return a list of sample notes.
  4. Crucially: While you’re working on this, simulate another developer (or yourself on main) making a small conflicting change to backend/server.js on the main branch. For example, change the hostname variable or the port variable on main.
  5. Commit your changes on feature/add-api-endpoint.
  6. Try to merge feature/add-api-endpoint into main (or create a PR).
  7. Resolve the inevitable merge conflict locally.
  8. Push your resolved branch and complete the merge on GitHub.

Hint: To simulate the main branch change, after step 3, switch to main, make the conflicting change, commit and push to main. Then switch back to your feature branch before attempting to merge/rebase.

What to Observe/Learn: This challenge will solidify your understanding of:

  • Creating and managing feature branches.
  • The exact steps for resolving merge conflicts.
  • The workflow of pulling, resolving, adding, committing, and pushing.

Common Pitfalls & Troubleshooting

  1. “Your local changes would be overwritten by merge”:

    • Cause: You have uncommitted changes in your working directory, and Git needs to pull new changes that conflict with them.
    • Solution: git stash your changes temporarily, git pull, then git stash pop. Or, commit your changes first (git add ., git commit -m "WIP").
  2. “fatal: refusing to merge unrelated histories”:

    • Cause: You’re trying to merge two repositories that started independently (e.g., you created a local repo, then cloned a remote one and tried to push/pull).
    • Solution: Use git pull origin main --allow-unrelated-histories (only if you are absolutely sure this is what you want). This tells Git to allow merging histories that don’t share a common ancestor. This is often an issue when initializing a local repo, then wanting to link it to an existing remote.
  3. Accidentally git reset --hard and lost work:

    • Cause: git reset --hard discards uncommitted changes and moves your branch pointer to a specific commit, destroying subsequent history.
    • Solution: Use git reflog! This command shows a log of where your HEAD has been. You can find the commit hash of your lost work and then git cherry-pick <commit-hash> or git reset --hard <commit-hash> to recover it. reflog is your safety net!
  4. Force pushing to a shared branch:

    • Cause: You rebased or amended commits on a branch that others have already pulled, and then you force push. This rewrites history and can cause major problems for collaborators.
    • Solution: Avoid force pushing to shared branches. If absolutely necessary, communicate clearly with your team first. Use git push --force-with-lease for safer force pushes on your own feature branches.

Summary

Phew! You’ve just completed a significant milestone: building a collaborative application and navigating the complex waters of team development with Git and GitHub.

Here’s what we covered and reinforced:

  • Project Setup: Initializing a shared repository on GitHub and cloning it locally.
  • Feature Branching Workflow: The standard practice of creating dedicated branches for new features to isolate work and prevent conflicts on main.
  • Pull Requests (PRs): How PRs facilitate code review, discussion, and controlled merging on GitHub.
  • Merge Conflict Resolution: A deep dive into understanding conflict markers (<<<<<<<, =======, >>>>>>>) and the step-by-step process of resolving conflicts locally.
  • Rebasing: Using git rebase to maintain a clean, linear project history by re-applying commits on top of an updated base branch. We also touched upon force pushing after rebase.
  • Detached HEAD: Understanding what it means and how to safely recover from this state by creating a new branch.
  • Common Pitfalls: Practical advice for dealing with common Git issues like uncommitted changes, unrelated histories, and recovering lost work.

You’re now equipped with the practical skills to contribute effectively to team projects, manage your code, and troubleshoot common collaboration hurdles. The ability to resolve conflicts and maintain a healthy Git history is invaluable in any development team.

What’s Next? In the next chapter, we’ll expand on team practices by exploring more advanced topics like Git hooks, integrating Git with Continuous Integration/Continuous Deployment (CI/CD) pipelines, and deeper security considerations in your Git workflow.


References

  1. Git Official Documentation - Git Branching - Basic Branching and Merging: https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging
  2. GitHub Docs - About Pull Requests: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests
  3. Git Official Documentation - Git Branching - Rebasing: https://git-scm.com/book/en/v2/Git-Branching-Rebasing
  4. GitHub Docs - Resolving a merge conflict on GitHub: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-on-github
  5. Atlassian Git Tutorial - Detached HEAD: https://www.atlassian.com/git/tutorials/using-branches/git-detached-head
  6. Git Official Documentation - Git Tools - Rewriting History: https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History

This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.