Chapter 42: Git: Branching, Merging, and Handling Conflicts

Chapter Objectives

By the end of this chapter, you will be able to:

  • Understand the fundamental concepts behind Git’s branching and merging model.
  • Implement common branching workflows, such as feature branching, for systematic development.
  • Perform various types of merges, including fast-forward and three-way merges.
  • Diagnose and resolve merge conflicts effectively.
  • Utilize rebasing to maintain a clean and linear project history.
  • Apply these version control skills to manage a sample embedded project on the Raspberry Pi 5.

Introduction

Complexity is the norm in embedded Linux development. A single project can involve a custom kernel, bootloader modifications, device drivers, multiple user-space applications, and extensive configuration files. Managing changes across this intricate ecosystem without a robust system is not just difficult; it’s practically impossible. This is where a Distributed Version Control System (DVCS) like Git becomes one of the most critical tools in a developer’s arsenal. Unlike centralized systems of the past, Git gives every developer a full, independent copy of the repository, empowering them to work offline and experiment freely.

This chapter delves into the heart of Git’s power: its branching and merging capabilities. Branching allows you to diverge from the main line of development to work on a new feature, a bug fix, or an experimental idea in complete isolation. This ensures that the primary, stable codebase—often called main or master—remains pristine and deployable. Once your work is complete and tested, you can merge it back, integrating your changes into the main project. This workflow is the bedrock of modern collaborative software development, enabling teams to work in parallel without treading on each other’s toes. We will explore how to create and manage branches, the mechanics of merging, and the inevitable challenge of resolving conflicts. By the end, you will not only understand the theory but will have practiced these essential skills, preparing you to contribute confidently to any embedded Linux project.

Technical Background

To truly master Git’s branching and merging, we must first look beneath the surface at how Git thinks about its data. Unlike many other version control systems that store information as a list of file-based changes (deltas or diffs), Git’s core data model is based on snapshots. When you make a commit, Git essentially takes a picture of what all your files look like at that moment and stores a reference to that snapshot. For efficiency, if files have not changed, Git doesn’t store the file again, just a link to the previous identical file it has already stored. This snapshot-based architecture is the key to Git’s speed and its powerful branching model.

branch in Git is not a cumbersome copy of the entire codebase. Instead, it is simply a lightweight, movable pointer to one of these snapshots, or commits. The default branch name in a new repository is typically main. When you start making commits, this main branch pointer moves forward automatically to point to the last commit you made. When you create a new branch, all Git does is create a new pointer to the same commit you are currently on. This operation is incredibly fast and cheap, encouraging developers to use branches frequently.

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'edgeLabelBackground':'#f8fafc', 'clusterBkg': '#f8fafc'}}}%%
graph TD
    subgraph Git Repository History
        C1(fa:fa-code-commit Commit 1<br><i>Initial Commit</i>)
        C2(fa:fa-code-commit Commit 2<br><i>Add main.c</i>)
        C3(fa:fa-code-commit Commit 3<br><i>Refactor code</i>)

        C1 --> C2 --> C3
    end

    subgraph Branch Pointers
        B1["<b>main</b><br>(Branch Pointer)"]
        B2["<b>feature-x</b><br>(New Branch Pointer)"]
    end

    B1 -- points to --> C3
    B2 -- also points to --> C3

    style C1 fill:#8b5cf6,stroke:#8b5cf6,stroke-width:2px,color:#ffffff
    style C2 fill:#8b5cf6,stroke:#8b5cf6,stroke-width:2px,color:#ffffff
    style C3 fill:#8b5cf6,stroke:#8b5cf6,stroke-width:2px,color:#ffffff
    style B1 fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff
    style B2 fill:#0d9488,stroke:#0d9488,stroke-width:2px,color:#ffffff

