Getting Started with Git and Version Control

Chapter 5
Branching, Merging and Integration

Diploma in Data science
Getting Started with Git and Version Control   •   Diploma

Branching, Merging and Integration

Introduction

Version control becomes substantially more useful when several lines of development can proceed safely at the same time. In Git, this is achieved through branches. A branch allows a developer, analyst, or data science team to work on a feature, experiment, correction, or release without immediately changing the main line of development. Changes can later be combined through merging or another integration method.

Branching is not merely a command-line technique. It is a way of organizing work, controlling risk, recording the history of decisions, and enabling collaboration. A well-designed branching practice allows experimental work to remain isolated, makes code review possible, and provides a clear path from development to production. Poor branching practices, by contrast, can produce confusing histories, conflicting edits, accidental overwrites, and unreliable releases.

This chapter explains how Git represents branches, how commits form ancestry relationships, how branches are created and moved, how development work is integrated, and how content conflicts are resolved safely.

Learning Objectives

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

  • explain branches, commits, references, pointers, and ancestry;
  • create, inspect, rename, switch, and delete branches;
  • distinguish fast-forward, three-way, squash, and rebase-based integration;
  • merge development work safely into another branch;
  • identify, understand, and resolve content conflicts;
  • select a suitable branching strategy for a small data science team;
  • use testing, review, and history inspection to improve integration quality.

1. The Git Object Model: Commits and Pointers

A Git repository stores snapshots of files together with metadata. Each commit records a project state, the author, a message, and a reference to one or more earlier commits. Most ordinary commits have one parent; a merge commit has two or more parents.

A branch is best understood as a movable pointer to a commit, not as a separate copy of the entire project. If a branch named main points to commit C, the files visible when main is checked out are determined by that commit. When a new commit is made, the branch pointer advances to the new commit.

HEAD is a symbolic reference indicating the commit or branch currently checked out. In normal use, HEAD points to a branch, and that branch points to the current commit. This state is called attached HEAD. If a specific commit is checked out directly, HEAD is detached; commits made in that state are not automatically associated with a named branch.

A simplified history might be represented as follows:

A---B---C   main
     \
      D---E   feature

Here, feature and main share commits A and B, but then follow different lines of development. The branch names are labels attached to the tips of those lines. The commits themselves remain in the repository even when a branch pointer moves, provided they remain reachable through another reference or Git's recovery mechanisms.

Important terms include:

  • Tip: the commit to which a branch currently points.
  • Parent: an earlier commit recorded by a later commit.
  • Ancestor: a commit reachable by repeatedly following parent links.
  • Reachability: the ability to trace a commit from a reference such as a branch or tag.
  • Reference: a name that identifies a commit, such as a branch or tag.
  • Working tree: the files currently checked out on disk.
  • Index: Git's staging area, which records the proposed contents of the next commit.

The distinction between the working tree, index, and repository is essential. A merge may change the working tree and index before a merge commit is created. Until the result is inspected and committed, integration is incomplete.

2. Creating and Managing Branches

A branch can be created with:

git branch feature-cleaning

This creates the branch but does not switch to it. To create and switch in one operation, use:

git switch -c feature-cleaning

The older command git checkout -b feature-cleaning performs a similar operation, but git switch communicates the intention more clearly and is preferred for branch movement in modern Git.

Useful commands include:

PurposeCommand
List local branchesgit branch
Show the current branchgit branch --show-current
Switch branchesgit switch branch-name
Create and switchgit switch -c branch-name
Rename the current branchgit branch -m new-name
Delete a fully merged branchgit branch -d branch-name
Force deletion of an unmerged branchgit branch -D branch-name
Show compact historygit log --oneline --graph --decorate --all

A branch should normally be created from a deliberate starting point. For example, a feature branch should usually begin from the latest approved commit on main or from a designated development branch. Creating a branch from an outdated or unrelated commit may cause avoidable conflicts and may omit required changes.

Before switching branches, inspect or save local work. Git may prevent a switch if uncommitted changes would be overwritten. Appropriate options are:

  1. commit the work if it forms a coherent change;
  2. temporarily store it with git stash;
  3. discard it only when the changes are definitely unnecessary.

