diff options
Diffstat (limited to 'p1')
| -rw-r--r-- | p1/Client.java | 91 | ||||
| -rw-r--r-- | p1/ExampleTesting.java | 148 | ||||
| -rw-r--r-- | p1/Repository.java | 241 | ||||
| -rw-r--r-- | p1/Testing.java | 99 |
4 files changed, 579 insertions, 0 deletions
diff --git a/p1/Client.java b/p1/Client.java new file mode 100644 index 0000000..510ed82 --- /dev/null +++ b/p1/Client.java @@ -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(); + } +} diff --git a/p1/ExampleTesting.java b/p1/ExampleTesting.java new file mode 100644 index 0000000..2c00e3f --- /dev/null +++ b/p1/ExampleTesting.java @@ -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)); + } + } +} diff --git a/p1/Repository.java b/p1/Repository.java new file mode 100644 index 0000000..dff76e7 --- /dev/null +++ b/p1/Repository.java @@ -0,0 +1,241 @@ +import java.util.*; +import java.text.SimpleDateFormat; + +public class Repository { + + private String name; + private Commit head; + private int size; + + public Repository(String name) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("Repository needs a name!"); + } + + this.name = name; + } + + public String getRepoHead() { + if (head == null) { + return null; + } + return head.id; + } + + public int getRepoSize() { + Commit temp = head; + int size = 0; + while (temp != null) { + size++; + temp = temp.past; + } + this.size = size; + return size; + } + + public String toString() { + if (head == null) { + return name + " - No commits"; + } + return name + " - Current head: " + head.toString(); + } + + public boolean contains(String targetId) { + Commit temp = head; + + while (temp != null) { + if (temp.id.equals(targetId)) { + return true; + } + temp = temp.past; + } + + return false; + } + + public String getHistory(int n) { + if (n <= 0) { + throw new IllegalArgumentException("do you want the history or not?"); + } + + Commit temp = head; + String history = ""; + if (n > size) { + while (temp != null) { + history += temp.toString() + "\n"; + temp = temp.past; + } + } + + for (int i = 0; i < n && temp != null; i++) { + history += temp.toString() + "\n"; + temp = temp.past; + } + + return history; + } + + public String commit(String message) { + if (head == null) { + head = new Commit(message); + return head.id; + } + Commit old = head; + head = new Commit(message, old); + size++; + return head.id; + } + + public boolean drop(String targetId) { + // list is empty + if (head == null) { + return false; + } + + size--; + Commit temp = head; + + // front + if (temp.id.equals(targetId)) { + head = temp.past; + return true; + } + + // middle + while (temp.past != null && temp.past.past != null) { + if (temp.past.id == targetId) { + temp.past = temp.past.past; + return true; + } + temp = temp.past; + } + + // end + if (temp.past != null && temp.past.id.equals(targetId)) { + temp.past = null; + return true; + } + + // if we get here, targetId was not found, return size to initial state + size++; + return false; + } + + public void synchronize(Repository other) { + if (head == null && other.head == null) { + head = null; + } else if (head == null) { + head = other.head; + other.head = null; + } else if (other.head == null) { + other.head = null; + } else { + List<Commit> commitList = new ArrayList<>(); + Comparator commitComparator = new Comparator<Commit>() { + public int compare(Commit one, Commit two) { + return Long.compare(one.timeStamp, two.timeStamp); + } + }; + Commit temp = head; + while (temp != null) { + commitList.add(temp); + temp = temp.past; + } + temp = other.head; + while (temp != null) { + commitList.add(temp); + temp = temp.past; + } + commitList.sort(commitComparator); + Commit prev = null; + for (Commit commit : commitList) { + commit.past = prev; + prev = commit; + } + head = commitList.get(commitList.size() - 1); + other.head = null; + } + } + /** + * 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; + } + } +} diff --git a/p1/Testing.java b/p1/Testing.java new file mode 100644 index 0000000..2f7ae88 --- /dev/null +++ b/p1/Testing.java @@ -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)); + } + } +} |