As you continue working on your new branch, making new commits, the new branch pointer moves forward, while the main branch pointer remains where it was. You have now created a divergence in your development history. This isolation is crucial; it allows you to build and test a new feature without any risk to the stable code on the main branch. You could be developing a new I2C driver on a feature/i2c-driver branch while a colleague is fixing a critical bug on a hotfix/memory-leak branch. Both lines of work proceed in parallel, completely independent of one another.

Branching Strategies

While you can create branches freely, professional development teams typically adopt a structured branching strategy or workflow to keep projects organized. One of the most well-known is Git Flow. It prescribes a complex system of branches, including a master branch for official releases, a develop branch for integrating features, and supporting branches for features, releases, and hotfixes. While comprehensive, its complexity can be overkill for many embedded projects.

Comparison of Git Branching Strategies

Strategy Core Idea Common Branches Best For
GitHub Flow The main branch is always stable and deployable. All work happens in short-lived feature branches. main
feature/*
Most projects, especially those practicing continuous delivery. Its simplicity is ideal for many embedded systems.
Git Flow A stricter model with dedicated branches for features, releases, and hotfixes. master (production releases)
develop (integration)
feature/*
release/*
hotfix/*
Projects with scheduled releases and a need for supporting multiple production versions. Can be overkill for smaller teams.
GitLab Flow A variation of GitHub Flow that adds environment-specific or release branches. main
feature/*
production
pre-production
Projects with multiple deployment environments (e.g., staging, production) or a need to maintain release branches.
Trunk-Based Development All developers commit to a single branch, the “trunk” (main). Short-lived feature branches are optional. main (trunk)
– (Optional) release/*
Highly mature teams with robust automated testing and CI/CD pipelines. Aims to avoid merge conflicts by integrating frequently.

A more streamlined and popular alternative is the GitHub Flow, which is simpler and often a better fit. In this model, the main branch is always considered stable and deployable. To do any work, you create a descriptive feature branch directly from main (e.g., add-pwm-support). All work and commits happen on this branch. When the feature is complete, it is merged back into main, and the feature branch is deleted. This keeps the history clean and ensures main is always a source of truth. For embedded systems, where you might have a “bleeding-edge” development line and a stable production line, a simple strategy like GitHub Flow, perhaps with an additional long-lived production branch for releases, works exceptionally well.

The Art of Merging

Once the work on your feature branch is complete, you need to integrate it back into your main line of development. This is done with the git merge command. The way the merge is performed depends on the relationship between the two branches.

The simplest scenario is a fast-forward merge. This occurs when the main branch has not had any new commits since you created your feature branch. In this case, Git sees that your feature branch’s history is a direct continuation of the main branch’s history. To perform the merge, Git simply moves the main branch pointer forward to point to the same commit as your feature branch. No new commits are created; it’s a clean and linear progression.

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'edgeLabelBackground':'#f8fafc', 'clusterBkg': '#f8fafc'}}}%%
graph TD

    subgraph "Scenario 1: Fast-Forward Merge"
        direction LR
        A1(C1) --> A2(C2);
        A2 -- "main<br>(no new commits)" --- A3( );
        A2 -- "feature-x<br>(new work)" --> A4(C3) --> A5(C4);
        A3[ ] -- "git merge feature-x" --> A5;
        subgraph Legend1 [ ]
            direction LR
            L1(main)
            L2(feature-x)
            L3(Result: main pointer moves to C4)
            style L1 fill:#1e3a8a,stroke:#1e3a8a,color:#ffffff
            style L2 fill:#0d9488,stroke:#0d9488,color:#ffffff
            style L3 fill:#10b981,stroke:#10b981,color:#ffffff
        end
        style A3 fill:#f8fafc, stroke:#f8fafc
    end



    style A1 fill:#8b5cf6,stroke:#8b5cf6,color:#ffffff
    style A2 fill:#8b5cf6,stroke:#8b5cf6,color:#ffffff
    style A4 fill:#8b5cf6,stroke:#8b5cf6,color:#ffffff
    style A5 fill:#8b5cf6,stroke:#8b5cf6,color:#ffffff

However, in a collaborative or long-running project, it’s very likely that the main branch will have received new commits while you were working on your feature. The histories have truly diverged. In this situation, Git cannot simply move the pointer. Instead, it performs a three-way merge. Git looks at three commits to make its decision: the tip of the main branch, the tip of your feature branch, and their common ancestor (the commit where the two branches diverged). Git then creates a new special commit, called a merge commit, that combines the changes from both branches. This merge commit is unique in that it has two parent commits. This preserves the historical context of both lines of development, showing explicitly where a feature was integrated.

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'edgeLabelBackground':'#f8fafc', 'clusterBkg': '#f8fafc'}}}%%
graph TD
    subgraph "Scenario 2: Three-Way Merge"
        direction TB
        B1(C1) --> B2(Common Ancestor<br>C2);
        B2 --> B3("main<br>C3");
        B2 --> B4("feature-x<br>C4");
        B3 -- "git merge feature-x" --> B5("Merge Commit<br>C5");
        B4 -- " " --> B5;
         subgraph Legend2 [ ]
            direction LR
            L4(main)
            L5(feature-x)
            L6(Result: New merge commit C5)
            style L4 fill:#1e3a8a,stroke:#1e3a8a,color:#ffffff
            style L5 fill:#0d9488,stroke:#0d9488,color:#ffffff
            style L6 fill:#f59e0b,stroke:#f59e0b,color:#ffffff
        end
    end




 
    style B1 fill:#8b5cf6,stroke:#8b5cf6,color:#ffffff
    style B2 fill:#8b5cf6,stroke:#8b5cf6,color:#ffffff
    style B3 fill:#1e3a8a,stroke:#1e3a8a,color:#ffffff
    style B4 fill:#0d9488,stroke:#0d9488,color:#ffffff
    style B5 fill:#f59e0b,stroke:#f59e0b,color:#ffffff

Handling Merge Conflicts

The three-way merge process is often automatic. Git is intelligent enough to combine changes as long as they don’t affect the same lines of code in the same file. But what happens when you and another developer both modify the exact same part of a file? This is a merge conflict, and Git cannot resolve it for you because it doesn’t know which change is correct.

When a merge conflict occurs, Git will pause the merge process and report the conflict. It will then modify the problematic file, inserting conflict markers to show you exactly where the clash occurred. These markers look like this:

<<<<<<< HEAD
This is the version of the code from your current branch (e.g., main).
=======
This is the version of the code from the branch you are trying to merge in.
>>>>>>> feature-x

Your job as the developer is to open the file, examine the conflicting sections, and edit the file to be what it should be. This might mean keeping your changes, keeping the other branch’s changes, or writing something entirely new that combines both. Once you have manually edited the file to remove the conflict markers and are satisfied with the result, you must tell Git that you have resolved the conflict. This is done by staging the modified file with git add, and then completing the merge by running git commit.

An Alternative: Rebasing

Another powerful tool for integrating changes is git rebase. While git merge combines two histories with a merge commit, git rebase works differently. It essentially replays your work from one branch on top of another.

Imagine your feature branch diverged from main three commits ago, and main has since moved forward. If you run git rebase main while on your feature branch, Git will:

  1. Find the common ancestor of your feature branch and main.
  2. Temporarily save the changes you made on your feature branch as patches.
  3. Reset your feature branch to be identical to the current main branch.
  4. Apply your saved patches, one by one, creating new commits on top of main.

The end result is that your feature branch now appears as if it had been created from the very latest version of main. This creates a perfectly linear history, which many developers find easier to read and navigate. After rebasing, you can then merge your feature branch into main, and it will be a clean fast-forward merge.

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#f8fafc', 'edgeLabelBackground':'#f8fafc', 'clusterBkg': '#f8fafc'}}}%%
graph TD

    subgraph "Legend"
        direction LR
        L1(main branch)
        L2(feature branch)
        L3(Merge Commit)
        L4(Rebased Commit)
        style L1 fill:#1e3a8a,stroke:#1e3a8a,color:#ffffff
        style L2 fill:#0d9488,stroke:#0d9488,color:#ffffff
        style L3 fill:#f59e0b,stroke:#f59e0b,color:#ffffff
        style L4 fill:#0d9488,stroke:#0d9488,color:#ffffff
    end
    subgraph "Option 1: Merge"
        E(C1) --> F(C2)
        F --> G("main: C3")
        F --> H("feature: C4")
        G --> I("Merge Commit: C5")
        H --> I
        style G fill:#1e3a8a,stroke:#1e3a8a,color:#ffffff
        style H fill:#0d9488,stroke:#0d9488,color:#ffffff
        style I fill:#f59e0b,stroke:#f59e0b,color:#ffffff
    end

    subgraph "Option 2: Rebase"
        J(C1) --> K(C2) --> L("main: C3")
        L --> M("feature: C4'")
        M -- "Clean, linear history" --> N(( ))
        style L fill:#1e3a8a,stroke:#1e3a8a,color:#ffffff
        style M fill:#0d9488,stroke:#0d9488,color:#ffffff
        style N fill:#f8fafc, stroke:#f8fafc
    end
        subgraph "Before: Diverged History"
        A(C1) --> B(C2)
        B --> C("main: C3")
        B --> D("feature: C4")
        style C fill:#1e3a8a,stroke:#1e3a8a,color:#ffffff
        style D fill:#0d9488,stroke:#0d9488,color:#ffffff
    end



    style A fill:#8b5cf6,stroke:#8b5cf6,color:#ffffff
    style B fill:#8b5cf6,stroke:#8b5cf6,color:#ffffff
    style E fill:#8b5cf6,stroke:#8b5cf6,color:#ffffff
    style F fill:#8b5cf6,stroke:#8b5cf6,color:#ffffff
    style J fill:#8b5cf6,stroke:#8b5cf6,color:#ffffff
    style K fill:#8b5cf6,stroke:#8b5cf6,color:#ffffff

Warning: Rebasing rewrites history. The new commits it creates have different IDs than the original ones. This is perfectly fine for your own local branches, but you should never rebase a branch that has been pushed and is being used by other developers. Doing so will create divergent histories for everyone else, leading to a very confusing and difficult-to-fix repository state. The golden rule is: rebase your own private work, but merge public/shared work.

Practical Examples

Let’s put this theory into practice using a hypothetical project on our Raspberry Pi 5. We’ll simulate the development of a simple C application that controls an LED.

Setup: Creating the Project

First, let’s create a project directory and initialize a Git repository. We’ll also create a simple C file and make our first commit.

Bash
# On your Raspberry Pi 5
mkdir led-control
cd led-control
git init

Now, create a file named led.c with some initial content.

C
// led.c
#include <stdio.h>

int main() {
    printf("Embedded LED Control Program\n");
    printf("Status: System Ready\n");
    return 0;
}

Add this file to the repository and commit it.

Bash
git add led.c
git commit -m "Initial commit: Add basic program structure"

Example 1: Feature Branch and Fast-Forward Merge

We need to add a function to turn the LED on. We’ll do this on a feature branch.

Bash
# Create and switch to a new branch called 'feature/led-on'
git checkout -b feature/led-on

# You can also use the newer 'git switch' command:
# git switch -c feature/led-on

Your active branch is now feature/led-on. Let’s modify led.c to add the new function.

C
// led.c
#include <stdio.h>

void turn_on_led() {
    printf("LED -> ON\n");
}

int main() {
    printf("Embedded LED Control Program\n");
    printf("Status: System Ready\n");
    
    turn_on_led(); // Call the new function
    
    return 0;
}

Now, commit this change to the feature branch.

Bash
git add led.c
git commit -m "feat: Implement turn_on_led function"

Our feature is complete. Let’s merge it back into main. First, we switch back to the main branch.

Bash
git checkout main

Now, we execute the merge.

Bash
git merge feature/led-on

You will see output similar to this:

Plaintext
Updating a1b2c3d..e4f5g6h
Fast-forward
 led.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

This was a fast-forward merge because main hadn’t changed. The history is still linear. You can now safely delete the feature branch.

Bash
git branch -d feature/led-on

Example 2: Three-Way Merge

Let’s simulate a more complex scenario. We’ll create a branch to add a “turn off” function. But while we’re working on it, a critical “hotfix” needs to be applied to main.

First, create the new feature branch from main.

Bash
git checkout -b feature/led-off

Modify led.c on this branch.

C
// led.c
#include <stdio.h>

void turn_on_led() {
    printf("LED -> ON\n");
}

void turn_off_led() {
    printf("LED -> OFF\n");
}

int main() {
    printf("Embedded LED Control Program\n");
    printf("Status: System Ready\n");
    
    turn_on_led();
    turn_off_led(); // Call the new function
    
    return 0;
}

Commit the change on the feature/led-off branch.

Bash
git add led.c
git commit -m "feat: Implement turn_off_led function"

Now, imagine an urgent bug is found. We need to switch back to main and fix it immediately.

Bash
git checkout main

Let’s modify led.c on main to add a version number. This is our “hotfix”.

C
// led.c
#include <stdio.h>

// Version 1.1
void turn_on_led() {
    printf("LED -> ON\n");
}

int main() {
    printf("Embedded LED Control Program v1.1\n"); // Updated line
    printf("Status: System Ready\n");
    
    turn_on_led();
    
    return 0;
}

Commit the hotfix on main.

Bash
git add led.c
git commit -m "fix: Add version number and update title"

Now the histories have diverged. main has a commit that feature/led-off does not, and vice-versa. Let’s merge the feature branch into main.

Bash
git merge feature/led-off

Because Git can’t do a fast-forward, it will perform a three-way merge. It will likely open your default text editor to ask for a merge commit message. The default message is usually sufficient. Just save and close the editor.

The result is a new merge commit on main that combines both lines of work. If you inspect led.c, you’ll see it contains both the version update and the turn_off_led function.

Example 3: Creating and Resolving a Merge Conflict

Now for the classic problem. Let’s create two branches from main and modify the same line on both.

Bash
# From main, create branch A
git checkout -b feature/status-a

# Modify the status line in led.c
# Change "Status: System Ready" to "Status: System A Ready"
# Then commit
git add led.c
git commit -m "feat: Update status for system A"

# Go back to main and create branch B
git checkout main
git checkout -b feature/status-b

# Modify the SAME status line in led.c
# Change "Status: System Ready" to "Status: System B Online"
# Then commit
git add led.c
git commit -m "feat: Update status for system B"

We now have two branches with conflicting changes. Let’s try to merge feature/status-a into main. This will be a fast-forward.

Bash
git checkout main
git merge feature/status-a

So far, so good. But now, let’s try to merge feature/status-b into main.

Bash
git merge feature/status-b

Git will fail with a message:

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

If you open led.c, you will see the conflict markers:

C
// led.c
#include <stdio.h>

// ... other code ...

int main() {
    printf("Embedded LED Control Program v1.1\n");
<<<<<<< HEAD
    printf("Status: System A Ready\n");
=======
    printf("Status: System B Online\n");
>>>>>>> feature/status-b
    
    turn_on_led();
    
    return 0;
}
```HEAD` refers to your current branch (`main`), which has the "System A" change. The incoming branch `feature/status-b` has the "System B" change. To resolve this, we must edit the file. Let's say the correct status should be a combination of both. We edit the file to look like this, removing the markers:

```c
// led.c
#include <stdio.h>

// ... other code ...

int main() {
    printf("Embedded LED Control Program v1.1\n");
    printf("Status: System A Ready, System B Online\n"); // Manually resolved
    
    turn_on_led();
    
    return 0;
}

Now, we tell Git the conflict is resolved by staging the file and committing.

Bash
git add led.c
git commit 
# A pre-populated commit message will appear. Just save and close.

The conflict is now resolved, and the merge is complete.

Common Mistakes & Troubleshooting

Even with a solid understanding, pitfalls are common. Here are some frequent mistakes and how to handle them.

Mistake / Issue Symptom(s) Troubleshooting / Solution
Developing on `main` You’ve made several commits for a new feature directly on the main or master branch. 1. Create a new branch from the current state:
git checkout -b new-feature
2. Reset `main` back to its original state (e.g., from the remote):
git checkout main
git reset –hard origin/main
Rebasing a Public Branch After you git push –force, teammates complain about errors when they try to pull changes. The project history has diverged. This is difficult to fix and requires team coordination.
Prevention is key: Never rebase a branch that others are using. If it happens, you may need to use git reflog to find the pre-rebase state and force-push a fix.
Massive, Unrelated Commits A single commit contains changes for multiple features, bug fixes, and style tweaks. It’s impossible to review or revert a specific change. Make small, atomic commits. Use interactive staging to select which changes go into a commit:
git add -p
This lets you stage parts of files, creating focused commits.
Useless Commit Messages History is full of messages like “fix”, “changes”, or “wip”, making it hard to understand the project’s evolution. Follow a convention. A good practice is:
– Imperative summary (e.g., feat: Add LED blink function)
– Optional detailed body explaining the ‘why’.
Merging in the Wrong Direction You merged main into your feature branch, creating messy, unnecessary merge commits in your feature’s history. To sync your feature branch with main, prefer rebasing:
git checkout feature-branch
git rebase main
This keeps your feature branch history clean and linear.
Lost Work / Detached HEAD You checked out a commit directly, made changes, and now can’t find them. Your HEAD is “detached.” 1. Don’t panic. Your commits are not lost yet.
2. Immediately create a branch to save your work:
git checkout -b saved-work
3. You can now merge this branch back into your main line of development.

Exercises

  1. Basic Branch and Merge:
    • Objective: Practice the fundamental feature branch workflow.
    • Steps:
      1. In your led-control project, ensure you are on the main branch.
      2. Create a new branch named feature/add-delay.
      3. On this new branch, add a sleep(1); call from <unistd.h> between turning the LED on and off in led.c.
      4. Commit this change with a descriptive message.
      5. Switch back to the main branch.
      6. Merge the feature/add-delay branch.
    • Verification: Confirm the merge was a fast-forward and that led.c on main now contains the sleep(1); call.
  2. Simple Conflict Resolution:
    • Objective: Learn to resolve a straightforward merge conflict.
    • Steps:
      1. From main, create a branch called refactor/on-message.
      2. On this branch, change the printf message in turn_on_led() to “GPIO Pin 17: HIGH”. Commit the change.
      3. Switch back to main. Create another branch called refactor/on-message-verbose.
      4. On this second branch, change the same printf message to “Setting LED output to ON state”. Commit the change.
      5. Merge refactor/on-message into main.
      6. Now, attempt to merge refactor/on-message-verbose into main.
      7. Resolve the resulting conflict by choosing one message or creating a new one.
      8. Complete the merge.
    • Verification: The main branch should have a new merge commit, and led.c should contain your resolved message.
  3. Rebasing for a Clean History:
    • Objective: Use git rebase to create a linear history.
    • Steps:
      1. Switch back to the main branch and create a new branch feature/blinking.
      2. On this branch, make two separate commits: first, add a for loop around the on/off functions to make the LED blink; second, add a comment explaining the blink functionality.
      3. While that was happening, switch back to main and add a new comment at the top of the file, like a copyright notice, and commit it.
      4. Now switch back to the feature/blinking branch.
      5. Instead of merging, rebase your work on top of main using git rebase main.
      6. Switch to main and merge feature/blinking.
    • Verification: The merge into main should be a fast-forward. Use git log --oneline --graph to see the clean, linear history.
  4. Using git stash for a Hotfix:
    • Objective: Practice using git stash to manage work-in-progress during an interruption.
    • Steps:
      1. Create a new branch feature/brightness-control.
      2. Start modifying led.c to add a placeholder function set_brightness(int level), but do not commit it. Your work is in progress.
      3. Suddenly, you need to fix a typo on main. Use git stash to save your uncommitted changes.
      4. Switch to main, fix a typo somewhere in a comment, and commit the fix.
      5. Switch back to feature/brightness-control.
      6. Use git stash pop to re-apply your saved changes and continue your work.
    • Verification: Your uncommitted changes to led.c should reappear, and you can continue where you left off.
  5. Collaborative Conflict Simulation:
    • Objective: Simulate a conflict scenario that occurs in a team environment.
    • Steps:
      1. Create a second clone of your local repository in a different directory: git clone ./led-control ../led-control-dev2.
      2. In the original led-control repo, create a branch dev1-work and add a function void cleanup_gpio() at the end of led.c. Commit the change.
      3. In the led-control-dev2 repo, create a branch dev2-work and add a different function void shutdown_system() at the end of led.c. Commit the change.
      4. In the original repo, merge dev1-work into main.
      5. Now, simulate the second developer pushing their work. In the led-control-dev2 repo, you can’t push to the other local repo directly without setting up a remote, so we’ll simulate it by creating a patch: git format-patch main. This creates a .patch file.
      6. Copy that patch file over to the original led-control directory.
      7. In the original repo, on main, try to apply the patch: git am < patch-file-name.patch.
      8. This should cause a conflict. Resolve it by ensuring both new functions are present in the final file. Use git am --continue to finish.
    • Verification: The main branch in the original repository should now contain both the cleanup_gpio and shutdown_system functions.

Summary

  • Git Branches are Pointers: A branch is a lightweight, movable pointer to a commit, making them fast and easy to use.
  • Branching Isolates Work: Creating feature branches allows you to develop new code, fix bugs, or experiment without destabilizing the main codebase.
  • Merging Integrates Work: git merge is used to combine the history of two branches. This can be a simple fast-forward or a three-way merge that creates a new merge commit.
  • Conflicts are Normal: Merge conflicts happen when two branches have competing changes to the same part of a file. They are resolved by manually editing the file, staging the change with git add, and committing.
  • Rebasing Creates Linear History: git rebase re-applies commits from one branch on top of another, which is useful for cleaning up local history before merging. However, it rewrites history and must not be used on shared branches.
  • Workflows Provide Structure: Adopting a branching strategy like GitHub Flow brings order and predictability to collaborative projects.

By mastering these workflows, you have gained a foundational skill for any modern software development, especially in the complex and collaborative field of embedded Linux.

Further Reading

  1. Pro Git Book: Scott Chacon and Ben Straub. The definitive, free online book on Git. Chapters 3 (“Git Branching”) and 6 (“Git Tools”) are particularly relevant. https://git-scm.com/book/en/v2
  2. Git Documentation: The official documentation for the git mergegit rebase, and git branch commands. Always an authoritative source. https://git-scm.com/docs
  3. Atlassian’s Git Tutorials: An excellent collection of tutorials covering everything from basic concepts to advanced workflows. Their explanation of comparing workflows is very insightful. https://www.atlassian.com/git/tutorials/comparing-workflows
  4. A successful Git branching model by Vincent Driessen: The original, classic blog post that proposed the “Git Flow” model. While not always the best fit, understanding it provides deep insight into branching strategies. https://nvie.com/posts/a-successful-git-branching-model/
  5. GitHub Flow: The official guide explaining the simpler GitHub Flow model. https://docs.github.com/en/get-started/quickstart/github-flow
  6. The Git Parable by Tom Preston-Werner: An easy-to-read story that explains the core concepts behind Git’s design, helping to build a better mental model of how it works. https://tom.preston-werner.com/2009/05/19/the-git-parable.html

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top