Discarding uncommitted work is potentially irreversible. A professional workflow treats this as a deliberate action, not as routine cleanup.

3. Branch Ancestry and Comparison

Branch integration depends on ancestry. Suppose main points to C and feature points to E, where C is an ancestor of E. Git can determine that the feature branch contains all commits reachable from main, plus additional commits.

The common ancestor used for a three-way merge is called the merge base. It is usually the most recent commit reachable from both branch tips. Git compares:

  1. the merge base;
  2. the current tip of the branch being merged into;
  3. the tip of the branch being merged.

This comparison allows Git to distinguish changes made on each side since their common starting point.

Useful inspection commands include:

git log --oneline --graph --decorate --all
git diff main...feature
git diff main feature
git merge-base main feature
git branch --contains COMMIT_ID

The notation main...feature in git diff compares the changes introduced by the feature branch relative to the common ancestor. By contrast, git diff main feature compares the two endpoint trees directly. These commands answer different questions and should not be treated as interchangeable.

4. Integration Strategies

4.1 Fast-Forward Merge

A fast-forward merge occurs when the target branch is an ancestor of the source branch. Git does not need to create a new commit; it simply moves the target pointer forward.

Before: A---B   main
             \
              C---D   feature

After:  A---B---C---D   main, feature

Command:

git switch main
git merge feature

Fast-forwarding produces a linear history and is appropriate when the target branch has not advanced independently. It does not create a record explicitly showing that a separate branch was integrated.

To require an integration commit even when fast-forwarding is possible, use:

git merge --no-ff feature

4.2 Three-Way Merge

When both branches have new commits, a fast-forward is impossible. Git performs a three-way merge using the merge base and the two branch tips. If the changes affect different lines or files, Git may combine them automatically. The result is normally a merge commit with two parents.

C---D   feature
       /
A---B
       \
        E---F   main

After merging feature into main, the new merge commit records both F and D as parents. This preserves the fact that two development histories were brought together.

4.3 Squash Merge

A squash merge combines the net effect of several feature commits into one new commit on the target branch. It does not preserve the original commits as ancestors of the target branch.

git switch main
git merge --squash feature
git commit -m "Add data validation pipeline"

Squashing can produce a clean main history, especially when a feature branch contains many corrective or exploratory commits. However, it loses the detailed commit ancestry and may make later branch relationships less obvious.

4.4 Rebase

Rebasing replays commits from one branch onto a new base. For example, a feature branch can be rebased onto the latest main before integration:

git switch feature
git rebase main

Rebase creates new commit identities because the parent relationship changes. It therefore rewrites history. Rebase is useful for organizing private or collaboratively coordinated work, but rewriting commits that others have already based work on can cause confusion and require force-pushing.

A safe publication practice is:

git push --force-with-lease

This is safer than --force because it refuses to overwrite a remote update that the local repository has not seen. Nevertheless, force-pushing should be governed by team policy and avoided on protected shared branches.

4.5 Comparing Strategies

StrategyMain resultStrengthRisk or limitation
Fast-forwardMoves target pointerSimple linear historyNo explicit integration commit
No-fast-forward mergeCreates merge commitPreserves feature boundaryMore graph complexity
Three-way mergeCombines independent historiesPreserves ancestryMay produce conflicts
Squash mergeOne target commitClean main historyLoses individual ancestry
Rebase then mergeRewritten linear historyEasier reading and reviewRewrites commit identities

The correct choice depends on repository policy, team size, review requirements, release practices, and whether preserving detailed ancestry matters.

5. A Safe Merge Workflow

A disciplined integration procedure reduces technical and organizational risk.

Step 1: Inspect the Repository

Check the current branch and working state:

git status
git branch --show-current
git log --oneline --graph --decorate --all

Do not begin a merge while unrelated local changes are present unless the workflow explicitly supports it.

Step 2: Update the Target Branch

If collaboration occurs through a remote repository, update references and integrate the current remote state:

git fetch origin
git switch main
git pull --ff-only origin main

