Skip to content

Commit

Permalink
redeem-nuggit: store nuggits in objects of remote repository
Browse files Browse the repository at this point in the history
This is done in order to make it
a) less likely that a player will find the dangling objects and
b) eventually we want to show the player about dangling references and
these just pollute the output
  • Loading branch information
miallo committed Jan 16, 2024
1 parent c076269 commit 35baca9
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 25 deletions.
10 changes: 6 additions & 4 deletions architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ Creating such an interactive repo that references itself everywhere is a bit ted

### The "database" of redeemed nuggits

When the player tries to redeem a nuggit, we don't want the redeeming script just to contain a list of all the nuggits, since the player could just open that file (maybe even by accident) and see the list. Instead they are stored as loose objects in the .git/objects folder themselves. but because the user could just run
When the player tries to redeem a nuggit, we don't want the redeeming script just to contain a list of all the nuggits, since the player could just open that file (maybe even by accident) and see the list. Instead they are stored as loose objects in the .git/my-origin/objects folder themselves. but because the user could just run
```sh
git fsck --dangling | cut -d " " -f3 | xargs -n 1 git cat-file -p
cd .git/my-origin; git fsck --dangling | cut -d " " -f3 | xargs -n 1 git cat-file -p
```
to get all the objects in plain text, we don't store the nuggits, but their hashes (yes, it feels a bit like hash-ception). And since we also want to store a description to show in the `git log nuggits`, we collect all the nuggits into a tree object (folder), which has trees again as children with the name of the hashed nuggit, and each of these folders contains a file "description" that we can append to the nuggit commit and a file "success" that we can use for a custom commit message per nuggit.

Once a nuggit is redeemed, we write another object to the "database" with the format `'$nuggit' already redeemed`. That way we can look it up in our "database" before we tell the user they redeemed a new one.

### The nuggit pseudo-branch

For the player to be able to run `git log nuggits`, we have another trick up our sleeves: we have a "pseudo branch". "Pseudo branch" as in "not found under `.git/refs/heads/nuggits`", but instead at `.git/nuggits`. That way it is not found with `git branch --list` or `git tag --list`, because that would just irritate the player later in the gameplay. But for the log we rely on gits way to dereference refs where it also searches from the toplevel of the git folder. But from the content side it is indeed (just as a normal branch) a file containing a commit hash.

When the player redeems a valid nuggit, we basically want to commit to that branch, but we obviously can't just `git switch nuggits && git commit --allow-empty -m "$nuggit" && git switch -`, since we
Expand All @@ -37,8 +41,6 @@ c) don't want to pollute the reflog.

So what do we do then? Easy: We use the plumbing commands to create the commit and then manually update our "branch".

Once a nuggit is redeemed, we write another object to the "database" with the format `'$nuggit' already redeemed`. That way we can look it up in our "database" before we tell the user they redeemed a new one.

As a side-note, we don't just rely on the number of commits in our nuggits branch, since the user could easily tinker with that. But with each newly redeemed nuggit we also write the total number of redeemed nuggits to the objects. That way we can at least cross reference a tiny bit if the player has tinkered with our system (but of course they could just open the script and reverse that as well...)

## Pushing & Pulling
Expand Down
8 changes: 3 additions & 5 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,11 @@ git commit-tree "$(printf "" | git mktree)" -m "RootOfAllNuggits
Have a free nuggit!" > .git/nuggits

# TODO: once we have the origin and another "clone" in the .git folder, we should store the blobs in there, because it is trivial to list all of them with `git fsck --dangling | cut -d " " -f3 | xargs -n 1 git cat-file -p`
# shellcheck disable=1091 # it does not seem to like the indirection
store_nuggits
ALMOST_CREDITS_HASH="$(git hash-object -w "$DOCDIR/almost_credits.txt")"
ALMOST_CREDITS_HASH="$(remote_hash_object_write "$DOCDIR/almost_credits.txt")"
# for the final credits do a little rot13, just to make life a bit harder if anyone e.g. greps through the loose objects...
FINAL_CREDITS_HASH="$(tr 'A-Za-z' 'N-ZA-Mn-za-m' < "$DOCDIR/credits.txt" | git hash-object -w --stdin)"
CREDITS_TREE="$(printf "100644 blob %s almost\n100644 blob %s final\n" "$ALMOST_CREDITS_HASH" "$FINAL_CREDITS_HASH" | git mktree)"
FINAL_CREDITS_HASH="$(tr 'A-Za-z' 'N-ZA-Mn-za-m' < "$DOCDIR/credits.txt" | remote_hash_object_write --stdin)"
CREDITS_TREE="$(printf "100644 blob %s almost\n100644 blob %s final\n" "$ALMOST_CREDITS_HASH" "$FINAL_CREDITS_HASH" | remote_mktree)"

