From 8be31ceaab1e4eab43b48d4e2a5167b406143b35 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 23 Mar 2017 05:39:20 +0300 Subject: [PATCH 01/30] Now it works --- build.gradle | 22 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 52928 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 169 +++++++ gradlew.bat | 84 ++++ settings.gradle | 2 + src/main/java/META-INF/MANIFEST.MF | 3 + .../spbau/lobanov/liteVCS/ConsoleWorker.java | 122 +++++ .../lobanov/liteVCS/logic/Algorithms.java | 205 ++++++++ .../lobanov/liteVCS/logic/DataManager.java | 472 ++++++++++++++++++ .../spbau/lobanov/liteVCS/logic/LiteVCS.java | 460 +++++++++++++++++ .../logic/VersionControlSystemException.java | 13 + .../lobanov/liteVCS/primitives/Branch.java | 26 + .../lobanov/liteVCS/primitives/Commit.java | 39 ++ .../liteVCS/primitives/ContentDescriptor.java | 52 ++ .../lobanov/liteVCS/primitives/Header.java | 25 + .../liteVCS/primitives/VersionNode.java | 43 ++ .../lobanov/liteVCS/logic/AlgorithmsTest.java | 81 +++ .../liteVCS/logic/DataManagerTest.java | 7 + .../lobanov/liteVCS/logic/FakeManager.java | 31 ++ .../lobanov/liteVCS/logic/LiteVCSTest.java | 7 + .../lobanov/liteVCS/logic/PathManager.java | 26 + 22 files changed, 1895 insertions(+) create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/META-INF/MANIFEST.MF create mode 100644 src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java create mode 100644 src/main/java/ru/spbau/lobanov/liteVCS/logic/Algorithms.java create mode 100644 src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java create mode 100644 src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java create mode 100644 src/main/java/ru/spbau/lobanov/liteVCS/logic/VersionControlSystemException.java create mode 100644 src/main/java/ru/spbau/lobanov/liteVCS/primitives/Branch.java create mode 100644 src/main/java/ru/spbau/lobanov/liteVCS/primitives/Commit.java create mode 100644 src/main/java/ru/spbau/lobanov/liteVCS/primitives/ContentDescriptor.java create mode 100644 src/main/java/ru/spbau/lobanov/liteVCS/primitives/Header.java create mode 100644 src/main/java/ru/spbau/lobanov/liteVCS/primitives/VersionNode.java create mode 100644 src/test/java/ru/spbau/lobanov/liteVCS/logic/AlgorithmsTest.java create mode 100644 src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java create mode 100644 src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java create mode 100644 src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java create mode 100644 src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..e77c40d --- /dev/null +++ b/build.gradle @@ -0,0 +1,22 @@ +group 'my_projects' +version '1.0-SNAPSHOT' + +apply plugin: 'java' + +jar { + manifest { + def manif = "${projectDir}/src/main/java/META-INF/MANIFEST.MF" + from file(manif) + } + baseName = "liteVCS" +} + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.11' + // https://mvnrepository.com/artifact/com.google.guava/guava + compile group: 'com.google.guava', name: 'guava', version: '21.0' +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..6ffa237849ef3607e39c3b334a92a65367962071 GIT binary patch literal 52928 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..55e76f1 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'my_svn' + diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000..c8bb453 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: ru.spbau.lobanov.liteVCS.ConsoleWorker + diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java new file mode 100644 index 0000000..745e960 --- /dev/null +++ b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java @@ -0,0 +1,122 @@ +package ru.spbau.lobanov.liteVCS; + +import ru.spbau.lobanov.liteVCS.logic.VersionControlSystemException; +import ru.spbau.lobanov.liteVCS.logic.LiteVCS; +import ru.spbau.lobanov.liteVCS.logic.LiteVCS.ConflictMergeException; +import ru.spbau.lobanov.liteVCS.primitives.Commit; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.List; + +public class ConsoleWorker { + + /** + * Sugar to simplify checking count of arguments + * + * @param size expected arguments number + * @param args array of arguments + * + * @throws WrongNumberArgumentsException if length of array isn't equal to expected value + */ + private static void checkArguments(int size, String[] args) throws WrongNumberArgumentsException { + if (args.length != size) { + throw new WrongNumberArgumentsException("Expected " + size + " arguments"); + } + } + + private static void execute(String command, String[] args) throws VersionControlSystemException, + WrongNumberArgumentsException, IOException, UnknownCommandException { + String path = Paths.get(System.getProperty("user.dir")).toString(); + switch (command) { + case "init": + checkArguments(0, args); + LiteVCS.init(path); + break; + case "add": + checkArguments(1, args); + LiteVCS.add(path, args[0]); + break; + case "commit": + checkArguments(1, args); + LiteVCS.commit(path, args[0]); + break; + case "checkout": + checkArguments(1, args); + LiteVCS.checkout(path, args[0]); + break; + case "clear": + checkArguments(0, args); + LiteVCS.clear(path); + break; + case "create_branch": + checkArguments(1, args); + LiteVCS.createBranch(path, args[0]); + break; + case "remove_branch": + checkArguments(1, args); + LiteVCS.removeBranch(path, args[0]); + break; + case "switch_branch": + checkArguments(1, args); + LiteVCS.switchBranch(path, args[0]); + break; + case "merge_branch": + checkArguments(2, args); + LiteVCS.mergeBranch(path, args[0], args[1]); + break; + case "reset": + checkArguments(0, args); + LiteVCS.reset(path); + break; + case "uninstall": + checkArguments(0, args); + LiteVCS.uninstall(path); + break; + case "logs": + checkArguments(0, args); + printLogs(LiteVCS.log(path, "100")); + default: + throw new UnknownCommandException(command); + } + } + + public static void main(String[] args) { + if (args.length == 0) { + System.out.println("Error: empty command"); + return; + } + String[] functionArgs = new String[args.length - 1]; + System.arraycopy(args, 1, functionArgs, 0, functionArgs.length); + try { + execute(args[0], functionArgs); + } catch (ConflictMergeException e) { + System.out.println("Conflicts were found:"); + for (String path : e.getConflicts()) { + System.out.println(" " + path); + } + } catch (Throwable e) { + System.out.println("Error: " + e.getMessage()); + } + } + + private static void printLogs(List commits) { + System.out.println("Local history:"); + for (int i = commits.size() - 1; i >= 0; i--) { + Commit commit = commits.get(i); + System.out.println(" " + commit.getCommitMessage() + commit.getContentDescriptorID()); + } + } + + public static class WrongNumberArgumentsException extends Exception { + WrongNumberArgumentsException(String message) { + super(message); + } + } + + public static class UnknownCommandException extends Exception { + UnknownCommandException(String message) { + super(message); + } + } +} diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/Algorithms.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/Algorithms.java new file mode 100644 index 0000000..c75e80b --- /dev/null +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/Algorithms.java @@ -0,0 +1,205 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import com.sun.istack.internal.NotNull; +import ru.spbau.lobanov.liteVCS.logic.DataManager.BrokenFileException; +import ru.spbau.lobanov.liteVCS.logic.DataManager.LostFileException; +import ru.spbau.lobanov.liteVCS.primitives.VersionNode; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +class Algorithms { + + private static final int CACHE_SIZE_LIMIT = 16; + private static final int LONGEST_JUMP_LENGTH = 1 << (CACHE_SIZE_LIMIT - 1); + + /** + * Special method which create root VersionNode. + * It's important, because we have to know its own + * id before create instance + * + * @param expectedID id of parent of VersionNode, which will be created + * @param commitID id of commit, associated with VersionNode which will be created + * @return created versionNode + */ + @NotNull + static VersionNode createRootNode(@NotNull String expectedID, @NotNull String commitID) { + String[] parentsTable = new String[CACHE_SIZE_LIMIT]; + Arrays.fill(parentsTable, expectedID); + return new VersionNode(commitID, 1, parentsTable); + } + + /** + * Special method which create VersionNode. + * It's important, because we have to calculate table of parents, + * before create VersionNode + * + * @param commitID id of commit, associated with VersionNode which will be created + * @param parentID id of parent of VersionNode, which will be created + * @param manager dataManager, which provides access to the files + * @return created versionNode + * @throws LostFileException if file contained one of interesting Node was corrupted + * @throws BrokenFileException if file contained one of interesting Node was not found + */ + @NotNull + static VersionNode createVersionNode(@NotNull String commitID, @NotNull String parentID, + @NotNull DataManager manager) throws LostFileException, BrokenFileException { + String[] parentsTable = new String[CACHE_SIZE_LIMIT]; + parentsTable[0] = parentID; + for (int i = 1; i < CACHE_SIZE_LIMIT; i++) { + parentsTable[i] = getParent(parentsTable[i - 1], i - 1, manager); + } + VersionNode parent = manager.getVersionNode(parentID); + return new VersionNode(commitID, parent.getDeepLevel() + 1, parentsTable); + } + + /** + * Method which go up from node and write down its parents + * + * @param versionID id of interesting node + * @param limit limit of recorded ancestors + * @param dataManager dataManager, which provides access to the files + * @return List of closed parents + * @throws LostFileException if file contained one of interesting Node was corrupted + * @throws BrokenFileException if file contained one of interesting Node was not found + */ + @NotNull + static List getAllParents(@NotNull String versionID, int limit, + @NotNull DataManager dataManager) throws BrokenFileException, LostFileException { + String currentVersionID = versionID; + ArrayList list = new ArrayList<>(); + for (int i = 0; i < limit; i++) { + VersionNode currentNode = dataManager.getVersionNode(currentVersionID); + list.add(currentNode); + if (currentVersionID.equals(currentNode.getParentsTable()[0])) { + break; + } + currentVersionID = currentNode.getParentsTable()[0]; + } + return list; + } + + /** + * Method which find LCA of given nodes by algorithm + * of binary expansion. In usual case, if deep of nodes + * lower than 2^CACHE_SIZE_LIMIT it work O(log(deep)) times + * + * @param nodeID1 id of one interesting node + * @param nodeID2 id of another interesting node + * @param manager dataManager, which provides access to the files + * @return id of LCA-node + * @throws LostFileException if file contained one of interesting Node was corrupted + * @throws BrokenFileException if file contained one of interesting Node was not found + */ + @NotNull + static String findLowestCommonAncestor(@NotNull String nodeID1, @NotNull String nodeID2, + @NotNull DataManager manager) throws LostFileException, BrokenFileException { + String currentNodeID1 = nodeID1; + String currentNodeID2 = nodeID2; + int deepDelta = getDeepLevel(nodeID1, manager) - getDeepLevel(nodeID2, manager); + if (deepDelta > 0) { + currentNodeID1 = jump(currentNodeID1, deepDelta, manager); + } else if (deepDelta < 0) { + currentNodeID2 = jump(currentNodeID2, -deepDelta, manager); + } + if (currentNodeID1.equals(currentNodeID2)) { + return currentNodeID1; + } + while (!isAncestorsEqual(currentNodeID1, currentNodeID2, CACHE_SIZE_LIMIT - 1, manager)) { + currentNodeID1 = getParent(currentNodeID1, CACHE_SIZE_LIMIT - 1, manager); + currentNodeID2 = getParent(currentNodeID2, CACHE_SIZE_LIMIT - 1, manager); + } + for (int level = CACHE_SIZE_LIMIT - 1; level >= 0; level--) { + if (!isAncestorsEqual(currentNodeID1, currentNodeID2, level, manager)) { + currentNodeID1 = getParent(currentNodeID1, level, manager); + currentNodeID2 = getParent(currentNodeID2, level, manager); + } + } + return getParent(currentNodeID1, 0, manager); + } + + /** + * Check if parents, remote from interesting nodes on + * 2^level distance, are the same + * Expected, that nodes has the same deep level + * + * @param nodeID1 id of one interesting node + * @param nodeID2 id of another interesting node + * @param level number, which define distance between nodes + * and their interesting parents + * @param manager dataManager, which provides access to the files + * @return true, if parents are the same + * @throws BrokenFileException if file contained one of interesting Node was corrupted + * @throws LostFileException if file contained one of interesting Node was not found + */ + private static boolean isAncestorsEqual(@NotNull String nodeID1, @NotNull String nodeID2, int level, + @NotNull DataManager manager) throws BrokenFileException, LostFileException { + String ancestorID1 = manager.getVersionNode(nodeID1).getParentsTable()[level]; + String ancestorID2 = manager.getVersionNode(nodeID2).getParentsTable()[level]; + return ancestorID1.equals(ancestorID2); + } + + /** + * Method which find parent of interesting node, located at length levels + * upper or the root of tree if such parent doesn't exist + * It works O(length / (2^CACHE_SIZE_LIMIT) + log(length)) time. + * + * @param nodeID id of interesting node + * @param length expected distance between interesting node and its interesting parent + * @param manager dataManager, which provides access to the files + * @return id of interesting parent + * @throws BrokenFileException if file contained interesting Node was corrupted + * @throws LostFileException if file contained interesting Node was not found + */ + @NotNull + private static String jump(@NotNull String nodeID, int length, + @NotNull DataManager manager) throws LostFileException, BrokenFileException { + String currentNodeID = nodeID; + int residualLength = length; + while (residualLength > LONGEST_JUMP_LENGTH) { + currentNodeID = getParent(currentNodeID, CACHE_SIZE_LIMIT - 1, manager); + residualLength -= LONGEST_JUMP_LENGTH; + } + for (int i = CACHE_SIZE_LIMIT - 1; i >= 0; i--) { + int jumpLength = 1 << i; + if (residualLength >= jumpLength) { + currentNodeID = getParent(currentNodeID, i, manager); + residualLength -= jumpLength; + } + } + return currentNodeID; + } + + /** + * Sugar to simplify getting parents of VersionNode by id + * Its return parent of interesting node, located at (2^level) levels + * upper or the root of tree if such parent doesn't exist + * + * @param nodeID id of interesting node + * @param level distance between interesting node and returned parent will be 2^level + * @param manager dataManager, which provides access to the files + * @return id of interesting parent + * @throws BrokenFileException if file contained interesting Node was corrupted + * @throws LostFileException if file contained interesting Node was not found + */ + @NotNull + private static String getParent(@NotNull String nodeID, int level, + @NotNull DataManager manager) throws BrokenFileException, LostFileException { + return manager.getVersionNode(nodeID).getParentsTable()[level]; + } + + /** + * Sugar to simplify getting the deep level by VersionNode id + * + * @param nodeID id of interesting node + * @param manager dataManager, which provides access to the files + * @return deep level of VersionNode associated this nodeID + * @throws BrokenFileException if file contained interesting Node was corrupted + * @throws LostFileException if file contained interesting Node was not found + */ + private static int getDeepLevel(@NotNull String nodeID, @NotNull DataManager manager) throws BrokenFileException, + LostFileException { + return manager.getVersionNode(nodeID).getDeepLevel(); + } +} diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java new file mode 100644 index 0000000..bff989a --- /dev/null +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java @@ -0,0 +1,472 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import com.google.common.hash.Hashing; +import com.google.common.io.Files; +import com.sun.istack.internal.NotNull; +import ru.spbau.lobanov.liteVCS.primitives.*; + +import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map.Entry; +import java.util.Random; + +/** + * This class is adapter between logic part of LiteVCS + * and data storage + */ +class DataManager { + + private static final String ROOT_DIRECTORY_NAME = ".liteVCS"; + private static final String PATH_TO_VERSIONS_FILES = ROOT_DIRECTORY_NAME + "\\versions"; + private static final String PATH_TO_CONTENT_DESCRIPTORS_FILES = ROOT_DIRECTORY_NAME + "\\descriptors"; + private static final String PATH_TO_COMMITS_FILES = ROOT_DIRECTORY_NAME + "\\commits"; + private static final String PATH_TO_SAVED_FILES = ROOT_DIRECTORY_NAME + "\\files"; + private static final String PATH_TO_BRANCHES = ROOT_DIRECTORY_NAME + "\\branches"; + private static final String ROOT_VERSION_NODE_ID = "root"; + private static final String PATH_TO_STAGE = ROOT_DIRECTORY_NAME + "\\stage.lVCS"; + private static final String PATH_TO_HEADER = ROOT_DIRECTORY_NAME + "\\header.lVCS"; + + private final String workingDirectory; + + DataManager(@NotNull String workingDirectory) { + this.workingDirectory = workingDirectory; + } + + /** + * Method which init repository: create folders and initial files + * + * @throws RecreatingRepositoryException if repository was already created + */ + void initRepository() throws RecreatingRepositoryException { + File rootDirectory = Paths.get(workingDirectory, DataManager.ROOT_DIRECTORY_NAME).toFile(); + if (rootDirectory.exists()) { + throw new RecreatingRepositoryException("Repository was already created here:" + workingDirectory); + } + boolean success = Paths.get(workingDirectory, DataManager.PATH_TO_COMMITS_FILES).toFile().mkdirs() && + Paths.get(workingDirectory, DataManager.PATH_TO_CONTENT_DESCRIPTORS_FILES).toFile().mkdirs() && + Paths.get(workingDirectory, DataManager.PATH_TO_SAVED_FILES).toFile().mkdirs() && + Paths.get(workingDirectory, DataManager.PATH_TO_VERSIONS_FILES).toFile().mkdirs() && + Paths.get(workingDirectory, DataManager.PATH_TO_BRANCHES).toFile().mkdirs(); + if (!success) { + throw new Error("Unexpected error during directories creating"); + } + try { + String initialDescriptorID = addContentDescriptor(ContentDescriptor.EMPTY); + String initialCommitID = addCommit(new Commit(initialDescriptorID, "Initial commit", + System.currentTimeMillis(), "lVCS")); + VersionNode start = Algorithms.createRootNode(DataManager.ROOT_VERSION_NODE_ID, initialCommitID); + writeObject(Paths.get(workingDirectory, PATH_TO_VERSIONS_FILES, ROOT_VERSION_NODE_ID), start); + Branch master = new Branch(DataManager.ROOT_VERSION_NODE_ID, "master"); + addBranch(master); + Header header = new Header("Unknown", master.getName()); + putHeader(header); + putStage(ContentDescriptor.EMPTY); + } catch (RepositoryNotInitializedException e) { + throw new Error("Unexpected error during repository initialization"); + } + } + + /** + * Method allow to find and load VersionNode by id + * + * @param id identifier of VersionNode + * @return loaded VersionNode + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + */ + @NotNull + VersionNode getVersionNode(@NotNull String id) throws LostFileException, BrokenFileException { + return readObject(Paths.get(workingDirectory, PATH_TO_VERSIONS_FILES, id), VersionNode.class); + } + + /** + * Method allow to save VersionNode in file system + * + * @param versionNode object to save + * @return generated id, by which you can get that object late + * @throws RepositoryNotInitializedException if file creating failed + */ + @NotNull + String addVersionNode(@NotNull VersionNode versionNode) throws RepositoryNotInitializedException { + String id = createUniqueID(PATH_TO_VERSIONS_FILES); + writeObject(Paths.get(workingDirectory, PATH_TO_VERSIONS_FILES, id), versionNode); + return id; + } + + /** + * Method allow to find and load ContentDescriptor by id + * + * @param id identifier of ContentDescriptor + * @return loaded ContentDescriptor + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + */ + @NotNull + ContentDescriptor getContentDescriptor(@NotNull String id) throws LostFileException, BrokenFileException { + return readObject(Paths.get(workingDirectory, PATH_TO_CONTENT_DESCRIPTORS_FILES, id), ContentDescriptor.class); + } + + /** + * Method allow to save ContentDescriptor in file system + * + * @param contentDescriptor object to save + * @return generated id, by which you can get that object late + * @throws RepositoryNotInitializedException if file creating failed + */ + @NotNull + String addContentDescriptor(@NotNull ContentDescriptor contentDescriptor) throws RepositoryNotInitializedException { + String id = createUniqueID(PATH_TO_CONTENT_DESCRIPTORS_FILES); + writeObject(Paths.get(workingDirectory, PATH_TO_CONTENT_DESCRIPTORS_FILES, id), contentDescriptor); + return id; + } + + /** + * Method allow to find and load Commit by id + * + * @param id identifier of Commit + * @return loaded Commit + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + */ + @NotNull + Commit getCommit(@NotNull String id) throws LostFileException, BrokenFileException { + return readObject(Paths.get(workingDirectory, PATH_TO_COMMITS_FILES, id), Commit.class); + } + + /** + * Method allow to save Commit in file system + * + * @param commit object to save + * @return generated id, by which you can get that object late + * @throws RepositoryNotInitializedException if file creating failed + */ + @NotNull + String addCommit(@NotNull Commit commit) throws RepositoryNotInitializedException { + String id = createUniqueID(PATH_TO_COMMITS_FILES); + writeObject(Paths.get(workingDirectory, PATH_TO_COMMITS_FILES, id), commit); + return id; + } + + /** + * Method allow to find copy of files by id + * + * @param id identifier of interesting file + * @return link to saved copy + * @throws LostFileException if file contained one of interesting object was corrupted + */ + @NotNull + File getFile(@NotNull String id) throws LostFileException { + File file = Paths.get(workingDirectory, PATH_TO_SAVED_FILES, id).toFile(); + if (!file.exists()) { + throw new LostFileException("File wasn't found:" + file.getName(), null, file); + } + return file; + } + + /** + * Method allow to save copy of file in file system + * + * @param file object to save + * @return generated id, by which you can get that object late + * @throws RepositoryNotInitializedException if file creating failed + */ + @NotNull + String addFile(@NotNull File file) throws RepositoryNotInitializedException { + String hash; + try { + hash = Files.hash(file, Hashing.sha256()).toString() + ".sc"; + } catch (IOException e) { + throw new Error("Unknown exception during hash creating"); + } + File savedCopy = Paths.get(workingDirectory, PATH_TO_SAVED_FILES, hash).toFile(); + try { + if (savedCopy.createNewFile()) { + Files.copy(file, savedCopy); + } + } catch (IOException e) { + throw new RepositoryNotInitializedException("Directory wasn't found:" + PATH_TO_SAVED_FILES, e); + } + return hash; + } + + /** + * Method allow to save branch in file system + * This method doesn't return id because branches are identified by name + * + * @param branch object to save + * @throws RepositoryNotInitializedException if file creating failed + */ + @NotNull + void addBranch(@NotNull Branch branch) throws RepositoryNotInitializedException { + writeObject(Paths.get(workingDirectory, PATH_TO_BRANCHES, branch.getName()), branch); + } + + /** + * Method allow to find and load Branch by name + * + * @param name name of interesting Branch + * @return loaded Branch + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + */ + @NotNull + Branch getBranch(@NotNull String name) throws LostFileException, BrokenFileException { + return readObject(Paths.get(workingDirectory, PATH_TO_BRANCHES, name), Branch.class); + } + + /** + * Method allow to check if Branch with such name exist + * + * @param name name of interesting Branch + * @return true if such branch exist, false otherwise + */ + boolean hasBranch(@NotNull String name) { + return Paths.get(workingDirectory, PATH_TO_BRANCHES, name).toFile().exists(); + } + + /** + * Method allow to check if Branch with such name exist + * + * @param name name of interesting Branch + * @throws LostFileException if such branch wasn't found + */ + void removeBranch(@NotNull String name) throws LostFileException { + File file = Paths.get(workingDirectory, PATH_TO_BRANCHES, name).toFile(); + if (!file.delete()) { + throw new LostFileException("File wasn't found", null, file); + } + } + + /** + * Method allow to load Header of repository + * + * @return loaded Header + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + */ + @NotNull + Header getHeader() throws LostFileException, BrokenFileException { + return readObject(Paths.get(workingDirectory, PATH_TO_HEADER), Header.class); + } + + /** + * Method allow to save Header of repository + * + * @param header object to save + * @throws RepositoryNotInitializedException if file saving failed + */ + void putHeader(@NotNull Header header) throws RepositoryNotInitializedException { + writeObject(Paths.get(workingDirectory, PATH_TO_HEADER), header); + } + + /** + * Method allow to load Stage descriptor + * + * @return stage ContentDescriptor + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + */ + @NotNull + ContentDescriptor getStage() throws LostFileException, BrokenFileException { + return readObject(Paths.get(workingDirectory, PATH_TO_STAGE), ContentDescriptor.class); + } + + /** + * Method allow to save Stage descriptor + * + * @param stage object to save + * @throws RepositoryNotInitializedException if file saving failed + */ + void putStage(@NotNull ContentDescriptor stage) throws RepositoryNotInitializedException { + writeObject(Paths.get(workingDirectory, PATH_TO_STAGE), stage); + } + + /** + * Method allow to create all files and folder in which + * they are contained, specified by ContentDescriptor + * + * @param descriptorID identifier of ContentDescriptor + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws IOException if file creating failed because of File System + */ + void loadFiles(@NotNull String descriptorID) throws BrokenFileException, LostFileException, IOException { + ContentDescriptor descriptor = getContentDescriptor(descriptorID); + for (Entry pair : descriptor.getFiles().entrySet()) { + File savedCopy = getFile(pair.getValue()); + File targetFile = Paths.get(workingDirectory, pair.getKey()).toFile(); + Files.createParentDirs(targetFile); + Files.touch(targetFile); + Files.copy(savedCopy, targetFile); + } + } + + /** + * Method allow to clear work space be removing all files except ROOT_DIRECTORY + */ + void clearWorkingCopy() { + Path rootDirectory = Paths.get(workingDirectory, ROOT_DIRECTORY_NAME); + File[] files = Paths.get(workingDirectory).toFile().listFiles(); + if (files == null) { + throw new Error("Cant clear Directory:"); + } + for (File file : files) { + if (!file.toPath().startsWith(rootDirectory)) { + if (file.isDirectory()) { + clearDirectory(file); + } + if (!file.delete()) { + throw new Error("Cant remove file: " + file.getAbsolutePath()); + } + } + } + } + + /** + * Method allow to clear work space be removing all files including ROOT_DIRECTORY + * + * @throws RepositoryNotInitializedException if there was no repository + */ + void uninstallRepository() throws RepositoryNotInitializedException { + File rootDirectory = Paths.get(workingDirectory, ROOT_DIRECTORY_NAME).toFile(); + if (!rootDirectory.exists()) { + throw new RepositoryNotInitializedException("Repository wasn't found"); + } + clearDirectory(rootDirectory); + if (!rootDirectory.delete()) { + throw new Error(); + } + + } + + /** + * Method remove all files from directory + * + * @param directory directory to remove + */ + private void clearDirectory(@NotNull File directory) { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + clearDirectory(file); + } + if (!file.delete()) { + throw new Error("Cant remove file"); + } + } + } + } + + /** + * Method allow you to create such names, + * that files with that names doesn't exist in interesting directory + * + * @param directory tracked folder + * @return such name, that there is no file with that name in that folder + */ + @NotNull + private String createUniqueID(@NotNull String directory) { + Random random = new Random(); + String id; + do { + id = "" + random.nextLong(); + } while (Paths.get(workingDirectory, directory, id).toFile().exists()); + return id; + } + + /** + * Method allow you to load every Serializable object + * from specified files + * + * @param path file, included important information + * @return loaded object + */ + @NotNull + private static T readObject(@NotNull Path path, @NotNull Class expectedType) throws BrokenFileException, + LostFileException { + Object o; + try (FileInputStream fileInputStream = new FileInputStream(path.toFile()); + ObjectInputStream inputStream = new ObjectInputStream(fileInputStream)) { + o = inputStream.readObject(); + } catch (ClassNotFoundException | InvalidClassException | OptionalDataException e) { + throw new BrokenFileException("Cant load data from file: " + path.toString(), e, path.toFile()); + } catch (FileNotFoundException e) { + throw new LostFileException("Found reference to non-existent file: " + path.toString(), e, path.toFile()); + } catch (IOException e) { + throw new Error("Unknown error occurred while reading the file: " + path.toString(), e); + } + if (!expectedType.isInstance(o)) { + throw new BrokenFileException("Unexpected data was found in file: " + path.toString(), path.toFile()); + } + return expectedType.cast(o); + } + + /** + * Method allow you to save every Serializable object + * in specified files + * + * @param path path to target file + * @param object object to save + */ + private static void writeObject(@NotNull Path path, @NotNull Object object) + throws RepositoryNotInitializedException { + try (FileOutputStream fileOutputStream = new FileOutputStream(path.toFile()); + ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream)) { + outputStream.writeObject(object); + } catch (FileNotFoundException e) { + throw new RepositoryNotInitializedException("Repository wasn't initialized or was corrupted"); + } catch (IOException e) { + throw new Error("Unknown error occurred while writing the file: " + path.toString(), e); + } + } + + public static class LostFileException extends VersionControlSystemException { + private final File expectedFile; + + LostFileException(String message, Throwable cause, File expectedFile) { + super(message, cause); + this.expectedFile = expectedFile; + } + + public File getExpectedFile() { + return expectedFile; + } + } + + public static class BrokenFileException extends VersionControlSystemException { + private final File brokenFile; + + BrokenFileException(String message, Throwable cause, File brokenFile) { + super(message, cause); + this.brokenFile = brokenFile; + } + + BrokenFileException(String message, File brokenFile) { + super(message); + this.brokenFile = brokenFile; + } + + public File getBrokenFile() { + return brokenFile; + } + } + + + public static class RepositoryNotInitializedException extends VersionControlSystemException { + RepositoryNotInitializedException(String message) { + super(message); + } + + RepositoryNotInitializedException(String message, Throwable cause) { + super(message, cause); + } + } + + public static class RecreatingRepositoryException extends VersionControlSystemException { + RecreatingRepositoryException(String message) { + super(message); + } + } +} diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java new file mode 100644 index 0000000..3dcea02 --- /dev/null +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java @@ -0,0 +1,460 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import com.sun.istack.internal.NotNull; +import com.sun.istack.internal.Nullable; +import ru.spbau.lobanov.liteVCS.logic.DataManager.BrokenFileException; +import ru.spbau.lobanov.liteVCS.logic.DataManager.LostFileException; +import ru.spbau.lobanov.liteVCS.logic.DataManager.RecreatingRepositoryException; +import ru.spbau.lobanov.liteVCS.logic.DataManager.RepositoryNotInitializedException; +import ru.spbau.lobanov.liteVCS.primitives.*; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +/** + * Special class which provides all the main + * functionality of library by static methods + */ +public class LiteVCS { + + private LiteVCS() { + } + + + public static void init(@NotNull String path) throws RecreatingRepositoryException, + RepositoryNotInitializedException { + new DataManager(path).initRepository(); + } + + /** + * Method which add file to stage (list of files to commit). + * + * @param path path to working directory + * @throws RecreatingRepositoryException if repository was already created + */ + public static void add(@NotNull String path, @NotNull String fileName) + throws VersionControlSystemException { + File file = Paths.get(path, fileName).toFile(); + DataManager dataManager = new DataManager(path); + ContentDescriptor stage = dataManager.getStage(); + ContentDescriptor updatedStage; + if (!file.isFile()) { + throw new Error("Possible to add files only"); + } + String fileID = dataManager.addFile(file); + String relatedPath = Paths.get(path).relativize(file.toPath()).toString(); + updatedStage = ContentDescriptor.builder() + .addAllFiles(stage) + .addFile(relatedPath, fileID) + .build(); + dataManager.putStage(updatedStage); + } + + /** + * Method which get list of changed from stage, + * create Commit and add it to head of current branch + * + * @param path path to working directory + * @param message text which explain changes which was made in this commit + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + */ + public static void commit(@NotNull String path, @NotNull String message) throws BrokenFileException, + LostFileException, RepositoryNotInitializedException { + DataManager dataManager = new DataManager(path); + Header header = dataManager.getHeader(); + Branch currentBranch = dataManager.getBranch(header.getCurrentBranchName()); + VersionNode currentVersion = dataManager.getVersionNode(currentBranch.getVersionNodeID()); + Commit lastCommit = dataManager.getCommit(currentVersion.getCommitID()); + ContentDescriptor currentDescriptor = dataManager.getContentDescriptor(lastCommit.getContentDescriptorID()); + ContentDescriptor stage = dataManager.getStage(); + + ContentDescriptor updatedDescriptor = ContentDescriptor.builder() + .addAllFiles(currentDescriptor) + .addAllFiles(stage) + .build(); + String descriptorID = dataManager.addContentDescriptor(updatedDescriptor); + Commit newCommit = new Commit(descriptorID, message, System.currentTimeMillis(), header.getAuthor()); + String commitID = dataManager.addCommit(newCommit); + VersionNode newVersion = Algorithms.createVersionNode(commitID, currentBranch.getVersionNodeID(), dataManager); + String versionID = dataManager.addVersionNode(newVersion); + Branch updatedBranch = new Branch(versionID, currentBranch.getName()); + dataManager.addBranch(updatedBranch); + dataManager.putStage(ContentDescriptor.EMPTY); + } + + /** + * Method which return list of node's parents sorted by increasing distance + * + * @param path path to working directory + * @param lengthLimit limit size of returned List + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + */ + @NotNull + public static List log(@NotNull String path, @NotNull String lengthLimit) + throws BrokenFileException, LostFileException { + int limit = Integer.MAX_VALUE; + if (lengthLimit != null) { + try { + limit = Integer.parseInt(lengthLimit); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cant parse length limit", e); + } + } + DataManager dataManager = new DataManager(path); + Header header = dataManager.getHeader(); + Branch currentBranch = dataManager.getBranch(header.getCurrentBranchName()); + List versions = Algorithms.getAllParents(currentBranch.getVersionNodeID(), limit, dataManager); + List commits = new ArrayList<>(); + for (VersionNode versionNode : versions) { + commits.add(dataManager.getCommit(versionNode.getCommitID())); + } + return commits; + } + + /** + * Method create new Branch which current version + * equal to version of active branch + * + * @param path path to working directory + * @param branchName name of new branch + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws ConflictNameException if branch with equal name is already exist + */ + public static void createBranch(@NotNull String path, @NotNull String branchName) + throws BrokenFileException, LostFileException, ConflictNameException, RepositoryNotInitializedException { + DataManager dataManager = new DataManager(path); + Header header = dataManager.getHeader(); + Branch currentBranch = dataManager.getBranch(header.getCurrentBranchName()); + if (dataManager.hasBranch(branchName)) { + throw new ConflictNameException("Branch with the same name is already exist"); + } + Branch newBranch = new Branch(currentBranch.getVersionNodeID(), branchName); + dataManager.addBranch(newBranch); + } + + /** + * Method removes record about given branch + * + * @param branchName name of branch to remove + * @param path path to working directory + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws RemoveActiveBranchException if user try to remove active(current) branch + * @throws UnknownBranchException if branch with the same name wasn't found + */ + public static void removeBranch(@NotNull String path, @NotNull String branchName) + throws BrokenFileException, LostFileException, + RemoveActiveBranchException, UnknownBranchException { + DataManager dataManager = new DataManager(path); + Header header = dataManager.getHeader(); + if (header.getCurrentBranchName().equals(branchName)) { + throw new RemoveActiveBranchException("Cant remove active branch"); + } + if (!dataManager.hasBranch(branchName)) { + throw new UnknownBranchException("Branch to remove doesn't exist"); + } + dataManager.removeBranch(branchName); + } + + /** + * Method merges given branch into current branch + * Conflicts can be resolved only if there is no file, + * which was changed in both branches since theirs last common version + * Stage have to be empty before merging + * + * @param branchName name of branch to remove + * @param message text ehich will be used in commit + * @param path path to working directory + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws IllegalBranchToMergeException if it's logically impossible to merge thar branches + * @throws UnknownBranchException if branch with the same name wasn't found + * @throws UncommittedChangesException if stage not empty + * @throws ConflictMergeException if unresolvable conflict was found + */ + public static void mergeBranch(@NotNull String path, @NotNull String branchName, @NotNull String message) + throws BrokenFileException, LostFileException, IllegalBranchToMergeException, UnknownBranchException, + UncommittedChangesException, ConflictMergeException, RepositoryNotInitializedException { + DataManager dataManager = new DataManager(path); + Header header = dataManager.getHeader(); + if (header.getCurrentBranchName().equals(branchName)) { + throw new IllegalBranchToMergeException("Cant merge branch with itself"); + } + if (!dataManager.hasBranch(branchName)) { + throw new UnknownBranchException("Branch to merge doesn't exist"); + } + if (!dataManager.getStage().getFiles().isEmpty()) { + throw new UncommittedChangesException("Commit changes before merge"); + } + String activeVersionID = dataManager.getBranch(header.getCurrentBranchName()).getVersionNodeID(); + String sideVersionID = dataManager.getBranch(branchName).getVersionNodeID(); + String lcaVersionID = Algorithms.findLowestCommonAncestor(activeVersionID, sideVersionID, dataManager); + ContentDescriptor activeContent = toContentDescriptor(activeVersionID, dataManager); + ContentDescriptor sideContent = toContentDescriptor(sideVersionID, dataManager); + ContentDescriptor lcaContent = toContentDescriptor(lcaVersionID, dataManager); + List conflicts = checkConflicts(activeContent, sideContent, lcaContent); + if (!conflicts.isEmpty()) { + throw new ConflictMergeException("Conflicts was found", conflicts); + } + ContentDescriptor mergedDescriptor = mergeContent(activeContent, sideContent, lcaContent); + String descriptorID = dataManager.addContentDescriptor(mergedDescriptor); + String commitMessage = Objects.nonNull(message) ? message : "Merge branch " + branchName; + Commit commit = new Commit(descriptorID, commitMessage, System.currentTimeMillis(), header.getAuthor()); + String commitID = dataManager.addCommit(commit); + VersionNode versionNode = Algorithms.createVersionNode(commitID, activeVersionID, dataManager); + String versionNodeID = dataManager.addVersionNode(versionNode); + Branch updatedBranch = new Branch(versionNodeID, header.getCurrentBranchName()); + dataManager.addBranch(updatedBranch); + dataManager.addBranch(new Branch(descriptorID, header.getCurrentBranchName())); + } + + /** + * Method which merge ContentDescriptors. + * Result of merging - ContentDescriptor contained changes from both descriptors + */ + @NotNull + private static ContentDescriptor mergeContent(@NotNull ContentDescriptor descriptor1, + @NotNull ContentDescriptor descriptor2, + @NotNull ContentDescriptor lcaDescriptor) { + ContentDescriptor.Builder builder = ContentDescriptor.builder(); + Map files1 = descriptor1.getFiles(); + Map files2 = descriptor2.getFiles(); + Map lcaFiles = lcaDescriptor.getFiles(); + + Set allFiles = new HashSet<>(); + allFiles.addAll(files1.keySet()); + allFiles.addAll(files2.keySet()); + + for (String path : allFiles) { + String resultVersion = mergeFileVersions(files1.get(path), files2.get(path), lcaFiles.get(path)); + if (resultVersion != null) { + builder.addFile(path, resultVersion); + } + } + return builder.build(); + } + + /** + * Check if these versions cause unresolvable conflict + */ + private static boolean hasConflict(@Nullable String version1, @Nullable String version2, + @Nullable String lcaVersion) { + return !Objects.equals(version1, version2) && !Objects.equals(version1, lcaVersion) + && !Objects.equals(version2, lcaVersion); + } + + /** + * Choose result version of file, based on it's version in different VersionNodes and in their LCA + */ + @Nullable + private static String mergeFileVersions(@Nullable String version1, @Nullable String version2, + @Nullable String lcaVersion) { + return Objects.equals(version1, lcaVersion) ? version2 : version1; + } + + /** + * Method which finds all unresolvable conflicts between different versions + * + * @param descriptor1 version of file in one Node + * @param descriptor2 version of file in another Node + * @param lcaDescriptor version of file in their LCA + */ + @NotNull + private static List checkConflicts(@NotNull ContentDescriptor descriptor1, + @NotNull ContentDescriptor descriptor2, + @NotNull ContentDescriptor lcaDescriptor) { + List conflicts = new ArrayList<>(); + + Map files1 = descriptor1.getFiles(); + Map files2 = descriptor2.getFiles(); + Map lcaFiles = lcaDescriptor.getFiles(); + + Set allFiles = new HashSet<>(); + allFiles.addAll(files1.keySet()); + allFiles.addAll(files2.keySet()); + + Set folders = new HashSet<>(); + + for (String path : allFiles) { + if (hasConflict(files1.get(path), files2.get(path), lcaFiles.get(path))) { + conflicts.add(path); + continue; + } + if (mergeFileVersions(files1.get(path), files2.get(path), lcaFiles.get(path)) == null) { + continue; + } + Path folder = Paths.get(path).getParent(); + while (folder != null) { + folders.add(folder.toString()); + } + } + + for (String path : allFiles) { + if (!hasConflict(files1.get(path), files2.get(path), lcaFiles.get(path)) + && mergeFileVersions(files1.get(path), files2.get(path), lcaFiles.get(path)) != null + && folders.contains(path)) { + conflicts.add(path); + } + } + + return conflicts; + } + + /** + * Sugar to simplify getting ContentDescriptor from ID of VersionNode + * + * @param versionID id of VersionNode + * @param dataManager dataManager, which provides access to the files + * @return loaded ContentDescriptor + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + */ + @NotNull + private static ContentDescriptor toContentDescriptor(@NotNull String versionID, @NotNull DataManager dataManager) + throws BrokenFileException, LostFileException { + VersionNode versionNode = dataManager.getVersionNode(versionID); + Commit commit = dataManager.getCommit(versionNode.getCommitID()); + return dataManager.getContentDescriptor(commit.getContentDescriptorID()); + } + + /** + * @param path path to working directory + * @param branchName name of interesting branch + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws SwitchOnCurrentBranchException if you try to switch on your current branch + * @throws UncommittedChangesException if stage isn't empty before change branch + * @throws UnknownBranchException if such branch wasn't found + * @throws IOException in case of some IO problems + */ + public static void switchBranch(@NotNull String path, @NotNull String branchName) + throws BrokenFileException, LostFileException, SwitchOnCurrentBranchException, + UncommittedChangesException, UnknownBranchException, IOException, RepositoryNotInitializedException { + DataManager dataManager = new DataManager(path); + Header header = dataManager.getHeader(); + if (header.getCurrentBranchName().equals(branchName)) { + throw new SwitchOnCurrentBranchException("This branch is already chosen"); + } + ContentDescriptor contentDescriptor = dataManager.getStage(); + if (!contentDescriptor.getFiles().isEmpty()) { + throw new UncommittedChangesException("Commit changes before switch branch"); + } + if (!dataManager.hasBranch(branchName)) { + throw new UnknownBranchException("Branch wasn't found"); + } + Branch branch = dataManager.getBranch(branchName); + VersionNode versionNode = dataManager.getVersionNode(branch.getVersionNodeID()); + Commit lastCommit = dataManager.getCommit(versionNode.getCommitID()); + checkout(path, lastCommit.getContentDescriptorID()); + Header updatedHeader = new Header(header.getAuthor(), branchName); + dataManager.putHeader(updatedHeader); + } + + /** + * Restore saved copies from current Branch + * + * @param path path to working directory + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws IOException in case of some IO problems + */ + public static void reset(@NotNull String path) throws BrokenFileException, LostFileException, IOException, + RepositoryNotInitializedException { + DataManager dataManager = new DataManager(path); + dataManager.putStage(ContentDescriptor.EMPTY); + Header header = dataManager.getHeader(); + Branch branch = dataManager.getBranch(header.getCurrentBranchName()); + VersionNode versionNode = dataManager.getVersionNode(branch.getVersionNodeID()); + Commit lastCommit = dataManager.getCommit(versionNode.getCommitID()); + checkout(path, lastCommit.getContentDescriptorID()); + } + + /** + * Restore saved file from given ContentDescriptor + * + * @param path path to working directory + * @param descriptorID id of interesting description + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws IOException in case of some IO problems + */ + public static void checkout(@NotNull String path, @NotNull String descriptorID) + throws IOException, BrokenFileException, LostFileException { + DataManager dataManager = new DataManager(path); + dataManager.clearWorkingCopy(); + dataManager.loadFiles(descriptorID); + } + + /** + * This method remove all file from working directory + * + * @param path path to working directory + */ + public static void clear(@NotNull String path) { + new DataManager(path).clearWorkingCopy(); + } + + /** + * @param path path to working directory + * @throws RepositoryNotInitializedException if there is nothing to delete + */ + public static void uninstall(@NotNull String path) throws RepositoryNotInitializedException { + new DataManager(path).uninstallRepository(); + } + + public static class SwitchOnCurrentBranchException extends VersionControlSystemException { + SwitchOnCurrentBranchException(String message) { + super(message); + } + } + + public static class UncommittedChangesException extends VersionControlSystemException { + UncommittedChangesException(String message) { + super(message); + } + } + + public static class UnknownBranchException extends VersionControlSystemException { + UnknownBranchException(String message) { + super(message); + } + } + + public static class ConflictNameException extends VersionControlSystemException { + ConflictNameException(String message) { + super(message); + } + } + + public static class RemoveActiveBranchException extends VersionControlSystemException { + RemoveActiveBranchException(String message) { + super(message); + } + } + + public static class ConflictMergeException extends VersionControlSystemException { + + private final List conflicts; + + ConflictMergeException(String message, List conflicts) { + super(message); + this.conflicts = Collections.unmodifiableList(conflicts); + } + + public List getConflicts() { + return conflicts; + } + } + + public static class IllegalBranchToMergeException extends VersionControlSystemException { + IllegalBranchToMergeException(String message) { + super(message); + } + } +} diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/VersionControlSystemException.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/VersionControlSystemException.java new file mode 100644 index 0000000..16c9540 --- /dev/null +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/VersionControlSystemException.java @@ -0,0 +1,13 @@ +package ru.spbau.lobanov.liteVCS.logic; + +public class VersionControlSystemException extends Exception { + + VersionControlSystemException(String message) { + super(message); + } + + VersionControlSystemException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Branch.java b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Branch.java new file mode 100644 index 0000000..567caa2 --- /dev/null +++ b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Branch.java @@ -0,0 +1,26 @@ +package ru.spbau.lobanov.liteVCS.primitives; + +import java.io.Serializable; + +/** + * Special class which represent branch in liteVCS + * It has name and link to VersionNode + * Its easy to save branch in file + */ +public class Branch implements Serializable { + private final String versionNodeID; + private final String name; + + public Branch(String versionNodeID, String name) { + this.versionNodeID = versionNodeID; + this.name = name; + } + + public String getVersionNodeID() { + return versionNodeID; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Commit.java b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Commit.java new file mode 100644 index 0000000..1cbf237 --- /dev/null +++ b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Commit.java @@ -0,0 +1,39 @@ +package ru.spbau.lobanov.liteVCS.primitives; + +import java.io.Serializable; + +/** + * Special class which represent commits in liteVCS + * It contains message, time of creating, its authors + * name and link to ContentDescriptor which contains information about files + */ +public class Commit implements Serializable { + + private final String contentDescriptorID; + private final String commitMessage; + private final long time; + private final String author; + + public Commit(String contentDescriptorID, String commitMessage, long time, String author) { + this.contentDescriptorID = contentDescriptorID; + this.commitMessage = commitMessage; + this.time = time; + this.author = author; + } + + public String getContentDescriptorID() { + return contentDescriptorID; + } + + public String getCommitMessage() { + return commitMessage; + } + + public long getTime() { + return time; + } + + public String getAuthor() { + return author; + } +} diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/ContentDescriptor.java b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/ContentDescriptor.java new file mode 100644 index 0000000..adf1043 --- /dev/null +++ b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/ContentDescriptor.java @@ -0,0 +1,52 @@ +package ru.spbau.lobanov.liteVCS.primitives; + +import java.io.Serializable; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +/** + * Special class which contains information about + * files and theirs actual versions + * Its possible to save it in file + */ +public class ContentDescriptor implements Serializable { + public static final ContentDescriptor EMPTY = new ContentDescriptor(new TreeMap<>()); + + // Map name of file (relative path from working directory) + // to ID of its actual version + private final TreeMap files; + + private ContentDescriptor(TreeMap files) { + this.files = files; + } + + public Map getFiles() { + return Collections.unmodifiableMap(files); + } + + public static Builder builder() { + return new Builder(); + } + + /** + * This class should help to create ContentDescriptors + */ + public static class Builder { + private final TreeMap files = new TreeMap<>(); + + public Builder addFile(String relativePath, String fileID) { + files.put(relativePath, fileID); + return this; + } + + public Builder addAllFiles(ContentDescriptor contentDescriptor) { + files.putAll(contentDescriptor.files); + return this; + } + + public ContentDescriptor build() { + return new ContentDescriptor(files); + } + } +} diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Header.java b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Header.java new file mode 100644 index 0000000..cfe8b1f --- /dev/null +++ b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Header.java @@ -0,0 +1,25 @@ +package ru.spbau.lobanov.liteVCS.primitives; + +import java.io.Serializable; + +/** + * Special class to save general information in file + * It knows name of current branch and name of author + */ +public class Header implements Serializable { + private final String author; + private final String currentBranchName; + + public Header(String author, String currentBranchName) { + this.author = author; + this.currentBranchName = currentBranchName; + } + + public String getAuthor() { + return author; + } + + public String getCurrentBranchName() { + return currentBranchName; + } +} diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/VersionNode.java b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/VersionNode.java new file mode 100644 index 0000000..a29326f --- /dev/null +++ b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/VersionNode.java @@ -0,0 +1,43 @@ +package ru.spbau.lobanov.liteVCS.primitives; + +import java.io.Serializable; + +/** + * Special class which allow to build tree of versions + * Has link to associated Commit + * Commit and VersionNode are separated to facilitate the version tree + */ +public class VersionNode implements Serializable { + + private final String commitID; + + // data for method of binary expansion + private final int deepLevel; + private final String[] parentsTable; + + public VersionNode(String commitID, int deepLevel, String[] parentsTable) { + this.commitID = commitID; + this.deepLevel = deepLevel; + this.parentsTable = parentsTable; + } + + public String getCommitID() { + return commitID; + } + + public int getDeepLevel() { + return deepLevel; + } + + /** + * This method should be used for method + * of binary expansion + * Returned table will contains parents, located at + * 1, 2, 4, 8... levels upper + * + * @return table of parents + */ + public String[] getParentsTable() { + return parentsTable; + } +} diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/AlgorithmsTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/AlgorithmsTest.java new file mode 100644 index 0000000..00347c4 --- /dev/null +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/AlgorithmsTest.java @@ -0,0 +1,81 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import org.junit.Test; +import ru.spbau.lobanov.liteVCS.primitives.Commit; +import ru.spbau.lobanov.liteVCS.primitives.VersionNode; + +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; +import static ru.spbau.lobanov.liteVCS.logic.Algorithms.*; + +public class AlgorithmsTest { + // its longer then length of max jump in binary expansion + private static final PathManager pathExample = new PathManager(100_000); + + // just usual tree + private static final FakeManager treeExample = new FakeManager(); + + static { + try { + treeExample.addVersionNode((String) null); // 0 + treeExample.addVersionNode("0"); // 1 + treeExample.addVersionNode("0"); // 2 + treeExample.addVersionNode("2"); // 3 + treeExample.addVersionNode("3"); // 4 + treeExample.addVersionNode("3"); // 5 + treeExample.addVersionNode("4"); // 6 + treeExample.addVersionNode("1"); // 7 + treeExample.addVersionNode("7"); // 8 + treeExample.addVersionNode("1"); // 9 + treeExample.addVersionNode("2"); // 10 + treeExample.addVersionNode("5"); // 11 + treeExample.addVersionNode("4"); // 12 + treeExample.addVersionNode("6"); // 13 + treeExample.addVersionNode("8"); // 14 + treeExample.addVersionNode("4"); // 15 + } catch (Exception ignored) {} + } + + @Test + public void smallDataLCATest() throws Exception { + assertEquals("0", findLowestCommonAncestor("14", "13", treeExample)); + assertEquals("4", findLowestCommonAncestor("12", "13", treeExample)); + assertEquals("3", findLowestCommonAncestor("13", "3", treeExample)); + assertEquals("0", findLowestCommonAncestor("1", "15", treeExample)); + assertEquals("0", findLowestCommonAncestor("0", "15", treeExample)); + assertEquals("2", findLowestCommonAncestor("10", "11", treeExample)); + assertEquals("1", findLowestCommonAncestor("14", "9", treeExample)); + assertEquals("8", findLowestCommonAncestor("8", "8", treeExample)); + } + + @Test + public void bigDataLCATest() throws Exception { + assertEquals("0", findLowestCommonAncestor("0", "99999", pathExample)); + assertEquals("7", findLowestCommonAncestor("7", "99435", pathExample)); + assertEquals("34", findLowestCommonAncestor("34", "9934", pathExample)); + assertEquals("53", findLowestCommonAncestor("53", "99977", pathExample)); + } + + @Test + public void allParentsSmallDataTest() throws DataManager.LostFileException, DataManager.BrokenFileException { + List parents = getAllParents("13", 7, treeExample); + VersionNode[] answer = {treeExample.getVersionNode("13"), treeExample.getVersionNode("6"), + treeExample.getVersionNode("4"), treeExample.getVersionNode("3"), + treeExample.getVersionNode("2"), treeExample.getVersionNode("0")}; + assertEquals(answer.length, parents.size()); + for (int i = 0; i < answer.length; i++) { + assertSame(answer[i], parents.get(i)); + } + + List parents2 = getAllParents("14", 4, treeExample); + VersionNode[] answer2 = {treeExample.getVersionNode("14"), treeExample.getVersionNode("8"), + treeExample.getVersionNode("7"), treeExample.getVersionNode("1")}; + assertEquals(answer2.length, parents2.size()); + for (int i = 0; i < answer2.length; i++) { + assertSame(answer2[i], parents2.get(i)); + } + } +} \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java new file mode 100644 index 0000000..64b0635 --- /dev/null +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java @@ -0,0 +1,7 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import static org.junit.Assert.*; + +public class DataManagerTest { + +} \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java new file mode 100644 index 0000000..5a9fced --- /dev/null +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java @@ -0,0 +1,31 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import ru.spbau.lobanov.liteVCS.primitives.VersionNode; + +import java.util.HashMap; + +/** + * Created by Артём on 23.03.2017. + */ +public class FakeManager extends DataManager { + private final HashMap map = new HashMap<>(); + + FakeManager() { + super(""); + } + + public void addVersionNode(String parentID) throws BrokenFileException, LostFileException { + VersionNode versionNode; + if (parentID == null) { + versionNode = Algorithms.createRootNode("0", ""); + } else { + versionNode = Algorithms.createVersionNode("", parentID, this); + } + map.put(map.size() + "", versionNode); + } + + @Override + public VersionNode getVersionNode(String id) { + return map.get(id); + } +} diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java new file mode 100644 index 0000000..0b7ef1a --- /dev/null +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java @@ -0,0 +1,7 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import static org.junit.Assert.*; + +public class LiteVCSTest { + +} \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java new file mode 100644 index 0000000..7d03daa --- /dev/null +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java @@ -0,0 +1,26 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import ru.spbau.lobanov.liteVCS.primitives.VersionNode; + +import java.util.ArrayList; + +public class PathManager extends DataManager { + + private final ArrayList versionNodes = new ArrayList<>(); + + PathManager(int size) { + super(""); + try { + versionNodes.add(Algorithms.createRootNode("0", "")); + for (int i = 1; i < size; i++) { + versionNodes.add(Algorithms.createVersionNode("", (i - 1) + "", this)); + } + } catch (Exception ignored){} + } + + @Override + public VersionNode getVersionNode(String id) { + int i = Integer.parseInt(id); + return versionNodes.get(i); + } +} From 50e3372484b3b6eb5b6deba38c53f1e6c26d314c Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 23 Mar 2017 05:45:21 +0300 Subject: [PATCH 02/30] Annotations --- .travis.yml | 4 ++++ build.gradle | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2b2dfcb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: java + +jdk: + - oraclejdk8 \ No newline at end of file diff --git a/build.gradle b/build.gradle index e77c40d..2f059fc 100644 --- a/build.gradle +++ b/build.gradle @@ -19,4 +19,7 @@ dependencies { testCompile group: 'junit', name: 'junit', version: '4.11' // https://mvnrepository.com/artifact/com.google.guava/guava compile group: 'com.google.guava', name: 'guava', version: '21.0' + + // https://mvnrepository.com/artifact/com.sun.istack/maven-istack-commons-plugin + compile group: 'com.sun.istack', name: 'maven-istack-commons-plugin', version: '2.11' } From 8328931a71e41f1a42f74dee49aef86a0d0595e0 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 23 Mar 2017 05:52:27 +0300 Subject: [PATCH 03/30] gradlew permissions --- gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From f68cd2970de152f1a5d156cdbe1cc11524350352 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 23 Mar 2017 06:09:43 +0300 Subject: [PATCH 04/30] JetBrains Annotations --- build.gradle | 3 +-- .../lobanov/liteVCS/logic/Algorithms.java | 2 +- .../lobanov/liteVCS/logic/DataManager.java | 2 +- .../spbau/lobanov/liteVCS/logic/LiteVCS.java | 4 ++-- .../lobanov/liteVCS/primitives/Branch.java | 6 +++++- .../lobanov/liteVCS/primitives/Commit.java | 9 +++++++-- .../liteVCS/primitives/ContentDescriptor.java | 19 +++++++++++++------ .../lobanov/liteVCS/primitives/Header.java | 6 +++++- .../liteVCS/primitives/VersionNode.java | 6 +++++- 9 files changed, 40 insertions(+), 17 deletions(-) diff --git a/build.gradle b/build.gradle index 2f059fc..017778d 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,5 @@ dependencies { // https://mvnrepository.com/artifact/com.google.guava/guava compile group: 'com.google.guava', name: 'guava', version: '21.0' - // https://mvnrepository.com/artifact/com.sun.istack/maven-istack-commons-plugin - compile group: 'com.sun.istack', name: 'maven-istack-commons-plugin', version: '2.11' + compile group: 'org.jetbrains', name: 'annotations', version: '13.0' } diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/Algorithms.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/Algorithms.java index c75e80b..4ac9fc0 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/Algorithms.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/Algorithms.java @@ -1,6 +1,6 @@ package ru.spbau.lobanov.liteVCS.logic; +import org.jetbrains.annotations.NotNull; -import com.sun.istack.internal.NotNull; import ru.spbau.lobanov.liteVCS.logic.DataManager.BrokenFileException; import ru.spbau.lobanov.liteVCS.logic.DataManager.LostFileException; import ru.spbau.lobanov.liteVCS.primitives.VersionNode; diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java index bff989a..f389eea 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java @@ -2,7 +2,7 @@ import com.google.common.hash.Hashing; import com.google.common.io.Files; -import com.sun.istack.internal.NotNull; +import org.jetbrains.annotations.NotNull; import ru.spbau.lobanov.liteVCS.primitives.*; import java.io.*; diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java index 3dcea02..4b5b802 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java @@ -1,7 +1,7 @@ package ru.spbau.lobanov.liteVCS.logic; -import com.sun.istack.internal.NotNull; -import com.sun.istack.internal.Nullable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import ru.spbau.lobanov.liteVCS.logic.DataManager.BrokenFileException; import ru.spbau.lobanov.liteVCS.logic.DataManager.LostFileException; import ru.spbau.lobanov.liteVCS.logic.DataManager.RecreatingRepositoryException; diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Branch.java b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Branch.java index 567caa2..f777a39 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Branch.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Branch.java @@ -1,5 +1,7 @@ package ru.spbau.lobanov.liteVCS.primitives; +import org.jetbrains.annotations.NotNull; + import java.io.Serializable; /** @@ -11,15 +13,17 @@ public class Branch implements Serializable { private final String versionNodeID; private final String name; - public Branch(String versionNodeID, String name) { + public Branch(@NotNull String versionNodeID, @NotNull String name) { this.versionNodeID = versionNodeID; this.name = name; } + @NotNull public String getVersionNodeID() { return versionNodeID; } + @NotNull public String getName() { return name; } diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Commit.java b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Commit.java index 1cbf237..e6425d8 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Commit.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Commit.java @@ -1,5 +1,7 @@ package ru.spbau.lobanov.liteVCS.primitives; +import org.jetbrains.annotations.NotNull; + import java.io.Serializable; /** @@ -14,17 +16,19 @@ public class Commit implements Serializable { private final long time; private final String author; - public Commit(String contentDescriptorID, String commitMessage, long time, String author) { - this.contentDescriptorID = contentDescriptorID; + public Commit(@NotNull String descriptorID, @NotNull String commitMessage, long time, @NotNull String author) { + this.contentDescriptorID = descriptorID; this.commitMessage = commitMessage; this.time = time; this.author = author; } + @NotNull public String getContentDescriptorID() { return contentDescriptorID; } + @NotNull public String getCommitMessage() { return commitMessage; } @@ -33,6 +37,7 @@ public long getTime() { return time; } + @NotNull public String getAuthor() { return author; } diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/ContentDescriptor.java b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/ContentDescriptor.java index adf1043..5a89d93 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/ContentDescriptor.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/ContentDescriptor.java @@ -1,9 +1,11 @@ package ru.spbau.lobanov.liteVCS.primitives; +import org.jetbrains.annotations.NotNull; + import java.io.Serializable; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; /** * Special class which contains information about @@ -17,14 +19,16 @@ public class ContentDescriptor implements Serializable { // to ID of its actual version private final TreeMap files; - private ContentDescriptor(TreeMap files) { + private ContentDescriptor(@NotNull TreeMap files) { this.files = files; } + @NotNull public Map getFiles() { return Collections.unmodifiableMap(files); } + @NotNull public static Builder builder() { return new Builder(); } @@ -35,16 +39,19 @@ public static Builder builder() { public static class Builder { private final TreeMap files = new TreeMap<>(); - public Builder addFile(String relativePath, String fileID) { + @NotNull + public Builder addFile(@NotNull String relativePath, @NotNull String fileID) { files.put(relativePath, fileID); return this; } - public Builder addAllFiles(ContentDescriptor contentDescriptor) { + @NotNull + public Builder addAllFiles(@NotNull ContentDescriptor contentDescriptor) { files.putAll(contentDescriptor.files); return this; } + @NotNull public ContentDescriptor build() { return new ContentDescriptor(files); } diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Header.java b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Header.java index cfe8b1f..9674940 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Header.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Header.java @@ -1,5 +1,7 @@ package ru.spbau.lobanov.liteVCS.primitives; +import org.jetbrains.annotations.NotNull; + import java.io.Serializable; /** @@ -10,15 +12,17 @@ public class Header implements Serializable { private final String author; private final String currentBranchName; - public Header(String author, String currentBranchName) { + public Header(@NotNull String author, @NotNull String currentBranchName) { this.author = author; this.currentBranchName = currentBranchName; } + @NotNull public String getAuthor() { return author; } + @NotNull public String getCurrentBranchName() { return currentBranchName; } diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/VersionNode.java b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/VersionNode.java index a29326f..4829071 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/VersionNode.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/VersionNode.java @@ -1,5 +1,7 @@ package ru.spbau.lobanov.liteVCS.primitives; +import org.jetbrains.annotations.NotNull; + import java.io.Serializable; /** @@ -15,12 +17,13 @@ public class VersionNode implements Serializable { private final int deepLevel; private final String[] parentsTable; - public VersionNode(String commitID, int deepLevel, String[] parentsTable) { + public VersionNode(@NotNull String commitID, int deepLevel, @NotNull String[] parentsTable) { this.commitID = commitID; this.deepLevel = deepLevel; this.parentsTable = parentsTable; } + @NotNull public String getCommitID() { return commitID; } @@ -37,6 +40,7 @@ public int getDeepLevel() { * * @return table of parents */ + @NotNull public String[] getParentsTable() { return parentsTable; } From 5e67e2d17950c80ec45cebeef16079563978f94f Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 23 Mar 2017 08:07:39 +0300 Subject: [PATCH 05/30] Bug fixes & testing --- .../spbau/lobanov/liteVCS/ConsoleWorker.java | 8 +- .../spbau/lobanov/liteVCS/logic/LiteVCS.java | 5 +- .../lobanov/liteVCS/logic/LiteVCSTest.java | 102 ++++++++++++++++++ 3 files changed, 111 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java index 745e960..1eee87e 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java @@ -28,6 +28,7 @@ private static void checkArguments(int size, String[] args) throws WrongNumberAr private static void execute(String command, String[] args) throws VersionControlSystemException, WrongNumberArgumentsException, IOException, UnknownCommandException { String path = Paths.get(System.getProperty("user.dir")).toString(); +// String path = "C:\\workspace\\liteVCS"; switch (command) { case "init": checkArguments(0, args); @@ -76,12 +77,14 @@ private static void execute(String command, String[] args) throws VersionControl case "logs": checkArguments(0, args); printLogs(LiteVCS.log(path, "100")); + break; default: throw new UnknownCommandException(command); } } public static void main(String[] args) { +// args = new String[]{"merge_branch", "master", "commit4444+"}; if (args.length == 0) { System.out.println("Error: empty command"); return; @@ -100,11 +103,14 @@ public static void main(String[] args) { } } + private static final String COMMIT_PLACE_HOLDER = "\"%s\" by %s (node: %s)"; + private static void printLogs(List commits) { System.out.println("Local history:"); for (int i = commits.size() - 1; i >= 0; i--) { Commit commit = commits.get(i); - System.out.println(" " + commit.getCommitMessage() + commit.getContentDescriptorID()); + System.out.printf(COMMIT_PLACE_HOLDER, commit.getCommitMessage(), commit.getAuthor(), + commit.getContentDescriptorID()); } } diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java index 4b5b802..ad57bc7 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java @@ -204,14 +204,12 @@ public static void mergeBranch(@NotNull String path, @NotNull String branchName, } ContentDescriptor mergedDescriptor = mergeContent(activeContent, sideContent, lcaContent); String descriptorID = dataManager.addContentDescriptor(mergedDescriptor); - String commitMessage = Objects.nonNull(message) ? message : "Merge branch " + branchName; - Commit commit = new Commit(descriptorID, commitMessage, System.currentTimeMillis(), header.getAuthor()); + Commit commit = new Commit(descriptorID, message, System.currentTimeMillis(), header.getAuthor()); String commitID = dataManager.addCommit(commit); VersionNode versionNode = Algorithms.createVersionNode(commitID, activeVersionID, dataManager); String versionNodeID = dataManager.addVersionNode(versionNode); Branch updatedBranch = new Branch(versionNodeID, header.getCurrentBranchName()); dataManager.addBranch(updatedBranch); - dataManager.addBranch(new Branch(descriptorID, header.getCurrentBranchName())); } /** @@ -292,6 +290,7 @@ private static List checkConflicts(@NotNull ContentDescriptor descriptor Path folder = Paths.get(path).getParent(); while (folder != null) { folders.add(folder.toString()); + folder = folder.getParent(); } } diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java index 0b7ef1a..b4ee9d3 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java @@ -1,7 +1,109 @@ package ru.spbau.lobanov.liteVCS.logic; +import com.google.common.hash.Hashing; +import com.google.common.io.Files; +import org.junit.Test; + +import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.DoubleSummaryStatistics; +import java.util.Random; + import static org.junit.Assert.*; public class LiteVCSTest { + private static final String workspace = "test_workspace"; + + private static String randomWorkspace() { + Path path; + do { + path = Paths.get(workspace, Double.toHexString(Math.random())); + } while (path.toFile().exists()); + return path.toString(); + } + + private static void fillFile(String fullPath, String name) throws FileNotFoundException { + PrintWriter out = new PrintWriter(Paths.get(fullPath, name).toFile()); + out.write(String.valueOf(Math.random())); + out.write(String.valueOf(Math.random())); + out.close(); + } + + private static String hash(String fullPath, String name) { + String hash; + try { + hash = Files.hash(Paths.get(fullPath, name).toFile(), Hashing.sha256()).toString(); + } catch (IOException e) { + throw new Error("Unknown exception during hash creating"); + } + return hash; + } + + + @Test + public void addBranchSwitchChangeSwitchMerge() throws Exception { + String folder = randomWorkspace(); + String fullPath = Paths.get(workspace, folder).toString(); + Files.createParentDirs(Paths.get(workspace, folder).toFile()); + LiteVCS.init(fullPath); + fillFile(fullPath, "a.txt"); + String initialState = hash(fullPath, "a.txt"); + LiteVCS.add(fullPath, "a.txt"); + LiteVCS.commit(fullPath, "commit #1"); + LiteVCS.createBranch(fullPath, "br"); + LiteVCS.switchBranch(fullPath, "br"); + fillFile(fullPath, "a.txt"); + String lastState = hash(fullPath, "a.txt"); + LiteVCS.add(fullPath, "a.txt"); + LiteVCS.commit(fullPath, "commit2"); + LiteVCS.switchBranch(fullPath, "master"); + assertEquals(initialState, hash(fullPath, "a.txt")); + LiteVCS.mergeBranch(fullPath,"br", "mesage"); + LiteVCS.reset(fullPath); + assertEquals(lastState, hash(fullPath, "a.txt")); + } + + @Test + public void addBranchChangeSwitchChangeSwitchMerge() throws Exception { + String folder = randomWorkspace(); + String fullPath = Paths.get(workspace, folder).toString(); + Files.createParentDirs(Paths.get(workspace, folder).toFile()); + LiteVCS.init(fullPath); + + fillFile(fullPath, "a.txt"); + String initialStateA = hash(fullPath, "a.txt"); + fillFile(fullPath, "b.txt"); + String initialStateB = hash(fullPath, "b.txt"); + + LiteVCS.add(fullPath, "a.txt"); + LiteVCS.add(fullPath, "b.txt"); + LiteVCS.commit(fullPath, "commit #1"); + LiteVCS.createBranch(fullPath, "br"); + + + fillFile(fullPath, "a.txt"); + String resultStateA = hash(fullPath, "a.txt"); + LiteVCS.add(fullPath, "a.txt"); + LiteVCS.commit(fullPath, "commit #2"); + + LiteVCS.switchBranch(fullPath, "br"); + + fillFile(fullPath, "b.txt"); + String resultStateB = hash(fullPath, "b.txt"); + LiteVCS.add(fullPath, "b.txt"); + LiteVCS.commit(fullPath, "commit3"); + + LiteVCS.switchBranch(fullPath, "master"); + assertEquals(resultStateA, hash(fullPath, "a.txt")); + assertEquals(initialStateB, hash(fullPath, "b.txt")); + + LiteVCS.mergeBranch(fullPath,"br", "mesage"); + LiteVCS.reset(fullPath); + + + assertEquals(resultStateA, hash(fullPath, "a.txt")); + assertEquals(resultStateB, hash(fullPath, "b.txt")); + } } \ No newline at end of file From 6dd012bd61df3d5a0f418453d6ec51e998eba833 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 23 Mar 2017 08:23:50 +0300 Subject: [PATCH 06/30] Minor changes --- .../spbau/lobanov/liteVCS/logic/DataManager.java | 14 +++++++------- .../spbau/lobanov/liteVCS/logic/LiteVCSTest.java | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java index f389eea..d94c8af 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java @@ -18,14 +18,14 @@ class DataManager { private static final String ROOT_DIRECTORY_NAME = ".liteVCS"; - private static final String PATH_TO_VERSIONS_FILES = ROOT_DIRECTORY_NAME + "\\versions"; - private static final String PATH_TO_CONTENT_DESCRIPTORS_FILES = ROOT_DIRECTORY_NAME + "\\descriptors"; - private static final String PATH_TO_COMMITS_FILES = ROOT_DIRECTORY_NAME + "\\commits"; - private static final String PATH_TO_SAVED_FILES = ROOT_DIRECTORY_NAME + "\\files"; - private static final String PATH_TO_BRANCHES = ROOT_DIRECTORY_NAME + "\\branches"; + private static final String PATH_TO_VERSIONS_FILES = Paths.get(ROOT_DIRECTORY_NAME, "versions").toString(); + private static final String PATH_TO_CONTENT_DESCRIPTORS_FILES = Paths.get(ROOT_DIRECTORY_NAME, "descriptors").toString(); + private static final String PATH_TO_COMMITS_FILES = Paths.get(ROOT_DIRECTORY_NAME, "commits").toString(); + private static final String PATH_TO_SAVED_FILES = Paths.get(ROOT_DIRECTORY_NAME, "files").toString();; + private static final String PATH_TO_BRANCHES = Paths.get(ROOT_DIRECTORY_NAME, "branches").toString();; private static final String ROOT_VERSION_NODE_ID = "root"; - private static final String PATH_TO_STAGE = ROOT_DIRECTORY_NAME + "\\stage.lVCS"; - private static final String PATH_TO_HEADER = ROOT_DIRECTORY_NAME + "\\header.lVCS"; + private static final String PATH_TO_STAGE = Paths.get(ROOT_DIRECTORY_NAME, "stage.lVCS").toString();; + private static final String PATH_TO_HEADER = Paths.get(ROOT_DIRECTORY_NAME, "header.lVCS").toString();; private final String workingDirectory; diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java index b4ee9d3..40c23ed 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java @@ -4,13 +4,13 @@ import com.google.common.io.Files; import org.junit.Test; -import java.io.*; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.DoubleSummaryStatistics; -import java.util.Random; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; public class LiteVCSTest { From 457a662551217984896d6eb898c4e32c947ebbe8 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 23 Mar 2017 08:55:28 +0300 Subject: [PATCH 07/30] Hello-function, error-messages improvement --- .../spbau/lobanov/liteVCS/ConsoleWorker.java | 44 +++++++++++++++---- .../lobanov/liteVCS/logic/DataManager.java | 6 +-- .../spbau/lobanov/liteVCS/logic/LiteVCS.java | 25 +++++++---- .../lobanov/liteVCS/logic/FakeManager.java | 3 +- .../lobanov/liteVCS/logic/PathManager.java | 3 +- 5 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java index 1eee87e..c901d7b 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java @@ -1,8 +1,15 @@ package ru.spbau.lobanov.liteVCS; -import ru.spbau.lobanov.liteVCS.logic.VersionControlSystemException; +import ru.spbau.lobanov.liteVCS.logic.DataManager.BrokenFileException; +import ru.spbau.lobanov.liteVCS.logic.DataManager.LostFileException; +import ru.spbau.lobanov.liteVCS.logic.DataManager.RecreatingRepositoryException; +import ru.spbau.lobanov.liteVCS.logic.DataManager.RepositoryNotInitializedException; import ru.spbau.lobanov.liteVCS.logic.LiteVCS; import ru.spbau.lobanov.liteVCS.logic.LiteVCS.ConflictMergeException; +import ru.spbau.lobanov.liteVCS.logic.LiteVCS.RemoveActiveBranchException; +import ru.spbau.lobanov.liteVCS.logic.LiteVCS.UncommittedChangesException; +import ru.spbau.lobanov.liteVCS.logic.LiteVCS.UnknownBranchException; +import ru.spbau.lobanov.liteVCS.logic.VersionControlSystemException; import ru.spbau.lobanov.liteVCS.primitives.Commit; import java.io.IOException; @@ -11,12 +18,13 @@ public class ConsoleWorker { + private static final String COMMIT_PLACE_HOLDER = "\"%s\" by %s (node: %s)\n"; + /** * Sugar to simplify checking count of arguments * * @param size expected arguments number * @param args array of arguments - * * @throws WrongNumberArgumentsException if length of array isn't equal to expected value */ private static void checkArguments(int size, String[] args) throws WrongNumberArgumentsException { @@ -28,7 +36,6 @@ private static void checkArguments(int size, String[] args) throws WrongNumberAr private static void execute(String command, String[] args) throws VersionControlSystemException, WrongNumberArgumentsException, IOException, UnknownCommandException { String path = Paths.get(System.getProperty("user.dir")).toString(); -// String path = "C:\\workspace\\liteVCS"; switch (command) { case "init": checkArguments(0, args); @@ -75,8 +82,12 @@ private static void execute(String command, String[] args) throws VersionControl LiteVCS.uninstall(path); break; case "logs": - checkArguments(0, args); - printLogs(LiteVCS.log(path, "100")); + checkArguments(1, args); + printLogs(LiteVCS.logs(path, args[0])); + break; + case "hello": + checkArguments(1, args); + LiteVCS.hello(path, args[0]); break; default: throw new UnknownCommandException(command); @@ -84,7 +95,6 @@ private static void execute(String command, String[] args) throws VersionControl } public static void main(String[] args) { -// args = new String[]{"merge_branch", "master", "commit4444+"}; if (args.length == 0) { System.out.println("Error: empty command"); return; @@ -98,13 +108,31 @@ public static void main(String[] args) { for (String path : e.getConflicts()) { System.out.println(" " + path); } + } catch (LostFileException e) { + System.out.println("Error: Looks, like important file disappeared: " + e.getExpectedFile().toString()); + } catch (BrokenFileException e) { + System.out.println("Error: Looks, like file was badly changed: " + e.getBrokenFile().toString()); + } catch (UnknownBranchException e) { + System.out.println("Error: branch doesn't exist"); + } catch (UncommittedChangesException e) { + System.out.println("Error: commit or reset changes first"); + } catch (UnknownCommandException e) { + System.out.println("Error: command doesn't exist"); + } catch (WrongNumberArgumentsException e) { + System.out.println("Error: wrong number of arguments"); + } catch (RepositoryNotInitializedException e) { + System.out.println("Error: Looks, like repository wasn't already created"); + } catch (RemoveActiveBranchException e) { + System.out.println("Error: you cant remove active branch"); + } catch (LiteVCS.IllegalBranchToMergeException e) { + System.out.println("Error: Looks, like you tried to branch with it-self"); + } catch (RecreatingRepositoryException e) { + System.out.println("Error: Looks, like you tried to create repository second time"); } catch (Throwable e) { System.out.println("Error: " + e.getMessage()); } } - private static final String COMMIT_PLACE_HOLDER = "\"%s\" by %s (node: %s)"; - private static void printLogs(List commits) { System.out.println("Local history:"); for (int i = commits.size() - 1; i >= 0; i--) { diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java index d94c8af..9d9dabd 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java @@ -15,14 +15,14 @@ * This class is adapter between logic part of LiteVCS * and data storage */ -class DataManager { +public class DataManager { private static final String ROOT_DIRECTORY_NAME = ".liteVCS"; private static final String PATH_TO_VERSIONS_FILES = Paths.get(ROOT_DIRECTORY_NAME, "versions").toString(); private static final String PATH_TO_CONTENT_DESCRIPTORS_FILES = Paths.get(ROOT_DIRECTORY_NAME, "descriptors").toString(); private static final String PATH_TO_COMMITS_FILES = Paths.get(ROOT_DIRECTORY_NAME, "commits").toString(); - private static final String PATH_TO_SAVED_FILES = Paths.get(ROOT_DIRECTORY_NAME, "files").toString();; - private static final String PATH_TO_BRANCHES = Paths.get(ROOT_DIRECTORY_NAME, "branches").toString();; + private static final String PATH_TO_SAVED_FILES = Paths.get(ROOT_DIRECTORY_NAME, "files").toString(); + private static final String PATH_TO_BRANCHES = Paths.get(ROOT_DIRECTORY_NAME, "branches").toString(); private static final String ROOT_VERSION_NODE_ID = "root"; private static final String PATH_TO_STAGE = Paths.get(ROOT_DIRECTORY_NAME, "stage.lVCS").toString();; private static final String PATH_TO_HEADER = Paths.get(ROOT_DIRECTORY_NAME, "header.lVCS").toString();; diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java index ad57bc7..b7a0dae 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java @@ -23,7 +23,18 @@ public class LiteVCS { private LiteVCS() { } + public static void hello(@NotNull String path, @NotNull String author) throws BrokenFileException, + LostFileException, RepositoryNotInitializedException { + DataManager dataManager = new DataManager(path); + String branchName = dataManager.getHeader().getCurrentBranchName(); + dataManager.putHeader(new Header(author, branchName)); + } + /** + * Wrapper for init-method of DataManager + * + * @throws RecreatingRepositoryException if repository was already created + */ public static void init(@NotNull String path) throws RecreatingRepositoryException, RepositoryNotInitializedException { new DataManager(path).initRepository(); @@ -95,15 +106,13 @@ public static void commit(@NotNull String path, @NotNull String message) throws * @throws BrokenFileException if file contained one of interesting object was not found */ @NotNull - public static List log(@NotNull String path, @NotNull String lengthLimit) + public static List logs(@NotNull String path, @NotNull String lengthLimit) throws BrokenFileException, LostFileException { - int limit = Integer.MAX_VALUE; - if (lengthLimit != null) { - try { - limit = Integer.parseInt(lengthLimit); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Cant parse length limit", e); - } + int limit; + try { + limit = Integer.parseInt(lengthLimit); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cant parse length limit", e); } DataManager dataManager = new DataManager(path); Header header = dataManager.getHeader(); diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java index 5a9fced..9bdf799 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java @@ -1,5 +1,6 @@ package ru.spbau.lobanov.liteVCS.logic; +import org.jetbrains.annotations.NotNull; import ru.spbau.lobanov.liteVCS.primitives.VersionNode; import java.util.HashMap; @@ -25,7 +26,7 @@ public void addVersionNode(String parentID) throws BrokenFileException, LostFile } @Override - public VersionNode getVersionNode(String id) { + public VersionNode getVersionNode(@NotNull String id) { return map.get(id); } } diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java index 7d03daa..fe24d58 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java @@ -1,5 +1,6 @@ package ru.spbau.lobanov.liteVCS.logic; +import org.jetbrains.annotations.NotNull; import ru.spbau.lobanov.liteVCS.primitives.VersionNode; import java.util.ArrayList; @@ -19,7 +20,7 @@ public class PathManager extends DataManager { } @Override - public VersionNode getVersionNode(String id) { + public VersionNode getVersionNode(@NotNull String id) { int i = Integer.parseInt(id); return versionNodes.get(i); } From cfec2d4458aa06c1ececf921fd89684ca9e6b118 Mon Sep 17 00:00:00 2001 From: Artyom Lobanov Date: Thu, 23 Mar 2017 09:19:50 +0300 Subject: [PATCH 08/30] Create README.md --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..5fe4746 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +Список команд: + init - Инициализирует репозиторий в текущей папке + add [файл] - Сохраняет копию текущего состояния файла, чтобы добавить его к ближайшему коммиту + commit [сообщение] - Делает коммит в текущую ветку, тем самым подтверждая все изменения + checkout [id of content descriptor] - id можно узнать через logs или подсмотрев в папку .liteVCS/descriptors. + очищает рабочую папку, а затем подгружает прописанные в дескрипторе файлы. + clear - Очищает рабочую папку, удаляя всё, кроме папки .liteVCS + create_branch [название] - Создёт новую ветку, ответвляющуюся от головы текущей ветки + remove_branch [название] - Удаляет ветку + switch_branch [название] - Переключается на другую ветку. Предварительно необходимо закоммитить все накопленные изменения. + merge_branch [название] [сообщение] - Если нет неразрешимых конфликтов (один и тот же файл изменили по разному в разных ветках), то + добавляет изменения из указанной ветки в текущую, создавая коммит с указанным сообщением. + reset - Приводит рабочую папку к состоянию, описанномуу в голове ветки. + uninstall - Удаляет репозиторий + logs [число] - Показывает последние [число] коммитов в текующей ветке + hello [имя] - Задаёт имя пользователя. + + + From 1d64c32c84f7db18bcc7d9ebfcea00fd5e20add5 Mon Sep 17 00:00:00 2001 From: Artyom Lobanov Date: Thu, 23 Mar 2017 09:21:20 +0300 Subject: [PATCH 09/30] Rename README.md to README.txt --- README.md => README.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README.md => README.txt (100%) diff --git a/README.md b/README.txt similarity index 100% rename from README.md rename to README.txt From 21075cee5d369054e93e15b9cd0e6b37cd5be23a Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Sun, 9 Apr 2017 05:55:12 +0300 Subject: [PATCH 10/30] Comments improved --- src/main/java/META-INF/MANIFEST.MF | 3 -- .../spbau/lobanov/liteVCS/logic/LiteVCS.java | 36 +++++++++++++------ 2 files changed, 26 insertions(+), 13 deletions(-) delete mode 100644 src/main/java/META-INF/MANIFEST.MF diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF deleted file mode 100644 index c8bb453..0000000 --- a/src/main/java/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Main-Class: ru.spbau.lobanov.liteVCS.ConsoleWorker - diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java index b7a0dae..5a48ec4 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java @@ -23,6 +23,15 @@ public class LiteVCS { private LiteVCS() { } + /** + * This method allows to set author's name. + * That name will be mentioned in following commits. + * + * @param author chosen name + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws RepositoryNotInitializedException if repository was not initialized + */ public static void hello(@NotNull String path, @NotNull String author) throws BrokenFileException, LostFileException, RepositoryNotInitializedException { DataManager dataManager = new DataManager(path); @@ -44,10 +53,12 @@ public static void init(@NotNull String path) throws RecreatingRepositoryExcepti * Method which add file to stage (list of files to commit). * * @param path path to working directory - * @throws RecreatingRepositoryException if repository was already created + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws RepositoryNotInitializedException if repository was not initialized */ public static void add(@NotNull String path, @NotNull String fileName) - throws VersionControlSystemException { + throws RepositoryNotInitializedException, LostFileException, BrokenFileException { File file = Paths.get(path, fileName).toFile(); DataManager dataManager = new DataManager(path); ContentDescriptor stage = dataManager.getStage(); @@ -70,8 +81,9 @@ public static void add(@NotNull String path, @NotNull String fileName) * * @param path path to working directory * @param message text which explain changes which was made in this commit - * @throws LostFileException if file contained one of interesting object was corrupted - * @throws BrokenFileException if file contained one of interesting object was not found + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws RepositoryNotInitializedException if repository was not initialized */ public static void commit(@NotNull String path, @NotNull String message) throws BrokenFileException, LostFileException, RepositoryNotInitializedException { @@ -133,6 +145,7 @@ public static List logs(@NotNull String path, @NotNull String lengthLimi * @param branchName name of new branch * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found + * @throws RepositoryNotInitializedException if repository was not initialized * @throws ConflictNameException if branch with equal name is already exist */ public static void createBranch(@NotNull String path, @NotNull String branchName) @@ -180,8 +193,9 @@ public static void removeBranch(@NotNull String path, @NotNull String branchName * @param branchName name of branch to remove * @param message text ehich will be used in commit * @param path path to working directory - * @throws LostFileException if file contained one of interesting object was corrupted - * @throws BrokenFileException if file contained one of interesting object was not found + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws RepositoryNotInitializedException if repository was not initialized * @throws IllegalBranchToMergeException if it's logically impossible to merge thar branches * @throws UnknownBranchException if branch with the same name wasn't found * @throws UncommittedChangesException if stage not empty @@ -334,8 +348,9 @@ private static ContentDescriptor toContentDescriptor(@NotNull String versionID, /** * @param path path to working directory * @param branchName name of interesting branch - * @throws LostFileException if file contained one of interesting object was corrupted - * @throws BrokenFileException if file contained one of interesting object was not found + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws RepositoryNotInitializedException if repository was not initialized * @throws SwitchOnCurrentBranchException if you try to switch on your current branch * @throws UncommittedChangesException if stage isn't empty before change branch * @throws UnknownBranchException if such branch wasn't found @@ -368,8 +383,9 @@ public static void switchBranch(@NotNull String path, @NotNull String branchName * Restore saved copies from current Branch * * @param path path to working directory - * @throws LostFileException if file contained one of interesting object was corrupted - * @throws BrokenFileException if file contained one of interesting object was not found + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws RepositoryNotInitializedException if repository was not initialized * @throws IOException in case of some IO problems */ public static void reset(@NotNull String path) throws BrokenFileException, LostFileException, IOException, From 1277332e252e7024e99539d779adc796f5b99b23 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Sun, 9 Apr 2017 06:07:18 +0300 Subject: [PATCH 11/30] Minor changes to simplify long lines --- .../lobanov/liteVCS/logic/DataManager.java | 26 ++++++++++++++----- .../lobanov/liteVCS/logic/FakeManager.java | 1 + .../lobanov/liteVCS/logic/PathManager.java | 1 + 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java index 9d9dabd..933e575 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java @@ -18,14 +18,14 @@ public class DataManager { private static final String ROOT_DIRECTORY_NAME = ".liteVCS"; - private static final String PATH_TO_VERSIONS_FILES = Paths.get(ROOT_DIRECTORY_NAME, "versions").toString(); - private static final String PATH_TO_CONTENT_DESCRIPTORS_FILES = Paths.get(ROOT_DIRECTORY_NAME, "descriptors").toString(); - private static final String PATH_TO_COMMITS_FILES = Paths.get(ROOT_DIRECTORY_NAME, "commits").toString(); - private static final String PATH_TO_SAVED_FILES = Paths.get(ROOT_DIRECTORY_NAME, "files").toString(); - private static final String PATH_TO_BRANCHES = Paths.get(ROOT_DIRECTORY_NAME, "branches").toString(); + private static final String PATH_TO_VERSIONS_FILES = ROOT_DIRECTORY_NAME + "\\versions"; + private static final String PATH_TO_CONTENT_DESCRIPTORS_FILES = concat(ROOT_DIRECTORY_NAME, "descriptors"); + private static final String PATH_TO_COMMITS_FILES = concat(ROOT_DIRECTORY_NAME, "commits"); + private static final String PATH_TO_SAVED_FILES = concat(ROOT_DIRECTORY_NAME, "files"); + private static final String PATH_TO_BRANCHES = concat(ROOT_DIRECTORY_NAME, "branches"); private static final String ROOT_VERSION_NODE_ID = "root"; - private static final String PATH_TO_STAGE = Paths.get(ROOT_DIRECTORY_NAME, "stage.lVCS").toString();; - private static final String PATH_TO_HEADER = Paths.get(ROOT_DIRECTORY_NAME, "header.lVCS").toString();; + private static final String PATH_TO_STAGE = concat(ROOT_DIRECTORY_NAME, "stage.lVCS"); + private static final String PATH_TO_HEADER = concat(ROOT_DIRECTORY_NAME, "header.lVCS"); private final String workingDirectory; @@ -422,6 +422,18 @@ private static void writeObject(@NotNull Path path, @NotNull Object object) } } + /** + * Special method to simplify definition of service paths + * + * @param root main directory + * @param paths relative paths + * @return concatenation of paths + */ + @NotNull + private static String concat(@NotNull String root, @NotNull String... paths) { + return Paths.get(root, paths).toString(); + } + public static class LostFileException extends VersionControlSystemException { private final File expectedFile; diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java index 9bdf799..a7a7901 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java @@ -25,6 +25,7 @@ public void addVersionNode(String parentID) throws BrokenFileException, LostFile map.put(map.size() + "", versionNode); } + @NotNull @Override public VersionNode getVersionNode(@NotNull String id) { return map.get(id); diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java index fe24d58..baacb3d 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java @@ -19,6 +19,7 @@ public class PathManager extends DataManager { } catch (Exception ignored){} } + @NotNull @Override public VersionNode getVersionNode(@NotNull String id) { int i = Integer.parseInt(id); From e93433508c2a1b0a18db27c5d26b00badbf78a28 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Sun, 9 Apr 2017 06:40:53 +0300 Subject: [PATCH 12/30] Rewrite static methdos to non-static --- build.gradle | 8 -- .../spbau/lobanov/liteVCS/ConsoleWorker.java | 27 ++-- .../lobanov/liteVCS/logic/Algorithms.java | 12 +- .../lobanov/liteVCS/logic/DataManager.java | 35 +++-- .../spbau/lobanov/liteVCS/logic/LiteVCS.java | 120 ++++++++---------- .../lobanov/liteVCS/logic/AlgorithmsTest.java | 13 +- .../lobanov/liteVCS/logic/FakeManager.java | 2 +- .../lobanov/liteVCS/logic/LiteVCSTest.java | 60 +++++---- .../lobanov/liteVCS/logic/PathManager.java | 2 +- 9 files changed, 135 insertions(+), 144 deletions(-) diff --git a/build.gradle b/build.gradle index 017778d..62d2276 100644 --- a/build.gradle +++ b/build.gradle @@ -3,14 +3,6 @@ version '1.0-SNAPSHOT' apply plugin: 'java' -jar { - manifest { - def manif = "${projectDir}/src/main/java/META-INF/MANIFEST.MF" - from file(manif) - } - baseName = "liteVCS" -} - repositories { mavenCentral() } diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java index c901d7b..ec21c4d 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java @@ -36,58 +36,59 @@ private static void checkArguments(int size, String[] args) throws WrongNumberAr private static void execute(String command, String[] args) throws VersionControlSystemException, WrongNumberArgumentsException, IOException, UnknownCommandException { String path = Paths.get(System.getProperty("user.dir")).toString(); + LiteVCS liteVCS = new LiteVCS(path); switch (command) { case "init": checkArguments(0, args); - LiteVCS.init(path); + liteVCS.init(); break; case "add": checkArguments(1, args); - LiteVCS.add(path, args[0]); + liteVCS.add(args[0]); break; case "commit": checkArguments(1, args); - LiteVCS.commit(path, args[0]); + liteVCS.commit(args[0]); break; case "checkout": checkArguments(1, args); - LiteVCS.checkout(path, args[0]); + liteVCS.checkout(args[0]); break; case "clear": checkArguments(0, args); - LiteVCS.clear(path); + liteVCS.clear(); break; case "create_branch": checkArguments(1, args); - LiteVCS.createBranch(path, args[0]); + liteVCS.createBranch(args[0]); break; case "remove_branch": checkArguments(1, args); - LiteVCS.removeBranch(path, args[0]); + liteVCS.removeBranch(args[0]); break; case "switch_branch": checkArguments(1, args); - LiteVCS.switchBranch(path, args[0]); + liteVCS.switchBranch(args[0]); break; case "merge_branch": checkArguments(2, args); - LiteVCS.mergeBranch(path, args[0], args[1]); + liteVCS.mergeBranch(args[0], args[1]); break; case "reset": checkArguments(0, args); - LiteVCS.reset(path); + liteVCS.reset(); break; case "uninstall": checkArguments(0, args); - LiteVCS.uninstall(path); + liteVCS.uninstall(); break; case "logs": checkArguments(1, args); - printLogs(LiteVCS.logs(path, args[0])); + printLogs(liteVCS.logs(args[0])); break; case "hello": checkArguments(1, args); - LiteVCS.hello(path, args[0]); + liteVCS.hello(args[0]); break; default: throw new UnknownCommandException(command); diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/Algorithms.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/Algorithms.java index 4ac9fc0..206a3aa 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/Algorithms.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/Algorithms.java @@ -50,7 +50,7 @@ static VersionNode createVersionNode(@NotNull String commitID, @NotNull String p for (int i = 1; i < CACHE_SIZE_LIMIT; i++) { parentsTable[i] = getParent(parentsTable[i - 1], i - 1, manager); } - VersionNode parent = manager.getVersionNode(parentID); + VersionNode parent = manager.fetchVersionNode(parentID); return new VersionNode(commitID, parent.getDeepLevel() + 1, parentsTable); } @@ -70,7 +70,7 @@ static List getAllParents(@NotNull String versionID, int limit, String currentVersionID = versionID; ArrayList list = new ArrayList<>(); for (int i = 0; i < limit; i++) { - VersionNode currentNode = dataManager.getVersionNode(currentVersionID); + VersionNode currentNode = dataManager.fetchVersionNode(currentVersionID); list.add(currentNode); if (currentVersionID.equals(currentNode.getParentsTable()[0])) { break; @@ -135,8 +135,8 @@ static String findLowestCommonAncestor(@NotNull String nodeID1, @NotNull String */ private static boolean isAncestorsEqual(@NotNull String nodeID1, @NotNull String nodeID2, int level, @NotNull DataManager manager) throws BrokenFileException, LostFileException { - String ancestorID1 = manager.getVersionNode(nodeID1).getParentsTable()[level]; - String ancestorID2 = manager.getVersionNode(nodeID2).getParentsTable()[level]; + String ancestorID1 = manager.fetchVersionNode(nodeID1).getParentsTable()[level]; + String ancestorID2 = manager.fetchVersionNode(nodeID2).getParentsTable()[level]; return ancestorID1.equals(ancestorID2); } @@ -186,7 +186,7 @@ private static String jump(@NotNull String nodeID, int length, @NotNull private static String getParent(@NotNull String nodeID, int level, @NotNull DataManager manager) throws BrokenFileException, LostFileException { - return manager.getVersionNode(nodeID).getParentsTable()[level]; + return manager.fetchVersionNode(nodeID).getParentsTable()[level]; } /** @@ -200,6 +200,6 @@ private static String getParent(@NotNull String nodeID, int level, */ private static int getDeepLevel(@NotNull String nodeID, @NotNull DataManager manager) throws BrokenFileException, LostFileException { - return manager.getVersionNode(nodeID).getDeepLevel(); + return manager.fetchVersionNode(nodeID).getDeepLevel(); } } diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java index 933e575..585a8f3 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java @@ -27,6 +27,7 @@ public class DataManager { private static final String PATH_TO_STAGE = concat(ROOT_DIRECTORY_NAME, "stage.lVCS"); private static final String PATH_TO_HEADER = concat(ROOT_DIRECTORY_NAME, "header.lVCS"); + @NotNull private final String workingDirectory; DataManager(@NotNull String workingDirectory) { @@ -76,7 +77,7 @@ void initRepository() throws RecreatingRepositoryException { * @throws BrokenFileException if file contained one of interesting object was not found */ @NotNull - VersionNode getVersionNode(@NotNull String id) throws LostFileException, BrokenFileException { + VersionNode fetchVersionNode(@NotNull String id) throws LostFileException, BrokenFileException { return readObject(Paths.get(workingDirectory, PATH_TO_VERSIONS_FILES, id), VersionNode.class); } @@ -84,7 +85,7 @@ VersionNode getVersionNode(@NotNull String id) throws LostFileException, BrokenF * Method allow to save VersionNode in file system * * @param versionNode object to save - * @return generated id, by which you can get that object late + * @return generated id, by which you can fetch that object late * @throws RepositoryNotInitializedException if file creating failed */ @NotNull @@ -103,7 +104,7 @@ String addVersionNode(@NotNull VersionNode versionNode) throws RepositoryNotInit * @throws BrokenFileException if file contained one of interesting object was not found */ @NotNull - ContentDescriptor getContentDescriptor(@NotNull String id) throws LostFileException, BrokenFileException { + ContentDescriptor fetchContentDescriptor(@NotNull String id) throws LostFileException, BrokenFileException { return readObject(Paths.get(workingDirectory, PATH_TO_CONTENT_DESCRIPTORS_FILES, id), ContentDescriptor.class); } @@ -111,7 +112,7 @@ ContentDescriptor getContentDescriptor(@NotNull String id) throws LostFileExcept * Method allow to save ContentDescriptor in file system * * @param contentDescriptor object to save - * @return generated id, by which you can get that object late + * @return generated id, by which you can fetch that object late * @throws RepositoryNotInitializedException if file creating failed */ @NotNull @@ -130,7 +131,7 @@ String addContentDescriptor(@NotNull ContentDescriptor contentDescriptor) throws * @throws BrokenFileException if file contained one of interesting object was not found */ @NotNull - Commit getCommit(@NotNull String id) throws LostFileException, BrokenFileException { + Commit fetchCommit(@NotNull String id) throws LostFileException, BrokenFileException { return readObject(Paths.get(workingDirectory, PATH_TO_COMMITS_FILES, id), Commit.class); } @@ -138,7 +139,7 @@ Commit getCommit(@NotNull String id) throws LostFileException, BrokenFileExcepti * Method allow to save Commit in file system * * @param commit object to save - * @return generated id, by which you can get that object late + * @return generated id, by which you can fetch that object late * @throws RepositoryNotInitializedException if file creating failed */ @NotNull @@ -156,7 +157,7 @@ String addCommit(@NotNull Commit commit) throws RepositoryNotInitializedExceptio * @throws LostFileException if file contained one of interesting object was corrupted */ @NotNull - File getFile(@NotNull String id) throws LostFileException { + File fetchFile(@NotNull String id) throws LostFileException { File file = Paths.get(workingDirectory, PATH_TO_SAVED_FILES, id).toFile(); if (!file.exists()) { throw new LostFileException("File wasn't found:" + file.getName(), null, file); @@ -168,7 +169,7 @@ File getFile(@NotNull String id) throws LostFileException { * Method allow to save copy of file in file system * * @param file object to save - * @return generated id, by which you can get that object late + * @return generated id, by which you can fetch that object late * @throws RepositoryNotInitializedException if file creating failed */ @NotNull @@ -197,7 +198,6 @@ String addFile(@NotNull File file) throws RepositoryNotInitializedException { * @param branch object to save * @throws RepositoryNotInitializedException if file creating failed */ - @NotNull void addBranch(@NotNull Branch branch) throws RepositoryNotInitializedException { writeObject(Paths.get(workingDirectory, PATH_TO_BRANCHES, branch.getName()), branch); } @@ -211,7 +211,7 @@ void addBranch(@NotNull Branch branch) throws RepositoryNotInitializedException * @throws BrokenFileException if file contained one of interesting object was not found */ @NotNull - Branch getBranch(@NotNull String name) throws LostFileException, BrokenFileException { + Branch fetchBranch(@NotNull String name) throws LostFileException, BrokenFileException { return readObject(Paths.get(workingDirectory, PATH_TO_BRANCHES, name), Branch.class); } @@ -292,9 +292,9 @@ void putStage(@NotNull ContentDescriptor stage) throws RepositoryNotInitializedE * @throws IOException if file creating failed because of File System */ void loadFiles(@NotNull String descriptorID) throws BrokenFileException, LostFileException, IOException { - ContentDescriptor descriptor = getContentDescriptor(descriptorID); + ContentDescriptor descriptor = fetchContentDescriptor(descriptorID); for (Entry pair : descriptor.getFiles().entrySet()) { - File savedCopy = getFile(pair.getValue()); + File savedCopy = fetchFile(pair.getValue()); File targetFile = Paths.get(workingDirectory, pair.getKey()).toFile(); Files.createParentDirs(targetFile); Files.touch(targetFile); @@ -340,6 +340,17 @@ void uninstallRepository() throws RepositoryNotInitializedException { } + /** + * Allow to get files from working directory + * + * @param filename target file + * @return File, associated with target file + */ + @NotNull + File getFile(@NotNull String filename) { + return Paths.get(this.workingDirectory, filename).toFile(); + } + /** * Method remove all files from directory * diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java index 5a48ec4..a41d2a9 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java @@ -20,7 +20,14 @@ */ public class LiteVCS { - private LiteVCS() { + private final DataManager dataManager; + + public LiteVCS(@NotNull String path) { + dataManager = new DataManager(path); + } + + public LiteVCS(@NotNull DataManager dataManager) { + this.dataManager = dataManager; } /** @@ -32,9 +39,8 @@ private LiteVCS() { * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized */ - public static void hello(@NotNull String path, @NotNull String author) throws BrokenFileException, + public void hello(@NotNull String author) throws BrokenFileException, LostFileException, RepositoryNotInitializedException { - DataManager dataManager = new DataManager(path); String branchName = dataManager.getHeader().getCurrentBranchName(); dataManager.putHeader(new Header(author, branchName)); } @@ -44,33 +50,31 @@ public static void hello(@NotNull String path, @NotNull String author) throws Br * * @throws RecreatingRepositoryException if repository was already created */ - public static void init(@NotNull String path) throws RecreatingRepositoryException, + public void init() throws RecreatingRepositoryException, RepositoryNotInitializedException { - new DataManager(path).initRepository(); + dataManager.initRepository(); } /** * Method which add file to stage (list of files to commit). * - * @param path path to working directory + * @param fileName relative path to target file * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized */ - public static void add(@NotNull String path, @NotNull String fileName) + public void add(@NotNull String fileName) throws RepositoryNotInitializedException, LostFileException, BrokenFileException { - File file = Paths.get(path, fileName).toFile(); - DataManager dataManager = new DataManager(path); + File file = dataManager.getFile(fileName); ContentDescriptor stage = dataManager.getStage(); ContentDescriptor updatedStage; if (!file.isFile()) { throw new Error("Possible to add files only"); } String fileID = dataManager.addFile(file); - String relatedPath = Paths.get(path).relativize(file.toPath()).toString(); updatedStage = ContentDescriptor.builder() .addAllFiles(stage) - .addFile(relatedPath, fileID) + .addFile(fileName, fileID) .build(); dataManager.putStage(updatedStage); } @@ -79,20 +83,18 @@ public static void add(@NotNull String path, @NotNull String fileName) * Method which get list of changed from stage, * create Commit and add it to head of current branch * - * @param path path to working directory * @param message text which explain changes which was made in this commit * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized */ - public static void commit(@NotNull String path, @NotNull String message) throws BrokenFileException, + public void commit(@NotNull String message) throws BrokenFileException, LostFileException, RepositoryNotInitializedException { - DataManager dataManager = new DataManager(path); Header header = dataManager.getHeader(); - Branch currentBranch = dataManager.getBranch(header.getCurrentBranchName()); - VersionNode currentVersion = dataManager.getVersionNode(currentBranch.getVersionNodeID()); - Commit lastCommit = dataManager.getCommit(currentVersion.getCommitID()); - ContentDescriptor currentDescriptor = dataManager.getContentDescriptor(lastCommit.getContentDescriptorID()); + Branch currentBranch = dataManager.fetchBranch(header.getCurrentBranchName()); + VersionNode currentVersion = dataManager.fetchVersionNode(currentBranch.getVersionNodeID()); + Commit lastCommit = dataManager.fetchCommit(currentVersion.getCommitID()); + ContentDescriptor currentDescriptor = dataManager.fetchContentDescriptor(lastCommit.getContentDescriptorID()); ContentDescriptor stage = dataManager.getStage(); ContentDescriptor updatedDescriptor = ContentDescriptor.builder() @@ -112,13 +114,12 @@ public static void commit(@NotNull String path, @NotNull String message) throws /** * Method which return list of node's parents sorted by increasing distance * - * @param path path to working directory * @param lengthLimit limit size of returned List * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found */ @NotNull - public static List logs(@NotNull String path, @NotNull String lengthLimit) + public List logs(@NotNull String lengthLimit) throws BrokenFileException, LostFileException { int limit; try { @@ -126,13 +127,12 @@ public static List logs(@NotNull String path, @NotNull String lengthLimi } catch (NumberFormatException e) { throw new IllegalArgumentException("Cant parse length limit", e); } - DataManager dataManager = new DataManager(path); Header header = dataManager.getHeader(); - Branch currentBranch = dataManager.getBranch(header.getCurrentBranchName()); + Branch currentBranch = dataManager.fetchBranch(header.getCurrentBranchName()); List versions = Algorithms.getAllParents(currentBranch.getVersionNodeID(), limit, dataManager); List commits = new ArrayList<>(); for (VersionNode versionNode : versions) { - commits.add(dataManager.getCommit(versionNode.getCommitID())); + commits.add(dataManager.fetchCommit(versionNode.getCommitID())); } return commits; } @@ -141,18 +141,16 @@ public static List logs(@NotNull String path, @NotNull String lengthLimi * Method create new Branch which current version * equal to version of active branch * - * @param path path to working directory * @param branchName name of new branch * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized * @throws ConflictNameException if branch with equal name is already exist */ - public static void createBranch(@NotNull String path, @NotNull String branchName) + public void createBranch(@NotNull String branchName) throws BrokenFileException, LostFileException, ConflictNameException, RepositoryNotInitializedException { - DataManager dataManager = new DataManager(path); Header header = dataManager.getHeader(); - Branch currentBranch = dataManager.getBranch(header.getCurrentBranchName()); + Branch currentBranch = dataManager.fetchBranch(header.getCurrentBranchName()); if (dataManager.hasBranch(branchName)) { throw new ConflictNameException("Branch with the same name is already exist"); } @@ -164,16 +162,14 @@ public static void createBranch(@NotNull String path, @NotNull String branchName * Method removes record about given branch * * @param branchName name of branch to remove - * @param path path to working directory * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found * @throws RemoveActiveBranchException if user try to remove active(current) branch * @throws UnknownBranchException if branch with the same name wasn't found */ - public static void removeBranch(@NotNull String path, @NotNull String branchName) + public void removeBranch(@NotNull String branchName) throws BrokenFileException, LostFileException, RemoveActiveBranchException, UnknownBranchException { - DataManager dataManager = new DataManager(path); Header header = dataManager.getHeader(); if (header.getCurrentBranchName().equals(branchName)) { throw new RemoveActiveBranchException("Cant remove active branch"); @@ -192,7 +188,6 @@ public static void removeBranch(@NotNull String path, @NotNull String branchName * * @param branchName name of branch to remove * @param message text ehich will be used in commit - * @param path path to working directory * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized @@ -201,10 +196,9 @@ public static void removeBranch(@NotNull String path, @NotNull String branchName * @throws UncommittedChangesException if stage not empty * @throws ConflictMergeException if unresolvable conflict was found */ - public static void mergeBranch(@NotNull String path, @NotNull String branchName, @NotNull String message) + public void mergeBranch(@NotNull String branchName, @NotNull String message) throws BrokenFileException, LostFileException, IllegalBranchToMergeException, UnknownBranchException, UncommittedChangesException, ConflictMergeException, RepositoryNotInitializedException { - DataManager dataManager = new DataManager(path); Header header = dataManager.getHeader(); if (header.getCurrentBranchName().equals(branchName)) { throw new IllegalBranchToMergeException("Cant merge branch with itself"); @@ -215,12 +209,12 @@ public static void mergeBranch(@NotNull String path, @NotNull String branchName, if (!dataManager.getStage().getFiles().isEmpty()) { throw new UncommittedChangesException("Commit changes before merge"); } - String activeVersionID = dataManager.getBranch(header.getCurrentBranchName()).getVersionNodeID(); - String sideVersionID = dataManager.getBranch(branchName).getVersionNodeID(); + String activeVersionID = dataManager.fetchBranch(header.getCurrentBranchName()).getVersionNodeID(); + String sideVersionID = dataManager.fetchBranch(branchName).getVersionNodeID(); String lcaVersionID = Algorithms.findLowestCommonAncestor(activeVersionID, sideVersionID, dataManager); - ContentDescriptor activeContent = toContentDescriptor(activeVersionID, dataManager); - ContentDescriptor sideContent = toContentDescriptor(sideVersionID, dataManager); - ContentDescriptor lcaContent = toContentDescriptor(lcaVersionID, dataManager); + ContentDescriptor activeContent = toContentDescriptor(activeVersionID); + ContentDescriptor sideContent = toContentDescriptor(sideVersionID); + ContentDescriptor lcaContent = toContentDescriptor(lcaVersionID); List conflicts = checkConflicts(activeContent, sideContent, lcaContent); if (!conflicts.isEmpty()) { throw new ConflictMergeException("Conflicts was found", conflicts); @@ -332,21 +326,19 @@ && mergeFileVersions(files1.get(path), files2.get(path), lcaFiles.get(path)) != * Sugar to simplify getting ContentDescriptor from ID of VersionNode * * @param versionID id of VersionNode - * @param dataManager dataManager, which provides access to the files * @return loaded ContentDescriptor * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found */ @NotNull - private static ContentDescriptor toContentDescriptor(@NotNull String versionID, @NotNull DataManager dataManager) + private ContentDescriptor toContentDescriptor(@NotNull String versionID) throws BrokenFileException, LostFileException { - VersionNode versionNode = dataManager.getVersionNode(versionID); - Commit commit = dataManager.getCommit(versionNode.getCommitID()); - return dataManager.getContentDescriptor(commit.getContentDescriptorID()); + VersionNode versionNode = dataManager.fetchVersionNode(versionID); + Commit commit = dataManager.fetchCommit(versionNode.getCommitID()); + return dataManager.fetchContentDescriptor(commit.getContentDescriptorID()); } /** - * @param path path to working directory * @param branchName name of interesting branch * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found @@ -356,10 +348,9 @@ private static ContentDescriptor toContentDescriptor(@NotNull String versionID, * @throws UnknownBranchException if such branch wasn't found * @throws IOException in case of some IO problems */ - public static void switchBranch(@NotNull String path, @NotNull String branchName) + public void switchBranch(@NotNull String branchName) throws BrokenFileException, LostFileException, SwitchOnCurrentBranchException, UncommittedChangesException, UnknownBranchException, IOException, RepositoryNotInitializedException { - DataManager dataManager = new DataManager(path); Header header = dataManager.getHeader(); if (header.getCurrentBranchName().equals(branchName)) { throw new SwitchOnCurrentBranchException("This branch is already chosen"); @@ -371,10 +362,10 @@ public static void switchBranch(@NotNull String path, @NotNull String branchName if (!dataManager.hasBranch(branchName)) { throw new UnknownBranchException("Branch wasn't found"); } - Branch branch = dataManager.getBranch(branchName); - VersionNode versionNode = dataManager.getVersionNode(branch.getVersionNodeID()); - Commit lastCommit = dataManager.getCommit(versionNode.getCommitID()); - checkout(path, lastCommit.getContentDescriptorID()); + Branch branch = dataManager.fetchBranch(branchName); + VersionNode versionNode = dataManager.fetchVersionNode(branch.getVersionNodeID()); + Commit lastCommit = dataManager.fetchCommit(versionNode.getCommitID()); + checkout(lastCommit.getContentDescriptorID()); Header updatedHeader = new Header(header.getAuthor(), branchName); dataManager.putHeader(updatedHeader); } @@ -382,54 +373,49 @@ public static void switchBranch(@NotNull String path, @NotNull String branchName /** * Restore saved copies from current Branch * - * @param path path to working directory * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized * @throws IOException in case of some IO problems */ - public static void reset(@NotNull String path) throws BrokenFileException, LostFileException, IOException, + public void reset() throws BrokenFileException, LostFileException, IOException, RepositoryNotInitializedException { - DataManager dataManager = new DataManager(path); dataManager.putStage(ContentDescriptor.EMPTY); Header header = dataManager.getHeader(); - Branch branch = dataManager.getBranch(header.getCurrentBranchName()); - VersionNode versionNode = dataManager.getVersionNode(branch.getVersionNodeID()); - Commit lastCommit = dataManager.getCommit(versionNode.getCommitID()); - checkout(path, lastCommit.getContentDescriptorID()); + Branch branch = dataManager.fetchBranch(header.getCurrentBranchName()); + VersionNode versionNode = dataManager.fetchVersionNode(branch.getVersionNodeID()); + Commit lastCommit = dataManager.fetchCommit(versionNode.getCommitID()); + checkout(lastCommit.getContentDescriptorID()); } /** * Restore saved file from given ContentDescriptor * - * @param path path to working directory * @param descriptorID id of interesting description * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found * @throws IOException in case of some IO problems */ - public static void checkout(@NotNull String path, @NotNull String descriptorID) + public void checkout(@NotNull String descriptorID) throws IOException, BrokenFileException, LostFileException { - DataManager dataManager = new DataManager(path); dataManager.clearWorkingCopy(); dataManager.loadFiles(descriptorID); } /** * This method remove all file from working directory - * - * @param path path to working directory */ - public static void clear(@NotNull String path) { - new DataManager(path).clearWorkingCopy(); + public void clear() { + dataManager.clearWorkingCopy(); } /** - * @param path path to working directory + * Remove repository, but doesn't touch files in working copy + * * @throws RepositoryNotInitializedException if there is nothing to delete */ - public static void uninstall(@NotNull String path) throws RepositoryNotInitializedException { - new DataManager(path).uninstallRepository(); + public void uninstall() throws RepositoryNotInitializedException { + dataManager.uninstallRepository(); } public static class SwitchOnCurrentBranchException extends VersionControlSystemException { diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/AlgorithmsTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/AlgorithmsTest.java index 00347c4..40ac824 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/AlgorithmsTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/AlgorithmsTest.java @@ -1,11 +1,8 @@ package ru.spbau.lobanov.liteVCS.logic; import org.junit.Test; -import ru.spbau.lobanov.liteVCS.primitives.Commit; import ru.spbau.lobanov.liteVCS.primitives.VersionNode; -import java.lang.annotation.Target; -import java.util.ArrayList; import java.util.List; import static org.junit.Assert.*; @@ -62,17 +59,17 @@ public void bigDataLCATest() throws Exception { @Test public void allParentsSmallDataTest() throws DataManager.LostFileException, DataManager.BrokenFileException { List parents = getAllParents("13", 7, treeExample); - VersionNode[] answer = {treeExample.getVersionNode("13"), treeExample.getVersionNode("6"), - treeExample.getVersionNode("4"), treeExample.getVersionNode("3"), - treeExample.getVersionNode("2"), treeExample.getVersionNode("0")}; + VersionNode[] answer = {treeExample.fetchVersionNode("13"), treeExample.fetchVersionNode("6"), + treeExample.fetchVersionNode("4"), treeExample.fetchVersionNode("3"), + treeExample.fetchVersionNode("2"), treeExample.fetchVersionNode("0")}; assertEquals(answer.length, parents.size()); for (int i = 0; i < answer.length; i++) { assertSame(answer[i], parents.get(i)); } List parents2 = getAllParents("14", 4, treeExample); - VersionNode[] answer2 = {treeExample.getVersionNode("14"), treeExample.getVersionNode("8"), - treeExample.getVersionNode("7"), treeExample.getVersionNode("1")}; + VersionNode[] answer2 = {treeExample.fetchVersionNode("14"), treeExample.fetchVersionNode("8"), + treeExample.fetchVersionNode("7"), treeExample.fetchVersionNode("1")}; assertEquals(answer2.length, parents2.size()); for (int i = 0; i < answer2.length; i++) { assertSame(answer2[i], parents2.get(i)); diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java index a7a7901..4896781 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java @@ -27,7 +27,7 @@ public void addVersionNode(String parentID) throws BrokenFileException, LostFile @NotNull @Override - public VersionNode getVersionNode(@NotNull String id) { + public VersionNode fetchVersionNode(@NotNull String id) { return map.get(id); } } diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java index 40c23ed..ab5cfa4 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java @@ -47,21 +47,22 @@ public void addBranchSwitchChangeSwitchMerge() throws Exception { String folder = randomWorkspace(); String fullPath = Paths.get(workspace, folder).toString(); Files.createParentDirs(Paths.get(workspace, folder).toFile()); - LiteVCS.init(fullPath); + LiteVCS liteVCS = new LiteVCS(fullPath); + liteVCS.init(); fillFile(fullPath, "a.txt"); String initialState = hash(fullPath, "a.txt"); - LiteVCS.add(fullPath, "a.txt"); - LiteVCS.commit(fullPath, "commit #1"); - LiteVCS.createBranch(fullPath, "br"); - LiteVCS.switchBranch(fullPath, "br"); + liteVCS.add("a.txt"); + liteVCS.commit("commit #1"); + liteVCS.createBranch("br"); + liteVCS.switchBranch("br"); fillFile(fullPath, "a.txt"); String lastState = hash(fullPath, "a.txt"); - LiteVCS.add(fullPath, "a.txt"); - LiteVCS.commit(fullPath, "commit2"); - LiteVCS.switchBranch(fullPath, "master"); + liteVCS.add("a.txt"); + liteVCS.commit("commit2"); + liteVCS.switchBranch("master"); assertEquals(initialState, hash(fullPath, "a.txt")); - LiteVCS.mergeBranch(fullPath,"br", "mesage"); - LiteVCS.reset(fullPath); + liteVCS.mergeBranch("br", "mesage"); + liteVCS.reset(); assertEquals(lastState, hash(fullPath, "a.txt")); } @@ -70,37 +71,40 @@ public void addBranchChangeSwitchChangeSwitchMerge() throws Exception { String folder = randomWorkspace(); String fullPath = Paths.get(workspace, folder).toString(); Files.createParentDirs(Paths.get(workspace, folder).toFile()); - LiteVCS.init(fullPath); - - fillFile(fullPath, "a.txt"); - String initialStateA = hash(fullPath, "a.txt"); - fillFile(fullPath, "b.txt"); - String initialStateB = hash(fullPath, "b.txt"); + LiteVCS liteVCS = new LiteVCS(fullPath); + liteVCS.init(); + String initialStateA, initialStateB; + do { + fillFile(fullPath, "a.txt"); + initialStateA = hash(fullPath, "a.txt"); + fillFile(fullPath, "b.txt"); + initialStateB = hash(fullPath, "b.txt"); + } while (initialStateA.equals(initialStateB)); - LiteVCS.add(fullPath, "a.txt"); - LiteVCS.add(fullPath, "b.txt"); - LiteVCS.commit(fullPath, "commit #1"); - LiteVCS.createBranch(fullPath, "br"); + liteVCS.add("a.txt"); + liteVCS.add("b.txt"); + liteVCS.commit("commit #1"); + liteVCS.createBranch("br"); fillFile(fullPath, "a.txt"); String resultStateA = hash(fullPath, "a.txt"); - LiteVCS.add(fullPath, "a.txt"); - LiteVCS.commit(fullPath, "commit #2"); + liteVCS.add("a.txt"); + liteVCS.commit("commit #2"); - LiteVCS.switchBranch(fullPath, "br"); + liteVCS.switchBranch("br"); fillFile(fullPath, "b.txt"); String resultStateB = hash(fullPath, "b.txt"); - LiteVCS.add(fullPath, "b.txt"); - LiteVCS.commit(fullPath, "commit3"); + liteVCS.add("b.txt"); + liteVCS.commit("commit3"); - LiteVCS.switchBranch(fullPath, "master"); + liteVCS.switchBranch("master"); assertEquals(resultStateA, hash(fullPath, "a.txt")); assertEquals(initialStateB, hash(fullPath, "b.txt")); - LiteVCS.mergeBranch(fullPath,"br", "mesage"); - LiteVCS.reset(fullPath); + liteVCS.mergeBranch("br", "mesage"); + liteVCS.reset(); assertEquals(resultStateA, hash(fullPath, "a.txt")); diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java index baacb3d..bae42ff 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java @@ -21,7 +21,7 @@ public class PathManager extends DataManager { @NotNull @Override - public VersionNode getVersionNode(@NotNull String id) { + public VersionNode fetchVersionNode(@NotNull String id) { int i = Integer.parseInt(id); return versionNodes.get(i); } From 24e2e39bb7acd5d811a0b1cea874f2500609a552 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Sun, 9 Apr 2017 06:47:46 +0300 Subject: [PATCH 13/30] Minor bug fixed --- src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java index 585a8f3..c51b9a6 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java @@ -18,7 +18,7 @@ public class DataManager { private static final String ROOT_DIRECTORY_NAME = ".liteVCS"; - private static final String PATH_TO_VERSIONS_FILES = ROOT_DIRECTORY_NAME + "\\versions"; + private static final String PATH_TO_VERSIONS_FILES = concat(ROOT_DIRECTORY_NAME, "versions"); private static final String PATH_TO_CONTENT_DESCRIPTORS_FILES = concat(ROOT_DIRECTORY_NAME, "descriptors"); private static final String PATH_TO_COMMITS_FILES = concat(ROOT_DIRECTORY_NAME, "commits"); private static final String PATH_TO_SAVED_FILES = concat(ROOT_DIRECTORY_NAME, "files"); From 28e439a781e3e6272526a789290c539cd8f7f651 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Sun, 9 Apr 2017 07:42:10 +0300 Subject: [PATCH 14/30] Tests improved --- .../lobanov/liteVCS/logic/AlgorithmsTest.java | 50 +++++ .../lobanov/liteVCS/logic/FakeManager.java | 33 ---- .../lobanov/liteVCS/logic/LiteVCSTest.java | 53 +++-- .../lobanov/liteVCS/logic/PathManager.java | 28 --- .../liteVCS/logic/VirtualDataManager.java | 187 ++++++++++++++++++ .../lobanov/liteVCS/logic/VirtualFile.java | 38 ++++ 6 files changed, 299 insertions(+), 90 deletions(-) delete mode 100644 src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java delete mode 100644 src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java create mode 100644 src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java create mode 100644 src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualFile.java diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/AlgorithmsTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/AlgorithmsTest.java index 40ac824..e89cbdb 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/AlgorithmsTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/AlgorithmsTest.java @@ -1,8 +1,11 @@ package ru.spbau.lobanov.liteVCS.logic; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import ru.spbau.lobanov.liteVCS.primitives.VersionNode; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import static org.junit.Assert.*; @@ -75,4 +78,51 @@ public void allParentsSmallDataTest() throws DataManager.LostFileException, Data assertSame(answer2[i], parents2.get(i)); } } + + private static class PathManager extends DataManager { + + private final ArrayList versionNodes = new ArrayList<>(); + + PathManager(int size) { + super(""); + try { + versionNodes.add(Algorithms.createRootNode("0", "")); + for (int i = 1; i < size; i++) { + versionNodes.add(Algorithms.createVersionNode("", (i - 1) + "", this)); + } + } catch (Exception ignored){} + } + + @NotNull + @Override + public VersionNode fetchVersionNode(@NotNull String id) { + int i = Integer.parseInt(id); + return versionNodes.get(i); + } + } + + private static class FakeManager extends DataManager { + private final HashMap map = new HashMap<>(); + + FakeManager() { + super(""); + } + + void addVersionNode(String parentID) throws BrokenFileException, LostFileException { + VersionNode versionNode; + if (parentID == null) { + versionNode = Algorithms.createRootNode("0", ""); + } else { + versionNode = Algorithms.createVersionNode("", parentID, this); + } + map.put(map.size() + "", versionNode); + } + + @NotNull + @Override + public VersionNode fetchVersionNode(@NotNull String id) { + return map.get(id); + } + + } } \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java deleted file mode 100644 index 4896781..0000000 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/FakeManager.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.spbau.lobanov.liteVCS.logic; - -import org.jetbrains.annotations.NotNull; -import ru.spbau.lobanov.liteVCS.primitives.VersionNode; - -import java.util.HashMap; - -/** - * Created by Артём on 23.03.2017. - */ -public class FakeManager extends DataManager { - private final HashMap map = new HashMap<>(); - - FakeManager() { - super(""); - } - - public void addVersionNode(String parentID) throws BrokenFileException, LostFileException { - VersionNode versionNode; - if (parentID == null) { - versionNode = Algorithms.createRootNode("0", ""); - } else { - versionNode = Algorithms.createVersionNode("", parentID, this); - } - map.put(map.size() + "", versionNode); - } - - @NotNull - @Override - public VersionNode fetchVersionNode(@NotNull String id) { - return map.get(id); - } -} diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java index ab5cfa4..cf542d1 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java @@ -44,42 +44,37 @@ private static String hash(String fullPath, String name) { @Test public void addBranchSwitchChangeSwitchMerge() throws Exception { - String folder = randomWorkspace(); - String fullPath = Paths.get(workspace, folder).toString(); - Files.createParentDirs(Paths.get(workspace, folder).toFile()); - LiteVCS liteVCS = new LiteVCS(fullPath); + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); liteVCS.init(); - fillFile(fullPath, "a.txt"); - String initialState = hash(fullPath, "a.txt"); + dataManager.writeFile("a.txt", "versiona1"); + String initialState = dataManager.hash("a.txt"); liteVCS.add("a.txt"); liteVCS.commit("commit #1"); liteVCS.createBranch("br"); liteVCS.switchBranch("br"); - fillFile(fullPath, "a.txt"); - String lastState = hash(fullPath, "a.txt"); + dataManager.writeFile("a.txt", "versiona2"); + String lastState = dataManager.hash("a.txt"); liteVCS.add("a.txt"); liteVCS.commit("commit2"); liteVCS.switchBranch("master"); - assertEquals(initialState, hash(fullPath, "a.txt")); + assertEquals(initialState, dataManager.hash("a.txt")); liteVCS.mergeBranch("br", "mesage"); liteVCS.reset(); - assertEquals(lastState, hash(fullPath, "a.txt")); + assertEquals(lastState, dataManager.hash("a.txt")); } @Test public void addBranchChangeSwitchChangeSwitchMerge() throws Exception { - String folder = randomWorkspace(); - String fullPath = Paths.get(workspace, folder).toString(); - Files.createParentDirs(Paths.get(workspace, folder).toFile()); - LiteVCS liteVCS = new LiteVCS(fullPath); + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); liteVCS.init(); - String initialStateA, initialStateB; - do { - fillFile(fullPath, "a.txt"); - initialStateA = hash(fullPath, "a.txt"); - fillFile(fullPath, "b.txt"); - initialStateB = hash(fullPath, "b.txt"); - } while (initialStateA.equals(initialStateB)); + + dataManager.writeFile("a.txt", "versiona1"); + String initialStateA = dataManager.hash("a.txt"); + + dataManager.writeFile("b.txt", "versionb1"); + String initialStateB = dataManager.hash("b.txt"); liteVCS.add("a.txt"); liteVCS.add("b.txt"); @@ -87,27 +82,27 @@ public void addBranchChangeSwitchChangeSwitchMerge() throws Exception { liteVCS.createBranch("br"); - fillFile(fullPath, "a.txt"); - String resultStateA = hash(fullPath, "a.txt"); + dataManager.writeFile("a.txt", "versiona2"); + String resultStateA = dataManager.hash("a.txt"); liteVCS.add("a.txt"); liteVCS.commit("commit #2"); liteVCS.switchBranch("br"); - fillFile(fullPath, "b.txt"); - String resultStateB = hash(fullPath, "b.txt"); + dataManager.writeFile("b.txt", "versionb2"); + String resultStateB = dataManager.hash("b.txt"); liteVCS.add("b.txt"); liteVCS.commit("commit3"); liteVCS.switchBranch("master"); - assertEquals(resultStateA, hash(fullPath, "a.txt")); - assertEquals(initialStateB, hash(fullPath, "b.txt")); + assertEquals(resultStateA, dataManager.hash("a.txt")); + assertEquals(initialStateB, dataManager.hash("b.txt")); liteVCS.mergeBranch("br", "mesage"); liteVCS.reset(); - assertEquals(resultStateA, hash(fullPath, "a.txt")); - assertEquals(resultStateB, hash(fullPath, "b.txt")); + assertEquals(resultStateA, dataManager.hash("a.txt")); + assertEquals(resultStateB, dataManager.hash("b.txt")); } } \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java deleted file mode 100644 index bae42ff..0000000 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/PathManager.java +++ /dev/null @@ -1,28 +0,0 @@ -package ru.spbau.lobanov.liteVCS.logic; - -import org.jetbrains.annotations.NotNull; -import ru.spbau.lobanov.liteVCS.primitives.VersionNode; - -import java.util.ArrayList; - -public class PathManager extends DataManager { - - private final ArrayList versionNodes = new ArrayList<>(); - - PathManager(int size) { - super(""); - try { - versionNodes.add(Algorithms.createRootNode("0", "")); - for (int i = 1; i < size; i++) { - versionNodes.add(Algorithms.createVersionNode("", (i - 1) + "", this)); - } - } catch (Exception ignored){} - } - - @NotNull - @Override - public VersionNode fetchVersionNode(@NotNull String id) { - int i = Integer.parseInt(id); - return versionNodes.get(i); - } -} diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java new file mode 100644 index 0000000..7c782b2 --- /dev/null +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java @@ -0,0 +1,187 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import org.jetbrains.annotations.NotNull; +import ru.spbau.lobanov.liteVCS.primitives.*; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class VirtualDataManager extends DataManager { + + private static final String ROOT_VERSION_NODE_ID = "root"; + + private boolean isInitialized; + private HashMap branches = new HashMap<>(); + private HashMap commits = new HashMap<>(); + private HashMap descriptors = new HashMap<>(); + private HashMap versions = new HashMap<>(); + private HashMap files = new HashMap<>(); + private Header header; + private ContentDescriptor stage; + private HashMap workingCopy = new HashMap<>(); + + + VirtualDataManager() { + super(""); + } + + private static String createRandomKey(Set used) { + String key; + do { + key = "khfd" + Math.random() + "efd"; + } while (used.contains(key)); + return key; + } + + public static String hash(File file) { + return "fl" + file.hashCode(); + } + + void initRepository() throws RecreatingRepositoryException { + if (isInitialized) { + throw new RecreatingRepositoryException(""); + } + isInitialized = true; + try { + String initialDescriptorID = addContentDescriptor(ContentDescriptor.EMPTY); + String initialCommitID = addCommit(new Commit(initialDescriptorID, "Initial commit", + System.currentTimeMillis(), "lVCS")); + VersionNode start = Algorithms.createRootNode(ROOT_VERSION_NODE_ID, initialCommitID); + versions.put(ROOT_VERSION_NODE_ID, start); + Branch master = new Branch(ROOT_VERSION_NODE_ID, "master"); + addBranch(master); + Header header = new Header("Unknown", master.getName()); + putHeader(header); + putStage(ContentDescriptor.EMPTY); + } catch (RepositoryNotInitializedException e) { + throw new Error(""); + } + } + + @NotNull + VersionNode fetchVersionNode(@NotNull String id) throws LostFileException, BrokenFileException { + return versions.get(id); + } + + @NotNull + String addVersionNode(@NotNull VersionNode versionNode) throws RepositoryNotInitializedException { + String id = createRandomKey(versions.keySet()); + versions.put(id, versionNode); + return id; + } + + @NotNull + ContentDescriptor fetchContentDescriptor(@NotNull String id) throws LostFileException, BrokenFileException { + return descriptors.get(id); + } + + @NotNull + String addContentDescriptor(@NotNull ContentDescriptor contentDescriptor) throws RepositoryNotInitializedException { + String id = createRandomKey(descriptors.keySet()); + descriptors.put(id, contentDescriptor); + return id; + } + + @NotNull + Commit fetchCommit(@NotNull String id) throws LostFileException, BrokenFileException { + return commits.get(id); + } + + @NotNull + String addCommit(@NotNull Commit commit) throws RepositoryNotInitializedException { + String id = createRandomKey(commits.keySet()); + commits.put(id, commit); + return id; + } + + @NotNull + File fetchFile(@NotNull String id) throws LostFileException { + return files.get(id); + } + + @NotNull + String addFile(@NotNull File file) throws RepositoryNotInitializedException { + String id = "fl" + file.hashCode(); + files.put(id, (VirtualFile) file); + return id; + } + + void addBranch(@NotNull Branch branch) throws RepositoryNotInitializedException { + branches.put(branch.getName(), branch); + } + + @NotNull + Branch fetchBranch(@NotNull String name) throws LostFileException, BrokenFileException { + return branches.get(name); + } + + boolean hasBranch(@NotNull String name) { + return branches.containsKey(name); + } + + void removeBranch(@NotNull String name) throws LostFileException { + branches.remove(name); + } + @NotNull + Header getHeader() throws LostFileException, BrokenFileException { + return header; + } + + void putHeader(@NotNull Header header) throws RepositoryNotInitializedException { + this.header = header; + } + + @NotNull + ContentDescriptor getStage() throws LostFileException, BrokenFileException { + return stage; + } + + void putStage(@NotNull ContentDescriptor stage) throws RepositoryNotInitializedException { + this.stage = stage; + } + + void loadFiles(@NotNull String descriptorID) throws BrokenFileException, LostFileException, IOException { + ContentDescriptor descriptor = fetchContentDescriptor(descriptorID); + for (Map.Entry pair : descriptor.getFiles().entrySet()) { + workingCopy.put(pair.getKey(), fetchFile(pair.getValue())); + } + } + + void clearWorkingCopy() { + workingCopy.clear(); + } + + /** + * Method allow to clear work space be removing all files including ROOT_DIRECTORY + * + * @throws RepositoryNotInitializedException if there was no repository + */ + void uninstallRepository() throws RepositoryNotInitializedException { + workingCopy.clear(); + branches.clear(); + files.clear(); + versions.clear(); + commits.clear(); + descriptors.clear(); + header = null; + stage = null; + isInitialized = false; + } + + @NotNull + File getFile(@NotNull String filename) { + return workingCopy.get(filename); + } + + void writeFile(String filename, String value) { + VirtualFile f = new VirtualFile(value); + workingCopy.put(filename, f); + } + + String hash(String filename) { + return "" + workingCopy.get(filename).hashCode(); + } +} diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualFile.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualFile.java new file mode 100644 index 0000000..0943e79 --- /dev/null +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualFile.java @@ -0,0 +1,38 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +public class VirtualFile extends File { + + private String value; + + public VirtualFile(@NotNull String value) { + super(""); + this.value = value; + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj.getClass() != VirtualFile.class) + return false; + VirtualFile vf = (VirtualFile) obj; + return value.equals(vf.value); + } + + @Override + public boolean exists() { + return true; + } + + @Override + public boolean isFile() { + return true; + } +} From 536072d3f920b6f941df4d444dc6695143327518 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Sun, 9 Apr 2017 08:27:33 +0300 Subject: [PATCH 15/30] Small tests added --- .../liteVCS/logic/DataManagerTest.java | 34 +++ .../liteVCS/logic/LiteVCSGeneralTests.java | 80 +++++++ .../lobanov/liteVCS/logic/LiteVCSTest.java | 221 ++++++++++++------ .../liteVCS/logic/VirtualDataManager.java | 18 +- .../lobanov/liteVCS/logic/VirtualFile.java | 4 + 5 files changed, 283 insertions(+), 74 deletions(-) create mode 100644 src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java index 64b0635..59f4dc5 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java @@ -1,7 +1,41 @@ package ru.spbau.lobanov.liteVCS.logic; +import com.google.common.hash.Hashing; +import com.google.common.io.Files; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; + import static org.junit.Assert.*; public class DataManagerTest { + private static final String workspace = "test_workspace"; + + private static String randomWorkspace() { + Path path; + do { + path = Paths.get(workspace, Double.toHexString(Math.random())); + } while (path.toFile().exists()); + return path.toString(); + } + + private static void fillFile(String fullPath, String name) throws FileNotFoundException { + PrintWriter out = new PrintWriter(Paths.get(fullPath, name).toFile()); + out.write(String.valueOf(Math.random())); + out.write(String.valueOf(Math.random())); + out.close(); + } + private static String hash(String fullPath, String name) { + String hash; + try { + hash = Files.hash(Paths.get(fullPath, name).toFile(), Hashing.sha256()).toString(); + } catch (IOException e) { + throw new Error("Unknown exception during hash creating"); + } + return hash; + } } \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java new file mode 100644 index 0000000..2f9f653 --- /dev/null +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java @@ -0,0 +1,80 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import com.google.common.hash.Hashing; +import com.google.common.io.Files; +import org.junit.Test; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.Assert.assertEquals; + +public class LiteVCSGeneralTests { + + @Test + public void addBranchSwitchChangeSwitchMerge() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); + liteVCS.init(); + dataManager.writeFile("a.txt", "versiona1"); + String initialState = dataManager.hash("a.txt"); + liteVCS.add("a.txt"); + liteVCS.commit("commit #1"); + liteVCS.createBranch("br"); + liteVCS.switchBranch("br"); + dataManager.writeFile("a.txt", "versiona2"); + String lastState = dataManager.hash("a.txt"); + liteVCS.add("a.txt"); + liteVCS.commit("commit2"); + liteVCS.switchBranch("master"); + assertEquals(initialState, dataManager.hash("a.txt")); + liteVCS.mergeBranch("br", "mesage"); + liteVCS.reset(); + assertEquals(lastState, dataManager.hash("a.txt")); + } + + @Test + public void addBranchChangeSwitchChangeSwitchMerge() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); + liteVCS.init(); + + dataManager.writeFile("a.txt", "versiona1"); + String initialStateA = dataManager.hash("a.txt"); + + dataManager.writeFile("b.txt", "versionb1"); + String initialStateB = dataManager.hash("b.txt"); + + liteVCS.add("a.txt"); + liteVCS.add("b.txt"); + liteVCS.commit("commit #1"); + liteVCS.createBranch("br"); + + + dataManager.writeFile("a.txt", "versiona2"); + String resultStateA = dataManager.hash("a.txt"); + liteVCS.add("a.txt"); + liteVCS.commit("commit #2"); + + liteVCS.switchBranch("br"); + + dataManager.writeFile("b.txt", "versionb2"); + String resultStateB = dataManager.hash("b.txt"); + liteVCS.add("b.txt"); + liteVCS.commit("commit3"); + + liteVCS.switchBranch("master"); + assertEquals(resultStateA, dataManager.hash("a.txt")); + assertEquals(initialStateB, dataManager.hash("b.txt")); + + liteVCS.mergeBranch("br", "mesage"); + liteVCS.reset(); + + + assertEquals(resultStateA, dataManager.hash("a.txt")); + assertEquals(resultStateB, dataManager.hash("b.txt")); + } +} \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java index cf542d1..ac554f0 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java @@ -1,108 +1,199 @@ package ru.spbau.lobanov.liteVCS.logic; -import com.google.common.hash.Hashing; -import com.google.common.io.Files; import org.junit.Test; +import ru.spbau.lobanov.liteVCS.primitives.*; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Path; -import java.nio.file.Paths; +import javax.management.Descriptor; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; +/** + * Created by Артём on 09.04.2017. + */ public class LiteVCSTest { + @Test + public void hello() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); - private static final String workspace = "test_workspace"; - - private static String randomWorkspace() { - Path path; - do { - path = Paths.get(workspace, Double.toHexString(Math.random())); - } while (path.toFile().exists()); - return path.toString(); + liteVCS.init(); + liteVCS.hello("author"); + Header header = dataManager.getHeader(); + assertEquals("author", header.getAuthor()); } - private static void fillFile(String fullPath, String name) throws FileNotFoundException { - PrintWriter out = new PrintWriter(Paths.get(fullPath, name).toFile()); - out.write(String.valueOf(Math.random())); - out.write(String.valueOf(Math.random())); - out.close(); - } + @Test + public void add() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); - private static String hash(String fullPath, String name) { - String hash; - try { - hash = Files.hash(Paths.get(fullPath, name).toFile(), Hashing.sha256()).toString(); - } catch (IOException e) { - throw new Error("Unknown exception during hash creating"); - } - return hash; + liteVCS.init(); + dataManager.writeFile("a.txt", "data"); + liteVCS.add("a.txt"); + ContentDescriptor stage = dataManager.getStage(); + String id = stage.getFiles().get("a.txt"); + assertNotNull(id); + VirtualFile f = (VirtualFile) dataManager.fetchFile(id); + assertEquals("data", f.getValue()); } + @Test + public void commit() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); + + liteVCS.init(); + dataManager.writeFile("a.txt", "data"); + liteVCS.add("a.txt"); + liteVCS.commit("message"); + assertEquals(2, dataManager.commits.size()); + } @Test - public void addBranchSwitchChangeSwitchMerge() throws Exception { + public void logs() throws Exception { VirtualDataManager dataManager = new VirtualDataManager(); LiteVCS liteVCS = new LiteVCS(dataManager); + liteVCS.init(); - dataManager.writeFile("a.txt", "versiona1"); - String initialState = dataManager.hash("a.txt"); + dataManager.writeFile("a.txt", "data"); liteVCS.add("a.txt"); - liteVCS.commit("commit #1"); - liteVCS.createBranch("br"); - liteVCS.switchBranch("br"); - dataManager.writeFile("a.txt", "versiona2"); - String lastState = dataManager.hash("a.txt"); + liteVCS.commit("message"); + dataManager.writeFile("b.txt", "data2"); + liteVCS.add("b.txt"); + liteVCS.commit("message2"); + + Commit commit1 = liteVCS.logs("2").get(1); + Commit commit2 = liteVCS.logs("2").get(0); + + assertEquals("message", commit1.getCommitMessage()); + assertEquals("message2", commit2.getCommitMessage()); + } + + @Test + public void createBranch() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); + + liteVCS.init(); + dataManager.writeFile("a.txt", "data"); liteVCS.add("a.txt"); - liteVCS.commit("commit2"); - liteVCS.switchBranch("master"); - assertEquals(initialState, dataManager.hash("a.txt")); - liteVCS.mergeBranch("br", "mesage"); - liteVCS.reset(); - assertEquals(lastState, dataManager.hash("a.txt")); + liteVCS.commit("message"); + + liteVCS.createBranch("branch"); + assertEquals(2, dataManager.branches.size()); + Branch master = dataManager.branches.get("master"); + Branch branch = dataManager.branches.get("branch"); + assertEquals(master.getVersionNodeID(), branch.getVersionNodeID()); } @Test - public void addBranchChangeSwitchChangeSwitchMerge() throws Exception { + public void removeBranch() throws Exception { VirtualDataManager dataManager = new VirtualDataManager(); LiteVCS liteVCS = new LiteVCS(dataManager); + liteVCS.init(); + dataManager.writeFile("a.txt", "data"); + liteVCS.add("a.txt"); + liteVCS.commit("message"); - dataManager.writeFile("a.txt", "versiona1"); - String initialStateA = dataManager.hash("a.txt"); + liteVCS.createBranch("branch"); + assertEquals(2, dataManager.branches.size()); + liteVCS.removeBranch("branch"); + assertEquals(1, dataManager.branches.size()); + } - dataManager.writeFile("b.txt", "versionb1"); - String initialStateB = dataManager.hash("b.txt"); + @Test + public void mergeBranch() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); + liteVCS.init(); + + liteVCS.createBranch("branch"); + + dataManager.writeFile("a.txt", "data"); liteVCS.add("a.txt"); + liteVCS.commit("message"); + + liteVCS.switchBranch("branch"); + dataManager.writeFile("b.txt", "data2"); liteVCS.add("b.txt"); - liteVCS.commit("commit #1"); - liteVCS.createBranch("br"); + liteVCS.commit("message2"); + liteVCS.mergeBranch("master", "message3"); - dataManager.writeFile("a.txt", "versiona2"); - String resultStateA = dataManager.hash("a.txt"); - liteVCS.add("a.txt"); - liteVCS.commit("commit #2"); + assertEquals(2, dataManager.branches.size()); + String versionNodeID = dataManager.branches.get("branch").getVersionNodeID(); + String commitID = dataManager.versions.get(versionNodeID).getCommitID(); + String descriptorID = dataManager.commits.get(commitID).getContentDescriptorID(); + ContentDescriptor descriptor = dataManager.descriptors.get(descriptorID); + assertEquals("data".hashCode(), dataManager.fetchFile(descriptor.getFiles().get("a.txt")).hashCode()); + assertEquals("data2".hashCode(), dataManager.fetchFile(descriptor.getFiles().get("b.txt")).hashCode()); + } - liteVCS.switchBranch("br"); + @Test + public void switchBranch() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); - dataManager.writeFile("b.txt", "versionb2"); - String resultStateB = dataManager.hash("b.txt"); - liteVCS.add("b.txt"); - liteVCS.commit("commit3"); + liteVCS.init(); + liteVCS.createBranch("branch"); + liteVCS.switchBranch("branch"); + + dataManager.writeFile("a.txt", "data"); + liteVCS.add("a.txt"); + liteVCS.commit("message"); + + + assertEquals(1, dataManager.workingCopy.size()); liteVCS.switchBranch("master"); - assertEquals(resultStateA, dataManager.hash("a.txt")); - assertEquals(initialStateB, dataManager.hash("b.txt")); + assertEquals(0, dataManager.workingCopy.size()); + } + + @Test + public void reset() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); + + liteVCS.init(); + + dataManager.writeFile("a.txt", "data"); + liteVCS.add("a.txt"); + liteVCS.commit("message"); + + dataManager.writeFile("a.txt", "data2"); + liteVCS.add("a.txt"); + dataManager.writeFile("b.txt", "data"); - liteVCS.mergeBranch("br", "mesage"); liteVCS.reset(); + assertEquals(1, dataManager.workingCopy.size()); + assertEquals("data".hashCode() + "", dataManager.hash("a.txt")); + } - assertEquals(resultStateA, dataManager.hash("a.txt")); - assertEquals(resultStateB, dataManager.hash("b.txt")); + @Test + public void checkout() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); + + liteVCS.init(); + + dataManager.writeFile("a.txt", "data"); + liteVCS.add("a.txt"); + liteVCS.commit("message"); + + String versionNodeID = dataManager.branches.get("master").getVersionNodeID(); + String commitID = dataManager.versions.get(versionNodeID).getCommitID(); + String descriptorID = dataManager.commits.get(commitID).getContentDescriptorID(); + + dataManager.writeFile("a.txt", "data2"); + liteVCS.add("a.txt"); + dataManager.writeFile("b.txt", "data"); + + liteVCS.checkout(descriptorID); + assertEquals(1, dataManager.workingCopy.size()); + assertEquals("data".hashCode() + "", dataManager.hash("a.txt")); } + } \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java index 7c782b2..c75a92c 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java @@ -13,15 +13,15 @@ public class VirtualDataManager extends DataManager { private static final String ROOT_VERSION_NODE_ID = "root"; - private boolean isInitialized; - private HashMap branches = new HashMap<>(); - private HashMap commits = new HashMap<>(); - private HashMap descriptors = new HashMap<>(); - private HashMap versions = new HashMap<>(); - private HashMap files = new HashMap<>(); - private Header header; - private ContentDescriptor stage; - private HashMap workingCopy = new HashMap<>(); + boolean isInitialized; + HashMap branches = new HashMap<>(); + HashMap commits = new HashMap<>(); + HashMap descriptors = new HashMap<>(); + HashMap versions = new HashMap<>(); + HashMap files = new HashMap<>(); + Header header; + ContentDescriptor stage; + HashMap workingCopy = new HashMap<>(); VirtualDataManager() { diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualFile.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualFile.java index 0943e79..9a37153 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualFile.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualFile.java @@ -35,4 +35,8 @@ public boolean exists() { public boolean isFile() { return true; } + + public String getValue() { + return value; + } } From 13ec8bd519d4b13b395db2c0515c0fe7b0083a46 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Sun, 9 Apr 2017 08:37:04 +0300 Subject: [PATCH 16/30] Rename method <> to <> --- .../ru/spbau/lobanov/liteVCS/ConsoleWorker.java | 4 ++-- .../ru/spbau/lobanov/liteVCS/logic/LiteVCS.java | 2 +- .../lobanov/liteVCS/logic/LiteVCSTest.java | 17 ++++++++--------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java index ec21c4d..998361d 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java @@ -84,7 +84,7 @@ private static void execute(String command, String[] args) throws VersionControl break; case "logs": checkArguments(1, args); - printLogs(liteVCS.logs(args[0])); + printHistory(liteVCS.history(args[0])); break; case "hello": checkArguments(1, args); @@ -134,7 +134,7 @@ public static void main(String[] args) { } } - private static void printLogs(List commits) { + private static void printHistory(List commits) { System.out.println("Local history:"); for (int i = commits.size() - 1; i >= 0; i--) { Commit commit = commits.get(i); diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java index a41d2a9..b51952a 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java @@ -119,7 +119,7 @@ public void commit(@NotNull String message) throws BrokenFileException, * @throws BrokenFileException if file contained one of interesting object was not found */ @NotNull - public List logs(@NotNull String lengthLimit) + public List history(@NotNull String lengthLimit) throws BrokenFileException, LostFileException { int limit; try { diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java index ac554f0..243863f 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java @@ -1,15 +1,14 @@ package ru.spbau.lobanov.liteVCS.logic; import org.junit.Test; -import ru.spbau.lobanov.liteVCS.primitives.*; +import ru.spbau.lobanov.liteVCS.primitives.Branch; +import ru.spbau.lobanov.liteVCS.primitives.Commit; +import ru.spbau.lobanov.liteVCS.primitives.ContentDescriptor; +import ru.spbau.lobanov.liteVCS.primitives.Header; -import javax.management.Descriptor; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.*; - -/** - * Created by Артём on 09.04.2017. - */ public class LiteVCSTest { @Test public void hello() throws Exception { @@ -62,8 +61,8 @@ public void logs() throws Exception { liteVCS.add("b.txt"); liteVCS.commit("message2"); - Commit commit1 = liteVCS.logs("2").get(1); - Commit commit2 = liteVCS.logs("2").get(0); + Commit commit1 = liteVCS.history("2").get(1); + Commit commit2 = liteVCS.history("2").get(0); assertEquals("message", commit1.getCommitMessage()); assertEquals("message2", commit2.getCommitMessage()); From 3015ea19b3d1494b4fc604248747ee0cede14ca3 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Sun, 9 Apr 2017 09:32:30 +0300 Subject: [PATCH 17/30] More tests for god of tests --- .../liteVCS/logic/DataManagerTest.java | 142 +++++++++++++++--- .../liteVCS/logic/LiteVCSGeneralTests.java | 9 -- 2 files changed, 117 insertions(+), 34 deletions(-) diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java index 59f4dc5..1772ed7 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java @@ -1,12 +1,11 @@ package ru.spbau.lobanov.liteVCS.logic; -import com.google.common.hash.Hashing; -import com.google.common.io.Files; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import ru.spbau.lobanov.liteVCS.primitives.*; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Path; +import java.io.File; import java.nio.file.Paths; import static org.junit.Assert.*; @@ -14,28 +13,121 @@ public class DataManagerTest { private static final String workspace = "test_workspace"; - private static String randomWorkspace() { - Path path; - do { - path = Paths.get(workspace, Double.toHexString(Math.random())); - } while (path.toFile().exists()); - return path.toString(); + @Before + public void initRepository() throws Exception { + DataManager dataManager = new DataManager(workspace); + dataManager.initRepository(); + assertNotNull(dataManager.getHeader()); + assertNotNull(dataManager.getStage()); + File dir = Paths.get(workspace, ".liteVCS").toFile(); + File[] files = dir.listFiles(); + assertNotNull(files); + assertEquals(7, files.length); } - private static void fillFile(String fullPath, String name) throws FileNotFoundException { - PrintWriter out = new PrintWriter(Paths.get(fullPath, name).toFile()); - out.write(String.valueOf(Math.random())); - out.write(String.valueOf(Math.random())); - out.close(); + @After + public void clearWorkspace() throws DataManager.RepositoryNotInitializedException { + DataManager dataManager = new DataManager(workspace); + dataManager.clearWorkingCopy(); + dataManager.uninstallRepository(); + File[] files = new File(workspace).listFiles(); + assertNotNull(files); + assertEquals(0, files.length); } - private static String hash(String fullPath, String name) { - String hash; - try { - hash = Files.hash(Paths.get(fullPath, name).toFile(), Hashing.sha256()).toString(); - } catch (IOException e) { - throw new Error("Unknown exception during hash creating"); - } - return hash; + @Test + public void addAndFetchVersionNode() throws Exception { + DataManager dataManager = new DataManager(workspace); + VersionNode versionNode = new VersionNode("id", 2, new String[0]); + String versionID = dataManager.addVersionNode(versionNode); + VersionNode copy = dataManager.fetchVersionNode(versionID); + assertEquals(versionNode.getCommitID(), copy.getCommitID()); + assertEquals(versionNode.getDeepLevel(), copy.getDeepLevel()); } + + @Test + public void addAndFetchContentDescriptor() throws Exception { + DataManager dataManager = new DataManager(workspace); + ContentDescriptor descriptor = ContentDescriptor + .builder() + .addFile("1", "2") + .addFile("hello", "world") + .build(); + String descriptorID = dataManager.addContentDescriptor(descriptor); + ContentDescriptor copy = dataManager.fetchContentDescriptor(descriptorID); + assertEquals(descriptor.getFiles(), copy.getFiles()); + } + + @Test + public void addAndFetchCommit() throws Exception { + DataManager dataManager = new DataManager(workspace); + Commit commit = new Commit("id", "mes", 19980108, "a"); + String commitID = dataManager.addCommit(commit); + Commit commit1 = dataManager.fetchCommit(commitID); + assertEquals(commit.getContentDescriptorID(), commit1.getContentDescriptorID()); + assertEquals(commit.getAuthor(), commit1.getAuthor()); + assertEquals(commit.getCommitMessage(), commit1.getCommitMessage()); + } + + @Test + public void fetchFile() throws Exception { + + } + + @Test + public void addFile() throws Exception { + + } + + @Test + public void addAndFetchBranch() throws Exception { + DataManager dataManager = new DataManager(workspace); + Branch branch = new Branch("id", "mes"); + dataManager.addBranch(branch); + Branch branch1 = dataManager.fetchBranch(branch.getName()); + assertEquals(branch.getName(), branch1.getName()); + assertEquals(branch.getVersionNodeID(), branch1.getVersionNodeID()); + } + + @Test + public void hasAndRemoveBranch() throws Exception { + DataManager dataManager = new DataManager(workspace); + assertFalse(dataManager.hasBranch("branch")); + dataManager.addBranch(new Branch("d", "branch")); + assertTrue(dataManager.hasBranch("branch")); + dataManager.removeBranch("branch"); + assertFalse(dataManager.hasBranch("branch")); + } + + @Test + public void putAndGetHeader() throws Exception { + DataManager dataManager = new DataManager(workspace); + Header header = new Header("a", "branch"); + dataManager.putHeader(header); + Header header1 = dataManager.getHeader(); + assertEquals(header.getAuthor(), header1.getAuthor()); + assertEquals(header.getCurrentBranchName(), header1.getCurrentBranchName()); + } + + @Test + public void putAndGetStage() throws Exception { + DataManager dataManager = new DataManager(workspace); + ContentDescriptor stage = ContentDescriptor + .builder() + .addFile("1", "2") + .addFile("hello", "world") + .build(); + dataManager.putStage(stage); + ContentDescriptor stage1 = dataManager.getStage(); + assertEquals(stage.getFiles(), stage1.getFiles()); + } + + @Test + public void getFile() throws Exception { + DataManager dataManager = new DataManager(workspace); + File file = dataManager.getFile(Paths.get(".liteVCS", "stage.lVCS").toString()); + assertTrue(file.exists()); + assertEquals("stage.lVCS", file.getName()); + } + } \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java index 2f9f653..c283029 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java @@ -1,15 +1,7 @@ package ru.spbau.lobanov.liteVCS.logic; -import com.google.common.hash.Hashing; -import com.google.common.io.Files; import org.junit.Test; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Path; -import java.nio.file.Paths; - import static org.junit.Assert.assertEquals; public class LiteVCSGeneralTests { @@ -43,7 +35,6 @@ public void addBranchChangeSwitchChangeSwitchMerge() throws Exception { liteVCS.init(); dataManager.writeFile("a.txt", "versiona1"); - String initialStateA = dataManager.hash("a.txt"); dataManager.writeFile("b.txt", "versionb1"); String initialStateB = dataManager.hash("b.txt"); From 770a3ff387bf604c2b42bc897f9d27d46795f5f9 Mon Sep 17 00:00:00 2001 From: Artyom Lobanov Date: Sun, 9 Apr 2017 10:01:47 +0300 Subject: [PATCH 18/30] Update README.txt Added information about lite VCS --- README.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.txt b/README.txt index 5fe4746..40d474b 100644 --- a/README.txt +++ b/README.txt @@ -1,3 +1,25 @@ +DataManager - класс-прослойка между логикой системы контроля версий и файловой системой. Умеет создавать всякие служебные папки, сохранять в них копии файлов, служебную информацию; очищать рабочую копию, откатываться к заданной версии. + +LiteVCS - основной класс, с которым будет работать пользователь библиотеки. Описывает логику работы. Большинство методов реализуют какую-то конкретную команду из списка ниже. Активно взаимодействует с DataManager. + +Algorithms - класс со статическими методами реализующими алгоритм поиска LCA с помощью двоичных подъёмов. Также может найти всех предков заданной версии. Активно взаимодействует с VersionNode, DataManager-ом и больше ни с чем. + +ConsoleWorker - консольное приложение, которое парсит команду и вызывает метод LiteVCS с нужными аргументами. + +Примитивные классы: +ContentDescriptor хранит мапу из относительных путей в id версий фйалов. По id ContentDescriptor-а можно днлать checkout. Также используется для хранения Stage - списка версий файлов, которые надо добавить к следующему коммиту. + +Commit хранит имя автора коммита, время его создания, комментарий к коммиту и id ContentDescriptor-а который задаёт состояние файлов в репозитории. + +VersionNode хранит id коммита, с которым она ассоциирована, а также таблицу предков и глубину в дереве версий, используемых алгоритмом двоичного подъёма. Отделён от Commit чтобы облегчить VersionNode, которые будут в большом количестве читаться в процессе поиска общего предка. + +Branch хранит id VersionNode, которая считается полседней версией этой ветки. Также ветка имеет имя. + +Header хранит имя последнего указанного автора (через hello) (это имя подставляется в создаваемые коммиты) и имя активной ветки. + + + + Список команд: init - Инициализирует репозиторий в текущей папке add [файл] - Сохраняет копию текущего состояния файла, чтобы добавить его к ближайшему коммиту From 22cc6d5c6429f4657d2f5dbd6819135dc31252c5 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Sun, 9 Apr 2017 10:09:12 +0300 Subject: [PATCH 19/30] Now jar should work --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 62d2276..a9b016a 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,11 @@ repositories { mavenCentral() } +jar { + from {configurations.compile.collect { it.isDirectory() ? it : zipTree(it)} } + manifest { attributes 'Main-Class': 'ru.spbau.lobanov.liteVCS.ConsoleWorker' } +} + dependencies { testCompile group: 'junit', name: 'junit', version: '4.11' // https://mvnrepository.com/artifact/com.google.guava/guava From 44d3ba2f9b5d84695b27d72259903338c0150455 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 13 Apr 2017 06:59:15 +0300 Subject: [PATCH 20/30] New features added --- .../spbau/lobanov/liteVCS/ConsoleWorker.java | 39 ++++- .../lobanov/liteVCS/logic/DataManager.java | 140 ++++++++++----- .../spbau/lobanov/liteVCS/logic/LiteVCS.java | 159 ++++++++++++++---- .../liteVCS/primitives/ContentDescriptor.java | 9 +- .../lobanov/liteVCS/primitives/Stage.java | 82 +++++++++ .../liteVCS/logic/DataManagerTest.java | 23 +-- .../liteVCS/logic/LiteVCSGeneralTests.java | 5 +- .../lobanov/liteVCS/logic/LiteVCSTest.java | 56 +++--- .../liteVCS/logic/VirtualDataManager.java | 25 +-- 9 files changed, 397 insertions(+), 141 deletions(-) create mode 100644 src/main/java/ru/spbau/lobanov/liteVCS/primitives/Stage.java diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java index 998361d..2c9f314 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java @@ -5,16 +5,15 @@ import ru.spbau.lobanov.liteVCS.logic.DataManager.RecreatingRepositoryException; import ru.spbau.lobanov.liteVCS.logic.DataManager.RepositoryNotInitializedException; import ru.spbau.lobanov.liteVCS.logic.LiteVCS; -import ru.spbau.lobanov.liteVCS.logic.LiteVCS.ConflictMergeException; -import ru.spbau.lobanov.liteVCS.logic.LiteVCS.RemoveActiveBranchException; -import ru.spbau.lobanov.liteVCS.logic.LiteVCS.UncommittedChangesException; -import ru.spbau.lobanov.liteVCS.logic.LiteVCS.UnknownBranchException; +import ru.spbau.lobanov.liteVCS.logic.LiteVCS.*; import ru.spbau.lobanov.liteVCS.logic.VersionControlSystemException; import ru.spbau.lobanov.liteVCS.primitives.Commit; import java.io.IOException; import java.nio.file.Paths; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; public class ConsoleWorker { @@ -54,9 +53,13 @@ private static void execute(String command, String[] args) throws VersionControl checkArguments(1, args); liteVCS.checkout(args[0]); break; - case "clear": + case "clean": checkArguments(0, args); - liteVCS.clear(); + liteVCS.clean(); + break; + case "status": + checkArguments(0, args); + printStatus(liteVCS.stageStatus(), liteVCS.workingCopyStatus()); break; case "create_branch": checkArguments(1, args); @@ -75,8 +78,12 @@ private static void execute(String command, String[] args) throws VersionControl liteVCS.mergeBranch(args[0], args[1]); break; case "reset": - checkArguments(0, args); - liteVCS.reset(); + checkArguments(1, args); + liteVCS.reset(args[0]); + break; + case "remove": + checkArguments(1, args); + liteVCS.remove(args[0]); break; case "uninstall": checkArguments(0, args); @@ -143,6 +150,22 @@ private static void printHistory(List commits) { } } + + + private static void printStatus(Map stageStatus, Map workingCopyStatus) { + System.out.println("---------------------------------------------"); + System.out.println("Status of repository:"); + System.out.println(" Stage:"); + for (Entry entry : stageStatus.entrySet()) { + System.out.println(" " + entry.getKey() + " status=" + entry.getValue()); + } + System.out.println(" Working copy:"); + for (Entry entry : workingCopyStatus.entrySet()) { + System.out.println(" " + entry.getKey() + " status=" + entry.getValue()); + } + System.out.println("---------------------------------------------"); + } + public static class WrongNumberArgumentsException extends Exception { WrongNumberArgumentsException(String message) { super(message); diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java index c51b9a6..602efd7 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java @@ -8,8 +8,12 @@ import java.io.*; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; import java.util.Map.Entry; import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * This class is adapter between logic part of LiteVCS @@ -62,7 +66,7 @@ void initRepository() throws RecreatingRepositoryException { addBranch(master); Header header = new Header("Unknown", master.getName()); putHeader(header); - putStage(ContentDescriptor.EMPTY); + putStage(Stage.EMPTY); } catch (RepositoryNotInitializedException e) { throw new Error("Unexpected error during repository initialization"); } @@ -157,7 +161,7 @@ String addCommit(@NotNull Commit commit) throws RepositoryNotInitializedExceptio * @throws LostFileException if file contained one of interesting object was corrupted */ @NotNull - File fetchFile(@NotNull String id) throws LostFileException { + private File fetchFile(@NotNull String id) throws LostFileException { File file = Paths.get(workingDirectory, PATH_TO_SAVED_FILES, id).toFile(); if (!file.exists()) { throw new LostFileException("File wasn't found:" + file.getName(), null, file); @@ -168,18 +172,19 @@ File fetchFile(@NotNull String id) throws LostFileException { /** * Method allow to save copy of file in file system * - * @param file object to save + * @param relativePath relative path to file which will bw saved * @return generated id, by which you can fetch that object late * @throws RepositoryNotInitializedException if file creating failed + * @throws NonexistentFileAdditionException if file wasn't found */ @NotNull - String addFile(@NotNull File file) throws RepositoryNotInitializedException { - String hash; - try { - hash = Files.hash(file, Hashing.sha256()).toString() + ".sc"; - } catch (IOException e) { - throw new Error("Unknown exception during hash creating"); + String addFile(@NotNull String relativePath) throws RepositoryNotInitializedException, + NonexistentFileAdditionException { + File file = Paths.get(workingDirectory, relativePath).toFile(); + if (!file.exists() || !file.isFile()) { + throw new NonexistentFileAdditionException("File " + relativePath + " doesn't exist"); } + String hash = hashFile(relativePath); File savedCopy = Paths.get(workingDirectory, PATH_TO_SAVED_FILES, hash).toFile(); try { if (savedCopy.createNewFile()) { @@ -191,6 +196,62 @@ String addFile(@NotNull File file) throws RepositoryNotInitializedException { return hash; } + void removeFile(String relativePath) throws NonexistentFileDeletionException { + File targetFile = Paths.get(workingDirectory, relativePath).toFile(); + if (!targetFile.isFile() || !targetFile.delete()) { + throw new NonexistentFileDeletionException("File " + relativePath + " doesn't exist"); + } + Path path = Paths.get(relativePath).getParent(); + while (path != null) { + File folder = Paths.get(workingDirectory, path.toString()).toFile(); + File[] files = folder.listFiles(); + if (files == null || files.length != 0 || !folder.delete()) { + break; + } + path = path.getParent(); + } + } + + List workingCopyFiles() throws IOException { + List paths = new ArrayList<>(); + File[] files = Paths.get(workingDirectory).toFile().listFiles(); + if (files == null){ + + } + Path mainDirectory = Paths.get(workingDirectory, ROOT_DIRECTORY_NAME); + for (File f : files) { + if (!f.toPath().startsWith(mainDirectory)) { + walkFileTree(f, paths); + } + } + return paths; + } + + private void walkFileTree(File file, List paths) { + if (file.isFile()) { + paths.add(Paths.get(workingDirectory).relativize(file.toPath()).toString()); + } else { + File[] files = file.listFiles(); + if (files == null) { + throw new Error(); + //todo + } + for (File f : files) { + walkFileTree(f, paths); + } + } + } + + String hashFile(String path) { + String hash; + try { + hash = Files.hash(Paths.get(workingDirectory, path).toFile(), Hashing.sha256()).toString() + ".sc"; + } catch (IOException e) { + throw new Error("Unknown exception during hash creating"); + } + return hash; + } + /** * Method allow to save branch in file system * This method doesn't return id because branches are identified by name @@ -263,43 +324,39 @@ void putHeader(@NotNull Header header) throws RepositoryNotInitializedException /** * Method allow to load Stage descriptor * - * @return stage ContentDescriptor + * @return stage current stage * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found */ @NotNull - ContentDescriptor getStage() throws LostFileException, BrokenFileException { - return readObject(Paths.get(workingDirectory, PATH_TO_STAGE), ContentDescriptor.class); + Stage getStage() throws LostFileException, BrokenFileException { + return readObject(Paths.get(workingDirectory, PATH_TO_STAGE), Stage.class); } /** - * Method allow to save Stage descriptor + * Method allow to save Stage * * @param stage object to save * @throws RepositoryNotInitializedException if file saving failed */ - void putStage(@NotNull ContentDescriptor stage) throws RepositoryNotInitializedException { + void putStage(@NotNull Stage stage) throws RepositoryNotInitializedException { writeObject(Paths.get(workingDirectory, PATH_TO_STAGE), stage); } /** - * Method allow to create all files and folder in which - * they are contained, specified by ContentDescriptor + * Clone file from repository to working copy * - * @param descriptorID identifier of ContentDescriptor - * @throws LostFileException if file contained one of interesting object was corrupted - * @throws BrokenFileException if file contained one of interesting object was not found - * @throws IOException if file creating failed because of File System + * @param fileID identifier of saved file + * @param targetPath desired relative path to new copy (including file name) + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws IOException if file creating failed because of File System */ - void loadFiles(@NotNull String descriptorID) throws BrokenFileException, LostFileException, IOException { - ContentDescriptor descriptor = fetchContentDescriptor(descriptorID); - for (Entry pair : descriptor.getFiles().entrySet()) { - File savedCopy = fetchFile(pair.getValue()); - File targetFile = Paths.get(workingDirectory, pair.getKey()).toFile(); - Files.createParentDirs(targetFile); - Files.touch(targetFile); - Files.copy(savedCopy, targetFile); - } + void loadFile(@NotNull String fileID, @NotNull String targetPath) throws LostFileException, IOException { + File savedCopy = fetchFile(fileID); + File targetFile = Paths.get(workingDirectory, targetPath).toFile(); + Files.createParentDirs(targetFile); + Files.touch(targetFile); + Files.copy(savedCopy, targetFile); } /** @@ -340,17 +397,6 @@ void uninstallRepository() throws RepositoryNotInitializedException { } - /** - * Allow to get files from working directory - * - * @param filename target file - * @return File, associated with target file - */ - @NotNull - File getFile(@NotNull String filename) { - return Paths.get(this.workingDirectory, filename).toFile(); - } - /** * Method remove all files from directory * @@ -436,7 +482,7 @@ private static void writeObject(@NotNull Path path, @NotNull Object object) /** * Special method to simplify definition of service paths * - * @param root main directory + * @param root main directory * @param paths relative paths * @return concatenation of paths */ @@ -492,4 +538,16 @@ public static class RecreatingRepositoryException extends VersionControlSystemEx super(message); } } + + public static class NonexistentFileAdditionException extends VersionControlSystemException { + NonexistentFileAdditionException(String message) { + super(message); + } + } + + public static class NonexistentFileDeletionException extends VersionControlSystemException { + NonexistentFileDeletionException(String message) { + super(message); + } + } } diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java index b51952a..4b10461 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java @@ -2,18 +2,17 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import ru.spbau.lobanov.liteVCS.logic.DataManager.BrokenFileException; -import ru.spbau.lobanov.liteVCS.logic.DataManager.LostFileException; -import ru.spbau.lobanov.liteVCS.logic.DataManager.RecreatingRepositoryException; -import ru.spbau.lobanov.liteVCS.logic.DataManager.RepositoryNotInitializedException; +import ru.spbau.lobanov.liteVCS.logic.DataManager.*; import ru.spbau.lobanov.liteVCS.primitives.*; -import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +import static ru.spbau.lobanov.liteVCS.logic.LiteVCS.FileStatus.*; +import static ru.spbau.lobanov.liteVCS.logic.LiteVCS.StageStatus.*; + /** * Special class which provides all the main * functionality of library by static methods @@ -63,22 +62,39 @@ public void init() throws RecreatingRepositoryException, * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized */ - public void add(@NotNull String fileName) - throws RepositoryNotInitializedException, LostFileException, BrokenFileException { - File file = dataManager.getFile(fileName); - ContentDescriptor stage = dataManager.getStage(); - ContentDescriptor updatedStage; - if (!file.isFile()) { - throw new Error("Possible to add files only"); - } - String fileID = dataManager.addFile(file); - updatedStage = ContentDescriptor.builder() - .addAllFiles(stage) + public void add(@NotNull String fileName) throws RepositoryNotInitializedException, + LostFileException, BrokenFileException, NonexistentFileAdditionException { + Stage stage = dataManager.getStage(); + String fileID = dataManager.addFile(fileName); + Stage updatedStage = stage.change() .addFile(fileName, fileID) .build(); dataManager.putStage(updatedStage); } + /** + * Method which mark file as removed at stage and remove that file from working directory + * + * @param fileName relative path to target file + * @throws RepositoryNotInitializedException if repository was not initialized + */ + public void remove(@NotNull String fileName) throws RepositoryNotInitializedException, + LostFileException, BrokenFileException, NonexistentFileDeletionException { + Header header = dataManager.getHeader(); + Branch currentBranch = dataManager.fetchBranch(header.getCurrentBranchName()); + VersionNode currentVersion = dataManager.fetchVersionNode(currentBranch.getVersionNodeID()); + Commit lastCommit = dataManager.fetchCommit(currentVersion.getCommitID()); + ContentDescriptor descriptor = dataManager.fetchContentDescriptor(lastCommit.getContentDescriptorID()); + Stage stage = dataManager.getStage(); + if (descriptor.getFiles().containsKey(fileName) || stage.getChangedFiles().containsKey(fileName)) { + Stage updatedStage = stage.change() + .removeFile(fileName) + .build(); + dataManager.putStage(updatedStage); + } + dataManager.removeFile(fileName); + } + /** * Method which get list of changed from stage, * create Commit and add it to head of current branch @@ -95,11 +111,11 @@ public void commit(@NotNull String message) throws BrokenFileException, VersionNode currentVersion = dataManager.fetchVersionNode(currentBranch.getVersionNodeID()); Commit lastCommit = dataManager.fetchCommit(currentVersion.getCommitID()); ContentDescriptor currentDescriptor = dataManager.fetchContentDescriptor(lastCommit.getContentDescriptorID()); - ContentDescriptor stage = dataManager.getStage(); + Stage stage = dataManager.getStage(); ContentDescriptor updatedDescriptor = ContentDescriptor.builder() - .addAllFiles(currentDescriptor) - .addAllFiles(stage) + .addAll(currentDescriptor) + .addAll(stage) .build(); String descriptorID = dataManager.addContentDescriptor(updatedDescriptor); Commit newCommit = new Commit(descriptorID, message, System.currentTimeMillis(), header.getAuthor()); @@ -108,7 +124,7 @@ public void commit(@NotNull String message) throws BrokenFileException, String versionID = dataManager.addVersionNode(newVersion); Branch updatedBranch = new Branch(versionID, currentBranch.getName()); dataManager.addBranch(updatedBranch); - dataManager.putStage(ContentDescriptor.EMPTY); + dataManager.putStage(Stage.EMPTY); } /** @@ -206,7 +222,7 @@ public void mergeBranch(@NotNull String branchName, @NotNull String message) if (!dataManager.hasBranch(branchName)) { throw new UnknownBranchException("Branch to merge doesn't exist"); } - if (!dataManager.getStage().getFiles().isEmpty()) { + if (!dataManager.getStage().isEmpty()) { throw new UncommittedChangesException("Commit changes before merge"); } String activeVersionID = dataManager.fetchBranch(header.getCurrentBranchName()).getVersionNodeID(); @@ -338,6 +354,15 @@ private ContentDescriptor toContentDescriptor(@NotNull String versionID) return dataManager.fetchContentDescriptor(commit.getContentDescriptorID()); } + @NotNull + private ContentDescriptor getActualDescriptor() throws BrokenFileException, LostFileException { + Header header = dataManager.getHeader(); + Branch currentBranch = dataManager.fetchBranch(header.getCurrentBranchName()); + VersionNode currentVersion = dataManager.fetchVersionNode(currentBranch.getVersionNodeID()); + Commit lastCommit = dataManager.fetchCommit(currentVersion.getCommitID()); + return dataManager.fetchContentDescriptor(lastCommit.getContentDescriptorID()); + } + /** * @param branchName name of interesting branch * @throws LostFileException if file contained one of interesting object was corrupted @@ -355,8 +380,7 @@ public void switchBranch(@NotNull String branchName) if (header.getCurrentBranchName().equals(branchName)) { throw new SwitchOnCurrentBranchException("This branch is already chosen"); } - ContentDescriptor contentDescriptor = dataManager.getStage(); - if (!contentDescriptor.getFiles().isEmpty()) { + if (!dataManager.getStage().isEmpty()) { throw new UncommittedChangesException("Commit changes before switch branch"); } if (!dataManager.hasBranch(branchName)) { @@ -371,25 +395,31 @@ public void switchBranch(@NotNull String branchName) } /** - * Restore saved copies from current Branch + * Restore saved copy from current Branch * * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized * @throws IOException in case of some IO problems */ - public void reset() throws BrokenFileException, LostFileException, IOException, + public void reset(String filename) throws BrokenFileException, LostFileException, IOException, RepositoryNotInitializedException { - dataManager.putStage(ContentDescriptor.EMPTY); - Header header = dataManager.getHeader(); - Branch branch = dataManager.fetchBranch(header.getCurrentBranchName()); - VersionNode versionNode = dataManager.fetchVersionNode(branch.getVersionNodeID()); - Commit lastCommit = dataManager.fetchCommit(versionNode.getCommitID()); - checkout(lastCommit.getContentDescriptorID()); + ContentDescriptor descriptor = getActualDescriptor(); + if (!descriptor.getFiles().containsKey(filename)) { + // todo + } + dataManager.loadFile(descriptor.getFiles().get(filename), filename); + Stage stage = dataManager.getStage(); + if (stage.getChangedFiles().containsKey(filename) || stage.getRemovedFiles().contains(filename)) { + Stage updatedStage = stage.change() + .reset(filename) + .build(); + dataManager.putStage(updatedStage); + } } /** - * Restore saved file from given ContentDescriptor + * Restore saved files from given ContentDescriptor * * @param descriptorID id of interesting description * @throws LostFileException if file contained one of interesting object was corrupted @@ -399,14 +429,24 @@ public void reset() throws BrokenFileException, LostFileException, IOException, public void checkout(@NotNull String descriptorID) throws IOException, BrokenFileException, LostFileException { dataManager.clearWorkingCopy(); - dataManager.loadFiles(descriptorID); + ContentDescriptor contentDescriptor = dataManager.fetchContentDescriptor(descriptorID); + for (Map.Entry file : contentDescriptor.getFiles().entrySet()) { + dataManager.loadFile(file.getValue(), file.getKey()); + } } /** - * This method remove all file from working directory + * This method remove all untracked files from working directory */ - public void clear() { - dataManager.clearWorkingCopy(); + public void clean() throws IOException, BrokenFileException, LostFileException, NonexistentFileDeletionException { + List paths = dataManager.workingCopyFiles(); + Stage stage = dataManager.getStage(); + ContentDescriptor headVersion = getActualDescriptor(); + for (String path : paths) { + if (!stage.getChangedFiles().containsKey(path) && !headVersion.getFiles().containsKey(path)) { + dataManager.removeFile(path); + } + } } /** @@ -418,6 +458,50 @@ public void uninstall() throws RepositoryNotInitializedException { dataManager.uninstallRepository(); } + public Map stageStatus() throws BrokenFileException, LostFileException { + Stage stage = dataManager.getStage(); + HashMap result = new HashMap<>(); + for (String s : stage.getChangedFiles().keySet()) { + result.put(s, UPDATED); + } + for (String s : stage.getRemovedFiles()) { + result.put(s, REMOVED); + } + return result; + } + + public Map workingCopyStatus() throws BrokenFileException, LostFileException, IOException { + List paths = dataManager.workingCopyFiles(); + Stage stage = dataManager.getStage(); + ContentDescriptor headVersion = getActualDescriptor(); + HashMap result = new HashMap<>(); + + ContentDescriptor stagedDescriptor = ContentDescriptor.builder() + .addAll(headVersion) + .addAll(stage) + .build(); + + Map files = stagedDescriptor.getFiles(); + for (String path : paths) { + if (!files.containsKey(path)) { + result.put(path, UNKNOWN); + continue; + } + String hash = dataManager.hashFile(path); + if (hash.equals(files.get(path))) { + result.put(path, NOT_CHANGED); + } else { + result.put(path, CHANGED); + } + } + for (String path : files.keySet()) { + if (!result.containsKey(path)) { + result.put(path, DISAPPEARED); + } + } + return result; + } + public static class SwitchOnCurrentBranchException extends VersionControlSystemException { SwitchOnCurrentBranchException(String message) { super(message); @@ -467,4 +551,7 @@ public static class IllegalBranchToMergeException extends VersionControlSystemEx super(message); } } + + public enum StageStatus {UPDATED, REMOVED} + public enum FileStatus {CHANGED, DISAPPEARED, UNKNOWN, NOT_CHANGED} } diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/ContentDescriptor.java b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/ContentDescriptor.java index 5a89d93..9c4efdd 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/ContentDescriptor.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/ContentDescriptor.java @@ -46,11 +46,18 @@ public Builder addFile(@NotNull String relativePath, @NotNull String fileID) { } @NotNull - public Builder addAllFiles(@NotNull ContentDescriptor contentDescriptor) { + public Builder addAll(@NotNull ContentDescriptor contentDescriptor) { files.putAll(contentDescriptor.files); return this; } + @NotNull + public Builder addAll(@NotNull Stage stage) { + files.putAll(stage.getChangedFiles()); + files.keySet().removeAll(stage.getRemovedFiles()); + return this; + } + @NotNull public ContentDescriptor build() { return new ContentDescriptor(files); diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Stage.java b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Stage.java new file mode 100644 index 0000000..916bda6 --- /dev/null +++ b/src/main/java/ru/spbau/lobanov/liteVCS/primitives/Stage.java @@ -0,0 +1,82 @@ +package ru.spbau.lobanov.liteVCS.primitives; + +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; +import java.util.*; + +public class Stage implements Serializable { + public static final Stage EMPTY = new Stage(new TreeMap<>(), new TreeSet<>()); + + // Map name of file (relative path from working directory) + // to ID of its actual version + private final TreeMap changedFiles; + private final TreeSet removedFiles; + + private Stage(TreeMap changedFiles, TreeSet removedFiles) { + this.changedFiles = changedFiles; + this.removedFiles = removedFiles; + } + + @NotNull + public Map getChangedFiles() { + return Collections.unmodifiableMap(changedFiles); + } + + public TreeSet getRemovedFiles() { + return removedFiles; + } + + public boolean isEmpty() { + return changedFiles.isEmpty() && removedFiles.isEmpty(); + } + + @NotNull + public static Builder builder() { + return new Builder(Collections.emptyMap(), Collections.emptySet()); + } + + @NotNull + public Builder change() { + return new Builder(changedFiles, removedFiles); + } + + + /** + * This class should help to create ContentDescriptors + */ + public static class Builder { + private final TreeMap changedFiles; + private final TreeSet removedFiles; + + private Builder(Map changedFiles, Set removedFiles) { + this.changedFiles = new TreeMap<>(changedFiles); + this.removedFiles = new TreeSet<>(removedFiles); + } + + @NotNull + public Builder addFile(@NotNull String relativePath, @NotNull String fileID) { + changedFiles.put(relativePath, fileID); + removedFiles.remove(relativePath); + return this; + } + + @NotNull + public Builder removeFile(@NotNull String relativePath) { + removedFiles.add(relativePath); + changedFiles.remove(relativePath); + return this; + } + + public Builder reset(String relationPath) { + removedFiles.remove(relationPath); + changedFiles.remove(relationPath); + return this; + } + + @NotNull + public Stage build() { + return new Stage(changedFiles, removedFiles); + } + } +} diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java index 1772ed7..0517e5a 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java @@ -112,22 +112,23 @@ public void putAndGetHeader() throws Exception { @Test public void putAndGetStage() throws Exception { DataManager dataManager = new DataManager(workspace); - ContentDescriptor stage = ContentDescriptor + Stage stage = Stage .builder() .addFile("1", "2") .addFile("hello", "world") .build(); dataManager.putStage(stage); - ContentDescriptor stage1 = dataManager.getStage(); - assertEquals(stage.getFiles(), stage1.getFiles()); - } - - @Test - public void getFile() throws Exception { - DataManager dataManager = new DataManager(workspace); - File file = dataManager.getFile(Paths.get(".liteVCS", "stage.lVCS").toString()); - assertTrue(file.exists()); - assertEquals("stage.lVCS", file.getName()); + Stage stage1 = dataManager.getStage(); + assertEquals(stage.getChangedFiles(), stage1.getChangedFiles()); + assertEquals(stage.getRemovedFiles(), stage1.getRemovedFiles()); } +// +// @Test +// public void getFile() throws Exception { +// DataManager dataManager = new DataManager(workspace); +// File file = dataManager.getFile(Paths.get(".liteVCS", "stage.lVCS").toString()); +// assertTrue(file.exists()); +// assertEquals("stage.lVCS", file.getName()); +// } } \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java index c283029..4511823 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java @@ -24,7 +24,7 @@ public void addBranchSwitchChangeSwitchMerge() throws Exception { liteVCS.switchBranch("master"); assertEquals(initialState, dataManager.hash("a.txt")); liteVCS.mergeBranch("br", "mesage"); - liteVCS.reset(); + liteVCS.reset("a.txt"); assertEquals(lastState, dataManager.hash("a.txt")); } @@ -62,7 +62,8 @@ public void addBranchChangeSwitchChangeSwitchMerge() throws Exception { assertEquals(initialStateB, dataManager.hash("b.txt")); liteVCS.mergeBranch("br", "mesage"); - liteVCS.reset(); + liteVCS.reset("a.txt"); + liteVCS.reset("b.txt"); assertEquals(resultStateA, dataManager.hash("a.txt")); diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java index 243863f..a93b441 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java @@ -1,10 +1,7 @@ package ru.spbau.lobanov.liteVCS.logic; import org.junit.Test; -import ru.spbau.lobanov.liteVCS.primitives.Branch; -import ru.spbau.lobanov.liteVCS.primitives.Commit; -import ru.spbau.lobanov.liteVCS.primitives.ContentDescriptor; -import ru.spbau.lobanov.liteVCS.primitives.Header; +import ru.spbau.lobanov.liteVCS.primitives.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -29,8 +26,8 @@ public void add() throws Exception { liteVCS.init(); dataManager.writeFile("a.txt", "data"); liteVCS.add("a.txt"); - ContentDescriptor stage = dataManager.getStage(); - String id = stage.getFiles().get("a.txt"); + Stage stage = dataManager.getStage(); + String id = stage.getChangedFiles().get("a.txt"); assertNotNull(id); VirtualFile f = (VirtualFile) dataManager.fetchFile(id); assertEquals("data", f.getValue()); @@ -150,26 +147,26 @@ public void switchBranch() throws Exception { assertEquals(0, dataManager.workingCopy.size()); } - @Test - public void reset() throws Exception { - VirtualDataManager dataManager = new VirtualDataManager(); - LiteVCS liteVCS = new LiteVCS(dataManager); - - liteVCS.init(); - - dataManager.writeFile("a.txt", "data"); - liteVCS.add("a.txt"); - liteVCS.commit("message"); - - dataManager.writeFile("a.txt", "data2"); - liteVCS.add("a.txt"); - dataManager.writeFile("b.txt", "data"); - - liteVCS.reset(); - - assertEquals(1, dataManager.workingCopy.size()); - assertEquals("data".hashCode() + "", dataManager.hash("a.txt")); - } +// @Test +// public void reset() throws Exception { +// VirtualDataManager dataManager = new VirtualDataManager(); +// LiteVCS liteVCS = new LiteVCS(dataManager); +// +// liteVCS.init(); +// +// dataManager.writeFile("a.txt", "data"); +// liteVCS.add("a.txt"); +// liteVCS.commit("message"); +// +// dataManager.writeFile("a.txt", "data2"); +// liteVCS.add("a.txt"); +// dataManager.writeFile("b.txt", "data"); +// +// liteVCS.reset(); +// +// assertEquals(1, dataManager.workingCopy.size()); +// assertEquals("data".hashCode() + "", dataManager.hash("a.txt")); +// } @Test public void checkout() throws Exception { @@ -195,4 +192,11 @@ public void checkout() throws Exception { assertEquals("data".hashCode() + "", dataManager.hash("a.txt")); } + @Test + public void switchBranch2() throws Exception { + DataManager dm = new DataManager(System.getProperty("user.dir")); + System.out.println(System.getProperty("user.dir")); + System.out.println(dm.workingCopyFiles()); + } + } \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java index c75a92c..6c68d19 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java @@ -20,7 +20,7 @@ public class VirtualDataManager extends DataManager { HashMap versions = new HashMap<>(); HashMap files = new HashMap<>(); Header header; - ContentDescriptor stage; + Stage stage; HashMap workingCopy = new HashMap<>(); @@ -55,7 +55,7 @@ void initRepository() throws RecreatingRepositoryException { addBranch(master); Header header = new Header("Unknown", master.getName()); putHeader(header); - putStage(ContentDescriptor.EMPTY); + putStage(Stage.EMPTY); } catch (RepositoryNotInitializedException e) { throw new Error(""); } @@ -103,9 +103,10 @@ File fetchFile(@NotNull String id) throws LostFileException { } @NotNull - String addFile(@NotNull File file) throws RepositoryNotInitializedException { + String addFile(@NotNull String path) throws RepositoryNotInitializedException { + VirtualFile file = (VirtualFile) workingCopy.get(path); String id = "fl" + file.hashCode(); - files.put(id, (VirtualFile) file); + files.put(id, file); return id; } @@ -135,19 +136,16 @@ void putHeader(@NotNull Header header) throws RepositoryNotInitializedException } @NotNull - ContentDescriptor getStage() throws LostFileException, BrokenFileException { + Stage getStage() throws LostFileException, BrokenFileException { return stage; } - void putStage(@NotNull ContentDescriptor stage) throws RepositoryNotInitializedException { + void putStage(@NotNull Stage stage) throws RepositoryNotInitializedException { this.stage = stage; } - void loadFiles(@NotNull String descriptorID) throws BrokenFileException, LostFileException, IOException { - ContentDescriptor descriptor = fetchContentDescriptor(descriptorID); - for (Map.Entry pair : descriptor.getFiles().entrySet()) { - workingCopy.put(pair.getKey(), fetchFile(pair.getValue())); - } + void loadFile(@NotNull String fileID, @NotNull String targetPath) throws LostFileException, IOException { + workingCopy.put(targetPath, fetchFile(fileID)); } void clearWorkingCopy() { @@ -171,11 +169,6 @@ void uninstallRepository() throws RepositoryNotInitializedException { isInitialized = false; } - @NotNull - File getFile(@NotNull String filename) { - return workingCopy.get(filename); - } - void writeFile(String filename, String value) { VirtualFile f = new VirtualFile(value); workingCopy.put(filename, f); From 477613f13f3eeb6d7b3331d5db044320332e6702 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 13 Apr 2017 09:50:43 +0300 Subject: [PATCH 21/30] New tests --- .../spbau/lobanov/liteVCS/ConsoleWorker.java | 5 +- .../spbau/lobanov/liteVCS/logic/LiteVCS.java | 1 + .../liteVCS/logic/DataManagerTest.java | 88 ++++++++++--- .../liteVCS/logic/LiteVCSGeneralTests.java | 22 ++-- .../lobanov/liteVCS/logic/LiteVCSTest.java | 117 ++++++++++++++++-- .../liteVCS/logic/VirtualDataManager.java | 21 ++-- 6 files changed, 212 insertions(+), 42 deletions(-) diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java index 2c9f314..3dd586a 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java @@ -18,6 +18,7 @@ public class ConsoleWorker { private static final String COMMIT_PLACE_HOLDER = "\"%s\" by %s (node: %s)\n"; + private static final String STATUS_PLACE_HOLDER = " \"%s\" status=%s\n"; /** * Sugar to simplify checking count of arguments @@ -157,11 +158,11 @@ private static void printStatus(Map stageStatus, Map entry : stageStatus.entrySet()) { - System.out.println(" " + entry.getKey() + " status=" + entry.getValue()); + System.out.printf(STATUS_PLACE_HOLDER, entry.getKey(), entry.getValue()); } System.out.println(" Working copy:"); for (Entry entry : workingCopyStatus.entrySet()) { - System.out.println(" " + entry.getKey() + " status=" + entry.getValue()); + System.out.printf(STATUS_PLACE_HOLDER, entry.getKey(), entry.getValue()); } System.out.println("---------------------------------------------"); } diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java index 4b10461..5e015ba 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java @@ -481,6 +481,7 @@ public Map workingCopyStatus() throws BrokenFileException, L .addAll(stage) .build(); + Map files = stagedDescriptor.getFiles(); for (String path : paths) { if (!files.containsKey(path)) { diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java index 0517e5a..b006c9a 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/DataManagerTest.java @@ -1,12 +1,17 @@ package ru.spbau.lobanov.liteVCS.logic; +import com.google.common.io.Files; import org.junit.After; import org.junit.Before; import org.junit.Test; import ru.spbau.lobanov.liteVCS.primitives.*; import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import static org.junit.Assert.*; @@ -70,13 +75,16 @@ public void addAndFetchCommit() throws Exception { } @Test - public void fetchFile() throws Exception { - - } - - @Test - public void addFile() throws Exception { - + public void addAndLoadFile() throws Exception { + File file = Paths.get(workspace, "a.txt").toFile(); + Files.touch(file); + DataManager dataManager = new DataManager(workspace); + String id = dataManager.addFile("a.txt"); + dataManager.removeFile("a.txt"); + assertFalse(file.exists()); + dataManager.loadFile(id, "a.txt"); + assertTrue(file.exists()); + assertEquals(id, dataManager.hashFile("a.txt")); } @Test @@ -116,19 +124,69 @@ public void putAndGetStage() throws Exception { .builder() .addFile("1", "2") .addFile("hello", "world") + .removeFile("3") .build(); dataManager.putStage(stage); Stage stage1 = dataManager.getStage(); assertEquals(stage.getChangedFiles(), stage1.getChangedFiles()); assertEquals(stage.getRemovedFiles(), stage1.getRemovedFiles()); } -// -// @Test -// public void getFile() throws Exception { -// DataManager dataManager = new DataManager(workspace); -// File file = dataManager.getFile(Paths.get(".liteVCS", "stage.lVCS").toString()); -// assertTrue(file.exists()); -// assertEquals("stage.lVCS", file.getName()); -// } + @Test + public void removeFile() throws Exception { + String relativePath = Paths.get("folder", "folder2", "a.txt").toString(); + Path path = Paths.get(workspace, relativePath); + + Files.createParentDirs(path.toFile()); + Files.touch(path.toFile()); + Files.touch(Paths.get(workspace, "folder", "b.txt").toFile()); + + DataManager dataManager = new DataManager(workspace); + dataManager.removeFile(relativePath); + assertFalse(path.toFile().exists()); + assertFalse(path.getParent().toFile().exists()); + assertTrue(path.getParent().getParent().toFile().exists()); + } + + @Test + public void workingCopy() throws Exception { + File file = Paths.get(workspace, "a.txt").toFile(); + Files.touch(file); + + file = Paths.get(workspace, "folder1", "a.txt").toFile(); + Files.createParentDirs(file); + Files.touch(file); + + file = Paths.get(workspace, "folder1", "folder2", "b.txt").toFile(); + Files.createParentDirs(file); + Files.touch(file); + + List wc = (new DataManager(workspace)).workingCopyFiles(); + wc.sort(String::compareTo); + + assertEquals(3, wc.size()); + assertTrue(wc.contains(Paths.get("a.txt").toString())); + assertTrue(wc.contains(Paths.get("folder1", "a.txt").toString())); + assertTrue(wc.contains(Paths.get("folder1", "folder2", "b.txt").toString())); + } + + @Test + public void hashFileTest() throws Exception { + PrintWriter out = new PrintWriter(Paths.get(workspace, "a.txt").toFile()); + out.println("hello"); + out.close(); + + out = new PrintWriter(Paths.get(workspace, "a2.txt").toFile()); + out.println("hello"); + out.close(); + + out = new PrintWriter(Paths.get(workspace, "b.txt").toFile()); + out.println("olleh"); + out.close(); + + DataManager dataManager = new DataManager(workspace); + assertEquals(dataManager.hashFile("a.txt"), dataManager.hashFile("a.txt")); + assertEquals(dataManager.hashFile("a.txt"), dataManager.hashFile("a2.txt")); + assertNotEquals(dataManager.hashFile("a2.txt"), dataManager.hashFile("b.txt")); + } } \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java index 4511823..d92c00f 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSGeneralTests.java @@ -12,20 +12,20 @@ public void addBranchSwitchChangeSwitchMerge() throws Exception { LiteVCS liteVCS = new LiteVCS(dataManager); liteVCS.init(); dataManager.writeFile("a.txt", "versiona1"); - String initialState = dataManager.hash("a.txt"); + String initialState = dataManager.hashFile("a.txt"); liteVCS.add("a.txt"); liteVCS.commit("commit #1"); liteVCS.createBranch("br"); liteVCS.switchBranch("br"); dataManager.writeFile("a.txt", "versiona2"); - String lastState = dataManager.hash("a.txt"); + String lastState = dataManager.hashFile("a.txt"); liteVCS.add("a.txt"); liteVCS.commit("commit2"); liteVCS.switchBranch("master"); - assertEquals(initialState, dataManager.hash("a.txt")); + assertEquals(initialState, dataManager.hashFile("a.txt")); liteVCS.mergeBranch("br", "mesage"); liteVCS.reset("a.txt"); - assertEquals(lastState, dataManager.hash("a.txt")); + assertEquals(lastState, dataManager.hashFile("a.txt")); } @Test @@ -37,7 +37,7 @@ public void addBranchChangeSwitchChangeSwitchMerge() throws Exception { dataManager.writeFile("a.txt", "versiona1"); dataManager.writeFile("b.txt", "versionb1"); - String initialStateB = dataManager.hash("b.txt"); + String initialStateB = dataManager.hashFile("b.txt"); liteVCS.add("a.txt"); liteVCS.add("b.txt"); @@ -46,27 +46,27 @@ public void addBranchChangeSwitchChangeSwitchMerge() throws Exception { dataManager.writeFile("a.txt", "versiona2"); - String resultStateA = dataManager.hash("a.txt"); + String resultStateA = dataManager.hashFile("a.txt"); liteVCS.add("a.txt"); liteVCS.commit("commit #2"); liteVCS.switchBranch("br"); dataManager.writeFile("b.txt", "versionb2"); - String resultStateB = dataManager.hash("b.txt"); + String resultStateB = dataManager.hashFile("b.txt"); liteVCS.add("b.txt"); liteVCS.commit("commit3"); liteVCS.switchBranch("master"); - assertEquals(resultStateA, dataManager.hash("a.txt")); - assertEquals(initialStateB, dataManager.hash("b.txt")); + assertEquals(resultStateA, dataManager.hashFile("a.txt")); + assertEquals(initialStateB, dataManager.hashFile("b.txt")); liteVCS.mergeBranch("br", "mesage"); liteVCS.reset("a.txt"); liteVCS.reset("b.txt"); - assertEquals(resultStateA, dataManager.hash("a.txt")); - assertEquals(resultStateB, dataManager.hash("b.txt")); + assertEquals(resultStateA, dataManager.hashFile("a.txt")); + assertEquals(resultStateB, dataManager.hashFile("b.txt")); } } \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java index a93b441..4813f45 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/LiteVCSTest.java @@ -1,10 +1,16 @@ package ru.spbau.lobanov.liteVCS.logic; import org.junit.Test; +import ru.spbau.lobanov.liteVCS.logic.LiteVCS.FileStatus; +import ru.spbau.lobanov.liteVCS.logic.LiteVCS.StageStatus; import ru.spbau.lobanov.liteVCS.primitives.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import java.util.Map; + +import static org.junit.Assert.*; +import static ru.spbau.lobanov.liteVCS.logic.LiteVCS.FileStatus.*; +import static ru.spbau.lobanov.liteVCS.logic.LiteVCS.StageStatus.REMOVED; +import static ru.spbau.lobanov.liteVCS.logic.LiteVCS.StageStatus.UPDATED; public class LiteVCSTest { @Test @@ -189,14 +195,111 @@ public void checkout() throws Exception { liteVCS.checkout(descriptorID); assertEquals(1, dataManager.workingCopy.size()); - assertEquals("data".hashCode() + "", dataManager.hash("a.txt")); + assertEquals("data".hashCode() + "", dataManager.hashFile("a.txt")); + } + + @Test + public void clean() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); + + liteVCS.init(); + + dataManager.writeFile("a.txt", "data"); + liteVCS.add("a.txt"); + liteVCS.commit("message"); + + dataManager.writeFile("b.txt", "data2"); + liteVCS.add("b.txt"); + dataManager.writeFile("b.txt", "data3"); + + dataManager.writeFile("c.txt", "data3"); + + liteVCS.clean(); + assertEquals(2, dataManager.workingCopy.size()); + assertTrue(dataManager.workingCopy.containsKey("a.txt")); + assertTrue(dataManager.workingCopy.containsKey("b.txt")); + assertEquals("data3".hashCode(), dataManager.workingCopy.get("b.txt").hashCode()); } @Test - public void switchBranch2() throws Exception { - DataManager dm = new DataManager(System.getProperty("user.dir")); - System.out.println(System.getProperty("user.dir")); - System.out.println(dm.workingCopyFiles()); + public void remove() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); + + liteVCS.init(); + + dataManager.writeFile("a.txt", "data"); + liteVCS.add("a.txt"); + liteVCS.commit("message"); + + liteVCS.remove("a.txt"); + assertFalse(dataManager.workingCopy.containsKey("a.txt")); + liteVCS.reset("a.txt"); + assertTrue(dataManager.workingCopy.containsKey("a.txt")); + + liteVCS.remove("a.txt"); + liteVCS.commit("1"); + assertFalse(dataManager.workingCopy.containsKey("a.txt")); + String descriptionID = liteVCS.history("1").get(0).getContentDescriptorID(); + liteVCS.checkout(descriptionID); + assertFalse(dataManager.workingCopy.containsKey("a.txt")); } + @Test + public void reset() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); + + liteVCS.init(); + + dataManager.writeFile("a.txt", "data"); + liteVCS.add("a.txt"); + liteVCS.commit("message"); + + dataManager.writeFile("a.txt", "data2"); + liteVCS.add("a.txt"); + + liteVCS.reset("a.txt"); + assertEquals("data".hashCode(), dataManager.workingCopy.get("a.txt").hashCode()); + assertFalse(dataManager.getStage().getChangedFiles().containsKey("a.txt")); + } + + @Test + public void workingCopyStatus() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); + liteVCS.init(); + + dataManager.writeFile("a.txt", "data"); + Map result = liteVCS.workingCopyStatus(); + assertEquals(UNKNOWN, result.get("a.txt")); + + liteVCS.add("a.txt"); + result = liteVCS.workingCopyStatus(); + assertEquals(NOT_CHANGED, result.get("a.txt")); + + dataManager.writeFile("a.txt", "data2"); + result = liteVCS.workingCopyStatus(); + assertEquals(CHANGED, result.get("a.txt")); + + dataManager.removeFile("a.txt"); + result = liteVCS.workingCopyStatus(); + assertEquals(DISAPPEARED, result.get("a.txt")); + } + + @Test + public void stageStatus() throws Exception { + VirtualDataManager dataManager = new VirtualDataManager(); + LiteVCS liteVCS = new LiteVCS(dataManager); + liteVCS.init(); + + dataManager.writeFile("a.txt", "data"); + liteVCS.add("a.txt"); + Map result = liteVCS.stageStatus(); + + liteVCS.remove("a.txt"); + result = liteVCS.stageStatus(); + assertEquals(REMOVED, result.get("a.txt")); + } } \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java index 6c68d19..39f3739 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/VirtualDataManager.java @@ -5,9 +5,7 @@ import java.io.File; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; public class VirtualDataManager extends DataManager { @@ -37,7 +35,7 @@ private static String createRandomKey(Set used) { } public static String hash(File file) { - return "fl" + file.hashCode(); + return "" + file.hashCode(); } void initRepository() throws RecreatingRepositoryException { @@ -105,11 +103,15 @@ File fetchFile(@NotNull String id) throws LostFileException { @NotNull String addFile(@NotNull String path) throws RepositoryNotInitializedException { VirtualFile file = (VirtualFile) workingCopy.get(path); - String id = "fl" + file.hashCode(); + String id = "" + file.hashCode(); files.put(id, file); return id; } + void removeFile(String path) { + workingCopy.remove(path); + } + void addBranch(@NotNull Branch branch) throws RepositoryNotInitializedException { branches.put(branch.getName(), branch); } @@ -148,6 +150,10 @@ void loadFile(@NotNull String fileID, @NotNull String targetPath) throws LostFil workingCopy.put(targetPath, fetchFile(fileID)); } + List workingCopyFiles() { + return new ArrayList<>(workingCopy.keySet()); + } + void clearWorkingCopy() { workingCopy.clear(); } @@ -174,7 +180,8 @@ void writeFile(String filename, String value) { workingCopy.put(filename, f); } - String hash(String filename) { - return "" + workingCopy.get(filename).hashCode(); + String hashFile(String filename) { + VirtualFile file = (VirtualFile) workingCopy.get(filename); + return "" + file.hashCode(); } } From 0e3f435b83bbd11b3fad27d9b16245929e5ba3b7 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 13 Apr 2017 12:43:05 +0300 Subject: [PATCH 22/30] Logging --- ...radle__org_mockito_mockito_all_1_10_19.xml | 11 ++ .../spbau/lobanov/liteVCS/ConsoleWorker.java | 27 ++++- .../lobanov/liteVCS/logic/DataManager.java | 39 ++++-- .../spbau/lobanov/liteVCS/logic/LiteVCS.java | 111 +++++++++++++----- .../spbau/lobanov/liteVCS/logic/Logging.java | 32 +++++ src/main/resources/logging.properties | 6 + .../liteVCS/logic/ConsoleWorkerTest.java | 17 +++ 7 files changed, 199 insertions(+), 44 deletions(-) create mode 100644 .idea/libraries/Gradle__org_mockito_mockito_all_1_10_19.xml create mode 100644 src/main/java/ru/spbau/lobanov/liteVCS/logic/Logging.java create mode 100644 src/main/resources/logging.properties create mode 100644 src/test/java/ru/spbau/lobanov/liteVCS/logic/ConsoleWorkerTest.java diff --git a/.idea/libraries/Gradle__org_mockito_mockito_all_1_10_19.xml b/.idea/libraries/Gradle__org_mockito_mockito_all_1_10_19.xml new file mode 100644 index 0000000..9bddc17 --- /dev/null +++ b/.idea/libraries/Gradle__org_mockito_mockito_all_1_10_19.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java index 3dd586a..a64ec52 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java @@ -6,6 +6,7 @@ import ru.spbau.lobanov.liteVCS.logic.DataManager.RepositoryNotInitializedException; import ru.spbau.lobanov.liteVCS.logic.LiteVCS; import ru.spbau.lobanov.liteVCS.logic.LiteVCS.*; +import ru.spbau.lobanov.liteVCS.logic.Logging; import ru.spbau.lobanov.liteVCS.logic.VersionControlSystemException; import ru.spbau.lobanov.liteVCS.primitives.Commit; @@ -14,12 +15,21 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; public class ConsoleWorker { private static final String COMMIT_PLACE_HOLDER = "\"%s\" by %s (node: %s)\n"; private static final String STATUS_PLACE_HOLDER = " \"%s\" status=%s\n"; + private final LiteVCS liteVCS; + + public ConsoleWorker(LiteVCS liteVCS) { + this.liteVCS = liteVCS; + } + /** * Sugar to simplify checking count of arguments * @@ -33,10 +43,8 @@ private static void checkArguments(int size, String[] args) throws WrongNumberAr } } - private static void execute(String command, String[] args) throws VersionControlSystemException, + private void execute(String command, String[] args) throws VersionControlSystemException, WrongNumberArgumentsException, IOException, UnknownCommandException { - String path = Paths.get(System.getProperty("user.dir")).toString(); - LiteVCS liteVCS = new LiteVCS(path); switch (command) { case "init": checkArguments(0, args); @@ -103,15 +111,24 @@ private static void execute(String command, String[] args) throws VersionControl } } - public static void main(String[] args) { + public static void main(String... args) { + try { + Logging.setupLogging(); + } catch (Logging.LoggingException e) { + System.out.println("Logging error: " + e.getMessage()); + System.out.println("Original message: " + e.getCause().getMessage()); + return; + } if (args.length == 0) { System.out.println("Error: empty command"); return; } String[] functionArgs = new String[args.length - 1]; System.arraycopy(args, 1, functionArgs, 0, functionArgs.length); + String targetPath = Paths.get(System.getProperty("user.dir")).toString(); + ConsoleWorker consoleWorker = new ConsoleWorker(new LiteVCS(targetPath)); try { - execute(args[0], functionArgs); + consoleWorker.execute(args[0], functionArgs); } catch (ConflictMergeException e) { System.out.println("Conflicts were found:"); for (String path : e.getConflicts()) { diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java index 602efd7..a80e67d 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/DataManager.java @@ -10,10 +10,8 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.Map.Entry; import java.util.Random; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.logging.Logger; /** * This class is adapter between logic part of LiteVCS @@ -21,6 +19,8 @@ */ public class DataManager { + private static final Logger logger = Logger.getLogger(DataManager.class.getName()); + private static final String ROOT_DIRECTORY_NAME = ".liteVCS"; private static final String PATH_TO_VERSIONS_FILES = concat(ROOT_DIRECTORY_NAME, "versions"); private static final String PATH_TO_CONTENT_DESCRIPTORS_FILES = concat(ROOT_DIRECTORY_NAME, "descriptors"); @@ -43,7 +43,7 @@ public class DataManager { * * @throws RecreatingRepositoryException if repository was already created */ - void initRepository() throws RecreatingRepositoryException { + void initRepository() throws RecreatingRepositoryException, IOException { File rootDirectory = Paths.get(workingDirectory, DataManager.ROOT_DIRECTORY_NAME).toFile(); if (rootDirectory.exists()) { throw new RecreatingRepositoryException("Repository was already created here:" + workingDirectory); @@ -54,7 +54,7 @@ void initRepository() throws RecreatingRepositoryException { Paths.get(workingDirectory, DataManager.PATH_TO_VERSIONS_FILES).toFile().mkdirs() && Paths.get(workingDirectory, DataManager.PATH_TO_BRANCHES).toFile().mkdirs(); if (!success) { - throw new Error("Unexpected error during directories creating"); + throw new IOException("Unexpected error during directories creating"); } try { String initialDescriptorID = addContentDescriptor(ContentDescriptor.EMPTY); @@ -196,6 +196,12 @@ String addFile(@NotNull String relativePath) throws RepositoryNotInitializedExce return hash; } + /** + * Method allow to remove files from working folder + * + * @param relativePath relative path to file which will bw saved + * @throws NonexistentFileDeletionException if target file doesn't exist + */ void removeFile(String relativePath) throws NonexistentFileDeletionException { File targetFile = Paths.get(workingDirectory, relativePath).toFile(); if (!targetFile.isFile() || !targetFile.delete()) { @@ -212,11 +218,17 @@ void removeFile(String relativePath) throws NonexistentFileDeletionException { } } + /** + * Method allow to get list of files, which are in the working folder + * + * @return list of relative paths to every file + * @throws IOException if file creating failed + */ List workingCopyFiles() throws IOException { List paths = new ArrayList<>(); File[] files = Paths.get(workingDirectory).toFile().listFiles(); if (files == null){ - + throw new IOException("Cant get children of folder"); } Path mainDirectory = Paths.get(workingDirectory, ROOT_DIRECTORY_NAME); for (File f : files) { @@ -227,14 +239,13 @@ List workingCopyFiles() throws IOException { return paths; } - private void walkFileTree(File file, List paths) { + private void walkFileTree(File file, List paths) throws IOException { if (file.isFile()) { paths.add(Paths.get(workingDirectory).relativize(file.toPath()).toString()); } else { File[] files = file.listFiles(); if (files == null) { - throw new Error(); - //todo + throw new IOException("Cant get children of folder:" + file.toPath()); } for (File f : files) { walkFileTree(f, paths); @@ -242,12 +253,18 @@ private void walkFileTree(File file, List paths) { } } + /** + * Method allow to calculate hash of files + * + * @param path relative path to target file + * @return HashCode + */ String hashFile(String path) { String hash; try { hash = Files.hash(Paths.get(workingDirectory, path).toFile(), Hashing.sha256()).toString() + ".sc"; } catch (IOException e) { - throw new Error("Unknown exception during hash creating"); + throw new RuntimeException("Unexpected error during hashing"); } return hash; } @@ -457,6 +474,7 @@ private static T readObject(@NotNull Path path, @NotNull Class expectedTy if (!expectedType.isInstance(o)) { throw new BrokenFileException("Unexpected data was found in file: " + path.toString(), path.toFile()); } + logger.fine("Instance of " + expectedType.getName() + " was successfully loaded"); return expectedType.cast(o); } @@ -477,6 +495,7 @@ private static void writeObject(@NotNull Path path, @NotNull Object object) } catch (IOException e) { throw new Error("Unknown error occurred while writing the file: " + path.toString(), e); } + logger.fine("Instance of " + object.getClass().getName() + " was successfully saved"); } /** diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java index 5e015ba..89034a2 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/LiteVCS.java @@ -9,6 +9,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +import java.util.logging.Logger; import static ru.spbau.lobanov.liteVCS.logic.LiteVCS.FileStatus.*; import static ru.spbau.lobanov.liteVCS.logic.LiteVCS.StageStatus.*; @@ -19,6 +20,8 @@ */ public class LiteVCS { + private static final Logger logger = Logger.getLogger(LiteVCS.class.getName()); + private final DataManager dataManager; public LiteVCS(@NotNull String path) { @@ -34,14 +37,15 @@ public LiteVCS(@NotNull DataManager dataManager) { * That name will be mentioned in following commits. * * @param author chosen name - * @throws LostFileException if file contained one of interesting object was corrupted - * @throws BrokenFileException if file contained one of interesting object was not found + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized */ public void hello(@NotNull String author) throws BrokenFileException, LostFileException, RepositoryNotInitializedException { String branchName = dataManager.getHeader().getCurrentBranchName(); dataManager.putHeader(new Header(author, branchName)); + logger.fine("Author's name set (" + author + ")"); } /** @@ -49,17 +53,17 @@ public void hello(@NotNull String author) throws BrokenFileException, * * @throws RecreatingRepositoryException if repository was already created */ - public void init() throws RecreatingRepositoryException, - RepositoryNotInitializedException { + public void init() throws RecreatingRepositoryException, IOException { dataManager.initRepository(); + logger.fine("Repository successfully created"); } /** * Method which add file to stage (list of files to commit). * * @param fileName relative path to target file - * @throws LostFileException if file contained one of interesting object was corrupted - * @throws BrokenFileException if file contained one of interesting object was not found + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized */ public void add(@NotNull String fileName) throws RepositoryNotInitializedException, @@ -70,6 +74,7 @@ public void add(@NotNull String fileName) throws RepositoryNotInitializedExcepti .addFile(fileName, fileID) .build(); dataManager.putStage(updatedStage); + logger.fine("File " + fileName + " was added to stage area"); } /** @@ -91,6 +96,7 @@ public void remove(@NotNull String fileName) throws RepositoryNotInitializedExce .removeFile(fileName) .build(); dataManager.putStage(updatedStage); + logger.fine("File " + fileName + " will be finally removed from repository in next commit"); } dataManager.removeFile(fileName); } @@ -100,8 +106,8 @@ public void remove(@NotNull String fileName) throws RepositoryNotInitializedExce * create Commit and add it to head of current branch * * @param message text which explain changes which was made in this commit - * @throws LostFileException if file contained one of interesting object was corrupted - * @throws BrokenFileException if file contained one of interesting object was not found + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized */ public void commit(@NotNull String message) throws BrokenFileException, @@ -125,6 +131,7 @@ public void commit(@NotNull String message) throws BrokenFileException, Branch updatedBranch = new Branch(versionID, currentBranch.getName()); dataManager.addBranch(updatedBranch); dataManager.putStage(Stage.EMPTY); + logger.fine("New commit created (descriptor id = " + descriptorID + ")"); } /** @@ -141,6 +148,7 @@ public List history(@NotNull String lengthLimit) try { limit = Integer.parseInt(lengthLimit); } catch (NumberFormatException e) { + logger.fine("Failed to show history, because length limit has wrong format: " + lengthLimit); throw new IllegalArgumentException("Cant parse length limit", e); } Header header = dataManager.getHeader(); @@ -150,6 +158,7 @@ public List history(@NotNull String lengthLimit) for (VersionNode versionNode : versions) { commits.add(dataManager.fetchCommit(versionNode.getCommitID())); } + logger.fine("History of commit was shown (" + commits.size() + " commits)"); return commits; } @@ -158,20 +167,22 @@ public List history(@NotNull String lengthLimit) * equal to version of active branch * * @param branchName name of new branch - * @throws LostFileException if file contained one of interesting object was corrupted - * @throws BrokenFileException if file contained one of interesting object was not found + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized - * @throws ConflictNameException if branch with equal name is already exist + * @throws ConflictNameException if branch with equal name is already exist */ public void createBranch(@NotNull String branchName) throws BrokenFileException, LostFileException, ConflictNameException, RepositoryNotInitializedException { Header header = dataManager.getHeader(); Branch currentBranch = dataManager.fetchBranch(header.getCurrentBranchName()); if (dataManager.hasBranch(branchName)) { + logger.warning("Failed to create new branch because of name collision"); throw new ConflictNameException("Branch with the same name is already exist"); } Branch newBranch = new Branch(currentBranch.getVersionNodeID(), branchName); dataManager.addBranch(newBranch); + logger.fine("New branch successfully created (name = " + branchName + ")"); } /** @@ -188,9 +199,11 @@ public void removeBranch(@NotNull String branchName) RemoveActiveBranchException, UnknownBranchException { Header header = dataManager.getHeader(); if (header.getCurrentBranchName().equals(branchName)) { + logger.warning("Failed to remove branch because target branch was active"); throw new RemoveActiveBranchException("Cant remove active branch"); } if (!dataManager.hasBranch(branchName)) { + logger.warning("Failed to remove branch because branch with name=" + branchName + " wasn't found"); throw new UnknownBranchException("Branch to remove doesn't exist"); } dataManager.removeBranch(branchName); @@ -204,25 +217,28 @@ public void removeBranch(@NotNull String branchName) * * @param branchName name of branch to remove * @param message text ehich will be used in commit - * @throws LostFileException if file contained one of interesting object was corrupted - * @throws BrokenFileException if file contained one of interesting object was not found + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized - * @throws IllegalBranchToMergeException if it's logically impossible to merge thar branches - * @throws UnknownBranchException if branch with the same name wasn't found - * @throws UncommittedChangesException if stage not empty - * @throws ConflictMergeException if unresolvable conflict was found + * @throws IllegalBranchToMergeException if it's logically impossible to merge thar branches + * @throws UnknownBranchException if branch with the same name wasn't found + * @throws UncommittedChangesException if stage not empty + * @throws ConflictMergeException if unresolvable conflict was found */ public void mergeBranch(@NotNull String branchName, @NotNull String message) throws BrokenFileException, LostFileException, IllegalBranchToMergeException, UnknownBranchException, UncommittedChangesException, ConflictMergeException, RepositoryNotInitializedException { Header header = dataManager.getHeader(); if (header.getCurrentBranchName().equals(branchName)) { + logger.warning("Failed to merge branches. Cant merge branch with itself"); throw new IllegalBranchToMergeException("Cant merge branch with itself"); } if (!dataManager.hasBranch(branchName)) { + logger.warning("Failed to merge branches because one of them (" + branchName + " wasn't found"); throw new UnknownBranchException("Branch to merge doesn't exist"); } if (!dataManager.getStage().isEmpty()) { + logger.warning("Failed to merge branches. Stage wasn't empty"); throw new UncommittedChangesException("Commit changes before merge"); } String activeVersionID = dataManager.fetchBranch(header.getCurrentBranchName()).getVersionNodeID(); @@ -233,6 +249,7 @@ public void mergeBranch(@NotNull String branchName, @NotNull String message) ContentDescriptor lcaContent = toContentDescriptor(lcaVersionID); List conflicts = checkConflicts(activeContent, sideContent, lcaContent); if (!conflicts.isEmpty()) { + logger.warning("Failed to merge branches because of difficult conflicts"); throw new ConflictMergeException("Conflicts was found", conflicts); } ContentDescriptor mergedDescriptor = mergeContent(activeContent, sideContent, lcaContent); @@ -243,6 +260,7 @@ public void mergeBranch(@NotNull String branchName, @NotNull String message) String versionNodeID = dataManager.addVersionNode(versionNode); Branch updatedBranch = new Branch(versionNodeID, header.getCurrentBranchName()); dataManager.addBranch(updatedBranch); + logger.warning("Branch " + branchName + " was successfully merged into " + header.getCurrentBranchName()); } /** @@ -341,7 +359,7 @@ && mergeFileVersions(files1.get(path), files2.get(path), lcaFiles.get(path)) != /** * Sugar to simplify getting ContentDescriptor from ID of VersionNode * - * @param versionID id of VersionNode + * @param versionID id of VersionNode * @return loaded ContentDescriptor * @throws LostFileException if file contained one of interesting object was corrupted * @throws BrokenFileException if file contained one of interesting object was not found @@ -365,25 +383,28 @@ private ContentDescriptor getActualDescriptor() throws BrokenFileException, Lost /** * @param branchName name of interesting branch - * @throws LostFileException if file contained one of interesting object was corrupted - * @throws BrokenFileException if file contained one of interesting object was not found + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized - * @throws SwitchOnCurrentBranchException if you try to switch on your current branch - * @throws UncommittedChangesException if stage isn't empty before change branch - * @throws UnknownBranchException if such branch wasn't found - * @throws IOException in case of some IO problems + * @throws SwitchOnCurrentBranchException if you try to switch on your current branch + * @throws UncommittedChangesException if stage isn't empty before change branch + * @throws UnknownBranchException if such branch wasn't found + * @throws IOException in case of some IO problems */ public void switchBranch(@NotNull String branchName) throws BrokenFileException, LostFileException, SwitchOnCurrentBranchException, UncommittedChangesException, UnknownBranchException, IOException, RepositoryNotInitializedException { Header header = dataManager.getHeader(); if (header.getCurrentBranchName().equals(branchName)) { + logger.warning("Failed to switch branches. Target branch is already active"); throw new SwitchOnCurrentBranchException("This branch is already chosen"); } if (!dataManager.getStage().isEmpty()) { + logger.warning("Failed to switch branches. Stage area isn't empty"); throw new UncommittedChangesException("Commit changes before switch branch"); } if (!dataManager.hasBranch(branchName)) { + logger.warning("Failed to switch branches. Branch wasn't found (" + branchName + ")"); throw new UnknownBranchException("Branch wasn't found"); } Branch branch = dataManager.fetchBranch(branchName); @@ -392,21 +413,23 @@ public void switchBranch(@NotNull String branchName) checkout(lastCommit.getContentDescriptorID()); Header updatedHeader = new Header(header.getAuthor(), branchName); dataManager.putHeader(updatedHeader); + logger.fine(branchName + " is active branch now"); } /** * Restore saved copy from current Branch * - * @throws LostFileException if file contained one of interesting object was corrupted - * @throws BrokenFileException if file contained one of interesting object was not found + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found * @throws RepositoryNotInitializedException if repository was not initialized - * @throws IOException in case of some IO problems + * @throws IOException in case of some IO problems + * @throws UnobservedFileException in case if filename wasn't observed before */ public void reset(String filename) throws BrokenFileException, LostFileException, IOException, - RepositoryNotInitializedException { + RepositoryNotInitializedException, UnobservedFileException { ContentDescriptor descriptor = getActualDescriptor(); if (!descriptor.getFiles().containsKey(filename)) { - // todo + throw new UnobservedFileException("File " + filename + " have no saved versions"); } dataManager.loadFile(descriptor.getFiles().get(filename), filename); Stage stage = dataManager.getStage(); @@ -415,6 +438,7 @@ public void reset(String filename) throws BrokenFileException, LostFileException .reset(filename) .build(); dataManager.putStage(updatedStage); + logger.fine("Information about " + filename + " was removed from stage"); } } @@ -433,10 +457,16 @@ public void checkout(@NotNull String descriptorID) for (Map.Entry file : contentDescriptor.getFiles().entrySet()) { dataManager.loadFile(file.getValue(), file.getKey()); } + logger.fine("Checkout to " + descriptorID + " was successfully finished"); } /** * This method remove all untracked files from working directory + * + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + * @throws IOException in case of some IO problems + * {@link NonexistentFileDeletionException in case if some error occurred during cleaning working copy}; */ public void clean() throws IOException, BrokenFileException, LostFileException, NonexistentFileDeletionException { List paths = dataManager.workingCopyFiles(); @@ -447,6 +477,7 @@ public void clean() throws IOException, BrokenFileException, LostFileException, dataManager.removeFile(path); } } + logger.fine("Working directory was successfully cleaned"); } /** @@ -458,6 +489,13 @@ public void uninstall() throws RepositoryNotInitializedException { dataManager.uninstallRepository(); } + /** + * Return information about files in the stage area + * + * @return Map from File name to Status for every file, added to stage + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + */ public Map stageStatus() throws BrokenFileException, LostFileException { Stage stage = dataManager.getStage(); HashMap result = new HashMap<>(); @@ -470,6 +508,14 @@ public Map stageStatus() throws BrokenFileException, LostFi return result; } + + /** + * Return information about files in the working folder + * + * @return Map from File name to Status for every file belonging to folder or known in last commit + * @throws LostFileException if file contained one of interesting object was corrupted + * @throws BrokenFileException if file contained one of interesting object was not found + */ public Map workingCopyStatus() throws BrokenFileException, LostFileException, IOException { List paths = dataManager.workingCopyFiles(); Stage stage = dataManager.getStage(); @@ -553,6 +599,13 @@ public static class IllegalBranchToMergeException extends VersionControlSystemEx } } + public static class UnobservedFileException extends VersionControlSystemException { + public UnobservedFileException(String message) { + super(message); + } + } + public enum StageStatus {UPDATED, REMOVED} + public enum FileStatus {CHANGED, DISAPPEARED, UNKNOWN, NOT_CHANGED} } diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/logic/Logging.java b/src/main/java/ru/spbau/lobanov/liteVCS/logic/Logging.java new file mode 100644 index 0000000..42e4393 --- /dev/null +++ b/src/main/java/ru/spbau/lobanov/liteVCS/logic/Logging.java @@ -0,0 +1,32 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import sun.rmi.runtime.Log; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +public class Logging { + + private static final Path LOG_STORAGE = Paths.get(".liteVCS", "logs"); + + public static void setupLogging() throws LoggingException { + try { + Files.createDirectories(LOG_STORAGE); + LogManager.getLogManager().readConfiguration( + Logging.class.getResourceAsStream("/logging.properties")); + } catch (IOException e) { + throw new LoggingException("Can't init logging", e); + } + Logger.getLogger(Logger.class.getName()).fine("Logging was set up"); + } + + public static class LoggingException extends Exception { + LoggingException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/src/main/resources/logging.properties b/src/main/resources/logging.properties new file mode 100644 index 0000000..a519949 --- /dev/null +++ b/src/main/resources/logging.properties @@ -0,0 +1,6 @@ +handlers= java.util.logging.FileHandler + +java.util.logging.FileHandler.pattern = .liteVCS\\logs\\application_log.txt +java.util.logging.FileHandler.limit = 1000000 +java.util.logging.FileHandler.count = 5 +java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter \ No newline at end of file diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/ConsoleWorkerTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/ConsoleWorkerTest.java new file mode 100644 index 0000000..58d94bd --- /dev/null +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/ConsoleWorkerTest.java @@ -0,0 +1,17 @@ +package ru.spbau.lobanov.liteVCS.logic; + +import org.junit.Test; +import ru.spbau.lobanov.liteVCS.ConsoleWorker; + +import java.io.OutputStream; +import java.io.PrintStream; + +import static org.mockito.Mockito.*; + +public class ConsoleWorkerTest { + @Test + public void commandsTest() throws Exception { + System.setOut(mock(PrintStream.class)); + ConsoleWorker.main(""); + } +} From 5620e5f3bbde8ef836edb3e63e19bc36842bd470 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 13 Apr 2017 12:50:31 +0300 Subject: [PATCH 23/30] Update build.gradle --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index a9b016a..93e839e 100644 --- a/build.gradle +++ b/build.gradle @@ -18,4 +18,8 @@ dependencies { compile group: 'com.google.guava', name: 'guava', version: '21.0' compile group: 'org.jetbrains', name: 'annotations', version: '13.0' + + // https://mvnrepository.com/artifact/org.mockito/mockito-all + compile group: 'org.mockito', name: 'mockito-all', version: '1.10.19' + } From 1e66911130a919d0f705ad37a0db25aafd80575e Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 13 Apr 2017 13:59:39 +0300 Subject: [PATCH 24/30] Mock-tests --- .../spbau/lobanov/liteVCS/ConsoleWorker.java | 5 +- .../liteVCS/logic/ConsoleWorkerTest.java | 109 +++++++++++++++++- 2 files changed, 105 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java index a64ec52..827164a 100644 --- a/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java +++ b/src/main/java/ru/spbau/lobanov/liteVCS/ConsoleWorker.java @@ -15,9 +15,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; public class ConsoleWorker { @@ -43,7 +40,7 @@ private static void checkArguments(int size, String[] args) throws WrongNumberAr } } - private void execute(String command, String[] args) throws VersionControlSystemException, + public void execute(String command, String[] args) throws VersionControlSystemException, WrongNumberArgumentsException, IOException, UnknownCommandException { switch (command) { case "init": diff --git a/src/test/java/ru/spbau/lobanov/liteVCS/logic/ConsoleWorkerTest.java b/src/test/java/ru/spbau/lobanov/liteVCS/logic/ConsoleWorkerTest.java index 58d94bd..4d62592 100644 --- a/src/test/java/ru/spbau/lobanov/liteVCS/logic/ConsoleWorkerTest.java +++ b/src/test/java/ru/spbau/lobanov/liteVCS/logic/ConsoleWorkerTest.java @@ -2,16 +2,115 @@ import org.junit.Test; import ru.spbau.lobanov.liteVCS.ConsoleWorker; +import ru.spbau.lobanov.liteVCS.primitives.Header; +import ru.spbau.lobanov.liteVCS.primitives.Stage; -import java.io.OutputStream; import java.io.PrintStream; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class ConsoleWorkerTest { - @Test - public void commandsTest() throws Exception { + + @Test(expected = LiteVCS.UnknownBranchException.class) + public void unknownBranchTest() throws Exception { + System.setOut(mock(PrintStream.class)); + + Header header = mock(Header.class); + when(header.getCurrentBranchName()).thenReturn("master"); + Stage stage = mock(Stage.class); + when(stage.isEmpty()).thenReturn(true); + + DataManager dataManager = mock(DataManager.class); + when(dataManager.hasBranch("branch")).thenReturn(false); + when(dataManager.getHeader()).thenReturn(header); + when(dataManager.getStage()).thenReturn(stage); + + ConsoleWorker consoleWorker = new ConsoleWorker(new LiteVCS(dataManager)); + consoleWorker.execute("switch_branch", new String[]{"branch"}); + } + + @Test(expected = LiteVCS.UncommittedChangesException.class) + public void notEmptyStageTest() throws Exception { + System.setOut(mock(PrintStream.class)); + + Header header = mock(Header.class); + when(header.getCurrentBranchName()).thenReturn("master"); + Stage stage = mock(Stage.class); + when(stage.isEmpty()).thenReturn(false); + + DataManager dataManager = mock(DataManager.class); + when(dataManager.hasBranch("branch")).thenReturn(false); + when(dataManager.getHeader()).thenReturn(header); + when(dataManager.getStage()).thenReturn(stage); + + ConsoleWorker consoleWorker = new ConsoleWorker(new LiteVCS(dataManager)); + consoleWorker.execute("switch_branch", new String[]{"branch"}); + } + + @Test(expected = LiteVCS.SwitchOnCurrentBranchException.class) + public void switchOnCurrentBranchTest() throws Exception { + System.setOut(mock(PrintStream.class)); + + Header header = mock(Header.class); + when(header.getCurrentBranchName()).thenReturn("branch"); + Stage stage = mock(Stage.class); + when(stage.isEmpty()).thenReturn(true); + + DataManager dataManager = mock(DataManager.class); + when(dataManager.hasBranch("branch")).thenReturn(false); + when(dataManager.getHeader()).thenReturn(header); + when(dataManager.getStage()).thenReturn(stage); + + ConsoleWorker consoleWorker = new ConsoleWorker(new LiteVCS(dataManager)); + consoleWorker.execute("switch_branch", new String[]{"branch"}); + } + + @Test(expected = LiteVCS.RemoveActiveBranchException.class) + public void removeActiveBranchTest() throws Exception { System.setOut(mock(PrintStream.class)); - ConsoleWorker.main(""); + + Header header = mock(Header.class); + when(header.getCurrentBranchName()).thenReturn("master"); + Stage stage = mock(Stage.class); + when(stage.isEmpty()).thenReturn(true); + + DataManager dataManager = mock(DataManager.class); + when(dataManager.hasBranch("master")).thenReturn(false); + when(dataManager.getHeader()).thenReturn(header); + when(dataManager.getStage()).thenReturn(stage); + + ConsoleWorker consoleWorker = new ConsoleWorker(new LiteVCS(dataManager)); + consoleWorker.execute("remove_branch", new String[]{"master"}); + } + + @Test(expected = LiteVCS.IllegalBranchToMergeException.class) + public void selfMergeTest() throws Exception { + System.setOut(mock(PrintStream.class)); + + Header header = mock(Header.class); + when(header.getCurrentBranchName()).thenReturn("master"); + + DataManager dataManager = mock(DataManager.class); + when(dataManager.hasBranch("master")).thenReturn(false); + when(dataManager.getHeader()).thenReturn(header); + + ConsoleWorker consoleWorker = new ConsoleWorker(new LiteVCS(dataManager)); + consoleWorker.execute("merge_branch", new String[]{"master", "commit"}); + } + + @Test(expected = LiteVCS.ConflictNameException.class) + public void cloneBranchTest() throws Exception { + System.setOut(mock(PrintStream.class)); + + Header header = mock(Header.class); + when(header.getCurrentBranchName()).thenReturn("master"); + + DataManager dataManager = mock(DataManager.class); + when(dataManager.hasBranch("branch")).thenReturn(true); + when(dataManager.getHeader()).thenReturn(header); + + ConsoleWorker consoleWorker = new ConsoleWorker(new LiteVCS(dataManager)); + consoleWorker.execute("create_branch", new String[]{"branch"}); } } From 8c39c47be65140bad0d61dc0aca9ca62e22e81ca Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Thu, 13 Apr 2017 14:09:57 +0300 Subject: [PATCH 25/30] Remove useless files --- .../Gradle__org_mockito_mockito_all_1_10_19.xml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .idea/libraries/Gradle__org_mockito_mockito_all_1_10_19.xml diff --git a/.idea/libraries/Gradle__org_mockito_mockito_all_1_10_19.xml b/.idea/libraries/Gradle__org_mockito_mockito_all_1_10_19.xml deleted file mode 100644 index 9bddc17..0000000 --- a/.idea/libraries/Gradle__org_mockito_mockito_all_1_10_19.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file From 481dcaecb31758d04acf165177b8412c0ff65199 Mon Sep 17 00:00:00 2001 From: Artyom Lobanov Date: Thu, 13 Apr 2017 14:31:06 +0300 Subject: [PATCH 26/30] Update README.txt --- README.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.txt b/README.txt index 40d474b..42d59f7 100644 --- a/README.txt +++ b/README.txt @@ -17,6 +17,8 @@ Branch хранит id VersionNode, которая считается полсе Header хранит имя последнего указанного автора (через hello) (это имя подставляется в создаваемые коммиты) и имя активной ветки. +Stage хранит информацию об изменениях, которые войдут в следующий коммит. + @@ -26,16 +28,18 @@ Header хранит имя последнего указанного автор commit [сообщение] - Делает коммит в текущую ветку, тем самым подтверждая все изменения checkout [id of content descriptor] - id можно узнать через logs или подсмотрев в папку .liteVCS/descriptors. очищает рабочую папку, а затем подгружает прописанные в дескрипторе файлы. - clear - Очищает рабочую папку, удаляя всё, кроме папки .liteVCS + clean - Очищает рабочую папку, удаляя всё, кроме папки .liteVCS и файлов, входивших в последний коммит или изменения которых уже добавлены в Stage + status - Показывает изменения, находящиеся в Stage, для каждого файла из рабочей папки сообщает статус (CHANGED - файл отличается от версии в последнем коммите и в Stage-е; NOT_CHANGED; UNKNOWN - не отслеживается репозиторием), а также перечисляет файлы, которые из папки пропали, но не были удалены через репозиторий. create_branch [название] - Создёт новую ветку, ответвляющуюся от головы текущей ветки remove_branch [название] - Удаляет ветку switch_branch [название] - Переключается на другую ветку. Предварительно необходимо закоммитить все накопленные изменения. merge_branch [название] [сообщение] - Если нет неразрешимых конфликтов (один и тот же файл изменили по разному в разных ветках), то добавляет изменения из указанной ветки в текущую, создавая коммит с указанным сообщением. - reset - Приводит рабочую папку к состоянию, описанномуу в голове ветки. + reset [файл] - Откатывает состояние файла к последнему коммиту. Удаляет иформацию об изменении этого файла из Stage uninstall - Удаляет репозиторий logs [число] - Показывает последние [число] коммитов в текующей ветке hello [имя] - Задаёт имя пользователя. + remove [файл] - Удаляет файл из рабочей папки, записывает в Stage информацию о том, что в следующем коммите надо этот файл перестать отслеживать From 50b5a6e67eefd42654d1529e60f67895e64282f8 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Fri, 29 Dec 2017 07:04:01 +0300 Subject: [PATCH 27/30] Add Jenkisfile --- Jenkinsfile | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..153ee49 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,10 @@ +pipeline { + agent any + stages { + stage('build') { + steps { + sh 'gradle build' + } + } + } +} \ No newline at end of file From aac60f472db599c5bbf2d380553157fb32b240ef Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Fri, 29 Dec 2017 07:35:48 +0300 Subject: [PATCH 28/30] Fix Jenkisfile --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index 153ee49..a53c4f9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,6 +3,7 @@ pipeline { stages { stage('build') { steps { + sh 'cd java-spbau' sh 'gradle build' } } From 5ec8b1682003926c959635200c2b56666964d769 Mon Sep 17 00:00:00 2001 From: ArtyomLobanov Date: Fri, 29 Dec 2017 07:42:18 +0300 Subject: [PATCH 29/30] Fix Jenkisfile --- Jenkinsfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a53c4f9..257468d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,8 +3,7 @@ pipeline { stages { stage('build') { steps { - sh 'cd java-spbau' - sh 'gradle build' + sh 'gradlew build' } } } From f65fcf7d0a71acabc9a00547d03cf938d7495ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC?= Date: Fri, 29 Dec 2017 07:46:08 +0300 Subject: [PATCH 30/30] Change Jenkinsfile --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 257468d..84889f6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,8 +3,8 @@ pipeline { stages { stage('build') { steps { - sh 'gradlew build' + sh './gradlew build' } } } -} \ No newline at end of file +}