This commit is contained in:
2026-03-18 00:39:35 -07:00
commit b4fdc98f10
43 changed files with 85012 additions and 0 deletions

21
p1/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,21 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Current File",
"request": "launch",
"mainClass": "${file}"
},
{
"type": "java",
"name": "Client",
"request": "launch",
"mainClass": "Client",
"projectName": "p1_b4eddd44"
}
]
}

91
p1/Client.java Normal file
View File

@@ -0,0 +1,91 @@
import java.util.*;
// A program to work with Mini-Git. Manages the state of repositories and allows for all
// operations defined in Mini-Git.
public class Client {
private static List<String> ops = new ArrayList<>();
public static void main(String[] args) {
Collections.addAll(ops, "create", "head", "history", "commit", "drop",
"synchronize", "quit");
Scanner console = new Scanner(System.in);
Map<String, Repository> repos = new HashMap<>();
String op = "";
String name = "";
intro();
while (!op.equalsIgnoreCase("quit")) {
System.out.println("Available repositories: ");
for (Repository repo : repos.values()) {
System.out.println("\t" + repo);
}
System.out.println("Operations: " + ops);
System.out.print("Enter operation and repository: ");
String[] input = console.nextLine().split("\\s+");
op = input[0];
name = input.length > 1 ? input[1] : "";
while (!ops.contains(op) || (!op.equalsIgnoreCase("create") &&
!op.equalsIgnoreCase("quit") &&
!repos.containsKey(name))) {
System.out.println(" **ERROR**: Operation or repository not recognized.");
System.out.print("Enter operation and repository: ");
input = console.nextLine().split("\\s+");
op = input[0];
name = input.length > 1 ? input[1] : "";
}
Repository currRepo = repos.get(name);
op = op.toLowerCase();
if (op.equalsIgnoreCase("create")) {
if (currRepo != null) {
System.out.println(" **ERROR**: Repository with that name already exists.");
} else {
Repository newRepo = new Repository(name);
repos.put(name, newRepo);
System.out.println(" New repository created: " + newRepo);
}
} else if (op.equalsIgnoreCase("head")) {
System.out.println(currRepo.getRepoHead());
} else if (op.equalsIgnoreCase("history")) {
System.out.print("How many commits back? ");
int nHist = console.nextInt();
console.nextLine();
System.out.println(currRepo.getHistory(nHist));
} else if (op.equalsIgnoreCase("commit")) {
System.out.print("Enter commit message: ");
String message = console.nextLine();
System.out.println(" New commit: " + currRepo.commit(message));
} else if (op.equalsIgnoreCase("drop")) {
System.out.print("Enter ID to drop: ");
String idDrop = console.nextLine();
if (currRepo.drop(idDrop)) {
System.out.println(" Successfully dropped " + idDrop);
} else {
System.out.println(" No commit dropped!");
}
} else if (op.equalsIgnoreCase("synchronize")) {
System.out.print("Which repository would you like to " +
"synchronize into the given one? ");
String repo = console.nextLine();
if (repo.equals(name)) {
System.out.println("Cannot synchronize the same repositories!");
} else if (!repos.containsKey(repo)) {
System.out.println("Repository does not exist!");
} else {
currRepo.synchronize(repos.get(repo));
}
}
System.out.println();
}
}
// Prints out an introduction to the Mini-Git test client.
public static void intro() {
System.out.println("Welcome to the Mini-Git test client!");
System.out.println("Use this program to test your Mini-Git repository implemenation.");
System.out.println("Make sure to test all operations in all cases --");
System.out.println("some cases are particularly tricky.");
System.out.println();
}
}

148
p1/ExampleTesting.java Normal file
View File