NUMBER_OF_NUGGITS="$(($(wc -l <"$DOCDIR/nuggits.tsv")))"

Expand Down
16 changes: 12 additions & 4 deletions lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ create_chapter() {
printf "\e[32mCreating chapter '%s'\e[0m\n" "$chapter"
}

remote_git_dir=.git/my-origin
remote_mktree() {
GIT_DIR="$remote_git_dir" git mktree
}
remote_hash_object_write() {
GIT_DIR="$remote_git_dir" git hash-object -w "$@"
}

# register the nuggits in our "git database" (aka some loose objects)
store_nuggits() {
# To avoid the player just running
Expand All @@ -109,14 +117,14 @@ store_nuggits() {
# We create a folder with subfolders of the names of the nuggit hashes, and
# each of them contains a file for the `git log nuggits`-description and
# the custom success message when redeeming it
NUGGIT_DESCRIPTION_TREE="$(git mktree < <(while read -r line; do
NUGGIT_DESCRIPTION_TREE="$(remote_mktree < <(while read -r line; do
nuggit="$(printf "%s" "$line" | cut -d " " -f 1)"
nuggit_folder_name="$(echo "$nuggit" | git hash-object --stdin)"
nuggit_description="$(printf "%s" "$line" | cut -d " " -f 2)"
nuggit_success_message="$(printf "%s" "$line" | cut -d " " -f 3)"
nuggit_description_file_hash="$(git hash-object -w --stdin <<< "$nuggit_description")"
success_file_hash="$(echo "Success! What a nice nuggit for your collection! 🏅 $nuggit_success_message" | git hash-object -w --stdin)"
description_tree_hash="$(printf "100644 blob %s description\n100644 blob %s success\n" "$nuggit_description_file_hash" "$success_file_hash"| git mktree)"
nuggit_description_file_hash="$(remote_hash_object_write --stdin <<< "$nuggit_description")"
success_file_hash="$(echo "Success! What a nice nuggit for your collection! 🏅 $nuggit_success_message" | remote_hash_object_write --stdin)"
description_tree_hash="$(printf "100644 blob %s description\n100644 blob %s success\n" "$nuggit_description_file_hash" "$success_file_hash"| remote_mktree)"
if [ "$nuggit" = LocalCodeExecution ]; then
printf "%s" "$description_tree_hash" > tmp
fi
Expand Down
2 changes: 1 addition & 1 deletion src/hook_preamble.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ this_file="$0"

hash="LOCAL_CODE_EXECUTION_HASH"
# Make sure to delete the nuggit, so it can't be redeemed after this got triggered once
rm ".git/objects/${hash:0:2}/${hash:2}"
rm ".git/my-origin/objects/${hash:0:2}/${hash:2}"

(
cd "$ROOT/.git/hooks" || exit
Expand Down
35 changes: 24 additions & 11 deletions src/redeem-nuggit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,22 @@ fi
already_redeemed="'$nuggit' already redeemed"
tried_before="You tried '$nuggit' before. It still isn't a valid answer... 🙄"

# A helper function to write to the remote git objects
# This is done, because:
# a) we don't want to "pollute" the local objects, since we eventually want to
# also teach the player about that and it would only be noise
# b) it makes it just a tiny bit harder to discover the nuggits
hash_object_write() {
GIT_DIR=.git/my-origin git hash-object --stdin -w >/dev/null 2>&1
}
# A helper function to read from to the remote git objects
catfile() {
GIT_DIR=.git/my-origin git cat-file "$@"
}

redeemed=0
# check if this nuggit was already_redeemed
git cat-file -e "$(echo "$already_redeemed" | git hash-object --stdin)" 2>/dev/null && redeemed=1
catfile -e "$(echo "$already_redeemed" | git hash-object --stdin)" 2>/dev/null && redeemed=1
# The total number of nuggits is one bigger than the already committed ones
# because of the root commit, so we have to subtract 1 if this one was already
# redeemed
Expand All @@ -35,31 +48,31 @@ redeemed_nuggits="$(($(git rev-list --count nuggits) - redeemed))"
# to give a hint that LocalCodeExecution is self-deleting.
# CREDITS_TREE is a tree-object and CREDITS_TREE:almost means the blob with the
# name "almost" inside of it
[ "$redeemed_nuggits" -ne $((NUMBER_OF_NUGGITS - 1)) ] || git cat-file -p CREDITS_TREE:almost;
[ "$redeemed_nuggits" -ne $((NUMBER_OF_NUGGITS - 1)) ] || catfile -p CREDITS_TREE:almost;

# shellcheck disable=2170 # NUMBER_OF_NUGGITS will be replaced by an integer, once we "build" it.
[ "$redeemed_nuggits" -ne NUMBER_OF_NUGGITS ] || {
# check if the player did not just add a commit to our "nuggits"
# pseudobranch by looking up if we wrote the last number of redeemed
# nuggits to the objects. This is just a very basic "cheat-detection"...
git cat-file -e "$(git hash-object --stdin <<< "$((NUMBER_OF_NUGGITS - 1))" | git hash-object --stdin)" 2>/dev/null || { echo Naughty boy!; exit 1; }
catfile -e "$(git hash-object --stdin <<< "$((NUMBER_OF_NUGGITS - 1))" | git hash-object --stdin)" 2>/dev/null || { echo Naughty boy!; exit 1; }
# print the final credits. See "almost" above for syntax description
# Also do rot13 on the result in order to make it just a bit harder to just
# find the blob and read it
git cat-file -p CREDITS_TREE:final | tr 'A-Za-z' 'N-ZA-Mn-za-m';
catfile -p CREDITS_TREE:final | tr 'A-Za-z' 'N-ZA-Mn-za-m';
}

# if we already have redeemed this nuggit, print that it was already redeemed and exit
git cat-file -p "$(echo "$already_redeemed" | git hash-object --stdin)" 2>/dev/null && exit
catfile -p "$(echo "$already_redeemed" | git hash-object --stdin)" 2>/dev/null && exit
# if the user already tried to submit this wrong string, print the error and exit
git cat-file -p "$(echo "$tried_before" | git hash-object --stdin)" 2>/dev/null && exit 1
catfile -p "$(echo "$tried_before" | git hash-object --stdin)" 2>/dev/null && exit 1

# Print the success message for this nuggit if it exists
git cat-file -p "NUGGIT_DESCRIPTION_TREE:$(echo "$nuggit" | git hash-object --stdin)/success" 2>/dev/null || {
catfile -p "NUGGIT_DESCRIPTION_TREE:$(echo "$nuggit" | git hash-object --stdin)/success" 2>/dev/null || {
# if it does not exist, then this is not a valid nuggit (or it was
# LocalCodeExecution and that was deleted)
echo "Unfortunately that is not a valid nuggit :/ Try again!" >&2
echo "$tried_before" | git hash-object --stdin -w >/dev/null 2>&1
echo "$tried_before" | hash_object_write >/dev/null 2>&1
exit 1
}

Expand All @@ -69,7 +82,7 @@ git cat-file -p "NUGGIT_DESCRIPTION_TREE:$(echo "$nuggit" | git hash-object --st
tree="$(git rev-parse "nuggits^{tree}")"
# get the description from our "nuggit tree object" from the folder with
# the hash of the nuggit and inside of that the description file
description="$(git cat-file -p "NUGGIT_DESCRIPTION_TREE:$(echo "$nuggit" | git hash-object --stdin)/description")"
description="$(catfile -p "NUGGIT_DESCRIPTION_TREE:$(echo "$nuggit" | git hash-object --stdin)/description")"
# add an empty commit with the parent being nuggits and "reset nuggits to that new commit"
git commit-tree "$tree" -p "$(cat .git/nuggits)" -m "$(printf "%s\n\n" "$nuggit" "$description")" > .git/nuggits.bak
# We can't directly pipe it into the file, because it will empty it before we read it...
Expand All @@ -79,6 +92,6 @@ mv .git/nuggits.bak .git/nuggits # update our "branch"
# Print some stats for the player, so they know if they still need to look for other nuggits
echo "Number of redeemed nuggits: $redeemed_nuggits of NUMBER_OF_NUGGITS"
# Write to our database, that this nuggit is now redeemed
echo "$already_redeemed" | git hash-object --stdin -w >/dev/null 2>&1
echo "$already_redeemed" | hash_object_write
# Write as a "cheat-detection" for the final credits how many we have redeemed
git hash-object --stdin <<< "$redeemed_nuggits"| git hash-object --stdin -w >/dev/null 2>&1
git hash-object --stdin <<< "$redeemed_nuggits"| hash_object_write

0 comments on commit 35baca9

Please sign in to comment.