bhylu.com

Advertisements

Git Rebase

Rebase replays selected commits onto a new base. The replay usually creates new commit objects and changes the history seen from the branch.

How it works

A private inspection branch adds one file while main advances with another. We preserve the old feature tip, rebase onto main and verify ancestry plus both files. The changes do not overlap, so no conflict resolution is needed here. A separate abort lesson covers stopping an operation that cannot be completed as intended.

Run the disposable lab

Use Windows PowerShell 5.1 or later and Git 2.28 or later. The reviewed CLI is Git for Windows 2.53.0.windows.2. These are PowerShell examples, not Git Bash commands. Save the download as rebase.ps1 in a separate empty learning folder and run it in a separate process: powershell -NoProfile -ExecutionPolicy Bypass -File .\rebase.ps1. The execution-policy option applies to that process, not your machine policy.

The script creates bhylu-git-rebase and refuses an existing folder. It supplies two fictional seed files and repository-local settings, then runs the lesson commands. The literal separator is quoted as '--' so PowerShell passes it through the guard. A guard function named git calls the real CLI and stops on unexpected failures; Expect-GitFailure checks deliberately refused operations. Child-process configuration isolates your personal Git settings. File transports are allowed only for owned local remotes; internet transports are disabled in this lab. No hosting account, Apache or MySQL service is required. There is no Git playground. Leave the generated folder for inspection; choose another empty parent to run again.

Download the complete rebase.ps1 lab

Example

rebase.ps1

# Bhylu Git lab: Rebase
# Run in a separate PowerShell process, in an empty learning folder.
# Existing lab folders are refused; nothing is pushed to an internet host.
$ErrorActionPreference = 'Stop'
$script:GitExe = (Get-Command git -CommandType Application | Select-Object -First 1).Source
$script:LabRoot = Join-Path (Get-Location) 'bhylu-git-rebase'
if (Test-Path -LiteralPath $script:LabRoot) { throw 'Lab folder already exists. Choose another empty parent folder.' }
New-Item -ItemType Directory -Path $script:LabRoot | Out-Null
$env:GIT_CONFIG_NOSYSTEM = '1'
$env:GIT_CONFIG_GLOBAL = Join-Path $script:LabRoot 'empty-global-config'
[IO.File]::WriteAllText($env:GIT_CONFIG_GLOBAL, '')
$env:GIT_TERMINAL_PROMPT = '0'
$env:GIT_EDITOR = 'true'
$env:GIT_SEQUENCE_EDITOR = 'true'
$env:GIT_AUTHOR_DATE = '2026-01-10T10:00:00+00:00'
$env:GIT_COMMITTER_DATE = $env:GIT_AUTHOR_DATE
function git {
    & $script:GitExe -c protocol.allow=never -c protocol.file.allow=always @args
    if ($LASTEXITCODE -ne 0) { throw "Git exited with code $LASTEXITCODE" }
}
function Expect-GitFailure {
    & $script:GitExe -c protocol.allow=never -c protocol.file.allow=always @args
    if ($LASTEXITCODE -eq 0) { throw 'Expected Git to refuse this operation' }
}
function Assert-Equal($Actual, $Expected) {
    if ((($Actual | Out-String).Trim()) -cne (($Expected | Out-String).Trim())) { throw "Expected [$Expected], got [$Actual]" }
}
function Write-LabFile([string]$Name, [string]$Text) {
    $target = [IO.Path]::GetFullPath((Join-Path (Get-Location) $Name))
    if (!$target.StartsWith($script:LabRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) { throw 'Path outside lab' }
    [IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($target)) | Out-Null
    [IO.File]::WriteAllText($target, ($Text.Replace('\n', "`n") + "`n"), (New-Object Text.UTF8Encoding($false)))
}
function New-LabRepo([string]$Name) {
    New-Item -ItemType Directory -Path $Name | Out-Null
    Push-Location $Name
    try {
        git init --initial-branch=main
        git config --local user.name 'Repair Lab'
        git config --local user.email 'repair@example.invalid'
        git config --local core.autocrlf false
        git config --local commit.gpgSign false
        git config --local tag.gpgSign false
        git config --local core.hooksPath (Join-Path $script:LabRoot 'empty-hooks')
        Write-LabFile 'notes.md' 'Inspect hinge'
        Write-LabFile 'tasks.txt' 'desk=open'
        git add '--' notes.md tasks.txt
        git commit -m 'Record initial repair plan'
    } finally { Pop-Location }
}
Push-Location $script:LabRoot
try {
    New-LabRepo 'desk'
    Set-Location desk
    git switch -c inspection
    Write-LabFile 'inspection.txt' 'Latch inspected'
    git add '--' 'inspection.txt'
    git commit -m 'Add inspection result'
    $before = git rev-parse HEAD
    git branch before-rebase $before
    git switch main
    Write-LabFile 'schedule.txt' 'Desk opens at nine'
    git add '--' 'schedule.txt'
    git commit -m 'Add desk schedule'
    git switch inspection
    git rebase main
    if ($before -eq (git rev-parse HEAD)) { throw 'Expected replayed commit ID' }
    git merge-base --is-ancestor main HEAD
    Assert-Equal (git show HEAD:inspection.txt) @('Latch inspected')
    Assert-Equal (git show HEAD:schedule.txt) @('Desk opens at nine')
    Write-Output 'Verified: rebase'
} finally { Pop-Location }

Focus on the lesson commands

This excerpt omits the seed repository and guard helpers. Use the complete download to run it.

Lesson excerpt

git switch -c inspection
Write-LabFile 'inspection.txt' 'Latch inspected'
git add '--' 'inspection.txt'
git commit -m 'Add inspection result'
$before = git rev-parse HEAD
git branch before-rebase $before
git switch main
Write-LabFile 'schedule.txt' 'Desk opens at nine'
git add '--' 'schedule.txt'
git commit -m 'Add desk schedule'
git switch inspection
git rebase main
if ($before -eq (git rev-parse HEAD)) { throw 'Expected replayed commit ID' }
git merge-base --is-ancestor main HEAD
Assert-Equal (git show HEAD:inspection.txt) @('Latch inspected')
Assert-Equal (git show HEAD:schedule.txt) @('Desk opens at nine')

Expected result

The inspection branch now descends from the new main tip and retains both files. Its replayed commit has a new ID.

The complete script ends with Verified: rebase only after its checks pass. Object IDs, temporary paths and progress output can differ.

Review the rebased range and run tests; a clean replay is only a text integration result. During interactive rebase you can reorder, combine or edit selected private commits, but the choices must preserve the intended result. Do not use an unattended editor as proof that a human reviewed a todo list.

Advertisements