Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .github/workflows/ci-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,14 @@ on:
permissions:
contents: read

# DAAF_BRANCH tells all lifecycle scripts which branch to download from and
# target. Resolves to the branch name for schedule/dispatch, or the tag name
# for release-tag pushes (git treats tags as valid refs for --branch).
# DAAF_BRANCH tells install/migrate/rebuild scripts which ref to clone from.
# Resolves to the branch name for schedule/dispatch, or the tag name for
# release-tag pushes (git clone -b accepts both).
#
# update_daaf.sh requires a BRANCH (not a tag) because it merges upstream
# changes. Steps that invoke update_daaf.sh override DAAF_BRANCH to "" so
# the script auto-detects main/master. See "env: DAAF_BRANCH" overrides below.
#
# DAAF_NESTED suppresses the interactive pause-before-exit in all scripts.
env:
DAAF_BRANCH: ${{ github.ref_name }}
Expand Down Expand Up @@ -153,6 +158,8 @@ jobs:
# --- Update (no-op on fresh install) ---
- name: Run update_daaf.sh (no-op on fresh install)
working-directory: daaf-docker
env:
DAAF_BRANCH: ""
run: bash update_daaf.sh

- name: Verify container running after update
Expand Down Expand Up @@ -238,6 +245,8 @@ jobs:
# --- Update with no upstream changes ---
- name: Run update (no-op after fresh install)
working-directory: daaf-docker
env:
DAAF_BRANCH: ""
run: bash update_daaf.sh 2>&1 | tee /tmp/update_noop.txt

- name: Verify already up to date
Expand Down Expand Up @@ -746,6 +755,8 @@ jobs:
# (merge) when it detects local commits ahead of upstream.
- name: Update with local-only commits
working-directory: daaf-docker
env:
DAAF_BRANCH: ""
run: |
bash update_daaf.sh > /tmp/update_local_commits.txt 2>&1
cat /tmp/update_local_commits.txt
Expand All @@ -766,6 +777,8 @@ jobs:
# a second one. The second should fail with the lock error.
- name: Test concurrent update lock
working-directory: daaf-docker
env:
DAAF_BRANCH: ""
run: |
bash update_daaf.sh &
BG_PID=$!
Expand Down
35 changes: 35 additions & 0 deletions scripts/host/update_daaf.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,20 @@ if ($OriginUrl -notlike "*$UpstreamRepo*") {
Write-Host ""
}

# =====================================================================
# Ensure fetch refspec covers all branches
# =====================================================================
# git clone --depth 1 -b <ref> implies --single-branch, which locks the
# fetch refspec to only the cloned ref. This means git fetch will never
# retrieve other branches (like main), breaking auto-detect. Widen to
# the standard wildcard if it's currently narrow.
$CurrentRefspec = Invoke-ComposeGit config --get "remote.$UpstreamRemote.fetch"
if ($LASTEXITCODE -eq 0 -and $CurrentRefspec -and
$CurrentRefspec.Trim() -ne "+refs/heads/*:refs/remotes/$UpstreamRemote/*") {
Invoke-ComposeGitNull config --replace-all "remote.$UpstreamRemote.fetch" `
"+refs/heads/*:refs/remotes/$UpstreamRemote/*"
}

# =====================================================================
# Fetch latest
# =====================================================================
Expand Down Expand Up @@ -878,6 +892,27 @@ if ($RemoteBranch) {
# User specified a branch - verify it exists on the remote
$null = Invoke-ComposeGit rev-parse --verify "$UpstreamRemote/$RemoteBranch"
if ($LASTEXITCODE -ne 0) {

# Check if the value is a version tag rather than a branch.
# Tags live in refs/tags/, not refs/remotes/origin/, so the branch
# check above correctly fails for them.
$null = Invoke-ComposeGit rev-parse --verify "refs/tags/$RemoteBranch"
if ($LASTEXITCODE -eq 0) {
Write-Host ""
Write-Host "'$RemoteBranch' is a version tag, not a branch." -ForegroundColor Yellow
Write-Host ""
Write-Host "The updater needs a branch to pull changes from. Tags are fixed"
Write-Host "snapshots and cannot receive updates."
Write-Host ""
Write-Host "To update to the latest release on the main branch:"
Write-Host " .\update_daaf.ps1"
Write-Host " (without setting `$env:DAAF_BRANCH)"
Write-Host ""
Write-Host "To update from a specific branch:"
Write-Host " `$env:DAAF_BRANCH = 'dev'; .\update_daaf.ps1"
Wait-AndExit 1
}

Write-Host ""
Write-Host "The branch '$RemoteBranch' (from DAAF_BRANCH) was not found on" -ForegroundColor Red
Write-Host "$UpstreamRemote."
Expand Down
41 changes: 41 additions & 0 deletions scripts/host/update_daaf.sh
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,25 @@ if ! echo "${ORIGIN_URL}" | grep -qi "${UPSTREAM_REPO}"; then
echo ""
fi

