TIG is a powerful yet lightweight implementation of a version control system, offering advanced operations for managing file states across branches. Unlike simpler tools, TIG provides robust support for branching, merging, and resetting, making it a practical alternative for small projects or as an educational tool to understand version control internals.
- Branch management with independent state tracking.
- Support for staging, committing, and logging changes.
- Ability to reset changes softly or revert to a specific state.
- Merge functionality for combining branches efficiently.
The repository maintains a structured directory under .tig:
graph LR
A((repo)) ----> Z((.tig))
A --> file1.txt
A --> file2.txt
A -->file3.txt
Z--> B((main))
B --> C((manifests))
B --> D((backup))
C --> E(123456789.csv)
C --> F(234567890.csv)
D --> G(6b95e5118901839583bdb37374)
D --> H(8a9faaa11f8ea34f79f4cb575c)
D --> I(8a9faaa11f8ea34f79f4cb575c)
B --> 1(committed_files.txt)
B --> 2(staged_files.txt)
B --> 3(modified_files.txt)
B --> 4(untracked_files.txt)
Z--> J((feature))
J --> K((manifests))
J --> L((backup))
K --> 5(...)
L --> ...
J --> committed_files.txt
J --> staged_files.txt
J --> modified_files.txt
J --> untracked_files.txt
-
Branch Directories (
main,test_branch):
Each branch has its directory to maintain its state:manifests/: Logs snapshots of tracked files at specific points.backup/: Stores unique hashes for file versions, enabling restoration.committed_files.txt: Tracks the files included in the last commit.staged_files.txt: Contains files staged for the next commit.modified_files.txt: Lists modified but not staged files.untracked_files.txt: Tracks files not under version control.
-
.tig/HEAD:
Stores the name of the current branch and the hash at which the HEAD is pointing.
| Operation Name | Command | Purpose |
|---|---|---|
| Initialize a Repository | 'tig init <repo>' |
Creates a new TIG repository with the required .tig structure. |
| Add Files to Staging | 'tig add <filename>''tig add .' |
Stages the specified file(s) for the next commit. Use 'tig add .' to stage all modified files. |
| Unstage Files | 'tig unstage <filename>' |
Removes a file from the staging area and moves it back to the modified state. |
| Check Repository Status | 'tig status' |
Displays the current state of the repository, including staged, modified, and untracked files. |
| Commit Changes | 'tig commit <message>' |
Commits the staged files with a descriptive message. |
| View Commit History | 'tig log' |
Displays the commit history with hashes and messages. |
| Checkout a Specific Commit | 'tig checkout <hash>' |
Switches the repository state to the specified commit hash. |
| Switch branches | 'tig switch <name_of_branch>' |
Switches between different branches and creates one if it doesn't yet exist. |
| Show Active Branches | 'tig branch' |
Displays the list of all branches and highlights the current one. |
| Merge Branches | 'tig merge <branch>' |
Merges the specified branch into the current branch. |
| File differences | 'tig diff <filename>' |
Displays differences between the working copy of the specified file and the last committed version. |
| Reset Changes | 'tig reset --soft''tig reset --hard <hash>' |
'--soft': Unstages the last commit but retains changes in the working directory.'--hard <hash>': Reverts the repository to a specific commit hash, discarding any subsequent changes. |
The init command sets up a new TIG repository by creating the required .tig directory structure inside the specified <repo> directory.
-
Creates the
.tigDirectory:
This hidden directory acts as the backbone of the repository, containing all metadata and branch-related data. -
Initializes the
HEADFile:
TheHEADfile is created to store:- The name of the current branch (
mainby default). - The commit hash at which the
HEADcurrently points (initially empty until the first commit is made).
- The name of the current branch (
-
Creates the Default Branch Directory (
main):
Inside.tig, amaindirectory is created with the following subdirectories and files:manifests/: To store CSV snapshots of tracked files.backup/: To store hashed versions of file content.- State Files:
committed_files.txtstaged_files.txtmodified_files.txtuntracked_files.txt
Example:
tig init my_repo cd my_repo ls -a # Output: .tig
The add command stages files for the next commit. Staged files are stored in the staged_files.txt file within the current branch directory.
-
Verify the File Exists:
TIG first ensures that the specified file is present in the repository directory. If the file doesn’t exist, an appropriate error is raised. -
Hash the File Content:
- A unique hash is generated using the
hashliblibrary to represent the current state of the file's content. - If the file has been modified since the last commit, the hash will differ from the previously stored version, triggering an update in the staging area.
- A unique hash is generated using the
-
State Check and Handling:
The system categorizes the file into different states and takes appropriate actions:- File Already Staged but Modified:
If the file is in the staging area but its content has changed (i.e., the hashes differ), the staged file is overwritten with the new version. - File Not Staged Yet:
- If the file is modified or untracked, it is removed from its current state list.
- The file is then added to the staging area in the format
{filename},{hash}.
- File Already Staged but Modified:
-
Update
staged_files.txt:
The file is logged instaged_files.txtto mark it as staged for the next commit. This ensures the file is ready to be included in the upcoming snapshot. -
Handle
tig add .:
When thetig add .command is executed, TIG stages all files that are currently in the "modified" or "untracked" states within the directory. This simplifies staging multiple changes at once.tig add myfile.txt cat .tig/main/staged_files.txt # Output: # myfile.txt,8a9faaa11f8ea34f79f4cb575cdba3901330a4fe37ded0d8323104797c3c5d08
The _unstage function removes a specified file from the staging area and re-categorizes it as modified, allowing the user to continue editing before staging it again.
-
Retrieve Repository Metadata:
- The function identifies the repository root directory using
find_repo_root()and retrieves metadata paths usingget_repo_info(). - Key metadata includes:
- Path to the
staged_files.txtfile. - Path to the
modified_files.txtfile.
- Path to the
- The function identifies the repository root directory using
-
Normalize and Validate the File Path:
- Converts the provided file path to an absolute path and resolves it to a relative path based on the repository root.
- Checks if the file is present in the
staged_files.txt. If the file isn’t staged, an error is printed, and the function exits.
-
Remove the File from the Staging Area:
- If the file is staged, its entry is removed from the
staged_files.txt.
- If the file is staged, its entry is removed from the
-
Reclassify the File as Modified:
- The function retrieves the file's hash from the staged area and adds it to the
modified_files.txt.
- The function retrieves the file's hash from the staged area and adds it to the
-
Write Updates to Metadata:
- Updates the
staged_files.txtto remove the file. - Updates the
modified_files.txtto include the file.
- Updates the
You have staged file1.txt but decide to unstage it.
`tig unstage file1.txt` -
Before Execution:
-
staged_files.txtcontains:`file1.txt,abc123` -
modified_files.txtis empty:
-
-
Execution:
_unstageremovesfile1.txtfromstaged_files.txtand adds it tomodified_files.txtwith its hash.
-
After Execution:
-
staged_files.txtis now empty: -
modified_files.txtcontains:`file1.txt,abc123`
-
The _status function in the tig.py script provides a detailed overview of the repository's current state by categorizing files into different statuses like staged, modified, and untracked.
-
Initialize Repository Paths:
The function retrieves the repository's root directory and metadata files, such asstaged_files.txt,modified_files.txt,untracked_files.txt, andcommitted_files.txt. -
Parse Existing Metadata:
The function reads and processes the current state of files in the repository:- Staged Files: Files marked for the next commit.
- Committed Files: Files included in the last commit.
- Modified Files: Files that have been changed since the last commit or staging.
- Untracked Files: Files in the repository that are not yet tracked.
-
Hash Current Files:
All files in the repository are hashed to compare their current state with previously stored hashes. -
Update File States:
- Staged Files:
The function checks if the content of staged files has changed. If so, they are moved to the "modified" state. - Committed Files:
Similar to staged files, committed files are checked for changes. Any differences cause the file to move to the "modified" state. - Modified Files:
Updated with newly identified changes from staged or committed files. - Untracked Files:
Identifies new files not tracked in any other state. Files now tracked are removed from the untracked list.
- Staged Files:
-
Write Updates to Metadata:
After recalculating file states, the function writes the updated lists to the respective metadata files. -
Display Repository Status:
A summary of the repository's state is printed, categorizing files into:- Changes to Be Committed: Files in the staging area.
- Changes Not Staged for Commit: Modified files not yet staged.
- Untracked Files: Files that are untracked and not yet staged.
On branch main
Changes to be committed:
(use "tig restore --staged <file>..." to unstage)
new file: file1.txt
Changes not staged for commit:
(use "tig add <file>..." to update what will be committed)
(use "tig restore <file>..." to discard changes in working directory)
modified: file2.txtThe commit command creates a snapshot of the staged files, associating them with a unique commit hash and a descriptive message. The commit is logged, and the repository's HEAD is updated.
-
Generate a Commit Hash:
Create a unique commit hash based on the current timestamp. This method guarantees that each commit has a distinct identifier while simplifying the implementation. -
Update the
manifests/Directory:- A new CSV file named after the commit hash is generated in the
manifests/directory. - This
commit.csvfile contains:- Filenames
- Corresponding content hashes
- Commit message
- A new CSV file named after the commit hash is generated in the
-
Update
backup/Directory:- Based on the files listed in the
commit.csv, only the files that are not already referenced in the backup are copied. - Files in the backup are organized by their hash values.
- For new hashes, a copy of the file is created and named with the appropriate hash. Existing hashes are ignored.
- Based on the files listed in the
-
Move Staged Files to Committed State:
- Transfer files from
staged_files.txttocommitted_files.txt. - Clear
staged_files.txtto prepare for future changes.
- Transfer files from
-
Update the
HEADFile:- Set the
HEADto point to the latest commit hash.
- Set the
- You have staged two files:
file1.txtandfile2.txt. - You want to commit these changes with the message "Initial commit".
tig commit "Initial commit"- staged_files.txt:
file1.txt,abc123
file2.txt,def456- committed_files.txt:
Empty. - .tig/main/manifests/:
Empty. - .tig/main/backup/:
Empty. - HEAD: Points to main, no commit hash yet.
- .tig/main/manifests/1690389123.csv now tracks the commit.
- .tig/main/backup/ contains
abc123
def456- staged_files.txt:
Empty. - committed_files.txt:
file1.txt,abc123
file2.txt,def456The _log function displays the commit history of the current branch, providing detailed information about each commit.
-
Retrieve Repository Metadata:
The function fetches essential metadata, including the repository's root directory, the current branch'shead, and the path to themanifestsdirectory where commit data is stored. -
Check for Commits:
The function checks whether themanifestsdirectory contains any commits. If no commits are found, it displays a message stating that the commit history is empty. -
Sort and List Commits:
- All commit files (stored as
.csvfiles) are retrieved and sorted by their timestamps. - Each commit is processed, and its details are read from the corresponding manifest file.
- If a commit is the current
HEAD, it is highlighted as the latest commit.
- All commit files (stored as
-
Parse Commit Details:
For each commit, the function extracts the following (stored insidecommit.csv):- Timestamp: Converted to a human-readable date format using
datetime.fromtimestamp. - Commit Message: The message associated with the commit.
- Timestamp: Converted to a human-readable date format using
-
Display Commit History:
- The function prints the commit hash, timestamp, and commit message.
- The current commit (HEAD) is distinctly marked.
>> python ..\tig.py log
Commit history for branch main:
commit 1732236219
Date: 2024-11-22 01:43:39
Message: Commit 1
commit 1732261196
Date: 2024-11-22 08:39:56
Message: Merged branch feature into main
commit 1732268042 (HEAD -> main)
Date: 2024-11-22 10:34:02
Message: Modified file2.txtThe _checkout function allows the user to switch the repository's working directory to match the state of a specific commit. It restores files to their state at the target commit while ensuring consistency with the repository's metadata.
-
Locate Repository Metadata:
- The function determines the repository's root directory using
find_repo_root()and retrieves metadata usingget_repo_info(). - Key metadata includes:
- Current branch (
head). - Latest commit hash (
head_hash). - Paths to the
manifests,backup,staged_files.txt, andmodified_files.txt.
- Current branch (
- The function determines the repository's root directory using
-
Verify Clean Working Directory:
- The function checks for any files in the staging area (
staged_files.txt) or modified files (modified_files.txt). - If there are uncommitted changes, the user is prompted to commit or discard them before proceeding.
- The function checks for any files in the staging area (
-
Locate the Target Commit:
- It scans the
manifestsdirectory for a.csvfile matching the provided commit hash. - If the target commit does not exist, an error is raised.
- It scans the
-
Restore Files from the Commit:
- The function parses the target commit's manifest file to identify all files and their associated hashes.
- For each file:
- It retrieves the corresponding backup file using the hash.
- The file is restored to its original location in the repository, creating any necessary directories.
- Existing files not in the target commit are deleted from the working directory.
-
Clear Staged and Modified Files:
- The function clears the
staged_files.txtandmodified_files.txtto reflect the clean state of the working directory.
- The function clears the
-
Update Metadata:
- The
committed_files.txtis updated with the files and hashes from the target commit. - The
HEADis updated to point to the specified commit.
- The
- The repository has a commit
1234567890containing two files:file1.txtandfile2.txt. - You want to restore the repository to this commit.
python ..\tig.py checkout 1234567890- Before Execution:
- Working directory contains
file1.txt,file2.txt, andfile3.txt. file3.txtis untracked.
- Working directory contains
- Execution:
_checkoutidentifies1234567890.csvin themanifestsdirectory.file1.txtandfile2.txtare restored from the backup directory to match the commit's state.file3.txtis removed as it is not part of the commit.
- After Execution:
- The working directory contains
file1.txtandfile2.txtin their committed state. - Metadata files (
staged_files.txt,modified_files.txt, andcommitted_files.txt) are updated. HEADpoints to1234567890.
- The working directory contains
The _switch function allows the user to switch to an existing branch or create a new one if it doesn't exist. It interacts with the repository's .tig structure to manage branch metadata and commit histories.
-
Locate Repository Metadata:
- The function begins by locating the repository's root directory using
find_repo_root()and retrieving metadata about the repository viaget_repo_info(). - Key metadata includes:
.tigdirectory path (dot_tig).- Current branch (
head). - Current branch's latest commit hash (
head_hash). - Path to the
manifestsdirectory containing commit metadata (manifests).
- The function begins by locating the repository's root directory using
-
Check if the Branch Exists:
- The function checks if the target branch exists in the
.tigdirectory:- Branch Exists: If the branch exists and is already the current branch (
head), a message is printed, and the function exits. - Branch Does Not Exist: If the branch does not exist, the function creates a new branch based on the current branch's state.
- Branch Exists: If the branch exists and is already the current branch (
- The function checks if the target branch exists in the
-
Create a New Branch (if needed):
- The function iterates over the current branch's
manifeststo find commit metadata files that match thehead_hash. Then it reads the associated manifest file to retrieve the list of files and their hashes. - Each file's information is parsed and stored in the
hashed_fileslist. - Invalid lines in the manifest are skipped with a warning.
Creating the Branch:
- The
create_branchfunction is called, passing:- The repository path.
- The parsed
hashed_fileslist. - The target branch name.
- The current branch name (
head).
- The function iterates over the current branch's
-
Update HEAD:
- After ensuring the branch exists (or creating it), the function updates the repository's
HEADto point to the new branch. - It identifies the latest commit in the new branch's
manifestsdirectory:- The commit hash is extracted from the newest
.csvfile. - The
change_headfunction updates the repository metadata to reflect the new branch and its latest commit.
- The commit hash is extracted from the newest
- After ensuring the branch exists (or creating it), the function updates the repository's
- The repository has a branch
mainwith one commit. - You want to create and switch to a new branch named
feature.
`tig switch feature` - Before Execution:
.tig/main/manifestscontains a single file:1234567890.csv.HEADpoints tomainand commit1234567890.
- Execution:
_switchchecks iffeatureexists (it doesn't)._switchreads1234567890.csv, parses the file list, and createsfeature.1234567890.csvis copied tofeature/manifests- all files specified in
1234567890.csvare copied tofeature/backup HEADis updated to point tofeatureand commit1234567890.
- After Execution:
.tig/feature/manifestsnow contains1234567890.csv.- the files in
1234567890.csvare inserted incommited_files.txt, to have a branch up to date HEADpoints tofeature.
The _branch function lists all the branches in the repository and highlights the current branch. This is useful for understanding the structure of the repository and identifying which branch is currently active.
-
Retrieve Metadata:
- The function uses
find_repo_root()to identify the root directory of the repository, ensuring the operation is performed in the correct context. - The repository's metadata is loaded using
get_repo_info(), which provides:- Path to the
.tigdirectory. - Name of the current branch (
head).
- Path to the
- The function uses
-
List All Branches:
- The function lists all subdirectories in the
.tigdirectory, which represent the repository's branches.
- The function lists all subdirectories in the
-
Highlight the Current Branch:
- While displaying the branch names, the current branch (as identified by
head) is highlighted using terminal color codes for clarity (\033[92mfor green).
- While displaying the branch names, the current branch (as identified by
The _merge function integrates changes from one branch into the current branch. It ensures the files and commit history from the target branch are incorporated into the current branch, with options for conflict resolution.
-
Locate Repository Metadata:
- The function identifies the repository root using
find_repo_root()and retrieves metadata usingget_repo_info(). - Key metadata includes:
- Current branch (
head). - Latest commit hash of the current branch (
head_hash). manifestsandbackupdirectories for both the current and target branches.
- Current branch (
- The function identifies the repository root using
-
Validate Commit States:
- Checks that the current branch's HEAD is the latest commit in the branch.
- Ensures the target branch exists and has a valid commit history.
-
Compare Manifests:
- Parses the manifest files of the current branch and the target branch's latest commit.
- Iterates over the target branch's files:
- If a file exists in both branches with identical content (same hash), it is added to the new manifest without changes.
- If a file exists with different content, a conflict is detected.
-
Handle Conflicts:
- Default Behavior: Stops the merge and prompts the user to resolve conflicts manually.
- Hard Merge Option (
--hard): Automatically resolves conflicts by prioritizing the target branch's version of conflicting files.
-
Write the Merged Manifest:
- A new manifest is created that consolidates changes from both branches.
- The backup directory is updated with files from the target branch.
-
Update HEAD:
- The
HEADpointer is updated to the new commit created by the merge. - If the
--hardmode is enabled, the working directory is updated to reflect the merged state.
- The
-
Cleanup:
- Deletes the target branch after the merge if specified.
The current branch is main with one commit, and you want to merge feature.
tig merge feature-
Before Execution:
maincontainsfile1.txtandfile2.txt.featurecontainsfile2.txt(modified) andfile3.txt.
-
Execution:
_mergeidentifies changes betweenmainandfeature.- Detects that
file2.txthas conflicting changes. - Prompts the user to resolve the conflict manually or applies the
--hardmode to usefeature's version.
-
After Execution:
- The merged state includes
file1.txt,file2.txt(fromfeature), andfile3.txt. HEADis updated to point to the new commit with the merged state.
- The merged state includes
The diff command compares the current working file against the most recent committed version. It highlights changes, including additions and deletions. Providing a unified diff format
-
Validate File Existance:
- It ensures that the file exists in the working directory and was part of the last commit.
-
Retrieve Last Commit Information:
- It reads the manifest file of the most recent commit, found by the name, to find the last recorded hash for the file to be compared.
-
Compare Current and Committed Versions:
- It computes the hash of the working file with the util function of hash and compares it with the hash on the manifest of the file to compare.
- if hashes differ, the contents of the two versions are read line by line and the diff is generated using the diff library.
- It computes the hash of the working file with the util function of hash and compares it with the hash on the manifest of the file to compare.
-
Display the Diffe:
- Outputs differences in a clear, color-coded format:
- Additions: Green
- Deletions: Red
- Unchanged: No highlight
- Outputs differences in a clear, color-coded format:
- File
example.txtwas committed in the last commit. - You modify the file locally and want to see what changed.
tig diff example.txtexample.txtcontent in working dictionary:
Hello, World!
This is a new line.- Last committed content of
example.txt:
--- example.txt (committed)
+++ example.txt (working)
+ This is a new line.