clement13.net
Original photo: Vue aérienne d’une petite rivière qui se jette dans un grand fleuve entre des rives boisées.
Description
Vue aérienne d’une petite rivière qui se jette dans un grand fleuve entre des rives boisées.
Source
Wikideas1 · original page
Licence
CC0 Public domain
Original photo, unretouched. The background is a version of it stylised by the site.

Git in Depth

Understand Git's object model (blob, tree, commit, tag), the index, references, and reflog: that's what makes commands predictable.

Guide≈ 16 min readCheckedCheck doneFacts, figures and references were checked against the sources cited in the page.Written with AI assistance; sources are cited at the bottom of the page. An error? Report it via clement13.net.Reading
In brief
  • Understand Git's object model (blob, tree, commit, tag), the index, references, and reflog: that's what makes commands predictable.
  • Choose between merge, rebase, cherry-pick, revert, reset by understanding their effects on history.
  • Find a defect with bisect, recover “lost” work with reflog.
  • Manage large files and large repositories (LFS, partial/shallow clone, sparse-checkout), secrets, and signing.
  • Know where Git on Azure DevOps, GitHub, and GitLab differs, and what AI brings (and does not replace).
On this page

Level: 🟢→🔴

The basics in ten commands#

🟢 Novice level

git clone <url>              # copy a repository (full history)
git status                   # where am I?
git switch -c feature/kpi    # create and switch to a branch
git add chemin/fichier.sql   # stage (index) a change
git commit -m "Message"      # save locally
git fetch                    # retrieve remote WITHOUT touching my work
git pull --rebase            # fetch + replay my commits on top
git push -u origin feature/kpi   # publish the branch
git log --oneline --graph --decorate   # read history
git diff / git diff --staged # see what changed (unstaged / staged)

Beginner's golden rule: git status before anything else, git fetch rather than git pull when in doubt, never rewrite a shared branch.

✅ Good practice

Configure once: git config --global user.name / user.email, core.autocrlf (see 06), pull.rebase true or pull.ff only, init.defaultBranch main, rerere.enabled true, fetch.prune true.

The object model#

🟡 Practical level

Everything is stored in .git/objects, by hash identifier (principle). Four object types:

Object Contains Example
blob The contents of a file (without its name) a .sql script
tree A (mode, name, identifier) list of blobs/trees a folder
commit A root tree + parents + author + message a snapshot
annotated tag A named pointer + signer + message v9.1.0
commit C3 ──► root tree ──► blob  README.md
   │                    └──► tree  Datamart/ ──► blob 010_Core.sql
   └─parent─► commit C2 ──► ...

Check for yourself:

git cat-file -t HEAD         # commit
git cat-file -p HEAD         # displays tree, parent, author, message
git cat-file -p HEAD^{tree}  # contents of root folder
git ls-tree -r HEAD --name-only | head

A branch is just a 41-byte file containing a commit identifier (.git/refs/heads/main). So creating a branch is instant and free. A commit knows its parents, never its children: history is a directed acyclic graph (DAG).

Physical storage: “loose” objects (zlib), then grouped into packfiles with delta compression (git gc). A repository may seem large in history but compact after gc.

💡 Expert tip

A renamed file is not stored as renamed: Git compares trees and detects similarity (git log --follow, git diff -M). If you move and heavily rewrite a file in the same commit, detection fails: make two commits.

The index and the three areas#

🟡 Practical level

Git manipulates three areas:

 Working directory  --git add-->  Index (staging area)  --git commit-->  Repository
        ▲                                   │                                         │
        └────────── git restore ◄───────────┘          git restore --staged ◄─────────┘

The index is a list of what the next commit will be. Benefit: compose clean commits. git add -p stages by chunks (hunks); git restore --staged unstages; git stash sets aside the working state (equivalent to a lightweight shelveset, see 03).

git add -p script.sql        # choose hunk by hunk
git commit -v                # view diff in message editor
git commit --fixup <sha>     # prepare a fix to integrate
git rebase -i --autosquash origin/main
⚠️ Pitfall

Symptom: “my modified file isn't in the commit.” Cause: change made after git add. The index keeps the old state. Fix: git status shows both lists (“to be committed” / “not staged”); run git add again.

References, HEAD, and reflog#

