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);
+ }
+}