I'm building a small Git-like version-control system in Java as a learning and portfolio project. I'm calling it NanoGit, and so far it uses content-addressable storage for blobs, trees, and commits. Objects are identified with SHA-256 hashes and compressed with zlib. The index is currently stored in a simple text-based format.
The first version is intended to work offline. I've implemented init, add, commit, status, log, help, branch, and checkout. I'm currently working on diff and merge, with push, pull, and fetch planned for a later version.
For diff generation, I'm considering either LCS or Myers' algorithm. Myers seems to be the more suitable approach, but I don't understand it well enough yet, so I may start with LCS and improve it later.
I've used language models for planning and debugging questions, but not to write the implementation. I'd appreciate feedback on the project direction, code quality, design choices, and what would make it a stronger learning or portfolio project.
4 Answers
A few code-quality observations: ByteArrayOutputStream does not need to be closed, although closing it is harmless. Also, the plain-text index is still an ad hoc data format—you’re defining a structure even if it resembles CSV. Make sure the format is clearly specified and handles escaping, unusual filenames, duplicate entries, and corrupted data.
The tokenizer makes sense for commands such as `commit -m "a message with spaces"`, since it keeps the quoted message together. You should also decide how to handle escaped quotes, unmatched quotes, tabs, repeated whitespace, and filenames containing special characters.
The storage foundation sounds promising, especially the object model and compression. For the diff feature, starting with a straightforward LCS implementation is reasonable: it lets you complete the end-to-end workflow and inspect real output. You can replace it with Myers later once you understand the tradeoffs and have tests to compare both implementations.
For the next steps, focus on strong tests around object hashing, checkout, branches, merges, file deletions, renamed files, binary data, and interrupted operations. Correctness and clear documentation will matter more than choosing the most sophisticated algorithm immediately.
Calling this a reimplementation or clone is more accurate than calling it reverse engineering. Reverse engineering usually implies deducing an undocumented system from its behavior or binaries. You’re studying Git’s documented and observable behavior and building a compatible system, which is still a very worthwhile project.
Porting would also be understandable if the goal were to reproduce Git’s behavior in Java, but reimplementation is probably the clearest description for a resume.
A project built entirely from scratch can teach you a lot, but contributing to an existing Java codebase teaches another important professional skill: reading unfamiliar code, following established conventions, fixing bugs, adding tests, and reviewing changes. Looking at established implementations such as JGit can help you compare design decisions and discover edge cases you may not encounter alone.
You could do both: continue NanoGit as a focused learning project while making a small documentation, testing, or bug-fix contribution to an existing project.
That’s good advice. Being able to understand and work within an existing codebase is just as valuable as creating a new one, so I’ll spend time studying other implementations as well.

The tokenizer is specifically for commit messages. Most commands are simple, but `commit -m "a message with spaces"` needs the quoted text to remain one argument. I’ll also look into handling malformed or escaped input.