@@ -0,0 +1,148 @@
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.*;
public class Testing {
private Repository repo1;
private Repository repo2;
// Occurs before each of the individual test cases
// (creates new repos and resets commit ids)
@BeforeEach
public void setUp() {
repo1 = new Repository("repo1");
repo2 = new Repository("repo2");
Repository.Commit.resetIds();
}
@Test
@DisplayName("EXAMPLE TEST - getHistory()")
public void getHistory() throws InterruptedException {
// Initialize commit messages
String[] commitMessages = new String[]{"Initial commit.",
"Updated method documentation.",
"Removed unnecessary object creation."};
commitAll(repo1, commitMessages);
testHistory(repo1, 1, commitMessages);
}
@Test
@DisplayName("EXAMPLE TEST - drop() (empty case)")
public void testDropEmpty() {
assertFalse(repo1.drop("123"));
}
@Test
@DisplayName("EXAMPLE TEST - drop() (front case)")
public void testDropFront() throws InterruptedException {
// Initialize commit messages
commitAll(repo1, new String[]{"First commit"}); // ID "0"
commitAll(repo2, new String[]{"Added unit tests."}); // ID "1"
// Assert that repo1 successfully dropped "0"
assertTrue(repo1.drop("0"));
assertEquals(repo1.getRepoSize(), 0);
// Assert that repo2 does not drop "0" but drops "1"
// (Note that the commit ID increments regardless of the repository!)
assertFalse(repo2.drop("0"));
assertTrue(repo2.drop("1"));
assertEquals(repo2.getRepoSize(), 0);
}
@Test
@DisplayName("EXAMPLE TEST - synchronize() (one: [1, 2], two: [3, 4])")
public void testSynchronizeOne() throws InterruptedException {
// Initialize commit messages
commitAll(repo1, new String[]{"One", "Two"});
commitAll(repo2, new String[]{"Three", "Four"});
// Make sure both repos got exactly 2 commits each
assertEquals(2, repo1.getRepoSize());
assertEquals(2, repo2.getRepoSize());
// Synchronize repo2 into repo1
repo1.synchronize(repo2);
assertEquals(4, repo1.getRepoSize());
assertEquals(0, repo2.getRepoSize());
// Make sure the history of repo1 is correctly synchronized
testHistory(repo1, 4, new String[]{"One", "Two", "Three", "Four"});
}
// Commits all of the provided messages into the provided repo, making sure timestamps
// are correctly sequential (no ties). If used, make sure to include
// 'throws InterruptedException'
// much like we do with 'throws FileNotFoundException'. Example useage:
//
// repo1:
// head -> null
// To commit the messages "one", "two", "three", "four"
// commitAll(repo1, new String[]{"one", "two", "three", "four"})
// This results in the following after picture
// repo1:
// head -> "four" -> "three" -> "two" -> "one" -> null
//
// YOU DO NOT NEED TO UNDERSTAND HOW THIS METHOD WORKS TO USE IT!
// (this is why documentation is important!)
public void commitAll(Repository repo, String[] messages) throws InterruptedException {
// Commit all of the provided messages
for (String message : messages) {
int size = repo.getRepoSize();
repo.commit(message);
// Make sure exactly one commit was added to the repo
assertEquals(size + 1, repo.getRepoSize(),
String.format("Size not correctly updated after commiting message [%s]",
message));
// Sleep to guarantee that all commits have different time stamps
Thread.sleep(2);
}
}
// Makes sure the given repositories history is correct up to 'n' commits, checking against
// all commits made in order. Example useage:
//
// repo1:
// head -> "four" -> "three" -> "two" -> "one" -> null
// (Commits made in the order ["one", "two", "three", "four"])
// To test the getHistory() method up to n=3 commits this can be done with:
// testHistory(repo1, 3, new String[]{"one", "two", "three", "four"})
// Similarly, to test getHistory() up to n=4 commits you'd use:
// testHistory(repo1, 4, new String[]{"one", "two", "three", "four"})
//
// YOU DO NOT NEED TO UNDERSTAND HOW THIS METHOD WORKS TO USE IT!
// (this is why documentation is important!)
public void testHistory(Repository repo, int n, String[] allCommits) {
int totalCommits = repo.getRepoSize();
assertTrue(n <= totalCommits,
String.format("Provided n [%d] too big. Only [%d] commits",
n, totalCommits));
String[] nCommits = repo.getHistory(n).split("\n");
assertTrue(nCommits.length <= n,
String.format("getHistory(n) returned more than n [%d] commits", n));
assertTrue(nCommits.length <= allCommits.length,
String.format("Not enough expected commits to check against. " +
"Expected at least [%d]. Actual [%d]",
n, allCommits.length));
for (int i = 0; i < n; i++) {
String commit = nCommits[i];
// Old commit messages/ids are on the left and the more recent commit messages/ids are
// on the right so need to traverse from right to left
int backwardsIndex = totalCommits - 1 - i;
String commitMessage = allCommits[backwardsIndex];
assertTrue(commit.contains(commitMessage),
String.format("Commit [%s] doesn't contain expected message [%s]",
commit, commitMessage));
assertTrue(commit.contains("" + backwardsIndex),
String.format("Commit [%s] doesn't contain expected id [%d]",
commit, backwardsIndex));
}
}
}