The --ff-only option prevents Git from silently creating an unexpected merge commit during the pull. A team can then decide explicitly how divergence should be handled.

Step 3: Review the Proposed Changes

Inspect the commits and file differences:

git log main..feature --oneline
git diff main...feature

For professional work, review should include tests, configuration changes, documentation, data assumptions, and security-sensitive files—not only the source code.

Step 4: Merge

git merge --no-ff feature

Git may complete the operation automatically, or it may stop because of conflicts.

Step 5: Validate

Run the project's tests, linters, data-quality checks, and relevant manual checks. In a data science project, validation may include checking schema compatibility, missing-value handling, feature calculations, model reproducibility, and leakage risks.

Step 6: Publish and Clean Up

After validation:

git push origin main
git branch -d feature

A branch should be deleted only when its work is safely integrated or intentionally abandoned. Deleting a branch does not normally delete commits that remain reachable from the target branch.

6. Understanding and Resolving Conflicts

A content conflict occurs when Git cannot determine which version should be retained. A common example is when two branches edit the same region of a file in incompatible ways. Conflicts can also occur when one branch deletes a file that another modifies, or when both branches rename related files.

During a conflict, Git places markers in affected files:

<<<<<<< HEAD
learning_rate = 0.01
=======
learning_rate = 0.001
>>>>>>> feature

The section above ======= represents the current branch, often labelled HEAD; the section below represents the branch being merged. These labels describe Git's perspective, not necessarily which version is correct.

The safe resolution process is:

  1. stop and identify the integration operation;
  2. inspect all conflicted files with git status;
  3. understand the intended behavior from requirements, tests, and the original authors;
  4. edit each file to produce the correct final content;
  5. remove all conflict markers;
  6. stage each resolved file using git add file-name;
  7. run tests and inspect the staged diff;
  8. complete the merge with git commit, or use the merge tool's completion command;
  9. abort if the correct resolution cannot yet be established.

Commands commonly used are:

git status
git diff
git diff --check
git add path/to/resolved-file
git diff --cached
git merge --continue
git merge --abort

git merge --abort attempts to restore the pre-merge state. It should be used before making unrelated changes during an unresolved merge. If local modifications existed before the merge, restoration may not be perfectly straightforward; clean working trees before integration are therefore strongly recommended.

A conflict should never be resolved merely by choosing “ours” or “theirs” mechanically. The correct result may combine both changes, reject both, or require a new implementation. For example, if one branch changes a model input column and another changes the validation schema, the resolution must preserve the intended contract between the data pipeline and the model.

7. Applied Case: Integrating a Data Validation Feature

Background

This illustrative case concerns a small data science team developing a customer-churn model. The shared main branch contains a working training pipeline. A data engineer creates feature/validate-age to reject impossible age values and add a validation report.

Problem

While the feature is being developed, another analyst changes the same configuration file to rename the input column from customer_age to age. The feature branch still refers to customer_age. Both branches are individually reasonable, but their integration creates a conflict.

Analysis

The team first inspects the history:

git log --oneline --graph --decorate --all
git diff main...feature/validate-age

They establish that the column rename is intentional and already reflected in the current data contract. They also verify that the validation rule should remain part of the final pipeline.

Approach and Solution

The team switches to main, updates it, and merges the feature. Git reports a conflict in config/schema.yml. Rather than selecting one side wholesale, the engineer edits the file so that:

  • the accepted column is named age;
  • the valid range is recorded in the schema;
  • the validation report remains enabled;
  • tests use the renamed field.

The engineer then stages the file and runs unit tests, schema checks, and a small pipeline run. The team reviews the staged diff before completing the merge.

Outcome

The integrated pipeline accepts the new column name, rejects invalid ages, and produces the requested report. The merge history records the integration, while the tests provide evidence that the conflict resolution preserved both requirements.

Learning Points

  • Conflicts are requirements problems as well as text-editing problems.
  • Data contracts should be documented and tested.
  • Integration should be performed against the latest target branch.
  • A successful merge command does not prove that the resulting behavior is correct.
  • Review and automated checks are essential after conflict resolution.

