Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions GitTester.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import java.io.IOException;

public class GitTester {

public static void main(String args[]) throws Exception {
GitWrapper gw = new GitWrapper();
gw.add("myProgram/hello.txt");
gw.add("myProgram/inner/world.txt");
gw.commit("John Doe", "Initial commit");
gw.checkout("74d475aeeced024a5e9984b7a6f5e0ffd091ccbc");
}
}
112 changes: 112 additions & 0 deletions GitWrapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;

public class GitWrapper {

/**
* Initializes a new Git repository.
* This should create the necessary directory structure
* and initial files required for a Git repository.
* This should create the initial commit and update HEAD accordingly
*/
public void init() throws IOException {
GPTester.reset();
initializeGit.initialize();
}

/**
* Stages a file for the next commit.
*
* @param filePath The path to the file to be staged.
*/
public void add(String filePath) throws Exception {
String contents = Files.readString(Path.of(filePath));
initializeGit.addToIndex(initializeGit.SHA1Hash(contents), filePath);
initializeGit.createBLOB(contents);
initializeGit.generateWorkingList();
}

/**
* Creates a commit with the given author and message.
* It should capture the current state of the repository,
* update the HEAD, and return the commit hash.
*
* @param author The name of the author making the commit.
* @param message The commit message describing the changes.
* @return The SHA1 hash of the new commit.
*/
public String commit(String author, String message) throws IOException {
return initializeGit.commit(author, message);
}

/**
* EXTRA CREDIT: Checks out a specific commit given its hash.
* This should update the working directory to match the
* state of the repository at that commit.
*
* @param commitHash The SHA1 hash of the commit to check out.
* @throws IOException
*/
public void checkout(String commitHash) throws IOException { // chatGPT
Path commitPath = Path.of("git/objects", commitHash);
if (!Files.exists(commitPath)) {
System.out.println("Commit not found: " + commitHash);
return;
}

// Step 1: read commit file and extract tree hash
String commitContents = Files.readString(commitPath);
String treeLine = Arrays.stream(commitContents.split("\n"))
.filter(line -> line.startsWith("tree:"))
.findFirst()
.orElseThrow(() -> new IOException("No tree found in commit"));
String treeHash = treeLine.substring(treeLine.indexOf(":") + 2).trim();

// Step 2: read tree object
Path treePath = Path.of("git/objects", treeHash);
if (!Files.exists(treePath)) {
throw new IOException("Tree object not found: " + treeHash);
}
String treeContents = Files.readString(treePath);

// Step 3: clear working directory (optional: skip .git folder)
try (var paths = Files.walk(Path.of("."))) {
paths.filter(Files::isRegularFile)
.filter(p -> !p.startsWith("git"))
.forEach(p -> {
try {
Files.delete(p);
} catch (IOException e) {
System.err.println("Could not delete: " + p);
}
});
}

// Step 4: restore each file from blobs listed in tree
for (String line : treeContents.split("\n")) {
if (line.isBlank())
continue;
String[] parts = line.split(":");
if (parts.length != 2)
continue;

String filePath = parts[0].trim();
String blobHash = parts[1].trim();

Path blobPath = Path.of("git/objects", blobHash);
if (!Files.exists(blobPath)) {
System.err.println("Missing blob: " + blobHash);
continue;
}

String blobContents = Files.readString(blobPath);
Path dest = Path.of(filePath);
if (dest.getParent() != null) {
Files.createDirectories(dest.getParent());
}
Files.writeString(dest, blobContents);
}
}
}
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# git-project-Ellie
10/7/2025

This project does not contain createTree() or createWorkingList() functionality

initializeGit.java
initialize()
initaializes all parts of the repository in one method.
Expand Down Expand Up @@ -27,4 +31,7 @@ GPTester.java
verifyAll()
returns true if all directories (git and objects) and files (index and HEAD) exist.
reset()
deletes all files in the git and objects directories first, then deletes the now empty directories.
deletes all files in the git and objects directories first, then deletes the now empty directories.

10/9/2025 I added the commit method and the wrapper class for the tester.
Also added missing createTree() and generateWorkingList() functionality
210 changes: 201 additions & 9 deletions initializeGit.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,46 @@
import java.io.IOError;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDate;
import java.util.List;
import java.util.stream.Stream;

