diff --git a/README.md b/README.md index 4314260..8feee6a 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -# java_homeworks \ No newline at end of file +## Мой GIT. +Для запуска выполните +``` + +mvn exec:java -Dexec.mainClass=hw_git.GitCli -Dexec.args="commit commessage testdir/file1" + +``` diff --git a/hw_git_2/pom.xml b/hw_git_2/pom.xml new file mode 100644 index 0000000..cf559c8 --- /dev/null +++ b/hw_git_2/pom.xml @@ -0,0 +1,64 @@ + + 4.0.0 + + 1 + hw_git_2 + 0.0.1-SNAPSHOT + jar + + hw_git_2 + 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_2/src/main/java/hw_git/GitCli.java b/hw_git_2/src/main/java/hw_git/GitCli.java new file mode 100644 index 0000000..833bc21 --- /dev/null +++ b/hw_git_2/src/main/java/hw_git/GitCli.java @@ -0,0 +1,74 @@ +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 "add": + System.out.println("Addition..."); + core.makeAdd(Arrays.copyOfRange(args, 1, args.length)); + break; + case "commit": + System.out.println("Commiting..."); + core.makeCommit(args[1]); + System.out.println("Commit made at revision " + core.getCurrentRevision()); + break; + case "checkout": + try { + revision = Integer.parseInt(args[1]); + System.out.println("Check out to revision " + revision); + core.makeCheckout(revision); + } catch (NumberFormatException e) { + core.makeCheckout(Arrays.copyOfRange(args, 2, args.length)); + } + 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; + case "rm": + System.out.println("Removing..."); + core.makeRM(Arrays.copyOfRange(args, 1, args.length)); + break; + case "status": + core.findRepInformation(); + System.out.println("Status:"); + System.out.println("Deleted files:\n________________"); + for (String fname : core.getDeletedFiles()) { + System.out.println(fname); + } + System.out.println("Changed files:\n________________"); + for (String fname : core.getChangedFiles()) { + System.out.println(fname); + } + System.out.println("Untracked files:\n________________"); + for (String fname : core.getUntrackedFiles()) { + System.out.println(fname); + } + 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_2/src/main/java/hw_git/GitCore.java b/hw_git_2/src/main/java/hw_git/GitCore.java new file mode 100644 index 0000000..9213b75 --- /dev/null +++ b/hw_git_2/src/main/java/hw_git/GitCore.java @@ -0,0 +1,329 @@ +package hw_git; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.io.FileUtils; + +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"; + private final String stageFolder = ".stageData"; + + private int getLastItem(ArrayList list) { + return list.get(list.size() - 1); + } + 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(); + omapper.writeValue(informPath.resolve(infoFileName).toFile(), inform); + } + + void makeInit() throws JsonGenerationException, JsonMappingException, IOException, UnversionedException { + try { + findRepInformation(); + } catch (UnversionedException e) { + informPath = Paths.get(""); + inform = new RepInformation(); + updateRepInformation(); + System.out.println("Ok."); + } + } + + void increaseRevisionNumber() { + inform.revision++; + } + + private Path getPathRealRelative(String filename) { + return informPath.relativize(Paths.get(filename)); + } + + private Path getStoragePath(Path keyPath, int revision) { + return informPath.resolve(storageFolder) + .resolve(keyPath.getParent()) + .resolve(keyPath.getFileName().toString() + "r" + revision); + } + + private void addFile(Path filepath) throws IOException { + int revision = inform.revision; + Path keyPath = filepath.subpath(1, filepath.getNameCount()); + Path storage = getStoragePath(keyPath, revision); + storage.getParent().toFile().mkdirs(); + Files.copy(filepath, storage); + String keyName = keyPath.toString(); + ArrayList revisions = inform.allFiles.get(keyName); + if (revisions == null) { + revisions = new ArrayList<>(); + inform.allFiles.put(keyName, revisions); + } + revisions.add(revision); + } + + void makeAdd(String[] filenames) throws IOException, UnversionedException { + findRepInformation(); + for (String fname : filenames) { + Path orig = getPathRealRelative(fname); + Path dest = informPath.resolve(stageFolder).resolve(orig); + //Files.copy(orig, dest); + FileUtils.copyFile(orig.toFile(), dest.toFile()); + } + } + + void makeCommit(String message) throws IOException, UnversionedException { + findRepInformation(); + increaseRevisionNumber(); + inform.commitMessages.add(message); + inform.timestamps.add(new Timestamp(System.currentTimeMillis())); + Files.walkFileTree( + informPath.resolve(stageFolder), + new FileVisitor() { + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + // TODO Auto-generated method stub + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + addFile(file); + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + // TODO Auto-generated method stub + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + // TODO Auto-generated method stub + return FileVisitResult.CONTINUE; + } + }); + updateRepInformation(); + } + + private void deleteVersionedFiles(File root) { + if (root.isFile()) { + String key = informPath.toAbsolutePath() + .relativize(Paths.get(root.getAbsolutePath())) + .toString(); + + if (inform.allFiles.containsKey(key)) { + 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); + FileUtils.copyFile( + informPath.resolve(getStoragePath(Paths.get(ent.getKey()), revNumber)).toFile(), + informPath.resolve(ent.getKey()).toFile()); + } + } + } + + void makeCheckout(int revision) throws IOException, UnversionedException { + findRepInformation(); + deleteVersionedFiles(informPath.toAbsolutePath().toFile()); + restoreVersionedFiles(revision); + } + + void makeCheckout(String[] files) throws JsonParseException, JsonMappingException, IOException, UnversionedException { + findRepInformation(); + for (String fname : files) { + String key = getPathRealRelative(fname).toString(); + System.out.println("checkout key: " + key); + FileUtils.copyFile(getStoragePath(getPathRealRelative(fname), + getLastItem(inform.allFiles.get(key))).toFile(), + informPath.resolve(key).toFile()); + } + } + + 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); + } + + List getDeletedFiles() throws JsonParseException, JsonMappingException, IOException, UnversionedException { + ArrayList result = new ArrayList<>(); + findRepInformation(); + + for (String keypath : inform.allFiles.keySet()) { + if (!Files.exists(informPath.resolve(keypath))) { + result.add(Paths.get("").relativize(Paths.get(keypath)).toString()); + } + } + + return result; + } + + List getChangedFiles() throws JsonParseException, JsonMappingException, IOException, UnversionedException { + ArrayList result = new ArrayList<>(); + findRepInformation(); + + for (String keypath : inform.allFiles.keySet()) { + if (Files.exists(informPath.resolve(keypath)) + && !FileUtils.contentEquals( + informPath.resolve(keypath).toFile(), + getStoragePath(Paths.get(keypath), getLastItem(inform.allFiles.get(keypath)) + ).toFile() + )) { + result.add(Paths.get("").relativize(Paths.get(keypath)).toString()); + } + } + + return result; + } + + List getUntrackedFiles() throws IOException { + ArrayList result = new ArrayList<>(); + Files.walkFileTree(Paths.get(""), new FileVisitor() { + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + // TODO Auto-generated method stub + return dir.equals(Paths.get(stageFolder)) || dir.equals(Paths.get(storageFolder)) + ? FileVisitResult.SKIP_SUBTREE : FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + // TODO Auto-generated method stub + if (!inform.allFiles.containsKey(informPath.relativize(file).toString())) { + result.add(file.toString()); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + // TODO Auto-generated method stub + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + // TODO Auto-generated method stub + return FileVisitResult.CONTINUE; + } + }); + + return result; + } + + int getCurrentRevision() { + return inform.revision; + } + + + private void removeFromRep(String filename) throws IOException { + Path keypath = getPathRealRelative(filename); + System.out.println("keypath: " + keypath); + for (int revision : inform.allFiles.get(keypath.toString())) { + Files.delete(getStoragePath(keypath, revision)); + } + Files.deleteIfExists(informPath.resolve(stageFolder).resolve(keypath)); + inform.allFiles.remove(keypath.toString()); + } + + void makeRM(String[] files) throws IOException, UnversionedException { + findRepInformation(); + for (String filename : files) { + removeFromRep(filename); + } + updateRepInformation(); + } +} diff --git a/hw_git_2/src/main/java/hw_git/RepInformation.java b/hw_git_2/src/main/java/hw_git/RepInformation.java new file mode 100644 index 0000000..89c288e --- /dev/null +++ b/hw_git_2/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_2/src/main/java/hw_git/UnversionedException.java b/hw_git_2/src/main/java/hw_git/UnversionedException.java new file mode 100644 index 0000000..33f9cf9 --- /dev/null +++ b/hw_git_2/src/main/java/hw_git/UnversionedException.java @@ -0,0 +1,5 @@ +package hw_git; + +public class UnversionedException extends Exception { + +} diff --git a/hw_git_2/src/test/java/hw_git/AppTest.java b/hw_git_2/src/test/java/hw_git/AppTest.java new file mode 100644 index 0000000..b031b58 --- /dev/null +++ b/hw_git_2/src/test/java/hw_git/AppTest.java @@ -0,0 +1,176 @@ +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.Arrays; +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[] {"add", "testdir/file.txt"}); + GitCli.main(new String[] {"commit", "message 1"}); + GitCli.main(new String[] {"add", "testdir/dir1/file_d1.txt"}); + GitCli.main(new String[] {"commit", "message 2"}); + + 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 testCheckoutFiles() throws JsonGenerationException, JsonMappingException, IOException { + GitCli.main(new String[] {"init"}); + try (PrintWriter out = new PrintWriter(new File("testdir/file.txt"))) { + out.println("text 1"); + } + GitCli.main(new String[] {"add", "testdir/file.txt"}); + GitCli.main(new String[] {"commit", "message 1"}); + try (PrintWriter out = new PrintWriter(new File("testdir/file.txt"))) { + out.println("text 2"); + } + + GitCli.main(new String[] {"checkout", "--", "testdir/file.txt"}); + try (Scanner in = new Scanner(new File("testdir/file.txt"))) { + assertTrue(in.nextLine().equals("text 1")); + } + } + + @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[] {"add", "testdir/file.txt"}); + GitCli.main(new String[] {"commit", "message 1"}); + GitCli.main(new String[] {"add", "testdir/dir1/file_d1.txt"}); + GitCli.main(new String[] {"commit", "message 2"}); + + try (PrintWriter out = new PrintWriter(new File("testdir/file.txt"))) { + out.print("commit 3 content"); + } + + GitCli.main(new String[] {"add", "testdir/file.txt"}); + GitCli.main(new String[] {"commit", "message 3"}); + + 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[] {"add", "testdir/file.txt"}); + GitCli.main(new String[] {"commit", "message 1"}); + String s1; + try (Scanner in = new Scanner(new File(".myGitData"))) { + s1 = in.nextLine(); + } + GitCli.main(new String[] {"add", "testdir/dir1/file_d1.txt"}); + GitCli.main(new String[] {"commit", "message 2"}); + GitCli.main(new String[] {"reset", "1"}); + String s2; + try (Scanner in = new Scanner(new File(".myGitData"))) { + s2 = in.nextLine(); + } + assertEquals(s1, s2); + } + + @Test + public void testRM() throws JsonGenerationException, JsonMappingException, IOException { + GitCli.main(new String[] {"init"}); + GitCli.main(new String[] {"add", "testdir/file.txt"}); + GitCli.main(new String[] {"commit", "message 1"}); + Files.delete(Paths.get("testdir/file.txt")); + GitCli.main(new String[] {"rm", "testdir/file.txt"}); + GitCli.main(new String[] {"checkout", "1"}); + assertFalse(Files.exists(Paths.get("testdir/file.txt"))); + } + + @Test + public void testStatus() throws JsonGenerationException, JsonMappingException, IOException, UnversionedException { + GitCli.main(new String[] {"init"}); + GitCli.main(new String[] {"add", "testdir/file.txt"}); + GitCli.main(new String[] {"commit", "message 1"}); + + GitCore core = new GitCore(); + core.findRepInformation(); + + assertEquals(core.getChangedFiles(), Arrays.asList()); + //assertEquals(core.getUntrackedFiles(), Arrays.asList("testdir/dir1/file_d1.txt")); + assertEquals(core.getDeletedFiles(), Arrays.asList()); + + try (PrintWriter out = new PrintWriter(new File("testdir/file.txt"))) { + out.print("commit 3 content"); + } + + assertEquals(core.getChangedFiles(), Arrays.asList("testdir/file.txt")); + + Files.delete(Paths.get("testdir/file.txt")); + + assertEquals(core.getDeletedFiles(), Arrays.asList("testdir/file.txt")); + } +}