8. Branching Models for Data Science Teams

A small team may use a simple model with a protected main branch and short-lived feature branches:

main  ← reviewed, tested, releasable
  └── feature branches ← focused development work

A team that releases frequently may use trunk-based development, in which branches are short-lived and integration occurs often. Feature flags can hide incomplete functionality without maintaining long-lived branches.

A team with scheduled releases may use release branches for stabilization and hotfix branches for urgent production corrections. A larger organization may use a model with main, development, release, and hotfix branches, but additional branches increase coordination cost and can delay integration.

Branch names should communicate purpose, for example:

  • feature/add-missing-value-report
  • fix/date-parsing-error
  • experiment/compare-random-forest
  • release/2025.04

Branching policies should define naming, review requirements, protected branches, test gates, merge methods, and deletion rules. These are governance controls, not merely stylistic preferences.

9. Professional, Security, and Governance Considerations

Version history may contain sensitive information such as credentials, personal data, proprietary datasets, or confidential model outputs. A branch is not a security boundary: creating a private-looking branch does not guarantee that its contents are inaccessible to repository users.

Do not commit passwords, access tokens, private keys, or regulated personal data. If a secret is committed, deleting it in a later commit is insufficient because it may remain in repository history or clones. Revoke or rotate the secret, then follow the organization's approved history-cleaning procedure.

For regulated or high-impact applications, retain evidence of review, test results, data lineage, model-version relationships, and approval decisions. Merge commits, pull requests, tags, and continuous-integration records can support auditability, but only when the team uses them consistently.

Artificial intelligence tools may assist with branch summaries, conflict explanations, or test generation. They must not be treated as authoritative. A developer remains responsible for checking generated resolutions, especially where data privacy, model behavior, licensing, or safety is involved.

10. Common Errors and Best Practices

Common errors include merging from the wrong branch, confusing local and remote references, resolving conflicts without reading the surrounding code, committing generated artifacts accidentally, and deleting work before it is safely integrated.

Recommended practices are:

  • keep branches focused and relatively short-lived;
  • commit coherent changes with informative messages;
  • pull or fetch before beginning integration;
  • review the graph and diff before merging;
  • use protected branches and required checks for shared production branches;
  • resolve conflicts according to requirements, not convenience;
  • test after every non-trivial merge or rebase;
  • document decisions that affect data contracts or model reproducibility;
  • avoid rewriting public history unless the team has explicitly agreed;
  • tag important releases and preserve rollback points.

Chapter Summary

Git branches are movable references to commits, while HEAD identifies the currently checked-out branch or commit. Commits form a directed ancestry graph, and the merge base provides the common point from which independent changes can be compared.

Integration may use fast-forward merges, explicit merge commits, squash merges, or rebasing. Each method offers different trade-offs between linearity, traceability, and history preservation. A safe workflow includes inspecting the repository, updating the target branch, reviewing differences, merging deliberately, validating the result, and publishing only after tests pass.

Conflicts occur when Git cannot safely combine competing changes. They must be resolved by understanding intended behavior, editing the final content, staging the resolution, and testing the result. In data science, this includes checking schemas, pipelines, model inputs, reproducibility, and data quality. Effective branching is therefore both a technical practice and a form of collaborative governance.

Key Terms

Branch: A movable reference to a commit.

Commit: A recorded snapshot and its metadata.

HEAD: Git's reference to the currently checked-out branch or commit.

Pointer: A name that identifies a commit and may move as history advances.

Parent: An earlier commit recorded by another commit.

Ancestor: A commit reachable by following parent links.

Tip: The commit currently referenced by a branch.

Merge base: The common ancestor used to compare diverging histories.

Fast-forward merge: An integration that advances a branch pointer without creating a merge commit.

Three-way merge: A merge based on the common ancestor and both branch tips.

Merge commit: A commit with two or more parents that records integration.

Squash merge: Integration that combines several source commits into one target commit.

Rebase: Replaying commits onto a different base, thereby rewriting commit identities.

Conflict: A situation in which Git cannot automatically determine the correct combined result.

