== Poor man’s git === wip
The goal here is to create a very minimal form of source code version control. I’d be shocked if any of this is new. If anything similar to this has been done before, hit me up, I’d love to hear about it.
For personal, non-collaborative use, git is a bit overkill. In my opinion.
I can count on two hands the number of times I’ve used branches in one of my personal solo projects. And I can count on zero hands that amount of times that I’ve needed them.
…
Not to try to get too needlessly rigorous about it, the setup is as follows:
| You have files, named things like $F_1$, $R_n$, etc. You also have collections (folders) of files, written like $D = (F_1 | F_2 | F_3)$. |
You have the $+$ and $-$ operators on these files and folders, such that $A = B - C \iff B = A + C$, etc etc. The output of $-$ is always a file, even if the inputs are folders. You can think of $-$ as a file diff, and $+$ as applying that diff.
You have a number of snapshots of the codebase, $C_n, C_{n-1}, … , C_1, C_0$.
| You have the codebase merged differences, $U_n = (C_n | U_{n-1} - C_n)$. The $U$ here, very suggestively, stands of “unidirectional”. The second file there, denoted $R_n = U_{n-1} - C_n$, I’m going to be referring below to as a “reverse patch”. For completion’s sake, $R_0 = \emptyset$, the null file. |
And that’s the main idea!
| If you have a version of the codebase $C_n$, or rather the associated $U_n = (C_n | R_n)$, and want to recover the previous version of the codebase $C_{n-1}$, you just do $C_n + R_n = C_n + (U_{n-1} - C_n) = U_{n-1} = (C_{n-1} | R_{n-1})$. From there you can rewind to the next previous version if you wish. |
As a practical matter, to implement this you need two folders. One is your “code” or “working directory,” the one where you’re doing your normal coding in. For most situations this fill the roll of $C_{n+1}$, the next version of your codebase before it gets committed.
You also need your “backup directory,” which corresponds to $U_n$. This is the one that you keep backed up.
You need two folders because at the very least you need to know what files have changed. This is no different from git, except there it’s stored in your .git folder.
You can implement the version backtracking with a couple GNU tools. $-$ corresponds to diff and $+$ with patch.
A commit, or transitioning $U_n$ to $U_{n+1}$ is pretty simple:
# Start: CODE = C(n+1) , BACK = U(n)
diff -rN -u0 $CODE $BACK > reverse.patch
# reverse.patch = R(n+1)
cat reverse.patch | patch -Rd $BACK
# BACK = U(n) + -R(n+1) = U(n) + C(n+1) - U(n) = C(n+1)
mv reverse.patch $BACK/reverse.patch
# BACK = (C(n+1) | R(n+1)) = U(n+1)
To rewind the backup directory:
# Start: BACK = U(n)
mv $BACK/reverse.patch reverse.patch
# BACK = C(n), reverse.patch = R(n)
cat reverse.patch | patch -d $BACK
# BACK = C(n) + R(n) = C(n) + U(n-1) - C(n) = U(n-1)
rm reverse.patch
There are a couple obvious improvements.
First, being able to go forwards as well as reverse in the codebase history would be nice.
| Instead of the unidirectional $U_n$, we’re going to be working with the bidirectional $B_n = (C_n | R_n | B_{n+1} - U_n)$. That last file, denoted $F_n = B_{n+1} - U_n$, is also called a “forward patch.” Note that you can also write $B_n = (U_n | F_n)$. |
The problem is that $F_n$ can’t be constructed as you’re coding, because you’d need future knowledge of your codebase as you’re committing. So as you’re coding we’re going to adopt the convention that, if $n$ is the version number of the latest source, $F_n = \emptyset$, the null file. Then, when you start scrubbing back and forth through the history the $F_k$’s can be constructed because the final code state is known.
Second is that it’d be nice if the code directory updated when going back and forth through the history, so that you don’t have to switch your editor to a different directory.
The script for commit is the same as before:
# CODE = C(n+1), BACK = B(n) = U(n)
diff -rN -u0 $CODE $BACK > reverse.patch
cat reverse.patch | patch -Rd $BACK
mv reverse.patch $BACK/reverse.patch
To step back a commit
# CODE = C(n) , BACK = B(n)
cat $BACK/reverse.patch | patch -d $CODE
# CODE = C(n) + R(n) = C(n) + U(n-1) - C(n) = U(n-1)
diff -rN -u0 $CODE $BACK > forward.patch
# forward.patch = B(n) - U(n-1) = F(n-1)
cat forward.patch | patch -Rd $BACK
# BACK = B(n) + -F(n-1) = B(n) + U(n-1) - B(n) = U(n-1)
mv forward.patch $BACK/forward.patch
# BACK = B(n-1)
rm $CODE/reverse.patch
# CODE = C(n-1)
To step forward a commit:
# CODE = C(n) , BACK = B(n)
mv $BACK/forward.patch forward.patch
# BACK = U(n)
cp $BACK/reverse.patch $CODE/reverse.patch
# CODE = U(n)
cat forward.patch | patch -d $BACK
# BACK = B(n+1)
cat $BACK/reverse.patch | patch -Rd $CODE
# CODE = U(n) + C(n+1) - U(n) = C(n+1)
rm forward.patch