# =====================================================================
# Ensure fetch refspec covers all branches
# =====================================================================
# git clone --depth 1 -b <ref> implies --single-branch, which locks the
# fetch refspec to only the cloned ref. This means git fetch will never
# retrieve other branches (like main), breaking auto-detect. Widen to
# the standard wildcard if it's currently narrow.
CURRENT_REFSPEC=$(docker compose exec -T daaf-docker \
git -C /daaf config --get remote."${UPSTREAM_REMOTE}".fetch \
</dev/null 2>/dev/null | tr -d '\r' || true)

if [ -n "${CURRENT_REFSPEC}" ] \
&& [ "${CURRENT_REFSPEC}" != "+refs/heads/*:refs/remotes/${UPSTREAM_REMOTE}/*" ]; then
docker compose exec -T daaf-docker \
git -C /daaf config --replace-all remote."${UPSTREAM_REMOTE}".fetch \
"+refs/heads/*:refs/remotes/${UPSTREAM_REMOTE}/*" </dev/null 2>/dev/null \
|| true
fi

# =====================================================================
# Fetch latest
# =====================================================================
Expand Down Expand Up @@ -757,6 +776,28 @@ if [ -n "${REMOTE_BRANCH}" ]; then
if ! docker compose exec -T daaf-docker \
git -C /daaf rev-parse --verify "${UPSTREAM_REMOTE}/${REMOTE_BRANCH}" \
</dev/null >/dev/null 2>&1; then

# Check if the value is a version tag rather than a branch.
# Tags live in refs/tags/, not refs/remotes/origin/, so the branch
# check above correctly fails for them.
if docker compose exec -T daaf-docker \
git -C /daaf rev-parse --verify "refs/tags/${REMOTE_BRANCH}" \
</dev/null >/dev/null 2>&1; then
echo ""
echo "'${REMOTE_BRANCH}' is a version tag, not a branch."
echo ""
echo "The updater needs a branch to pull changes from. Tags are fixed"
echo "snapshots and cannot receive updates."
echo ""
echo "To update to the latest release on the main branch:"
echo " bash update_daaf.sh"
echo " (without setting DAAF_BRANCH)"
echo ""
echo "To update from a specific branch:"
echo " DAAF_BRANCH=dev bash update_daaf.sh"
exit 1
fi

echo ""
echo "The branch '${REMOTE_BRANCH}' (from DAAF_BRANCH) was not found on"
echo "${UPSTREAM_REMOTE}."
Expand Down
63 changes: 63 additions & 0 deletions tests/bash/update_daaf.bats
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,7 @@ setup_state_machine() {
*"compose exec"*"fetch"*) return 0 ;;
*"compose exec"*"rev-parse --verify"*"backup/"*) return 1 ;;
*"compose exec"*"rev-parse --verify"*"origin/nonexistent-branch-xyz"*) return 1 ;;
*"compose exec"*"rev-parse --verify"*"refs/tags/nonexistent-branch-xyz"*) return 1 ;;
*"compose exec"*"branch"*) return 0 ;;
*"compose exec"*"rev-parse"*"HEAD"*) echo "abc123" ;;
*) return 0 ;;
Expand All @@ -844,4 +845,66 @@ setup_state_machine() {
assert_failure
assert_output --partial "nonexistent-branch-xyz"
assert_output --partial "was not found"
refute_output --partial "version tag"
}

@test "update: DAAF_BRANCH is a tag gives tag-specific error" {
setup_state_machine
run bash -c '
export DAAF_BRANCH="v2.1.0"
docker() {
local all_args="$*"
case "$all_args" in
"info") return 0 ;;
*"compose ps"*"--format"*) echo "daaf-docker" ;;
*"compose exec"*"true"*) return 0 ;;
*"compose exec"*"test -f"*"/daaf/CLAUDE.md"*) return 0 ;;
*"compose exec"*"test -f"*"/daaf/.git/shallow"*) return 1 ;;
*"compose exec"*"remote get-url"*"origin"*) echo "https://github.com/DAAF-Contribution-Community/daaf.git" ;;
*"compose exec"*"fetch"*) return 0 ;;
*"compose exec"*"rev-parse --verify"*"backup/"*) return 1 ;;
*"compose exec"*"rev-parse --verify"*"origin/v2.1.0"*) return 1 ;;
*"compose exec"*"rev-parse --verify"*"refs/tags/v2.1.0"*) return 0 ;;
*"compose exec"*"branch"*) return 0 ;;
*"compose exec"*"rev-parse"*"HEAD"*) echo "abc123" ;;
*) return 0 ;;
esac
}
export -f docker
bash "'"${REPO_ROOT}"'/scripts/host/update_daaf.sh"
'
assert_failure
assert_output --partial "version tag"
assert_output --partial "not a branch"
}