public class initializeGit {
public static void main(String[] args) throws IOException {
// initialize();
initialize();
// System.out.println("testing SHA1");
// System.out.println(SHA1Hash("HELLO"));
// createBLOB("hello");
// createBLOB("Hi");
// System.out.println("SHA1 of hell0: " + SHA1Hash("hello"));
// System.out.println(blobExists(SHA1Hash("hello")));
// System.out.println(blobExists(SHA1Hash("Hi")));
// deleteAllBlobs();
// System.out.println(blobExists(SHA1Hash("hello")));
// System.out.println(blobExists(SHA1Hash("Hi")));
createBLOB("hello");
createBLOB("Hi");
System.out.println("SHA1 of hell0: " + SHA1Hash("hello"));
System.out.println(blobExists(SHA1Hash("hello")));
System.out.println(blobExists(SHA1Hash("Hi")));
deleteAllBlobs();
System.out.println(blobExists(SHA1Hash("hello")));
System.out.println(blobExists(SHA1Hash("Hi")));

// 2.4.1 Testing
String one = "hi";
String two = "hello";
String three = "hi hello";
String four = "hello";

createBLOB(one);
createBLOB(two);
createBLOB(three);
createBLOB(four);

addToIndex(SHA1Hash(one), "hi.txt");
addToIndex(SHA1Hash(two), "hello.txt");
addToIndex(SHA1Hash(three), "hihello.txt");
addToIndex(SHA1Hash(four), "hello");
resetAllFiles();

// resetAllFiles();
}
Expand Down Expand Up @@ -134,4 +145,185 @@ public static void resetAllFiles() throws IOException {
br.write("");
br.close();
}

public static String commit(String author, String message) throws IOException {
String contents = "tree: ";
String workingListSha = Files.readString(Path.of("git/objects/workinglist")); //edit path name
workingListSha = workingListSha.substring(workingListSha.indexOf(" ") + 1);
workingListSha = workingListSha.substring(0, workingListSha.indexOf(" "));
String parent = Files.readString(Path.of("git/HEAD"));
contents = contents.concat(workingListSha + "\nparent: " + parent + "\nauthor: " + author + "\ndate: ");
LocalDate date = LocalDate.now();
String dateStr = date.toString();
String year = dateStr.substring(0, dateStr.indexOf("-"));
dateStr = dateStr.substring(dateStr.indexOf("-") + 1);
String month = dateStr.substring(0, dateStr.indexOf("-"));
dateStr = dateStr.substring(dateStr.indexOf("-") + 1);
if (dateStr.charAt(0) == '0') {
dateStr = dateStr.substring(1);
}
int m = Integer.parseInt(month);
String fm = "";
switch (m) {
case 1:
fm = "Jan";
break;
case 2:
fm = "Feb";
break;
case 3:
fm = "Mar";
break;
case 4:
fm = "Apr";
break;
case 5:
fm = "May";
break;
case 6:
fm = "Jun";
break;
case 7:
fm = "Jul";
break;
case 8:
fm = "Aug";
break;
case 9:
fm = "Sep";
break;
case 10:
fm = "Oct";
break;
case 11:
fm = "Nov";
break;
default:
fm = "Dec";
}
contents = contents.concat(fm + " " + dateStr + ", " + year + "\nmessage: " + message);
Path p = Path.of("git/objects/tempCommit");
Files.createFile(p);
Files.writeString(p, contents);
String commitSha = SHA1Hash(p.toString());
Files.move(p, Path.of("git/objects/" + commitSha));
Files.writeString(Path.of("git/HEAD"), commitSha);
return ("git/objects/" + commitSha);
}

public static String createTree(String path) throws Exception {
return createTree(path, 0, false);
}

public static String createTree(String path, int tempCount) throws Exception {
return createTree(path, tempCount, false);
}

public static String createTree(String path, boolean working) throws Exception {
return createTree(path, 0, working);
}

public static String createTree(String path, int tempCount, boolean working) throws Exception {
Path parameterPath = Path.of(path);
// Stream<Path> streamOne = Files.walk(parameterPath);
// for (Path p : (Iterable<Path>) streamOne::iterator) {
// if (checkPath(path, p.toString())) {
// continue;
// }
// System.out.println(p.toString());
// }
// System.out.println(" ");
// streamOne.close();
// return "";
Stream<Path> streamTwo = Files.walk(parameterPath);
boolean isFirst = true;
Path treePath = Path.of("git/objects/temporary" + String.valueOf(tempCount));
Files.createFile(treePath);
for (Path p : (Iterable<Path>) streamTwo::iterator) {
if (isFirst) {
isFirst = false;
continue;
}
String dsStr = p.toString();
if (dsStr.indexOf("DS_Store") != -1) {
continue;
}
if (checkPath(path, dsStr)) {
continue;
}
if (working && notInIndex(p)) {
continue;
}
if (Files.isDirectory(p)) {
tempCount++;
String workingSha = createTree(p.toString(), tempCount);
if (Files.size(treePath) != 0) {
Files.writeString(treePath, "\n", StandardOpenOption.APPEND);
}
Files.writeString(treePath, "tree " + SHA1Hash("git/objects/" + workingSha) + " " + p.toString(),
StandardOpenOption.APPEND);
} else {
if (Files.size(treePath) != 0) {
Files.writeString(treePath, "\n", StandardOpenOption.APPEND);
}
Files.writeString(treePath, "blob " + SHA1Hash(p.toString()) + " " + p.toString(),
StandardOpenOption.APPEND);
if (!working) {
createBLOB(p.toString());
}
}
}
// streamOne.close();
streamTwo.close();
String shaOne = SHA1Hash(treePath.toString());
try {
Files.move(treePath, Path.of("git/objects/" + shaOne));
} catch (FileAlreadyExistsException e) {
Files.delete(treePath);
}
return shaOne;
}

public static boolean checkPath(String enclose, String inside) {
try {
inside = inside.substring(enclose.length() + 1);
return (inside.indexOf("/") != -1);
} catch (StringIndexOutOfBoundsException e) {
return false;
}
}

public static boolean notInIndex(Path path) throws IOException {
List<String> entireIndex = Files.readAllLines(Path.of("git/index"));
String all = "";
for (String s : entireIndex) {
all = all.concat(s);
}
return (all.indexOf(path.toString()) == -1);
}

public static boolean generateWorkingList() throws Exception {
List<String> all = Files.readAllLines(Path.of("git/index"));
String first;
if (all.size() != 0) {
first = all.get(0);
} else {
return false;
}
first = first.substring(first.indexOf(" ") + 1, first.indexOf("/"));
Path p = Path.of("git/objects/workinglist");
try {
Files.createFile(p);
} catch (FileAlreadyExistsException e) {

}
Files.writeString(p, "tree " + createTree(first, true) + " " + first);
String shaOne = SHA1Hash("git/objects/workinglist");
Files.writeString(p, "tree " + shaOne + " (root)");
return true;
}

public static String readFile(String path) throws IOException {
return Files.readString(Path.of(path));
}
}
1 change: 1 addition & 0 deletions myProgram/hello.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Wazzup
1 change: 1 addition & 0 deletions myProgram/inner/world.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Aiden is here