Working tree: The files currently present on disk.

Index: The staging area for the next commit.

Protected branch: A branch subject to controls such as review or required tests.

Remote-tracking branch: A local reference representing a branch last observed on a remote repository.

Feature branch: A branch dedicated to a focused enhancement or experiment.

Data contract: An agreed structure and meaning for data exchanged between components.

Review Questions

Short Answer Questions

  1. What is a branch in Git, and how does it differ from a separate copy of a repository?
  2. What role does HEAD play in a Git repository?
  3. When can Git perform a fast-forward merge?
  4. What is a merge base?
  5. Why should a merge be followed by testing?

Long Answer Questions

  1. Explain the differences among fast-forward, three-way, squash, and rebase-based integration.
  2. Describe a complete and safe workflow for merging a feature branch into main.
  3. Explain how Git represents ancestry and why ancestry matters during integration.
  4. Describe the procedure for resolving a content conflict and explain why blindly choosing one side is unsafe.
  5. Discuss a suitable branching policy for a small data science team, including review, testing, and branch-deletion rules.

Analytical / Application Questions

  1. A feature branch and main both modify a model configuration file. One changes a feature name, while the other changes its data type. Design a conflict-resolution and validation plan.
  2. A team wants a perfectly linear history but frequently rewrites commits that have already been pushed. Analyze the risks and recommend a safer policy.
  3. A production model has been updated through a merge, but the tests pass only because they use an old sample dataset. Identify the governance and technical weaknesses and propose corrective actions.

Multiple Choice Questions with Answers

Question 1

Which statement best describes a Git branch?

A. A complete independent copy of the repository

B. A movable reference to a commit

C. A permanent backup of the working tree

D. A list of uncommitted files

Correct Answer: B. A movable reference to a commit

Question 2

What does a fast-forward merge do when it is possible?

A. Deletes the source branch automatically

B. Creates a commit with three parents

C. Moves the target branch pointer forward

D. Rewrites every commit in the repository

Correct Answer: C. Moves the target branch pointer forward

Question 3

Which command creates and switches to a new branch?

A. git branch --delete name

B. git switch -c name

C. git merge -c name

D. git commit -b name

Correct Answer: B. git switch -c name

Question 4

What is used in a typical three-way merge?

A. Only the two latest files

B. The merge base and both branch tips

C. Only the current branch tip

D. The oldest commit in the repository

Correct Answer: B. The merge base and both branch tips

Question 5

What is a principal effect of rebasing?

A. It changes commit parent relationships and creates new commit identities

B. It permanently prevents future conflicts

C. It converts all commits into tags

D. It deletes the target branch

Correct Answer: A. It changes commit parent relationships and creates new commit identities

Question 6

Which command can abandon an in-progress merge?

A. git merge --abort

B. git branch --reset

C. git commit --cancel

D. git history --remove

Correct Answer: A. git merge --abort

Question 7

During conflict resolution, what should be done before completing the merge?

A. Ignore the conflict markers

B. Stage the resolved files and run appropriate checks

C. Delete the repository metadata

D. Push the unresolved working tree

Correct Answer: B. Stage the resolved files and run appropriate checks

Question 8

Why can squash merging be attractive?

A. It preserves every original parent relationship automatically

B. It creates a concise target-branch history

C. It guarantees that conflicts cannot occur

D. It prevents all future history changes

Correct Answer: B. It creates a concise target-branch history

Question 9

Which command displays a compact graphical view of all branch histories?

A. git log --oneline --graph --decorate --all

B. git show --files-only --remote

C. git status --history --branches

D. git branch --commit-tree --compact

Correct Answer: A. git log --oneline --graph --decorate --all

Question 10

Why is blindly selecting 'ours' or 'theirs' during a conflict unsafe?

A. Those options always delete the repository

B. The correct result may need a deliberate combination of both changes

C. Git does not permit any conflict resolution

D. It converts the merge into a rebase

Correct Answer: B. The correct result may need a deliberate combination of both changes

Getting Started with Git and Version Control
Chapter 5 of 14