146
p1/Repository.java Normal file
View File

@@ -0,0 +1,146 @@
import java.util.*;
import java.text.SimpleDateFormat;
public class Repository {
private String name;
private LinkedList<Commit> commits;
public Repository(String name) {
if (name.isEmpty() || name == null) {
throw new IllegalArgumentException("Repository must have a name!");
}
this.name = name;
this.commits = new LinkedList<>();
}
public String getRepoHead() {
return commits.peek().id;
}
public int getRepoSize() {
return commits.size();
}
public String toString() {
return name + " - Current head: " + commits.peek().toString();
}
public boolean contains(String targetId) {
for (Commit commit : commits) {
if (commit.id == targetId) {
return true;
}
}
return false;
}
public String getHistory(int n) {
String output = "";
for (int i = 0; i < commits.size() - 1; i++) {
output += commits.get(i).toString() + "\n";
}
return output;
}
public String commit(String message) {
commits.add(new Commit(message));
return commits.peek().id;
}
public boolean drop(String targetId) {
for (int n = 0; n < commits.size() - 1; n ++) {
if (commits.get(n).id == targetId) {
commits.remove(n);
return true;
}
}
return false;
}
public void synchronize(Repository other) {
}
/**
* DO NOT MODIFY
* A class that represents a single commit in the repository.
* Commits are characterized by an identifier, a commit message,
* and the time that the commit was made. A commit also stores
* a reference to the immediately previous commit if it exists.
*
* Staff Note: You may notice that the comments in this
* class openly mention the fields of the class. This is fine
* because the fields of the Commit class are public. In general,
* be careful about revealing implementation details!
*/
public class Commit {
private static int currentCommitID;
/**
* The time, in milliseconds, at which this commit was created.
*/
public final long timeStamp;
/**
* A unique identifier for this commit.
*/
public final String id;
/**
* A message describing the changes made in this commit.
*/
public final String message;
/**
* A reference to the previous commit, if it exists. Otherwise, null.
*/
public Commit past;
/**
* Constructs a commit object. The unique identifier and timestamp
* are automatically generated.
* @param message A message describing the changes made in this commit.
* @param past A reference to the commit made immediately before this
* commit.
*/
public Commit(String message, Commit past) {
this.id = "" + currentCommitID++;
this.message = message;
this.timeStamp = System.currentTimeMillis();
this.past = past;
}
/**
* Constructs a commit object with no previous commit. The unique
* identifier and timestamp are automatically generated.
* @param message A message describing the changes made in this commit.
*/
public Commit(String message) {
this(message, null);
}
/**
* Returns a string representation of this commit. The string
* representation consists of this commit's unique identifier,
* timestamp, and message, in the following form:
* "[identifier] at [timestamp]: [message]"
* @return The string representation of this collection.
*/
@Override
public String toString() {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
Date date = new Date(timeStamp);
return id + " at " + formatter.format(date) + ": " + message;
}
/**
* Resets the IDs of the commit nodes such that they reset to 0.
* Primarily for testing purposes.
*/
public static void resetIds() {
Commit.currentCommitID = 0;
}
}
}

