diff --git a/lgit/Readme.md b/lgit/Readme.md new file mode 100644 index 0000000..e328909 --- /dev/null +++ b/lgit/Readme.md @@ -0,0 +1,48 @@ +# LGit + +### Description + +This repository contains simple draft of **git**. + +### Usage + +The following commands are available:
+``` +init +add +rm +status +commit +reset +log [from_revision] +checkout +checkout -r +``` + +where *<smth>* means mandatory argument while *[smth]* +implies he optional one.
+*revision* is represented as short (at least 7 symbols) or entire hash code.
+*checkout -r * is the replacement of *checkout -- *.
+ +### File hierarchy + + .l_git/ + logs/ + [log_file_for_branch] + storage/ + [dir_as_hashcode] + [file] + index/ + [file] + HEAD - file with information about current state + + +### How to build and run + +In order to run this app, follow this steps: + +* clone this repository +* checkout on branch *git_milestone_2* +* run `./gradlew installDist` +* go to the `./build/install/l_git/bin/` +* execute `l_git` (or `l_git.bat` on Windows) \ No newline at end of file diff --git a/lgit/build.gradle b/lgit/build.gradle new file mode 100644 index 0000000..5888999 --- /dev/null +++ b/lgit/build.gradle @@ -0,0 +1,22 @@ +group 'ru.ifmo' +version '1.0-SNAPSHOT' + +apply plugin: 'java' +apply plugin: 'application' + +mainClassName = "ru.ifmo.git.LGit" + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + compile group: 'net.sourceforge.argparse4j', name: 'argparse4j', version: '0.2.0' + compile group: 'org.apache.commons', name: 'commons-io', version: '1.3.2' + implementation 'com.google.code.gson:gson:2.8.5' + compile group: 'commons-codec', name: 'commons-codec', version: '1.11' + compile group: 'info.picocli', name: 'picocli', version: '3.6.0' +} diff --git a/lgit/gradle/wrapper/gradle-wrapper.properties b/lgit/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ef57f61 --- /dev/null +++ b/lgit/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Oct 15 04:46:20 MSK 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip diff --git a/lgit/gradlew b/lgit/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/lgit/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + 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 + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# 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" "$@" diff --git a/lgit/gradlew.bat b/lgit/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/lgit/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/lgit/settings.gradle b/lgit/settings.gradle new file mode 100644 index 0000000..8c41d34 --- /dev/null +++ b/lgit/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'l_git' + diff --git a/lgit/src/main/java/ru/ifmo/git/LGit.java b/lgit/src/main/java/ru/ifmo/git/LGit.java new file mode 100644 index 0000000..0a78c95 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/LGit.java @@ -0,0 +1,42 @@ +package ru.ifmo.git; + +import picocli.CommandLine; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import java.util.List; + +import ru.ifmo.git.commands.Git; +import ru.ifmo.git.entities.GitManager; +import ru.ifmo.git.util.CommandResult; + +public class LGit { + + private static Path cwd() { + return Paths.get(System.getProperty("user.dir")); + } + + public static void main(String[] args) { + CommandLine commandLine = new CommandLine(new Git()); + try { + List parsed = commandLine.parse(args); + + if (parsed.size() == 2) { + CommandLine command = parsed.get(1); + GitManager manager = new GitManager(cwd()); + if (command.isUsageHelpRequested()) { + command.usage(System.out); + return; + } + CommandResult result = manager.executeCommand(command); + result.print(); + } else { + commandLine.usage(System.out); + } + } catch (CommandLine.UnmatchedArgumentException e) { + System.out.println("l_git: no such command. See 'l_git --help'."); + } + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/commands/Add.java b/lgit/src/main/java/ru/ifmo/git/commands/Add.java new file mode 100644 index 0000000..591c222 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/commands/Add.java @@ -0,0 +1,44 @@ +package ru.ifmo.git.commands; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +import java.io.IOException; + +import java.nio.file.Path; + +import java.util.List; + +import ru.ifmo.git.entities.GitFileKeeper; +import ru.ifmo.git.entities.GitManager; +import ru.ifmo.git.util.CommandResult; + +@Command( + name = "add", + description = "Add file contents to the index", + helpCommand = true +) +public class Add implements GitCommand { + + @Option( + names = {"-h", "--help"}, + usageHelp = true, + description = "Display more info about command add." + ) + boolean usageHelpRequested; + + @Parameters(arity = "*", paramLabel = "") + private List files; + + @Override + public boolean incorrectArgs() { + return !GitFileKeeper.checkFilesExist(files); + } + + @Override + public CommandResult doWork(GitManager gitManager) throws IOException { + return gitManager.add(files); + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/commands/Checkout.java b/lgit/src/main/java/ru/ifmo/git/commands/Checkout.java new file mode 100644 index 0000000..499d7f0 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/commands/Checkout.java @@ -0,0 +1,56 @@ +package ru.ifmo.git.commands; + +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; +import picocli.CommandLine.Command; + +import java.io.IOException; + +import java.nio.file.Path; + +import java.util.List; + +import ru.ifmo.git.entities.*; +import ru.ifmo.git.util.*; + +@Command( + name = "checkout", + description = "Switch branches or restore working tree files", + helpCommand = true +) +public class Checkout implements GitCommand { + + @Option( + names = {"-h", "--help"}, + usageHelp = true, + description = "Display more info about command checkout." + ) + boolean usageHelpRequested; + + @Parameters(arity = "?", paramLabel = "") + private String revision; + + @Option( + names = {"-r"}, + arity = "*", + paramLabel = "", + description = "Discard changes in working directory in the given files.", + type = Path.class + ) + private List files; + + @Override + public boolean incorrectArgs() { + return (revision == null || revision.length() < 6) && (files.isEmpty()); + } + + @Override + public CommandResult doWork(GitManager gitManager) throws GitException, IOException { + if (revision != null) { + return gitManager.checkout(revision); + } else { + return gitManager.checkout(files); + } + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/commands/Commit.java b/lgit/src/main/java/ru/ifmo/git/commands/Commit.java new file mode 100644 index 0000000..740b356 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/commands/Commit.java @@ -0,0 +1,39 @@ +package ru.ifmo.git.commands; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +import java.io.IOException; + +import ru.ifmo.git.entities.GitManager; +import ru.ifmo.git.util.CommandResult; +import ru.ifmo.git.util.GitException; + +@Command( + name = "commit", + description = "Record changes to the repository", + helpCommand = true +) +public class Commit implements GitCommand { + + @Option( + names = {"-h", "--help"}, + usageHelp = true, + description = "Display more info about command commit." + ) + boolean usageHelpRequested; + + @Option( + names = {"-m", "--message"}, + arity = "?", + paramLabel = "", + description = "Use the given as the commit message." + ) + private String message; + + @Override + public CommandResult doWork(GitManager gitManager) throws GitException, IOException { + return gitManager.commit(message); + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/commands/Git.java b/lgit/src/main/java/ru/ifmo/git/commands/Git.java new file mode 100644 index 0000000..c0cab9d --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/commands/Git.java @@ -0,0 +1,28 @@ +package ru.ifmo.git.commands; + +import picocli.CommandLine; + +@CommandLine.Command( + name = "l_git", + subcommands = { + Init.class, + Add.class, + Remove.class, + Status.class, + Commit.class, + Reset.class, + Log.class, + Checkout.class + }, + description = "A version control system created by lergor." +) +public class Git { + + @CommandLine.Option( + names = {"-h", "--help"}, + help = true, + description = "Prints the synopsis and a list of the most commonly used commands." + ) + boolean isHelpRequested; + +} diff --git a/lgit/src/main/java/ru/ifmo/git/commands/GitCommand.java b/lgit/src/main/java/ru/ifmo/git/commands/GitCommand.java new file mode 100644 index 0000000..b22386e --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/commands/GitCommand.java @@ -0,0 +1,38 @@ +package ru.ifmo.git.commands; + +import java.io.IOException; + +import ru.ifmo.git.entities.GitManager; +import ru.ifmo.git.entities.GitTree; +import ru.ifmo.git.util.*; + +public interface GitCommand { + + default boolean incorrectArgs() { + return false; + } + + default boolean gitNotInited(GitTree tree) { + return !tree.exists(); + } + + CommandResult doWork(GitManager gitManager) throws GitException, IOException; + + default CommandResult execute(GitManager gitManager) { + String name = this.getClass().getSimpleName().toLowerCase(); + if (gitNotInited(gitManager.tree())) { + return new CommandResult(ExitStatus.ERROR, "fatal: Not a l_git repository"); + } + if (incorrectArgs()) { + return new CommandResult(ExitStatus.ERROR, name + ": incorrect arguments"); + } + try { + return doWork(gitManager); + } catch (GitException e) { + return new CommandResult(ExitStatus.ERROR, name + ": " + e.getMessage()); + } catch (IOException e) { + return new CommandResult(ExitStatus.ERROR, name + ": error appeared", e); + } + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/commands/Init.java b/lgit/src/main/java/ru/ifmo/git/commands/Init.java new file mode 100644 index 0000000..b153df6 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/commands/Init.java @@ -0,0 +1,48 @@ +package ru.ifmo.git.commands; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import ru.ifmo.git.entities.GitManager; +import ru.ifmo.git.entities.GitTree; +import ru.ifmo.git.util.*; + +@Command( + name = "init", + description = "Create an empty Git repository or reinitialize an existing one", + helpCommand = true +) +public class Init implements GitCommand { + + @Option( + names = {"-h", "--help"}, + usageHelp = true, + description = "Display more info about command init." + ) + boolean usageHelpRequested; + + @Parameters(arity = "?", paramLabel = "directory") + private Path repositoryDirectory; + + private Path repositoryDirectory() { + if (repositoryDirectory == null) { + return Paths.get(System.getProperty("user.dir")).normalize().toAbsolutePath(); + } + return repositoryDirectory; + } + + @Override + public boolean gitNotInited(GitTree tree) { + return false; + } + + @Override + public CommandResult doWork(GitManager gitManager) throws GitException { + return new GitManager(repositoryDirectory()).init(); + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/commands/Log.java b/lgit/src/main/java/ru/ifmo/git/commands/Log.java new file mode 100644 index 0000000..c397454 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/commands/Log.java @@ -0,0 +1,37 @@ +package ru.ifmo.git.commands; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +import ru.ifmo.git.entities.GitManager; +import ru.ifmo.git.util.CommandResult; +import ru.ifmo.git.util.GitException; + +@Command( + name = "log", + description = "Show commit logs", + helpCommand = true +) +public class Log implements GitCommand { + + @Option( + names = {"-h", "--help"}, + usageHelp = true, + description = "Display more info about command log." + ) + boolean usageHelpRequested; + + @Parameters( + arity = "?", + paramLabel = "", + description = "Show only commits since specified ." + ) + private String revision; + + @Override + public CommandResult doWork(GitManager gitManager) throws GitException { + return gitManager.log(revision); + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/commands/Remove.java b/lgit/src/main/java/ru/ifmo/git/commands/Remove.java new file mode 100644 index 0000000..e8966a4 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/commands/Remove.java @@ -0,0 +1,43 @@ +package ru.ifmo.git.commands; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +import java.io.IOException; + +import java.nio.file.Path; + +import java.util.*; + +import ru.ifmo.git.entities.*; +import ru.ifmo.git.util.*; + +@Command( + name = "rm", + description = "Remove files from the working tree and from the index", + helpCommand = true +) +public class Remove implements GitCommand { + + @Option( + names = {"-h", "--help"}, + usageHelp = true, + description = "Display more info about command rm." + ) + boolean usageHelpRequested; + + @Parameters(arity = "*", paramLabel = "") + private List files; + + @Override + public boolean incorrectArgs() { + return files.isEmpty(); + } + + @Override + public CommandResult doWork(GitManager gitManager) throws IOException { + return gitManager.remove(files); + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/commands/Reset.java b/lgit/src/main/java/ru/ifmo/git/commands/Reset.java new file mode 100644 index 0000000..0f6557a --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/commands/Reset.java @@ -0,0 +1,40 @@ +package ru.ifmo.git.commands; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +import java.io.IOException; + +import ru.ifmo.git.entities.GitManager; +import ru.ifmo.git.util.CommandResult; +import ru.ifmo.git.util.GitException; + +@Command( + name = "reset", + description = "Reset current HEAD to the specified state", + helpCommand = true +) +public class Reset implements GitCommand { + + @Option( + names = {"-h", "--help"}, + usageHelp = true, + description = "Display more info about command reset." + ) + boolean usageHelpRequested; + + @Parameters(arity = "1", paramLabel = "") + private String revision; + + @Override + public boolean incorrectArgs() { + return (revision.length() < 6); + } + + @Override + public CommandResult doWork(GitManager gitManager) throws GitException, IOException { + return gitManager.reset(revision); + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/commands/Status.java b/lgit/src/main/java/ru/ifmo/git/commands/Status.java new file mode 100644 index 0000000..3433135 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/commands/Status.java @@ -0,0 +1,31 @@ +package ru.ifmo.git.commands; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +import java.io.IOException; + +import ru.ifmo.git.entities.GitManager; +import ru.ifmo.git.util.CommandResult; +import ru.ifmo.git.util.GitException; + +@Command( + name = "status", + description = "Show current state of the repository", + helpCommand = true +) +public class Status implements GitCommand { + + @Option( + names = {"-h", "--help"}, + usageHelp = true, + description = "Display more info about command checkout." + ) + boolean usageHelpRequested; + + @Override + public CommandResult doWork(GitManager gitManager) throws GitException, IOException { + return gitManager.status(); + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/entities/GitClerk.java b/lgit/src/main/java/ru/ifmo/git/entities/GitClerk.java new file mode 100644 index 0000000..4084af6 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/entities/GitClerk.java @@ -0,0 +1,182 @@ +package ru.ifmo.git.entities; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.apache.commons.io.FileUtils; + +import java.io.*; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +import java.util.*; +import java.util.stream.Collectors; + +import ru.ifmo.git.util.CommitInfo; +import ru.ifmo.git.util.GitException; +import ru.ifmo.git.util.HeadInfo; + +public class GitClerk { + + private static final String ENCODING = "UTF-8"; + private static final String sep = System.getProperty("line.separator"); + private final Gson gson = new GsonBuilder().create(); + private final GitTree gitTree; + + GitClerk(GitTree gitTree) { + this.gitTree = gitTree; + } + + public HeadInfo getHeadInfo() throws GitException { + String headJson; + try { + headJson = FileUtils.readFileToString(gitTree.head().toFile()); + } catch (IOException e) { + throw new GitException("error while reading HEAD"); + } + return gson.fromJson(headJson, HeadInfo.class); + } + + void writeHeadInfo(HeadInfo newHeadInfo) throws GitException { + String newInfo = gson.toJson(newHeadInfo); + try { + FileUtils.writeStringToFile(gitTree.head().toFile(), newInfo, ENCODING); + } catch (IOException e) { + throw new GitException("error while writing log " + e.getMessage()); + } + } + + void changeHeadInfo(String hash) throws GitException { + HeadInfo headInfo = getHeadInfo(); + if (headInfo.headHash == null || headInfo.headHash.equals(headInfo.currentHash)) { + headInfo.moveBoth(hash); + } else { + headInfo.moveCurrent(hash); + } + writeHeadInfo(headInfo); + } + + public void writeLog(CommitInfo commit) throws GitException { + File logFile = gitTree.log().resolve(commit.branch).toFile(); + try { + if (logFile.exists() || logFile.createNewFile()) { + writeToFile(logFile.toPath(), gson.toJson(commit) + sep, true); + } + } catch (IOException e) { + throw new GitException("error while creating log " + e.getMessage()); + } + } + + private String getUserMessage() { + try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) { + System.out.print("please enter message: "); + return br.readLine(); + } catch (IOException e) { + return ""; + } + } + + private static void writeToFile(Path file, String content, boolean append) throws GitException { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file.toFile(), append))) { + writer.write(content); + } catch (IOException e) { + throw new GitException("error while writing to " + file.getFileName()); + } + } + + public List getLogHistory() throws GitException { + String branch = getHeadInfo().branchName; + File logFile = gitTree.log().resolve(branch).toFile(); + List history = new ArrayList<>(); + if(!logFile.exists()) { + return history; + } + try (BufferedReader br = new BufferedReader(new FileReader(logFile))) { + for (String line; (line = br.readLine()) != null; ) { + history.add(gson.fromJson(line, CommitInfo.class)); + } + } catch (IOException e) { + throw new GitException("error while reading log for " + branch); + } + Collections.reverse(history); + return history; + } + + private static String getAuthor() { + return System.getProperty("user.name"); + } + + private static String getCurrentTime() { + DateFormat df = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy ZZ"); + return df.format(Calendar.getInstance().getTime()); + } + + public CommitInfo fillCommitInfo(String message) throws GitException { + CommitInfo info = new CommitInfo(); + info.setAuthor(getAuthor()); + info.setTime(getCurrentTime()); + info.setRootDirectory(gitTree.repo()); + info.setMessage(message == null ? getUserMessage() : message); + info.setHash(GitEncoder.createCommitHash(info)); + info.setBranch(getHeadInfo().branchName); + return info; + } + + String emptyLogResult() throws GitException { + HeadInfo headInfo = getHeadInfo(); + return "fatal: your current branch '" + + headInfo.branchName + + "' does not have any commits yet\n"; + } + + public Map compareRepoAndIndex() throws GitException, IOException { + Map fileToStatus = new HashMap<>(); + List inIndex = collectNames(gitTree.index()); + List inCWD = collectNames(gitTree.repo()); + for (String file : inIndex) { + Path fileInCWD = gitTree.repo().resolve(file); + Path fileInIndex = gitTree.index().resolve(file); + if (Files.isRegularFile(fileInIndex)) { + if (!Files.exists(fileInCWD)) { + fileToStatus.put(file, "deleted"); + } else { + String hashInIndex = GitEncoder.getHash(fileInIndex, gitTree.index()); + String hashInCWD = GitEncoder.getHash(fileInCWD, gitTree.repo()); + if (!hashInCWD.equals(hashInIndex)) { + fileToStatus.put(file, "modified"); + } + } + } + } + inCWD.forEach(f -> { + if(!f.contains(".l_git") && !Files.exists(gitTree.index().resolve(f))) { + fileToStatus.put(f, "new"); + } + }); + return fileToStatus; + } + + List collectNames(Path directory) throws IOException { + List fileNames = new ArrayList<>(); + List files = Files.list(directory).collect(Collectors.toList()); + for (Path file : files) { + String fileName = file.toFile().getName(); + if (!fileName.equals(".l_git")) { + fileNames.add(fileName); + if (Files.isDirectory(file)) { + List fromSubDir = collectNames(file); + for (String f : fromSubDir) { + fileNames.add(Paths.get(fileName, f).toString()); + } + } + } + } + return fileNames; + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/entities/GitDecoder.java b/lgit/src/main/java/ru/ifmo/git/entities/GitDecoder.java new file mode 100644 index 0000000..365fbe5 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/entities/GitDecoder.java @@ -0,0 +1,74 @@ +package ru.ifmo.git.entities; + +import org.apache.commons.io.IOUtils; + +import ru.ifmo.git.util.BlobType; +import ru.ifmo.git.util.FileReference; + +import java.io.BufferedReader; +import java.io.IOException; + +import java.nio.file.Files; +import java.nio.file.Path; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class GitDecoder { + + private static final String ENCODING = "UTF-8"; + private static final int markLength = BlobType.size(); + private static final String tab = "\t"; + + + private static BlobType readMarker(String infoString) { + return BlobType.typeOf(infoString.substring(0, BlobType.size())); + } + + private static String removeMarker(String string) { + return string.substring(BlobType.size()); + } + + static List formCommitReferences(Path commitFile, GitFileKeeper storage) throws IOException { + return decodeTree(commitFile, storage); + } + + private static String withoutMarker(String infoString) { + return infoString.substring(markLength); + } + + private static List decodeTree(Path treeFile, GitFileKeeper storage) throws IOException { + List references = new ArrayList<>(); + FileReference decodedTree = decodeFile(treeFile); + List components = IOUtils.readLines(decodedTree.content, ENCODING); + for (Object component : components) { + references.addAll(decodeComponent((String) component, storage)); + } + return references; + } + + private static FileReference decodeFile(Path file) throws IOException { + FileReference reference = new FileReference(); + BufferedReader reader = Files.newBufferedReader(file); + String infoLine = reader.readLine(); + reference.type = readMarker(infoLine); + reference.name = withoutMarker(infoLine); + reference.content = IOUtils.toInputStream(IOUtils.toString(reader)); + return reference; + } + + private static List decodeComponent(String component, GitFileKeeper storage) throws IOException { + BlobType type = readMarker(component); + String[] HashAndName = removeMarker(component).split(tab); + Path encodedFile = storage.correctPath(HashAndName[0]); + if (type.equals(BlobType.FILE)) { + return Collections.singletonList(decodeFile(encodedFile)); + } + if (type.equals(BlobType.TREE)) { + return decodeTree(encodedFile, storage); + } + return Collections.emptyList(); + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/entities/GitEncoder.java b/lgit/src/main/java/ru/ifmo/git/entities/GitEncoder.java new file mode 100644 index 0000000..54e8068 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/entities/GitEncoder.java @@ -0,0 +1,152 @@ +package ru.ifmo.git.entities; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.IOUtils; + +import ru.ifmo.git.util.BlobType; +import ru.ifmo.git.util.CommitInfo; +import ru.ifmo.git.util.FileReference; + +import java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; + +import java.nio.file.Files; +import java.nio.file.Path; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class GitEncoder { + + private static final String ENCODING = "UTF-8"; + private static final String sep = System.getProperty("line.separator"); + + private static String marker(Path path) { + if (Files.isRegularFile(path)) { + return BlobType.FILE.asString(); + } + return BlobType.TREE.asString(); + } + + static String getHash(Path file, Path root) throws IOException { + InputStream fileContent = formContent(file, root); + String filePath = file.toFile().getName(); + fileContent = new SequenceInputStream( + fileContent, + IOUtils.toInputStream(filePath) + ); + return DigestUtils.sha1Hex(fileContent); + } + + private static InputStream formContent(Path path, Path root) throws IOException { + if (Files.isRegularFile(path)) { + return Files.newInputStream(path); + } + List files = Files.list(path).collect(Collectors.toList()); + StringBuilder builder = new StringBuilder(); + for (Path file : files) { + if (!file.toFile().getName().equals(".l_git")) { + writeInfoString(builder, file, root); + } + } + return IOUtils.toInputStream(builder.toString(), ENCODING); + } + + private static void writeInfoString(StringBuilder builder, Path file, Path root) throws IOException { + String tab = "\t"; + builder.append(marker(file)) + .append(getHash(file, root)) + .append(tab) + .append(root.relativize(file)) + .append(sep); + } + + private static InputStream formHeader(Path file, Path root) throws IOException { + String info = marker(file) + root.relativize(file) + sep; + return IOUtils.toInputStream(info, ENCODING); + } + + private static InputStream encodeFile(Path file, Path root) throws IOException { + return new SequenceInputStream( + formHeader(file, root), + formContent(file, root) + ); + } + + private static FileReference formEncodeReference(Path file, Path root) throws IOException { + FileReference reference = new FileReference(); + reference.type = BlobType.typeOf(marker(file)); + reference.name = getHash(file, root); + reference.content = encodeFile(file, root); + return reference; + } + + private static List formEncodeReferences(Path file, Path root) throws IOException { + if (Files.isRegularFile(file)) { + return Collections.singletonList(formEncodeReference(file, root)); + } else { + List references = new ArrayList<>(); + List files = Files.list(file).collect(Collectors.toList()); + references.add(formEncodeReference(file, root)); + for (Path f : files) { + if (Files.isRegularFile(f)) { + FileReference lll = formEncodeReference(f, root); + references.add(lll); + } else { + references.addAll(formEncodeReferences(f, root)); + } + } + return references; + } + } + + static String createHash() { + return DigestUtils.sha1Hex(UUID.randomUUID().toString()); + } + + static String createCommitHash(CommitInfo info) { + String builder = info.time + info.rootDirectory + + info.author + info.branch; + return DigestUtils.sha1Hex(builder); + } + + private static FileReference formCommitReference(String hash, List files, Path root) throws IOException { + FileReference commit = new FileReference(); + commit.type = BlobType.COMMIT; + commit.name = hash; + commit.content = encodeCommitFiles(files, root); + return commit; + } + + private static List formEncodeReferences(List files, Path root) throws IOException { + List references = new ArrayList<>(); + for (Path file : files) { + if (!file.getFileName().toString().equals(".l_git")) { + references.addAll(formEncodeReferences(file, root)); + } + } + return references; + } + + private static InputStream encodeCommitFiles(List files, Path root) throws IOException { + StringBuilder builder = new StringBuilder(); + builder.append(BlobType.COMMIT.asString()).append(sep); + for (Path file : files) { + writeInfoString(builder, file, root); + } + return IOUtils.toInputStream(builder.toString(), ENCODING); + } + + static List formCommitReferences(String hash, Path root) throws IOException { + List files = Files.list(root).collect(Collectors.toList()); + List references = formEncodeReferences(files, root); + FileReference commitReference = formCommitReference(hash, files, root); + references.add(commitReference); + return references; + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/entities/GitFileKeeper.java b/lgit/src/main/java/ru/ifmo/git/entities/GitFileKeeper.java new file mode 100644 index 0000000..57f5077 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/entities/GitFileKeeper.java @@ -0,0 +1,119 @@ +package ru.ifmo.git.entities; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import ru.ifmo.git.util.BlobType; +import ru.ifmo.git.util.FileReference; + +import java.io.File; +import java.io.IOException; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class GitFileKeeper { + + private final Path storage; + private static final int dirNameLen = 2; + + GitFileKeeper(GitTree gitTree) { + storage = gitTree.storage(); + } + + private Path getDir(String blob) { + return storage.resolve(blob.substring(0, dirNameLen)); + } + + public Path correctPath(String blob) { + return getDir(blob).resolve(blob.substring(dirNameLen)); + } + + private Path filePath(String blob) { + Path directory = getDir(blob); + if (Files.notExists(directory)) { + boolean ignored = directory.toFile().mkdirs(); + } + return directory.resolve(blob.substring(dirNameLen)); + } + + public void saveCommit(List references) throws IOException { + for (FileReference reference : references) { + Path file = filePath(reference.name); + Files.copy(reference.content, file, StandardCopyOption.REPLACE_EXISTING); + } + } + + public Optional findFileInStorage(String hash) throws IOException { + List blobs; + blobs = Files.list(getDir(hash)).collect(Collectors.toList()); + for (Path blob : blobs) { + if (blob.toFile().getName().startsWith(hash.substring(dirNameLen))) { + return Optional.of(blob); + } + } + return Optional.empty(); + } + + public void restoreCommit(List references, Path destination) throws IOException { + for (FileReference reference : references) { + Path file = destination.resolve(reference.name); + if (reference.type.equals(BlobType.FILE)) { + if(Files.notExists(file)) { + Path dirs = file.getParent(); + if(!destination.equals(dirs)) { + boolean ignored = dirs.toFile().mkdirs(); + } + Files.createFile(file); + } + Files.copy(reference.content, file, StandardCopyOption.REPLACE_EXISTING); + } else { + boolean ignored = file.toFile().mkdirs(); + } + } + } + + static public void copyAll(List files, Path source, Path targetDir) throws IOException { + File destination = targetDir.toFile(); + if (destination.exists() || (!destination.exists() && destination.mkdirs())) { + for (Path file : files) { + Path newFile = targetDir.resolve(source.relativize(file)); + if (Files.isRegularFile(file)) { + FileUtils.writeLines(newFile.toFile(), Files.readAllLines(file), System.lineSeparator()); + } else if (Files.isDirectory(file)) { + FileUtils.copyDirectoryToDirectory(file.toFile(), newFile.getParent().toFile()); + } + } + } + } + + static public void clearDirectory(Path directory) throws IOException { + List files = Files.list(directory).collect(Collectors.toList()); + removeAll(files); + } + + static public void removeAll(List files) throws IOException { + for (Path file : files) { + if (Files.isDirectory(file)) { + clearDirectory(file); + } + Files.deleteIfExists(file); + } + } + + static public boolean checkFilesExist(List files) { + for (Path file : files) { + if (!Files.exists(file)) { + System.out.println("fatal: pathspec " + file.getFileName() + " did not match any files"); + return false; + } + } + return true; + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/entities/GitManager.java b/lgit/src/main/java/ru/ifmo/git/entities/GitManager.java new file mode 100644 index 0000000..1c932db --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/entities/GitManager.java @@ -0,0 +1,185 @@ +package ru.ifmo.git.entities; + +import picocli.CommandLine; + +import ru.ifmo.git.commands.GitCommand; +import ru.ifmo.git.util.*; + +import java.io.IOException; + +import java.nio.file.Files; +import java.nio.file.Path; + +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class GitManager { + + private GitTree tree; + private GitClerk clerk; + private GitFileKeeper fileKeeper; + + private static final String sep = System.getProperty("line.separator"); + + public GitManager(Path directory) { + this.tree = new GitTree(directory); + clerk = new GitClerk(tree); + fileKeeper = new GitFileKeeper(tree); + } + + public GitTree tree() { + return tree; + } + + public CommandResult executeCommand(CommandLine commandLine) { + GitCommand command = commandLine.getCommand(); + return command.execute(this); + } + + public CommandResult init() throws GitException { + Message message = new Message(); + if (!tree.exists()) { + try { + tree.createGitTree(); + message.write("initialized empty "); + clerk.writeHeadInfo(new HeadInfo()); + } catch (IOException e) { + String msg = "unable to create repository in " + tree.repo(); + return new CommandResult(ExitStatus.FAILURE, msg, e); + } + } else { + message.write("reinitialized existing "); + } + message.write("lGit repository in " + tree.repo()); + return new CommandResult(ExitStatus.SUCCESS, message); + } + + public CommandResult add(List files) throws IOException { + GitFileKeeper.copyAll(files, tree.repo(), tree.index()); + return new CommandResult(ExitStatus.SUCCESS, "add: done!"); + } + + public CommandResult commit(String message) throws GitException, IOException { + CommitInfo commitInfo = clerk.fillCommitInfo(message); + List references = GitEncoder.formCommitReferences(commitInfo.hash, tree.index()); + fileKeeper.saveCommit(references); + clerk.writeLog(commitInfo); + clerk.changeHeadInfo(commitInfo.hash); + return new CommandResult(ExitStatus.SUCCESS, "commit: done!"); + } + + public CommandResult log(String revision) throws GitException { + CommandResult emptyLog = new CommandResult(ExitStatus.SUCCESS, clerk.emptyLogResult()); + if (Files.notExists(tree.log())) { + return emptyLog; + } + List history = clerk.getLogHistory(); + if (history.size() == 0) { + return emptyLog; + } + history = history.stream().filter( + new Predicate() { + private boolean include = (revision == null || revision.isEmpty()); + + @Override + public boolean test(CommitInfo commitInfo) { + include = include || commitInfo.hash.startsWith(revision); + return include; + } + } + ).collect(Collectors.toList()); + if (history.size() == 0) { + String failMessage = "log: '" + revision + "' unknown revision"; + return new CommandResult(ExitStatus.FAILURE, failMessage); + } + Message logContent = new Message(); + history.forEach(info -> logContent.write(info.toString())); + return new CommandResult(ExitStatus.SUCCESS, logContent); + } + + public CommandResult reset(String revision) throws GitException, IOException { + if (clerk.getHeadInfo().currentHash.equals(revision)) { + return new CommandResult(ExitStatus.FAILURE, + "reset: already on commit " + revision); + } + Optional commit = fileKeeper.findFileInStorage(revision); + if (commit.isPresent()) { + Path commitFile = commit.get(); + List references = GitDecoder.formCommitReferences(commitFile, fileKeeper); + GitFileKeeper.clearDirectory(tree.index()); + fileKeeper.restoreCommit(references, tree.index()); + clerk.changeHeadInfo(revision); + return new CommandResult(ExitStatus.SUCCESS, "reset: done!"); + } + String failMessage = "reset: '" + revision + "' unknown revision"; + return new CommandResult(ExitStatus.FAILURE, failMessage); + } + + public CommandResult checkout(String revision) throws GitException, IOException { + if (clerk.getHeadInfo().currentHash.equals(revision)) { + return new CommandResult(ExitStatus.FAILURE, + "checkout: already on commit " + revision); + } + Optional commit = fileKeeper.findFileInStorage(revision); + if (commit.isPresent()) { + Path commitFile = commit.get(); + List references = GitDecoder.formCommitReferences(commitFile, fileKeeper); + GitFileKeeper.clearDirectory(tree.index()); + fileKeeper.restoreCommit(references, tree.index()); + GitFileKeeper.copyAll(Files.list(tree.index()).collect(Collectors.toList()), + tree.index(), tree.repo()); + clerk.changeHeadInfo(revision); + } + return new CommandResult(ExitStatus.SUCCESS, "checkout: done!"); + } + + public CommandResult checkout(List files) throws IOException { + files = files.stream() + .map(f -> tree.index().resolve(tree.repo().relativize(f))) + .collect(Collectors.toList()); + if (!GitFileKeeper.checkFilesExist(files)) { + return new CommandResult(ExitStatus.FAILURE, ""); + } + GitFileKeeper.copyAll(files, tree.index(), tree.repo()); + return new CommandResult(ExitStatus.SUCCESS, "checkout: done!"); + } + + public CommandResult checkoutBranch(String branch) throws GitException { + return new CommandResult(ExitStatus.SUCCESS, "kek"); + } + + public CommandResult status() throws GitException, IOException { + HeadInfo headInfo = clerk.getHeadInfo(); + Message info = new Message(); + info.write("On branch " + headInfo.branchName + sep); + Map fileToStatus = clerk.compareRepoAndIndex(); + if (fileToStatus.isEmpty()) { + info.write("No changed files"); + } else { + for (Map.Entry e : fileToStatus.entrySet()) { + info.write(sep); + info.write(e.getValue() + ": "); + info.write("\t"); + info.write(e.getKey()); + } + } + return new CommandResult(ExitStatus.SUCCESS, info); + } + + public CommandResult remove(List files) throws IOException { + List filesInIndex = files.stream() + .map(f -> tree.index().resolve(tree.repo().relativize(f))) + .collect(Collectors.toList()); + GitFileKeeper.removeAll(filesInIndex); +// List filesInCWD = files.stream() +// .map(tree.repo()::resolve) +// .collect(Collectors.toList()); +// GitFileKeeper.removeAll(filesInCWD); + return new CommandResult(ExitStatus.SUCCESS, "rm: done!"); + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/entities/GitTree.java b/lgit/src/main/java/ru/ifmo/git/entities/GitTree.java new file mode 100644 index 0000000..11fd833 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/entities/GitTree.java @@ -0,0 +1,66 @@ +package ru.ifmo.git.entities; + +import java.io.IOException; + +import java.nio.file.Files; +import java.nio.file.Path; + +public class GitTree { + + private Path rootDir; + private Path metaDir; + private Path logDir; + private Path storageDir; + private Path indexDir; + private Path headFile; + + GitTree(Path repository) { + setRepository(repository); + } + + private void setRepository(Path repository) { + rootDir = repository; + metaDir = rootDir.resolve(".l_git"); + headFile = metaDir.resolve("HEAD"); + logDir = metaDir.resolve("log"); + storageDir = metaDir.resolve("storage"); + indexDir = metaDir.resolve("index"); + } + + void createGitTree() throws IOException { + metaDir = Files.createDirectory(rootDir.resolve(".l_git")); + headFile = Files.createFile(metaDir.resolve("HEAD")); + logDir = Files.createDirectory(metaDir.resolve("log")); + storageDir = Files.createDirectory(metaDir.resolve("storage")); + indexDir = Files.createDirectory(metaDir.resolve("index")); + } + + Path repo() { + return rootDir; + } + + Path index() { + return indexDir; + } + + Path log() { + return logDir; + } + + Path storage() { + return storageDir; + } + + Path head() { + return headFile; + } + + Path git() { + return metaDir; + } + + public boolean exists() { + return Files.exists(metaDir); + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/util/BlobType.java b/lgit/src/main/java/ru/ifmo/git/util/BlobType.java new file mode 100644 index 0000000..3b5f548 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/util/BlobType.java @@ -0,0 +1,29 @@ +package ru.ifmo.git.util; + +public enum BlobType { + + FILE, TREE, COMMIT; + + static public BlobType typeOf(String mark) { + return + mark.equals("cm\\") ? COMMIT : + mark.equals("tr\\") ? TREE : + FILE; + } + + public String asString() { + switch (this) { + case FILE: + return "fl\\"; + case TREE: + return "tr\\"; + case COMMIT: + return "cm\\"; + } + return ""; + } + + static public int size() { + return 3; + } +} diff --git a/lgit/src/main/java/ru/ifmo/git/util/CommandResult.java b/lgit/src/main/java/ru/ifmo/git/util/CommandResult.java new file mode 100644 index 0000000..20e7528 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/util/CommandResult.java @@ -0,0 +1,67 @@ +package ru.ifmo.git.util; + +public class CommandResult { + + private ExitStatus status; + private Message message; + private Throwable error; + + public CommandResult(ExitStatus status) { + this.status = status; + this.message = new Message(); + } + + public CommandResult(ExitStatus status, Message message) { + this.status = status; + this.message = message; + } + + public CommandResult(ExitStatus status, String message) { + this.status = status; + this.message = new Message(message); + } + + public CommandResult(ExitStatus status, String message, Throwable error) { + this.status = status; + this.message = new Message(message); + this.error = error; + } + + public ExitStatus getStatus() { + return status; + } + + public void setStatus(ExitStatus status) { + this.status = status; + } + + public Message getMessage() { + return message; + } + + public void setMessage(Message message) { + this.message = message; + } + + public void setMessage(String message) { + this.message = new Message(message); + } + + public void setError(Throwable error) { + this.error = error; + } + + public Throwable getError() { + return error; + } + + public void print() { + if (status != ExitStatus.SUCCESS) { + System.out.println("Exit with code " + status); + } + System.out.println(message.read()); + if(error != null) { + error.printStackTrace(); + } + } +} diff --git a/lgit/src/main/java/ru/ifmo/git/util/CommitInfo.java b/lgit/src/main/java/ru/ifmo/git/util/CommitInfo.java new file mode 100644 index 0000000..d3cb588 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/util/CommitInfo.java @@ -0,0 +1,59 @@ +package ru.ifmo.git.util; + +import java.nio.file.Path; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +public class CommitInfo { + public String author; + public String time; + public String hash; + public String message; + public String branch; + public String rootDirectory; + + public CommitInfo() { + } + + public void setAuthor(String author) { + this.author = author; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public void setMessage(String message) { + this.message = message; + } + + public void setBranch(String branch) { + this.branch = branch; + } + + public void setAuthor() { + this.author = System.getProperty("user.name"); + } + + public void setTime() { + DateFormat df = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy ZZ"); + this.time = df.format(Calendar.getInstance().getTime()); + } + + public void setTime(String time) { + this.time = time; + } + + public void setRootDirectory(Path rootPath) { + this.rootDirectory = rootPath.toAbsolutePath().toString(); + } + + public String toString() { + return "commit " + hash + "\n" + + "Author:\t" + author + "\n" + + "Date:\t" + time + "\n" + + "\t\t" + message + "\n\n"; + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/util/ExitStatus.java b/lgit/src/main/java/ru/ifmo/git/util/ExitStatus.java new file mode 100644 index 0000000..beb2f68 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/util/ExitStatus.java @@ -0,0 +1,5 @@ +package ru.ifmo.git.util; + +public enum ExitStatus { + SUCCESS, FAILURE, ERROR; +} diff --git a/lgit/src/main/java/ru/ifmo/git/util/FileReference.java b/lgit/src/main/java/ru/ifmo/git/util/FileReference.java new file mode 100644 index 0000000..b125cfa --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/util/FileReference.java @@ -0,0 +1,11 @@ +package ru.ifmo.git.util; + +import java.io.InputStream; + +public class FileReference { + + public BlobType type; + public String name; + public InputStream content; + +} diff --git a/lgit/src/main/java/ru/ifmo/git/util/GitException.java b/lgit/src/main/java/ru/ifmo/git/util/GitException.java new file mode 100644 index 0000000..10cfb2e --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/util/GitException.java @@ -0,0 +1,9 @@ +package ru.ifmo.git.util; + +public class GitException extends Exception { + + public GitException(String message) { + super(message); + } + +} diff --git a/lgit/src/main/java/ru/ifmo/git/util/HeadInfo.java b/lgit/src/main/java/ru/ifmo/git/util/HeadInfo.java new file mode 100644 index 0000000..2085e51 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/util/HeadInfo.java @@ -0,0 +1,25 @@ +package ru.ifmo.git.util; + +public class HeadInfo { + + public String branchName; + public String headHash; + public String currentHash; + + public HeadInfo() { + branchName = "master"; + } + + public void moveHead(String hash) { + headHash = hash; + } + + public void moveCurrent(String hash) { + currentHash = hash; + } + + public void moveBoth(String hash) { + moveHead(hash); + moveCurrent(hash); + } +} diff --git a/lgit/src/main/java/ru/ifmo/git/util/Message.java b/lgit/src/main/java/ru/ifmo/git/util/Message.java new file mode 100644 index 0000000..a83b093 --- /dev/null +++ b/lgit/src/main/java/ru/ifmo/git/util/Message.java @@ -0,0 +1,38 @@ +package ru.ifmo.git.util; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +public class Message { + + private ByteArrayOutputStream os; + + public Message() { + os = new ByteArrayOutputStream(); + } + + public Message(String text) { + os = new ByteArrayOutputStream(); + write(text); + } + + public void write(String text) { + try (Writer writer = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { + writer.write(text); + } catch (IOException ignored) { + } + } + + public String read() { + return os.toString(); + } + + public void clear() { + os.reset(); + } + + public void print() { + System.out.print(read()); + } + +}