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
45 changes: 37 additions & 8 deletions .mise/tasks/obfuscate
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,17 @@ renamed=$(mktemp) || {
echo "Error: failed to create rename result" >&2
exit 1
}
head_manifest=$(mktemp) || {
indexed_manifest=$(mktemp) || {
rm -f "$plan" "$renamed"
echo "Error: failed to create manifest snapshot" >&2
exit 1
}
trap 'rm -f "$plan" "$renamed" "$head_manifest"' EXIT
staged_manifest=$(mktemp) || {
rm -f "$plan" "$renamed" "$indexed_manifest"
echo "Error: failed to create staged manifest" >&2
exit 1
}
trap 'rm -f "$plan" "$renamed" "$indexed_manifest" "$staged_manifest"' EXIT

if ! build_obfuscation_plan "$abs_notes_dir" "$plan" ${ARGS[@]+"${ARGS[@]}"}; then
echo "Error: obfuscation planning failed" >&2
Expand Down Expand Up @@ -109,15 +114,16 @@ git -C "$TARGET_DIR" rm --cached --quiet --ignore-unmatch -- \
"${readable_paths[@]}"

# Full mode always owns the complete manifest. Scoped mode stages it only when
# a renamed mapping is absent from HEAD, preventing unrelated uncommitted
# mappings from leaking into a pre-commit operation.
# a renamed mapping is absent from the index, preventing unrelated unstaged
# mappings from leaking while preserving manifest changes already selected by
# notes stage.
stage_manifest=false
if ! $scoped_mode; then
stage_manifest=true
else
if ! git -C "$TARGET_DIR" cat-file --filters \
"HEAD:$notes_dir/.manifest" > "$head_manifest" 2>/dev/null; then
: > "$head_manifest"
":$notes_dir/.manifest" > "$indexed_manifest" 2>/dev/null; then
: > "$indexed_manifest"
fi
if ! awk -F '\t' -v renamed="$renamed" '
FILENAME != renamed {
Expand All @@ -126,13 +132,36 @@ else
}
!(($2 FS $1) in entries) { missing = 1 }
END { exit missing ? 1 : 0 }
' "$head_manifest" "$renamed"; then
' "$indexed_manifest" "$renamed"; then
stage_manifest=true
fi
fi

if $stage_manifest; then
git -C "$TARGET_DIR" add "$manifest"
if ! $scoped_mode; then
git -C "$TARGET_DIR" add "$manifest"
else
# Stage the indexed manifest plus only this operation's mappings. Keep
# unrelated working edits out of the index, and use the same canonical
# name ordering as the working manifest.
awk -F '\t' -v OFS='\t' -v renamed="$renamed" '
FILENAME == renamed {
target_ids[$2] = 1
target_names[$1] = 1
rows[++count] = $2 FS $1
next
}
!(($1 in target_ids) || ($2 in target_names)) { print }
END {
for (i = 1; i <= count; i++) print rows[i]
}
' "$renamed" "$indexed_manifest" \
| sort -t$'\t' -k2 > "$staged_manifest"
manifest_blob=$(git -C "$TARGET_DIR" hash-object -w \
--path="$notes_dir/.manifest" "$staged_manifest")
git -C "$TARGET_DIR" update-index --add --cacheinfo \
100644 "$manifest_blob" "$notes_dir/.manifest"
fi
fi

echo ""
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

**Collective memory, encrypted.**

[![tests: 419](https://img.shields.io/badge/tests-419-brightgreen?style=flat)](test/)
[![tests: 420](https://img.shields.io/badge/tests-420-brightgreen?style=flat)](test/)
![lints: 8](https://img.shields.io/badge/lints-8-blue?style=flat)
[![license: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat)](LICENSE)

Expand Down
32 changes: 30 additions & 2 deletions test/obfuscate.bats
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,34 @@ setup() {
[ -f "$NOTES_CALLER_PWD/notes/$alpha_id" ]
}

@test "scoped new mapping stages canonical order without unrelated manifest edits" {
# Select a later-sorting mapping in the index, then leave another mapping
# only in the working manifest.
printf '22222222\tgamma.txt\n' > "$NOTES_CALLER_PWD/notes/.manifest"
git -C "$NOTES_CALLER_PWD" add -f notes/.manifest
printf '33333333\tbeta.md\n' >> "$NOTES_CALLER_PWD/notes/.manifest"

notes obfuscate alpha.md

local staged_manifest staged_names working_manifest
staged_manifest=$(git -C "$NOTES_CALLER_PWD" show :notes/.manifest)
staged_names=$(printf '%s\n' "$staged_manifest" | cut -f2)
working_manifest=$(cat "$NOTES_CALLER_PWD/notes/.manifest")
[ "$staged_names" = $'alpha.md\ngamma.txt' ]
[[ "$staged_manifest" != *$'\tbeta.md'* ]]
[[ "$working_manifest" == *$'\talpha.md'* ]]
[[ "$working_manifest" == *$'33333333\tbeta.md'* ]]

# After committing and removing the intentionally unstaged mapping, no
# order-only manifest difference remains.
git -C "$NOTES_CALLER_PWD" commit -q --no-verify -m "scoped obfuscation"
grep -v $'33333333\tbeta.md' "$NOTES_CALLER_PWD/notes/.manifest" \
> "$NOTES_CALLER_PWD/notes/.manifest.filtered"
mv "$NOTES_CALLER_PWD/notes/.manifest.filtered" \
"$NOTES_CALLER_PWD/notes/.manifest"
git -C "$NOTES_CALLER_PWD" diff --quiet -- notes/.manifest
}

@test "obfuscate refuses to overwrite an existing known-ID destination" {
notes obfuscate
local alpha_id
Expand Down Expand Up @@ -284,7 +312,7 @@ SH
[ "$rm_calls" -eq 1 ]
}

@test "scoped obfuscate reads the HEAD manifest once and batches staging" {
@test "scoped obfuscate reads the indexed manifest once and batches staging" {
local mock_bin command real_command call_log
notes obfuscate
git -C "$NOTES_CALLER_PWD" commit -q --no-verify -m "obfuscated"
Expand All @@ -306,7 +334,7 @@ SH
notes obfuscate alpha.md beta.md

[ "$status" -eq 0 ]
[ "$(grep -c $'^git\t.* cat-file --filters HEAD:notes/.manifest$' "$call_log" || true)" -eq 1 ]
[ "$(grep -c $'^git\t.* cat-file --filters :notes/.manifest$' "$call_log" || true)" -eq 1 ]
[ "$(grep -c $'^git\t.* add -- notes/' "$call_log" || true)" -eq 1 ]
[ "$(grep -c $'^git\t.* rm --cached --quiet --ignore-unmatch -- notes/' "$call_log" || true)" -eq 1 ]
}
Expand Down
Loading