@test "update: DAAF_BRANCH is neither branch nor tag gives generic error" {
setup_state_machine
run bash -c '
export DAAF_BRANCH="totally-bogus-ref"
docker() {
local all_args="$*"
case "$all_args" in
"info") return 0 ;;
*"compose ps"*"--format"*) echo "daaf-docker" ;;
*"compose exec"*"true"*) return 0 ;;
*"compose exec"*"test -f"*"/daaf/CLAUDE.md"*) return 0 ;;
*"compose exec"*"test -f"*"/daaf/.git/shallow"*) return 1 ;;
*"compose exec"*"remote get-url"*"origin"*) echo "https://github.com/DAAF-Contribution-Community/daaf.git" ;;
*"compose exec"*"fetch"*) return 0 ;;
*"compose exec"*"rev-parse --verify"*"backup/"*) return 1 ;;
*"compose exec"*"rev-parse --verify"*"origin/totally-bogus-ref"*) return 1 ;;
*"compose exec"*"rev-parse --verify"*"refs/tags/totally-bogus-ref"*) return 1 ;;
*"compose exec"*"branch"*) return 0 ;;
*"compose exec"*"rev-parse"*"HEAD"*) echo "abc123" ;;
*) return 0 ;;
esac
}
export -f docker
bash "'"${REPO_ROOT}"'/scripts/host/update_daaf.sh"
'
assert_failure
assert_output --partial "totally-bogus-ref"
assert_output --partial "was not found"
refute_output --partial "version tag"
}
79 changes: 79 additions & 0 deletions tests/powershell/update_daaf.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,85 @@ function docker {
$outputStr = $output | Out-String
$LASTEXITCODE | Should -Be 1
$outputStr | Should -BeLike "*was not found*"
$outputStr | Should -Not -BeLike "*version tag*"
Remove-Item Env:DAAF_BRANCH -ErrorAction SilentlyContinue
}

It "DAAF_BRANCH is a tag gives tag-specific error" {
$env:DAAF_NESTED = "1"
$env:DAAF_BRANCH = "v2.1.0"
$wrapperScript = Join-Path $script:TestDir "test_wrapper_tag.ps1"
Set-Content -Path $wrapperScript -Value @'
$ErrorActionPreference = "Stop"
function docker {
$argStr = $args -join ' '
$global:LASTEXITCODE = 0
switch -Wildcard ($argStr) {
"*info*" { return }
"*compose ps*--format*" { Write-Output "daaf-docker" }
"*compose exec*true*" { return }
"*compose exec*test -f*/daaf/.git/shallow*" { $global:LASTEXITCODE = 1; return }
"*compose exec*test -f*" { return }
"*compose exec*git -C /daaf remote get-url origin*" {
Write-Output "https://github.com/DAAF-Contribution-Community/daaf.git"
}
"*compose exec*git -C /daaf fetch*" { return }
"*compose exec*git -C /daaf rev-parse --verify*backup/*" { $global:LASTEXITCODE = 1; return }
"*compose exec*git -C /daaf rev-parse --verify*origin/v2.1.0*" { $global:LASTEXITCODE = 1; return }
"*compose exec*git -C /daaf rev-parse --verify*refs/tags/v2.1.0*" { $global:LASTEXITCODE = 0; return }
"*compose exec*git -C /daaf branch*" { return }
"*compose exec*git -C /daaf rev-parse*HEAD*" { Write-Output "abc123" }
"*compose exec*git*" { return }
"*compose exec*" { return }
default { return }
}
}
'@
Add-Content -Path $wrapperScript -Value ". '$RepoRoot/scripts/host/update_daaf.ps1'"
$output = & pwsh -NoProfile -File $wrapperScript *>&1
$outputStr = $output | Out-String
$LASTEXITCODE | Should -Be 1
$outputStr | Should -BeLike "*version tag*"
$outputStr | Should -BeLike "*not a branch*"
Remove-Item Env:DAAF_BRANCH -ErrorAction SilentlyContinue
}

