Skip to content

Basic File Version Control System

Abstract

Build a Basic File Version Control System that tracks changes to files, allows committing changes with messages, and reverting to previous versions. This project demonstrates file hashing, serialization, and command-line interaction in Python.

Prerequisites

  • Python 3.6 or above
  • Text Editor or IDE
  • Basic understanding of Python syntax
  • Knowledge of file handling and hashing
  • Familiarity with serialization (pickle)

Getting Started

Creating a new project

  1. Create a new project folder and name it basic_file_version_control_systembasic_file_version_control_system.
  2. Create a new file inside the folder and name it basic_file_version_control_system.pybasic_file_version_control_system.py.
  3. Open the project folder in your favorite text editor or IDE.
  4. Copy the code below and paste it into the basic_file_version_control_system.pybasic_file_version_control_system.py file.

Write the code

⚙️ basic_file_version_control_system.py
basic_file_version_control_system.py
"""
Basic File Version Control System
 
A Python-based version control system with the following features:
- Track changes to files
- Commit changes with messages
- Revert to previous versions
"""
 
import os
import hashlib
import pickle
from datetime import datetime
 
 
class BasicFileVersionControl:
    def __init__(self, repo_path):
        self.repo_path = repo_path
        self.version_file = os.path.join(repo_path, ".versions.pkl")
        self.versions = {}
 
        if not os.path.exists(repo_path):
            os.makedirs(repo_path)
 
        if os.path.exists(self.version_file):
            with open(self.version_file, "rb") as f:
                self.versions = pickle.load(f)
 
    def hash_file(self, file_path):
        """Generate a hash for the given file."""
        hasher = hashlib.sha256()
        with open(file_path, "rb") as f:
            while chunk := f.read(8192):
                hasher.update(chunk)
        return hasher.hexdigest()
 
    def commit(self, file_path, message):
        """Commit changes to the file."""
        if not os.path.exists(file_path):
            print("File does not exist.")
            return
 
        file_hash = self.hash_file(file_path)
        file_name = os.path.basename(file_path)
 
        if file_name in self.versions and self.versions[file_name]["hash"] == file_hash:
            print("No changes detected.")
            return
 
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        version_data = {
            "hash": file_hash,
            "timestamp": timestamp,
            "message": message,
            "content": open(file_path, "rb").read(),
        }
 
        self.versions[file_name] = version_data
 
        with open(self.version_file, "wb") as f:
            pickle.dump(self.versions, f)
 
        print(f"Committed {file_name} at {timestamp} with message: {message}")
 
    def revert(self, file_name):
        """Revert the file to the last committed version."""
        if file_name not in self.versions:
            print("No version history found for this file.")
            return
 
        with open(os.path.join(self.repo_path, file_name), "wb") as f:
            f.write(self.versions[file_name]["content"])
 
        print(f"Reverted {file_name} to the last committed version.")
 
    def log(self, file_name):
        """Display the commit history for the file."""
        if file_name not in self.versions:
            print("No version history found for this file.")
            return
 
        version_data = self.versions[file_name]
        print(f"File: {file_name}")
        print(f"Last Commit: {version_data['timestamp']}")
        print(f"Message: {version_data['message']}")
 
 
def main():
    repo_path = "./repo"
    vcs = BasicFileVersionControl(repo_path)
 
    while True:
        print("\nBasic File Version Control System")
        print("1. Commit File")
        print("2. Revert File")
        print("3. View Log")
        print("4. Exit")
 
        choice = input("Enter your choice: ")
 
        if choice == "1":
            file_path = input("Enter the file path to commit: ")
            message = input("Enter commit message: ")
            vcs.commit(file_path, message)
        elif choice == "2":
            file_name = input("Enter the file name to revert: ")
            vcs.revert(file_name)
        elif choice == "3":
            file_name = input("Enter the file name to view log: ")
            vcs.log(file_name)
        elif choice == "4":
            break
        else:
            print("Invalid choice. Please try again.")
 
 
if __name__ == "__main__":
    main()
 
basic_file_version_control_system.py
"""
Basic File Version Control System
 
A Python-based version control system with the following features:
- Track changes to files
- Commit changes with messages
- Revert to previous versions
"""
 
