Commit bde763e1629e

Vincent Demeester <vincent@sbr.pm>
2026-01-08 14:43:44
refactor(nixpkgs): Simplify consolidation with git rebase approach
- Replace 150-line cherry-pick logic with 40-line rebase for clarity - Let git automatically skip empty commits already in upstream - Reuse worktrees across runs to prevent resource busy errors - Fix service permissions and dependencies for proper execution Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 6c1326a
Changed files (4)
modules
nixpkgs-consolidate
systems
tools
modules/nixpkgs-consolidate/default.nix
@@ -87,16 +87,16 @@ in
         Type = "oneshot";
         User = cfg.user;
         ExecStart = "${consolidateScript}";
+        Environment = ''"GIT_SSH_COMMAND=ssh -o ControlMaster=no"'';
 
         # Security hardening
         PrivateTmp = true;
         ProtectSystem = "strict";
-        ProtectHome = "read-only";
+        ProtectHome = "false"; # Need full access to home for git operations
         ReadWritePaths = [
           cfg.repoPath
           cfg.worktreePath
           cfg.logDir
-          (builtins.dirOf cfg.configFile)
         ];
         NoNewPrivileges = true;
 
@@ -126,6 +126,7 @@ in
       "d ${cfg.logDir} 0750 ${cfg.user} users -"
       "d ${cfg.repoPath} 0750 ${cfg.user} users -"
       "d ${cfg.worktreePath} 0750 ${cfg.user} users -"
+      "d ${builtins.dirOf cfg.configFile} 0755 ${cfg.user} users -"
     ];
   };
 }
systems/aomi/extra.nix
@@ -49,9 +49,9 @@
   # Age secrets
   age.secrets."ntfy-token" = {
     file = ../../secrets/sakhalin/ntfy-token.age;
-    mode = "400";
+    mode = "440";
     owner = "root";
-    group = "root";
+    group = "users";
   };
 
   # TODO make it an option ? (otherwise I'll add it for all)