It "DAAF_BRANCH is neither branch nor tag gives generic error" {
$env:DAAF_NESTED = "1"
$env:DAAF_BRANCH = "totally-bogus-ref"
$wrapperScript = Join-Path $script:TestDir "test_wrapper_bogus.ps1"
Set-Content -Path $wrapperScript -Value @'
$ErrorActionPreference = "Stop"
function docker {
$argStr = $args -join ' '
$global:LASTEXITCODE = 0
switch -Wildcard ($argStr) {
"*info*" { return }
"*compose ps*--format*" { Write-Output "daaf-docker" }
"*compose exec*true*" { return }
"*compose exec*test -f*/daaf/.git/shallow*" { $global:LASTEXITCODE = 1; return }
"*compose exec*test -f*" { return }
"*compose exec*git -C /daaf remote get-url origin*" {
Write-Output "https://github.com/DAAF-Contribution-Community/daaf.git"
}
"*compose exec*git -C /daaf fetch*" { return }
"*compose exec*git -C /daaf rev-parse --verify*backup/*" { $global:LASTEXITCODE = 1; return }
"*compose exec*git -C /daaf rev-parse --verify*origin/totally-bogus-ref*" { $global:LASTEXITCODE = 1; return }
"*compose exec*git -C /daaf rev-parse --verify*refs/tags/totally-bogus-ref*" { $global:LASTEXITCODE = 1; return }
"*compose exec*git -C /daaf branch*" { return }
"*compose exec*git -C /daaf rev-parse*HEAD*" { Write-Output "abc123" }
"*compose exec*git*" { return }
"*compose exec*" { return }
default { return }
}
}
'@
Add-Content -Path $wrapperScript -Value ". '$RepoRoot/scripts/host/update_daaf.ps1'"
$output = & pwsh -NoProfile -File $wrapperScript *>&1
$outputStr = $output | Out-String
$LASTEXITCODE | Should -Be 1
$outputStr | Should -BeLike "*was not found*"
$outputStr | Should -Not -BeLike "*version tag*"
Remove-Item Env:DAAF_BRANCH -ErrorAction SilentlyContinue
}

Expand Down
8 changes: 6 additions & 2 deletions user_reference/01_installation_and_quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ The update script handles everything for you:
- **Syncs utility scripts** — automatically copies any updated host-side scripts (run, backup, rebuild, update) from the container
- **Auto-rebuilds if needed** — if the Dockerfile or docker-compose.yml changed, it offers to run `rebuild_daaf` automatically

By default, the update script auto-detects the remote's default branch (`main` or `master`). If you installed from a specific branch (e.g., `dev` or a release tag) and want to keep updating from that branch, set the `DAAF_BRANCH` environment variable — the same one used by the installer:
By default, the update script auto-detects the remote's default branch (`main` or `master`). If you installed from a specific branch (e.g., `dev`) and want to keep updating from that branch, set the `DAAF_BRANCH` environment variable:

```bash
# macOS / Linux
Expand All @@ -403,6 +403,8 @@ $env:DAAF_BRANCH = "dev"; .\update_daaf.ps1

If `DAAF_BRANCH` is not set, the updater defaults to `main` (or `master` if `main` doesn't exist). The script validates that the specified branch exists on the remote before proceeding.

> **Note:** `DAAF_BRANCH` must be a **branch name** for the updater — not a version tag like `v2.1.0`. Tags are fixed snapshots and cannot receive updates. If you installed from a tag, the updater will automatically pull from `main` when `DAAF_BRANCH` is not set. You can use tags with the installer (see "Installing a specific version or branch" below), but for ongoing updates, use the default or specify a branch.

Your research files in `research/` are not tracked by git (they're local to your volume), so they are completely unaffected by updates.

**If the Dockerfile changed** (new packages, updated Claude Code version, etc.), you'll also need to rebuild the Docker image. The update script will detect this automatically and print instructions.
Expand Down Expand Up @@ -486,7 +488,9 @@ $env:DAAF_BRANCH="v2.1.0"; irm "https://raw.githubusercontent.com/DAAF-Contribut
$env:DAAF_BRANCH="dev"; irm "https://raw.githubusercontent.com/DAAF-Contribution-Community/daaf/$env:DAAF_BRANCH/scripts/host/install.ps1" | iex
```

This fetches the installer itself from the specified branch, and also controls the Docker build files and repository clone, so everything comes from the specified branch or tag consistently. The `export` on macOS/Linux is required so that the variable is inherited by the `bash` process on the other side of the pipe. Check the [Releases page](https://github.com/DAAF-Contribution-Community/daaf/releases) to see available versions. If `DAAF_BRANCH` is not set, the installer defaults to `main`.
This fetches the installer itself from the specified branch or tag, and also controls the Docker build files and repository clone, so everything comes from the specified ref consistently. The `export` on macOS/Linux is required so that the variable is inherited by the `bash` process on the other side of the pipe. Check the [Releases page](https://github.com/DAAF-Contribution-Community/daaf/releases) to see available versions. If `DAAF_BRANCH` is not set, the installer defaults to `main`.

> **Note:** The installer accepts both branch names and version tags, but the **updater** (`update_daaf.sh` / `update_daaf.ps1`) only accepts branch names. If you install from a tag, you do not need to set `DAAF_BRANCH` when updating — the updater will automatically pull from `main`.

### Re-installing DAAF

Expand Down
Loading