import os
import hashlib
import pickle
from datetime import datetime
 
 
class BasicFileVersionControl:
    def __init__(self, repo_path):
        self.repo_path = repo_path
        self.version_file = os.path.join(repo_path, ".versions.pkl")
        self.versions = {}
 
        if not os.path.exists(repo_path):
            os.makedirs(repo_path)
 
        if os.path.exists(self.version_file):
            with open(self.version_file, "rb") as f:
                self.versions = pickle.load(f)
 
    def hash_file(self, file_path):
        """Generate a hash for the given file."""
        hasher = hashlib.sha256()
        with open(file_path, "rb") as f:
            while chunk := f.read(8192):
                hasher.update(chunk)
        return hasher.hexdigest()
 
    def commit(self, file_path, message):
        """Commit changes to the file."""
        if not os.path.exists(file_path):
            print("File does not exist.")
            return
 
        file_hash = self.hash_file(file_path)
        file_name = os.path.basename(file_path)
 
        if file_name in self.versions and self.versions[file_name]["hash"] == file_hash:
            print("No changes detected.")
            return
 
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        version_data = {
            "hash": file_hash,
            "timestamp": timestamp,
            "message": message,
            "content": open(file_path, "rb").read(),
        }
 
        self.versions[file_name] = version_data
 
        with open(self.version_file, "wb") as f:
            pickle.dump(self.versions, f)
 
        print(f"Committed {file_name} at {timestamp} with message: {message}")
 
    def revert(self, file_name):
        """Revert the file to the last committed version."""
        if file_name not in self.versions:
            print("No version history found for this file.")
            return
 
        with open(os.path.join(self.repo_path, file_name), "wb") as f:
            f.write(self.versions[file_name]["content"])
 
        print(f"Reverted {file_name} to the last committed version.")
 
    def log(self, file_name):
        """Display the commit history for the file."""
        if file_name not in self.versions:
            print("No version history found for this file.")
            return
 
        version_data = self.versions[file_name]
        print(f"File: {file_name}")
        print(f"Last Commit: {version_data['timestamp']}")
        print(f"Message: {version_data['message']}")
 
 
def main():
    repo_path = "./repo"
    vcs = BasicFileVersionControl(repo_path)
 
    while True:
        print("\nBasic File Version Control System")
        print("1. Commit File")
        print("2. Revert File")
        print("3. View Log")
        print("4. Exit")
 
        choice = input("Enter your choice: ")
 
        if choice == "1":
            file_path = input("Enter the file path to commit: ")
            message = input("Enter commit message: ")
            vcs.commit(file_path, message)
        elif choice == "2":
            file_name = input("Enter the file name to revert: ")
            vcs.revert(file_name)
        elif choice == "3":
            file_name = input("Enter the file name to view log: ")
            vcs.log(file_name)
        elif choice == "4":
            break
        else:
            print("Invalid choice. Please try again.")
 
 
if __name__ == "__main__":
    main()
 

Key Features

  • Track changes to files
  • Commit changes with messages
  • Revert to previous versions
  • Command-line interface for user interaction

Explanation

File Hashing

The app uses SHA-256 to hash file contents for change detection:

basic_file_version_control_system.py
hasher = hashlib.sha256()
with open(file_path, "rb") as f:
    hasher.update(f.read())
basic_file_version_control_system.py
hasher = hashlib.sha256()
with open(file_path, "rb") as f:
    hasher.update(f.read())

Committing Changes

Changes are committed with a message and stored in a serialized file:

basic_file_version_control_system.py
version_data = {
    "hash": file_hash,
    "timestamp": timestamp,
    "message": message,
    "content": open(file_path, "rb").read(),
}
self.versions[file_name] = version_data
with open(self.version_file, "wb") as f:
    pickle.dump(self.versions, f)
basic_file_version_control_system.py
version_data = {
    "hash": file_hash,
    "timestamp": timestamp,
    "message": message,
    "content": open(file_path, "rb").read(),
}
self.versions[file_name] = version_data
with open(self.version_file, "wb") as f:
    pickle.dump(self.versions, f)

Reverting and Logging

Users can revert files to previous versions and view commit logs:

basic_file_version_control_system.py
def revert(self, file_name):
    # Restore file from self.versions
 
def log(self, file_name):
    # Print commit history
basic_file_version_control_system.py
def revert(self, file_name):
    # Restore file from self.versions
 
def log(self, file_name):
    # Print commit history

Running the Application

  1. Save the file.
  2. Run the application:
python basic_file_version_control_system.py
python basic_file_version_control_system.py

Conclusion

This Basic File Version Control System project is a great way to learn about file tracking and serialization in Python. You can extend it by adding branching, remote repositories, or a GUI interface.

Was this page helpful?

Let us know how we did