diff --git a/README.md b/README.md index 4314260..2a79e5e 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -# java_homeworks \ No newline at end of file +## Система контроля версий +Для запуска системы контроля версий выполните +``` + +mvn exec:java -Dexec.mainClass=hw_git.GitCli -Dexec.args="commit commessage testdir/file1" + +``` diff --git a/hw_git_1/pom.xml b/hw_git_1/pom.xml new file mode 100644 index 0000000..8284e27 --- /dev/null +++ b/hw_git_1/pom.xml @@ -0,0 +1,64 @@ + + 4.0.0 + + 1 + hw_git_1 + 0.0.1-SNAPSHOT + jar + + hw_git_1 + http://maven.apache.org + + + UTF-8 + + + + + maven-compiler-plugin + + 1.8 + 1.8 + UTF-8 + + + + + + + + junit + junit + 4.12 + test + + + + + + com.fasterxml.jackson.core + jackson-core + 2.9.6 + + + + com.fasterxml.jackson.core + jackson-annotations + 2.9.6 + + + + com.fasterxml.jackson.core + jackson-databind + 2.9.6 + + + + + org.apache.commons + commons-io + 1.3.2 + + + diff --git a/hw_git_1/src/main/java/hw_git/GitCli.java b/hw_git_1/src/main/java/hw_git/GitCli.java new file mode 100644 index 0000000..ffa7e0c --- /dev/null +++ b/hw_git_1/src/main/java/hw_git/GitCli.java @@ -0,0 +1,46 @@ +package hw_git; + +import java.io.IOException; +import java.util.Arrays; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.databind.JsonMappingException; + +public class GitCli { + public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException { + + GitCore core = new GitCore(); + int revision; + + try { + switch (args[0]) { + case "init": + core.makeInit(); + break; + case "commit": + System.out.println("Commiting..."); + core.makeCommit(args[1], Arrays.copyOfRange(args, 2, args.length)); + System.out.println("Commit made at revision " + core.getCurrentRevision()); + break; + case "checkout": + revision = Integer.parseInt(args[1]); + System.out.println("Check out to revision " + revision); + core.makeCheckout(revision); + break; + case "reset": + revision = Integer.parseInt(args[1]); + System.out.println("Performing reset to revision " + revision); + core.makeReset(revision); + break; + case "log": + revision = args.length == 2 ? Integer.parseInt(args[1]) : -1; + System.out.println("Log: " + core.getLog(revision)); + break; + default: + System.out.println("Unknown argument: " + args[0]); + } + } catch (UnversionedException e) { + System.out.println("This directory is not versioned"); + } + } +} diff --git a/hw_git_1/src/main/java/hw_git/GitCore.java b/hw_git_1/src/main/java/hw_git/GitCore.java new file mode 100644 index 0000000..004116b --- /dev/null +++ b/hw_git_1/src/main/java/hw_git/GitCore.java @@ -0,0 +1,197 @@ +package hw_git; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class GitCore { + private RepInformation inform = null; + private Path informPath = null; + private final String infoFileName = ".myGitData"; + private final String storageFolder = ".mygitdata"; + + void findRepInformation() throws JsonParseException, JsonMappingException, IOException, UnversionedException { + RepInformation result = null; + Path p = Paths.get(""); + + while (p != null && !Files.exists(p.resolve(infoFileName))) { + p = p.getParent(); + } + + if (p != null) { + ObjectMapper omapper = new ObjectMapper(); + result = omapper.readValue(p.resolve(infoFileName).toFile(), RepInformation.class); + } + + if (result == null) { + throw new UnversionedException(); + } + inform = result; + informPath = p; + } + + private void updateRepInformation() throws JsonGenerationException, JsonMappingException, IOException { + ObjectMapper omapper = new ObjectMapper(); + System.out.println("writing to path: " + informPath.toString()); + omapper.writeValue(informPath.resolve(infoFileName).toFile(), inform); + } + + void makeInit() throws JsonGenerationException, JsonMappingException, IOException, UnversionedException { + try { + findRepInformation(); + } catch (UnversionedException e) { + //ObjectMapper omapper = new ObjectMapper(); + //omapper.writeValue(Paths.get("").resolve(filename).toFile(), new RepInformation()); + informPath = Paths.get(""); + inform = new RepInformation(); + //inform.allFiles.put(Paths.get(""), 0); + updateRepInformation(); + System.out.println("Ok."); + } + } + + void increaseRevisionNumber() { + inform.revision++; + } + + private Path getPathRealRelative(String filename) { + return informPath.relativize(Paths.get("")).resolve(filename); + } + + private Path getStoragePath(String filename, int revision) { + Path rel = getPathRealRelative(filename); + String fname = rel.getFileName().toString(); + + rel = rel.getParent(); + + Path storage = informPath.resolve(storageFolder).resolve(rel).resolve(fname + revision); + + return storage; + } + + private void addFile(String filename) throws IOException { + int revision = inform.revision; + Path storage = getStoragePath(filename, revision); + storage.getParent().toFile().mkdirs(); + //System.out.println("trying write file to " + storage); + Files.copy(Paths.get("").resolve(filename), getStoragePath(filename, revision)); + ArrayList revisions = inform.allFiles.get(getPathRealRelative(filename).toString()); + if (revisions == null) { + revisions = new ArrayList<>(); + inform.allFiles.put(getPathRealRelative(filename).toString(), revisions); + } + revisions.add(revision); + } + + void makeCommit(String message, String[] filenames) throws IOException, UnversionedException { + findRepInformation(); + increaseRevisionNumber(); + inform.commitMessages.add(message); + inform.timestamps.add(new Timestamp(System.currentTimeMillis())); + for (String fname : filenames) { + addFile(fname); + } + updateRepInformation(); + } + + private void deleteVersionedFiles(File root) { + if (root.isFile()) { + String key = informPath.toAbsolutePath() + .relativize(Paths.get(root.getAbsolutePath())) + .toString(); + + //System.out.println("key: " + key); + + if (inform.allFiles.containsKey(key)) { + System.out.println("deleting " + root.getName()); + root.delete(); + } + return; + } + if (root.isDirectory()) { + for (File f : root.listFiles()) { + if (f.getName().equals(infoFileName) || f.getName().equals(storageFolder)) { + continue; + } + deleteVersionedFiles(f); + } + } + } + + private int getIndexOfLessEq(ArrayList list, int val) { + int curr = 0; + int prev = -1; + while (curr < list.size() && list.get(curr) <= val) { + prev = curr; + curr++; + } + + return prev; + } + + private void restoreVersionedFiles(int revision) throws IOException { + for (Map.Entry> ent : inform.allFiles.entrySet()) { + int revisionIdx = getIndexOfLessEq(ent.getValue(), revision); + if (revisionIdx >= 0) { + int revNumber = ent.getValue().get(revisionIdx); + Files.copy(informPath.resolve(storageFolder).resolve(ent.getKey() + revNumber), + informPath.resolve(ent.getKey())); + System.out.println("restored " + ent.getKey()); + } + } + } + + void makeCheckout(int revision) throws IOException, UnversionedException { + findRepInformation(); + deleteVersionedFiles(informPath.toAbsolutePath().toFile()); + restoreVersionedFiles(revision); + } + + void makeReset(int revision) throws JsonParseException, JsonMappingException, IOException, UnversionedException { + findRepInformation(); + Iterator>> it = inform.allFiles.entrySet().iterator(); + while(it.hasNext()) { + Map.Entry> ent = it.next(); + int revisionIndex = getIndexOfLessEq(ent.getValue(), revision); + if (revisionIndex == -1) { + it.remove(); + } else { + ent.getValue().subList(revisionIndex + 1, ent.getValue().size()).clear(); + } + + } + inform.revision = revision; + inform.commitMessages.subList(revision , inform.commitMessages.size()).clear(); + inform.timestamps.subList(revision, inform.timestamps.size()).clear(); + updateRepInformation(); + } + + String getLog(int revision) throws JsonParseException, JsonMappingException, IOException, UnversionedException { + findRepInformation(); + if (revision == -1) { + revision = inform.revision; + } + if(revision == 0) { + return "Empty log"; + } + return "revision: " + revision + "\n" + + inform.commitMessages.get(revision - 1) + "\n" + + inform.timestamps.get(revision - 1); + } + + int getCurrentRevision() { + return inform.revision; + } +} diff --git a/hw_git_1/src/main/java/hw_git/RepInformation.java b/hw_git_1/src/main/java/hw_git/RepInformation.java new file mode 100644 index 0000000..89c288e --- /dev/null +++ b/hw_git_1/src/main/java/hw_git/RepInformation.java @@ -0,0 +1,39 @@ +package hw_git; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class RepInformation { + int revision = 0; + ArrayList commitMessages = new ArrayList<>(); + ArrayList timestamps = new ArrayList<>(); + Map> allFiles = new TreeMap<>(); + + public int getRevision() { + return revision; + } + public void setRevision(int revision) { + this.revision = revision; + } + public List getCommitMessages() { + return commitMessages; + } + public void setCommitMessages(ArrayList commitMessages) { + this.commitMessages = commitMessages; + } + public List getTimestamps() { + return timestamps; + } + public void setTimestamps(ArrayList timestamps) { + this.timestamps = timestamps; + } + public Map> getAllFiles() { + return allFiles; + } + public void setAllFiles(Map> allFiles) { + this.allFiles = allFiles; + } +} diff --git a/hw_git_1/src/main/java/hw_git/UnversionedException.java b/hw_git_1/src/main/java/hw_git/UnversionedException.java new file mode 100644 index 0000000..33f9cf9 --- /dev/null +++ b/hw_git_1/src/main/java/hw_git/UnversionedException.java @@ -0,0 +1,5 @@ +package hw_git; + +public class UnversionedException extends Exception { + +} diff --git a/hw_git_1/src/test/java/hw_git/AppTest.java b/hw_git_1/src/test/java/hw_git/AppTest.java new file mode 100644 index 0000000..7fbeb02 --- /dev/null +++ b/hw_git_1/src/test/java/hw_git/AppTest.java @@ -0,0 +1,115 @@ +package hw_git; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Scanner; + +import org.apache.commons.io.FileUtils; + + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.databind.JsonMappingException; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class AppTest extends Assert { + + @Before + public void setUp() throws IOException { + //System.out.println("setUp"); + Files.createDirectories(Paths.get("testdir/dir1")); + Files.createFile(Paths.get("testdir/dir1/file_d1.txt")); + Files.createFile(Paths.get("testdir/file.txt")); + } + + @After + public void tearDown() throws IOException { + //System.out.println("tearDown"); + FileUtils.deleteDirectory(Paths.get("testdir").toFile()); + FileUtils.deleteDirectory(Paths.get(".mygitdata").toFile()); + if (Files.exists(Paths.get(".myGitData"))) { + Files.delete(Paths.get(".myGitData")); + } + } + + @Test + public void testInformationLoad() throws JsonGenerationException, JsonMappingException, IOException, UnversionedException { + GitCli.main(new String[] {"init"}); + GitCore core = new GitCore(); + core.findRepInformation(); + assertEquals(core.getCurrentRevision(), 0); + //Files.delete(Paths.get(".myGitData")); + } + + @Test(expected = UnversionedException.class) + public void testUnversioned() throws IOException, UnversionedException { + GitCore core = new GitCore(); + core.findRepInformation(); + //core.makeCheckout(0); + } + + @Test + public void testCheckout() throws JsonGenerationException, JsonMappingException, IOException { + GitCli.main(new String[] {"init"}); + GitCli.main(new String[] {"commit", "message 1", "testdir/file.txt"}); + GitCli.main(new String[] {"commit", "message 2", "testdir/dir1/file_d1.txt"}); + + GitCli.main(new String[] {"checkout", "1"}); + assertTrue(Files.exists(Paths.get("testdir/file.txt"))); + assertFalse(Files.exists(Paths.get("testdir/dir1/file_d1.txt"))); + + GitCli.main(new String[] {"checkout", "2"}); + assertTrue(Files.exists(Paths.get("testdir/file.txt"))); + assertTrue(Files.exists(Paths.get("testdir/dir1/file_d1.txt"))); + } + + @Test + public void testFileChangeBetweenCommits() throws JsonGenerationException, JsonMappingException, IOException { + GitCli.main(new String[] {"init"}); + try (PrintWriter out = new PrintWriter(new File("testdir/file.txt"))) { + out.print("commit 1 content"); + } + GitCli.main(new String[] {"commit", "message 1", "testdir/file.txt"}); + GitCli.main(new String[] {"commit", "message 2", "testdir/dir1/file_d1.txt"}); + + try (PrintWriter out = new PrintWriter(new File("testdir/file.txt"))) { + out.print("commit 3 content"); + } + + GitCli.main(new String[] {"commit", "message 3", "testdir/file.txt"}); + + GitCli.main(new String[] {"checkout", "2"}); + try (Scanner in = new Scanner(new File("testdir/file.txt"))) { + assertEquals(in.nextLine(), "commit 1 content"); + } + + GitCli.main(new String[] {"checkout", "3"}); + try (Scanner in = new Scanner(new File("testdir/file.txt"))) { + assertEquals(in.nextLine(), "commit 3 content"); + } + } + + @Test + public void testReset() throws JsonGenerationException, JsonMappingException, IOException { + GitCli.main(new String[] {"init"}); + GitCli.main(new String[] {"commit", "message 1", "testdir/file.txt"}); + String s1; + try (Scanner in = new Scanner(new File(".myGitData"))) { + s1 = in.nextLine(); + } + GitCli.main(new String[] {"commit", "message 2", "testdir/dir1/file_d1.txt"}); + GitCli.main(new String[] {"reset", "1"}); + String s2; + try (Scanner in = new Scanner(new File(".myGitData"))) { + s2 = in.nextLine(); + } + assertEquals(s1, s2); + } +}