diff --git a/GitTester.java b/GitTester.java new file mode 100644 index 0000000..0d4251f --- /dev/null +++ b/GitTester.java @@ -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"); + } +} \ No newline at end of file diff --git a/GitWrapper.java b/GitWrapper.java new file mode 100644 index 0000000..d78c5ee --- /dev/null +++ b/GitWrapper.java @@ -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); + } + } +} diff --git a/README.md b/README.md index 022db1b..94df3b4 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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. \ No newline at end of file + 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 diff --git a/initializeGit.java b/initializeGit.java index 56de568..a0517b4 100644 --- a/initializeGit.java +++ b/initializeGit.java @@ -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(); } @@ -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 streamOne = Files.walk(parameterPath); + // for (Path p : (Iterable) streamOne::iterator) { + // if (checkPath(path, p.toString())) { + // continue; + // } + // System.out.println(p.toString()); + // } + // System.out.println(" "); + // streamOne.close(); + // return ""; + Stream streamTwo = Files.walk(parameterPath); + boolean isFirst = true; + Path treePath = Path.of("git/objects/temporary" + String.valueOf(tempCount)); + Files.createFile(treePath); + for (Path p : (Iterable) 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 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 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)); + } } \ No newline at end of file diff --git a/myProgram/hello.txt b/myProgram/hello.txt new file mode 100644 index 0000000..f15bc78 --- /dev/null +++ b/myProgram/hello.txt @@ -0,0 +1 @@ +Wazzup \ No newline at end of file diff --git a/myProgram/inner/world.txt b/myProgram/inner/world.txt new file mode 100644 index 0000000..64613de --- /dev/null +++ b/myProgram/inner/world.txt @@ -0,0 +1 @@ +Aiden is here \ No newline at end of file