99
p1/Testing.java Normal file
View File

@@ -0,0 +1,99 @@
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.*;
public class Testing {
private Repository repo1;
private Repository repo2;
// Occurs before each of the individual test cases
// (creates new repos and resets commit ids)
@BeforeEach
public void setUp() {
repo1 = new Repository("repo1");
repo2 = new Repository("repo2");
Repository.Commit.resetIds();
}
// TODO: Write your tests here!
/////////////////////////////////////////////////////////////////////////////////
// PROVIDED HELPER METHODS (You don't have to use these if you don't want to!) //
/////////////////////////////////////////////////////////////////////////////////
// Commits all of the provided messages into the provided repo, making sure timestamps
// are correctly sequential (no ties). If used, make sure to include
// 'throws InterruptedException'
// much like we do with 'throws FileNotFoundException'. Example useage:
//
// repo1:
// head -> null
// To commit the messages "one", "two", "three", "four"
// commitAll(repo1, new String[]{"one", "two", "three", "four"})
// This results in the following after picture
// repo1:
// head -> "four" -> "three" -> "two" -> "one" -> null
//
// YOU DO NOT NEED TO UNDERSTAND HOW THIS METHOD WORKS TO USE IT! (this is why documentation
// is important!)
public void commitAll(Repository repo, String[] messages) throws InterruptedException {
// Commit all of the provided messages
for (String message : messages) {
int size = repo.getRepoSize();
repo.commit(message);
// Make sure exactly one commit was added to the repo
assertEquals(size + 1, repo.getRepoSize(),
String.format("Size not correctly updated after commiting message [%s]",
message));
// Sleep to guarantee that all commits have different time stamps
Thread.sleep(2);
}
}
// Makes sure the given repositories history is correct up to 'n' commits, checking against
// all commits made in order. Example useage:
//
// repo1:
// head -> "four" -> "three" -> "two" -> "one" -> null
// (Commits made in the order ["one", "two", "three", "four"])
// To test the getHistory() method up to n=3 commits this can be done with:
// testHistory(repo1, 3, new String[]{"one", "two", "three", "four"})
// Similarly, to test getHistory() up to n=4 commits you'd use:
// testHistory(repo1, 4, new String[]{"one", "two", "three", "four"})
//
// YOU DO NOT NEED TO UNDERSTAND HOW THIS METHOD WORKS TO USE IT! (this is why documentation
// is important!)
public void testHistory(Repository repo, int n, String[] allCommits) {
int totalCommits = repo.getRepoSize();
assertTrue(n <= totalCommits,
String.format("Provided n [%d] too big. Only [%d] commits",
n, totalCommits));
String[] nCommits = repo.getHistory(n).split("\n");
assertTrue(nCommits.length <= n,
String.format("getHistory(n) returned more than n [%d] commits", n));
assertTrue(nCommits.length <= allCommits.length,
String.format("Not enough expected commits to check against. " +
"Expected at least [%d]. Actual [%d]",
n, allCommits.length));
for (int i = 0; i < n; i++) {
String commit = nCommits[i];
// Old commit messages/ids are on the left and the more recent commit messages/ids are
// on the right so need to traverse from right to left
int backwardsIndex = totalCommits - 1 - i;
String commitMessage = allCommits[backwardsIndex];
assertTrue(commit.contains(commitMessage),
String.format("Commit [%s] doesn't contain expected message [%s]",
commit, commitMessage));
assertTrue(commit.contains("" + backwardsIndex),
String.format("Commit [%s] doesn't contain expected id [%d]",
commit, backwardsIndex));
}
}
}