Command-line-driven version control system from scratch that mirrors Git’s core features, such as file tracking, branching and merging.
Implemented in Java.
- Leveraged the directed acyclic graph structure of Git commits to optimize storage and retrieval of repository data.
- Employed advanced graph theory concepts, such as Lowest Common Ancestor, to efficiently manage and traverse the commit history tree and resolve merge conflicts.
- Ensured low memory usage and minimal storage duplication by using the SHA-1 hash function.
- Clone the repository.
- Add the path to the repository to your
PATH
environment variable:export CLASSPATH=.../out/production/gitfly
- Run
gitfly
in your terminal.
java gitfly.Main <command-name> <args>
init
: Initialize a new Gitfly repository.- Sets up the necessary Gitfly files and directories.
- Creates a new commit with the initial commit message and sets it as the current commit.
- Creates a new branch called
master
. - Sets the current branch to
master
.
add
: Add files to the staging area.- Updates the current index with the new file versions.
- If the added file was in a merge conflict, adds final version to the index and resolves the conflict.
rm
: Remove files from the staging area and from disk.- Removes the file from the current index.
- Removes the file from disk if it's the case.
commit
: Commit the files in the staging area.- Creates a new commit with the given commit message.
- Computes SHA1 for the new commit object from the index tree.
- Sets the new commit as the current commit and sets current branch to point at it.
- Clears the staging area.
checkout
: Checkouts a branch or a commit hash.- Modifies the HEAD pointer.
- Updates the working directory to match its state from the given commit.
log
: Prints the commit history of the current branch.branch
: Creates a new branch with the given name.rm-branch
: Removes the branch with the given name, if it exists.status
: Prints the current status of the repository.- Branch names and current branch.
- Files found in merge conflict.
- Untracked files.
- Files with changes to be committed.
- Files with changes not staged for commit.
merge
: Merges the giver branch into the current branch.- Aborts if the giver branch is the current branch/doesn't exist.
- There are three main cases:
- The giver branch is an ancestor of the current branch => Already up-to-date, no merge is needed.
- The receiver branch is an ancestor of the giver branch => Fast-forwarded, the commit history isn't changed, only the current branch is moved to the giver branch.
- The receiver branch and the giver branch are not related. Merge conflicts can be encountered. Perform eight steps:
-
Create a new commit with the given commit message.
-
Find the LCA of the giver and the receiver branches. This will be the base commit.
-
Get contents of the receiver, giver and base.
-
Generate a diff that specifies the status of each file analyzing the contents of the receiver, giver and base.
- If the file is different in all three commits, then it is a merge conflict.
-
Apply changes to the working directory.
- If there are any files found in conflict, write both versions (receiver and giver) to the working directory (invoke getContentOfConflictedFile function).
-
Write the contents of the working directory to the index.
- If there are any files found in conflict, write three versions in the index: 1 - SHA1 of base contents, 2 - SHA1 of receiver contents, 3 - SHA1 of giver contents
The following step differs fundamentally on whether merge conflicts were found.
-
No conflict:
- Create a commit with the given index.
Conflict:
- When a user adds a conflicted file, the other index entries for the respective filename (which indicate conflict) get removed. Wait until all merge conflicts are resolved.
- User makes a new commit. Gitfly sees that a merge is ongoing (MERGE_HEAD exists) and checks that there are no more conflicted files. A new simple commit is created.
-
Delete MERGE_HEAD and update current branch.
-