🟡 Practical level

  • References: refs/heads/* (local branches), refs/remotes/origin/* (remote tracking), refs/tags/*.
  • HEAD: the current position; normally a symbolic reference to a branch. Detached HEAD: HEAD points directly to a commit (after git checkout <sha> or a tag). Commits created then are attached to no branch: create one (git switch -c rescue) before leaving.
  • Relative notation: HEAD~2 (two “first” parents back), HEAD^2 (second parent of a merge), main@{yesterday}, :/text (commit whose message contains “text”).

Reflog is the local journal of HEAD and branch movements. It saves you: a failed reset --hard or rebase doesn't destroy commits, they simply become unreachable by a branch and remain recoverable for about 90 days by default (30 days for unreachable ones, orders of magnitude for default gc.reflogExpire configuration).

git reflog                       # HEAD journal
git reset --hard HEAD@{3}        # return to state from three moves ago
git branch rescue <sha>          # reattach a recovered commit
git fsck --lost-found            # orphan objects
🧪 Example

After a git rebase -i where a line was deleted by mistake, a SQL migration script seems lost. git reflog shows the pre-rebase state (HEAD@{5}: rebase (start)); git reset --hard HEAD@{6} restores it. Reflog is local: it does not protect a deleted clone.

Merging with merge, rebase, cherry-pick, revert#

🟡 Practical level

Operation Effect on history When to use it Risk
merge (fast-forward) Simply moves the pointer forward Short branch with no divergence None
merge --no-ff Creates a two-parent merge commit Keep the trace of a feature Denser history
merge --squash A single commit without merge link Integrate a “draft” branch Loss of detail; branch relationships not tracked
rebase Replays your commits on a new base (new identifiers) Clean up a private branch before PR Rewrite: forbidden on shared branch
cherry-pick Copies a commit elsewhere (new identifier) Backport a fix to a release Duplicates, repeated conflicts
revert Creates a reverse commit Undo on public branch Reverted merge: must be handled carefully

Three-way merge: Git finds the common ancestor, compares each branch to it, and combines. Default strategy ort (replacement for recursive, verify by version). A conflict occurs when both sides modify the same area.

git merge feature/kpi
# conflict:
git status                  # "both modified"
git diff --name-only --diff-filter=U
# edit markers <<<<<<< ======= >>>>>>>
git add fichier && git merge --continue
git merge --abort           # give up
git config --global merge.conflictStyle zdiff3   # also shows common base
git config --global rerere.enabled true          # remembers resolutions

Cherry-pick a fix to a release branch:

git switch release/9.1
git cherry-pick -x abc1234     # -x adds "(cherry picked from ...)": traceability
✅ Good practice

Use -x for every cherry-pick to a release: audit can find the origin. Even better: fix first on the oldest affected branch, then merge forward (see 05), so the VCS knows the fix is present everywhere.

⚠️ Pitfall

Rebasing an already pushed branch then push --force: teammates who pulled the old version end up with duplicate commits or phantom conflicts. Fix: git push --force-with-lease (refuses if remote advanced), only on your branch; team rule: never force-push on main or on a release branch (branch protections).

💡 Expert tip

git rebase --onto new_base old_base my_branch transplants a branch that started from the wrong point. git rebase --rebase-merges preserves merge topology. git merge-base A B gives the common ancestor; git range-diff compares two versions of a commit series before/after rebase.

Undo with reset, restore, revert#

🟡 Practical level

Need Command Note
Undo the last commit while keeping changes git reset --soft HEAD~1 Index and files untouched
Undo while keeping files, clear the index git reset --mixed HEAD~1 Default
Throw everything away (⚠️ destructive) git reset --hard HEAD~1 Recoverable via reflog if committed
Restore a file from index/HEAD git restore fichier Loses uncommitted local changes
Restore from a commit git restore --source=<sha> fichier
Undo a published commit git revert <sha> History preserved
Fix the last message git commit --amend Commit not pushed yet
Delete untracked files git clean -fdn then -fd -n = simulation
⚠️ Pitfall

git reset --hard and git clean -fd destroy uncommitted changes with no safety net at all (reflog does not contain them). Habit: git stash -u before any destructive operation.

Find with log, blame, bisect, pickaxe#

🟡 Practical level

git log --oneline --graph --all --decorate -20
git log -- chemin/script.sql                  # history of a file
git log --follow -p -- chemin/script.sql      # follow across renames
git log -S"DateExploitation" --oneline        # pickaxe: commits that add/remove the string
git log -G"regex" -p                          # same by regular expression
git log --since="2026-09-01" --author="name" --stat
git blame -w -C -L 120,160 script.sql         # -w ignores spaces, -C follows copies
git shortlog -sn --no-merges                  # contribution by author

git bisect automates binary search for the faulty commit: you mark a “bad” commit and a “good” one, Git proposes a midpoint, you test, and so on in O(log n).

git bisect start
git bisect bad HEAD
git bisect good v9.0.0
# Git checks out a commit: test then tell it
git bisect good     # or git bisect bad
# or automate:
git bisect run powershell -File tests/Test-Datamart.ps1
git bisect reset
🧪 Numeric example

Between two kit versions: 480 commits. Bisect finds the culprit in at most 9 steps (2⁹ = 512). With an automated test (consolidating a sample then comparing three business totals), the operation runs without intervention. Condition: each commit must be testable in isolation (atomic commits, no “broken” commit).

Hooks#

🟡 Practical level

Hooks are scripts in .git/hooks (not versioned). To share them: git config core.hooksPath .githooks and version the folder, or use a manager (pre-commit, Husky, lefthook).

Hook When Usage
pre-commit Before commit Syntax checks, UTF-8/BOM encoding, secret detection
commit-msg Message entered Enforce a format (ticket reference)
pre-push Before push Run quick tests
post-merge, post-checkout After update Regenerate artifacts
pre-receive / update (server) On receive Server-side policy (not available on all cloud hosts)
#!/bin/sh
# .githooks/commit-msg: requires a ticket reference
grep -Eq '^(#[0-9]+|AB#[0-9]+) ' "$1" || { echo "Message: prefix with AB#<work item no.>"; exit 1; }
⚠️ Pitfall

Local hooks can be bypassed (--no-verify): they are an aid, not a barrier. The barrier is server-side validation (branch policies, required pipeline).

Submodules, subtrees, LFS#

🟡 Practical level

Submodule (git submodule): a Git repository inside another, pinned to a specific commit. Useful for sharing a library (a shared SQL toolbox) across repositories. Cost: --recurse-submodules clone, explicit updates, frequent confusion.

Subtree (git subtree): copies a repository's history into a subfolder; no special command for consumers, but heavier history.

Criterion Submodule Subtree Copy / package
Simplicity for the consumer 2/5 4/5 5/5
Version traceability 5/5 3/5 4/5
Sending changes back to source 3/5 3/5 1/5

Git LFS (Large File Storage) replaces large files with small pointers in history; content is stored elsewhere.

git lfs install
git lfs track "*.pbix" "*.bak" "*.zip"
git add .gitattributes
git lfs ls-files
git lfs migrate import --include="*.pbix" --everything   # rewrites history!
⚠️ Pitfall

LFS is not a cure-all: storage and bandwidth are billed or capped by the host; one .pbix modified 200 times = 200 stored versions. Prefer PBIP format for reports (06) and reserve LFS for true binaries.

Large repositories, partial, shallow, sparse, monorepo#

🔴 Expert level

Technique Command Effect Limit
Shallow clone git clone --depth 1 Only the latest commit No history: log, blame, bisect limited; merges difficult
Partial clone git clone --filter=blob:none Full history, blobs on demand Requires compatible server; later network access
Sparse checkout git sparse-checkout set Datamart/ Materializes only certain folders Repository stays complete (except partial)
Commit-graph, FSMonitor git config core.fsmonitor true, git maintenance start Speed up status/log Depends on platform
Scalar scalar clone <url> Ready-to-use setup for large repositories Microsoft tool, integrated into Git
git clone --filter=blob:none --sparse https://.../mon-produit.git
cd mon-produit
git sparse-checkout set Datamart/Datamart Reports

Monorepo or multi-repo? The monorepo (one repo, multiple products/modules) brings: cross-cutting atomic changes (one commit modifies SQL and report), a single history, one toolchain. Cost: size, non-granular permissions, builds to make incremental. Multi-repo brings: independent permissions and cycles; cost: version coordination, synchronization, “dependency hell.”

🧪 BI example

A datamart (SQL scripts) and its Power BI reports evolve together: renaming a column breaks reports. A monorepo allows a single commit/PR that updates the SQL layer and the semantic layer; two repos require a cross PR and a deployment order.

Criterion Monorepo Multi-repo
Atomic cross-cutting changes 5/5 2/5
Permission isolation 2/5 5/5
Clone performance 2/5 (3/5 with partial) 5/5
CI simplicity 3/5 4/5

Context: team of 5 to 30 people, a few GB of content.

Signing commits and tags#

🔴 Expert level

The author of a commit is a simple string (user.name): it can be spoofed. Cryptographic signing proves the signer's identity.

git config --global gpg.format ssh                # sign with SSH key (Git ≥ 2.34)
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
git log --show-signature -1
git tag -s v9.1.0 -m "Release 9.1.0"
git verify-tag v9.1.0

Choices: GPG (legacy, keyring management), SSH (simpler, already present), S/MIME (enterprise certificates). Platforms show a “verified” badge; for a real audit requirement, enforce signing through branch policy (platform-dependent, verify). Don't confuse: signature = identity; content integrity = hashing.

✅ Good practice

Sign at least release tags: they underpin release reproducibility.

Secret management#

🔴 Expert level

A committed secret (SQL password, connection string, token, API key) is compromised even after deletion: it remains in history, forks, and clones.

Defense hierarchy:

  1. Don't write it: use variables, a vault (Azure Key Vault), integrated auth/managed identity instead of passwords.
  2. Prevent commit: .gitignore (*.local.*, *.pfx, appsettings.*.json files), pre-commit hook with a detector (gitleaks, detect-secrets).
  3. Detect server-side: platform secret scanning (GitHub secret scanning/push protection, GitHub Advanced Security for Azure DevOps, also offered as separate products Secret Protection and Code Security, limited to Azure DevOps Services — license to verify).
  4. If leaked: revoke and rotate immediately (rewriting history is never enough), then clean with git filter-repo (recommended tool replacing filter-branch) or BFG, then force-push rewritten history and have the team re-clone.
git filter-repo --path secrets.json --invert-paths   # removes a file from all history
⚠️ Pitfall

Wrong reflex: “I delete the file and recommit.” The secret remains readable via git show <ancien sha>:fichier. Only secret rotation resolves the risk.

Expert commands#

🔴 Expert level

git worktree add ../hotfix release/9.0     # multiple directories, one single repository
git stash push -m "wip" -- chemin/         # partial stash
git restore -SW --source=release/9.0 -- fichier
git diff --stat main...feature             # three dots: from common ancestor
git log main..feature                      # two dots: what feature has extra
git log --left-right --cherry-pick main...feature   # see "equivalent" commits
git cherry -v main feature                 # commits not yet applied (by patch-id)
git rev-list --count A..B
git reflog expire --expire=now --all && git gc --prune=now   # purge (irreversible)
git update-index --assume-unchanged fichier  # avoid: hides, does not protect
git notes add -m "Acceptance tested" <sha>      # annotation outside commit
git archive --format=zip HEAD -o kit.zip     # clean export of a state (without .git)
git describe --tags                          # v9.1.0-14-gabc1234
git maintenance start
git config --global alias.lg "log --oneline --graph --decorate --all"

git worktree is the BI expert's weapon when maintaining multiple product versions: one directory per version (see 05), a single repository, no destructive branch switching.

git describe generates a readable version number from the latest tag: useful for timestamping a kit (kit-9.1.0-14-gabc1234).

Classic pitfalls#

🔴 Expert level

Symptom Cause Remedy
“rejected (non-fast-forward)” on push The remote moved ahead git fetch then pull --rebase or merge; no force
Diff “of the entire file” Line endings or BOM changed .gitattributes, core.autocrlf; see 06
File ignored despite .gitignore Already tracked git rm --cached fichier
“Disappeared” branch Abandoned detached HEAD git reflog, git branch rescue <sha>
Repeated conflicts during a long rebase Overlapping commits rerere, or merge instead of rebase, or squash beforehand
Huge repository after importing binaries History keeps everything git filter-repo, LFS, git gc --aggressive (rarely useful)
“Lost” merge after revert of a merge Git believes content is already merged Re-revert the revert before merging again
“Almost identical” filenames on Windows Case-insensitive system core.ignorecase, rename in two steps
Path too long on Windows 260-character limit git config core.longpaths true, short root folders
✅ Good practice

Commit message convention: a one-line title under 72 characters in the imperative, blank line, then a body explaining the why and not the what. Work item reference in footer (AB#12345 for Azure Boards).

Git on Azure DevOps, GitHub, and GitLab#

🔴 Expert level

Capability Azure Repos (Git) GitHub GitLab
Merge request Pull request Pull request Merge request
Branch policies Minimum reviewers, validation build, comment resolution, mandatory work item linking Branch protection / rulesets, required checks Protected branches, approval rules
Ticket integration Native with Azure Boards (AB#) Issues, Projects Issues, boards
CI/CD Azure Pipelines (YAML) Actions GitLab CI
On-prem hosting Azure DevOps Server GitHub Enterprise Server Self-managed GitLab
Folder-level permissions Limited (repository level); path-based reviewer policies CODEOWNERS CODEOWNERS
Built-in AI Copilot (in the IDE) Copilot (PR, review, agent) GitLab Duo

Notable specifics:

  • Azure Repos: in the same project, you can have multiple Git repositories and one TFVC repository (03). Branch policies (reviewers, builds, work item linking) cover much of what check-in policies did in TFVC. More details in Azure DevOps Server (ex-TFS) in Depth.
  • GitHub: richest ecosystem (Actions, Dependabot, code scanning, Codespaces). Rulesets complement branch protection rules (both coexist and their rules stack; multiple rulesets can apply to the same branch).
  • GitLab: integrated DevOps platform, a good choice if you want everything in one self-hostable tool.
  • In all cases: protect main, require PR + build, forbid force-push, tag releases, back up the repository.

Ratings /5 (context: team of 10 to 30 people, release cycles by version, Microsoft ecosystem already in place):

Criterion Azure Repos GitHub GitLab
Integration with Azure Boards work items 5/5 3/5 2/5
Richness of the extension ecosystem 3/5 5/5 4/5
Self-hosting for sovereignty requirements 4/5 4/5 5/5
Cost / license already acquired 4/5 3/5 3/5

AI contribution#

🤖 AI

Concrete AI use cases around Git.

Use case Gain Risk Maturity Prerequisites Guardrails
Generate a commit message from the diff 3/5 2/5 4/5 Small, focused diff Review it: AI describes the what, not the business why
Summarize a pull request 4/5 2/5 4/5 Platform with Copilot or equivalent Do not replace reading the diff
Assisted code review 3/5 3/5 3/5 Explicit team rules False negatives; AI does not know your implicit business rule
Suggest conflict resolution 3/5 4/5 3/5 Context from both branches Never accept without testing: a “plausible” merge can invert a business rule
Explain history (“why this line?”) 4/5 2/5 4/5 git blame/log provided Verify assumptions in linked tickets
Write the appropriate Git command (reset, rebase, filter-repo) 4/5 4/5 4/5 Model understanding Destructive commands: read, simulate (-n), back up a branch
Draft release notes from commits 4/5 2/5 4/5 Commits linked to tickets Functional review, human author sign-off

Prompt examples:

Here is the staged diff (git diff --staged). Propose a commit message in French:
title ≤ 72 characters in the imperative, body explaining why, without inventing
a ticket. If the diff mixes multiple topics, say so and propose how to split it.
Here is the output of git log --oneline v9.0.0..v9.1.0 -- Datamart/.
Group by theme (bug fix, enhancement, technical) and draft release
notes for a functional consultant. Flag ambiguous entries instead of guessing.
⚠️ Pitfall

Confidentiality: sending a diff containing secrets, customer data, or restrictively licensed code to an external AI service. Remedy: usage policy, company-approved models/instances, secret detection before sending.

AI does not replace: judgment about what should be committed, responsibility for the merge, business acceptance testing of the numbers, or the discipline of atomic commits (AI on a catch-all commit produces a catch-all message). See AI in Planning and Version Control Tools and the Cross-Functional Summary — AI Contribution.

See also#

✻ Written by Claude Sonnet 5.5