Introduction
In our previous lecture, we explored branching strategies and workflows in Git. We discussed how branches allow developers to work independently without affecting the main codebase. But what happens when it's time to combine these separate lines of development?
This is where merging and rebasing come in—two different approaches to integrating changes from one branch into another. While they achieve similar goals, they do so in different ways, each with its own advantages and considerations.
Just as important is understanding how to handle conflicts—those inevitable situations where Git needs human intervention to resolve competing changes. Successfully navigating merge conflicts is a critical skill for any developer using Git.
In this lecture, we'll dive deep into:
- The mechanics of merging and when to use it
- The rebasing process and its advantages
- How to effectively resolve merge conflicts
- Advanced techniques for integrating changes
- Best practices to minimize and manage conflicts
By the end of this lecture, you'll understand how to seamlessly integrate changes across branches and confidently handle any conflicts that arise.
Merging Fundamentals
Merging is the process of integrating changes from one branch into another. It's like combining two separate streams of work into a unified whole.
Types of Merges
Git performs different types of merges depending on the relationship between the branches:
Fast-Forward Merge
When the target branch is a direct ancestor of the source branch (i.e., no divergent work on the target branch), Git performs a "fast-forward" merge by simply moving the pointer of the target branch forward.
In this scenario, main was not changed since feature was created, so merging feature into main just moves the main pointer forward to include C3 and C4.
Command:
git checkout main
git merge feature
Three-Way Merge
When both branches have diverged (i.e., each has unique commits), Git performs a "three-way" merge, creating a new merge commit that represents the integration of the two branches.
Here, both main and feature have new commits since they diverged. The merge creates a new commit (C5) with two parents.
Command:
git checkout main
git merge feature
This creates a merge commit with a default commit message, which you can customize in the editor that opens.
Squash Merge
A squash merge takes all the changes from the source branch and condenses them into a single commit on the target branch.
The resulting commit C5 contains all the changes from C3 and C4, but as a single new commit on main. The feature branch history remains unchanged, but its changes are incorporated as one commit on main.
Command:
git checkout main
git merge --squash feature
git commit -m "Merge feature branch"
Note that --squash doesn't automatically create a commit, so you need to run a separate commit command.
Merge Commit Messages
When performing a merge that creates a new commit, Git generates a default message like "Merge branch 'feature' into main". You can customize this message either by:
- Editing it in the editor that opens during merge
- Using the -m flag:
git merge feature -m "Custom merge message"
A good merge commit message should:
- Describe what is being integrated (e.g., "Merge user authentication feature")
- Mention any special considerations for the merge
- Reference related issues or pull requests
Merge Strategies and Options
Git offers several merge strategies and options for different scenarios:
Merge Strategies
- recursive (default): Used for regular merges between branches
- resolve: Used for merging exactly two heads (less common)
- octopus: Used for merging more than two heads
- ours: Ignores all changes from the merged branch, keeping only the history of the merge
- subtree: Used for merging the history of a project into a subdirectory of another project
Useful Merge Options
- --ff (default): Perform a fast-forward merge when possible
- --no-ff: Always create a merge commit, even if a fast-forward is possible
- --ff-only: Only allow fast-forward merges, aborting if not possible
- --squash: Squash all changes into a single commit
- --strategy=<strategy>: Specify a merge strategy
- -X<option> or --strategy-option=<option>: Pass options to the merge strategy
Example with options:
# Create a merge commit even if fast-forward is possible
git merge feature --no-ff
# Use the "ours" strategy option for the recursive merge strategy
git merge feature -X ours
Rebasing Fundamentals
While merging integrates branches by creating a merge commit, rebasing takes a different approach: it rewrites history by moving the entire branch to a new base commit.
How Rebasing Works
Rebasing is best understood as "replaying" commits from one branch onto another. When you rebase, Git:
- Finds the common ancestor of the two branches
- Stores the changes introduced by the commits in your current branch as temporary files ("patches")
- Resets your current branch to the same commit as the branch you're rebasing onto
- Applies each patch one by one, creating new commits
This effectively moves your branch to have a new starting point, as if you had created it from the latest commit on the target branch.
After rebasing feature onto main:
Notice that the commits C3 and C4 are recreated as C3' and C4' on top of main's latest commit.
Basic rebase command:
git checkout feature
git rebase main
When to Use Rebasing
Rebasing is particularly useful in these scenarios:
Updating a Feature Branch
Keeping a long-running feature branch up to date with the latest main branch changes:
git checkout feature
git rebase main
This keeps your feature branch current without creating merge commits.
Cleaning Up Before Sharing
Cleaning up your local history before pushing to a shared repository:
git checkout feature
git rebase -i HEAD~5 # Interactively rebase the last 5 commits
This allows you to squash, reword, or otherwise edit your commits before sharing them.
Creating Linear History
Some teams prefer a linear commit history without merge commits. After rebasing a feature branch:
git checkout main
git merge feature
This will result in a fast-forward merge since feature is now ahead of main.
The Golden Rule of Rebasing
Never rebase commits that exist outside your repository (i.e., that you've already pushed and others might have based work on).
Why? Because rebasing creates new commits with the same changes but different hashes. If others have based work on your original commits, rebasing will cause major confusion when you try to integrate those histories later.
Think of it like changing the foundation of a building after others have already built on top of it—it can cause the entire structure to collapse.
Breaking this rule can lead to painful situations where the same changes appear multiple times in the history and conflicts become incredibly difficult to resolve.
Interactive Rebasing
One of the most powerful features of Git is interactive rebasing, which gives you control over each commit in the rebasing process:
git rebase -i <base-commit>
This opens an editor with a list of commits and instructions for how to handle each one:
pick 36d1535 Add login form
pick 7bc563a Style login form
pick 08e4e17 Add form validation
pick 2bc4f3c Fix validation bug
pick 0bec652 Update login documentation
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's message
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
By changing the word "pick" to one of the other commands, you can transform your commit history in various ways:
Common Interactive Rebase Operations
- Squashing Related Commits: Combine multiple commits into one
pick 36d1535 Add login form squash 7bc563a Style login form squash 08e4e17 Add form validation - Rewording Commit Messages: Change a commit message
pick 36d1535 Add login form reword 7bc563a Style login form - Removing Commits: Delete a commit entirely
pick 36d1535 Add login form drop 7bc563a Style login form pick 08e4e17 Add form validation - Reordering Commits: Change the order of commits
pick 08e4e17 Add form validation pick 36d1535 Add login form pick 7bc563a Style login form - Editing Commits: Stop at a commit to modify it
pick 36d1535 Add login form edit 7bc563a Style login form pick 08e4e17 Add form validation
Interactive rebasing is like having a time machine for your commits—you can revise history to make it cleaner and more logical before sharing it with others.
Merge vs. Rebase: Choosing the Right Approach
Both merging and rebasing integrate changes from one branch into another, but they do so in fundamentally different ways. Let's compare them to understand when to use each approach.
Conceptual Differences
that combines changes] B -->|Rebase| D[Replay Branch A's
commits on top of Branch B] C --> E[Preserves complete history
Shows when branches
diverged and merged] D --> F[Creates linear history
As if Branch A was
created from latest Branch B] style A fill:#f9f9f9,stroke:#333,stroke-width:2px style B fill:#f9f9f9,stroke:#333 style C fill:#e1f5fe,stroke:#0288d1 style D fill:#fff3e0,stroke:#ff9800 style E fill:#e1f5fe,stroke:#0288d1 style F fill:#fff3e0,stroke:#ff9800
Comparing Characteristics
| Characteristic | Merge | Rebase |
|---|---|---|
| History Structure | Preserves branch history with merge commits | Creates linear history by recreating commits |
| Commit Integrity | Preserves original commits exactly as they were created | Creates new commits with the same changes but different hashes |
| Traceability | Clearly shows when branches diverged and were integrated | Makes it appear as if work was done sequentially |
| Conflict Resolution | Resolve conflicts once during the merge | May need to resolve conflicts for each commit being rebased |
| Safety | Safe to use on public branches | Should only be used on private/local branches |
| Visibility of Integration | Explicit merge commits show where integration happened | No explicit indication of when branches were integrated |
When to Merge
Merging is typically better when:
- Integrating a completed feature: When a feature is complete and ready to be incorporated into the main branch
- Working with shared branches: When the branch has been pushed and others may have based work on it
- Preserving history: When you want to maintain a record of when changes were integrated
- Dealing with complex conflicts: When you'd rather resolve conflicts once instead of potentially multiple times
- Following GitFlow: When using a branching strategy that emphasizes merge commits
When to Rebase
Rebasing is typically better when:
- Keeping a feature branch updated: To incorporate the latest changes from the main branch
- Cleaning up private branches: To reorganize commits before sharing them
- Creating a linear history: When you prefer a straight line of commits without merge bubbles
- Preparing for a pull request: To make your changes appear as if they were made on the latest version
- Following Trunk-Based Development: When using a branching strategy that emphasizes linear history
Hybrid Approaches
Many teams use hybrid approaches that combine the benefits of both methods:
Rebase Then Merge
- Rebase your feature branch onto the latest main
- Create a merge commit when integrating into main
git checkout feature
git rebase main
git checkout main
git merge feature --no-ff
This results in a linear history within the feature branch, but preserves the record of the feature integration.
Squash and Merge
- Clean up your feature branch with interactive rebasing
- Squash all feature commits into a single commit when merging
git checkout feature
git rebase -i main
git checkout main
git merge --squash feature
git commit -m "Add feature X"
This combines the detailed history during development with a clean, focused integration into the main branch.
Understanding Merge Conflicts
Merge conflicts occur when Git cannot automatically reconcile differences between branches. This happens when the same part of a file has been modified in different ways on the branches being integrated.
What Causes Conflicts?
Conflicts typically arise from:
- Concurrent edits: Two developers modifying the same lines of a file
- Incompatible changes: Changes that cannot be automatically merged (e.g., one person modifies a file while another deletes it)
- Structural changes: Significant reorganization of code in one branch while the other makes specific changes
- Long-lived branches: The longer branches diverge, the more likely conflicts become
Anatomy of a Conflict
When Git encounters a conflict, it modifies the affected file to show both versions of the conflicting section:
<<<<<<< HEAD
This is the content from the branch you're merging into (current branch)
=======
This is the content from the branch you're merging from (incoming branch)
>>>>>>> feature-branch
The conflict markers divide the file into sections:
- <<<<<<< HEAD: Marks the beginning of the conflicting section from your current branch
- =======: Separates the two conflicting changes
- >>>>>>> feature-branch: Marks the end of the conflicting section from the incoming branch
Git also updates the staging area to reflect the conflict state:
- Files with resolved conflicts are marked as staged
- Files with unresolved conflicts remain unstaged
- Files without conflicts are automatically staged
You can see this status by running git status during a conflict:
$ git status
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: README.md
Resolving Merge Conflicts
When you encounter a merge conflict, Git pauses the merge process and asks you to resolve the conflicts manually. Here's a step-by-step approach to effective conflict resolution:
Basic Conflict Resolution Process
- Identify conflicting files: Use
git statusto see which files have conflicts - Open each conflicting file: Use your editor to view and edit the conflict markers
- Choose how to resolve each conflict: You can keep one version, combine both, or write something entirely new
- Remove conflict markers: Delete the
<<<<<<<,=======, and>>>>>>>lines - Mark as resolved: Use
git add <file>to mark each file as resolved - Complete the merge: Once all conflicts are resolved, run
git committo create the merge commit
Resolution Strategies
When deciding how to resolve a conflict, consider these strategies:
Accept One Version
If one version is clearly correct, you can choose to keep it entirely:
Keep your changes (current branch):
<<<<<<< HEAD
This is the content to keep
=======
This is the content to discard
>>>>>>> feature-branch
Replace with:
This is the content to keep
Keep their changes (incoming branch):
<<<<<<< HEAD
This is the content to discard
=======
This is the content to keep
>>>>>>> feature-branch
Replace with:
This is the content to keep
Combine Both Changes
Often, you'll want to incorporate elements from both versions:
<<<<<<< HEAD
Users can log in with email
=======
Users can log in with username
>>>>>>> feature-branch
Replace with:
Users can log in with email or username
Create Something New
Sometimes, neither version is quite right, and you need to implement a third solution:
<<<<<<< HEAD
function calculateTotal(items) {
return items.reduce((total, item) => total + item.price, 0);
}
=======
function calculateTotal(items) {
let total = 0;
for (const item of items) {
total += item.price;
}
return total;
}
>>>>>>> feature-branch
Replace with a better implementation:
function calculateTotal(items) {
if (!items || items.length === 0) return 0;
return items.reduce((total, item) => total + (item.price || 0), 0);
}
Using Git Tools for Conflict Resolution
Git provides several commands to help with conflict resolution:
Aborting a Problematic Merge
If you want to cancel a merge and start over:
git merge --abort
This command reverts the working directory and index to their state before the merge began.
Using Built-in Merge Tool
Git has a built-in tool to help resolve conflicts:
git mergetool
This launches a visual diff and merge tool. Git supports various tools like vimdiff, kdiff3, meld, and many others.
Checking Differences Between Branches
To understand the context of changes before resolving conflicts:
git diff --ours # Shows differences between common ancestor and HEAD
git diff --theirs # Shows differences between common ancestor and the branch being merged
Choosing One Side Automatically
For some files, you might want to automatically choose one version:
git checkout --ours path/to/file # Use the version from your current branch
git checkout --theirs path/to/file # Use the version from the incoming branch
git add path/to/file # Mark as resolved
Showing Conflict Markers Again
If you accidentally removed conflict markers but want them back:
git checkout -m path/to/file
This restores the file to its conflicted state with markers.
Visual Merge Tools
Many developers prefer visual tools for resolving conflicts. These tools present the conflicting sections side by side, making it easier to see the differences and choose the correct resolution.
Popular merge tools include:
- VS Code: Built-in merge conflict resolver
- GitKraken: Visual merge tool with intuitive interface
- Meld: Free and open-source diff and merge tool
- Beyond Compare: Powerful commercial diff and merge tool
- kdiff3: Free three-way merge tool
To configure Git to use your preferred tool:
git config --global merge.tool <tool>
git config --global mergetool.<tool>.path /path/to/tool # if needed
For example, to use VS Code:
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'
Advanced Conflict Resolution Techniques
For complex projects or particularly challenging conflicts, you may need more advanced techniques to effectively resolve conflicts and maintain a clean history.
Understanding Conflict Resolution in Rebasing
Rebasing can involve multiple conflict resolution steps, as Git applies each commit one by one:
- Git starts the rebase by trying to apply the first commit
- If a conflict occurs, Git pauses and asks you to resolve it
- After resolution and
git add, you continue withgit rebase --continue - Git tries to apply the next commit, potentially leading to another conflict
- This process repeats until all commits are applied
At any point, you can abort the rebase with git rebase --abort.
For complex rebases, it may be easier to:
- Perform a merge to resolve all conflicts at once
- Reset to the state before the merge
- Then perform the rebase, which will now have the conflict resolution "built in"
# Approach for handling complex rebase conflicts
git checkout feature
git merge main # Resolve all conflicts at once
git reset --soft HEAD~1 # Undo the merge but keep the resolutions
git stash # Save the conflict resolutions
git rebase main # Start the rebase
git stash apply # Apply the saved resolutions
# Resolve any remaining conflicts
git rebase --continue
Cherry-Picking and Conflict Resolution
Cherry-picking applies specific commits from one branch to another and can also lead to conflicts:
git cherry-pick <commit-hash>
If conflicts occur during cherry-picking:
- Resolve conflicts in the files
- Add the resolved files with
git add - Continue with
git cherry-pick --continue - Or abort with
git cherry-pick --abort
Cherry-picking can be especially useful for applying specific bug fixes across multiple release branches.
Resolving Binary File Conflicts
Binary files (images, PDFs, etc.) cannot be merged line-by-line like text files. When binary files conflict, you have three options:
- Choose one version:
git checkout --ours path/to/binary.file # or git checkout --theirs path/to/binary.file git add path/to/binary.file - Use an external merge tool that supports the binary format
- Manually create a new version and replace the conflicted file
For projects with many binary files, consider using Git LFS (Large File Storage) to better handle these files.
Using .gitattributes for Custom Merge Strategies
You can define custom merge strategies for specific files using the .gitattributes file:
# .gitattributes
database.xml merge=ours # Always use our version during merge conflicts
*.generated.cs -merge # Treat as binary (don't try to merge)
*.css merge=union # Combine both versions for CSS files
Common merge strategies in .gitattributes:
- ours: Always keep the current branch's version
- theirs: Always keep the incoming branch's version
- union: Combine both versions (good for files where order doesn't matter)
- -merge: Treat as binary, don't try to merge line by line
Merge Conflict Prevention
The best way to handle conflicts is to prevent them in the first place:
- Frequent integration: Merge or rebase from the main branch often
- Small, focused commits: Keep changes modular and specific
- Clear ownership: Define who's responsible for which parts of the codebase
- Communication: Let teammates know when you're working on shared files
- Code organization: Structure your code to minimize overlap between features
- Automated formatting: Use tools like Prettier or ESLint to standardize formatting
Real-World Merge and Rebase Scenarios
Let's explore some common real-world scenarios and how to handle them effectively using merge and rebase techniques.
Scenario 1: Feature Branch Getting Out of Date
Situation: You've been working on a feature branch for a week, and the main branch has moved forward with other developers' changes.
Solution: Update your feature branch by rebasing on main.
After rebase:
git checkout feature
git rebase main
# Resolve any conflicts that arise
# Continue development on an up-to-date feature branch
Benefit: Your feature is now based on the latest main, making eventual integration smoother.
Scenario 2: Cleaning Up a Feature Branch Before Merging
Situation: Your feature branch has many small, incremental commits with messages like "WIP" or "Fix typo", and you want to clean it up before merging.
Solution: Use interactive rebasing to reorganize and squash commits.
After interactive rebase:
git checkout feature
git rebase -i main
# In the editor, change:
pick abcd123 WIP
pick efgh456 Fix typo
pick ijkl789 More work
pick mnop012 Bug fix
pick qrst345 Final touches
# To:
pick abcd123 WIP
squash efgh456 Fix typo
squash ijkl789 More work
squash mnop012 Bug fix
squash qrst345 Final touches
# In the next editor, write a comprehensive commit message:
Implement feature X with tests
This commit:
- Adds the core feature X functionality
- Includes comprehensive tests
- Handles edge cases
- Updates documentation
Benefit: The feature is represented by a single, well-documented commit that's easier to review and understand.
Scenario 3: Implementing a Feature with Dependent Sub-Features
Situation: You're working on a large feature that has several sub-features, and you want each sub-feature to be reviewed separately.
Solution: Use multiple branches with a clear dependency chain.
# Start with the base feature
git checkout -b feature-base main
# Implement base infrastructure and create PR
# Once feature-base is merged or at least reviewed
git checkout -b feature-auth main
git merge feature-base # or rebase onto feature-base if not yet merged
# Implement authentication and create PR
# Finally, add the UI components
git checkout -b feature-ui main
git merge feature-auth # or rebase onto feature-auth if not yet merged
# Implement UI and create PR
Benefit: Each part can be reviewed separately, and dependencies are clear. If one part needs changes, it doesn't block the entire feature.
Scenario 4: Handling Release Branches and Hotfixes
Situation: You've created a release branch, but a critical bug is discovered that needs to be fixed in both the release and main branches.
Solution: Create a hotfix branch from the release branch and cherry-pick the fix to main.
git checkout release/1.0
git checkout -b hotfix/bug
# Fix the bug and commit
git add .
git commit -m "Fix critical bug"
# Update the release branch
git checkout release/1.0
git merge hotfix/bug
git tag -a v1.0.1 -m "Version 1.0.1"
# Also apply the fix to main
git checkout main
git cherry-pick hotfix/bug # Use the commit hash of the fix
# Resolve any conflicts if needed
Benefit: The bug is fixed in both the current release and future releases, while maintaining a clear history of when and why the fix was applied.
Scenario 5: Resolving Complex Merge Conflicts with Multiple Developers
Situation: A large feature branch has significant conflicts with main when it's time to merge, involving files worked on by multiple developers.
Solution: Hold a merge party with all involved developers.
# Preparation
git checkout feature
git rebase main # Try to rebase first to see the conflicts
# If rebasing is too complex, abort and try merging
git rebase --abort
git merge main # This will show conflicts
# During the merge party
# 1. Go through each conflicted file
# 2. Have the relevant developers explain their changes
# 3. Decide on the best resolution together
# 4. Mark each file as resolved
git add path/to/resolved/file
# Once all conflicts are resolved
git commit # Complete the merge with a detailed message
Benefit: Collaborative resolution ensures the best outcome with input from all stakeholders, reducing the risk of introducing bugs or losing important changes.
Best Practices for Merging and Rebasing
To make your merge and rebase operations as smooth as possible, follow these best practices:
General Best Practices
- Integrate frequently: The longer branches diverge, the more complex conflicts become
- Commit logically: Make each commit focus on a single logical change
- Write clear commit messages: Explain why changes were made, not just what was changed
- Test before and after: Ensure your code works before integration and validate it again afterward
- Use pull requests/code reviews: Have others review your changes before they're merged
- Document your team's workflow: Ensure everyone understands when to merge vs. rebase
Merging Best Practices
- Keep feature branches short-lived: Aim to merge within days, not weeks or months
- Be descriptive in merge commits: Summarize what the feature does and any important implementation details
- Consider merge options: Decide whether to use
--no-ff,--squash, or other options based on the situation - Always merge topic branches into the correct target: Double-check that you're merging into the intended branch
- Delete branches after merging: Keep your repository clean by removing merged branches
Rebasing Best Practices
- Follow the golden rule: Only rebase local/private branches that aren't shared with others
- Rebase before sharing: Clean up your branch with interactive rebase before opening a pull request
- Understand what you're rebasing onto: Be clear about the base commit you're targeting
- Take small steps: For complex rebases, consider rebasing a few commits at a time
- Test after rebasing: Ensure your code still works after the rebase
- Force push with care: When updating a pushed branch after rebase, use
--force-with-leaseinstead of--force
Conflict Resolution Best Practices
- Understand both sides: Take time to understand why both changes were made
- Consult with the other developer: When in doubt, talk to the person who made the other changes
- Preserve functionality: Ensure your resolution maintains the intended behavior of both changes
- Be thorough: Check the entire file, not just the conflicted sections, to ensure consistency
- Use visual tools: Complex conflicts are easier to resolve with visual diff tools
- Test your resolution: Always test your code after resolving conflicts to ensure it works correctly
Team Workflow Considerations
- Establish clear guidelines: Document when to merge vs. rebase in your team workflow
- Set up branch protection: Require pull requests and passing tests before merging to important branches
- Consider automating integration: Use CI/CD to automatically test merged code
- Train team members: Ensure everyone understands Git fundamentals and conflict resolution
- Schedule regular cleanup: Periodically review and prune old branches
- Monitor integration issues: Keep track of common conflicts to identify areas for improvement
Practice Exercises
Apply what you've learned with these hands-on exercises:
Exercise 1: Basic Merge and Conflict Resolution
- Create a new Git repository with a file called
README.mdcontaining some text - Create two branches:
feature1andfeature2 - In
feature1, modify the README.md file by adding a section about installation - In
feature2, modify the same README.md file by adding a section about usage - Merge
feature1intomain - Try to merge
feature2intomain- you should encounter a conflict - Resolve the conflict to include both the installation and usage sections
- Complete the merge and view the history with
git log --graph --oneline
Exercise 2: Rebasing and Interactive Rebasing
- Create a new Git repository with a file called
app.js - Make several commits to the main branch
- Create a feature branch and make several small commits
- Return to main and make another commit
- Switch back to your feature branch and use rebase to incorporate the new main branch commit
- Use interactive rebasing to clean up your feature branch:
- Squash some related commits
- Reword a commit message
- Reorder commits if needed
- Verify the modified history with
git log
Exercise 3: Merge Strategies Comparison
- Create a new Git repository with several files
- Create three identical feature branches:
feature-regular,feature-noff, andfeature-squash - Make the same series of commits on each branch
- Merge each branch into main using different strategies:
- Regular merge (fast-forward if possible):
git merge feature-regular - No-fast-forward merge:
git merge --no-ff feature-noff - Squash merge:
git merge --squash feature-squashfollowed bygit commit
- Regular merge (fast-forward if possible):
- Compare the resulting history with
git log --graph --oneline - Consider the pros and cons of each approach
Exercise 4: Cherry-Picking and Selective Integration
- Create a new Git repository with a file called
project.js - Create a branch called
experimentaland make several commits, each adding a different function to the file - Decide that only some of these functions are ready for production
- Cherry-pick only the commits with the ready functions into the main branch
- Verify that main now contains only the selected functions
Exercise 5: Complex Merge Conflict Resolution
- Create a new Git repository with a file containing a complex data structure (like a JSON configuration)
- Create two branches and make significant changes to different but overlapping parts of the structure in each branch
- Attempt to merge one branch into the other, which should result in conflicts
- Use a visual merge tool to resolve the conflicts (try
git mergetoolif you have one configured) - Create a resolution that preserves the important changes from both branches
- Complete the merge and test that the resulting file is valid (e.g., valid JSON)
Challenge Exercise: Release Management with Hotfixes
This exercise simulates a real-world release management scenario:
- Create a new Git repository with several files representing a software project
- Create a
developbranch frommain - Create several feature branches from develop, implement them, and merge them back into develop
- Create a
release/1.0branch from develop - Make some release preparation commits on the release branch
- Merge
release/1.0into main and tag it asv1.0.0 - Continue development on develop (add new features)
- Discover a critical bug in the released version
- Create a hotfix branch from the release tag, fix the bug, and commit
- Merge the hotfix into both main and develop
- Tag the new version on main as
v1.0.1 - Visualize and explain the resulting repository structure