case "$1" in
--force) FORCE_CLEAN=yes ;;
--hard) HARD_FORCE=yes ;;
+ --repack) DO_REPACK=yes ;;
--init) FORCE_INIT=yes ;;
-*) echo "Unknown option: $1" >&2; exit 1 ;;
*)
}
# do_push PUSHARGS (multiple lines like +src:dst, with both + and src opt.)
+# Perform repack, manifest generation, and upload
+# Requires: r_revlist, Tempdir, Packkey_bytes, Hashtype, Packlist, Keeplist, Recipients, Refslist, Repoid, Extnlist, URL, NAME, VERSION
+perform_repack()
+{
+ local tmp_encrypted tmp_objlist tmp_manifest pack_id key_ r_pack_delete=""
+
+ tmp_encrypted="$Tempdir/packP"
+ tmp_objlist="$Tempdir/objlP"
+
+ {
+ xfeed "$r_revlist" git rev-list --objects --stdin --
+ repack_if_needed @r_pack_delete
+ } > "$tmp_objlist"
+
+ # Only send pack if we have any objects to send
+ if [ -s "$tmp_objlist" ]
+ then
+ key_=$(genkey "$Packkey_bytes")
+ pack_id=$(export GIT_ALTERNATE_OBJECT_DIRECTORIES="$Tempdir";
+ pipefail git pack-objects --stdout < "$tmp_objlist" |
+ pipefail ENCRYPT "$key_" |
+ tee "$tmp_encrypted" | gpg_hash "$Hashtype")
+
+ append_to @Packlist "pack :${Hashtype}:$pack_id $key_"
+ if isnonnull "$r_pack_delete"
+ then
+ append_to @Keeplist "keep :${Hashtype}:$pack_id 1"
+ fi
+ fi
+
+ # Generate manifest
+ # Update the gcrypt version in extensions (remove old, add current)
+ filter_to ! @Extnlist "extn gcrypt-version *" "$Extnlist"
+ append_to @Extnlist "extn gcrypt-version $VERSION"
+
+ echo_info "Encrypting to: $Recipients"
+ echo_info "Requesting manifest signature"
+
+ tmp_manifest="$Tempdir/maniP"
+ PRIVENCRYPT "$Recipients" > "$tmp_manifest" <<EOF
+$Refslist
+$Packlist
+$Keeplist
+repo $Repoid
+$Extnlist
+EOF
+
+ # Upload pack
+ if [ -s "$tmp_objlist" ]
+ then
+ PUT "$URL" "$pack_id" "$tmp_encrypted"
+ fi
+
+ # Upload manifest
+ PUT "$URL" "$Manifestfile" "$tmp_manifest"
+
+ rm -f "$tmp_encrypted"
+ rm -f "$tmp_objlist"
+ rm -f "$tmp_manifest"
+
+ # Delete packs
+ if isnonnull "$r_pack_delete"; then
+ REMOVE "$URL" "$(xecho "$r_pack_delete" | \
+ while IFS=': ' read -r _ _ pack_
+ do
+ isnonnull "$pack_" || continue
+ xecho "$pack_"
+ done)"
+ fi
+}
+
do_push()
{
# Security protocol:
fi
fi
- tmp_encrypted="$Tempdir/packP"
- tmp_objlist="$Tempdir/objlP"
-
- {
- xfeed "$r_revlist" git rev-list --objects --stdin --
- repack_if_needed @r_pack_delete
- } > "$tmp_objlist"
-
- # Only send pack if we have any objects to send
- if [ -s "$tmp_objlist" ]
- then
- key_=$(genkey "$Packkey_bytes")
- pack_id=$(export GIT_ALTERNATE_OBJECT_DIRECTORIES="$Tempdir";
- pipefail git pack-objects --stdout < "$tmp_objlist" |
- pipefail ENCRYPT "$key_" |
- tee "$tmp_encrypted" | gpg_hash "$Hashtype")
-
- append_to @Packlist "pack :${Hashtype}:$pack_id $key_"
- if isnonnull "$r_pack_delete"
- then
- append_to @Keeplist "keep :${Hashtype}:$pack_id 1"
- fi
- fi
-
- # Generate manifest
- # Update the gcrypt version in extensions (remove old, add current)
- filter_to ! @Extnlist "extn gcrypt-version *" "$Extnlist"
- append_to @Extnlist "extn gcrypt-version $VERSION"
-
- echo_info "Encrypting to: $Recipients"
- echo_info "Requesting manifest signature"
-
- tmp_manifest="$Tempdir/maniP"
- PRIVENCRYPT "$Recipients" > "$tmp_manifest" <<EOF
-$Refslist
-$Packlist
-$Keeplist
-repo $Repoid
-$Extnlist
-EOF
-
- # Upload pack
- if [ -s "$tmp_objlist" ]
- then
- PUT "$URL" "$pack_id" "$tmp_encrypted"
- fi
-
- # Upload manifest
- PUT "$URL" "$Manifestfile" "$tmp_manifest"
-
- rm -f "$tmp_encrypted"
- rm -f "$tmp_objlist"
- rm -f "$tmp_manifest"
-
- # Delete packs
- if isnonnull "$r_pack_delete"; then
- REMOVE "$URL" "$(xecho "$r_pack_delete" | \
- while IFS=': ' read -r _ _ pack_
- do
- isnonnull "$pack_" || continue
- xecho "$pack_"
- done)"
- fi
+ perform_repack
PUT_FINAL "$URL"
done
IFS="$OIFS"
bad_files="${bad_files#"$Newline"}"
-
+
if isnull "$bad_files"; then
echo_info "No unencrypted files found. Remote is clean."
+ if [ "${DO_REPACK:-}" = "yes" ]; then
+ echo_info "Repacking remote..."
+ # Prepare r_revlist for repack (all refs)
+ r_revlist=""
+ if isnonnull "$Refslist"; then
+ r_revlist=$(xecho "$Refslist" | cut -d' ' -f1)
+ fi
+ GCRYPT_FULL_REPACK=1
+ perform_repack
+ PUT_FINAL "$URL"
+ fi
CLEAN_FINAL "$URL"
git remote remove "$NAME" 2>/dev/null || true
exit 0
else
echo_info " git-remote-gcrypt clean --force $URL"
fi
+
+ # If user requested repack but found bad files and no force, abort (safety first)
+ if [ "${DO_REPACK:-}" = "yes" ]; then
+ echo_info "NOTE: Repack requested but pending file deletions require --force."
+ fi
+
CLEAN_FINAL "$URL"
git remote remove "$NAME" 2>/dev/null || true
exit 0
echo_info "Removing files..."
REMOVE "$URL" "$bad_files"
+
+ if [ "${DO_REPACK:-}" = "yes" ]; then
+ echo_info "Repacking remote..."
+ # Prepare r_revlist from all current refs for full repack
+ r_revlist=""
+ if isnonnull "$Refslist"; then
+ r_revlist=$(xecho "$Refslist" | cut -d' ' -f1)
+ fi
+
+ # Set flag to force repack_if_needed to act
+ GCRYPT_FULL_REPACK=1
+ perform_repack
+ fi
+
PUT_FINAL "$URL"
CLEAN_FINAL "$URL"
git remote remove "$NAME" 2>/dev/null || true
--- /dev/null
+#!/bin/sh
+set -e
+
+# Setup test environment
+echo "Setting up repack test environment..."
+PROJECT_ROOT="$(pwd)"
+mkdir -p .tmp
+TEST_DIR="$PROJECT_ROOT/.tmp/repack_test"
+rm -rf "$TEST_DIR"
+mkdir -p "$TEST_DIR"
+
+# Repo paths
+REPO_DIR="$TEST_DIR/repo"
+REMOTE_DIR="$TEST_DIR/remote"
+
+mkdir -p "$REPO_DIR"
+mkdir -p "$REMOTE_DIR"
+
+# Tools
+GCRYPT_BIN="$PROJECT_ROOT/git-remote-gcrypt"
+if [ ! -x "$GCRYPT_BIN" ]; then
+ echo "Error: git-remote-gcrypt binary not found at $GCRYPT_BIN"
+ exit 1
+fi
+
+# GPG Setup
+export GNUPGHOME="$TEST_DIR/gpg"
+mkdir -p "$GNUPGHOME"
+chmod 700 "$GNUPGHOME"
+
+cat <<EOF >"${GNUPGHOME}/gpg"
+#!/usr/bin/env bash
+export GNUPGHOME="$GNUPGHOME"
+set -efuC -o pipefail; shopt -s inherit_errexit
+args=( "\${@}" )
+for ((i = 0; i < \${#}; ++i)); do
+ if [[ \${args[\${i}]} = "--secret-keyring" ]]; then
+ unset "args[\${i}]" "args[\$(( i + 1 ))]"
+ break
+ fi
+done
+exec gpg "\${args[@]}"
+EOF
+chmod +x "${GNUPGHOME}/gpg"
+
+echo "Generating GPG key..."
+gpg --batch --passphrase "" --quick-generate-key "Test <test@test.com>"
+
+# Initialize repo
+cd "$REPO_DIR"
+git init
+git config user.email "test@test.com"
+git config user.name "Test User"
+git config --global advice.defaultBranchName false
+
+# Initialize local remote
+git init --bare "$REMOTE_DIR"
+git remote add origin "gcrypt::$REMOTE_DIR"
+git config remote.origin.gcrypt-participants "test@test.com"
+git config remote.origin.gcrypt-signingkey "test@test.com"
+git config gpg.program "${GNUPGHOME}/gpg"
+git config user.signingkey "test@test.com"
+
+export PATH="$PROJECT_ROOT:$PATH"
+
+# Create fragmentation by pushing multiple times
+echo "Push 1"
+echo "data 1" >file1.txt
+git add file1.txt
+git commit -m "Commit 1" --no-gpg-sign
+# Initial push needs force to initialize remote gcrypt repo
+git push origin +master
+
+echo "Push 2"
+echo "data 2" >file2.txt
+git add file2.txt
+git commit -m "Commit 2" --no-gpg-sign
+git push origin master
+
+echo "Push 3"
+echo "data 3" >file3.txt
+git add file3.txt
+git commit -m "Commit 3" --no-gpg-sign
+git push origin master
+
+# Verify we have multiple pack files in remote
+# Note: gcrypt stores packs in 'pack' directory if using rsync-like backend?
+# For git backend (gitception), they are objects in the git repo.
+# We are using local file backend? No, gcrypt::$REMOTE_DIR where REMOTE_DIR is bare git repo.
+# This makes it a Git Backend (gitception).
+# The packs are stored as blobs in the backend repo.
+# But 'do_push' logic downloads packs using 'git rev-list'.
+# The 'Packlist' manifest file lists the active packs.
+# We can check the Manifest to count packs.
+
+# Clone the raw backend to inspect manifest
+cd "$TEST_DIR"
+git clone "$REMOTE_DIR" raw_backend
+cd raw_backend
+git checkout master
+# The manifest is a file with randomized name, but we can find it encrypt/decrypt?
+# No, easier: use git-remote-gcrypt to list packs via debug or inference.
+# Or just trust that multiple pushes created multiple packs (as gcrypt doesn't auto-repack on push unless configured).
+
+# Let's count lines in Packlist from the helper's debug output?
+# Or we can verify the backend git repo has multiple commits (one per push).
+HEAD_SHA=$(git rev-parse HEAD)
+echo "Backend SHA: $HEAD_SHA"
+# Start should have 3 commits (init, push1, push2, push3) -> wait, init is implicit.
+# Each push updates the manifested repo.
+
+# Run clean --repack
+cd "$REPO_DIR"
+echo "Running clean --repack..."
+git-remote-gcrypt clean --repack origin
+
+# Verify result
+# Clone backend again (pull) and check structure
+cd "$TEST_DIR/raw_backend"
+git pull origin master
+
+# Count commits? Repack might add a commit?
+# Repack reads all objects, creates 1 new pack, updates manifest.
+# This results in a NEW commit on the backend that has the new manifest.
+# The OLD packs are removed (deleted from backend).
+# So we should see a new commit.
+# Check if commit SHA changed. Repack force-pushes a new manifest state.
+NEW_HEAD=$(git rev-parse HEAD)
+echo "Old HEAD: $HEAD_SHA"
+echo "New HEAD: $NEW_HEAD"
+
+if [ "$NEW_HEAD" != "$HEAD_SHA" ]; then
+ echo "Repack successful (HEAD changed)."
+else
+ echo "Repack failed (HEAD did not change)."
+ exit 1
+fi
+
+# Verify data integrity
+cd "$REPO_DIR"
+# Force fresh clone to verified data
+cd "$TEST_DIR"
+git clone "gcrypt::$REMOTE_DIR" verified_repo
+cd verified_repo
+if [ -f file1.txt ] && [ -f file2.txt ] && [ -f file3.txt ]; then
+ echo "Data integrity verified."
+else
+ echo "Data integrity failed!"
+ exit 1
+fi
+
+echo "Test passed."