tools/nixpkgs-consolidate/default.nix
@@ -1,4 +1,12 @@
-{ lib, stdenv }:
+{
+  lib,
+  stdenv,
+  makeWrapper,
+  git,
+  curl,
+  coreutils,
+  openssh,
+}:
 
 stdenv.mkDerivation {
   pname = "nixpkgs-consolidate";
@@ -6,6 +14,8 @@ stdenv.mkDerivation {
 
   src = ./.;
 
+  nativeBuildInputs = [ makeWrapper ];
+
   dontUnpack = true;
   dontBuild = true;
 
@@ -19,6 +29,17 @@ stdenv.mkDerivation {
     # Patch shebang
     patchShebangs $out/bin/nixpkgs-consolidate
 
+    # Wrap with runtime dependencies
+    wrapProgram $out/bin/nixpkgs-consolidate \
+      --prefix PATH : ${
+        lib.makeBinPath [
+          git
+          curl
+          coreutils
+          openssh
+        ]
+      }
+
     runHook postInstall
   '';
 
tools/nixpkgs-consolidate/nixpkgs-consolidate.sh
@@ -10,6 +10,8 @@ NIXPKGS_REPO_PATH="${NIXPKGS_REPO_PATH:-/home/vincent/src/nixpkgs}"
 NIXPKGS_WORKTREE_PATH="${NIXPKGS_WORKTREE_PATH:-/home/vincent/src/nixpkgs-consolidate-work}"
 NIXPKGS_UPSTREAM_REMOTE="${NIXPKGS_UPSTREAM_REMOTE:-upstream}"
 NIXPKGS_FORK_REMOTE="${NIXPKGS_FORK_REMOTE:-origin}"
+NIXPKGS_FORK_URL="${NIXPKGS_FORK_URL:-git@github.com:vdemeester/nixpkgs.git}"
+NIXPKGS_UPSTREAM_URL="${NIXPKGS_UPSTREAM_URL:-https://github.com/NixOS/nixpkgs.git}"
 NIXPKGS_CONSOLIDATED_BRANCH="${NIXPKGS_CONSOLIDATED_BRANCH:-wip-consolidated}"
 NIXPKGS_CONFIG_FILE="${NIXPKGS_CONFIG_FILE:-$HOME/.config/nixpkgs-automation/branches.conf}"
 NTFY_SERVER="${NTFY_SERVER:-https://ntfy.sbr.pm}"
@@ -24,7 +26,6 @@ WIP_BRANCHES=()
 TOTAL_COMMITS=0
 COMPLETED_BRANCHES=()
 FAILED_BRANCH=""
-FAILED_COMMIT=""
 WORKTREE_CREATED=false
 
 # Setup logging
@@ -57,12 +58,9 @@ notify() {
 cleanup() {
   local exit_code=$?
 
-  # Clean up worktree if it was created
-  if [ "$WORKTREE_CREATED" = true ]; then
-    log "Cleaning up worktree..."
-    cd "$NIXPKGS_REPO_PATH"
-    git worktree remove "$NIXPKGS_WORKTREE_PATH" --force 2>&1 | tee -a "$LOG_FILE" || true
-  fi
+  # Note: We intentionally do NOT remove the worktree here
+  # It will be reused on the next run, avoiding "Device or resource busy" issues
+  # The worktree will be cleaned up automatically by setup_worktree() on next run
 
   if [ $exit_code -ne 0 ]; then
     log "ERROR: Consolidation failed with exit code $exit_code"
@@ -76,12 +74,11 @@ cleanup() {
     fi
 
     # Send failure notification
-    local failure_msg="Cherry-pick conflict or error during consolidation
+    local failure_msg="Rebase conflict or error during consolidation
 
 Base: $BASE_BRANCH
 Completed: $completed_str
 Failed at: ${FAILED_BRANCH:-unknown}
-${FAILED_COMMIT:+Commit: $FAILED_COMMIT}
 
 Logs: $LOG_FILE"
 
@@ -140,19 +137,19 @@ parse_config() {
 }
 
 ensure_repo() {
-  if [ ! -d "$NIXPKGS_REPO_PATH" ]; then
+  if [ ! -d "$NIXPKGS_REPO_PATH/.git" ]; then
     log "Repository not found at $NIXPKGS_REPO_PATH"
-    log "Cloning NixOS/nixpkgs..."
+    log "Cloning from fork: $NIXPKGS_FORK_URL"
 
     mkdir -p "$(dirname "$NIXPKGS_REPO_PATH")"
-    git clone git@github.com:NixOS/nixpkgs.git "$NIXPKGS_REPO_PATH"
+    git clone "$NIXPKGS_FORK_URL" "$NIXPKGS_REPO_PATH"
 
     cd "$NIXPKGS_REPO_PATH"
 
-    # Setup remotes if needed
+    # Setup upstream remote
     if ! git remote | grep -q "^${NIXPKGS_UPSTREAM_REMOTE}$"; then
-      log "Adding upstream remote: $NIXPKGS_UPSTREAM_REMOTE"
-      git remote add "$NIXPKGS_UPSTREAM_REMOTE" git@github.com:NixOS/nixpkgs.git
+      log "Adding upstream remote: $NIXPKGS_UPSTREAM_URL"
+      git remote add "$NIXPKGS_UPSTREAM_REMOTE" "$NIXPKGS_UPSTREAM_URL"
     fi
 
     log "Repository cloned successfully"
@@ -179,22 +176,36 @@ ensure_repo() {
 setup_worktree() {
   cd "$NIXPKGS_REPO_PATH"
 
-  # Remove existing worktree if it exists
-  if [ -d "$NIXPKGS_WORKTREE_PATH" ]; then
-    log "Removing existing worktree at $NIXPKGS_WORKTREE_PATH"
-    git worktree remove "$NIXPKGS_WORKTREE_PATH" --force 2>&1 | tee -a "$LOG_FILE" || true
+  # Check if worktree is registered in git
+  if git worktree list | grep -q "$NIXPKGS_WORKTREE_PATH"; then
+    log "Reusing existing worktree at $NIXPKGS_WORKTREE_PATH"
+    cd "$NIXPKGS_WORKTREE_PATH"
+
+    # Reset to upstream base branch
+    log "Resetting worktree to $NIXPKGS_UPSTREAM_REMOTE/$BASE_BRANCH"
+    git checkout -B "$NIXPKGS_CONSOLIDATED_BRANCH" "$NIXPKGS_UPSTREAM_REMOTE/$BASE_BRANCH" 2>&1 | tee -a "$LOG_FILE"
+    WORKTREE_CREATED=true
+    log "Worktree reset successfully"
+    return
   fi
 
-  # Delete local consolidated branch if it exists (we'll recreate it fresh)
+  # Worktree not registered - clean up stale references and create fresh
+  log "Creating new worktree at $NIXPKGS_WORKTREE_PATH"
+  log "Branching from $NIXPKGS_UPSTREAM_REMOTE/$BASE_BRANCH"
+
+  # Clean up any stale worktree references and remove directory if exists
+  git worktree prune 2>&1 | tee -a "$LOG_FILE" || true
+  if [ -d "$NIXPKGS_WORKTREE_PATH" ]; then
+    log "Removing stale worktree directory"
+    rm -rf "$NIXPKGS_WORKTREE_PATH"
+  fi
+
+  # Delete local consolidated branch if it exists and is not checked out
   if git show-ref --verify --quiet "refs/heads/$NIXPKGS_CONSOLIDATED_BRANCH"; then
     log "Deleting existing local branch $NIXPKGS_CONSOLIDATED_BRANCH"
     git branch -D "$NIXPKGS_CONSOLIDATED_BRANCH" 2>&1 | tee -a "$LOG_FILE" || true
   fi
 
-  # Create new worktree with consolidated branch from base
-  log "Creating worktree at $NIXPKGS_WORKTREE_PATH"
-  log "Branching from $NIXPKGS_UPSTREAM_REMOTE/$BASE_BRANCH"
-
   if git worktree add -b "$NIXPKGS_CONSOLIDATED_BRANCH" \
     "$NIXPKGS_WORKTREE_PATH" \
     "$NIXPKGS_UPSTREAM_REMOTE/$BASE_BRANCH" 2>&1 | tee -a "$LOG_FILE"; then
@@ -207,7 +218,6 @@ setup_worktree() {
 }
 
 consolidate_branches() {
-  local commit_count=0
   cd "$NIXPKGS_WORKTREE_PATH"
 
   for branch in "${WIP_BRANCHES[@]}"; do
@@ -220,55 +230,40 @@ consolidate_branches() {
       exit 1
     fi
 
-    # Get commits that are in WIP branch but not in base
-    log "Finding commits to cherry-pick from $branch..."
-    COMMITS=$(git log --reverse --pretty=format:%H \
-      "$NIXPKGS_UPSTREAM_REMOTE/$BASE_BRANCH..$NIXPKGS_FORK_REMOTE/$branch" 2>/dev/null || true)
+    # Count commits before rebase (for logging)
+    local commits_before
+    commits_before=$(git rev-list --count HEAD)
 
-    if [ -z "$COMMITS" ]; then
-      log "No commits to cherry-pick from $branch (already in base or branch is behind base)"
-      COMPLETED_BRANCHES+=("$branch")
-      continue
+    # Rebase the WIP branch onto wip-consolidated
+    # This automatically drops empty commits (already in base)
+    log "Rebasing $NIXPKGS_FORK_REMOTE/$branch onto wip-consolidated..."
+    if ! git -c commit.gpgsign=false rebase "$NIXPKGS_FORK_REMOTE/$branch" 2>&1 | tee -a "$LOG_FILE"; then
+      log "ERROR: Rebase failed for branch $branch"
+      log "Aborting rebase..."
+      git rebase --abort 2>&1 | tee -a "$LOG_FILE" || true
+      exit 1
     fi
 
-    # Count commits
-    local branch_commit_count
-    branch_commit_count=$(echo "$COMMITS" | wc -l)
-    log "Found $branch_commit_count commit(s) to cherry-pick"
+    # Count commits after rebase
+    local commits_after
+    commits_after=$(git rev-list --count HEAD)
+    local commits_added=$((commits_after - commits_before))
 
-    # Cherry-pick each commit
-    for commit in $COMMITS; do
-      FAILED_COMMIT="$commit"
-      local commit_msg
-      commit_msg=$(git log -1 --pretty=format:%s "$commit")
-      log "Cherry-picking $commit: $commit_msg"
-
-      if ! git cherry-pick "$commit" 2>&1 | tee -a "$LOG_FILE"; then
-        log "ERROR: Cherry-pick failed for commit $commit in branch $branch"
-        log "Aborting cherry-pick..."
-        git cherry-pick --abort || true
-        exit 1
-      fi
-
-      ((commit_count++))
-    done
-
-    log "Successfully cherry-picked $branch_commit_count commit(s) from $branch"
+    log "Successfully rebased $branch: added $commits_added commit(s)"
     COMPLETED_BRANCHES+=("$branch")
     FAILED_BRANCH=""
-    FAILED_COMMIT=""
+    TOTAL_COMMITS=$((TOTAL_COMMITS + commits_added))
   done
 
-  TOTAL_COMMITS=$commit_count
-  log "Total commits cherry-picked: $TOTAL_COMMITS"
+  log "Total commits added: $TOTAL_COMMITS"
 }
 
 main() {
   log "=== Starting nixpkgs branch consolidation ==="
   log "Repository: $NIXPKGS_REPO_PATH"
   log "Worktree path: $NIXPKGS_WORKTREE_PATH"
-  log "Upstream remote: $NIXPKGS_UPSTREAM_REMOTE"
-  log "Fork remote: $NIXPKGS_FORK_REMOTE"
+  log "Fork remote: $NIXPKGS_FORK_REMOTE ($NIXPKGS_FORK_URL)"
+  log "Upstream remote: $NIXPKGS_UPSTREAM_REMOTE ($NIXPKGS_UPSTREAM_URL)"
   log "Consolidated branch: $NIXPKGS_CONSOLIDATED_BRANCH"
   log "Configuration: $NIXPKGS_CONFIG_FILE"
   log "Dry run: $DRY_RUN"
@@ -287,6 +282,12 @@ main() {
   log "Fetching from $NIXPKGS_FORK_REMOTE..."
   git fetch "$NIXPKGS_FORK_REMOTE" 2>&1 | tee -a "$LOG_FILE"
 
+  # List available branches on fork remote
+  log "Available branches on $NIXPKGS_FORK_REMOTE:"
+  git branch -r | grep "^  $NIXPKGS_FORK_REMOTE/" | sed "s|^  $NIXPKGS_FORK_REMOTE/||" | while read -r branch; do
+    log "  - $branch"
+  done
+
   # Verify base branch exists
   if ! git rev-parse --verify "$NIXPKGS_UPSTREAM_REMOTE/$BASE_BRANCH" >/dev/null 2>&1; then
     log "ERROR: Base branch $BASE_BRANCH not found on remote $NIXPKGS_UPSTREAM_REMOTE"
@@ -317,7 +318,7 @@ main() {
     log "Branch differs from upstream/$BASE_BRANCH by $TOTAL_COMMITS commits"
   else
     log "Force pushing $NIXPKGS_CONSOLIDATED_BRANCH to $NIXPKGS_FORK_REMOTE (with --force-with-lease)..."
-    if git push --force-with-lease "$NIXPKGS_FORK_REMOTE" "$NIXPKGS_CONSOLIDATED_BRANCH" 2>&1 | tee -a "$LOG_FILE"; then
+    if git push --force-with-lease "$NIXPKGS_FORK_REMOTE" "$NIXPKGS_CONSOLIDATED_BRANCH:$NIXPKGS_CONSOLIDATED_BRANCH" 2>&1 | tee -a "$LOG_FILE"; then
       log "Successfully pushed to $NIXPKGS_FORK_REMOTE/$NIXPKGS_CONSOLIDATED_BRANCH"
     else
       log "ERROR: Push failed. This might mean the remote branch was updated by someone else."