aboutsummaryrefslogtreecommitdiff
path: root/comp2041
diff options
context:
space:
mode:
Diffstat (limited to 'comp2041')
-rwxr-xr-xcomp2041/slippy/slippy.py385
-rwxr-xr-xcomp2041/tigger/test00.sh58
-rwxr-xr-xcomp2041/tigger/test01.sh53
-rwxr-xr-xcomp2041/tigger/test02.sh73
-rwxr-xr-xcomp2041/tigger/test03.sh56
-rwxr-xr-xcomp2041/tigger/test04.sh45
-rwxr-xr-xcomp2041/tigger/test05.sh46
-rwxr-xr-xcomp2041/tigger/test06.sh62
-rwxr-xr-xcomp2041/tigger/test07.sh51
-rwxr-xr-xcomp2041/tigger/test08.sh70
-rwxr-xr-xcomp2041/tigger/test09.sh61
-rwxr-xr-xcomp2041/tigger/tigger-add42
-rwxr-xr-xcomp2041/tigger/tigger-branch68
-rwxr-xr-xcomp2041/tigger/tigger-checkout84
-rwxr-xr-xcomp2041/tigger/tigger-commit88
-rwxr-xr-xcomp2041/tigger/tigger-init39
-rwxr-xr-xcomp2041/tigger/tigger-log24
-rwxr-xr-xcomp2041/tigger/tigger-rm66
-rwxr-xr-xcomp2041/tigger/tigger-show63
-rwxr-xr-xcomp2041/tigger/tigger-status100
20 files changed, 1534 insertions, 0 deletions
diff --git a/comp2041/slippy/slippy.py b/comp2041/slippy/slippy.py
new file mode 100755
index 0000000..8cd02ba
--- /dev/null
+++ b/comp2041/slippy/slippy.py
@@ -0,0 +1,385 @@
+#!/usr/bin/env python3
+
+import re, sys, os, getopt, fileinput
+
+def get_error_strs():
+ '''
+ Returns a dictionary of error strings we throw in multiple locations.
+ '''
+ error_strs = {
+ "usage": "usage: slippy [-i] [-n] [-f <script-file> | <sed-command>] "\
+ "[<files>...]",
+ "invalid_command": "slippy: command line: invalid command",
+ }
+ return error_strs
+
+
+def find_regex(regex, str):
+ '''
+ Returns a pair describing matched regex - None if not found.
+ The first element is a string describing the entire match captured.
+ The second element is the list of captured groups.
+ '''
+ match = re.search(regex, str)
+ if match == None:
+ return None
+
+ ret = []
+ if match != None:
+ for group in match.groups():
+ if group == None:
+ continue
+ ret.append(group)
+ return (match.group(0), ret)
+
+
+# We use getopt instead of argparse because we need to match the spec's errors.
+def get_args():
+ '''
+ Returns a tuple where the first element is a dictionary of parsed args and
+ the second argument is a dictionary of remaining args (sed regex + files).
+ The key in both dictionaries is the position of the argument.
+ This function will throw if the arguments are invalid.
+ Eg: ({0: ("-p", ""), 1: ("-q", "3"}, {0: ["s/asdf/asdf/g"]})
+ '''
+ short = "inf:"
+ long = []
+ try:
+ (parsed, unparsed) = getopt.getopt(sys.argv[1:], short, long)
+ except:
+ raise RuntimeError(get_error_strs()["usage"])
+ return (dict(enumerate(parsed)), dict(enumerate(unparsed)))
+
+
+def get_command_address(command, delimit):
+ '''
+ Returns a pair (<string>, list<string>) of addresses for a command.
+ The first element in the pair is the entire address.
+ The second element in the pair is a list of addresses.
+ If the address is a line number, it will NOT be contained by the delimiter.
+ If the address is regex, it will be surrounded by the delimiter.
+ If the list has more than one element, it is a range.
+ 3p -> ("3", ["3"])
+ /.1/q -> ("/.1/", ["/.1/"])
+ /4/,/6/s/[12]/9/ -> ("/4/,/6/", ["/4/", "/6/"]) NOTE the lack of ","
+ '''
+
+ d = delimit if delimit != "/" else r"\/" # regex needs \/ instead of /
+
+ regex = r"^(\/[^\/]*\/|[0-9$]),(\/[^\/]+\/|[0-9$]+)|^(\/[^\/]+\/)|(^[0-9$]+),([0-9$]+)|(^[0-9$]+)"
+ find = find_regex(regex, command)
+ return (("", []) if find == None else find)
+
+
+def get_command_arguments(command, delimit):
+ '''
+ Returns the contents of a the command split by "/".
+ /megamaid -> ["megamaid"]
+ /asdf/qwer/g -> ["asdf", "qwer", "g"]
+ '''
+ return command.split(delimit)[1:]
+
+
+def parse_command(command):
+ '''
+ Transforms a command represented as a string into a dictionary describing
+ the command.
+ Examples:
+ "1d" ->
+ "addresses" : ["1"], // A string list of addresses
+ "type" : "d", // Type as a string
+ "arguments" : [] // List of args
+ "enabled" : False // Stateful range indicator
+
+ "s/.1/qwer/g" ->
+ "addresses" : [],
+ "type" : "s",
+ "arguments" : [".1", "qwer", "g"]
+ "enabled" : False
+
+ "/4/,/6/s/[12]/9/" ->
+ "addresses" : ["/4/", "/6/"],
+ "type" : "s",
+ "arguments" : ["[12]", "9"]
+ "enabled" : False
+ '''
+
+ delimit = command[1] if len(command) >= 2 and command[0] == "s" else "/"
+
+ (address_prefix, addresses) = get_command_address(command, delimit)
+ command = command.removeprefix(address_prefix)
+
+ type = command[0]
+ command = command.removeprefix(type)
+
+ arguments = get_command_arguments(command, delimit)
+
+ return {"addresses": addresses, \
+ "type": type, \
+ "arguments": arguments, \
+ "enabled": False}
+
+
+def unroll_commands(commands):
+ '''
+ Transforms a list of commands into a list of the results of parse_commands.
+ Splits commands into multiple if necessary. Removes comments.
+ '''
+ ret = []
+ for unrolled in commands:
+ for command in unrolled.split(";"):
+
+ command = command.replace(" ", "") # remove whitespace
+
+ if len(command) <= 0:
+ continue
+
+ command = command.split("#", 1)[0] # remove comments
+
+ if len(command) <= 0:
+ continue
+
+ ret.append(command)
+
+ return list(map(parse_command, ret))
+
+
+
+def get_state():
+ '''
+ Returns a dictionary containing elements describing the commands passed to
+ the program.
+ The dictionary will contain the following key-values (with examples):
+ "commands" : [parse_command("s/qwer/asdf/g"), parse_command("etc")],
+ "args": ["-p", "-q"],
+ "files": ["readme.txt", "example.py"],
+
+ '''
+ (parsed_args, unparsed_args) = get_args()
+
+ commands = []
+ args = []
+ files = []
+
+ for key in parsed_args:
+ arg = parsed_args[key][0]
+ if arg == "-f":
+ filename = parsed_args[key][1]
+ with open(filename) as f:
+ commands = f.read().splitlines()
+ args.append(arg)
+ for key in sorted(unparsed_args.keys()):
+ arg = unparsed_args[key]
+ if not "-f" in args and key == 0:
+ commands += [arg]
+ continue
+ files += [arg]
+
+ # No command parsed is invalid syntax.
+ if len(commands) <= 0:
+ raise RuntimeError(get_error_strs()["usage"])
+
+ return {
+ "commands": unroll_commands(commands),
+ "args": args,
+ "files": files,
+ }
+
+
+def is_regex_match(regex, count, current_line, next_line):
+ '''
+ Returns true if the regex exists in the string, value otherwise.
+ '''
+ return find_regex(regex, current_line) != None
+
+
+def is_line_match(target, count, current_line, next_line, lequal=False):
+ '''
+ Returns true if the target is equal to count, false otherwise.
+ '''
+ if target == "$":
+ return len(next_line) == 0
+
+ conv = int(target)
+ if lequal:
+ return conv > count
+ return conv == count
+
+
+def is_address_regex(address):
+ return len(address) >= 1 and address[0] == "/" and address[-1] == "/"
+
+
+def is_address_match(address, count, current_line, next_line):
+ '''
+ Deduces whether the address is a line count or regex address and returns
+ its deduced value.
+ '''
+ if is_address_regex(address):
+ return is_regex_match(address[1:-1], count, current_line, next_line)
+ return is_line_match(address, count, current_line, next_line)
+
+
+def is_address_within(address, count, current_line, next_line):
+ if is_address_regex(address):
+ return not is_regex_match(address[1:-1], count, current_line, next_line)
+ return is_line_match(address, count, current_line, next_line, True)
+
+
+def is_command_within(command, count, current_line, next_line):
+ addresses = command["addresses"]
+
+ start = addresses[0]
+ end = addresses[1]
+
+ # If we're not in the range...
+ if not command["enabled"]:
+ if not is_address_match(start, count, current_line, next_line):
+ return False
+
+ command["enabled"] = True
+ if not is_address_regex(end) and not is_address_within(end, count, current_line, next_line):
+ command["enabled"] = False
+ return True
+
+ # We're in the range, check if we should be out.
+ if not is_address_within(end, count, current_line, next_line):
+ command["enabled"] = False
+ return True
+
+
+def handle_q(command, count, current_line, next_line, args):
+ if not "-n" in args:
+ print(current_line, end="")
+ sys.exit(1)
+ return current_line # superfluous
+
+
+def handle_p(command, count, current_line, next_line, args):
+ print(current_line, end="")
+ return current_line
+
+
+def handle_d(command, count, current_line, next_line, args):
+ return ""
+
+
+def handle_s(command, count, current_line, next_line, args):
+ command_arguments = command["arguments"]
+ pattern = command_arguments[0]
+ replace = command_arguments[1]
+
+ sub = (re.sub(pattern, replace, current_line) \
+ if len(command_arguments) >= 3 and command_arguments[2] == "g" \
+ else re.sub(pattern, replace, current_line, 1))
+ return sub
+
+
+def get_command_funcs():
+ '''
+ Returns a dictionary of function pointers via each command type.
+ '''
+ command_funcs = {
+ "q": handle_q,
+ "p": handle_p,
+ "d": handle_d,
+ "s": handle_s
+ }
+ return command_funcs
+
+
+def should_run_command(command, count, current_line, next_line):
+ addresses = command["addresses"]
+ length = len(addresses)
+ if length == 0:
+ return True
+ if length == 1:
+ return is_address_match(addresses[0], count, current_line, next_line)
+ if length == 2:
+ val = is_command_within(command, count, current_line, next_line)
+ return val
+ raise RuntimeError(get_error_strs()["usage"])
+
+
+def run_command(command, count, current_line, next_line, args):
+ '''
+ Runs the command, modifying current_line if required.
+ '''
+
+ # Check if the command should be run before executing.
+ if not should_run_command(command, count, current_line, next_line):
+ return current_line
+
+ command_func = get_command_funcs()[command["type"]]
+ ret = command_func(command, count, current_line, next_line, args)
+
+ return ret
+
+
+def make_file_lines(files):
+ '''
+ Turns a list of filenames into a list of lines, in order as they appear.
+ '''
+ ret = []
+ for filename in files:
+ with open(filename) as f:
+ ret += f.read().splitlines(True)
+ return ret
+
+
+def get_new_line(file_lines, files):
+ '''
+ Gets a new line from stdin, or from the .
+ Returns a zero length string if out of data. This is distinct from an empty
+ line, which will be "\n".
+ '''
+
+ if len(files) <= 0:
+ return sys.stdin.readline()
+
+ if len(file_lines) <= 0:
+ return ""
+
+ line = file_lines[0]
+ file_lines.pop(0)
+ return line
+
+
+def loop(state):
+ '''
+ Main loop, handles running of commands.
+ '''
+ args = state["args"]
+ commands = state["commands"]
+ files = state["files"]
+ file_lines = make_file_lines(files)
+
+ count = 1
+ current_line = get_new_line(file_lines, files)
+ while current_line:
+ next_line = get_new_line(file_lines, files)
+
+ for command in commands:
+ current_line = run_command(command, count, current_line, next_line,\
+ args)
+ if len(current_line) == 0:
+ break
+
+ if not "-n" in args:
+ print(current_line, end="")
+
+ count += 1
+ current_line = next_line
+
+
+# We use a try block to handle error messages with the appropriate exit code.
+try:
+ state = get_state()
+ #print(state)
+ loop(state)
+except SystemExit:
+ pass
+except BaseException as error:
+ print(error, file=sys.stderr)
+ sys.exit(1)
+sys.exit(0)
diff --git a/comp2041/tigger/test00.sh b/comp2041/tigger/test00.sh
new file mode 100755
index 0000000..b76f4a3
--- /dev/null
+++ b/comp2041/tigger/test00.sh
@@ -0,0 +1,58 @@
+#!/bin/dash
+
+# Test startup, create a temporary directory and cd into it with files.
+tmp_dir=$(mktemp --directory --quiet --tmpdir=./)
+if [ "$(find tigger-* | wc -l)" -le 0 ]; then
+ printf "error: no tigger commands found, cannot run tests\n" >&2
+ exit 1
+fi
+cp tigger-* "$tmp_dir"/
+cd "$tmp_dir" || exit
+
+# Functions to test if the return value was 0 or non-zero.
+assert_good_exec () {
+ printf "%s: " "$1"
+ if $1; then
+ return
+ fi
+ printf "test failed, previous command expected zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+# Functions to test if the return value was 0 or non-zero.
+assert_bad_exec () {
+ printf "%s: " "$1"
+ if ! $1; then
+ return
+ fi
+ printf "test failed, previous command expected non-zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+printf "starting tests: output and order will be printed\n\n"
+# End of test startup
+
+# The purpose of this test is to ensure that removing the expected .tigger
+# folder does reset the state of tigger. In addition, there should be NO extra
+# files at the end of this operation, even if it fails. We are only allowed to
+# edit/create .tigger and what is inside .tigger!
+
+num_files_before=$(find ./ | wc -l)
+assert_good_exec "./tigger-init"
+num_files_after=$(find ./ | wc -l)
+assert_bad_exec "./tigger-init" # should make no more files
+if [ "$(find ./ | wc -l)" -ne "$num_files_after" ]; then
+ printf "the amount of files should be equal to before when tigger-init was called previously!\n" >&2
+ exit 1
+fi
+rm -rf ./.tigger
+if [ "$(find ./ | wc -l)" -ne "$num_files_before" ]; then
+ printf "there should be the same amount as files as before any commands were run!\n" >&2
+ exit 1
+fi
+assert_good_exec "./tigger-init"
+
+# Test teardown/cleanup
+printf "\ntest completed successfully\n"
+rm -rf ../"$tmp_dir"
+exit 0
diff --git a/comp2041/tigger/test01.sh b/comp2041/tigger/test01.sh
new file mode 100755
index 0000000..246ed2a
--- /dev/null
+++ b/comp2041/tigger/test01.sh
@@ -0,0 +1,53 @@
+#!/bin/dash
+
+# Test startup, create a temporary directory and cd into it with files.
+tmp_dir=$(mktemp --directory --quiet --tmpdir=./)
+if [ "$(find tigger-* | wc -l)" -le 0 ]; then
+ printf "error: no tigger commands found, cannot run tests\n" >&2
+ exit 1
+fi
+cp tigger-* "$tmp_dir"/
+cd "$tmp_dir" || exit
+
+# Functions to test if the return value was 0 or non-zero.
+assert_good_exec () {
+ printf "%s: " "$1"
+ if $1; then
+ return
+ fi
+ printf "test failed, previous command expected zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+# Functions to test if the return value was 0 or non-zero.
+assert_bad_exec () {
+ printf "%s: " "$1"
+ if ! $1; then
+ return
+ fi
+ printf "test failed, previous command expected non-zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+printf "starting tests: output and order will be printed\n\n"
+# End of test startup
+
+# The purpose of this test is to ensure that all commands fail if a valid
+# repository is not present (naturally aside from tigger-init).
+
+#assert_bad_exec "./tigger-init"
+assert_bad_exec "./tigger-add"
+assert_bad_exec "./tigger-commit"
+assert_bad_exec "./tigger-log"
+assert_bad_exec "./tigger-show"
+assert_bad_exec "./tigger-commit"
+assert_bad_exec "./tigger-rm"
+assert_bad_exec "./tigger-status"
+assert_bad_exec "./tigger-branch"
+assert_bad_exec "./tigger-checkout"
+assert_bad_exec "./tigger-merge"
+
+# Test teardown/cleanup
+printf "\ntest completed successfully\n"
+rm -rf ../"$tmp_dir"
+exit 0
diff --git a/comp2041/tigger/test02.sh b/comp2041/tigger/test02.sh
new file mode 100755
index 0000000..461da82
--- /dev/null
+++ b/comp2041/tigger/test02.sh
@@ -0,0 +1,73 @@
+#!/bin/dash
+
+# Test startup, create a temporary directory and cd into it with files.
+tmp_dir=$(mktemp --directory --quiet --tmpdir=./)
+if [ "$(find tigger-* | wc -l)" -le 0 ]; then
+ printf "error: no tigger commands found, cannot run tests\n" >&2
+ exit 1
+fi
+cp tigger-* "$tmp_dir"/
+cd "$tmp_dir" || exit
+
+# Functions to test if the return value was 0 or non-zero.
+assert_good_exec () {
+ printf "%s: " "$1"
+ if $1; then
+ return
+ fi
+ printf "test failed, previous command expected zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+# Functions to test if the return value was 0 or non-zero.
+assert_bad_exec () {
+ printf "%s: " "$1"
+ if ! $1; then
+ return
+ fi
+ printf "test failed, previous command expected non-zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+printf "starting tests: output and order will be printed\n\n"
+# End of test startup
+
+# The purpose of this test is to ensure that there are no arbitrary limits on
+# the amount of times a command may be performed, and that file
+# creation/deletion and increment logic is handled correctly between commits.
+# It's possible that this fails due to bad increment handling at the radix
+# point (if a program is implemented incorrectly). This test might also expose
+# complexity issues if they are present.
+
+# Specifically we might use sort as opposed to sort -n, which works until 10.
+# ... i did :(
+
+assert_good_exec "./tigger-init"
+printf "adding and committing large number of files...\n"
+for _ in $(seq 25); do
+ echo "repeated\n" >> repeated
+ assert_good_exec "./tigger-add repeated" > /dev/null
+ assert_good_exec "./tigger-commit -m placeholder" > /dev/null
+done
+
+# Commit count should be 50 here!
+printf "testing commit count string result\n"
+printf "repeated\n" >> repeated
+assert_good_exec "./tigger-add repeated" > /dev/null
+if [ "$(./tigger-commit -m placeholder)" != "Committed as commit 25" ]; then
+ printf "bad commit count string\n" >&2
+ exit 0
+fi
+
+# New one added so 26 now.
+printf "testing tigger-log line count\n"
+assert_good_exec "./tigger-log" > /dev/null
+if [ "$(./tigger-log | wc --line)" -ne 26 ]; then
+ printf "bad tigger log string length\n" >&2
+ exit 0
+fi
+
+# Test teardown/cleanup
+printf "\ntest completed successfully\n"
+rm -rf ../"$tmp_dir"
+exit 0
diff --git a/comp2041/tigger/test03.sh b/comp2041/tigger/test03.sh
new file mode 100755
index 0000000..6ce8559
--- /dev/null
+++ b/comp2041/tigger/test03.sh
@@ -0,0 +1,56 @@
+#!/bin/dash
+
+# Test startup, create a temporary directory and cd into it with files.
+tmp_dir=$(mktemp --directory --quiet --tmpdir=./)
+if [ "$(find tigger-* | wc -l)" -le 0 ]; then
+ printf "error: no tigger commands found, cannot run tests\n" >&2
+ exit 1
+fi
+cp tigger-* "$tmp_dir"/
+cd "$tmp_dir" || exit
+
+# Functions to test if the return value was 0 or non-zero.
+assert_good_exec () {
+ printf "%s: " "$1"
+ if $1; then
+ return
+ fi
+ printf "test failed, previous command expected zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+# Functions to test if the return value was 0 or non-zero.
+assert_bad_exec () {
+ printf "%s: " "$1"
+ if ! $1; then
+ return
+ fi
+ printf "test failed, previous command expected non-zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+printf "starting tests: output and order will be printed\n\n"
+# End of test startup
+
+# The purpose of this test is to test whether tigger-add handles a large number
+# of filenames. Also tests tigger-status against a new, uncommitted repo.
+
+assert_good_exec "./tigger-init"
+filenames=$(seq 20)
+printf "adding large number of files...\n"
+for filename in $filenames; do
+ touch "$filename" > /dev/null
+ assert_good_exec "./tigger-add $filename" > /dev/null
+done
+
+# Test the length of tigger-status.
+printf "testing tigger-status line count\n"
+if [ "$(./tigger-status | wc --line)" -ge 100 ]; then
+ printf "bad tigger status line count\n" >&2
+ exit 1
+fi
+
+# Test teardown/cleanup
+printf "\ntest completed successfully\n"
+rm -rf ../"$tmp_dir"
+exit 0
diff --git a/comp2041/tigger/test04.sh b/comp2041/tigger/test04.sh
new file mode 100755
index 0000000..80f7b2e
--- /dev/null
+++ b/comp2041/tigger/test04.sh
@@ -0,0 +1,45 @@
+#!/bin/dash
+
+# Test startup, create a temporary directory and cd into it with files.
+tmp_dir=$(mktemp --directory --quiet --tmpdir=./)
+if [ "$(find tigger-* | wc -l)" -le 0 ]; then
+ printf "error: no tigger commands found, cannot run tests\n" >&2
+ exit 1
+fi
+cp tigger-* "$tmp_dir"/
+cd "$tmp_dir" || exit
+
+# Functions to test if the return value was 0 or non-zero.
+assert_good_exec () {
+ printf "%s: " "$1"
+ if $1; then
+ return
+ fi
+ printf "test failed, previous command expected zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+# Functions to test if the return value was 0 or non-zero.
+assert_bad_exec () {
+ printf "%s: " "$1"
+ if ! $1; then
+ return
+ fi
+ printf "test failed, previous command expected non-zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+printf "starting tests: output and order will be printed\n\n"
+# End of test startup
+
+# The purpose of this test is to ensure that it isn't possible to commit a
+# binary file. (as specified, all files should be string-representable).
+
+assert_good_exec "./tigger-init"
+dd if=/dev/zero of=zero bs=1c count=1 status=none # stack overflow make binary file
+assert_bad_exec "./tigger-add a" >&2
+
+# Test teardown/cleanup
+printf "\ntest completed successfully\n"
+rm -rf ../"$tmp_dir"
+exit 0
diff --git a/comp2041/tigger/test05.sh b/comp2041/tigger/test05.sh
new file mode 100755
index 0000000..cc49cd1
--- /dev/null
+++ b/comp2041/tigger/test05.sh
@@ -0,0 +1,46 @@
+#!/bin/dash
+
+# Test startup, create a temporary directory and cd into it with files.
+tmp_dir=$(mktemp --directory --quiet --tmpdir=./)
+if [ "$(find tigger-* | wc -l)" -le 0 ]; then
+ printf "error: no tigger commands found, cannot run tests\n" >&2
+ exit 1
+fi
+cp tigger-* "$tmp_dir"/
+cd "$tmp_dir" || exit
+
+# Functions to test if the return value was 0 or non-zero.
+assert_good_exec () {
+ printf "%s: " "$1"
+ if $1; then
+ return
+ fi
+ printf "test failed, previous command expected zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+# Functions to test if the return value was 0 or non-zero.
+assert_bad_exec () {
+ printf "%s: " "$1"
+ if ! $1; then
+ return
+ fi
+ printf "test failed, previous command expected non-zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+printf "starting tests: output and order will be printed\n\n"
+# End of test startup
+
+# The purpose of this test is to ensure that adding a file with spacebars
+# results in an error. This is specified to not occur in the spec, but it should
+# still be handled correctly.
+
+assert_good_exec "./tigger-init"
+printf "placeholder contents" > "file name"
+assert_bad_exec "./tigger-add "file name""
+
+# Test teardown/cleanup
+printf "\ntest completed successfully\n"
+rm -rf ../"$tmp_dir"
+exit 0
diff --git a/comp2041/tigger/test06.sh b/comp2041/tigger/test06.sh
new file mode 100755
index 0000000..33c93f6
--- /dev/null
+++ b/comp2041/tigger/test06.sh
@@ -0,0 +1,62 @@
+#!/bin/dash
+
+# Test startup, create a temporary directory and cd into it with files.
+tmp_dir=$(mktemp --directory --quiet --tmpdir=./)
+if [ "$(find tigger-* | wc -l)" -le 0 ]; then
+ printf "error: no tigger commands found, cannot run tests\n" >&2
+ exit 1
+fi
+cp tigger-* "$tmp_dir"/
+cd "$tmp_dir" || exit
+
+# Functions to test if the return value was 0 or non-zero.
+assert_good_exec () {
+ printf "%s: " "$1"
+ if $1; then
+ return
+ fi
+ printf "test failed, previous command expected zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+# Functions to test if the return value was 0 or non-zero.
+assert_bad_exec () {
+ printf "%s: " "$1"
+ if ! $1; then
+ return
+ fi
+ printf "test failed, previous command expected non-zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+printf "starting tests: output and order will be printed\n\n"
+# End of test startup
+
+# The purpose of this test is to ensure that deleting a .tigger folder, and then
+# restoring the folder, results in the same state that occurred before deletion.
+# This implies that the metadata is held only in the .tigger directory.
+
+assert_good_exec "./tigger-init"
+touch a
+touch b
+touch c
+assert_good_exec "./tigger-add a b c"
+echo a >> a
+assert_good_exec "./tigger-add a"
+touch d
+assert_good_exec "./tigger-commit -m commit_message"
+status=$(./tigger-status)
+mv ./.tigger ./.tigger.old
+assert_good_exec "./tigger-init"
+rm -r ./.tigger
+mv ./.tigger.old ./.tigger
+printf "testing previous status is the same as the current status\n"
+if [ "$(./tigger-status)" != "$status" ]; then
+ printf "tigger-status was not the same between copies\n" >&2
+ exit 1
+fi
+
+# Test teardown/cleanup
+printf "\ntest completed successfully\n"
+rm -rf ../"$tmp_dir"
+exit 0
diff --git a/comp2041/tigger/test07.sh b/comp2041/tigger/test07.sh
new file mode 100755
index 0000000..fdffb7c
--- /dev/null
+++ b/comp2041/tigger/test07.sh
@@ -0,0 +1,51 @@
+#!/bin/dash
+
+# Test startup, create a temporary directory and cd into it with files.
+tmp_dir=$(mktemp --directory --quiet --tmpdir=./)
+if [ "$(find tigger-* | wc -l)" -le 0 ]; then
+ printf "error: no tigger commands found, cannot run tests\n" >&2
+ exit 1
+fi
+cp tigger-* "$tmp_dir"/
+cd "$tmp_dir" || exit
+
+# Functions to test if the return value was 0 or non-zero.
+assert_good_exec () {
+ printf "%s: " "$1"
+ if $1; then
+ return
+ fi
+ printf "test failed, previous command expected zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+# Functions to test if the return value was 0 or non-zero.
+assert_bad_exec () {
+ printf "%s: " "$1"
+ if ! $1; then
+ return
+ fi
+ printf "test failed, previous command expected non-zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+printf "starting tests: output and order will be printed\n\n"
+# End of test startup
+
+# The purpose of this test is to ensure that calling ./tigger-status and
+# tigger-log in an empty directory is not considered an error and results in
+# empty output.
+
+assert_good_exec "./tigger-init"
+assert_good_exec "./tigger-status"
+assert_good_exec "./tigger-log"
+printf "testing tigger status in an empty dir\n"
+if [ "$(./tigger-status | grep -E -v "^tigger-*")" != "" ]; then # remove tigger-* files from output
+ printf "tigger-status was not empty in an empty dir\n" >&2
+ exit 1
+fi
+
+# Test teardown/cleanup
+printf "\ntest completed successfully\n"
+rm -rf ../"$tmp_dir"
+exit 0
diff --git a/comp2041/tigger/test08.sh b/comp2041/tigger/test08.sh
new file mode 100755
index 0000000..6d9116f
--- /dev/null
+++ b/comp2041/tigger/test08.sh
@@ -0,0 +1,70 @@
+#!/bin/dash
+
+# Test startup, create a temporary directory and cd into it with files.
+tmp_dir=$(mktemp --directory --quiet --tmpdir=./)
+if [ "$(find tigger-* | wc -l)" -le 0 ]; then
+ printf "error: no tigger commands found, cannot run tests\n" >&2
+ exit 1
+fi
+cp tigger-* "$tmp_dir"/
+cd "$tmp_dir" || exit
+
+# Functions to test if the return value was 0 or non-zero.
+assert_good_exec () {
+ printf "%s: " "$1"
+ if $1; then
+ return
+ fi
+ printf "test failed, previous command expected zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+# Functions to test if the return value was 0 or non-zero.
+assert_bad_exec () {
+ printf "%s: " "$1"
+ if ! $1; then
+ return
+ fi
+ printf "test failed, previous command expected non-zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+printf "starting tests: output and order will be printed\n\n"
+# End of test startup
+
+# The purpose of this test is to ensure that creating a new branch from an empty
+# branch branches with multiple files with multiple commits and then deleting
+# the branch results in the same status as an empty branch.
+
+# TODO CURRENTLY DOESNT FUNCTION BECAUSE CHECKOUT IS WRONG IN MY IMPLEMENTATION!
+
+printf "WARNING: DOESN'T FUNCTION DUE TO NON FULLY IMPLEMENTED CHECKOUT\n\n"
+
+assert_good_exec "./tigger-init"
+touch z
+assert_good_exec "./tigger-add z"
+assert_good_exec "./tigger-commit -m commitmessage"
+before_status=$(./tigger-status)
+assert_good_exec "./tigger-branch new"
+assert_good_exec "./tigger-checkout new"
+touch a
+touch b
+assert_good_exec "./tigger-add a b"
+assert_good_exec "./tigger-commit -m commitmessage"
+touch c
+touch d
+assert_good_exec "./tigger-add c d"
+assert_good_exec "./tigger-commit -m commitmessage"
+assert_good_exec "./tigger-checkout master"
+assert_good_exec "./tigger-branch -d new"
+
+printf "testing tigger status is the same as before the branch was created\n"
+if [ "$(./tigger-status)" != "$before_status" ]; then
+ printf "tigger-status was not empty in an empty dir\n" >&2
+ exit 1
+fi
+
+# Test teardown/cleanup
+printf "\ntest completed successfully\n"
+rm -rf ../"$tmp_dir"
+exit 0
diff --git a/comp2041/tigger/test09.sh b/comp2041/tigger/test09.sh
new file mode 100755
index 0000000..6931de6
--- /dev/null
+++ b/comp2041/tigger/test09.sh
@@ -0,0 +1,61 @@
+#!/bin/dash
+
+# Test startup, create a temporary directory and cd into it with files.
+tmp_dir=$(mktemp --directory --quiet --tmpdir=./)
+if [ "$(find tigger-* | wc -l)" -le 0 ]; then
+ printf "error: no tigger commands found, cannot run tests\n" >&2
+ exit 1
+fi
+cp tigger-* "$tmp_dir"/
+cd "$tmp_dir" || exit
+
+# Functions to test if the return value was 0 or non-zero.
+assert_good_exec () {
+ printf "%s: " "$1"
+ if $1; then
+ return
+ fi
+ printf "test failed, previous command expected zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+# Functions to test if the return value was 0 or non-zero.
+assert_bad_exec () {
+ printf "%s: " "$1"
+ if ! $1; then
+ return
+ fi
+ printf "test failed, previous command expected non-zero output code\n" >&2
+ rm -rf ../"$tmp_dir"
+ exit 1
+}
+printf "starting tests: output and order will be printed\n\n"
+# End of test startup
+
+# The purpose of this test is to ensure that tigger-rm'ing a file can still be
+# retrieved later (provided it was in a different commit).
+assert_good_exec "./tigger-init"
+printf "contents" > a
+assert_good_exec "./tigger-add a"
+assert_good_exec "./tigger-commit -m commitmessage"
+assert_good_exec "./tigger-rm a"
+assert_good_exec "./tigger-commit -m commitmessage"
+
+# Ensure it was deleted.
+if [ -e a ]; then
+ printf "error: file was not deleted after git rm\n" >&2
+ exit 1
+fi
+
+# Ensure it can be later retrieved.
+assert_good_exec "./tigger-show 0:a"
+if [ "$(./tigger-show 0:a)" = "" ]; then
+ printf "error: file was not retrievable after git rm\n" >&2
+ exit 1
+fi
+
+
+# Test teardown/cleanup
+printf "\ntest completed successfully\n"
+rm -rf ../"$tmp_dir"
+exit 0
diff --git a/comp2041/tigger/tigger-add b/comp2041/tigger/tigger-add
new file mode 100755
index 0000000..4ec51bb
--- /dev/null
+++ b/comp2041/tigger/tigger-add
@@ -0,0 +1,42 @@
+#!/bin/dash
+
+# Test if we have a valid repo before doing anything.
+if ! [ -d ".tigger/" ]; then
+ printf "tigger-add: error: tigger repository directory .tigger not found\n"
+ exit 1
+fi
+
+# Get current branch, then current commit (default 0).
+branch=$(cat .tigger/branch.tig)
+mkdir --parents ".tigger/$branch/"
+
+
+# Grabs the latest commit
+commit=$(find .tigger/"$branch"/ -maxdepth 1 -type d | grep -o -E "[\/][^\/]*$" | tr -d "/" | sort -n -r | head -n 1)
+if [ "$commit" = "" ]; then
+ commit="0"
+ mkdir --parents ".tigger/$branch/$commit/"
+fi
+
+
+staged_dir=".tigger/$branch/$commit/staged/"
+mkdir --parents "$staged_dir"
+for filename in "$@"; do
+
+ if ! [ -f "$filename" ]; then
+
+ # If it doesn't exist in the dir, but does exist in staged, delete staged.
+ if [ -f .tigger/"$branch"/"$commit"/staged/"$filename" ]; then
+ rm --force .tigger/"$branch"/"$commit"/staged/"$filename"
+ continue
+ fi
+
+ printf "tigger-add: error: can not open '%s'\n" "$filename"
+ exit 1
+ fi
+
+
+ cp "$filename" "$staged_dir"
+done
+
+exit 0
diff --git a/comp2041/tigger/tigger-branch b/comp2041/tigger/tigger-branch
new file mode 100755
index 0000000..f4a5472
--- /dev/null
+++ b/comp2041/tigger/tigger-branch
@@ -0,0 +1,68 @@
+#!/bin/dash
+
+# Test if we have a valid repo before doing anything.
+if ! [ -d ".tigger/" ]; then
+ printf "tigger-add: error: tigger repository directory .tigger not found\n" >&2
+ exit 1
+fi
+
+
+# For some reason we have to make it so that we can only run this if we're after
+# the first commit, even though this isn't a part of git and our program can do
+# it without issues.
+if ! [ -e ".tigger/branch.unlock" ]; then
+ printf "tigger-branch: error: this command can not be run until after the first commit\n" >&2
+ exit 1
+fi
+
+# Case where we give it no arguments -> list the branches.
+if [ "$(echo "$@" | tr -d " \n")" = "" ]; then
+ printf "%s\n" "$(find .tigger/* -maxdepth 0 -type d | sort | grep -o -E "[\/][^\/]*$" | tr -d "/")"
+ exit 0
+fi
+
+target_branch=$(echo "$@" | sed "s|-d||g" | tr -d " ");
+if [ "$target_branch" = "" ]; then
+ # no branch provided
+ printf "tigger-branch: error: no branch provided\n" >&2
+ exit 1
+fi
+
+# Delete a branch, again taking care that we remove the target branch string
+# before testing if it contains -d.
+if [ "$(echo "$@" | sed "s|$target_branch||g" | grep -o -E "\-d" | tr -d " ")" != "" ]; then
+
+ if [ "$target_branch" = master ]; then
+ printf "tigger-branch: error: can not delete branch '%s'\n" "$target_branch" >&2
+ exit 1
+ fi
+
+ if ! [ -d .tigger/"$target_branch"/ ]; then
+ printf "tigger-branch: error: branch '%s' doesn't exist\n" "$target_branch" >&2
+ exit 1
+ fi
+
+ rm -r .tigger/"$target_branch"
+ printf "Deleted branch '%s'\n" "$target_branch"
+ exit 0
+fi
+
+# Adding branch logic should test if a branch exists before adding it.
+if [ -d .tigger/"$target_branch"/ ]; then
+ printf "tigger-branch: error: branch '%s' already exists\n" "$target_branch" >&2
+ exit 1
+fi
+
+# We have to copy our current commit to the target branch.
+current_branch=$(cat .tigger/branch.tig)
+current_commit=$(find .tigger/"$current_branch"/ -maxdepth 1 -type d | grep -o -E "[\/][^\/]*$" | tr -d "/" | sort -n -r | head -n 1)
+if [ "$current_commit" = "" ]; then
+ current_commit="0"
+ mkdir --parents ".tigger/$current_branch/$current_commit/"
+fi
+
+mkdir --parents .tigger/"$target_branch"/
+mkdir --parents .tigger/"$target_branch"/0/staged
+cp -r .tigger/"$current_branch"/"$current_commit"/commit .tigger/"$target_branch"/0/
+
+exit 0
diff --git a/comp2041/tigger/tigger-checkout b/comp2041/tigger/tigger-checkout
new file mode 100755
index 0000000..a1a5a21
--- /dev/null
+++ b/comp2041/tigger/tigger-checkout
@@ -0,0 +1,84 @@
+#!/bin/dash
+
+# Test if we have a valid repo before doing anything.
+if ! [ -d ".tigger/" ]; then
+ printf "tigger-add: error: tigger repository directory .tigger not found\n" >&2
+ exit 1
+fi
+
+# Case where we give it no arguments -> early out (without error?)
+target_branch=$(echo "$@" | tr -d " ");
+if [ "$target_branch" = "" ]; then
+ # no branch provided, idk if we should print anything so just early out
+ exit 1
+fi
+
+if ! [ -d .tigger/"$target_branch" ]; then
+ printf "tigger-checkout: error: unknown branch '%s'\n" "$target_branch" >&2
+ exit 1
+fi
+
+
+current_branch=$(cat .tigger/branch.tig)
+current_commit=$(find .tigger/"$current_branch"/ -maxdepth 1 -type d | grep -o -E "[\/][^\/]*$" | tr -d "/" | sort -n -r | head -n 1)
+if [ "$current_commit" = "" ]; then
+ current_commit="0"
+ mkdir --parents ".tigger/$current_branch/$current_commit/"
+fi
+
+target_commit=$(find .tigger/"$target_branch"/ -maxdepth 1 -type d | grep -o -E "[\/][^\/]*$" | tr -d "/" | sort -n -r | head -n 1)
+if [ "$target_commit" = "" ]; then
+ target_commit="0"
+ mkdir --parents ".tigger/$target_branch/$target_commit/"
+fi
+
+for filename in *; do
+ basename=$(basename "$filename")
+
+ # nothing in dir, just exit (should exit)
+ if ! [ -e "$filename" ]; then
+ continue
+ fi
+
+ # if it's in staging, do not remove
+ #if [ -e .tigger/"$current_branch"/"$current_commit"/staged/"$basename" ]; then
+ # continue
+ #fi
+
+ # if it doesn't exist in the target commit, remove
+ if ! [ -e .tigger/"$target_branch"/"$target_commit"/commit/"$basename" ]; then
+ rm "$basename"
+ continue
+ fi
+done
+
+for filename in .tigger/"$target_branch"/"$target_commit"/commit/*; do
+ basename=$(basename "$filename")
+
+ # nothing in dir, just exit (should exit)
+ if ! [ -e "$filename" ]; then
+ continue
+ fi
+
+ # if it's in the current dir, do nothing TODO fix if different
+ if [ -e "$basename" ]; then
+
+ # If it's the same AND not different to current staging, don't copy.
+ if [ "$(diff .tigger/"$target_branch"/"$target_commit"/commit/"$basename" "$basename")" = "" ]; then
+ if [ "$(diff .tigger/"$current_branch"/"$current_commit"/staged/"$basename" "$basename")" != "" ]; then
+ continue
+ fi
+ fi
+
+ fi
+
+ cp "$filename" ./
+done
+
+# Non-compliant tigger vs git behaviour: copy our staged to theirs.
+rm -rf .tigger/"$target_branch"/"$target_commit"/staged
+cp -r .tigger/"$current_branch"/"$current_commit"/staged .tigger/"$target_branch"/"$target_commit"/staged
+
+echo "$target_branch" > .tigger/branch.tig
+printf "Switched to branch '%s'\n" "$target_branch"
+exit 0
diff --git a/comp2041/tigger/tigger-commit b/comp2041/tigger/tigger-commit
new file mode 100755
index 0000000..b2c7573
--- /dev/null
+++ b/comp2041/tigger/tigger-commit
@@ -0,0 +1,88 @@
+#!/bin/dash
+
+# Test if we have a valid repo before doing anything.
+if ! [ -d ".tigger/" ]; then
+ printf "tigger-add: error: tigger repository directory .tigger not found\n" >&2
+ exit 1
+fi
+
+
+# Get current branch, then current commit (default 0).
+branch=$(cat .tigger/branch.tig)
+mkdir --parents ".tigger/$branch/"
+commit=$(find .tigger/"$branch"/ -maxdepth 1 -type d | grep -o -E "[\/][^\/]*$" | tr -d "/" | sort -n -r | head -n 1)
+if [ "$commit" = "" ]; then
+ commit="0"
+ mkdir --parents ".tigger/$branch/$commit/"
+fi
+
+
+commit_files=".tigger/$branch/$commit/"
+file_dir="$commit_files/commit/"
+mkdir --parents "$file_dir"
+mkdir --parents "$commit_files/staged/"
+
+
+# -a causes all files already in the index to have their contents from the
+# current directory added to the index before the commit. We have to remove
+# the message arg before testing the regex here.
+message=$(echo "$@" | grep -o -E "\-m\s?.*" | cut -d' ' -f2- | tr -d \"\')
+if [ "$(echo "$@" | sed "s|$message||g" | grep -o -E "\-a")" != "" ]; then
+ for filename in "$commit_files"/staged/*; do
+ basename=$(basename "$filename")
+ if ! [ -e "$basename" ]; then
+ continue
+ fi
+ cp "$basename" "$commit_files"/staged/
+ done
+fi
+
+
+# If all files in staging are equal to those in commit, we have nothing to commit.
+different=""
+for filename in "$commit_files"/staged/*; do
+ basename=$(basename "$filename")
+
+ EXISTS="$(cmp --silent .tigger/"$branch"/"$commit"/commit/"$basename" "$filename"; echo $?)"
+ if [ "$EXISTS" -ne 0 ]; then
+ different="true"
+ break
+ fi
+done
+for filename in "$commit_files"/commit/*; do
+ basename=$(basename "$filename")
+
+ EXISTS="$(cmp --silent .tigger/"$branch"/"$commit"/staged/"$basename" "$filename"; echo $?)"
+ if [ "$EXISTS" -ne 0 ]; then
+ different="true"
+ break
+ fi
+done
+if [ "$different" = "" ] || [ -z "$(find .tigger/"$branch"/"$commit"/staged/ .tigger/"$branch"/"$commit"/commit/ -maxdepth 1 -type f)" ] ; then
+ printf "nothing to commit\n"
+ exit 0
+fi
+
+
+# Copy all from staged into commit.
+#for filename in "$commit_files"/staged/*; do
+# cp "$filename" "$file_dir"
+#done
+rm -r --force "$commit_files"/commit/
+cp -r "$commit_files"/staged "$commit_files"/commit/
+
+
+# Copy our commit message and setup our new commit (n + 1).
+#printf "%s > .tigger/%s/%s/message.tig\n" "$branch" "$commit"
+printf "%s" "$message" > .tigger/"$branch"/"$commit"/message.tig
+new_commit="$((($(cat .tigger/commit_count.tig) + 1)))"
+mkdir --parents ".tigger/$branch/$new_commit/commit"
+cp -r ".tigger/$branch/$commit/commit" ".tigger/$branch/$new_commit/"
+cp -r ".tigger/$branch/$commit/commit" ".tigger/$branch/$new_commit/staged"
+touch .tigger/branch.unlock
+
+# Increment our commit count value (which isn't used outside of printing).
+echo "$new_commit" > .tigger/commit_count.tig
+
+printf "Committed as commit %s\n" "$((($(printf "%s" "$new_commit") - 1)))"
+exit 0
diff --git a/comp2041/tigger/tigger-init b/comp2041/tigger/tigger-init
new file mode 100755
index 0000000..cee1a77
--- /dev/null
+++ b/comp2041/tigger/tigger-init
@@ -0,0 +1,39 @@
+#!/bin/dash
+
+# We will create a new file in .tigger called "branch.tig" with our current
+# branch. By default this should be master. All future ops should read this.
+
+# Commits will be contained in files starting at 0...n inside our branch dir.
+# Our directory heirarchy might look like:
+# .tigger/
+# branch.tig // current branch
+# commit_count.tig // see the below comment @ echo "0"...
+# master/
+# 0/
+# commit/
+# staged/
+# message.tig // because we have a message, it's a finished commit
+# 1/
+# commit/
+# staged/
+
+if [ -d ".tigger/" ]; then
+ printf "tigger-init: error: .tigger already exists\n" >&2
+ exit 1
+fi
+
+branch="master" # in future should be ~ branch=$(cat ./.tigger/branch.tig)
+mkdir --parents ".tigger/$branch"
+echo "$branch" > ".tigger/branch.tig"
+
+# This is necessary because their implementation of git branch considers a
+# global commit count as opposed to a (more logical) per branch commit count.
+# Whenever we do any commit we have to increment this value (which is only
+# used for printing so that we are compliant with their autotests).
+echo "0" > .tigger/commit_count.tig
+
+mkdir --parents ".tigger/$branch/0/commit"
+mkdir --parents ".tigger/$branch/0/staged"
+
+printf "Initialized empty tigger repository in .tigger\n"
+exit 0
diff --git a/comp2041/tigger/tigger-log b/comp2041/tigger/tigger-log
new file mode 100755
index 0000000..5e269c2
--- /dev/null
+++ b/comp2041/tigger/tigger-log
@@ -0,0 +1,24 @@
+#!/bin/dash
+
+# Test if we have a valid repo before doing anything.
+if ! [ -d ".tigger/" ]; then
+ printf "tigger-add: error: tigger repository directory .tigger not found\n" >&2
+ exit 1
+fi
+
+# Get current branch.
+branch=$(cat .tigger/branch.tig)
+mkdir --parents ".tigger/$branch/"
+
+# Iterate through commits and print their message.
+for commit in $(find .tigger/"$branch"/* -maxdepth 0 -type d | grep -o -E "[\/][^\/]*$" | tr -d "/" | sort -r -n); do
+
+ # No message = we're working on it, so it's not considered here.
+ if ! [ -e .tigger/"$branch"/"$commit"/message.tig ]; then
+ continue
+ fi
+
+ printf "%s %s\n" "$commit" "$(cat .tigger/"$branch"/"$commit"/message.tig)"
+done
+
+exit 0
diff --git a/comp2041/tigger/tigger-rm b/comp2041/tigger/tigger-rm
new file mode 100755
index 0000000..e50420a
--- /dev/null
+++ b/comp2041/tigger/tigger-rm
@@ -0,0 +1,66 @@
+#!/bin/dash
+
+# Test if we have a valid repo before doing anything.
+if ! [ -d ".tigger/" ]; then
+ printf "tigger-add: error: tigger repository directory .tigger not found\n" >&2
+ exit 1
+fi
+
+# Get current branch, then current commit (default 0).
+branch=$(cat .tigger/branch.tig)
+mkdir --parents ".tigger/$branch/"
+commit=$(find .tigger/"$branch"/ -maxdepth 1 -type d | grep -o -E "[\/][^\/]*$" | tr -d "/" | sort -n -r | head -n 1)
+if [ "$commit" = "" ]; then
+ commit="0"
+ mkdir --parents ".tigger/$branch/$commit/"
+fi
+
+commit_files=".tigger/$branch/$commit/"
+mkdir --parents "$commit_files/commit/"
+mkdir --parents "$commit_files/staged/"
+
+for filename in $(echo "$@" | sed "s|--force||g" | sed "s|--cached||g"); do
+
+ basename=$(basename "$filename")
+ if ! [ -e .tigger/"$branch"/"$commit"/staged/"$basename" ]; then
+ printf "tigger-rm: error: '%s' is not in the tigger repository\n" "$basename" >&2
+ exit 1
+ fi
+
+ # Accidental deletion checking, should be non-cached and non-forced.
+ if [ "$(echo "$@" | grep -o -E "\-\-force")" = "" ]; then
+
+ if [ "$(cmp --silent .tigger/"$branch"/"$commit"/staged/"$basename" "$basename"; echo $?)" -ne 0 ]; then
+
+ if [ "$(cmp --silent .tigger/"$branch"/"$commit"/staged/"$basename" .tigger/"$branch"/"$commit"/commit/"$basename"; echo $?)" -ne 0 ]; then
+ printf "tigger-rm: error: '%s' in index is different to both the working file and the repository\n" "$basename" >&2
+ exit 1
+ fi
+
+ if ! [ "$(echo "$@" | grep -o -E "\-\-cached")" != "" ]; then
+ printf "tigger-rm: error: '%s' in the repository is different to the working file\n" "$basename" >&2
+ exit 1
+ fi
+
+ fi
+
+
+ if ! [ "$(echo "$@" | grep -o -E "\-\-cached")" != "" ]; then
+ if [ "$(cmp --silent .tigger/"$branch"/"$commit"/staged/"$basename" .tigger/"$branch"/"$commit"/commit/"$basename"; echo $?)" -ne 0 ]; then
+ printf "tigger-rm: error: '%s' has staged changes in the index\n" "$basename" >&2
+ exit 1
+ fi
+ fi
+ fi
+
+ rm --force .tigger/"$branch"/"$commit"/staged/"$basename"
+ # If the cached option is specified the files are removed only from the index.
+ if [ "$(echo "$@" | grep -o -E "\-\-cached")" != "" ]; then
+ continue
+ fi
+
+ rm --force "$basename"
+
+done
+
+exit 0
diff --git a/comp2041/tigger/tigger-show b/comp2041/tigger/tigger-show
new file mode 100755
index 0000000..05d8405
--- /dev/null
+++ b/comp2041/tigger/tigger-show
@@ -0,0 +1,63 @@
+#!/bin/dash
+
+# Test if we have a valid repo before doing anything.
+if ! [ -d ".tigger/" ]; then
+ printf "tigger-add: error: tigger repository directory .tigger not found\n" >&2
+ exit 1
+fi
+
+if [ "$(printf "%s" "$@")" = "" ]; then
+ printf "tigger-show: error: no arguments provided\n" >&2
+ exit 1
+fi
+
+branch=$(cat .tigger/branch.tig)
+filename=$(echo "$@" | cut -d':' -f2-2)
+commit=$(echo "$@" | cut -d':' -f1-1)
+
+# Lots of error checking here and unnecessarily distinct printing per error.
+
+target="" # LAMBDA CAPTURE
+if [ "$commit" != "" ]; then
+ target=".tigger/$branch/$commit/commit/$filename"
+
+ if ! [ -e ".tigger/$branch/$commit/message.tig" ]; then
+ printf "tigger-show: error: unknown commit '%s'\n" "$commit" >&2
+ exit 1
+ fi
+
+ if ! [ -e "$target" ]; then
+ printf "tigger-show: error: '%s' not found in commit %s\n" "$filename" "$commit" >&2
+ exit 1
+ fi
+
+ printf "%s\n" "$(cat "$target")"
+ exit 0
+
+else
+ commit=$(find .tigger/"$branch"/ -maxdepth 1 -type d | grep -o -E "[\/][^\/]*$" | tr -d "/" | sort -n -r | head -n 1)
+ if [ "$commit" = "" ]; then
+ commit="0"
+ mkdir --parents ".tigger/$branch/$commit/"
+ fi
+
+ if ! [ -e ".tigger/$branch/$commit/staged/$filename" ]; then
+ printf "tigger-show: error: '%s' not found in index\n" "$filename" >&2
+ exit 1
+ fi
+
+ target=".tigger/$branch/$commit/staged/$filename"
+fi
+
+if ! [ -e ".tigger/$branch/$commit/" ]; then
+ printf "tigger-show: error: unknown commit '%s'\n" "$commit" >&2
+ exit 1
+fi
+
+if ! [ -e "$target" ]; then
+ printf "tigger-show: error: '%s' not found in commit %s\n" "$filename" "$commit" >&2
+ exit 1
+fi
+
+printf "%s\n" "$(cat "$target")"
+exit 0
diff --git a/comp2041/tigger/tigger-status b/comp2041/tigger/tigger-status
new file mode 100755
index 0000000..b623432
--- /dev/null
+++ b/comp2041/tigger/tigger-status
@@ -0,0 +1,100 @@
+#!/bin/dash
+
+# Test if we have a valid repo before doing anything.
+if ! [ -d ".tigger/" ]; then
+ printf "tigger-add: error: tigger repository directory .tigger not found\n" >&2
+ exit 1
+fi
+
+# Get current branch + commit as usual.
+branch=$(cat .tigger/branch.tig)
+mkdir --parents ".tigger/$branch/"
+commit=$(find .tigger/"$branch"/ -maxdepth 1 -type d | grep -o -E "[\/][^\/]*$" | tr -d "/" | sort -n -r | head -n 1)
+if [ "$commit" = "" ]; then
+ commit="0"
+ mkdir --parents ".tigger/$branch/$commit/"
+fi
+
+# Grabs regular files from our working dir, staging and commit.
+files=$(find .tigger/"$branch"/"$commit"/commit/ .tigger/"$branch"/"$commit"/staged/ ./ -maxdepth 1 -type f -printf "%P\n" | sort | uniq | grep -v -E "^\." 2>/dev/null)
+for filename in $files; do
+
+ c_dir=".tigger/$branch/$commit/commit/$filename";
+ s_dir=".tigger/$branch/$commit/staged/$filename";
+
+ is_c=0
+ is_s=0
+ is_w=0
+ if [ -e "$c_dir" ]; then
+ is_c=1
+ fi
+ if [ -e "$s_dir" ]; then
+ is_s=1
+ fi
+ if [ -e "$filename" ]; then
+ is_w=1
+ fi
+
+ # There are a LOT of very specific cases here.
+
+ if [ $is_w -eq 0 ] && [ $is_s -eq 0 ] && [ $is_c -eq 1 ]; then
+ printf "%s - deleted\n" "$filename"
+ continue
+ fi
+
+ if [ $is_w -eq 1 ] && [ $is_s -eq 0 ] && [ $is_c -eq 0 ]; then
+ printf "%s - untracked\n" "$filename"
+ continue;
+ fi
+
+ if [ $is_w -eq 1 ] && [ $is_s -eq 1 ] && [ $is_c -eq 1 ]; then
+ c="$(cat "$c_dir")"
+ s="$(cat "$s_dir")"
+ w="$(cat "$filename")"
+
+ if [ "$w" != "$s" ] && [ "$s" != "$c" ] && [ "$c" != "$w" ]; then
+ printf "%s - file changed, different changes staged for commit\n" "$filename"
+ continue
+ fi
+
+ if [ "$w" = "$s" ] && [ "$s" != "$c" ]; then
+ printf "%s - file changed, changes staged for commit\n" "$filename"
+ continue
+ fi
+
+ if [ "$w" != "$s" ] && [ "$s" = "$c" ]; then
+ printf "%s - file changed, changes not staged for commit\n" "$filename"
+ continue
+ fi
+
+ if [ "$w" = "$s" ] && [ "$s" = "$c" ]; then
+ printf "%s - same as repo\n" "$filename"
+ continue
+ fi
+ fi
+
+ if [ $is_w -eq 0 ] && [ $is_s -eq 1 ] && [ $is_c -eq 1 ] && [ "$(diff "$s_dir" "$c_dir")" = "" ]; then
+ printf "%s - file deleted\n" "$filename"
+ continue
+ fi
+
+ if [ $is_w -eq 1 ] && [ $is_s -eq 1 ] && [ $is_c -eq 0 ] && [ "$(diff "$filename" "$s_dir")" = "" ]; then
+ printf "%s - added to index\n" "$filename"
+ continue
+ fi
+
+ if [ $is_w -eq 1 ] && [ $is_s -eq 1 ] && [ $is_c -eq 0 ] && [ "$(diff "$filename" "$s_dir")" != "" ]; then
+ printf "%s - added to index, file changed\n" "$filename"
+ continue;
+ fi
+
+ if [ $is_w -eq 0 ] && [ $is_s -eq 1 ] && [ $is_c -eq 0 ]; then
+ printf "%s - added to index, file deleted\n" "$filename"
+ continue;
+ fi
+
+ printf "%s - untracked\n" "$filename"
+
+done
+
+exit 0