From 5dbacefde0b991b7ba5b02412d7995d5a7c408d9 Mon Sep 17 00:00:00 2001 From: Joseph Aquino Date: Thu, 25 Dec 2025 02:43:42 -0500 Subject: [PATCH] Initial commit --- .gitignore | 390 +++++++++++++++++++++++++++++++++++ CMakeLists.txt | 30 +++ include/cat-file.h | 3 + include/clone.h | 3 + include/commands.h | 8 + include/hash-object.h | 3 + include/init.h | 3 + include/ls-tree.h | 3 + include/sha1.hpp | 334 ++++++++++++++++++++++++++++++ include/write-tree.h | 3 + src/commands/cat-file.cpp | 42 ++++ src/commands/clone.cpp | 12 ++ src/commands/hash-object.cpp | 52 +++++ src/commands/init.cpp | 34 +++ src/commands/ls-tree.cpp | 74 +++++++ src/commands/write-tree.cpp | 175 ++++++++++++++++ src/main.cpp | 47 +++++ 17 files changed, 1216 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 include/cat-file.h create mode 100644 include/clone.h create mode 100644 include/commands.h create mode 100644 include/hash-object.h create mode 100644 include/init.h create mode 100644 include/ls-tree.h create mode 100644 include/sha1.hpp create mode 100644 include/write-tree.h create mode 100644 src/commands/cat-file.cpp create mode 100644 src/commands/clone.cpp create mode 100644 src/commands/hash-object.cpp create mode 100644 src/commands/init.cpp create mode 100644 src/commands/ls-tree.cpp create mode 100644 src/commands/write-tree.cpp create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..487523d --- /dev/null +++ b/.gitignore @@ -0,0 +1,390 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +#[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +intermediate-files/ + +.cache/ +.vscode/ +*.json +*.make +Makefile +*.sublime-* + +*.vcxproj + +*.sln + +*.vcxproj.filters + +*.code-workspace + +*.o + +*.d + +*.ninja* + +.idea/ + +cmake*/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..06414ee --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 4.1) +project(basic-vcs) + +include(FetchContent) +FetchContent_Declare(ZStrGitRepo + GIT_REPOSITORY "https://github.com/mateidavid/zstr" # can also be a local filesystem path! + GIT_TAG "master" +) +FetchContent_MakeAvailable(ZStrGitRepo) # defines INTERFACE target 'zstr::zstr' + +set(CMAKE_CXX_STANDARD 23) + +add_executable(basic-vcs src/main.cpp + include/sha1.hpp + include/init.h + include/cat-file.h + include/commands.h + include/ls-tree.h + include/write-tree.h + src/commands/cat-file.cpp + src/commands/init.cpp + src/commands/ls-tree.cpp + src/commands/write-tree.cpp + src/commands/hash-object.cpp + include/hash-object.h + src/commands/clone.cpp + include/clone.h) + +target_link_libraries(basic-vcs zstr::zstr) +target_include_directories(basic-vcs PRIVATE include) \ No newline at end of file diff --git a/include/cat-file.h b/include/cat-file.h new file mode 100644 index 0000000..6fbaebf --- /dev/null +++ b/include/cat-file.h @@ -0,0 +1,3 @@ +#pragma once + +int catFile(const int argc, const char** argv); \ No newline at end of file diff --git a/include/clone.h b/include/clone.h new file mode 100644 index 0000000..9ab3349 --- /dev/null +++ b/include/clone.h @@ -0,0 +1,3 @@ +#pragma once + +int clone(const int argc, const char** argv); \ No newline at end of file diff --git a/include/commands.h b/include/commands.h new file mode 100644 index 0000000..2512e11 --- /dev/null +++ b/include/commands.h @@ -0,0 +1,8 @@ +#pragma once + +#include "cat-file.h" +#include "clone.h" +#include "hash-object.h" +#include "init.h" +#include "ls-tree.h" +#include "write-tree.h" diff --git a/include/hash-object.h b/include/hash-object.h new file mode 100644 index 0000000..c275af1 --- /dev/null +++ b/include/hash-object.h @@ -0,0 +1,3 @@ +#pragma once + +int hashObject(const int argc, const char** argv); \ No newline at end of file diff --git a/include/init.h b/include/init.h new file mode 100644 index 0000000..eeb040e --- /dev/null +++ b/include/init.h @@ -0,0 +1,3 @@ +#pragma once + +int init(); \ No newline at end of file diff --git a/include/ls-tree.h b/include/ls-tree.h new file mode 100644 index 0000000..bf593fa --- /dev/null +++ b/include/ls-tree.h @@ -0,0 +1,3 @@ +#pragma once + +int lsTree(const int argc, const char** argv); \ No newline at end of file diff --git a/include/sha1.hpp b/include/sha1.hpp new file mode 100644 index 0000000..571a1ec --- /dev/null +++ b/include/sha1.hpp @@ -0,0 +1,334 @@ +/* + sha1.hpp - source code of + + ============ + SHA-1 in C++ + ============ + + 100% Public Domain. + + Original C Code + -- Steve Reid + Small changes to fit into bglibs + -- Bruce Guenter + Translation to simpler C++ Code + -- Volker Diels-Grabsch + Safety fixes + -- Eugene Hopkinson + Header-only library + -- Zlatko Michailov +*/ + +#ifndef SHA1_HPP +#define SHA1_HPP + + +#include +#include +#include +#include +#include +#include + + +class SHA1 +{ +public: + SHA1(); + void update(const std::string &s); + void update(std::istream &is); + std::string final(); + static std::string from_file(const std::string &filename); + +private: + uint32_t digest[5]; + std::string buffer; + uint64_t transforms; +}; + + +static const size_t BLOCK_INTS = 16; /* number of 32bit integers per SHA1 block */ +static const size_t BLOCK_BYTES = BLOCK_INTS * 4; + + +inline static void reset(uint32_t digest[], std::string &buffer, uint64_t &transforms) +{ + /* SHA1 initialization constants */ + digest[0] = 0x67452301; + digest[1] = 0xefcdab89; + digest[2] = 0x98badcfe; + digest[3] = 0x10325476; + digest[4] = 0xc3d2e1f0; + + /* Reset counters */ + buffer = ""; + transforms = 0; +} + + +inline static uint32_t rol(const uint32_t value, const size_t bits) +{ + return (value << bits) | (value >> (32 - bits)); +} + + +inline static uint32_t blk(const uint32_t block[BLOCK_INTS], const size_t i) +{ + return rol(block[(i+13)&15] ^ block[(i+8)&15] ^ block[(i+2)&15] ^ block[i], 1); +} + + +/* + * (R0+R1), R2, R3, R4 are the different operations used in SHA1 + */ + +inline static void R0(const uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +{ + z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); + w = rol(w, 30); +} + + +inline static void R1(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +{ + block[i] = blk(block, i); + z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); + w = rol(w, 30); +} + + +inline static void R2(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +{ + block[i] = blk(block, i); + z += (w^x^y) + block[i] + 0x6ed9eba1 + rol(v, 5); + w = rol(w, 30); +} + + +inline static void R3(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +{ + block[i] = blk(block, i); + z += (((w|x)&y)|(w&x)) + block[i] + 0x8f1bbcdc + rol(v, 5); + w = rol(w, 30); +} + + +inline static void R4(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +{ + block[i] = blk(block, i); + z += (w^x^y) + block[i] + 0xca62c1d6 + rol(v, 5); + w = rol(w, 30); +} + + +/* + * Hash a single 512-bit block. This is the core of the algorithm. + */ + +inline static void transform(uint32_t digest[], uint32_t block[BLOCK_INTS], uint64_t &transforms) +{ + /* Copy digest[] to working vars */ + uint32_t a = digest[0]; + uint32_t b = digest[1]; + uint32_t c = digest[2]; + uint32_t d = digest[3]; + uint32_t e = digest[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(block, a, b, c, d, e, 0); + R0(block, e, a, b, c, d, 1); + R0(block, d, e, a, b, c, 2); + R0(block, c, d, e, a, b, 3); + R0(block, b, c, d, e, a, 4); + R0(block, a, b, c, d, e, 5); + R0(block, e, a, b, c, d, 6); + R0(block, d, e, a, b, c, 7); + R0(block, c, d, e, a, b, 8); + R0(block, b, c, d, e, a, 9); + R0(block, a, b, c, d, e, 10); + R0(block, e, a, b, c, d, 11); + R0(block, d, e, a, b, c, 12); + R0(block, c, d, e, a, b, 13); + R0(block, b, c, d, e, a, 14); + R0(block, a, b, c, d, e, 15); + R1(block, e, a, b, c, d, 0); + R1(block, d, e, a, b, c, 1); + R1(block, c, d, e, a, b, 2); + R1(block, b, c, d, e, a, 3); + R2(block, a, b, c, d, e, 4); + R2(block, e, a, b, c, d, 5); + R2(block, d, e, a, b, c, 6); + R2(block, c, d, e, a, b, 7); + R2(block, b, c, d, e, a, 8); + R2(block, a, b, c, d, e, 9); + R2(block, e, a, b, c, d, 10); + R2(block, d, e, a, b, c, 11); + R2(block, c, d, e, a, b, 12); + R2(block, b, c, d, e, a, 13); + R2(block, a, b, c, d, e, 14); + R2(block, e, a, b, c, d, 15); + R2(block, d, e, a, b, c, 0); + R2(block, c, d, e, a, b, 1); + R2(block, b, c, d, e, a, 2); + R2(block, a, b, c, d, e, 3); + R2(block, e, a, b, c, d, 4); + R2(block, d, e, a, b, c, 5); + R2(block, c, d, e, a, b, 6); + R2(block, b, c, d, e, a, 7); + R3(block, a, b, c, d, e, 8); + R3(block, e, a, b, c, d, 9); + R3(block, d, e, a, b, c, 10); + R3(block, c, d, e, a, b, 11); + R3(block, b, c, d, e, a, 12); + R3(block, a, b, c, d, e, 13); + R3(block, e, a, b, c, d, 14); + R3(block, d, e, a, b, c, 15); + R3(block, c, d, e, a, b, 0); + R3(block, b, c, d, e, a, 1); + R3(block, a, b, c, d, e, 2); + R3(block, e, a, b, c, d, 3); + R3(block, d, e, a, b, c, 4); + R3(block, c, d, e, a, b, 5); + R3(block, b, c, d, e, a, 6); + R3(block, a, b, c, d, e, 7); + R3(block, e, a, b, c, d, 8); + R3(block, d, e, a, b, c, 9); + R3(block, c, d, e, a, b, 10); + R3(block, b, c, d, e, a, 11); + R4(block, a, b, c, d, e, 12); + R4(block, e, a, b, c, d, 13); + R4(block, d, e, a, b, c, 14); + R4(block, c, d, e, a, b, 15); + R4(block, b, c, d, e, a, 0); + R4(block, a, b, c, d, e, 1); + R4(block, e, a, b, c, d, 2); + R4(block, d, e, a, b, c, 3); + R4(block, c, d, e, a, b, 4); + R4(block, b, c, d, e, a, 5); + R4(block, a, b, c, d, e, 6); + R4(block, e, a, b, c, d, 7); + R4(block, d, e, a, b, c, 8); + R4(block, c, d, e, a, b, 9); + R4(block, b, c, d, e, a, 10); + R4(block, a, b, c, d, e, 11); + R4(block, e, a, b, c, d, 12); + R4(block, d, e, a, b, c, 13); + R4(block, c, d, e, a, b, 14); + R4(block, b, c, d, e, a, 15); + + /* Add the working vars back into digest[] */ + digest[0] += a; + digest[1] += b; + digest[2] += c; + digest[3] += d; + digest[4] += e; + + /* Count the number of transformations */ + transforms++; +} + + +inline static void buffer_to_block(const std::string &buffer, uint32_t block[BLOCK_INTS]) +{ + /* Convert the std::string (byte buffer) to a uint32_t array (MSB) */ + for (size_t i = 0; i < BLOCK_INTS; i++) + { + block[i] = (buffer[4*i+3] & 0xff) + | (buffer[4*i+2] & 0xff)<<8 + | (buffer[4*i+1] & 0xff)<<16 + | (buffer[4*i+0] & 0xff)<<24; + } +} + + +inline SHA1::SHA1() +{ + reset(digest, buffer, transforms); +} + + +inline void SHA1::update(const std::string &s) +{ + std::istringstream is(s); + update(is); +} + + +inline void SHA1::update(std::istream &is) +{ + while (true) + { + char sbuf[BLOCK_BYTES]; + is.read(sbuf, BLOCK_BYTES - buffer.size()); + buffer.append(sbuf, (std::size_t)is.gcount()); + if (buffer.size() != BLOCK_BYTES) + { + return; + } + uint32_t block[BLOCK_INTS]; + buffer_to_block(buffer, block); + transform(digest, block, transforms); + buffer.clear(); + } +} + + +/* + * Add padding and return the message digest. + */ + +inline std::string SHA1::final() +{ + /* Total number of hashed bits */ + uint64_t total_bits = (transforms*BLOCK_BYTES + buffer.size()) * 8; + + /* Padding */ + buffer += (char)0x80; + size_t orig_size = buffer.size(); + while (buffer.size() < BLOCK_BYTES) + { + buffer += (char)0x00; + } + + uint32_t block[BLOCK_INTS]; + buffer_to_block(buffer, block); + + if (orig_size > BLOCK_BYTES - 8) + { + transform(digest, block, transforms); + for (size_t i = 0; i < BLOCK_INTS - 2; i++) + { + block[i] = 0; + } + } + + /* Append total_bits, split this uint64_t into two uint32_t */ + block[BLOCK_INTS - 1] = (uint32_t)total_bits; + block[BLOCK_INTS - 2] = (uint32_t)(total_bits >> 32); + transform(digest, block, transforms); + + /* Hex std::string */ + std::ostringstream result; + for (size_t i = 0; i < sizeof(digest) / sizeof(digest[0]); i++) + { + result << std::hex << std::setfill('0') << std::setw(8); + result << digest[i]; + } + + /* Reset for next run */ + reset(digest, buffer, transforms); + + return result.str(); +} + + +inline std::string SHA1::from_file(const std::string &filename) +{ + std::ifstream stream(filename.c_str(), std::ios::binary); + SHA1 checksum; + checksum.update(stream); + return checksum.final(); +} + + +#endif /* SHA1_HPP */ \ No newline at end of file diff --git a/include/write-tree.h b/include/write-tree.h new file mode 100644 index 0000000..b471161 --- /dev/null +++ b/include/write-tree.h @@ -0,0 +1,3 @@ +#pragma once + +int writeTree(const char** argv); \ No newline at end of file diff --git a/src/commands/cat-file.cpp b/src/commands/cat-file.cpp new file mode 100644 index 0000000..6ad677f --- /dev/null +++ b/src/commands/cat-file.cpp @@ -0,0 +1,42 @@ +#include "cat-file.h" + +#include +#include +#include +#include +#include + +#include "zstr.hpp" + +int catFile(const int argc, const char **argv) +{ + if (argc < 4) + { + std::cerr << "please provide proper arguments. example '-p '.\n"; + return 1; + } + + const std::string flag = argv[2]; + if (flag != "-p") + { + std::cerr << flag << "is an invalid flag, please use \"-p\"\n"; + return 1; + } + + const std::string input = argv[3]; + const std::string dir = input.substr(0, 2); + const std::string file = input.substr(2); + + zstr::ifstream inputFile("./.git/objects/" + dir + "/" + file, std::ofstream::binary); + if (!inputFile.is_open()) + { + std::cerr << "Could not open file\n"; + return 1; + } + + const std::string output{std::istreambuf_iterator(inputFile), std::istreambuf_iterator()}; + std::cout << std::string_view(output).substr(output.find('\0') + 1); + inputFile.close(); + + return 0; +} diff --git a/src/commands/clone.cpp b/src/commands/clone.cpp new file mode 100644 index 0000000..4fa5c44 --- /dev/null +++ b/src/commands/clone.cpp @@ -0,0 +1,12 @@ +#include "clone.h" + +#include +#include +#include +#include +#include + +int clone(const int argc, const char **argv) +{ + return 0; +} diff --git a/src/commands/hash-object.cpp b/src/commands/hash-object.cpp new file mode 100644 index 0000000..ccb8902 --- /dev/null +++ b/src/commands/hash-object.cpp @@ -0,0 +1,52 @@ +#include "hash-object.h" + +#include +#include +#include +#include +#include + +#include "sha1.hpp" +#include "zstr.hpp" + +int hashObject(const int argc, const char **argv) +{ + std::filesystem::create_directory("./.git/objects"); + if (argc < 4) + { + std::cerr << "please provide proper arguments. example '-w '.\n"; + return 1; + } + + const std::string_view flag = argv[2]; + if (flag != "-w") + { + std::cerr << flag << "is an invalid flag, please use \"-w\"\n"; + return 1; + } + const std::string path = argv[3]; + std::ifstream inputFile("./" + path); + if (!inputFile.is_open()) + { + std::cerr << "Could not open file\n"; + return 1; + } + + std::string content {std::istreambuf_iterator(inputFile), std::istreambuf_iterator()}; + std::string objectData = "blob " + std::to_string(content.size()) + '\0' + content; + + SHA1 checksum; + checksum.update(objectData); + const std::string hash = checksum.final(); + std::cout << hash << "\n"; + + std::filesystem::create_directory("./.git/objects/" + hash.substr(0,2)); + + zstr::ofstream outputFile("./.git/objects/" + hash.substr(0,2) + "/" + hash.substr(2)); + outputFile.write(objectData.c_str(), objectData.size()); + + inputFile.close(); + outputFile.close(); + + return 0; +} diff --git a/src/commands/init.cpp b/src/commands/init.cpp new file mode 100644 index 0000000..d01f6a6 --- /dev/null +++ b/src/commands/init.cpp @@ -0,0 +1,34 @@ +#include "init.h" + +#include +#include +#include +#include + +int init() +{ + try + { + std::filesystem::create_directory(".git"); + std::filesystem::create_directory(".git/objects"); + std::filesystem::create_directory(".git/refs"); + + std::ofstream headFile(".git/HEAD"); + if (headFile.is_open()) { + headFile << "ref: refs/heads/main\n"; + headFile.close(); + } else { + std::cerr << "Failed to create .git/HEAD file.\n"; + return 1; + } + + std::cout << "Initialized git directory\n"; + } + catch (const std::filesystem::filesystem_error& e) + { + std::cerr << e.what() << '\n'; + return 1; + } + + return 0; +} diff --git a/src/commands/ls-tree.cpp b/src/commands/ls-tree.cpp new file mode 100644 index 0000000..68b0052 --- /dev/null +++ b/src/commands/ls-tree.cpp @@ -0,0 +1,74 @@ +#include "ls-tree.h" + +#include +#include +#include +#include +#include + +#include "zstr.hpp" + +int lsTree(const int argc, const char **argv) +{ + bool nameOnly = false; + if (argc == 4) + { + const std::string flag = argv[2]; + if (flag != "--name-only") + { + std::cerr << flag << "is an invalid flag, please use \"--name-only\"\n"; + return 1; + } + nameOnly = true; + } + + const std::string input = nameOnly ? argv[3] : argv[2]; + const std::string dir = input.substr(0, 2); + const std::string file = input.substr(2); + + zstr::ifstream inputFile("./.git/objects/" + dir + "/" + file, std::ofstream::binary); + if (!inputFile.is_open()) + { + std::cerr << "Could not open file\n"; + return 1; + } + + const std::string output{std::istreambuf_iterator(inputFile), std::istreambuf_iterator()}; + //start right after tree \0 + size_t startChar = output.find('\0') + 1; + size_t nextNullChar = output.find('\0', startChar); + + while (nextNullChar != std::string::npos) + { + const std::string_view modeAndName = std::string_view(output).substr(startChar, nextNullChar - startChar); + const std::string_view mode = modeAndName.substr(0, modeAndName.find(' ')); + const std::string_view name = modeAndName.substr(modeAndName.find(' ') + 1); + const std::string_view hash = std::string_view(output).substr(nextNullChar + 1, 20); + + if (!nameOnly) + { + if (mode == "100644" or mode == "100755") + { + std::cout << mode << ' '; + std::cout << "blob "; + } + else + {//assume it's a tree obj + std::cout << "0" << mode << ' '; + std::cout << "tree "; + } + + for (const auto& value : hash) + std::printf("%02x", static_cast(value)); // simpler solution + + std::cout << " "; + } + std::cout << name << "\n"; + startChar = nextNullChar + 21; + nextNullChar = output.find('\0', startChar); + } + + inputFile.close(); + return 0; + +} diff --git a/src/commands/write-tree.cpp b/src/commands/write-tree.cpp new file mode 100644 index 0000000..36672c3 --- /dev/null +++ b/src/commands/write-tree.cpp @@ -0,0 +1,175 @@ +#include "write-tree.h" + +#include +#include +#include +#include +#include + +#include "sha1.hpp" +#include "zstr.hpp" + +struct node +{ + std::string name; + int mode; + std::string hash; +}; + +uint8_t hexToRaw(char firstDigit, char secondDigit) +{ + uint8_t result = 0; + switch (firstDigit) + { + case '0': result = 0; break; + case '1': result = 1 << 4; break; + case '2': result = 2 << 4; break; + case '3': result = 3 << 4; break; + case '4': result = 4 << 4; break; + case '5': result = 5 << 4; break; + case '6': result = 6 << 4; break; + case '7': result = 7 << 4; break; + case '8': result = 8 << 4; break; + case '9': result = 9 << 4; break; + case 'a': result = 10 << 4; break; + case 'b': result = 11 << 4; break; + case 'c': result = 12 << 4; break; + case 'd': result = 13 << 4; break; + case 'e': result = 14 << 4; break; + case 'f': result = 15 << 4; break; + default: result = 0; break; + } + + switch (secondDigit) + { + case '0': result |= 0x0; break; + case '1': result |= 0x1; break; + case '2': result |= 0x2; break; + case '3': result |= 0x3; break; + case '4': result |= 0x4; break; + case '5': result |= 0x5; break; + case '6': result |= 0x6; break; + case '7': result |= 0x7; break; + case '8': result |= 0x8; break; + case '9': result |= 0x9; break; + case 'a': result |= 0xa; break; + case 'b': result |= 0xb; break; + case 'c': result |= 0xc; break; + case 'd': result |= 0xd; break; + case 'e': result |= 0xe; break; + case 'f': result |= 0xf; break; + default: result = 0; break; + } + + return result; +} + +std::string hashDir(const std::string& path) +{ + + std::vector nodes; + + for (const auto& object : std::filesystem::directory_iterator(path)) + { + if (object.is_directory()) + { + nodes.emplace_back(object.path().filename().string(), 40000, hashDir(object.path().string())); + } + else + { + std::ifstream file(object.path().string()); + std::string fileContent {std::istreambuf_iterator(file), std::istreambuf_iterator()}; + + SHA1 checksum; + checksum.update("blob " + std::to_string(fileContent.size()) + '\0' + fileContent); + nodes.emplace_back(object.path().filename(), 100644, checksum.final()); + file.close(); + } + } + + std::sort(nodes.begin(), nodes.end(), + [](const auto& first, const auto& second){return first.name < second.name;}); + + std::string treeObject; + + for (const auto&[name, mode, hash] : nodes) + { + treeObject += std::to_string(mode) + " " + name + '\0'; + for (int i = 0; i < 39; i += 2) + { + uint8_t byte = hexToRaw(hash.at(i), hash.at(i+1)); + + treeObject += static_cast(byte); + } + } + auto temp = treeObject.size(); + + treeObject = "tree " + std::to_string(treeObject.size()) + '\0' + treeObject; + SHA1 checksum; + checksum.update(treeObject); + + return checksum.final(); + +} + +int writeTree(const char **argv) +{ + if (!std::filesystem::exists("./.git/objects")) + std::filesystem::create_directory("./.git/objects"); + + std::vector nodes; + + + for (const auto& object : std::filesystem::directory_iterator("./")) + { + if (object.path().filename() == ".git") continue; + + if (object.is_directory()) + { + nodes.emplace_back(object.path().filename().string(), 40000, hashDir(object.path().string())); + } + else + { + std::ifstream file(object.path().string()); + std::string fileContent {std::istreambuf_iterator(file), std::istreambuf_iterator()}; + SHA1 checksum; + checksum.update("blob " + std::to_string(fileContent.size()) + '\0' + fileContent); + nodes.emplace_back(object.path().filename(), 100644, checksum.final()); + file.close(); + } + + } + + std::sort(nodes.begin(), nodes.end(), + [](const auto& first, const auto& second){return first.name < second.name;}); + + std::string treeObject; + + for (const auto&[name, mode, hash] : nodes) + { + treeObject += std::to_string(mode) + " " + name + '\0'; + for (int i = 0; i < 39; i += 2) + { + uint8_t byte = hexToRaw(hash.at(i), hash.at(i+1)); + + treeObject += static_cast(byte); + } + } + + treeObject = "tree " + std::to_string(treeObject.size()) + '\0' + treeObject; + + SHA1 checksum; + checksum.update(treeObject); + + const std::string hash = checksum.final(); + std::cout << hash << "\n"; + + std::filesystem::create_directory("./.git/objects/" + hash.substr(0,2)); + + zstr::ofstream outputFile("./.git/objects/" + hash.substr(0,2) + "/" + hash.substr(2)); + outputFile.write(treeObject.c_str(), treeObject.size()); + + outputFile.close(); + + return 0; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..20125fe --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include "commands.h" +#include "../include/hash-object.h" + +int main(const int argc, const char** argv) +{ + if (argc < 2) + { + std::cerr << "please provide a command: init, cat-file, hash-object, ls-tree, write-tree, clone\n"; + return 1; + } + + const std::string_view command = argv[1]; + + if (command == "init") + { + return init(); + } + else if (command == "cat-file") + { + return catFile(argc, argv); + } + else if (command == "hash-object") + { + return hashObject(argc, argv); + } + else if (command == "ls-tree") + { + return lsTree(argc, argv); + } + else if (command == "write-tree") + { + return writeTree(argv); + } + else if (command == "clone") + { + return clone(argc, argv); + } + else + { + std::cerr << argv[0] << " is not a valid command\n"; + return 1; + } +} +