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