From 5045bf9e0b84c8f8cf275045f9b1d2984904ae1c Mon Sep 17 00:00:00 2001 From: Ilya Date: Wed, 22 Mar 2017 03:29:41 +0300 Subject: [PATCH 01/22] Branch +/- done --- build.gradle | 14 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53556 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 +++++++++++++++++++++ gradlew.bat | 90 +++++++++++ settings.gradle | 3 + src/main/java/Blob.java | 8 + src/main/java/Commit.java | 8 + src/main/java/GitObject.java | 7 + src/main/java/GitTreeObject.java | 2 + src/main/java/Tree.java | 12 ++ src/main/java/VCS.java | 138 +++++++++++++++++ src/main/java/VCSException.java | 8 + src/main/java/VCSRefNotFoundException.java | 5 + 14 files changed, 465 insertions(+) create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/Blob.java create mode 100644 src/main/java/Commit.java create mode 100644 src/main/java/GitObject.java create mode 100644 src/main/java/GitTreeObject.java create mode 100644 src/main/java/Tree.java create mode 100644 src/main/java/VCS.java create mode 100644 src/main/java/VCSException.java create mode 100644 src/main/java/VCSRefNotFoundException.java diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..8c2ffda --- /dev/null +++ b/build.gradle @@ -0,0 +1,14 @@ +group 'ru.spbau.shevchenko' +version '1.0-SNAPSHOT' + +apply plugin: 'java' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.11' +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..ca78035ef0501d802d4fc55381ef2d5c3ce0ec6e GIT binary patch literal 53556 zcmafaW3XsJ(%7|a+qP}nwr$(CZQFj=wr$(@UA(+xH(#=wO)^z|&iv@9neOWDX^nz3 zFbEU?00abpJ7cBo`loO)|22l7HMDRNfRDr(;s(%6He@B!R zl#>(_RaT*s6?>AMo|2KKrCWfNrlp#lo@-WOSZ3Zod7P#lmzMGa(ZwA{NHx8{)|HLtOGBmL<{ePk& z|0}Aylc9rysnh?l#3IPVtoSeL%3mP<&r3w?-R*4b4NXWG>5Od*ot=GSWT6Hb5JLAX zShc9#=!2lw!t#FMI}pFJc zw6Uj8`Bst|cD2?nsG(d*ZG#%NF?Y80v0PGQSJPsUg@n3BQIkW_dR~d>N{{*bSH}Pd zIWdTJ#iH#>%S&)$tqoH6b*V7fLp<>(xL_ji`jq2`%oD)~iD7`@hsO@Vy3*qM{u`G^ zc0*TD{z`zuUlxn}e`r+pbapYdRdBNZ%Pbd5Q|G@k4^Kf?7YkE67fWM97kj6FFrif0 z)*eX^!4Hihd~D&c(x5hVbJa`bB+7ol01GlU5|UB2N>+y7))3gd&fUa5@v;6n+Lq-3 z{Jl7)Ss;}F5czIs_L}Eunuojl?dWXn4q(#5iYPV+5*ifPnsS@1F)kK`O<80078hB& z!Uu$#cM=e$$6FUI2Uys(|$Fxqmy zG@_F97OGMH;TUgxma36@BQi`!B{e(ZeayiDo z;os4R9{50YQVC-ThdC9S{Ee)4ikHa8|X*ach%>dfECip|EPi!8S zDh{J&bjYD?EYtrlYx3Xq_Uu~2x$3X9ZT$tJ|15Qq|5LU8AycBUzy2x~OxU04i>D z9w@yRqlcbqC}2T_XT5eNHYx5)7rtz8{DE*J?o>>OiS)0JC!ZaB0JL-Ob1w)8zanZ< zR(Xiz3$ioy*%XQmL-bJnNfvE$rI2P~LX90G#gt4nb9mku*6S{mqFw`_kt{LAkj!x21fSFo(-^4px?_hH9-@XW8zqNrs(RYSX5R zn7kQuX>YGYLyM(G>^wtn&><_Q!~W27r537fQwZIqYL965<@&T|=xUF6c$g=5 z9B|kBeu>}r8R@-o3b!=}4_HG6sot1tgjjbmglPS~q)5GX6CU&gxsD0v9llaw7Bh7W zG`o>aya0{@c}L+Gw`1PRqcl6e6}@o3Bcd#mP)9H<2a|Wi{ZWqCzX%93IfRpvQ5Gba z7lEPC4fM4WC?*W3IpV-cRPh5Sc}Q>vS@2qu<+V(nS%!Sm&*^W!gSj)# z5h9&o{KIKp2kov&g`CP%-CqAqA#o0Mw?;q#0Dk{<4VeG4n2LHB+qgPgx|xbu+L#I& z8=E>i%Np7lnw$R9>ZhtnJ0P3l{ISg3VawG!KBZ_pvN2DYtK&W!-f06 z`*U{p=QkVw&*us(0Q^xhL0e%n5Ms&j;)%FBf*#J>kq82xOVpI4<0WK)`n9DXCuv$A zfn4!kd?3Iqh$3+WD+l&4vj>}m@*Jom+}vj&2m=KQGoVRm7M2KY7**ns0|M5px)Deh zez6~hUk1`@NgO%XoGXd)&6$_Hs|(2|X^7HUDkEtbwHV#1wRTpbb)rHlLu^njhFg9S zx+)}U8(USDXm>S%pp;a_Y<5>3i_Hp_vWwtzt5uj8ewqTFEE)E15)Wjvv?x}}8HMiX z;^3-OH85AzcV_0O-Exhrj`RpUZ;j$qjmZ|L#+*_US5`JV%8wqakxhD&XCpyuWo{N- z+bNS}p+afKlpHI>3VBBeq|G8boGeUaC)(Ru3u`YLW30>~)5=GL=sUjLgu65%VcPGs}PA z2_OLv=2)9Xm11f*FTt*o*yc8FG>4G~q{mOUX#}$!=u>KSGyX(=*}&rI;2K(U?Koxp z7F-pc*}}pO@m;7sff=FGTE4TA9ZNTRx%XWeaa|lx9o$qjHByj0HxuO5TvpM}CwTW> z#R=1vZp)76kO?#z;(>6Mu&gCwrlvRCVG_g8sMl;^DrH)&-*)v5ZHl3IWWpPi!|ZNQ z4&vdL!lWNaYH)lo!KJkFQfoCqF_@w-in(c2pNkpCKo6my8_yVs_Uj=zGVLKUT#^z^ z-)|f>)fuk#(@A>3(o0VqQ1$4+z_E9HCQ7R^ z30tu-(OIxDiiOEkGpXw&zReM}VP+C}bFAvU5%L?0cQ@?`fBSwH7!4o)d`OImPc+X< zrwk1#`^<8L8#>HOQb0pxt)HxXg%o|3x3nsPjSioaPqZ^lnSNOaJHg}1zqdDur0PoP zRVh{xV61JsNFuq`Xd6MtK*HtXN?NH20{)o}s_-I*YU7#=qn8b)kV`MS%A%ewrx<5I zY9{WpWlK^G^SP=5nvS-WEy+2%2}G?;#q01CSQ@%UJgw>}sHVEQip4`tToFyKHmwTV z-vWa!(`#8lj^drh)TLYVZLU!F!ak3OPw(qUajt(mO&u~ANUN%r3KUzV%k%|1=7Iat z5Pt`rL>P6u2G|qX<$)j~A0r2ZdE%y2n!@s>8}^KzEQEj6Kc?A%>r0ye>xB@wj|1Ob47`2EH4(rA(O{ zU}u2kj}N3&2?^3EQ{aT{?2g=~RLM;{)T7k%gI$^7qr`&%?-K{7Z|xhUKgd+!`-Yie zuE4Z_s?8kT>|npn6{66?E4$Pc2K(`?YTz3q(aigbu-ShRhKK|(f0cCh1&Q1?!Rr=v&a!K}wA-|$Gr{J~k~ z7@gS_x|i#V?>C5h_S4>+&Y9UC;Z@h2@kZgiJ|M%c)C38h@es^Y`p#a9|M_8mi3pR( z6*QJ0&b&7q+!3NCbBMs(x}XlEUyQp~0K9id;Wx1KycVf%ae(I8KJgjc!$0vE-NSwS zEu2^31P|2W6P)+j90blNtRJ5=DmAN?R}TD4!&z=N=@IeHhDTl-!_-e0hc?;+-;cCJ zm~zCBdd&GjPVt9?QcvkJQtf#Mv5mGLq7;pHYUils+`Yo8=kJB06UOcuYC;cMU2)oG zMH>rDE_p-R8=u3n)w%~+lE$>My@gq^RU(c_#Yk|`!Sjm$ug=Rfte#lnU+3im?EmV# zsQ)8&61KN9vov>gGIX)DxBI8_l58uFEQm1nXX|V=m@g=xsEFu>FsERj84_NVQ56PN z!biByA&vMXZd;f2LD`as@gWp{0NymGSG%BQYnYw6nfWRI`$p&Ub8b!_;Pjp%TsmXI zfGrv)2Ikh0e{6<_{jJk;U`7Zl+LFg){?(TM{#uQ_K{wp6!O_Bx33d!Brgr9~942)4 zchrS8Old{AF_&$zBx^bCTQ74ka9H84%F{rOzJ`rkJjSB_^^pZqe9`VQ^HyUpX_!ZA z+f0In>sw`>{d(L>oA+{4&zo5_^6t%TX0Gj0^M@u0@~^-f=4Gt9HMY&X&b`K%xjauF z8_!X>V|CrL;+a6gp zKd)6{;@wH+A{&U6?dAu>etSxBD)@5z;S~6%oQqH(uVW(Ajr>Dy{pPKUlD+ zFbjJ6c69Zum)+VkzfW(gW7%C{gU6X+a{LH?s2^BS64n$B%cf()0AWRUIbQPhQ|q|& z55=zLH=!8-f5HKjA|4`9M&54<=^^w{`bc~@pMec>@~;_k-6-b93So0uesmwYOL zmrx9lp%heN8h0j@P=!rO5=@h9UIZ^85wMay-2UO?xo>XOHLK<6Q|uyT6%*f4V!dYTC-$swh8fk{pCMlf5hw+9jV|?GlEBEAx zj#np5nqD`peZ6m5`&-xKetv((^8@xo*!!N3lmt=YUou<_xyn#yJp3Y#wf`tEP?IB4 z>Mq>31$Blx^|cr*L09CYlW3$Ek;PY`k@ToRobo6~q}E71Oxr##L$~JJ9_?1@As_if z`YlL&yDtoy733P&wytI4>Gd;vxHw2O@+@KgbPa)>3z8mMkyAS%Fna#8Sg!uWhMEubF;n{i3Ae4j{$p>dYj-^9?1ysjK~i0Q(4XUQE? zq8WLEcE@FsQ%hrS`3O$YbyPGkF6o;%&dxfHG?_n@Z&K4vR@ieBC{}cst~pIc4R0u& zj`QUL>5UQF@PgvVoBbRAtoQ_wyeeA9wsSN9mXX-dN^aFG=EB_B_b{U`BenI&D=;Fj zT!n`sy{aPu9YibsEpvrQ^0t(q&Inj%Pca%Yu&!K1ORT4wD6j-dc+{?5(JAouXgIy8 z%-H6Fbhd6%S=KCeIm`}PC!@`F>UKx&(#(Exk?s77w@&*`_tZ&sgzQ!_QK=DBnare8 z;)ocuEeZw)R1@{BuzGzIj$Z6EqM#s17Zv{q88!cq88!bXFpB=ZG^k$1C)OSWOnz4h zh&DA{Lx8q4*47TCo_gzx?MlHD(Bx{$87ha%T$XB*_{8uv@LhK>VV`UY=tPjwOandObAG0 z65^99S$7U)%^i%0Rnv*|IFjxg{!=`YHMJK^XV#j)p>*^S8FcuGV-BAwAU)a(e+)Wj z<=0$&0zB{usg@89sQBDI-|(HM1iz{8?zwn?5-k8jfM6Uf#vp^D4ozQhw#0tB@N(_V z5G#8|@Ta&(7#{whu<-X6VG66*t5~?Wlg0j8JGkpMEo%Sg1fExMxWXFTg2;1a+bNC~ zMiFaxTcU3ZKjv)V5kM}`LLzVunn%c$N*BoJj-NZ6`Q{g=3;*E#!f_{#*C?+ad~5zZ z=keRIuK5M;04KWI+Ycv(7YzExxp+b(xFaY3Z^kf3mPKNCd{OQbO%F%7nd8P(nBNon z_?lN|<`FF*oN)KZYNm_512Er;<8GEqpFWsK<1M&j{|B zo5C*08{%HJJyGfROq44Q!PMdxq^&J+j?ahYI=`%GLh<*U*BGQ36lvssxuhS-weUq^_|F7sRH2KqhQ2}MFKYfgn|}o{=of1QHP+(v0l0HYK}G+OiNO_D__5DAvd@{ul69am-m8ERsfZLSCNp9cTU% zmH*GrZ`geV`DBTGGoW+_>cFiEGR0sT5#0!Gq3u)$0>Q+2gNXQYFn7##$e~T?O6@UKnaPmHYrr;IL66 zpHCH6FCU(hv{CKW&}j6$b_zL?RWjo+BMls3=9G<#5Tzqzb=To%u9RQYw&j~}FJ@T0 zwqYi7d0bfhOvCF+KQ?e8GFX^6Wr;#sLd>z=9rOo+Sn!Gx#S!8{JZOiICy=>JL!*Db z?0=i<6a%%-Qb$_VMK#jDzwycH@RdM&ODTf(BM+(VE<)*OfvATsOZ?;*Z|+KHl#LYV zwB(~69*ivMM^es;_qv2a`F=yr7hG(h9F_QsJdxq1W);`Gg)XvElwdAOhjO9z zZr>li{sH_~k(_n9ib4ek0I-7t03iF%BB@~LVj<}4Y-(%tUl(nv+J`Z=I^xgjDynBP zN0jq=Yp@Y{EX@X*q%wsh^8JcPZT)X5xy=r1Yhrts;iZ@>npp;KAbS=u^ z7C^t_c%Z%wUF|lirC0D?_B+enX?Etl?DjuDbKmTMIivlD98rUKIU`CqV0Ocly#&IF zVJ8$a8*L_yNF&jX!-@&G+9c#)>ZeLLirXnS+DtWKjc8+nJ|uDRlm6xpN-+4*hewV+ zK>0BT%8ou*`H3UuqFuNnXC^;BIAixsF!~XP(TYBlVf14Qq4mS}s)|2ZF#71(dk7cV zj6Tw*_G9cDz}0~ zXB=I`eTPx>~gi%8(4o7@g1GNnp$hJ_%Mg1`VLZDvLJeHGr+zT1&yk_ z)dbBKq?T{~APy~$Nlig_@z&C!xIWPDo3m~uxHe!qrNb26;xt|ht-7c7np#s+cje~J zZ~taj5)DfMbEaGGQw!+3dN0G2S=fRaa3rl z7Osx|l1jjjIOhCoaPxPQt1`ZxtLxIkA`VmUHN|vTlJRWNz<2C9m^>k4usuSUG})b%|D<wP^rU?JNVjdb*1yWsZBE8HZC}Q5va#I zsBwfZp;FX)RpB3EoWZyd4Bs{TNmbQ{0Kzz-0SgBPl2=f6IWi{9_QZu%rTT_|l31Q_ zycR4qyR5Il(L|CofDAL(ez5(KmRFo@U&>^{qK1eq^QMA`FZE_d6`2iXL�H$uJM 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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" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..832fdb6 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@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 +if "%@eval[2+2]" == "4" goto 4NT_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=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +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..c271805 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'VCS' +rootProject.name = 'VCS' + diff --git a/src/main/java/Blob.java b/src/main/java/Blob.java new file mode 100644 index 0000000..65670be --- /dev/null +++ b/src/main/java/Blob.java @@ -0,0 +1,8 @@ +import java.util.List; + +public class Blob extends GitTreeObject { + private List content; + Blob(){ + type = ObjectType.BLOB; + } +} diff --git a/src/main/java/Commit.java b/src/main/java/Commit.java new file mode 100644 index 0000000..db53845 --- /dev/null +++ b/src/main/java/Commit.java @@ -0,0 +1,8 @@ +public class Commit extends GitObject { + private String message; + private Tree tree; + private Commit prevCommit; + Commit(){ + type = ObjectType.COMMIT; + } +} diff --git a/src/main/java/GitObject.java b/src/main/java/GitObject.java new file mode 100644 index 0000000..5239d37 --- /dev/null +++ b/src/main/java/GitObject.java @@ -0,0 +1,7 @@ +import java.io.Serializable; + +public class GitObject implements Serializable{ + public enum ObjectType {TREE, BLOB, COMMIT} + public String name; + public ObjectType type; +} diff --git a/src/main/java/GitTreeObject.java b/src/main/java/GitTreeObject.java new file mode 100644 index 0000000..b998e6c --- /dev/null +++ b/src/main/java/GitTreeObject.java @@ -0,0 +1,2 @@ +public class GitTreeObject extends GitObject { +} diff --git a/src/main/java/Tree.java b/src/main/java/Tree.java new file mode 100644 index 0000000..23f0c74 --- /dev/null +++ b/src/main/java/Tree.java @@ -0,0 +1,12 @@ +import java.util.ArrayList; +import java.util.List; + +public class Tree extends GitTreeObject { + private List children; + Tree(){ + children = new ArrayList<>(); + type = ObjectType.TREE; + } + + +} diff --git a/src/main/java/VCS.java b/src/main/java/VCS.java new file mode 100644 index 0000000..596ce9a --- /dev/null +++ b/src/main/java/VCS.java @@ -0,0 +1,138 @@ +import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class VCS { + private static final Path GIT_DIRECTORY = Paths.get(".git/"); + private static final Path REFS_DIRECTORY = GIT_DIRECTORY.resolve("refs/"); + private static final Path HEAD_FILE = GIT_DIRECTORY.resolve("HEAD"); + private static final String REF_PREFIX = "ref: "; + private final Map objects = new HashMap<>(); + private final Map refs = new HashMap<>(); + private String head = null; + + VCS() throws IOException { + try { + if (Files.notExists(HEAD_FILE)) { + Files.createFile(HEAD_FILE); + Files.write(HEAD_FILE, "master".getBytes()); + head = "master"; + } + if (Files.notExists(GIT_DIRECTORY)) { + Files.createDirectory(GIT_DIRECTORY); + } + if (Files.notExists(REFS_DIRECTORY)) { + Files.createDirectory(REFS_DIRECTORY); + } + } + catch (IOException e) { + // TODO: throw custom exception? + throw new IOException("Cannot create VCS directories!"); + } + } + + public void add(Path filePath){ + throw new UnsupportedOperationException(); + } + public void commit(String commitMessage){ + throw new UnsupportedOperationException(); + } + + public void checkout(String branchName) throws IOException, VCSException { + if (getHead().equals(branchName)) { + return; + } + assertBranchExists(branchName); + setHead(branchName); + } + + private void assertBranchExists(String branchName) throws VCSException { + if (Files.notExists(REFS_DIRECTORY.resolve(branchName))) { + throw new VCSException("Branch with name " + branchName + " doesn't exist!"); + } + } + + public void createBranch(String branchName) throws VCSException, IOException { + Path branchPath = REFS_DIRECTORY.resolve(branchName); + if (Files.exists(branchPath)) { + throw new VCSException("A branch '" + branchName + "' already exists!"); + } + try { + Files.createFile(branchPath); + } catch (IOException e) { + throw new IOException("Cannot create ref for branch " + branchName); + } + // TODO: handle ref not found + Files.write(branchPath, getHeadCommit().getBytes()); + } + public void deleteBranch(String branchName) throws VCSException, IOException { + assertBranchExists(branchName); + if (branchName.equals(getHead())) { + throw new VCSException("Cannot delete branch '" + branchName + "' while you are currently on."); + } + Files.delete(REFS_DIRECTORY.resolve(branchName);); + } + public List log(){ + throw new UnsupportedOperationException(); + } + public void merge(String branchName){ + throw new UnsupportedOperationException(); + } + + private String getHead() throws IOException { + if (head == null) { + try { + head = readString(HEAD_FILE); + } catch (IOException e) { + // TODO: throw custom exception? + throw new IOException("Cannot read HEAD file!"); + } + } + return head; + } + + private String getHeadCommit() throws IOException, VCSRefNotFoundException { + String head = getHead(); + if (head.startsWith(REF_PREFIX)) + return getRefContent(head.substring(REF_PREFIX.length())); + else + return head; + } + + private String getRefContent(String ref) throws VCSRefNotFoundException, IOException { + if (!refs.containsKey(ref)) { + Path refPath = REFS_DIRECTORY.resolve(ref); + if (Files.notExists(refPath)) { + throw new VCSRefNotFoundException(); + } + try { + String refContent = readString(refPath); + refs.put(ref, refContent); + } catch (IOException e) { + throw new IOException("Cannot read ref file for "+ ref); + } + } + return refs.get(ref); + } + + private String readString(Path path) throws IOException { + return new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + } + + private void setHead(String head) throws IOException { + this.head = head; + try { + Files.write(HEAD_FILE, head.getBytes()); + } + catch (IOException e) { + throw new IOException("Cannot write to HEAD file!"); + } + } +} diff --git a/src/main/java/VCSException.java b/src/main/java/VCSException.java new file mode 100644 index 0000000..3e9821a --- /dev/null +++ b/src/main/java/VCSException.java @@ -0,0 +1,8 @@ +public class VCSException extends Exception { + public VCSException() { + } + + public VCSException(String s) { + super(s); + } +} diff --git a/src/main/java/VCSRefNotFoundException.java b/src/main/java/VCSRefNotFoundException.java new file mode 100644 index 0000000..f124c04 --- /dev/null +++ b/src/main/java/VCSRefNotFoundException.java @@ -0,0 +1,5 @@ +public class VCSRefNotFoundException extends VCSException { + public VCSRefNotFoundException() { + } + +} From f62eb4b8512914eab67a5f890b5804a0a5f3ec35 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 23 Mar 2017 05:32:21 +0300 Subject: [PATCH 02/22] All VCS methods added --- src/main/java/Main.java | 91 +++++ src/main/java/vcs/Blob.java | 59 +++ .../java/vcs/BranchNotFoundException.java | 7 + src/main/java/vcs/Commit.java | 59 +++ src/main/java/vcs/GitObject.java | 14 + src/main/java/vcs/LightBlob.java | 22 ++ .../java/vcs/NothingToCommitException.java | 7 + src/main/java/vcs/RefNotFoundException.java | 7 + src/main/java/vcs/VCS.java | 370 ++++++++++++++++++ src/main/java/vcs/VCSException.java | 10 + .../java/vcs/VCSFilesCorruptedException.java | 4 + src/main/java/vcs/VCSRef.java | 24 ++ 12 files changed, 674 insertions(+) create mode 100644 src/main/java/Main.java create mode 100644 src/main/java/vcs/Blob.java create mode 100644 src/main/java/vcs/BranchNotFoundException.java create mode 100644 src/main/java/vcs/Commit.java create mode 100644 src/main/java/vcs/GitObject.java create mode 100644 src/main/java/vcs/LightBlob.java create mode 100644 src/main/java/vcs/NothingToCommitException.java create mode 100644 src/main/java/vcs/RefNotFoundException.java create mode 100644 src/main/java/vcs/VCS.java create mode 100644 src/main/java/vcs/VCSException.java create mode 100644 src/main/java/vcs/VCSFilesCorruptedException.java create mode 100644 src/main/java/vcs/VCSRef.java diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 0000000..0723451 --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,91 @@ +import vcs.*; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.List; + +public class Main { + public static void main(String[] args) { + for (String arg : args) { + System.out.println(arg); + } + VCS vcs; + try { + vcs = new VCS(); + } catch (IOException | VCSException e) { + System.out.println(e.getMessage()); + return; + } + switch (args[0]) { + case "add": { + try { + vcs.add(Paths.get(args[1])); + } catch (IOException | VCSException e) { + System.out.println(e.getMessage()); + } + break; + } + case "commit": { + try { + vcs.commit(args[1]); + } catch (NothingToCommitException e) { + System.out.println("Nothing to commit."); + } catch (IOException | VCSException e) { + System.out.println(e.getMessage()); + } + break; + } + case "branch": { + if (args[1].equals("-d")) { + try { + vcs.deleteBranch(args[2]); + } catch (VCSException | IOException e) { + System.out.println(e.getMessage()); + } + } + else { + try { + vcs.createBranch(args[1]); + } catch (VCSException | IOException e) { + System.out.println(e.getMessage()); + } + } + break; + } + case "log": { + try { + List log = vcs.log(); + for (String record : log) { + System.out.print(record); + System.out.println("---------------------"); + } + } catch (IOException | VCSFilesCorruptedException | RefNotFoundException e) { + System.out.println(e.getMessage()); + } + break; + } + case "checkout": { + try { + vcs.checkout(args[1]); + } catch (IOException | BranchNotFoundException | VCSFilesCorruptedException | RefNotFoundException e) { + System.out.println(e.getMessage()); + } + break; + } + case "merge": { + try { + List conflicts = vcs.merge(args[1]); + if (conflicts.isEmpty()) { + System.out.println("Merged successfully"); + } + else { + System.out.println("Following files conflict:"); + conflicts.forEach(System.out::println); + } + } catch (VCSException | IOException e) { + System.out.println(e.getMessage()); + } + } + } + } +} diff --git a/src/main/java/vcs/Blob.java b/src/main/java/vcs/Blob.java new file mode 100644 index 0000000..0a81294 --- /dev/null +++ b/src/main/java/vcs/Blob.java @@ -0,0 +1,59 @@ +package vcs; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + + +public class Blob extends GitObject { + private String path; + private byte[] content; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Blob blob = (Blob) o; + + return path.equals(blob.path) && Arrays.equals(content, blob.content); + + } + + @Override + public int hashCode() { + return getSHA().hashCode(); + } + + @Override + public String getSHA(){ + try { + MessageDigest messageDigest = MessageDigest.getInstance(SHA1); + messageDigest.update(path.getBytes()); + messageDigest.update(content); + return byteArrayToHex(messageDigest.digest()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-1 hashing algorithm not implemented!"); + } + } + + public Blob(Path filePath) throws IOException { + path = filePath.toString(); + content = Files.readAllBytes(filePath); + } + + public String getPath() { + return path; + } + + public LightBlob getLightBlob() { + return new LightBlob(path, getSHA()); + } + + public byte[] getContent() { + return content; + } +} diff --git a/src/main/java/vcs/BranchNotFoundException.java b/src/main/java/vcs/BranchNotFoundException.java new file mode 100644 index 0000000..f49b76f --- /dev/null +++ b/src/main/java/vcs/BranchNotFoundException.java @@ -0,0 +1,7 @@ +package vcs; + +public class BranchNotFoundException extends VCSException { + public BranchNotFoundException(String branchName) { + super("Branch with name " + branchName + " doesn't exist!"); + } +} diff --git a/src/main/java/vcs/Commit.java b/src/main/java/vcs/Commit.java new file mode 100644 index 0000000..bb65d73 --- /dev/null +++ b/src/main/java/vcs/Commit.java @@ -0,0 +1,59 @@ +package vcs; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +public class Commit extends GitObject{ + private final long time; + private final String message; + private final List blobs; + private final String prevCommit; + + public Commit(String message, List blobs, String prevCommit) { + this.message = message; + this.blobs = blobs; + this.prevCommit = prevCommit; + this.time = System.currentTimeMillis(); + } + + @Override + public String getSHA() { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(time); + try { + MessageDigest messageDigest = MessageDigest.getInstance(SHA1); + messageDigest.update(buffer.array()); + messageDigest.update(message.getBytes()); + return byteArrayToHex(messageDigest.digest()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-1 hashing algorithm not implemented!"); + } + + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + result.append("Date: " + dateFormat.format(new Date(time)) + "\n"); + result.append("SHA: " + getSHA() + "\n"); + result.append("Message: " + message + "\n"); + + + return result.toString(); + } + + public String getPrevCommit() { + return prevCommit; + } + + public List getBlobs() { + return blobs; + } +} diff --git a/src/main/java/vcs/GitObject.java b/src/main/java/vcs/GitObject.java new file mode 100644 index 0000000..c36d9cf --- /dev/null +++ b/src/main/java/vcs/GitObject.java @@ -0,0 +1,14 @@ +package vcs; + +import java.io.Serializable; +import java.math.BigInteger; + +public abstract class GitObject implements Serializable{ + protected static final String SHA1 = "SHA-1"; + + public abstract String getSHA(); + + protected String byteArrayToHex(byte[] array) { + return (new BigInteger(1, array)).toString(16); + } +} diff --git a/src/main/java/vcs/LightBlob.java b/src/main/java/vcs/LightBlob.java new file mode 100644 index 0000000..882150a --- /dev/null +++ b/src/main/java/vcs/LightBlob.java @@ -0,0 +1,22 @@ +package vcs; + +import java.io.Serializable; + +public class LightBlob implements Serializable{ + private String path; + private String hash; + + public LightBlob(String path, String hash) { + this.path = path; + this.hash = hash; + } + + public String getHash() { + return hash; + } + + public String getPath() { + return path; + } + +} diff --git a/src/main/java/vcs/NothingToCommitException.java b/src/main/java/vcs/NothingToCommitException.java new file mode 100644 index 0000000..d995c9a --- /dev/null +++ b/src/main/java/vcs/NothingToCommitException.java @@ -0,0 +1,7 @@ +package vcs; + +public class NothingToCommitException extends Throwable { + public NothingToCommitException(String s) { + super(s); + } +} diff --git a/src/main/java/vcs/RefNotFoundException.java b/src/main/java/vcs/RefNotFoundException.java new file mode 100644 index 0000000..3598cc9 --- /dev/null +++ b/src/main/java/vcs/RefNotFoundException.java @@ -0,0 +1,7 @@ +package vcs; + +public class RefNotFoundException extends VCSException { + public RefNotFoundException() { + } + +} diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java new file mode 100644 index 0000000..ee785d5 --- /dev/null +++ b/src/main/java/vcs/VCS.java @@ -0,0 +1,370 @@ +package vcs; + +import java.io.*; +import java.nio.file.*; +import java.util.*; + +public class VCS { + private static final Path GIT_DIRECTORY = Paths.get(".git/"); + private static final Path REFS_DIRECTORY = GIT_DIRECTORY.resolve("refs/"); + private static final Path OBJS_DIRECTORY = GIT_DIRECTORY.resolve("objects/"); + private static final Path HEAD_FILE = GIT_DIRECTORY.resolve("HEAD"); + private static final Path INDEX_FILE = GIT_DIRECTORY.resolve("index"); + private static final VCSRef MASTER_BRANCH = new VCSRef("master"); + private static final String CONFLICT_SEPARATOR = "\n=======================\n"; + private final Map objects = new HashMap<>(); + private final Map refs = new HashMap<>(); + private String head = null; + private ArrayList index = null; + + /** + * + * @param branchName name of branch to be merge into current + * @return list of conflicting pathes + * @throws BranchNotFoundException + * @throws VCSFilesCorruptedException + * @throws RefNotFoundException + * @throws IOException + */ + public List merge(String branchName) throws VCSException, IOException { + if (!branchExists(branchName)) { + throw new BranchNotFoundException(branchName); + } + List currentBlobs = getBlobsRecursively(getHeadCommit()); + List newBlobs = getBlobsRecursively(getRefContent(new VCSRef(branchName))); + Map presentBlobs = new HashMap<>(); + currentBlobs.forEach(blob -> presentBlobs.put(blob.getPath(), blob)); + List conflicts = new ArrayList<>(); + for (LightBlob lightBlob : newBlobs) { + if (presentBlobs.containsKey(lightBlob.getPath()) && + !presentBlobs.get(lightBlob.getPath()).getHash().equals(lightBlob.getHash())) { + conflicts.add(lightBlob.getPath()); + Blob blob = (Blob) getObject(lightBlob.getHash()); + Files.write(Paths.get(lightBlob.getPath()), CONFLICT_SEPARATOR.getBytes(), StandardOpenOption.APPEND); + Files.write(Paths.get(lightBlob.getPath()), blob.getContent(), StandardOpenOption.APPEND); + } + else { + putBlobOnDisk(lightBlob); + addBlobToIndex((Blob) getObject(lightBlob.getHash())); + } + } + return conflicts; + } + + private List getBlobsRecursively(String commitHash) throws IOException, VCSFilesCorruptedException { + Map blobs = new HashMap<>(); + String currentCommitHash = commitHash; + while (currentCommitHash != null) { + Commit currentCommit = (Commit) getObject(currentCommitHash); + currentCommit.getBlobs().forEach(blob -> blobs.putIfAbsent(blob.getPath(), blob)); + currentCommitHash = currentCommit.getPrevCommit(); + } + List result = new ArrayList<>(); + blobs.forEach((key, blob) -> result.add(blob)); + return result; + } + + public void checkout(String revision) throws IOException, BranchNotFoundException, VCSFilesCorruptedException, RefNotFoundException { + if (branchExists(revision)) { + VCSRef branch = new VCSRef(revision); + if (getHead().equals(branch.toString())) { + return; + } + setHead(branch.toString()); + } + else { + if (getHead().equals(revision)) { + return; + } + setHead(revision); + } + + List blobs = getBlobsRecursively(getHeadCommit()); + for (LightBlob blob : blobs) { + putBlobOnDisk(blob); + } + } + + private void putBlobOnDisk(LightBlob lightBlob) throws IOException, VCSFilesCorruptedException { + Path blobPath = Paths.get(lightBlob.getPath()); + Path blobParent = blobPath.getParent(); + if (blobParent != null && Files.notExists(blobParent)) { + Files.createDirectories(blobParent); + } + Blob blob = (Blob) getObject(lightBlob.getHash()); + Files.write(blobPath, blob.getContent()); + } + + public VCS() throws IOException, VCSException { + try { + if (Files.notExists(GIT_DIRECTORY)) { + Files.createDirectory(GIT_DIRECTORY); + } + if (Files.notExists(REFS_DIRECTORY)) { + Files.createDirectory(REFS_DIRECTORY); + } + if (Files.notExists(OBJS_DIRECTORY)) { + Files.createDirectory(OBJS_DIRECTORY); + } + } + catch (IOException e) { + // TODO: throw custom exception? + throw new IOException("Cannot create VCS directories!"); + } + if (Files.notExists(INDEX_FILE)) { + index = new ArrayList<>(); + try { + writeObject(index, INDEX_FILE.toFile()); + } catch (IOException e) { + throw new IOException("Cannot create index file!"); + } + } + if (Files.notExists(HEAD_FILE)) { + try { + writeObject(MASTER_BRANCH.toString(), HEAD_FILE.toFile()); + } catch (IOException e) { + throw new IOException("Cannot create or write to HEAD file!"); + } + head = MASTER_BRANCH.toString(); + Commit initialCommit = new Commit("initial", new ArrayList<>(), null); + writeGitObject(initialCommit); + setRefContent(MASTER_BRANCH.getName(), initialCommit.getSHA()); + } + } + + public void createBranch(String branchName) throws VCSException, IOException { + Path branchPath = REFS_DIRECTORY.resolve(branchName); + if (Files.exists(branchPath)) { + throw new VCSException("A branch '" + branchName + "' already exists!"); + } + try { + Files.createFile(branchPath); + } catch (IOException e) { + throw new IOException("Cannot create ref for branch " + branchName); + } + // TODO: handle ref not found + writeObject(getHeadCommit(), branchPath.toFile()); + } + public void deleteBranch(String branchName) throws VCSException, IOException { + assertBranchExists(branchName); + if (branchName.equals(getHead())) { + throw new VCSException("Cannot delete branch '" + branchName + "' while you are currently on."); + } + Files.delete(REFS_DIRECTORY.resolve(branchName)); + } + + public void add(Path filePath) throws IOException, VCSException { + if (Files.isDirectory(filePath)) { + throw new UnsupportedOperationException(); + } + Blob newBlob = new Blob(filePath); + writeGitObject(newBlob); + addBlobToIndex(newBlob); + } + + private void addBlobToIndex(Blob blob) throws IOException, VCSException { + ArrayList index = getIndex(); + boolean found = false; + for (int i = 0; i < index.size(); i++) { + LightBlob currentBlob = index.get(i); + if (blob.getPath().equals(currentBlob.getPath())) { + if (!blob.getSHA().equals(currentBlob.getHash())) { + index.set(i, blob.getLightBlob()); + } + else { + return; + } + found = true; + break; + } + } + if (!found) { + index.add(blob.getLightBlob()); + } + setIndex(index); + } + + public List log() throws IOException, VCSFilesCorruptedException, RefNotFoundException { + List result = new ArrayList<>(); + String lastCommitHash = null; + if (VCSRef.isRef(getHead())) { + lastCommitHash = getRefContent(VCSRef.fromString(getHead())); + } + else { + lastCommitHash = getHead(); + } + while (lastCommitHash != null) { + Commit lastCommit = (Commit) getObject(lastCommitHash); + result.add(lastCommit.toString()); + lastCommitHash = lastCommit.getPrevCommit(); + } + return result; + } + + public void commit(String commitMessage) throws NothingToCommitException, IOException, VCSException { + List index = getIndex(); + if (index.size() == 0) { + throw new NothingToCommitException("Nothing to commit"); + } + String prevCommit = getHeadCommit(); + Commit commit = new Commit(commitMessage, index, prevCommit); + writeGitObject(commit); + if (VCSRef.isRef(getHead())) { + setRefContent(getHead(), commit.getSHA()); + } + else { + setHead(commit.getSHA()); + } + setIndex(new ArrayList<>()); + } + + private GitObject getObject(String hash) throws VCSFilesCorruptedException, IOException { + if (!objects.containsKey(hash)) { + try { + objects.put(hash, readGitObject(hash)); + } catch (ClassNotFoundException e) { + throw new VCSFilesCorruptedException(); + } catch (IOException e) { + throw new IOException("Cannot get object from '"+ hash +"' file"); + } + } + return objects.get(hash); + } + + + + private boolean branchExists(String branchName) { + return Files.exists(REFS_DIRECTORY.resolve(branchName)); + } + + private void assertBranchExists(String branchName) throws BranchNotFoundException { + if (!branchExists(branchName)) { + throw new BranchNotFoundException(branchName); + } + } + + private String getHead() throws IOException, VCSFilesCorruptedException { + if (head == null) { + try { + head = (String) readObject(HEAD_FILE.toFile()); + } catch (IOException e) { + // TODO: throw custom exception? + throw e; + //throw new IOException("Cannot read HEAD file!"); + } catch (ClassNotFoundException e) { + throw new VCSFilesCorruptedException(); + } + } + return head; + } + + private String getHeadCommit() throws IOException, RefNotFoundException, VCSFilesCorruptedException { + String head = getHead(); + if (VCSRef.isRef(head)) { + return getRefContent(VCSRef.fromString(head)); + } + else + return head; + } + + private String getRefContent(VCSRef ref) throws RefNotFoundException, IOException, VCSFilesCorruptedException { + if (!refs.containsKey(ref.getName())) { + Path refPath = REFS_DIRECTORY.resolve(ref.getName()); + if (Files.notExists(refPath)) { + throw new RefNotFoundException(); + } + try { + String refContent = (String) readObject(refPath.toFile()); + refs.put(ref.getName(), refContent); + } catch (IOException e) { + throw new IOException("Cannot read ref file for "+ ref); + } catch (ClassNotFoundException e) { + throw new VCSFilesCorruptedException(); + } + } + return refs.get(ref.getName()); + } + + private void setHead(String head) throws IOException { + this.head = head; + try { + writeObject(head, HEAD_FILE.toFile()); + } + catch (IOException e) { + throw new IOException("Cannot write to HEAD file!"); + } + } + + private ArrayList getIndex() throws VCSException, IOException { + if (index == null) { + try { + FileInputStream indexIn = new FileInputStream(INDEX_FILE.toFile()); + ObjectInputStream indexObjIn = new ObjectInputStream(indexIn); + //noinspection unchecked + index = (ArrayList) indexObjIn.readObject(); + } catch (FileNotFoundException e) { + // TODO: throw more specialized exception + throw new VCSException("Index file not found!"); + } catch (IOException e) { + // TODO: throw another exception? + throw new IOException("Unable to read from index file!"); + } catch (ClassNotFoundException e) { + throw new VCSFilesCorruptedException(); + } + } + return index; + } + + private void setIndex(ArrayList index) throws VCSException, IOException { + this.index = index; + try { + writeObject(index, INDEX_FILE.toFile()); + } + catch (FileNotFoundException e) { + // TODO: throw more specialized exception + throw new VCSException("Index file not found!"); + } catch (IOException e) { + // TODO: throw another exception? + throw new IOException("Unable to write to index file!"); +// throw new IOException("Unable to write to index file!"); + } + } + private void setRefContent(String ref, String hash) throws IOException { + if (VCSRef.isRef(ref)) { + ref = VCSRef.fromString(ref).getName(); + } + writeObject(hash, REFS_DIRECTORY.resolve(ref).toFile()); + } + + private void writeGitObject(GitObject gitObject) throws VCSException { + // Destination determines object content + try { + Path path = OBJS_DIRECTORY.resolve(gitObject.getSHA()); + if (Files.exists(path)) { + return; + } + writeObject(gitObject, path.toFile()); + } catch (IOException e) { + throw new VCSException("Can't write "+ gitObject.getSHA() +" object to file!"); + } + } + private GitObject readGitObject(String hash) throws IOException, ClassNotFoundException { + return (GitObject) readObject(OBJS_DIRECTORY.resolve(hash).toFile()); + } + private void writeObject(Object object, File destination) throws IOException { + FileOutputStream outStream = new FileOutputStream(destination); + ObjectOutputStream objOutStream = new ObjectOutputStream(outStream); + objOutStream.writeObject(object); + objOutStream.close(); + outStream.close(); + } + private Object readObject(File source) throws IOException, ClassNotFoundException { + FileInputStream inStream = new FileInputStream(source); + ObjectInputStream objInStream = new ObjectInputStream(inStream); + Object object = objInStream.readObject(); + objInStream.close(); + inStream.close(); + return object; + + } +} diff --git a/src/main/java/vcs/VCSException.java b/src/main/java/vcs/VCSException.java new file mode 100644 index 0000000..8155296 --- /dev/null +++ b/src/main/java/vcs/VCSException.java @@ -0,0 +1,10 @@ +package vcs; + +public class VCSException extends Exception { + public VCSException() { + } + + public VCSException(String s) { + super(s); + } +} diff --git a/src/main/java/vcs/VCSFilesCorruptedException.java b/src/main/java/vcs/VCSFilesCorruptedException.java new file mode 100644 index 0000000..f94dd20 --- /dev/null +++ b/src/main/java/vcs/VCSFilesCorruptedException.java @@ -0,0 +1,4 @@ +package vcs; + +public class VCSFilesCorruptedException extends VCSException { +} diff --git a/src/main/java/vcs/VCSRef.java b/src/main/java/vcs/VCSRef.java new file mode 100644 index 0000000..7bdebc4 --- /dev/null +++ b/src/main/java/vcs/VCSRef.java @@ -0,0 +1,24 @@ +package vcs; + +public class VCSRef { + private static final String REF_PREFIX = "ref: "; + private String name; + public static boolean isRef(String name) { + return name.startsWith(REF_PREFIX); + } + public VCSRef(String name) { + this.name = name; + } + static VCSRef fromString(String refString) { + return new VCSRef(refString.substring(REF_PREFIX.length())); + } + + @Override + public String toString() { + return REF_PREFIX + name; + } + + public String getName() { + return name; + } +} From 379c89b5941381ff5285396465d62000dce30ef6 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 23 Mar 2017 13:30:44 +0300 Subject: [PATCH 03/22] Tests added --- src/main/java/Main.java | 6 +- src/main/java/vcs/Blob.java | 4 + src/main/java/vcs/Commit.java | 4 + src/main/java/vcs/VCS.java | 10 +- src/test/java/vcs/VCSTest.java | 187 +++++++++++++++++++++++++++++++++ 5 files changed, 204 insertions(+), 7 deletions(-) create mode 100644 src/test/java/vcs/VCSTest.java diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 0723451..7361498 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -54,9 +54,9 @@ public static void main(String[] args) { } case "log": { try { - List log = vcs.log(); - for (String record : log) { - System.out.print(record); + List log = vcs.log(); + for (Commit record : log) { + System.out.print(record.toString()); System.out.println("---------------------"); } } catch (IOException | VCSFilesCorruptedException | RefNotFoundException e) { diff --git a/src/main/java/vcs/Blob.java b/src/main/java/vcs/Blob.java index 0a81294..5c105be 100644 --- a/src/main/java/vcs/Blob.java +++ b/src/main/java/vcs/Blob.java @@ -1,5 +1,6 @@ package vcs; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -41,6 +42,9 @@ public String getSHA(){ } public Blob(Path filePath) throws IOException { + if (Files.notExists(filePath)) { + throw new FileNotFoundException("File " + filePath.toString() + " not found!"); + } path = filePath.toString(); content = Files.readAllBytes(filePath); } diff --git a/src/main/java/vcs/Commit.java b/src/main/java/vcs/Commit.java index bb65d73..0c847e6 100644 --- a/src/main/java/vcs/Commit.java +++ b/src/main/java/vcs/Commit.java @@ -56,4 +56,8 @@ public String getPrevCommit() { public List getBlobs() { return blobs; } + + public String getMessage() { + return message; + } } diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index ee785d5..236d8dd 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -5,7 +5,7 @@ import java.util.*; public class VCS { - private static final Path GIT_DIRECTORY = Paths.get(".git/"); + private static final Path GIT_DIRECTORY = Paths.get(".vcs/"); private static final Path REFS_DIRECTORY = GIT_DIRECTORY.resolve("refs/"); private static final Path OBJS_DIRECTORY = GIT_DIRECTORY.resolve("objects/"); private static final Path HEAD_FILE = GIT_DIRECTORY.resolve("HEAD"); @@ -184,8 +184,8 @@ private void addBlobToIndex(Blob blob) throws IOException, VCSException { setIndex(index); } - public List log() throws IOException, VCSFilesCorruptedException, RefNotFoundException { - List result = new ArrayList<>(); + public List log() throws IOException, VCSFilesCorruptedException, RefNotFoundException { + List result = new ArrayList<>(); String lastCommitHash = null; if (VCSRef.isRef(getHead())) { lastCommitHash = getRefContent(VCSRef.fromString(getHead())); @@ -195,9 +195,10 @@ public List log() throws IOException, VCSFilesCorruptedException, RefNot } while (lastCommitHash != null) { Commit lastCommit = (Commit) getObject(lastCommitHash); - result.add(lastCommit.toString()); + result.add(lastCommit); lastCommitHash = lastCommit.getPrevCommit(); } + Collections.reverse(result); return result; } @@ -333,6 +334,7 @@ private void setRefContent(String ref, String hash) throws IOException { if (VCSRef.isRef(ref)) { ref = VCSRef.fromString(ref).getName(); } + refs.put(ref, hash); writeObject(hash, REFS_DIRECTORY.resolve(ref).toFile()); } diff --git a/src/test/java/vcs/VCSTest.java b/src/test/java/vcs/VCSTest.java new file mode 100644 index 0000000..996e6b3 --- /dev/null +++ b/src/test/java/vcs/VCSTest.java @@ -0,0 +1,187 @@ +package vcs; + +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +public class VCSTest { + private static final TestFile A = new TestFile("A.txt", "A text"); + private static final TestFile A1 = new TestFile("A.txt", "A1 text"); + private static final TestFile B = new TestFile("B.txt", "B text"); + private static final TestFile C = new TestFile("C.txt", "C text"); + private static final TestFile A_B = new TestFile("A/B.txt", "A/B.txt text"); + private static final TestFile A_B_C = new TestFile("A/B/C.txt", "A/B/C.txt text"); + + private static final String INITIAL_MSG = "initial"; + private static final String MASTER_BRANCH = "master"; + private static final Path VCS_PATH = Paths.get(".vcs/"); + + /* + + 2016 touch a + 2017 touch b + 2018 touch c + 2019 vcs add a + 2021 vcs commit add_A + 2027 vcs log + 2023 vcs add b + 2024 vcs commit add_B + 2027 vcs log + 2025 vcs add c + 2026 vcs commit add_C + 2027 vcs log + + + */ + + @Test + public void simpleLog() throws Exception, NothingToCommitException { + deleteRecursive(VCS_PATH); + deleteRecursive(TestFile.TEST_DIR); + VCS vcs = new VCS(); + List files = Arrays.asList(A, B, C); + List commitMessages = new ArrayList<>(); + commitMessages.add(INITIAL_MSG); + for (int i = 0; i < files.size(); i++) { + TestFile cur = files.get(i); + cur.create(); + vcs.add(cur.getPath()); + String commitMessage = "add_" + cur.getPath().toString(); + vcs.commit(commitMessage); + commitMessages.add(commitMessage); + + assertArrayEquals(commitMessages.toArray(), + vcs.log().stream().map(commit -> commit.getMessage()).collect(Collectors.toList()).toArray()); + } + } + + /* + 2031 vcs branch A + 2032 vcs branch B + 2032 vcs branch С + + 2033 touch A + 1111 echo "A1" > A + 2034 touch B + 1111 echo "B1" > B + 2035 touch C + 1111 echo "C1" > C + + 2039 vcs checkout A + 2036 vcs add A + 2037 vcs commit add_A + 2038 vcs log + + 2039 vcs checkout B + 2042 vcs add B + 2043 vcs commit add_B + 2052 vcs log + + 2058 vcs checkout C + 2062 vcs add C + 2063 vcs commit add_C + 2064 vcs log + + 2066 rm a b c + 2071 vcs checkout A + 1111 cat A + 2069 vcs checkout B + 1111 cat B + 2069 vcs checkout C + 1111 cat C + + */ + @Test + public void branchesCheckout() throws Exception, NothingToCommitException { + deleteRecursive(TestFile.TEST_DIR); + deleteRecursive(VCS_PATH); + VCS vcs = new VCS(); + List branches = Arrays.asList("A", "B", "C"); + List files = Arrays.asList(A, B, C); + for (String branch : branches) { + vcs.createBranch(branch); + } + for (int i = 0; i < branches.size(); i++) { + TestFile cur = files.get(i); + String branch = branches.get(i); + cur.create(); + vcs.checkout(branch); + vcs.add(cur.getPath()); + String commitMessage = "add_" + cur.getPath().toString(); + vcs.commit(commitMessage); + List log = vcs.log(); + assertEquals(2, log.size()); + assertEquals(INITIAL_MSG, log.get(0).getMessage()); + assertEquals(commitMessage, log.get(1).getMessage()); + } + vcs.checkout(MASTER_BRANCH); + for (TestFile file : files) { + file.remove(); + } + + for (int i = 0; i < branches.size(); i++) { + TestFile cur = files.get(i); + String branch = branches.get(i); + vcs.checkout(branch); + cur.check(); + } + } + + private void deleteRecursive(Path path) throws IOException { + if (Files.notExists(path)) { + return; + } + if (Files.isDirectory(path)) { + DirectoryStream directoryStream = Files.newDirectoryStream(path); + for (Path child : directoryStream) { + deleteRecursive(child); + } + } + Files.delete(path); + } + +} + +class TestFile { + public static final Path TEST_DIR = Paths.get("testXX/"); + + private String content; + private Path path; + + public TestFile(String path, String content){ + this.content = content; + this.path = TEST_DIR.resolve(path); + } + public void create() throws IOException { + Path parent = path.getParent(); + if (parent != null && Files.notExists(parent)) { + Files.createDirectories(parent); + } + if (Files.notExists(path)) { + Files.createFile(path); + } + Files.write(path, content.getBytes()); + } + public void remove() throws IOException { + Files.delete(path); + } + public boolean check() throws IOException { + return Files.exists(path) && + Arrays.toString(Files.readAllBytes(path)).equals(content); + } + + public Path getPath() { + return path; + } +} \ No newline at end of file From 17aa54a4ca401074e498d2fee3d127dee5b6e6cd Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 23 Mar 2017 13:31:45 +0300 Subject: [PATCH 04/22] Travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a1866fa --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: java + +jdk: +- oraclejdk8 \ No newline at end of file From 1611240a2a5160c9048eb189a898e8060e0a65a6 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 23 Mar 2017 13:47:37 +0300 Subject: [PATCH 05/22] Warnings fixed --- src/main/java/Main.java | 4 +- src/main/java/vcs/Blob.java | 4 ++ .../java/vcs/BranchNotFoundException.java | 1 + src/main/java/vcs/Commit.java | 5 ++ src/main/java/vcs/GitObject.java | 1 + src/main/java/vcs/LightBlob.java | 1 + .../java/vcs/NothingToCommitException.java | 1 + src/main/java/vcs/VCS.java | 53 +++++++++---------- src/main/java/vcs/VCSException.java | 2 + .../java/vcs/VCSFilesCorruptedException.java | 3 ++ src/main/java/vcs/VCSRef.java | 1 + 11 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 7361498..47c6acc 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -59,7 +59,7 @@ public static void main(String[] args) { System.out.print(record.toString()); System.out.println("---------------------"); } - } catch (IOException | VCSFilesCorruptedException | RefNotFoundException e) { + } catch (IOException | VCSFilesCorruptedException e) { System.out.println(e.getMessage()); } break; @@ -67,7 +67,7 @@ public static void main(String[] args) { case "checkout": { try { vcs.checkout(args[1]); - } catch (IOException | BranchNotFoundException | VCSFilesCorruptedException | RefNotFoundException e) { + } catch (IOException | BranchNotFoundException | VCSFilesCorruptedException e) { System.out.println(e.getMessage()); } break; diff --git a/src/main/java/vcs/Blob.java b/src/main/java/vcs/Blob.java index 5c105be..b4ed406 100644 --- a/src/main/java/vcs/Blob.java +++ b/src/main/java/vcs/Blob.java @@ -9,6 +9,7 @@ import java.util.Arrays; +@SuppressWarnings("WeakerAccess") public class Blob extends GitObject { private String path; private byte[] content; @@ -49,14 +50,17 @@ public Blob(Path filePath) throws IOException { content = Files.readAllBytes(filePath); } + @SuppressWarnings("WeakerAccess") public String getPath() { return path; } + @SuppressWarnings("WeakerAccess") public LightBlob getLightBlob() { return new LightBlob(path, getSHA()); } + @SuppressWarnings("WeakerAccess") public byte[] getContent() { return content; } diff --git a/src/main/java/vcs/BranchNotFoundException.java b/src/main/java/vcs/BranchNotFoundException.java index f49b76f..45b9df4 100644 --- a/src/main/java/vcs/BranchNotFoundException.java +++ b/src/main/java/vcs/BranchNotFoundException.java @@ -1,6 +1,7 @@ package vcs; public class BranchNotFoundException extends VCSException { + @SuppressWarnings("WeakerAccess") public BranchNotFoundException(String branchName) { super("Branch with name " + branchName + " doesn't exist!"); } diff --git a/src/main/java/vcs/Commit.java b/src/main/java/vcs/Commit.java index 0c847e6..1af35b0 100644 --- a/src/main/java/vcs/Commit.java +++ b/src/main/java/vcs/Commit.java @@ -13,6 +13,7 @@ public class Commit extends GitObject{ private final List blobs; private final String prevCommit; + @SuppressWarnings("WeakerAccess") public Commit(String message, List blobs, String prevCommit) { this.message = message; this.blobs = blobs; @@ -35,6 +36,7 @@ public String getSHA() { } + @SuppressWarnings({"StringBufferReplaceableByString", "StringConcatenationInsideStringBufferAppend"}) @Override public String toString() { StringBuilder result = new StringBuilder(); @@ -49,14 +51,17 @@ public String toString() { return result.toString(); } + @SuppressWarnings("WeakerAccess") public String getPrevCommit() { return prevCommit; } + @SuppressWarnings("WeakerAccess") public List getBlobs() { return blobs; } + @SuppressWarnings("WeakerAccess") public String getMessage() { return message; } diff --git a/src/main/java/vcs/GitObject.java b/src/main/java/vcs/GitObject.java index c36d9cf..cf0ad24 100644 --- a/src/main/java/vcs/GitObject.java +++ b/src/main/java/vcs/GitObject.java @@ -3,6 +3,7 @@ import java.io.Serializable; import java.math.BigInteger; +@SuppressWarnings("WeakerAccess") public abstract class GitObject implements Serializable{ protected static final String SHA1 = "SHA-1"; diff --git a/src/main/java/vcs/LightBlob.java b/src/main/java/vcs/LightBlob.java index 882150a..1d8ee72 100644 --- a/src/main/java/vcs/LightBlob.java +++ b/src/main/java/vcs/LightBlob.java @@ -2,6 +2,7 @@ import java.io.Serializable; +@SuppressWarnings("WeakerAccess") public class LightBlob implements Serializable{ private String path; private String hash; diff --git a/src/main/java/vcs/NothingToCommitException.java b/src/main/java/vcs/NothingToCommitException.java index d995c9a..e4f7566 100644 --- a/src/main/java/vcs/NothingToCommitException.java +++ b/src/main/java/vcs/NothingToCommitException.java @@ -1,6 +1,7 @@ package vcs; public class NothingToCommitException extends Throwable { + @SuppressWarnings("WeakerAccess") public NothingToCommitException(String s) { super(s); } diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index 236d8dd..7e91fc3 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -21,12 +21,11 @@ public class VCS { * * @param branchName name of branch to be merge into current * @return list of conflicting pathes - * @throws BranchNotFoundException - * @throws VCSFilesCorruptedException - * @throws RefNotFoundException - * @throws IOException + * @throws BranchNotFoundException if there's no branch with the specified name + * @throws VCSFilesCorruptedException if one of .vcs/ files is absent or contains corrupted data + * @throws IOException if for some reason VCS files cannot be read or written */ - public List merge(String branchName) throws VCSException, IOException { + public List merge(String branchName) throws IOException, BranchNotFoundException, VCSFilesCorruptedException { if (!branchExists(branchName)) { throw new BranchNotFoundException(branchName); } @@ -64,7 +63,7 @@ private List getBlobsRecursively(String commitHash) throws IOExceptio return result; } - public void checkout(String revision) throws IOException, BranchNotFoundException, VCSFilesCorruptedException, RefNotFoundException { + public void checkout(String revision) throws IOException, BranchNotFoundException, VCSFilesCorruptedException { if (branchExists(revision)) { VCSRef branch = new VCSRef(revision); if (getHead().equals(branch.toString())) { @@ -162,7 +161,7 @@ public void add(Path filePath) throws IOException, VCSException { addBlobToIndex(newBlob); } - private void addBlobToIndex(Blob blob) throws IOException, VCSException { + private void addBlobToIndex(Blob blob) throws IOException, VCSFilesCorruptedException { ArrayList index = getIndex(); boolean found = false; for (int i = 0; i < index.size(); i++) { @@ -184,9 +183,9 @@ private void addBlobToIndex(Blob blob) throws IOException, VCSException { setIndex(index); } - public List log() throws IOException, VCSFilesCorruptedException, RefNotFoundException { + public List log() throws IOException, VCSFilesCorruptedException { List result = new ArrayList<>(); - String lastCommitHash = null; + String lastCommitHash; if (VCSRef.isRef(getHead())) { lastCommitHash = getRefContent(VCSRef.fromString(getHead())); } @@ -223,8 +222,6 @@ private GitObject getObject(String hash) throws VCSFilesCorruptedException, IOEx if (!objects.containsKey(hash)) { try { objects.put(hash, readGitObject(hash)); - } catch (ClassNotFoundException e) { - throw new VCSFilesCorruptedException(); } catch (IOException e) { throw new IOException("Cannot get object from '"+ hash +"' file"); } @@ -250,16 +247,15 @@ private String getHead() throws IOException, VCSFilesCorruptedException { head = (String) readObject(HEAD_FILE.toFile()); } catch (IOException e) { // TODO: throw custom exception? - throw e; - //throw new IOException("Cannot read HEAD file!"); + throw new IOException("Cannot read HEAD file!"); } catch (ClassNotFoundException e) { - throw new VCSFilesCorruptedException(); + throw new VCSFilesCorruptedException(HEAD_FILE + " is corrupted!"); } } return head; } - private String getHeadCommit() throws IOException, RefNotFoundException, VCSFilesCorruptedException { + private String getHeadCommit() throws IOException, VCSFilesCorruptedException { String head = getHead(); if (VCSRef.isRef(head)) { return getRefContent(VCSRef.fromString(head)); @@ -268,11 +264,11 @@ private String getHeadCommit() throws IOException, RefNotFoundException, VCSFile return head; } - private String getRefContent(VCSRef ref) throws RefNotFoundException, IOException, VCSFilesCorruptedException { + private String getRefContent(VCSRef ref) throws IOException, VCSFilesCorruptedException { if (!refs.containsKey(ref.getName())) { Path refPath = REFS_DIRECTORY.resolve(ref.getName()); if (Files.notExists(refPath)) { - throw new RefNotFoundException(); + throw new VCSFilesCorruptedException(refPath + " is absent!"); } try { String refContent = (String) readObject(refPath.toFile()); @@ -280,7 +276,7 @@ private String getRefContent(VCSRef ref) throws RefNotFoundException, IOExceptio } catch (IOException e) { throw new IOException("Cannot read ref file for "+ ref); } catch (ClassNotFoundException e) { - throw new VCSFilesCorruptedException(); + throw new VCSFilesCorruptedException(refPath + " is corrupted!"); } } return refs.get(ref.getName()); @@ -296,7 +292,7 @@ private void setHead(String head) throws IOException { } } - private ArrayList getIndex() throws VCSException, IOException { + private ArrayList getIndex() throws IOException, VCSFilesCorruptedException { if (index == null) { try { FileInputStream indexIn = new FileInputStream(INDEX_FILE.toFile()); @@ -304,26 +300,24 @@ private ArrayList getIndex() throws VCSException, IOException { //noinspection unchecked index = (ArrayList) indexObjIn.readObject(); } catch (FileNotFoundException e) { - // TODO: throw more specialized exception - throw new VCSException("Index file not found!"); + throw new VCSFilesCorruptedException("Index file not found!"); } catch (IOException e) { // TODO: throw another exception? throw new IOException("Unable to read from index file!"); } catch (ClassNotFoundException e) { - throw new VCSFilesCorruptedException(); + throw new VCSFilesCorruptedException("Index file is corrupted!"); } } return index; } - private void setIndex(ArrayList index) throws VCSException, IOException { + private void setIndex(ArrayList index) throws IOException, VCSFilesCorruptedException { this.index = index; try { writeObject(index, INDEX_FILE.toFile()); } catch (FileNotFoundException e) { - // TODO: throw more specialized exception - throw new VCSException("Index file not found!"); + throw new VCSFilesCorruptedException("Index file not found!"); } catch (IOException e) { // TODO: throw another exception? throw new IOException("Unable to write to index file!"); @@ -350,8 +344,13 @@ private void writeGitObject(GitObject gitObject) throws VCSException { throw new VCSException("Can't write "+ gitObject.getSHA() +" object to file!"); } } - private GitObject readGitObject(String hash) throws IOException, ClassNotFoundException { - return (GitObject) readObject(OBJS_DIRECTORY.resolve(hash).toFile()); + private GitObject readGitObject(String hash) throws IOException, VCSFilesCorruptedException { + Path path = OBJS_DIRECTORY.resolve(hash); + try { + return (GitObject) readObject(path.toFile()); + } catch (ClassNotFoundException e) { + throw new VCSFilesCorruptedException(path.toString() + " is corrupted!"); + } } private void writeObject(Object object, File destination) throws IOException { FileOutputStream outStream = new FileOutputStream(destination); diff --git a/src/main/java/vcs/VCSException.java b/src/main/java/vcs/VCSException.java index 8155296..08b433b 100644 --- a/src/main/java/vcs/VCSException.java +++ b/src/main/java/vcs/VCSException.java @@ -1,9 +1,11 @@ package vcs; public class VCSException extends Exception { + @SuppressWarnings("WeakerAccess") public VCSException() { } + @SuppressWarnings("WeakerAccess") public VCSException(String s) { super(s); } diff --git a/src/main/java/vcs/VCSFilesCorruptedException.java b/src/main/java/vcs/VCSFilesCorruptedException.java index f94dd20..f6bc028 100644 --- a/src/main/java/vcs/VCSFilesCorruptedException.java +++ b/src/main/java/vcs/VCSFilesCorruptedException.java @@ -1,4 +1,7 @@ package vcs; public class VCSFilesCorruptedException extends VCSException { + public VCSFilesCorruptedException(String s) { + super(s); + } } diff --git a/src/main/java/vcs/VCSRef.java b/src/main/java/vcs/VCSRef.java index 7bdebc4..08a3e85 100644 --- a/src/main/java/vcs/VCSRef.java +++ b/src/main/java/vcs/VCSRef.java @@ -1,5 +1,6 @@ package vcs; +@SuppressWarnings("WeakerAccess") public class VCSRef { private static final String REF_PREFIX = "ref: "; private String name; From 261f287676fda2b04988a586797e0d88940ebf1c Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 23 Mar 2017 14:05:54 +0300 Subject: [PATCH 06/22] JavaDocs --- .../vcs/BranchAlreadyExistsException.java | 7 + .../java/vcs/DeleteActiveBranchException.java | 7 + src/main/java/vcs/RefNotFoundException.java | 7 - src/main/java/vcs/VCS.java | 254 ++++++++++-------- 4 files changed, 162 insertions(+), 113 deletions(-) create mode 100644 src/main/java/vcs/BranchAlreadyExistsException.java create mode 100644 src/main/java/vcs/DeleteActiveBranchException.java delete mode 100644 src/main/java/vcs/RefNotFoundException.java diff --git a/src/main/java/vcs/BranchAlreadyExistsException.java b/src/main/java/vcs/BranchAlreadyExistsException.java new file mode 100644 index 0000000..6740b0c --- /dev/null +++ b/src/main/java/vcs/BranchAlreadyExistsException.java @@ -0,0 +1,7 @@ +package vcs; + +public class BranchAlreadyExistsException extends Throwable { + public BranchAlreadyExistsException(String s) { + super(s); + } +} diff --git a/src/main/java/vcs/DeleteActiveBranchException.java b/src/main/java/vcs/DeleteActiveBranchException.java new file mode 100644 index 0000000..bbf1af3 --- /dev/null +++ b/src/main/java/vcs/DeleteActiveBranchException.java @@ -0,0 +1,7 @@ +package vcs; + +public class DeleteActiveBranchException extends Throwable { + public DeleteActiveBranchException(String s) { + super(s); + } +} diff --git a/src/main/java/vcs/RefNotFoundException.java b/src/main/java/vcs/RefNotFoundException.java deleted file mode 100644 index 3598cc9..0000000 --- a/src/main/java/vcs/RefNotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package vcs; - -public class RefNotFoundException extends VCSException { - public RefNotFoundException() { - } - -} diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index 7e91fc3..1fdebaf 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -17,82 +17,7 @@ public class VCS { private String head = null; private ArrayList index = null; - /** - * - * @param branchName name of branch to be merge into current - * @return list of conflicting pathes - * @throws BranchNotFoundException if there's no branch with the specified name - * @throws VCSFilesCorruptedException if one of .vcs/ files is absent or contains corrupted data - * @throws IOException if for some reason VCS files cannot be read or written - */ - public List merge(String branchName) throws IOException, BranchNotFoundException, VCSFilesCorruptedException { - if (!branchExists(branchName)) { - throw new BranchNotFoundException(branchName); - } - List currentBlobs = getBlobsRecursively(getHeadCommit()); - List newBlobs = getBlobsRecursively(getRefContent(new VCSRef(branchName))); - Map presentBlobs = new HashMap<>(); - currentBlobs.forEach(blob -> presentBlobs.put(blob.getPath(), blob)); - List conflicts = new ArrayList<>(); - for (LightBlob lightBlob : newBlobs) { - if (presentBlobs.containsKey(lightBlob.getPath()) && - !presentBlobs.get(lightBlob.getPath()).getHash().equals(lightBlob.getHash())) { - conflicts.add(lightBlob.getPath()); - Blob blob = (Blob) getObject(lightBlob.getHash()); - Files.write(Paths.get(lightBlob.getPath()), CONFLICT_SEPARATOR.getBytes(), StandardOpenOption.APPEND); - Files.write(Paths.get(lightBlob.getPath()), blob.getContent(), StandardOpenOption.APPEND); - } - else { - putBlobOnDisk(lightBlob); - addBlobToIndex((Blob) getObject(lightBlob.getHash())); - } - } - return conflicts; - } - private List getBlobsRecursively(String commitHash) throws IOException, VCSFilesCorruptedException { - Map blobs = new HashMap<>(); - String currentCommitHash = commitHash; - while (currentCommitHash != null) { - Commit currentCommit = (Commit) getObject(currentCommitHash); - currentCommit.getBlobs().forEach(blob -> blobs.putIfAbsent(blob.getPath(), blob)); - currentCommitHash = currentCommit.getPrevCommit(); - } - List result = new ArrayList<>(); - blobs.forEach((key, blob) -> result.add(blob)); - return result; - } - - public void checkout(String revision) throws IOException, BranchNotFoundException, VCSFilesCorruptedException { - if (branchExists(revision)) { - VCSRef branch = new VCSRef(revision); - if (getHead().equals(branch.toString())) { - return; - } - setHead(branch.toString()); - } - else { - if (getHead().equals(revision)) { - return; - } - setHead(revision); - } - - List blobs = getBlobsRecursively(getHeadCommit()); - for (LightBlob blob : blobs) { - putBlobOnDisk(blob); - } - } - - private void putBlobOnDisk(LightBlob lightBlob) throws IOException, VCSFilesCorruptedException { - Path blobPath = Paths.get(lightBlob.getPath()); - Path blobParent = blobPath.getParent(); - if (blobParent != null && Files.notExists(blobParent)) { - Files.createDirectories(blobParent); - } - Blob blob = (Blob) getObject(lightBlob.getHash()); - Files.write(blobPath, blob.getContent()); - } public VCS() throws IOException, VCSException { try { @@ -131,10 +56,79 @@ public VCS() throws IOException, VCSException { } } - public void createBranch(String branchName) throws VCSException, IOException { + /** + * Sets HEAD to revison and makes files consistent with revision state. + * @param revision name of a commit or a branch to checkout to + * @throws IOException if for some reason VCS files cannot be read or written + * @throws BranchNotFoundException if passed revision is branch and there's no such branch + * @throws VCSFilesCorruptedException if VCS files are absent or corrupted + */ + public void checkout(String revision) throws IOException, BranchNotFoundException, VCSFilesCorruptedException { + if (branchExists(revision)) { + VCSRef branch = new VCSRef(revision); + if (getHead().equals(branch.toString())) { + return; + } + setHead(branch.toString()); + } + else { + if (getHead().equals(revision)) { + return; + } + setHead(revision); + } + + List blobs = getBlobsRecursively(getHeadCommit()); + for (LightBlob blob : blobs) { + putBlobOnDisk(blob); + } + } + /** + * Merges branchName into current branch. + * + * Conflicting file contents are merged with separator. + * @param branchName name of branch to be merge into current + * @return list of conflicting paths + * @throws BranchNotFoundException if there's no branch with the specified name + * @throws VCSFilesCorruptedException if one of .vcs/ files is absent or contains corrupted data + * @throws IOException if for some reason VCS files cannot be read or written + */ + public List merge(String branchName) throws IOException, BranchNotFoundException, VCSFilesCorruptedException { + if (!branchExists(branchName)) { + throw new BranchNotFoundException(branchName); + } + List currentBlobs = getBlobsRecursively(getHeadCommit()); + List newBlobs = getBlobsRecursively(getRefContent(new VCSRef(branchName))); + Map presentBlobs = new HashMap<>(); + currentBlobs.forEach(blob -> presentBlobs.put(blob.getPath(), blob)); + List conflicts = new ArrayList<>(); + for (LightBlob lightBlob : newBlobs) { + if (presentBlobs.containsKey(lightBlob.getPath()) && + !presentBlobs.get(lightBlob.getPath()).getHash().equals(lightBlob.getHash())) { + conflicts.add(lightBlob.getPath()); + Blob blob = (Blob) getObject(lightBlob.getHash()); + Files.write(Paths.get(lightBlob.getPath()), CONFLICT_SEPARATOR.getBytes(), StandardOpenOption.APPEND); + Files.write(Paths.get(lightBlob.getPath()), blob.getContent(), StandardOpenOption.APPEND); + } + else { + putBlobOnDisk(lightBlob); + addBlobToIndex((Blob) getObject(lightBlob.getHash())); + } + } + return conflicts; + } + + /** + * Creates new branch pointing to same commit as HEAD. + * @param branchName name for a newly created branch + * @throws IOException if for some reason VCS files cannot be read or written + * @throws BranchAlreadyExistsException if branchName already exists + * @throws VCSFilesCorruptedException if VCS files are absent or corrupted + */ + public void createBranch(String branchName) throws IOException, BranchAlreadyExistsException, VCSFilesCorruptedException { Path branchPath = REFS_DIRECTORY.resolve(branchName); if (Files.exists(branchPath)) { - throw new VCSException("A branch '" + branchName + "' already exists!"); + throw new BranchAlreadyExistsException("A branch '" + branchName + "' already exists!"); } try { Files.createFile(branchPath); @@ -144,15 +138,31 @@ public void createBranch(String branchName) throws VCSException, IOException { // TODO: handle ref not found writeObject(getHeadCommit(), branchPath.toFile()); } - public void deleteBranch(String branchName) throws VCSException, IOException { + + /** + * Deletes specified branch. + * @param branchName branch to be deleted + * @throws IOException if for some reason VCS files cannot be read or written + * @throws DeleteActiveBranchException if branchName is active branch + * @throws VCSFilesCorruptedException if VCS files are absent or corrupted + * @throws BranchNotFoundException if branchName does not exist + */ + public void deleteBranch(String branchName) throws IOException, DeleteActiveBranchException, + VCSFilesCorruptedException, BranchNotFoundException { assertBranchExists(branchName); if (branchName.equals(getHead())) { - throw new VCSException("Cannot delete branch '" + branchName + "' while you are currently on."); + throw new DeleteActiveBranchException("Cannot delete branch '" + branchName + "' while you are currently on."); } Files.delete(REFS_DIRECTORY.resolve(branchName)); } - public void add(Path filePath) throws IOException, VCSException { + /** + * Adds file to stage for the next commit. + * @param filePath file to add + * @throws IOException if for some reason VCS files cannot be read or written + * @throws VCSFilesCorruptedException if VCS files are absent or corrupted + */ + public void add(Path filePath) throws IOException, VCSFilesCorruptedException { if (Files.isDirectory(filePath)) { throw new UnsupportedOperationException(); } @@ -161,28 +171,12 @@ public void add(Path filePath) throws IOException, VCSException { addBlobToIndex(newBlob); } - private void addBlobToIndex(Blob blob) throws IOException, VCSFilesCorruptedException { - ArrayList index = getIndex(); - boolean found = false; - for (int i = 0; i < index.size(); i++) { - LightBlob currentBlob = index.get(i); - if (blob.getPath().equals(currentBlob.getPath())) { - if (!blob.getSHA().equals(currentBlob.getHash())) { - index.set(i, blob.getLightBlob()); - } - else { - return; - } - found = true; - break; - } - } - if (!found) { - index.add(blob.getLightBlob()); - } - setIndex(index); - } - + /** + * Returns list of all commits in the current branch. + * @return list of all commits in the current branch + * @throws IOException if for some reason VCS files cannot be read or written + * @throws VCSFilesCorruptedException if VCS files are absent or corrupted + */ public List log() throws IOException, VCSFilesCorruptedException { List result = new ArrayList<>(); String lastCommitHash; @@ -201,7 +195,14 @@ public List log() throws IOException, VCSFilesCorruptedException { return result; } - public void commit(String commitMessage) throws NothingToCommitException, IOException, VCSException { + /** + * Creates new commit from all staged files. + * @param commitMessage a commit message + * @throws NothingToCommitException if there no files staged for commit + * @throws IOException if for some reason VCS files cannot be read or written + * @throws VCSFilesCorruptedException if VCS files are absent or corrupted + */ + public void commit(String commitMessage) throws NothingToCommitException, IOException, VCSFilesCorruptedException { List index = getIndex(); if (index.size() == 0) { throw new NothingToCommitException("Nothing to commit"); @@ -332,7 +333,7 @@ private void setRefContent(String ref, String hash) throws IOException { writeObject(hash, REFS_DIRECTORY.resolve(ref).toFile()); } - private void writeGitObject(GitObject gitObject) throws VCSException { + private void writeGitObject(GitObject gitObject) throws IOException { // Destination determines object content try { Path path = OBJS_DIRECTORY.resolve(gitObject.getSHA()); @@ -341,7 +342,7 @@ private void writeGitObject(GitObject gitObject) throws VCSException { } writeObject(gitObject, path.toFile()); } catch (IOException e) { - throw new VCSException("Can't write "+ gitObject.getSHA() +" object to file!"); + throw new IOException("Can't write "+ gitObject.getSHA() +" object to file!"); } } private GitObject readGitObject(String hash) throws IOException, VCSFilesCorruptedException { @@ -366,6 +367,47 @@ private Object readObject(File source) throws IOException, ClassNotFoundExceptio objInStream.close(); inStream.close(); return object; - + } + private List getBlobsRecursively(String commitHash) throws IOException, VCSFilesCorruptedException { + Map blobs = new HashMap<>(); + String currentCommitHash = commitHash; + while (currentCommitHash != null) { + Commit currentCommit = (Commit) getObject(currentCommitHash); + currentCommit.getBlobs().forEach(blob -> blobs.putIfAbsent(blob.getPath(), blob)); + currentCommitHash = currentCommit.getPrevCommit(); + } + List result = new ArrayList<>(); + blobs.forEach((key, blob) -> result.add(blob)); + return result; + } + private void putBlobOnDisk(LightBlob lightBlob) throws IOException, VCSFilesCorruptedException { + Path blobPath = Paths.get(lightBlob.getPath()); + Path blobParent = blobPath.getParent(); + if (blobParent != null && Files.notExists(blobParent)) { + Files.createDirectories(blobParent); + } + Blob blob = (Blob) getObject(lightBlob.getHash()); + Files.write(blobPath, blob.getContent()); + } + private void addBlobToIndex(Blob blob) throws IOException, VCSFilesCorruptedException { + ArrayList index = getIndex(); + boolean found = false; + for (int i = 0; i < index.size(); i++) { + LightBlob currentBlob = index.get(i); + if (blob.getPath().equals(currentBlob.getPath())) { + if (!blob.getSHA().equals(currentBlob.getHash())) { + index.set(i, blob.getLightBlob()); + } + else { + return; + } + found = true; + break; + } + } + if (!found) { + index.add(blob.getLightBlob()); + } + setIndex(index); } } From 04a6bdb072f10742ae32ad5f3cfbffb68c73e6c6 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 23 Mar 2017 14:15:16 +0300 Subject: [PATCH 07/22] crap deleted --- src/main/java/Blob.java | 8 -- src/main/java/Commit.java | 8 -- src/main/java/GitObject.java | 7 -- src/main/java/GitTreeObject.java | 2 - src/main/java/Tree.java | 12 -- src/main/java/VCS.java | 138 --------------------- src/main/java/VCSException.java | 8 -- src/main/java/VCSRefNotFoundException.java | 5 - 8 files changed, 188 deletions(-) delete mode 100644 src/main/java/Blob.java delete mode 100644 src/main/java/Commit.java delete mode 100644 src/main/java/GitObject.java delete mode 100644 src/main/java/GitTreeObject.java delete mode 100644 src/main/java/Tree.java delete mode 100644 src/main/java/VCS.java delete mode 100644 src/main/java/VCSException.java delete mode 100644 src/main/java/VCSRefNotFoundException.java diff --git a/src/main/java/Blob.java b/src/main/java/Blob.java deleted file mode 100644 index 65670be..0000000 --- a/src/main/java/Blob.java +++ /dev/null @@ -1,8 +0,0 @@ -import java.util.List; - -public class Blob extends GitTreeObject { - private List content; - Blob(){ - type = ObjectType.BLOB; - } -} diff --git a/src/main/java/Commit.java b/src/main/java/Commit.java deleted file mode 100644 index db53845..0000000 --- a/src/main/java/Commit.java +++ /dev/null @@ -1,8 +0,0 @@ -public class Commit extends GitObject { - private String message; - private Tree tree; - private Commit prevCommit; - Commit(){ - type = ObjectType.COMMIT; - } -} diff --git a/src/main/java/GitObject.java b/src/main/java/GitObject.java deleted file mode 100644 index 5239d37..0000000 --- a/src/main/java/GitObject.java +++ /dev/null @@ -1,7 +0,0 @@ -import java.io.Serializable; - -public class GitObject implements Serializable{ - public enum ObjectType {TREE, BLOB, COMMIT} - public String name; - public ObjectType type; -} diff --git a/src/main/java/GitTreeObject.java b/src/main/java/GitTreeObject.java deleted file mode 100644 index b998e6c..0000000 --- a/src/main/java/GitTreeObject.java +++ /dev/null @@ -1,2 +0,0 @@ -public class GitTreeObject extends GitObject { -} diff --git a/src/main/java/Tree.java b/src/main/java/Tree.java deleted file mode 100644 index 23f0c74..0000000 --- a/src/main/java/Tree.java +++ /dev/null @@ -1,12 +0,0 @@ -import java.util.ArrayList; -import java.util.List; - -public class Tree extends GitTreeObject { - private List children; - Tree(){ - children = new ArrayList<>(); - type = ObjectType.TREE; - } - - -} diff --git a/src/main/java/VCS.java b/src/main/java/VCS.java deleted file mode 100644 index 596ce9a..0000000 --- a/src/main/java/VCS.java +++ /dev/null @@ -1,138 +0,0 @@ -import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class VCS { - private static final Path GIT_DIRECTORY = Paths.get(".git/"); - private static final Path REFS_DIRECTORY = GIT_DIRECTORY.resolve("refs/"); - private static final Path HEAD_FILE = GIT_DIRECTORY.resolve("HEAD"); - private static final String REF_PREFIX = "ref: "; - private final Map objects = new HashMap<>(); - private final Map refs = new HashMap<>(); - private String head = null; - - VCS() throws IOException { - try { - if (Files.notExists(HEAD_FILE)) { - Files.createFile(HEAD_FILE); - Files.write(HEAD_FILE, "master".getBytes()); - head = "master"; - } - if (Files.notExists(GIT_DIRECTORY)) { - Files.createDirectory(GIT_DIRECTORY); - } - if (Files.notExists(REFS_DIRECTORY)) { - Files.createDirectory(REFS_DIRECTORY); - } - } - catch (IOException e) { - // TODO: throw custom exception? - throw new IOException("Cannot create VCS directories!"); - } - } - - public void add(Path filePath){ - throw new UnsupportedOperationException(); - } - public void commit(String commitMessage){ - throw new UnsupportedOperationException(); - } - - public void checkout(String branchName) throws IOException, VCSException { - if (getHead().equals(branchName)) { - return; - } - assertBranchExists(branchName); - setHead(branchName); - } - - private void assertBranchExists(String branchName) throws VCSException { - if (Files.notExists(REFS_DIRECTORY.resolve(branchName))) { - throw new VCSException("Branch with name " + branchName + " doesn't exist!"); - } - } - - public void createBranch(String branchName) throws VCSException, IOException { - Path branchPath = REFS_DIRECTORY.resolve(branchName); - if (Files.exists(branchPath)) { - throw new VCSException("A branch '" + branchName + "' already exists!"); - } - try { - Files.createFile(branchPath); - } catch (IOException e) { - throw new IOException("Cannot create ref for branch " + branchName); - } - // TODO: handle ref not found - Files.write(branchPath, getHeadCommit().getBytes()); - } - public void deleteBranch(String branchName) throws VCSException, IOException { - assertBranchExists(branchName); - if (branchName.equals(getHead())) { - throw new VCSException("Cannot delete branch '" + branchName + "' while you are currently on."); - } - Files.delete(REFS_DIRECTORY.resolve(branchName);); - } - public List log(){ - throw new UnsupportedOperationException(); - } - public void merge(String branchName){ - throw new UnsupportedOperationException(); - } - - private String getHead() throws IOException { - if (head == null) { - try { - head = readString(HEAD_FILE); - } catch (IOException e) { - // TODO: throw custom exception? - throw new IOException("Cannot read HEAD file!"); - } - } - return head; - } - - private String getHeadCommit() throws IOException, VCSRefNotFoundException { - String head = getHead(); - if (head.startsWith(REF_PREFIX)) - return getRefContent(head.substring(REF_PREFIX.length())); - else - return head; - } - - private String getRefContent(String ref) throws VCSRefNotFoundException, IOException { - if (!refs.containsKey(ref)) { - Path refPath = REFS_DIRECTORY.resolve(ref); - if (Files.notExists(refPath)) { - throw new VCSRefNotFoundException(); - } - try { - String refContent = readString(refPath); - refs.put(ref, refContent); - } catch (IOException e) { - throw new IOException("Cannot read ref file for "+ ref); - } - } - return refs.get(ref); - } - - private String readString(Path path) throws IOException { - return new String(Files.readAllBytes(path), StandardCharsets.UTF_8); - } - - private void setHead(String head) throws IOException { - this.head = head; - try { - Files.write(HEAD_FILE, head.getBytes()); - } - catch (IOException e) { - throw new IOException("Cannot write to HEAD file!"); - } - } -} diff --git a/src/main/java/VCSException.java b/src/main/java/VCSException.java deleted file mode 100644 index 3e9821a..0000000 --- a/src/main/java/VCSException.java +++ /dev/null @@ -1,8 +0,0 @@ -public class VCSException extends Exception { - public VCSException() { - } - - public VCSException(String s) { - super(s); - } -} diff --git a/src/main/java/VCSRefNotFoundException.java b/src/main/java/VCSRefNotFoundException.java deleted file mode 100644 index f124c04..0000000 --- a/src/main/java/VCSRefNotFoundException.java +++ /dev/null @@ -1,5 +0,0 @@ -public class VCSRefNotFoundException extends VCSException { - public VCSRefNotFoundException() { - } - -} From 6acf56cb56fd29d531741b467c8a090418f291a2 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 23 Mar 2017 14:37:26 +0300 Subject: [PATCH 08/22] Fixed --- .gitignore | 1 + src/main/java/Main.java | 4 ++-- src/test/java/vcs/VCSTest.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 47c6acc..dd06611 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -39,14 +39,14 @@ public static void main(String[] args) { if (args[1].equals("-d")) { try { vcs.deleteBranch(args[2]); - } catch (VCSException | IOException e) { + } catch (VCSException | IOException | DeleteActiveBranchException e) { System.out.println(e.getMessage()); } } else { try { vcs.createBranch(args[1]); - } catch (VCSException | IOException e) { + } catch (VCSException | IOException | BranchAlreadyExistsException e) { System.out.println(e.getMessage()); } } diff --git a/src/test/java/vcs/VCSTest.java b/src/test/java/vcs/VCSTest.java index 996e6b3..e17d8dc 100644 --- a/src/test/java/vcs/VCSTest.java +++ b/src/test/java/vcs/VCSTest.java @@ -103,7 +103,7 @@ public void simpleLog() throws Exception, NothingToCommitException { */ @Test - public void branchesCheckout() throws Exception, NothingToCommitException { + public void branchesCheckout() throws Exception, NothingToCommitException, BranchAlreadyExistsException { deleteRecursive(TestFile.TEST_DIR); deleteRecursive(VCS_PATH); VCS vcs = new VCS(); From ebfa82fda221e05f2ed65d5ab00e71edd7c3cc42 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 23 Mar 2017 15:08:56 +0300 Subject: [PATCH 09/22] More JavaDocs and reorganization --- src/main/java/{ => console}/Main.java | 2 ++ src/main/java/console/package-info.java | 4 ++++ src/main/java/vcs/Blob.java | 5 ++--- .../vcs/BranchAlreadyExistsException.java | 7 +++++-- .../java/vcs/BranchNotFoundException.java | 6 ++++-- src/main/java/vcs/Commit.java | 20 +++++++++++-------- .../java/vcs/DeleteActiveBranchException.java | 7 +++++-- src/main/java/vcs/GitObject.java | 10 +++++++--- src/main/java/vcs/LightBlob.java | 9 ++++----- .../java/vcs/NothingToCommitException.java | 8 +++++--- src/main/java/vcs/VCS.java | 4 ++++ src/main/java/vcs/VCSException.java | 10 ++++------ .../java/vcs/VCSFilesCorruptedException.java | 5 ++++- src/main/java/vcs/VCSRef.java | 9 ++++----- src/main/java/vcs/package-info.java | 4 ++++ 15 files changed, 70 insertions(+), 40 deletions(-) rename src/main/java/{ => console}/Main.java (99%) create mode 100644 src/main/java/console/package-info.java create mode 100644 src/main/java/vcs/package-info.java diff --git a/src/main/java/Main.java b/src/main/java/console/Main.java similarity index 99% rename from src/main/java/Main.java rename to src/main/java/console/Main.java index dd06611..32fc4e6 100644 --- a/src/main/java/Main.java +++ b/src/main/java/console/Main.java @@ -1,3 +1,5 @@ +package console; + import vcs.*; import java.io.IOException; diff --git a/src/main/java/console/package-info.java b/src/main/java/console/package-info.java new file mode 100644 index 0000000..b804d98 --- /dev/null +++ b/src/main/java/console/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides Main class for console interface. + */ +package console; \ No newline at end of file diff --git a/src/main/java/vcs/Blob.java b/src/main/java/vcs/Blob.java index b4ed406..24999d2 100644 --- a/src/main/java/vcs/Blob.java +++ b/src/main/java/vcs/Blob.java @@ -9,8 +9,7 @@ import java.util.Arrays; -@SuppressWarnings("WeakerAccess") -public class Blob extends GitObject { +class Blob extends GitObject { private String path; private byte[] content; @@ -42,7 +41,7 @@ public String getSHA(){ } } - public Blob(Path filePath) throws IOException { + Blob(Path filePath) throws IOException { if (Files.notExists(filePath)) { throw new FileNotFoundException("File " + filePath.toString() + " not found!"); } diff --git a/src/main/java/vcs/BranchAlreadyExistsException.java b/src/main/java/vcs/BranchAlreadyExistsException.java index 6740b0c..26f6a6d 100644 --- a/src/main/java/vcs/BranchAlreadyExistsException.java +++ b/src/main/java/vcs/BranchAlreadyExistsException.java @@ -1,7 +1,10 @@ package vcs; -public class BranchAlreadyExistsException extends Throwable { - public BranchAlreadyExistsException(String s) { +/** + * Signals an attempt to create branch with name that is already taken. + */ +public class BranchAlreadyExistsException extends VCSException { + BranchAlreadyExistsException(String s) { super(s); } } diff --git a/src/main/java/vcs/BranchNotFoundException.java b/src/main/java/vcs/BranchNotFoundException.java index 45b9df4..e099fde 100644 --- a/src/main/java/vcs/BranchNotFoundException.java +++ b/src/main/java/vcs/BranchNotFoundException.java @@ -1,8 +1,10 @@ package vcs; +/** + * Signals an attempt to reference branch that does not exist. + */ public class BranchNotFoundException extends VCSException { - @SuppressWarnings("WeakerAccess") - public BranchNotFoundException(String branchName) { + BranchNotFoundException(String branchName) { super("Branch with name " + branchName + " doesn't exist!"); } } diff --git a/src/main/java/vcs/Commit.java b/src/main/java/vcs/Commit.java index 1af35b0..7bd53d1 100644 --- a/src/main/java/vcs/Commit.java +++ b/src/main/java/vcs/Commit.java @@ -7,14 +7,17 @@ import java.util.Date; import java.util.List; +/** + * Class that encapsulates a single commit. + */ + public class Commit extends GitObject{ private final long time; private final String message; private final List blobs; private final String prevCommit; - @SuppressWarnings("WeakerAccess") - public Commit(String message, List blobs, String prevCommit) { + Commit(String message, List blobs, String prevCommit) { this.message = message; this.blobs = blobs; this.prevCommit = prevCommit; @@ -36,6 +39,10 @@ public String getSHA() { } + /** + * User-friendly string representation of commit. + * @return string representation of commit + */ @SuppressWarnings({"StringBufferReplaceableByString", "StringConcatenationInsideStringBufferAppend"}) @Override public String toString() { @@ -51,18 +58,15 @@ public String toString() { return result.toString(); } - @SuppressWarnings("WeakerAccess") - public String getPrevCommit() { + String getPrevCommit() { return prevCommit; } - @SuppressWarnings("WeakerAccess") - public List getBlobs() { + List getBlobs() { return blobs; } - @SuppressWarnings("WeakerAccess") - public String getMessage() { + String getMessage() { return message; } } diff --git a/src/main/java/vcs/DeleteActiveBranchException.java b/src/main/java/vcs/DeleteActiveBranchException.java index bbf1af3..466a5b0 100644 --- a/src/main/java/vcs/DeleteActiveBranchException.java +++ b/src/main/java/vcs/DeleteActiveBranchException.java @@ -1,7 +1,10 @@ package vcs; -public class DeleteActiveBranchException extends Throwable { - public DeleteActiveBranchException(String s) { +/** + * Signals an attempt to delete branch to which HEAD points. + */ +public class DeleteActiveBranchException extends VCSException { + DeleteActiveBranchException(String s) { super(s); } } diff --git a/src/main/java/vcs/GitObject.java b/src/main/java/vcs/GitObject.java index cf0ad24..8a59a22 100644 --- a/src/main/java/vcs/GitObject.java +++ b/src/main/java/vcs/GitObject.java @@ -3,13 +3,17 @@ import java.io.Serializable; import java.math.BigInteger; -@SuppressWarnings("WeakerAccess") -public abstract class GitObject implements Serializable{ +abstract class GitObject implements Serializable{ + @SuppressWarnings("WeakerAccess") protected static final String SHA1 = "SHA-1"; + /** + * Computes and returns SHA-1 hash of this object. + * @return hash String generated with SHA-1 algorithm + */ public abstract String getSHA(); - protected String byteArrayToHex(byte[] array) { + String byteArrayToHex(byte[] array) { return (new BigInteger(1, array)).toString(16); } } diff --git a/src/main/java/vcs/LightBlob.java b/src/main/java/vcs/LightBlob.java index 1d8ee72..027efa5 100644 --- a/src/main/java/vcs/LightBlob.java +++ b/src/main/java/vcs/LightBlob.java @@ -2,21 +2,20 @@ import java.io.Serializable; -@SuppressWarnings("WeakerAccess") -public class LightBlob implements Serializable{ +class LightBlob implements Serializable{ private String path; private String hash; - public LightBlob(String path, String hash) { + LightBlob(String path, String hash) { this.path = path; this.hash = hash; } - public String getHash() { + String getHash() { return hash; } - public String getPath() { + String getPath() { return path; } diff --git a/src/main/java/vcs/NothingToCommitException.java b/src/main/java/vcs/NothingToCommitException.java index e4f7566..9cae9b6 100644 --- a/src/main/java/vcs/NothingToCommitException.java +++ b/src/main/java/vcs/NothingToCommitException.java @@ -1,8 +1,10 @@ package vcs; -public class NothingToCommitException extends Throwable { - @SuppressWarnings("WeakerAccess") - public NothingToCommitException(String s) { +/** + * Signals an attempt to commit when no files were staged for commit. + */ +public class NothingToCommitException extends VCSException { + NothingToCommitException(String s) { super(s); } } diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index 1fdebaf..5dea39a 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -4,6 +4,10 @@ import java.nio.file.*; import java.util.*; +/** + * Class that contains all VCS interface methods. + */ + public class VCS { private static final Path GIT_DIRECTORY = Paths.get(".vcs/"); private static final Path REFS_DIRECTORY = GIT_DIRECTORY.resolve("refs/"); diff --git a/src/main/java/vcs/VCSException.java b/src/main/java/vcs/VCSException.java index 08b433b..0da5653 100644 --- a/src/main/java/vcs/VCSException.java +++ b/src/main/java/vcs/VCSException.java @@ -1,12 +1,10 @@ package vcs; +/** + * Signals that exception inside VCS of some sort occurred. + */ public class VCSException extends Exception { - @SuppressWarnings("WeakerAccess") - public VCSException() { - } - - @SuppressWarnings("WeakerAccess") - public VCSException(String s) { + VCSException(String s) { super(s); } } diff --git a/src/main/java/vcs/VCSFilesCorruptedException.java b/src/main/java/vcs/VCSFilesCorruptedException.java index f6bc028..15c4bfb 100644 --- a/src/main/java/vcs/VCSFilesCorruptedException.java +++ b/src/main/java/vcs/VCSFilesCorruptedException.java @@ -1,7 +1,10 @@ package vcs; +/** + * Signals that VCS files are absent when they should be present or their content is corrupted. + */ public class VCSFilesCorruptedException extends VCSException { - public VCSFilesCorruptedException(String s) { + VCSFilesCorruptedException(String s) { super(s); } } diff --git a/src/main/java/vcs/VCSRef.java b/src/main/java/vcs/VCSRef.java index 08a3e85..80c1c9b 100644 --- a/src/main/java/vcs/VCSRef.java +++ b/src/main/java/vcs/VCSRef.java @@ -1,13 +1,12 @@ package vcs; -@SuppressWarnings("WeakerAccess") -public class VCSRef { +class VCSRef { private static final String REF_PREFIX = "ref: "; private String name; - public static boolean isRef(String name) { + static boolean isRef(String name) { return name.startsWith(REF_PREFIX); } - public VCSRef(String name) { + VCSRef(String name) { this.name = name; } static VCSRef fromString(String refString) { @@ -19,7 +18,7 @@ public String toString() { return REF_PREFIX + name; } - public String getName() { + String getName() { return name; } } diff --git a/src/main/java/vcs/package-info.java b/src/main/java/vcs/package-info.java new file mode 100644 index 0000000..11105a7 --- /dev/null +++ b/src/main/java/vcs/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides VCS classes. + */ +package vcs; \ No newline at end of file From 66bf85e4faa63d3033ecd36d88193f79dd016496 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 23 Mar 2017 15:11:41 +0300 Subject: [PATCH 10/22] Bug fixes --- src/main/java/console/Main.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/console/Main.java b/src/main/java/console/Main.java index 32fc4e6..a69bfe8 100644 --- a/src/main/java/console/Main.java +++ b/src/main/java/console/Main.java @@ -41,14 +41,14 @@ public static void main(String[] args) { if (args[1].equals("-d")) { try { vcs.deleteBranch(args[2]); - } catch (VCSException | IOException | DeleteActiveBranchException e) { + } catch (VCSException | IOException e) { System.out.println(e.getMessage()); } } else { try { vcs.createBranch(args[1]); - } catch (VCSException | IOException | BranchAlreadyExistsException e) { + } catch (VCSException | IOException e) { System.out.println(e.getMessage()); } } From e138327c7bcd787de3fad8d7441a71c9a1a4dd64 Mon Sep 17 00:00:00 2001 From: Ilya Date: Sun, 26 Mar 2017 22:52:41 +0300 Subject: [PATCH 11/22] ignore updated --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c795b05..b7c1c7e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -build \ No newline at end of file +build +.idea \ No newline at end of file From cd83e6bc9689db8185a1f87e5a21568ec63b085b Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 30 Mar 2017 14:47:21 +0300 Subject: [PATCH 12/22] stubbed version --- src/main/java/console/Main.java | 8 + src/main/java/vcs/Blob.java | 68 +-- src/main/java/vcs/BlobSHARef.java | 26 + src/main/java/vcs/Branch.java | 28 ++ src/main/java/vcs/Branches.java | 17 + src/main/java/vcs/Commit.java | 66 +-- src/main/java/vcs/CommitRef.java | 7 + src/main/java/vcs/CommitSHARef.java | 20 + src/main/java/vcs/ContentfulBlob.java | 33 ++ src/main/java/vcs/ContentlessBlob.java | 45 ++ .../java/vcs/EmptyCommitMessageException.java | 4 + src/main/java/vcs/GitObject.java | 18 +- src/main/java/vcs/LightBlob.java | 22 - .../vcs/MergeWhenStagedNotEmptyException.java | 4 + .../java/vcs/NothingToCommitException.java | 3 - src/main/java/vcs/RepoState.java | 30 ++ src/main/java/vcs/SHARef.java | 5 + src/main/java/vcs/VCS.java | 450 +++--------------- src/main/java/vcs/VCSException.java | 1 + src/main/java/vcs/VCSRef.java | 24 - .../java/vcs/WrongArgumentsException.java | 7 + 21 files changed, 314 insertions(+), 572 deletions(-) create mode 100644 src/main/java/vcs/BlobSHARef.java create mode 100644 src/main/java/vcs/Branch.java create mode 100644 src/main/java/vcs/Branches.java create mode 100644 src/main/java/vcs/CommitRef.java create mode 100644 src/main/java/vcs/CommitSHARef.java create mode 100644 src/main/java/vcs/ContentfulBlob.java create mode 100644 src/main/java/vcs/ContentlessBlob.java create mode 100644 src/main/java/vcs/EmptyCommitMessageException.java delete mode 100644 src/main/java/vcs/LightBlob.java create mode 100644 src/main/java/vcs/MergeWhenStagedNotEmptyException.java create mode 100644 src/main/java/vcs/RepoState.java create mode 100644 src/main/java/vcs/SHARef.java delete mode 100644 src/main/java/vcs/VCSRef.java create mode 100644 src/main/java/vcs/WrongArgumentsException.java diff --git a/src/main/java/console/Main.java b/src/main/java/console/Main.java index a69bfe8..49ae7ff 100644 --- a/src/main/java/console/Main.java +++ b/src/main/java/console/Main.java @@ -7,7 +7,15 @@ import java.util.List; public class Main { + private static void jack(String kek){ + kek = "jack"; + } public static void main(String[] args) { + String kek = "kek"; + jack(kek); + System.out.print(kek); + } + public static void kek(String[] args) { for (String arg : args) { System.out.println(arg); } diff --git a/src/main/java/vcs/Blob.java b/src/main/java/vcs/Blob.java index 24999d2..879a508 100644 --- a/src/main/java/vcs/Blob.java +++ b/src/main/java/vcs/Blob.java @@ -1,66 +1,10 @@ package vcs; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; +import java.io.Serializable; - -class Blob extends GitObject { - private String path; - private byte[] content; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Blob blob = (Blob) o; - - return path.equals(blob.path) && Arrays.equals(content, blob.content); - - } - - @Override - public int hashCode() { - return getSHA().hashCode(); - } - - @Override - public String getSHA(){ - try { - MessageDigest messageDigest = MessageDigest.getInstance(SHA1); - messageDigest.update(path.getBytes()); - messageDigest.update(content); - return byteArrayToHex(messageDigest.digest()); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("SHA-1 hashing algorithm not implemented!"); - } - } - - Blob(Path filePath) throws IOException { - if (Files.notExists(filePath)) { - throw new FileNotFoundException("File " + filePath.toString() + " not found!"); - } - path = filePath.toString(); - content = Files.readAllBytes(filePath); - } - - @SuppressWarnings("WeakerAccess") - public String getPath() { - return path; - } - - @SuppressWarnings("WeakerAccess") - public LightBlob getLightBlob() { - return new LightBlob(path, getSHA()); - } - - @SuppressWarnings("WeakerAccess") - public byte[] getContent() { - return content; - } +public interface Blob extends Serializable{ + BlobSHARef getSHA(); + String getPath(); + ContentfulBlob getContentfulBlob(); + ContentlessBlob getContentlessBlob(); } diff --git a/src/main/java/vcs/BlobSHARef.java b/src/main/java/vcs/BlobSHARef.java new file mode 100644 index 0000000..31e375b --- /dev/null +++ b/src/main/java/vcs/BlobSHARef.java @@ -0,0 +1,26 @@ +package vcs; + +public class BlobSHARef implements SHARef { + private String blobSHA; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BlobSHARef that = (BlobSHARef) o; + + return blobSHA != null ? blobSHA.equals(that.blobSHA) : that.blobSHA == null; + + } + + @Override + public int hashCode() { + return blobSHA != null ? blobSHA.hashCode() : 0; + } + + @Override + public GitObject getObject() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/vcs/Branch.java b/src/main/java/vcs/Branch.java new file mode 100644 index 0000000..8973d35 --- /dev/null +++ b/src/main/java/vcs/Branch.java @@ -0,0 +1,28 @@ +package vcs; + +public class Branch implements CommitRef { + private CommitSHARef headCommit; + private String name; + + + public Branch(CommitSHARef headCommit) { + this.headCommit = headCommit; + } + + + @Override + public CommitSHARef getCommitSHA() { + throw new UnsupportedOperationException(); + } + + @Override + public Commit getCommit() { + throw new UnsupportedOperationException(); + } + + @Override + public CommitRef addCommitAfter(Commit commit) { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/vcs/Branches.java b/src/main/java/vcs/Branches.java new file mode 100644 index 0000000..93b368e --- /dev/null +++ b/src/main/java/vcs/Branches.java @@ -0,0 +1,17 @@ +package vcs; + +class Branches { + public static boolean exists(String branchName) { + throw new UnsupportedOperationException(); + } + static void create(String branchName) { + throw new UnsupportedOperationException(); + } + static void delete(String branchName) { + throw new UnsupportedOperationException(); + } + + static Branch get(String branchName) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/vcs/Commit.java b/src/main/java/vcs/Commit.java index 7bd53d1..7b0b507 100644 --- a/src/main/java/vcs/Commit.java +++ b/src/main/java/vcs/Commit.java @@ -1,72 +1,26 @@ package vcs; -import java.nio.ByteBuffer; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.text.SimpleDateFormat; -import java.util.Date; import java.util.List; -/** - * Class that encapsulates a single commit. - */ +public class Commit { + private List files; + private CommitSHARef prevCommit; + private long time; + private String message; -public class Commit extends GitObject{ - private final long time; - private final String message; - private final List blobs; - private final String prevCommit; - - Commit(String message, List blobs, String prevCommit) { + public Commit(String message, List files, CommitSHARef prevCommit) { this.message = message; - this.blobs = blobs; - this.prevCommit = prevCommit; + this.files = files; this.time = System.currentTimeMillis(); + this.prevCommit = prevCommit; } - @Override - public String getSHA() { - ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); - buffer.putLong(time); - try { - MessageDigest messageDigest = MessageDigest.getInstance(SHA1); - messageDigest.update(buffer.array()); - messageDigest.update(message.getBytes()); - return byteArrayToHex(messageDigest.digest()); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("SHA-1 hashing algorithm not implemented!"); - } - - } - - /** - * User-friendly string representation of commit. - * @return string representation of commit - */ - @SuppressWarnings({"StringBufferReplaceableByString", "StringConcatenationInsideStringBufferAppend"}) @Override public String toString() { - StringBuilder result = new StringBuilder(); - - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - - result.append("Date: " + dateFormat.format(new Date(time)) + "\n"); - result.append("SHA: " + getSHA() + "\n"); - result.append("Message: " + message + "\n"); - - - return result.toString(); + throw new UnsupportedOperationException(); } - String getPrevCommit() { + public CommitSHARef getPrevCommit() { return prevCommit; } - - List getBlobs() { - return blobs; - } - - String getMessage() { - return message; - } } diff --git a/src/main/java/vcs/CommitRef.java b/src/main/java/vcs/CommitRef.java new file mode 100644 index 0000000..0df3de6 --- /dev/null +++ b/src/main/java/vcs/CommitRef.java @@ -0,0 +1,7 @@ +package vcs; + +public interface CommitRef { + CommitSHARef getCommitSHA(); + Commit getCommit(); + CommitRef addCommitAfter(Commit commit); +} diff --git a/src/main/java/vcs/CommitSHARef.java b/src/main/java/vcs/CommitSHARef.java new file mode 100644 index 0000000..e36f206 --- /dev/null +++ b/src/main/java/vcs/CommitSHARef.java @@ -0,0 +1,20 @@ +package vcs; + +public class CommitSHARef implements CommitRef{ + private String commitSHA; + + @Override + public CommitSHARef getCommitSHA() { + throw new UnsupportedOperationException(); + } + + @Override + public Commit getCommit() { + throw new UnsupportedOperationException(); + } + + @Override + public CommitRef addCommitAfter(Commit commit) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/vcs/ContentfulBlob.java b/src/main/java/vcs/ContentfulBlob.java new file mode 100644 index 0000000..9960462 --- /dev/null +++ b/src/main/java/vcs/ContentfulBlob.java @@ -0,0 +1,33 @@ +package vcs; + +import java.nio.file.Path; + +public class ContentfulBlob implements Blob{ + private String path; + private byte[] content; + + public ContentfulBlob(Path filePath) { + throw new UnsupportedOperationException(); + } + + + @Override + public BlobSHARef getSHA() { + throw new UnsupportedOperationException(); + } + + @Override + public String getPath() { + throw new UnsupportedOperationException(); + } + + @Override + public ContentfulBlob getContentfulBlob() { + return this; + } + + @Override + public ContentlessBlob getContentlessBlob() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/vcs/ContentlessBlob.java b/src/main/java/vcs/ContentlessBlob.java new file mode 100644 index 0000000..1a6cb3e --- /dev/null +++ b/src/main/java/vcs/ContentlessBlob.java @@ -0,0 +1,45 @@ +package vcs; + +public class ContentlessBlob implements Blob{ + private String path; + private BlobSHARef blobRef; + + @Override + public BlobSHARef getSHA() { + throw new UnsupportedOperationException(); + } + + @Override + public String getPath() { + throw new UnsupportedOperationException(); + } + + @Override + public ContentfulBlob getContentfulBlob() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ContentlessBlob that = (ContentlessBlob) o; + + if (path != null ? !path.equals(that.path) : that.path != null) return false; + return blobRef != null ? blobRef.equals(that.blobRef) : that.blobRef == null; + + } + + @Override + public int hashCode() { + int result = path != null ? path.hashCode() : 0; + result = 31 * result + (blobRef != null ? blobRef.hashCode() : 0); + return result; + } + + @Override + public ContentlessBlob getContentlessBlob() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/vcs/EmptyCommitMessageException.java b/src/main/java/vcs/EmptyCommitMessageException.java new file mode 100644 index 0000000..83561d1 --- /dev/null +++ b/src/main/java/vcs/EmptyCommitMessageException.java @@ -0,0 +1,4 @@ +package vcs; + +public class EmptyCommitMessageException extends VCSException { +} diff --git a/src/main/java/vcs/GitObject.java b/src/main/java/vcs/GitObject.java index 8a59a22..f207efc 100644 --- a/src/main/java/vcs/GitObject.java +++ b/src/main/java/vcs/GitObject.java @@ -1,19 +1,5 @@ package vcs; -import java.io.Serializable; -import java.math.BigInteger; - -abstract class GitObject implements Serializable{ - @SuppressWarnings("WeakerAccess") - protected static final String SHA1 = "SHA-1"; - - /** - * Computes and returns SHA-1 hash of this object. - * @return hash String generated with SHA-1 algorithm - */ - public abstract String getSHA(); - - String byteArrayToHex(byte[] array) { - return (new BigInteger(1, array)).toString(16); - } +public interface GitObject { + SHARef getSHARef(); } diff --git a/src/main/java/vcs/LightBlob.java b/src/main/java/vcs/LightBlob.java deleted file mode 100644 index 027efa5..0000000 --- a/src/main/java/vcs/LightBlob.java +++ /dev/null @@ -1,22 +0,0 @@ -package vcs; - -import java.io.Serializable; - -class LightBlob implements Serializable{ - private String path; - private String hash; - - LightBlob(String path, String hash) { - this.path = path; - this.hash = hash; - } - - String getHash() { - return hash; - } - - String getPath() { - return path; - } - -} diff --git a/src/main/java/vcs/MergeWhenStagedNotEmptyException.java b/src/main/java/vcs/MergeWhenStagedNotEmptyException.java new file mode 100644 index 0000000..71ebd61 --- /dev/null +++ b/src/main/java/vcs/MergeWhenStagedNotEmptyException.java @@ -0,0 +1,4 @@ +package vcs; + +public class MergeWhenStagedNotEmptyException extends VCSException { +} diff --git a/src/main/java/vcs/NothingToCommitException.java b/src/main/java/vcs/NothingToCommitException.java index 9cae9b6..d778ad5 100644 --- a/src/main/java/vcs/NothingToCommitException.java +++ b/src/main/java/vcs/NothingToCommitException.java @@ -4,7 +4,4 @@ * Signals an attempt to commit when no files were staged for commit. */ public class NothingToCommitException extends VCSException { - NothingToCommitException(String s) { - super(s); - } } diff --git a/src/main/java/vcs/RepoState.java b/src/main/java/vcs/RepoState.java new file mode 100644 index 0000000..1045512 --- /dev/null +++ b/src/main/java/vcs/RepoState.java @@ -0,0 +1,30 @@ +package vcs; + +import java.util.List; + +public class RepoState { + private List files; + + public void add(ContentlessBlob blob) { + throw new UnsupportedOperationException(); + } + public void remove(ContentlessBlob blob) { + throw new UnsupportedOperationException(); + } + + public boolean empty() { + return files.isEmpty(); + } + + public void updateWith(RepoState delta) { + throw new UnsupportedOperationException(); + } + + public List getFiles() { + return files; + } + + public static RepoState getFromCommit(Commit commit) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/vcs/SHARef.java b/src/main/java/vcs/SHARef.java new file mode 100644 index 0000000..b120d16 --- /dev/null +++ b/src/main/java/vcs/SHARef.java @@ -0,0 +1,5 @@ +package vcs; + +public interface SHARef { + GitObject getObject(); +} diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index 5dea39a..cbc49c2 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -1,417 +1,89 @@ package vcs; -import java.io.*; -import java.nio.file.*; -import java.util.*; - -/** - * Class that contains all VCS interface methods. - */ +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; public class VCS { - private static final Path GIT_DIRECTORY = Paths.get(".vcs/"); - private static final Path REFS_DIRECTORY = GIT_DIRECTORY.resolve("refs/"); - private static final Path OBJS_DIRECTORY = GIT_DIRECTORY.resolve("objects/"); - private static final Path HEAD_FILE = GIT_DIRECTORY.resolve("HEAD"); - private static final Path INDEX_FILE = GIT_DIRECTORY.resolve("index"); - private static final VCSRef MASTER_BRANCH = new VCSRef("master"); - private static final String CONFLICT_SEPARATOR = "\n=======================\n"; - private final Map objects = new HashMap<>(); - private final Map refs = new HashMap<>(); - private String head = null; - private ArrayList index = null; - + private CommitRef head; + private RepoState index; + private RepoState staged; + // TODO: take String[] as args and check them - public VCS() throws IOException, VCSException { - try { - if (Files.notExists(GIT_DIRECTORY)) { - Files.createDirectory(GIT_DIRECTORY); - } - if (Files.notExists(REFS_DIRECTORY)) { - Files.createDirectory(REFS_DIRECTORY); - } - if (Files.notExists(OBJS_DIRECTORY)) { - Files.createDirectory(OBJS_DIRECTORY); - } - } - catch (IOException e) { - // TODO: throw custom exception? - throw new IOException("Cannot create VCS directories!"); - } - if (Files.notExists(INDEX_FILE)) { - index = new ArrayList<>(); - try { - writeObject(index, INDEX_FILE.toFile()); - } catch (IOException e) { - throw new IOException("Cannot create index file!"); - } - } - if (Files.notExists(HEAD_FILE)) { - try { - writeObject(MASTER_BRANCH.toString(), HEAD_FILE.toFile()); - } catch (IOException e) { - throw new IOException("Cannot create or write to HEAD file!"); - } - head = MASTER_BRANCH.toString(); - Commit initialCommit = new Commit("initial", new ArrayList<>(), null); - writeGitObject(initialCommit); - setRefContent(MASTER_BRANCH.getName(), initialCommit.getSHA()); - } + public VCS(){ + throw new UnsupportedOperationException(); } - /** - * Sets HEAD to revison and makes files consistent with revision state. - * @param revision name of a commit or a branch to checkout to - * @throws IOException if for some reason VCS files cannot be read or written - * @throws BranchNotFoundException if passed revision is branch and there's no such branch - * @throws VCSFilesCorruptedException if VCS files are absent or corrupted - */ - public void checkout(String revision) throws IOException, BranchNotFoundException, VCSFilesCorruptedException { - if (branchExists(revision)) { - VCSRef branch = new VCSRef(revision); - if (getHead().equals(branch.toString())) { - return; - } - setHead(branch.toString()); - } - else { - if (getHead().equals(revision)) { - return; - } - setHead(revision); - } - - List blobs = getBlobsRecursively(getHeadCommit()); - for (LightBlob blob : blobs) { - putBlobOnDisk(blob); - } - } - /** - * Merges branchName into current branch. - * - * Conflicting file contents are merged with separator. - * @param branchName name of branch to be merge into current - * @return list of conflicting paths - * @throws BranchNotFoundException if there's no branch with the specified name - * @throws VCSFilesCorruptedException if one of .vcs/ files is absent or contains corrupted data - * @throws IOException if for some reason VCS files cannot be read or written - */ - public List merge(String branchName) throws IOException, BranchNotFoundException, VCSFilesCorruptedException { - if (!branchExists(branchName)) { - throw new BranchNotFoundException(branchName); - } - List currentBlobs = getBlobsRecursively(getHeadCommit()); - List newBlobs = getBlobsRecursively(getRefContent(new VCSRef(branchName))); - Map presentBlobs = new HashMap<>(); - currentBlobs.forEach(blob -> presentBlobs.put(blob.getPath(), blob)); - List conflicts = new ArrayList<>(); - for (LightBlob lightBlob : newBlobs) { - if (presentBlobs.containsKey(lightBlob.getPath()) && - !presentBlobs.get(lightBlob.getPath()).getHash().equals(lightBlob.getHash())) { - conflicts.add(lightBlob.getPath()); - Blob blob = (Blob) getObject(lightBlob.getHash()); - Files.write(Paths.get(lightBlob.getPath()), CONFLICT_SEPARATOR.getBytes(), StandardOpenOption.APPEND); - Files.write(Paths.get(lightBlob.getPath()), blob.getContent(), StandardOpenOption.APPEND); - } - else { - putBlobOnDisk(lightBlob); - addBlobToIndex((Blob) getObject(lightBlob.getHash())); - } - } - return conflicts; - } - - /** - * Creates new branch pointing to same commit as HEAD. - * @param branchName name for a newly created branch - * @throws IOException if for some reason VCS files cannot be read or written - * @throws BranchAlreadyExistsException if branchName already exists - * @throws VCSFilesCorruptedException if VCS files are absent or corrupted - */ - public void createBranch(String branchName) throws IOException, BranchAlreadyExistsException, VCSFilesCorruptedException { - Path branchPath = REFS_DIRECTORY.resolve(branchName); - if (Files.exists(branchPath)) { - throw new BranchAlreadyExistsException("A branch '" + branchName + "' already exists!"); - } - try { - Files.createFile(branchPath); - } catch (IOException e) { - throw new IOException("Cannot create ref for branch " + branchName); - } - // TODO: handle ref not found - writeObject(getHeadCommit(), branchPath.toFile()); - } - - /** - * Deletes specified branch. - * @param branchName branch to be deleted - * @throws IOException if for some reason VCS files cannot be read or written - * @throws DeleteActiveBranchException if branchName is active branch - * @throws VCSFilesCorruptedException if VCS files are absent or corrupted - * @throws BranchNotFoundException if branchName does not exist - */ - public void deleteBranch(String branchName) throws IOException, DeleteActiveBranchException, - VCSFilesCorruptedException, BranchNotFoundException { - assertBranchExists(branchName); - if (branchName.equals(getHead())) { - throw new DeleteActiveBranchException("Cannot delete branch '" + branchName + "' while you are currently on."); - } - Files.delete(REFS_DIRECTORY.resolve(branchName)); - } - - /** - * Adds file to stage for the next commit. - * @param filePath file to add - * @throws IOException if for some reason VCS files cannot be read or written - * @throws VCSFilesCorruptedException if VCS files are absent or corrupted - */ - public void add(Path filePath) throws IOException, VCSFilesCorruptedException { + public void add(String pathString) { + Path filePath = Paths.get(pathString); if (Files.isDirectory(filePath)) { throw new UnsupportedOperationException(); } - Blob newBlob = new Blob(filePath); - writeGitObject(newBlob); - addBlobToIndex(newBlob); + ContentfulBlob newBlob = new ContentfulBlob(filePath); + staged.add(newBlob.getContentlessBlob()); } - /** - * Returns list of all commits in the current branch. - * @return list of all commits in the current branch - * @throws IOException if for some reason VCS files cannot be read or written - * @throws VCSFilesCorruptedException if VCS files are absent or corrupted - */ - public List log() throws IOException, VCSFilesCorruptedException { - List result = new ArrayList<>(); - String lastCommitHash; - if (VCSRef.isRef(getHead())) { - lastCommitHash = getRefContent(VCSRef.fromString(getHead())); - } - else { - lastCommitHash = getHead(); - } - while (lastCommitHash != null) { - Commit lastCommit = (Commit) getObject(lastCommitHash); - result.add(lastCommit); - lastCommitHash = lastCommit.getPrevCommit(); - } - Collections.reverse(result); - return result; + public void createBranch(String branchName) { + Branches.create(branchName); } - - /** - * Creates new commit from all staged files. - * @param commitMessage a commit message - * @throws NothingToCommitException if there no files staged for commit - * @throws IOException if for some reason VCS files cannot be read or written - * @throws VCSFilesCorruptedException if VCS files are absent or corrupted - */ - public void commit(String commitMessage) throws NothingToCommitException, IOException, VCSFilesCorruptedException { - List index = getIndex(); - if (index.size() == 0) { - throw new NothingToCommitException("Nothing to commit"); - } - String prevCommit = getHeadCommit(); - Commit commit = new Commit(commitMessage, index, prevCommit); - writeGitObject(commit); - if (VCSRef.isRef(getHead())) { - setRefContent(getHead(), commit.getSHA()); - } - else { - setHead(commit.getSHA()); - } - setIndex(new ArrayList<>()); + public void deleteBranch(String branchName) { + Branches.delete(branchName); } - - private GitObject getObject(String hash) throws VCSFilesCorruptedException, IOException { - if (!objects.containsKey(hash)) { - try { - objects.put(hash, readGitObject(hash)); - } catch (IOException e) { - throw new IOException("Cannot get object from '"+ hash +"' file"); - } + public void commit(String commitMessage) throws NothingToCommitException, EmptyCommitMessageException { + if (commitMessage.isEmpty()) { + throw new EmptyCommitMessageException(); } - return objects.get(hash); - } - - - - private boolean branchExists(String branchName) { - return Files.exists(REFS_DIRECTORY.resolve(branchName)); - } - - private void assertBranchExists(String branchName) throws BranchNotFoundException { - if (!branchExists(branchName)) { - throw new BranchNotFoundException(branchName); + if (staged.empty()) { + throw new NothingToCommitException(); } + index.updateWith(staged); + Commit commit = new Commit(commitMessage, staged.getFiles(), head.getCommitSHA()); + head = head.addCommitAfter(commit); } - - private String getHead() throws IOException, VCSFilesCorruptedException { - if (head == null) { - try { - head = (String) readObject(HEAD_FILE.toFile()); - } catch (IOException e) { - // TODO: throw custom exception? - throw new IOException("Cannot read HEAD file!"); - } catch (ClassNotFoundException e) { - throw new VCSFilesCorruptedException(HEAD_FILE + " is corrupted!"); - } + public List log() { + CommitRef currentCommitRef = head; + List result = new ArrayList<>(); + while (currentCommitRef != null) { + Commit currentCommit = currentCommitRef.getCommit() + result.add(currentCommit.toString()); + currentCommitRef = currentCommit.getPrevCommit(); } - return head; - } - - private String getHeadCommit() throws IOException, VCSFilesCorruptedException { - String head = getHead(); - if (VCSRef.isRef(head)) { - return getRefContent(VCSRef.fromString(head)); - } - else - return head; + Collections.reverse(result); + return result; } - - private String getRefContent(VCSRef ref) throws IOException, VCSFilesCorruptedException { - if (!refs.containsKey(ref.getName())) { - Path refPath = REFS_DIRECTORY.resolve(ref.getName()); - if (Files.notExists(refPath)) { - throw new VCSFilesCorruptedException(refPath + " is absent!"); - } - try { - String refContent = (String) readObject(refPath.toFile()); - refs.put(ref.getName(), refContent); - } catch (IOException e) { - throw new IOException("Cannot read ref file for "+ ref); - } catch (ClassNotFoundException e) { - throw new VCSFilesCorruptedException(refPath + " is corrupted!"); + public List merge(String branchName) throws MergeWhenStagedNotEmptyException { + if (!staged.empty()) { + throw new MergeWhenStagedNotEmptyException(); + } + Branch mergingBranch = Branches.get(branchName); + RepoState curState = index; + RepoState mergingState = RepoState.getFromCommit(mergingBranch.getCommit()); + Map curBlobs = curState.getFiles().stream().collect(Collectors.toMap(ContentlessBlob::getPath, Function.identity())); + List mergingBlobs = mergingState.getFiles(); + for (ContentlessBlob newBlob : mergingBlobs) { + if (curBlobs.containsKey(newBlob.getPath())) { + ContentlessBlob oldBlob = curBlobs.get(newBlob.getPath()); + if (!oldBlob.getSHA().equals(newBlob.getSHA()) { + // TODO: Bad from design point of view + mergeBlobs(oldBlob, newBlob); + } } - } - return refs.get(ref.getName()); - } - - private void setHead(String head) throws IOException { - this.head = head; - try { - writeObject(head, HEAD_FILE.toFile()); - } - catch (IOException e) { - throw new IOException("Cannot write to HEAD file!"); - } - } - - private ArrayList getIndex() throws IOException, VCSFilesCorruptedException { - if (index == null) { - try { - FileInputStream indexIn = new FileInputStream(INDEX_FILE.toFile()); - ObjectInputStream indexObjIn = new ObjectInputStream(indexIn); - //noinspection unchecked - index = (ArrayList) indexObjIn.readObject(); - } catch (FileNotFoundException e) { - throw new VCSFilesCorruptedException("Index file not found!"); - } catch (IOException e) { - // TODO: throw another exception? - throw new IOException("Unable to read from index file!"); - } catch (ClassNotFoundException e) { - throw new VCSFilesCorruptedException("Index file is corrupted!"); + else { + staged.add(newBlob); } } - return index; - } - private void setIndex(ArrayList index) throws IOException, VCSFilesCorruptedException { - this.index = index; - try { - writeObject(index, INDEX_FILE.toFile()); - } - catch (FileNotFoundException e) { - throw new VCSFilesCorruptedException("Index file not found!"); - } catch (IOException e) { - // TODO: throw another exception? - throw new IOException("Unable to write to index file!"); -// throw new IOException("Unable to write to index file!"); - } - } - private void setRefContent(String ref, String hash) throws IOException { - if (VCSRef.isRef(ref)) { - ref = VCSRef.fromString(ref).getName(); - } - refs.put(ref, hash); - writeObject(hash, REFS_DIRECTORY.resolve(ref).toFile()); + return null; } - private void writeGitObject(GitObject gitObject) throws IOException { - // Destination determines object content - try { - Path path = OBJS_DIRECTORY.resolve(gitObject.getSHA()); - if (Files.exists(path)) { - return; - } - writeObject(gitObject, path.toFile()); - } catch (IOException e) { - throw new IOException("Can't write "+ gitObject.getSHA() +" object to file!"); - } - } - private GitObject readGitObject(String hash) throws IOException, VCSFilesCorruptedException { - Path path = OBJS_DIRECTORY.resolve(hash); - try { - return (GitObject) readObject(path.toFile()); - } catch (ClassNotFoundException e) { - throw new VCSFilesCorruptedException(path.toString() + " is corrupted!"); - } - } - private void writeObject(Object object, File destination) throws IOException { - FileOutputStream outStream = new FileOutputStream(destination); - ObjectOutputStream objOutStream = new ObjectOutputStream(outStream); - objOutStream.writeObject(object); - objOutStream.close(); - outStream.close(); - } - private Object readObject(File source) throws IOException, ClassNotFoundException { - FileInputStream inStream = new FileInputStream(source); - ObjectInputStream objInStream = new ObjectInputStream(inStream); - Object object = objInStream.readObject(); - objInStream.close(); - inStream.close(); - return object; - } - private List getBlobsRecursively(String commitHash) throws IOException, VCSFilesCorruptedException { - Map blobs = new HashMap<>(); - String currentCommitHash = commitHash; - while (currentCommitHash != null) { - Commit currentCommit = (Commit) getObject(currentCommitHash); - currentCommit.getBlobs().forEach(blob -> blobs.putIfAbsent(blob.getPath(), blob)); - currentCommitHash = currentCommit.getPrevCommit(); - } - List result = new ArrayList<>(); - blobs.forEach((key, blob) -> result.add(blob)); - return result; - } - private void putBlobOnDisk(LightBlob lightBlob) throws IOException, VCSFilesCorruptedException { - Path blobPath = Paths.get(lightBlob.getPath()); - Path blobParent = blobPath.getParent(); - if (blobParent != null && Files.notExists(blobParent)) { - Files.createDirectories(blobParent); - } - Blob blob = (Blob) getObject(lightBlob.getHash()); - Files.write(blobPath, blob.getContent()); - } - private void addBlobToIndex(Blob blob) throws IOException, VCSFilesCorruptedException { - ArrayList index = getIndex(); - boolean found = false; - for (int i = 0; i < index.size(); i++) { - LightBlob currentBlob = index.get(i); - if (blob.getPath().equals(currentBlob.getPath())) { - if (!blob.getSHA().equals(currentBlob.getHash())) { - index.set(i, blob.getLightBlob()); - } - else { - return; - } - found = true; - break; - } - } - if (!found) { - index.add(blob.getLightBlob()); - } - setIndex(index); + private void mergeBlobs(ContentlessBlob oldBlob, ContentlessBlob newBlob) { + throw new UnsupportedOperationException(); } } diff --git a/src/main/java/vcs/VCSException.java b/src/main/java/vcs/VCSException.java index 0da5653..0895b27 100644 --- a/src/main/java/vcs/VCSException.java +++ b/src/main/java/vcs/VCSException.java @@ -4,6 +4,7 @@ * Signals that exception inside VCS of some sort occurred. */ public class VCSException extends Exception { + VCSException(){} VCSException(String s) { super(s); } diff --git a/src/main/java/vcs/VCSRef.java b/src/main/java/vcs/VCSRef.java deleted file mode 100644 index 80c1c9b..0000000 --- a/src/main/java/vcs/VCSRef.java +++ /dev/null @@ -1,24 +0,0 @@ -package vcs; - -class VCSRef { - private static final String REF_PREFIX = "ref: "; - private String name; - static boolean isRef(String name) { - return name.startsWith(REF_PREFIX); - } - VCSRef(String name) { - this.name = name; - } - static VCSRef fromString(String refString) { - return new VCSRef(refString.substring(REF_PREFIX.length())); - } - - @Override - public String toString() { - return REF_PREFIX + name; - } - - String getName() { - return name; - } -} diff --git a/src/main/java/vcs/WrongArgumentsException.java b/src/main/java/vcs/WrongArgumentsException.java new file mode 100644 index 0000000..9bb84fa --- /dev/null +++ b/src/main/java/vcs/WrongArgumentsException.java @@ -0,0 +1,7 @@ +package vcs; + +public class WrongArgumentsException extends VCSException { + public WrongArgumentsException(String s) { + super(s); + } +} From 7bfe6ff3b9ccec40bbfaa0c27ff841dbebbc10e7 Mon Sep 17 00:00:00 2001 From: Ilya Date: Wed, 5 Apr 2017 00:16:52 +0300 Subject: [PATCH 13/22] added checkout --- src/main/java/vcs/CheckoutStagedNotEmpty.java | 4 ++++ src/main/java/vcs/Commit.java | 8 +++++++ src/main/java/vcs/VCS.java | 23 ++++++++++++++++--- 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 src/main/java/vcs/CheckoutStagedNotEmpty.java diff --git a/src/main/java/vcs/CheckoutStagedNotEmpty.java b/src/main/java/vcs/CheckoutStagedNotEmpty.java new file mode 100644 index 0000000..40d9798 --- /dev/null +++ b/src/main/java/vcs/CheckoutStagedNotEmpty.java @@ -0,0 +1,4 @@ +package vcs; + +public class CheckoutStagedNotEmpty extends VCSException { +} diff --git a/src/main/java/vcs/Commit.java b/src/main/java/vcs/Commit.java index 7b0b507..3d346ed 100644 --- a/src/main/java/vcs/Commit.java +++ b/src/main/java/vcs/Commit.java @@ -23,4 +23,12 @@ public String toString() { public CommitSHARef getPrevCommit() { return prevCommit; } + + public CommitRef getRef() { + throw new UnsupportedOperationException(); + } + + public static Commit get(String revision) { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index cbc49c2..7daad05 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -21,6 +21,21 @@ public VCS(){ throw new UnsupportedOperationException(); } + public void checkout(String revision) throws CheckoutStagedNotEmpty { + if (!staged.empty()) { + throw new CheckoutStagedNotEmpty(); + } + if (Branches.exists(revision)) { + Branch newBranch = Branches.get(revision); + head = newBranch; + index = RepoState.getFromCommit(newBranch.getCommit()); + } + else { + Commit headCommit = Commit.get(revision); + head = headCommit.getRef(); + index = RepoState.getFromCommit(headCommit); + } + } public void add(String pathString) { Path filePath = Paths.get(pathString); if (Files.isDirectory(filePath)) { @@ -29,7 +44,9 @@ public void add(String pathString) { ContentfulBlob newBlob = new ContentfulBlob(filePath); staged.add(newBlob.getContentlessBlob()); } - + public void rm(String pathString) { + throw new UnsupportedOperationException(); + } public void createBranch(String branchName) { Branches.create(branchName); } @@ -51,7 +68,7 @@ public List log() { CommitRef currentCommitRef = head; List result = new ArrayList<>(); while (currentCommitRef != null) { - Commit currentCommit = currentCommitRef.getCommit() + Commit currentCommit = currentCommitRef.getCommit(); result.add(currentCommit.toString()); currentCommitRef = currentCommit.getPrevCommit(); } @@ -70,7 +87,7 @@ public List merge(String branchName) throws MergeWhenStagedNotEmptyExcep for (ContentlessBlob newBlob : mergingBlobs) { if (curBlobs.containsKey(newBlob.getPath())) { ContentlessBlob oldBlob = curBlobs.get(newBlob.getPath()); - if (!oldBlob.getSHA().equals(newBlob.getSHA()) { + if (!oldBlob.getSHA().equals(newBlob.getSHA())) { // TODO: Bad from design point of view mergeBlobs(oldBlob, newBlob); } From a1e6fab6c79449a35a5f6832e17b0e5b8fdc141d Mon Sep 17 00:00:00 2001 From: Ilya Date: Mon, 10 Apr 2017 13:49:56 +0300 Subject: [PATCH 14/22] Some operations implemented --- src/main/java/vcs/Branch.java | 4 ++-- src/main/java/vcs/Commit.java | 6 +++++- src/main/java/vcs/CommitSHARef.java | 4 ++++ src/main/java/vcs/ContentfulBlob.java | 4 ++-- src/main/java/vcs/ContentlessBlob.java | 5 +++++ 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/java/vcs/Branch.java b/src/main/java/vcs/Branch.java index 8973d35..260b66d 100644 --- a/src/main/java/vcs/Branch.java +++ b/src/main/java/vcs/Branch.java @@ -12,12 +12,12 @@ public Branch(CommitSHARef headCommit) { @Override public CommitSHARef getCommitSHA() { - throw new UnsupportedOperationException(); + return headCommit.getCommitSHA(); } @Override public Commit getCommit() { - throw new UnsupportedOperationException(); + return headCommit.getCommit(); } @Override diff --git a/src/main/java/vcs/Commit.java b/src/main/java/vcs/Commit.java index 3d346ed..e62a51b 100644 --- a/src/main/java/vcs/Commit.java +++ b/src/main/java/vcs/Commit.java @@ -25,7 +25,11 @@ public CommitSHARef getPrevCommit() { } public CommitRef getRef() { - throw new UnsupportedOperationException(); + return new CommitSHARef(getSHA()); + + } + + private String getSHA() { throw new UnsupportedOperationException(); } public static Commit get(String revision) { diff --git a/src/main/java/vcs/CommitSHARef.java b/src/main/java/vcs/CommitSHARef.java index e36f206..df7b25d 100644 --- a/src/main/java/vcs/CommitSHARef.java +++ b/src/main/java/vcs/CommitSHARef.java @@ -3,6 +3,10 @@ public class CommitSHARef implements CommitRef{ private String commitSHA; + public CommitSHARef(String commitSHA) { + this.commitSHA = commitSHA; + } + @Override public CommitSHARef getCommitSHA() { throw new UnsupportedOperationException(); diff --git a/src/main/java/vcs/ContentfulBlob.java b/src/main/java/vcs/ContentfulBlob.java index 9960462..559e5b8 100644 --- a/src/main/java/vcs/ContentfulBlob.java +++ b/src/main/java/vcs/ContentfulBlob.java @@ -18,7 +18,7 @@ public BlobSHARef getSHA() { @Override public String getPath() { - throw new UnsupportedOperationException(); + return path; } @Override @@ -28,6 +28,6 @@ public ContentfulBlob getContentfulBlob() { @Override public ContentlessBlob getContentlessBlob() { - throw new UnsupportedOperationException(); + return new ContentlessBlob(path, getSHA()); } } diff --git a/src/main/java/vcs/ContentlessBlob.java b/src/main/java/vcs/ContentlessBlob.java index 1a6cb3e..e8f099d 100644 --- a/src/main/java/vcs/ContentlessBlob.java +++ b/src/main/java/vcs/ContentlessBlob.java @@ -4,6 +4,11 @@ public class ContentlessBlob implements Blob{ private String path; private BlobSHARef blobRef; + public ContentlessBlob(String path, BlobSHARef ref) { + this.path = path; + this.blobRef = ref; + } + @Override public BlobSHARef getSHA() { throw new UnsupportedOperationException(); From c7092a7e9e8463caa3677bf7d697bb61be1656d3 Mon Sep 17 00:00:00 2001 From: Ilya Date: Tue, 11 Apr 2017 02:44:52 +0300 Subject: [PATCH 15/22] Working --- src/main/java/vcs/BlobSHARef.java | 9 +++- src/main/java/vcs/Branch.java | 30 +++++++++++-- src/main/java/vcs/Branches.java | 35 ++++++++++++---- src/main/java/vcs/Commit.java | 12 +++--- src/main/java/vcs/CommitSHARef.java | 5 +++ src/main/java/vcs/GitObject.java | 4 +- src/main/java/vcs/SHARef.java | 4 +- src/main/java/vcs/VCS.java | 12 ++++-- src/main/java/vcs/VCSFiles.java | 42 +++++++++++++++++++ .../java/vcs/VCSFilesCorruptedException.java | 1 + 10 files changed, 129 insertions(+), 25 deletions(-) create mode 100644 src/main/java/vcs/VCSFiles.java diff --git a/src/main/java/vcs/BlobSHARef.java b/src/main/java/vcs/BlobSHARef.java index 31e375b..8b88bed 100644 --- a/src/main/java/vcs/BlobSHARef.java +++ b/src/main/java/vcs/BlobSHARef.java @@ -1,6 +1,11 @@ package vcs; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + public class BlobSHARef implements SHARef { + private static final Path BLOB_DIR = Paths.get("blobs"); private String blobSHA; @Override @@ -20,7 +25,7 @@ public int hashCode() { } @Override - public GitObject getObject() { - throw new UnsupportedOperationException(); + public GitObject getObject() throws IOException, ClassNotFoundException { + return (GitObject) VCSFiles.readObject(BLOB_DIR.resolve(blobSHA)); } } diff --git a/src/main/java/vcs/Branch.java b/src/main/java/vcs/Branch.java index 260b66d..d0c05ec 100644 --- a/src/main/java/vcs/Branch.java +++ b/src/main/java/vcs/Branch.java @@ -1,14 +1,32 @@ package vcs; -public class Branch implements CommitRef { +import java.io.Serializable; + +public class Branch implements CommitRef, Serializable { private CommitSHARef headCommit; private String name; - public Branch(CommitSHARef headCommit) { + public Branch(String name, CommitSHARef headCommit) { + this.name = name; this.headCommit = headCommit; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Branch branch = (Branch) o; + + return name.equals(branch.name); + + } + + @Override + public int hashCode() { + return name.hashCode(); + } @Override public CommitSHARef getCommitSHA() { @@ -20,9 +38,15 @@ public Commit getCommit() { return headCommit.getCommit(); } + /** + * + * @param commit + * @return new head + */ @Override public CommitRef addCommitAfter(Commit commit) { - throw new UnsupportedOperationException(); + headCommit = commit.getSHARef(); + return this; } } diff --git a/src/main/java/vcs/Branches.java b/src/main/java/vcs/Branches.java index 93b368e..83bbc53 100644 --- a/src/main/java/vcs/Branches.java +++ b/src/main/java/vcs/Branches.java @@ -1,17 +1,38 @@ package vcs; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + class Branches { + private static final Path BRANCHES_DIR = Paths.get("branches"); public static boolean exists(String branchName) { - throw new UnsupportedOperationException(); + return VCSFiles.exists(getPath(branchName)); + } + static void create(String branchName, CommitSHARef headCommit) throws IOException { + Path branchPath = getPath(branchName); + VCSFiles.create(branchPath); + VCSFiles.writeObject(branchPath, new Branch(branchName, headCommit)); } - static void create(String branchName) { - throw new UnsupportedOperationException(); + static void delete(String branchName) throws IOException, BranchNotFoundException { + if (!exists(branchName)) { + throw new BranchNotFoundException(branchName); + } + VCSFiles.delete(getPath(branchName)); } - static void delete(String branchName) { - throw new UnsupportedOperationException(); + + static Branch get(String branchName) throws BranchNotFoundException, IOException, VCSFilesCorruptedException { + if (!exists(branchName)) { + throw new BranchNotFoundException(branchName); + } + try { + return (Branch) VCSFiles.readObject(getPath(branchName)); + } catch (ClassNotFoundException e) { + throw new VCSFilesCorruptedException(); + } } - static Branch get(String branchName) { - throw new UnsupportedOperationException(); + private static Path getPath(String branchName) { + return BRANCHES_DIR.resolve(branchName); } } diff --git a/src/main/java/vcs/Commit.java b/src/main/java/vcs/Commit.java index e62a51b..071da27 100644 --- a/src/main/java/vcs/Commit.java +++ b/src/main/java/vcs/Commit.java @@ -24,15 +24,13 @@ public CommitSHARef getPrevCommit() { return prevCommit; } - public CommitRef getRef() { - return new CommitSHARef(getSHA()); - - } - - private String getSHA() { throw new UnsupportedOperationException(); - } + private String getSHA() { throw new UnsupportedOperationException();} public static Commit get(String revision) { throw new UnsupportedOperationException(); } + + public CommitSHARef getSHARef() { + return new CommitSHARef(getSHA()); + } } diff --git a/src/main/java/vcs/CommitSHARef.java b/src/main/java/vcs/CommitSHARef.java index df7b25d..992dc5f 100644 --- a/src/main/java/vcs/CommitSHARef.java +++ b/src/main/java/vcs/CommitSHARef.java @@ -21,4 +21,9 @@ public Commit getCommit() { public CommitRef addCommitAfter(Commit commit) { throw new UnsupportedOperationException(); } + + @Override + public String toString() { + return commitSHA; + } } diff --git a/src/main/java/vcs/GitObject.java b/src/main/java/vcs/GitObject.java index f207efc..a3562be 100644 --- a/src/main/java/vcs/GitObject.java +++ b/src/main/java/vcs/GitObject.java @@ -1,5 +1,7 @@ package vcs; -public interface GitObject { +import java.io.Serializable; + +public interface GitObject extends Serializable{ SHARef getSHARef(); } diff --git a/src/main/java/vcs/SHARef.java b/src/main/java/vcs/SHARef.java index b120d16..e23fb3d 100644 --- a/src/main/java/vcs/SHARef.java +++ b/src/main/java/vcs/SHARef.java @@ -1,5 +1,7 @@ package vcs; +import java.io.IOException; + public interface SHARef { - GitObject getObject(); + GitObject getObject() throws IOException, ClassNotFoundException; } diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index 7daad05..fd8c362 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -1,5 +1,6 @@ package vcs; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -32,7 +33,7 @@ public void checkout(String revision) throws CheckoutStagedNotEmpty { } else { Commit headCommit = Commit.get(revision); - head = headCommit.getRef(); + head = headCommit.getSHARef(); index = RepoState.getFromCommit(headCommit); } } @@ -47,10 +48,13 @@ public void add(String pathString) { public void rm(String pathString) { throw new UnsupportedOperationException(); } - public void createBranch(String branchName) { - Branches.create(branchName); + public void createBranch(String branchName) throws IOException { + Branches.create(branchName, head.getCommitSHA()); } - public void deleteBranch(String branchName) { + public void deleteBranch(String branchName) throws BranchNotFoundException, IOException { + if (head.equals(Branches.get(branchName))) { + throw new BranchNotFoundException(branchName); + } Branches.delete(branchName); } public void commit(String commitMessage) throws NothingToCommitException, EmptyCommitMessageException { diff --git a/src/main/java/vcs/VCSFiles.java b/src/main/java/vcs/VCSFiles.java new file mode 100644 index 0000000..d85dd06 --- /dev/null +++ b/src/main/java/vcs/VCSFiles.java @@ -0,0 +1,42 @@ +package vcs; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class VCSFiles { + private static final Path VCS_DIR = Paths.get("vcs"); + static void delete(Path path) throws IOException { + Files.delete(VCS_DIR.resolve(path)); + } + + public static void create(Path path) throws IOException { + Files.createFile(path); + } + + public static void write(Path path, String s) throws IOException { + Files.write(VCS_DIR.resolve(path), s.getBytes()); + } + + public static boolean exists(Path path) { + return Files.exists(VCS_DIR.resolve(path)); + } + + public static void writeObject(Path path, Serializable object) throws IOException { + FileOutputStream outStream = new FileOutputStream(path.toFile()); + ObjectOutputStream objOutStream = new ObjectOutputStream(outStream); + objOutStream.writeObject(object); + objOutStream.close(); + outStream.close(); + } + + public static Object readObject(Path path) throws IOException, ClassNotFoundException { + FileInputStream inStream = new FileInputStream(path.toFile()); + ObjectInputStream objInStream = new ObjectInputStream(inStream); + Object object = objInStream.readObject(); + objInStream.close(); + inStream.close(); + return object; + } +} diff --git a/src/main/java/vcs/VCSFilesCorruptedException.java b/src/main/java/vcs/VCSFilesCorruptedException.java index 15c4bfb..ed7527d 100644 --- a/src/main/java/vcs/VCSFilesCorruptedException.java +++ b/src/main/java/vcs/VCSFilesCorruptedException.java @@ -4,6 +4,7 @@ * Signals that VCS files are absent when they should be present or their content is corrupted. */ public class VCSFilesCorruptedException extends VCSException { + VCSFilesCorruptedException(){} VCSFilesCorruptedException(String s) { super(s); } From 6f911f627192c3fdc310dc8bae1ff06661598250 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 13 Apr 2017 12:31:56 +0300 Subject: [PATCH 16/22] fixed version ready --- build.gradle | 1 + src/main/java/vcs/Blob.java | 10 ++- src/main/java/vcs/BlobSHARef.java | 16 +++-- src/main/java/vcs/Branch.java | 17 +++-- .../vcs/BranchAlreadyExistsException.java | 2 +- src/main/java/vcs/Branches.java | 2 +- src/main/java/vcs/CheckoutStagedNotEmpty.java | 4 -- .../vcs/CheckoutStagedNotEmptyException.java | 4 ++ src/main/java/vcs/Commit.java | 34 ++++++++-- src/main/java/vcs/CommitRef.java | 11 ++- src/main/java/vcs/CommitSHARef.java | 31 +++++++-- src/main/java/vcs/ContentfulBlob.java | 31 +++++++-- src/main/java/vcs/ContentlessBlob.java | 31 +++++---- .../java/vcs/EmptyCommitMessageException.java | 2 +- src/main/java/vcs/GitObject.java | 8 ++- .../vcs/MergeWhenStagedNotEmptyException.java | 2 +- src/main/java/vcs/RepoState.java | 68 +++++++++++++++---- src/main/java/vcs/SHARef.java | 3 +- src/main/java/vcs/VCS.java | 42 +++++++----- src/main/java/vcs/VCSFiles.java | 18 +++-- .../java/vcs/VCSFilesCorruptedException.java | 3 - 21 files changed, 249 insertions(+), 91 deletions(-) delete mode 100644 src/main/java/vcs/CheckoutStagedNotEmpty.java create mode 100644 src/main/java/vcs/CheckoutStagedNotEmptyException.java diff --git a/build.gradle b/build.gradle index 8c2ffda..6a927bc 100644 --- a/build.gradle +++ b/build.gradle @@ -11,4 +11,5 @@ repositories { dependencies { testCompile group: 'junit', name: 'junit', version: '4.11' + compile group: 'org.jetbrains', name: 'annotations', version: '13.0' } diff --git a/src/main/java/vcs/Blob.java b/src/main/java/vcs/Blob.java index 879a508..c2ebbbe 100644 --- a/src/main/java/vcs/Blob.java +++ b/src/main/java/vcs/Blob.java @@ -1,10 +1,14 @@ package vcs; +import java.io.IOException; import java.io.Serializable; +import java.nio.file.Path; +import java.nio.file.Paths; + +interface Blob extends GitObject, Serializable{ + Path BLOB_DIR = Paths.get("blobs"); -public interface Blob extends Serializable{ - BlobSHARef getSHA(); String getPath(); - ContentfulBlob getContentfulBlob(); + ContentfulBlob getContentfulBlob() throws IOException, ClassNotFoundException; ContentlessBlob getContentlessBlob(); } diff --git a/src/main/java/vcs/BlobSHARef.java b/src/main/java/vcs/BlobSHARef.java index 8b88bed..08f7179 100644 --- a/src/main/java/vcs/BlobSHARef.java +++ b/src/main/java/vcs/BlobSHARef.java @@ -1,13 +1,14 @@ package vcs; import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -public class BlobSHARef implements SHARef { - private static final Path BLOB_DIR = Paths.get("blobs"); +class BlobSHARef implements SHARef { private String blobSHA; + BlobSHARef(String blobSHA) { + this.blobSHA = blobSHA; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -26,6 +27,11 @@ public int hashCode() { @Override public GitObject getObject() throws IOException, ClassNotFoundException { - return (GitObject) VCSFiles.readObject(BLOB_DIR.resolve(blobSHA)); + return (GitObject) VCSFiles.readObject(Blob.BLOB_DIR.resolve(blobSHA)); + } + + @Override + public String toString() { + return blobSHA; } } diff --git a/src/main/java/vcs/Branch.java b/src/main/java/vcs/Branch.java index d0c05ec..e4fe813 100644 --- a/src/main/java/vcs/Branch.java +++ b/src/main/java/vcs/Branch.java @@ -1,13 +1,14 @@ package vcs; +import java.io.IOException; import java.io.Serializable; -public class Branch implements CommitRef, Serializable { +class Branch implements CommitRef, Serializable { private CommitSHARef headCommit; private String name; - public Branch(String name, CommitSHARef headCommit) { + Branch(String name, CommitSHARef headCommit) { this.name = name; this.headCommit = headCommit; } @@ -34,19 +35,25 @@ public CommitSHARef getCommitSHA() { } @Override - public Commit getCommit() { + public Commit getCommit() throws IOException, ClassNotFoundException { return headCommit.getCommit(); } /** * - * @param commit + * @param commit to be added after * @return new head */ @Override public CommitRef addCommitAfter(Commit commit) { - headCommit = commit.getSHARef(); + headCommit = commit.getSHARef(); + writeToDisk(); return this; } + @Override + public void writeToDisk() { + throw new UnsupportedOperationException(); + } + } diff --git a/src/main/java/vcs/BranchAlreadyExistsException.java b/src/main/java/vcs/BranchAlreadyExistsException.java index 26f6a6d..e43ef05 100644 --- a/src/main/java/vcs/BranchAlreadyExistsException.java +++ b/src/main/java/vcs/BranchAlreadyExistsException.java @@ -3,7 +3,7 @@ /** * Signals an attempt to create branch with name that is already taken. */ -public class BranchAlreadyExistsException extends VCSException { +class BranchAlreadyExistsException extends VCSException { BranchAlreadyExistsException(String s) { super(s); } diff --git a/src/main/java/vcs/Branches.java b/src/main/java/vcs/Branches.java index 83bbc53..084c1ad 100644 --- a/src/main/java/vcs/Branches.java +++ b/src/main/java/vcs/Branches.java @@ -6,7 +6,7 @@ class Branches { private static final Path BRANCHES_DIR = Paths.get("branches"); - public static boolean exists(String branchName) { + static boolean exists(String branchName) { return VCSFiles.exists(getPath(branchName)); } static void create(String branchName, CommitSHARef headCommit) throws IOException { diff --git a/src/main/java/vcs/CheckoutStagedNotEmpty.java b/src/main/java/vcs/CheckoutStagedNotEmpty.java deleted file mode 100644 index 40d9798..0000000 --- a/src/main/java/vcs/CheckoutStagedNotEmpty.java +++ /dev/null @@ -1,4 +0,0 @@ -package vcs; - -public class CheckoutStagedNotEmpty extends VCSException { -} diff --git a/src/main/java/vcs/CheckoutStagedNotEmptyException.java b/src/main/java/vcs/CheckoutStagedNotEmptyException.java new file mode 100644 index 0000000..a3ef98f --- /dev/null +++ b/src/main/java/vcs/CheckoutStagedNotEmptyException.java @@ -0,0 +1,4 @@ +package vcs; + +class CheckoutStagedNotEmptyException extends VCSException { +} diff --git a/src/main/java/vcs/Commit.java b/src/main/java/vcs/Commit.java index 071da27..e314363 100644 --- a/src/main/java/vcs/Commit.java +++ b/src/main/java/vcs/Commit.java @@ -1,14 +1,21 @@ package vcs; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.List; -public class Commit { +public class Commit implements GitObject { + + private static final Path COMMIT_DIR = Paths.get("commits"); private List files; private CommitSHARef prevCommit; private long time; private String message; - public Commit(String message, List files, CommitSHARef prevCommit) { + Commit(String message, List files, CommitSHARef prevCommit) { this.message = message; this.files = files; this.time = System.currentTimeMillis(); @@ -20,17 +27,32 @@ public String toString() { throw new UnsupportedOperationException(); } - public CommitSHARef getPrevCommit() { + CommitSHARef getPrevCommit() { return prevCommit; } - private String getSHA() { throw new UnsupportedOperationException();} + private String getSHA() { + try { + MessageDigest messageDigest = MessageDigest.getInstance(SHA1); + messageDigest.update(String.valueOf(time).getBytes()); + messageDigest.update(message.getBytes()); + return GitObject.byteArrayToHex(messageDigest.digest()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-1 hashing algorithm not implemented!"); + } + } - public static Commit get(String revision) { - throw new UnsupportedOperationException(); + public static Commit get(String revision) throws IOException, ClassNotFoundException { + return (Commit) VCSFiles.readObject(COMMIT_DIR.resolve(revision)); } + @Override public CommitSHARef getSHARef() { return new CommitSHARef(getSHA()); } + + List getFiles() { + return files; + } + } diff --git a/src/main/java/vcs/CommitRef.java b/src/main/java/vcs/CommitRef.java index 0df3de6..95ad37f 100644 --- a/src/main/java/vcs/CommitRef.java +++ b/src/main/java/vcs/CommitRef.java @@ -1,7 +1,16 @@ package vcs; +import java.io.IOException; +import java.nio.file.Paths; + public interface CommitRef { + static CommitRef readRef(String filename) throws IOException, ClassNotFoundException { + return (CommitRef) VCSFiles.readObject(Paths.get(filename)); + } + CommitSHARef getCommitSHA(); - Commit getCommit(); + Commit getCommit() throws IOException, ClassNotFoundException; CommitRef addCommitAfter(Commit commit); + + void writeToDisk(); } diff --git a/src/main/java/vcs/CommitSHARef.java b/src/main/java/vcs/CommitSHARef.java index 992dc5f..8292ded 100644 --- a/src/main/java/vcs/CommitSHARef.java +++ b/src/main/java/vcs/CommitSHARef.java @@ -1,24 +1,40 @@ package vcs; -public class CommitSHARef implements CommitRef{ + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +class CommitSHARef implements CommitRef, SHARef{ + private static final Path COMMIT_DIR = Paths.get("commits"); private String commitSHA; - public CommitSHARef(String commitSHA) { + CommitSHARef(String commitSHA) { this.commitSHA = commitSHA; } @Override public CommitSHARef getCommitSHA() { - throw new UnsupportedOperationException(); + return this; } @Override - public Commit getCommit() { - throw new UnsupportedOperationException(); + public Commit getCommit() throws IOException, ClassNotFoundException { + return (Commit) VCSFiles.readObject(COMMIT_DIR.resolve(commitSHA)); } + /** + * Adds commit after the one reference points to. + * @param commit commit to be added after the one reference points to + * @return reference to added commit + */ @Override public CommitRef addCommitAfter(Commit commit) { + return commit.getSHARef(); + } + + @Override + public void writeToDisk() { throw new UnsupportedOperationException(); } @@ -26,4 +42,9 @@ public CommitRef addCommitAfter(Commit commit) { public String toString() { return commitSHA; } + + @Override + public GitObject getObject() throws IOException, ClassNotFoundException { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/vcs/ContentfulBlob.java b/src/main/java/vcs/ContentfulBlob.java index 559e5b8..08720b5 100644 --- a/src/main/java/vcs/ContentfulBlob.java +++ b/src/main/java/vcs/ContentfulBlob.java @@ -1,19 +1,34 @@ package vcs; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; -public class ContentfulBlob implements Blob{ +class ContentfulBlob implements Blob{ private String path; private byte[] content; - public ContentfulBlob(Path filePath) { - throw new UnsupportedOperationException(); + private String blobSHA; + + ContentfulBlob(Path filePath) throws IOException { + content = Files.readAllBytes(filePath); + path = filePath.toString(); + try { + MessageDigest messageDigest = MessageDigest.getInstance(SHA1); + messageDigest.update(path.getBytes()); + messageDigest.update(content); + blobSHA = GitObject.byteArrayToHex(messageDigest.digest()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-1 hashing algorithm not implemented!"); + } } @Override - public BlobSHARef getSHA() { - throw new UnsupportedOperationException(); + public BlobSHARef getSHARef() { + return new BlobSHARef(blobSHA); } @Override @@ -28,6 +43,10 @@ public ContentfulBlob getContentfulBlob() { @Override public ContentlessBlob getContentlessBlob() { - return new ContentlessBlob(path, getSHA()); + return new ContentlessBlob(path, getSHARef()); + } + + byte[] getContent() { + return content; } } diff --git a/src/main/java/vcs/ContentlessBlob.java b/src/main/java/vcs/ContentlessBlob.java index e8f099d..eeaf1a1 100644 --- a/src/main/java/vcs/ContentlessBlob.java +++ b/src/main/java/vcs/ContentlessBlob.java @@ -1,27 +1,35 @@ package vcs; -public class ContentlessBlob implements Blob{ +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +class ContentlessBlob implements Blob{ + @NotNull private String path; + @NotNull private BlobSHARef blobRef; - public ContentlessBlob(String path, BlobSHARef ref) { + ContentlessBlob(@NotNull String path, @NotNull BlobSHARef ref) { + this.path = path; this.blobRef = ref; } @Override - public BlobSHARef getSHA() { - throw new UnsupportedOperationException(); + public BlobSHARef getSHARef() { + return blobRef; } + @NotNull @Override public String getPath() { - throw new UnsupportedOperationException(); + return path; } @Override - public ContentfulBlob getContentfulBlob() { - throw new UnsupportedOperationException(); + public ContentfulBlob getContentfulBlob() throws IOException, ClassNotFoundException { + return (ContentfulBlob) VCSFiles.readObject(BLOB_DIR.resolve(blobRef.toString())); } @Override @@ -31,20 +39,19 @@ public boolean equals(Object o) { ContentlessBlob that = (ContentlessBlob) o; - if (path != null ? !path.equals(that.path) : that.path != null) return false; - return blobRef != null ? blobRef.equals(that.blobRef) : that.blobRef == null; + return blobRef.equals(that.blobRef); } @Override public int hashCode() { - int result = path != null ? path.hashCode() : 0; - result = 31 * result + (blobRef != null ? blobRef.hashCode() : 0); + int result = path.hashCode(); + result = 31 * result + blobRef.hashCode(); return result; } @Override public ContentlessBlob getContentlessBlob() { - throw new UnsupportedOperationException(); + return this; } } diff --git a/src/main/java/vcs/EmptyCommitMessageException.java b/src/main/java/vcs/EmptyCommitMessageException.java index 83561d1..463a5d4 100644 --- a/src/main/java/vcs/EmptyCommitMessageException.java +++ b/src/main/java/vcs/EmptyCommitMessageException.java @@ -1,4 +1,4 @@ package vcs; -public class EmptyCommitMessageException extends VCSException { +class EmptyCommitMessageException extends VCSException { } diff --git a/src/main/java/vcs/GitObject.java b/src/main/java/vcs/GitObject.java index a3562be..7580164 100644 --- a/src/main/java/vcs/GitObject.java +++ b/src/main/java/vcs/GitObject.java @@ -1,7 +1,13 @@ package vcs; import java.io.Serializable; +import java.math.BigInteger; -public interface GitObject extends Serializable{ +interface GitObject extends Serializable{ + String SHA1 = "SHA-1"; SHARef getSHARef(); + + static String byteArrayToHex(byte[] array) { + return (new BigInteger(1, array)).toString(16); + } } diff --git a/src/main/java/vcs/MergeWhenStagedNotEmptyException.java b/src/main/java/vcs/MergeWhenStagedNotEmptyException.java index 71ebd61..0573075 100644 --- a/src/main/java/vcs/MergeWhenStagedNotEmptyException.java +++ b/src/main/java/vcs/MergeWhenStagedNotEmptyException.java @@ -1,4 +1,4 @@ package vcs; -public class MergeWhenStagedNotEmptyException extends VCSException { +class MergeWhenStagedNotEmptyException extends VCSException { } diff --git a/src/main/java/vcs/RepoState.java b/src/main/java/vcs/RepoState.java index 1045512..da668c2 100644 --- a/src/main/java/vcs/RepoState.java +++ b/src/main/java/vcs/RepoState.java @@ -1,30 +1,72 @@ package vcs; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; import java.util.List; +import java.util.stream.Collectors; -public class RepoState { - private List files; +class RepoState { + private HashSet files; + private Path filePath = null; - public void add(ContentlessBlob blob) { - throw new UnsupportedOperationException(); + private RepoState() { + files = new HashSet<>(); } - public void remove(ContentlessBlob blob) { - throw new UnsupportedOperationException(); + RepoState(String stateName) throws IOException, ClassNotFoundException { + filePath = Paths.get(stateName); + if (VCSFiles.exists(filePath)) { + //noinspection unchecked + files = (HashSet) VCSFiles.readObject(filePath); + } + else { + VCSFiles.create(filePath); + } + files = new HashSet<>(); } - public boolean empty() { + void add(ContentlessBlob blob) throws IOException { + if (files.contains(blob)) { + return; + } + files.add(blob); + if (filePath != null) { + VCSFiles.writeObject(filePath, files); + } + } + @SuppressWarnings("unused") + public void remove(ContentlessBlob blob) throws IOException { + if (!files.contains(blob)) { + return; // TODO: throw exception? + } + files.remove(blob); + if (filePath != null) { + VCSFiles.writeObject(filePath, files); + } + } + + boolean empty() { return files.isEmpty(); } - public void updateWith(RepoState delta) { - throw new UnsupportedOperationException(); + void updateWith(RepoState delta) { + files.addAll(delta.getFiles()); } - public List getFiles() { - return files; + List getFiles() { + return files.stream().collect(Collectors.toList()); } - public static RepoState getFromCommit(Commit commit) { - throw new UnsupportedOperationException(); + static RepoState getFromCommit(Commit commit) throws IOException, ClassNotFoundException { + RepoState result = new RepoState(); + while (commit != null) { + List blobs = commit.getFiles(); + for (ContentlessBlob blob : blobs) { + result.add(blob); + } + commit = commit.getPrevCommit().getCommit(); + } + return result; } } diff --git a/src/main/java/vcs/SHARef.java b/src/main/java/vcs/SHARef.java index e23fb3d..d79f6c7 100644 --- a/src/main/java/vcs/SHARef.java +++ b/src/main/java/vcs/SHARef.java @@ -2,6 +2,7 @@ import java.io.IOException; -public interface SHARef { +interface SHARef { + @SuppressWarnings("unused") GitObject getObject() throws IOException, ClassNotFoundException; } diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index fd8c362..45f0189 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -1,9 +1,7 @@ package vcs; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.nio.file.*; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -12,19 +10,26 @@ import java.util.stream.Collectors; public class VCS { + private static final String INDEX_FILENAME = "index"; + private static final String STAGED_FILENAME = "staged"; + private static final String HEAD_FILENAME = "HEAD"; + private static final byte[] MERGED_FILES_SEPARATOR = "\n===============\n".getBytes(); private CommitRef head; private RepoState index; private RepoState staged; // TODO: take String[] as args and check them - public VCS(){ - throw new UnsupportedOperationException(); + public VCS() throws IOException, ClassNotFoundException { + index = new RepoState(INDEX_FILENAME); + staged = new RepoState(STAGED_FILENAME); + head = CommitRef.readRef(HEAD_FILENAME); } - public void checkout(String revision) throws CheckoutStagedNotEmpty { + public void checkout(String revision) throws CheckoutStagedNotEmptyException, VCSFilesCorruptedException, + BranchNotFoundException, IOException, ClassNotFoundException { if (!staged.empty()) { - throw new CheckoutStagedNotEmpty(); + throw new CheckoutStagedNotEmptyException(); } if (Branches.exists(revision)) { Branch newBranch = Branches.get(revision); @@ -36,8 +41,9 @@ public void checkout(String revision) throws CheckoutStagedNotEmpty { head = headCommit.getSHARef(); index = RepoState.getFromCommit(headCommit); } + head.writeToDisk(); } - public void add(String pathString) { + public void add(String pathString) throws IOException { Path filePath = Paths.get(pathString); if (Files.isDirectory(filePath)) { throw new UnsupportedOperationException(); @@ -45,13 +51,10 @@ public void add(String pathString) { ContentfulBlob newBlob = new ContentfulBlob(filePath); staged.add(newBlob.getContentlessBlob()); } - public void rm(String pathString) { - throw new UnsupportedOperationException(); - } public void createBranch(String branchName) throws IOException { Branches.create(branchName, head.getCommitSHA()); } - public void deleteBranch(String branchName) throws BranchNotFoundException, IOException { + public void deleteBranch(String branchName) throws BranchNotFoundException, IOException, VCSFilesCorruptedException { if (head.equals(Branches.get(branchName))) { throw new BranchNotFoundException(branchName); } @@ -68,7 +71,7 @@ public void commit(String commitMessage) throws NothingToCommitException, EmptyC Commit commit = new Commit(commitMessage, staged.getFiles(), head.getCommitSHA()); head = head.addCommitAfter(commit); } - public List log() { + public List log() throws IOException, ClassNotFoundException { CommitRef currentCommitRef = head; List result = new ArrayList<>(); while (currentCommitRef != null) { @@ -79,7 +82,8 @@ public List log() { Collections.reverse(result); return result; } - public List merge(String branchName) throws MergeWhenStagedNotEmptyException { + public List merge(String branchName) throws MergeWhenStagedNotEmptyException, VCSFilesCorruptedException, + BranchNotFoundException, IOException, ClassNotFoundException { if (!staged.empty()) { throw new MergeWhenStagedNotEmptyException(); } @@ -91,7 +95,7 @@ public List merge(String branchName) throws MergeWhenStagedNotEmptyExcep for (ContentlessBlob newBlob : mergingBlobs) { if (curBlobs.containsKey(newBlob.getPath())) { ContentlessBlob oldBlob = curBlobs.get(newBlob.getPath()); - if (!oldBlob.getSHA().equals(newBlob.getSHA())) { + if (!oldBlob.getSHARef().equals(newBlob.getSHARef())) { // TODO: Bad from design point of view mergeBlobs(oldBlob, newBlob); } @@ -104,7 +108,11 @@ public List merge(String branchName) throws MergeWhenStagedNotEmptyExcep return null; } - private void mergeBlobs(ContentlessBlob oldBlob, ContentlessBlob newBlob) { - throw new UnsupportedOperationException(); + private void mergeBlobs(ContentlessBlob oldBlob, ContentlessBlob newBlob) throws IOException, ClassNotFoundException { + // TODO: looks not right + Path path = Paths.get(oldBlob.getPath()); + Files.write(path, oldBlob.getContentfulBlob().getContent()); + Files.write(path, MERGED_FILES_SEPARATOR, StandardOpenOption.APPEND); + Files.write(path, newBlob.getContentfulBlob().getContent(), StandardOpenOption.APPEND); } } diff --git a/src/main/java/vcs/VCSFiles.java b/src/main/java/vcs/VCSFiles.java index d85dd06..b629609 100644 --- a/src/main/java/vcs/VCSFiles.java +++ b/src/main/java/vcs/VCSFiles.java @@ -5,13 +5,13 @@ import java.nio.file.Path; import java.nio.file.Paths; -public class VCSFiles { +class VCSFiles { private static final Path VCS_DIR = Paths.get("vcs"); static void delete(Path path) throws IOException { Files.delete(VCS_DIR.resolve(path)); } - public static void create(Path path) throws IOException { + static void create(Path path) throws IOException { Files.createFile(path); } @@ -19,11 +19,11 @@ public static void write(Path path, String s) throws IOException { Files.write(VCS_DIR.resolve(path), s.getBytes()); } - public static boolean exists(Path path) { + static boolean exists(Path path) { return Files.exists(VCS_DIR.resolve(path)); } - public static void writeObject(Path path, Serializable object) throws IOException { + static void writeObject(Path path, Serializable object) throws IOException { FileOutputStream outStream = new FileOutputStream(path.toFile()); ObjectOutputStream objOutStream = new ObjectOutputStream(outStream); objOutStream.writeObject(object); @@ -31,7 +31,7 @@ public static void writeObject(Path path, Serializable object) throws IOExceptio outStream.close(); } - public static Object readObject(Path path) throws IOException, ClassNotFoundException { + static Object readObject(Path path) throws IOException, ClassNotFoundException { FileInputStream inStream = new FileInputStream(path.toFile()); ObjectInputStream objInStream = new ObjectInputStream(inStream); Object object = objInStream.readObject(); @@ -39,4 +39,12 @@ public static Object readObject(Path path) throws IOException, ClassNotFoundExce inStream.close(); return object; } + + public static Path getVCSPath(String relativePath) { + return VCS_DIR.resolve(relativePath); + } + + public static void appendToFile(String s) { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/vcs/VCSFilesCorruptedException.java b/src/main/java/vcs/VCSFilesCorruptedException.java index ed7527d..ac9a080 100644 --- a/src/main/java/vcs/VCSFilesCorruptedException.java +++ b/src/main/java/vcs/VCSFilesCorruptedException.java @@ -5,7 +5,4 @@ */ public class VCSFilesCorruptedException extends VCSException { VCSFilesCorruptedException(){} - VCSFilesCorruptedException(String s) { - super(s); - } } From c56abae6c3d0e6ba30b142cc6d3c88254cd3af44 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 13 Apr 2017 13:33:41 +0300 Subject: [PATCH 17/22] bug fixes --- src/main/java/console/Main.java | 37 +++++++++++++---------------- src/main/java/vcs/Branch.java | 7 +++--- src/main/java/vcs/Branches.java | 8 ++++--- src/main/java/vcs/Commit.java | 3 ++- src/main/java/vcs/CommitRef.java | 6 ++--- src/main/java/vcs/CommitSHARef.java | 4 ---- src/main/java/vcs/RepoState.java | 3 ++- src/main/java/vcs/SHARef.java | 3 ++- src/main/java/vcs/VCS.java | 12 ++++++++-- src/main/java/vcs/VCSFiles.java | 30 +++++++++++++++-------- src/test/java/vcs/VCSTest.java | 29 +++++++++++----------- 11 files changed, 77 insertions(+), 65 deletions(-) diff --git a/src/main/java/console/Main.java b/src/main/java/console/Main.java index 49ae7ff..322e21f 100644 --- a/src/main/java/console/Main.java +++ b/src/main/java/console/Main.java @@ -1,36 +1,29 @@ package console; -import vcs.*; +import vcs.NothingToCommitException; +import vcs.VCS; +import vcs.VCSException; import java.io.IOException; -import java.nio.file.Paths; import java.util.List; public class Main { - private static void jack(String kek){ - kek = "jack"; - } public static void main(String[] args) { - String kek = "kek"; - jack(kek); - System.out.print(kek); - } - public static void kek(String[] args) { for (String arg : args) { System.out.println(arg); } VCS vcs; try { vcs = new VCS(); - } catch (IOException | VCSException e) { + } catch (Exception e) { System.out.println(e.getMessage()); return; } switch (args[0]) { case "add": { try { - vcs.add(Paths.get(args[1])); - } catch (IOException | VCSException e) { + vcs.add(args[1]); + } catch (IOException e) { System.out.println(e.getMessage()); } break; @@ -40,7 +33,7 @@ public static void kek(String[] args) { vcs.commit(args[1]); } catch (NothingToCommitException e) { System.out.println("Nothing to commit."); - } catch (IOException | VCSException e) { + } catch (VCSException | IOException e) { System.out.println(e.getMessage()); } break; @@ -56,7 +49,7 @@ public static void kek(String[] args) { else { try { vcs.createBranch(args[1]); - } catch (VCSException | IOException e) { + } catch (IOException e) { System.out.println(e.getMessage()); } } @@ -64,20 +57,22 @@ public static void kek(String[] args) { } case "log": { try { - List log = vcs.log(); - for (Commit record : log) { - System.out.print(record.toString()); + List log = vcs.log(); + for (String record : log) { + System.out.print(record); System.out.println("---------------------"); } - } catch (IOException | VCSFilesCorruptedException e) { + } catch (IOException e) { System.out.println(e.getMessage()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); } break; } case "checkout": { try { vcs.checkout(args[1]); - } catch (IOException | BranchNotFoundException | VCSFilesCorruptedException e) { + } catch (Exception e) { System.out.println(e.getMessage()); } break; @@ -92,7 +87,7 @@ public static void kek(String[] args) { System.out.println("Following files conflict:"); conflicts.forEach(System.out::println); } - } catch (VCSException | IOException e) { + } catch (Exception e) { System.out.println(e.getMessage()); } } diff --git a/src/main/java/vcs/Branch.java b/src/main/java/vcs/Branch.java index e4fe813..f8a0e54 100644 --- a/src/main/java/vcs/Branch.java +++ b/src/main/java/vcs/Branch.java @@ -45,15 +45,14 @@ public Commit getCommit() throws IOException, ClassNotFoundException { * @return new head */ @Override - public CommitRef addCommitAfter(Commit commit) { + public CommitRef addCommitAfter(Commit commit) throws IOException { headCommit = commit.getSHARef(); writeToDisk(); return this; } - @Override - public void writeToDisk() { - throw new UnsupportedOperationException(); + public void writeToDisk() throws IOException { + VCSFiles.writeObject(Branches.BRANCHES_DIR.resolve(name), this); } } diff --git a/src/main/java/vcs/Branches.java b/src/main/java/vcs/Branches.java index 084c1ad..9826815 100644 --- a/src/main/java/vcs/Branches.java +++ b/src/main/java/vcs/Branches.java @@ -5,14 +5,16 @@ import java.nio.file.Paths; class Branches { - private static final Path BRANCHES_DIR = Paths.get("branches"); + static final Path BRANCHES_DIR = Paths.get("branches"); static boolean exists(String branchName) { return VCSFiles.exists(getPath(branchName)); } - static void create(String branchName, CommitSHARef headCommit) throws IOException { + static Branch create(String branchName, CommitSHARef headCommit) throws IOException { Path branchPath = getPath(branchName); VCSFiles.create(branchPath); - VCSFiles.writeObject(branchPath, new Branch(branchName, headCommit)); + Branch newBranch = new Branch(branchName, headCommit); + VCSFiles.writeObject(branchPath, newBranch); + return newBranch; } static void delete(String branchName) throws IOException, BranchNotFoundException { if (!exists(branchName)) { diff --git a/src/main/java/vcs/Commit.java b/src/main/java/vcs/Commit.java index e314363..28b289f 100644 --- a/src/main/java/vcs/Commit.java +++ b/src/main/java/vcs/Commit.java @@ -15,11 +15,12 @@ public class Commit implements GitObject { private long time; private String message; - Commit(String message, List files, CommitSHARef prevCommit) { + Commit(String message, List files, CommitSHARef prevCommit) throws IOException { this.message = message; this.files = files; this.time = System.currentTimeMillis(); this.prevCommit = prevCommit; + VCSFiles.writeObject(COMMIT_DIR.resolve(getSHA()), this); } @Override diff --git a/src/main/java/vcs/CommitRef.java b/src/main/java/vcs/CommitRef.java index 95ad37f..7adda78 100644 --- a/src/main/java/vcs/CommitRef.java +++ b/src/main/java/vcs/CommitRef.java @@ -1,16 +1,16 @@ package vcs; import java.io.IOException; +import java.io.Serializable; import java.nio.file.Paths; -public interface CommitRef { +public interface CommitRef extends Serializable{ static CommitRef readRef(String filename) throws IOException, ClassNotFoundException { return (CommitRef) VCSFiles.readObject(Paths.get(filename)); } CommitSHARef getCommitSHA(); Commit getCommit() throws IOException, ClassNotFoundException; - CommitRef addCommitAfter(Commit commit); + CommitRef addCommitAfter(Commit commit) throws IOException; - void writeToDisk(); } diff --git a/src/main/java/vcs/CommitSHARef.java b/src/main/java/vcs/CommitSHARef.java index 8292ded..cd699f1 100644 --- a/src/main/java/vcs/CommitSHARef.java +++ b/src/main/java/vcs/CommitSHARef.java @@ -33,10 +33,6 @@ public CommitRef addCommitAfter(Commit commit) { return commit.getSHARef(); } - @Override - public void writeToDisk() { - throw new UnsupportedOperationException(); - } @Override public String toString() { diff --git a/src/main/java/vcs/RepoState.java b/src/main/java/vcs/RepoState.java index da668c2..808f183 100644 --- a/src/main/java/vcs/RepoState.java +++ b/src/main/java/vcs/RepoState.java @@ -21,9 +21,10 @@ private RepoState() { files = (HashSet) VCSFiles.readObject(filePath); } else { + files = new HashSet<>(); VCSFiles.create(filePath); + VCSFiles.writeObject(filePath, files); } - files = new HashSet<>(); } void add(ContentlessBlob blob) throws IOException { diff --git a/src/main/java/vcs/SHARef.java b/src/main/java/vcs/SHARef.java index d79f6c7..c5a55e6 100644 --- a/src/main/java/vcs/SHARef.java +++ b/src/main/java/vcs/SHARef.java @@ -1,8 +1,9 @@ package vcs; import java.io.IOException; +import java.io.Serializable; -interface SHARef { +interface SHARef extends Serializable{ @SuppressWarnings("unused") GitObject getObject() throws IOException, ClassNotFoundException; } diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index 45f0189..2a3add4 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -14,6 +14,7 @@ public class VCS { private static final String STAGED_FILENAME = "staged"; private static final String HEAD_FILENAME = "HEAD"; private static final byte[] MERGED_FILES_SEPARATOR = "\n===============\n".getBytes(); + private static final String MASTER_BRANCH = "master"; private CommitRef head; private RepoState index; private RepoState staged; @@ -21,8 +22,15 @@ public class VCS { // TODO: take String[] as args and check them public VCS() throws IOException, ClassNotFoundException { + VCSFiles.init(); index = new RepoState(INDEX_FILENAME); staged = new RepoState(STAGED_FILENAME); + Path headPath = Paths.get(HEAD_FILENAME); + if (!VCSFiles.exists(headPath)) { + VCSFiles.create(headPath); + Commit initial = new Commit("initial", new ArrayList<>(), null); + VCSFiles.writeObject(headPath, Branches.create(MASTER_BRANCH, initial.getSHARef())); + } head = CommitRef.readRef(HEAD_FILENAME); } @@ -41,7 +49,7 @@ public void checkout(String revision) throws CheckoutStagedNotEmptyException, VC head = headCommit.getSHARef(); index = RepoState.getFromCommit(headCommit); } - head.writeToDisk(); + VCSFiles.writeObject(Paths.get(HEAD_FILENAME), head); } public void add(String pathString) throws IOException { Path filePath = Paths.get(pathString); @@ -60,7 +68,7 @@ public void deleteBranch(String branchName) throws BranchNotFoundException, IOEx } Branches.delete(branchName); } - public void commit(String commitMessage) throws NothingToCommitException, EmptyCommitMessageException { + public void commit(String commitMessage) throws NothingToCommitException, EmptyCommitMessageException, IOException { if (commitMessage.isEmpty()) { throw new EmptyCommitMessageException(); } diff --git a/src/main/java/vcs/VCSFiles.java b/src/main/java/vcs/VCSFiles.java index b629609..a0527c7 100644 --- a/src/main/java/vcs/VCSFiles.java +++ b/src/main/java/vcs/VCSFiles.java @@ -6,24 +6,40 @@ import java.nio.file.Paths; class VCSFiles { - private static final Path VCS_DIR = Paths.get("vcs"); + private static final Path VCS_DIR = Paths.get(".vcs"); static void delete(Path path) throws IOException { Files.delete(VCS_DIR.resolve(path)); } + + static void init() throws IOException { + if (!Files.exists(VCS_DIR) || !Files.isDirectory(VCS_DIR)) { + Files.createDirectories(VCS_DIR); + } + } static void create(Path path) throws IOException { + path = VCS_DIR.resolve(path); + if (!Files.exists(path.getParent())) { + Files.createDirectories(path.getParent()); + } Files.createFile(path); } public static void write(Path path, String s) throws IOException { - Files.write(VCS_DIR.resolve(path), s.getBytes()); + path = VCS_DIR.resolve(path); + Files.write(path, s.getBytes()); } static boolean exists(Path path) { - return Files.exists(VCS_DIR.resolve(path)); + path = VCS_DIR.resolve(path); + return Files.exists(path); } static void writeObject(Path path, Serializable object) throws IOException { + path = VCS_DIR.resolve(path); + if (!Files.exists(path.getParent())) { + Files.createDirectories(path.getParent()); + } FileOutputStream outStream = new FileOutputStream(path.toFile()); ObjectOutputStream objOutStream = new ObjectOutputStream(outStream); objOutStream.writeObject(object); @@ -32,6 +48,7 @@ static void writeObject(Path path, Serializable object) throws IOException { } static Object readObject(Path path) throws IOException, ClassNotFoundException { + path = VCS_DIR.resolve(path); FileInputStream inStream = new FileInputStream(path.toFile()); ObjectInputStream objInStream = new ObjectInputStream(inStream); Object object = objInStream.readObject(); @@ -40,11 +57,4 @@ static Object readObject(Path path) throws IOException, ClassNotFoundException { return object; } - public static Path getVCSPath(String relativePath) { - return VCS_DIR.resolve(relativePath); - } - - public static void appendToFile(String s) { - throw new UnsupportedOperationException(); - } } diff --git a/src/test/java/vcs/VCSTest.java b/src/test/java/vcs/VCSTest.java index e17d8dc..94daa9f 100644 --- a/src/test/java/vcs/VCSTest.java +++ b/src/test/java/vcs/VCSTest.java @@ -53,16 +53,15 @@ public void simpleLog() throws Exception, NothingToCommitException { List files = Arrays.asList(A, B, C); List commitMessages = new ArrayList<>(); commitMessages.add(INITIAL_MSG); - for (int i = 0; i < files.size(); i++) { - TestFile cur = files.get(i); + for (TestFile cur : files) { cur.create(); vcs.add(cur.getPath()); - String commitMessage = "add_" + cur.getPath().toString(); + String commitMessage = "add_" + cur.getPath(); vcs.commit(commitMessage); commitMessages.add(commitMessage); assertArrayEquals(commitMessages.toArray(), - vcs.log().stream().map(commit -> commit.getMessage()).collect(Collectors.toList()).toArray()); + vcs.log().toArray()); } } @@ -118,12 +117,12 @@ public void branchesCheckout() throws Exception, NothingToCommitException, Branc cur.create(); vcs.checkout(branch); vcs.add(cur.getPath()); - String commitMessage = "add_" + cur.getPath().toString(); + String commitMessage = "add_" + cur.getPath(); vcs.commit(commitMessage); - List log = vcs.log(); + List log = vcs.log(); assertEquals(2, log.size()); - assertEquals(INITIAL_MSG, log.get(0).getMessage()); - assertEquals(commitMessage, log.get(1).getMessage()); + assertEquals(INITIAL_MSG, log.get(0)); + assertEquals(commitMessage, log.get(1)); } vcs.checkout(MASTER_BRANCH); for (TestFile file : files) { @@ -154,16 +153,16 @@ private void deleteRecursive(Path path) throws IOException { } class TestFile { - public static final Path TEST_DIR = Paths.get("testXX/"); + static final Path TEST_DIR = Paths.get("testXX/"); private String content; private Path path; - public TestFile(String path, String content){ + TestFile(String path, String content){ this.content = content; this.path = TEST_DIR.resolve(path); } - public void create() throws IOException { + void create() throws IOException { Path parent = path.getParent(); if (parent != null && Files.notExists(parent)) { Files.createDirectories(parent); @@ -173,15 +172,15 @@ public void create() throws IOException { } Files.write(path, content.getBytes()); } - public void remove() throws IOException { + void remove() throws IOException { Files.delete(path); } - public boolean check() throws IOException { + boolean check() throws IOException { return Files.exists(path) && Arrays.toString(Files.readAllBytes(path)).equals(content); } - public Path getPath() { - return path; + String getPath() { + return path.toString(); } } \ No newline at end of file From 782d2500f0269f822059a4ea70c6e0f535d00e35 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 13 Apr 2017 13:36:04 +0300 Subject: [PATCH 18/22] Tests disabled --- src/test/java/vcs/VCSTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/vcs/VCSTest.java b/src/test/java/vcs/VCSTest.java index 94daa9f..03f0a2a 100644 --- a/src/test/java/vcs/VCSTest.java +++ b/src/test/java/vcs/VCSTest.java @@ -45,7 +45,7 @@ public class VCSTest { */ - @Test +// @Test public void simpleLog() throws Exception, NothingToCommitException { deleteRecursive(VCS_PATH); deleteRecursive(TestFile.TEST_DIR); @@ -101,7 +101,7 @@ public void simpleLog() throws Exception, NothingToCommitException { 1111 cat C */ - @Test +// @Test public void branchesCheckout() throws Exception, NothingToCommitException, BranchAlreadyExistsException { deleteRecursive(TestFile.TEST_DIR); deleteRecursive(VCS_PATH); From 205ec7dbab9c79a573ee71a041d4652f67de9b5f Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 13 Apr 2017 14:02:57 +0300 Subject: [PATCH 19/22] fixes --- src/main/java/console/Main.java | 27 ++++++++--------- src/main/java/vcs/Branches.java | 3 ++ src/main/java/vcs/Commit.java | 10 +++++-- .../java/vcs/EmptyCommitMessageException.java | 2 +- src/main/java/vcs/RepoState.java | 29 ++++++++++++++----- src/main/java/vcs/VCS.java | 8 +++++ src/main/java/vcs/VCSFiles.java | 8 ++++- 7 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/main/java/console/Main.java b/src/main/java/console/Main.java index 322e21f..283c542 100644 --- a/src/main/java/console/Main.java +++ b/src/main/java/console/Main.java @@ -1,5 +1,6 @@ package console; +import vcs.EmptyCommitMessageException; import vcs.NothingToCommitException; import vcs.VCS; import vcs.VCSException; @@ -24,17 +25,15 @@ public static void main(String[] args) { try { vcs.add(args[1]); } catch (IOException e) { - System.out.println(e.getMessage()); + printException(e); } break; } case "commit": { try { vcs.commit(args[1]); - } catch (NothingToCommitException e) { - System.out.println("Nothing to commit."); - } catch (VCSException | IOException e) { - System.out.println(e.getMessage()); + } catch (Exception e) { + printException(e); } break; } @@ -43,14 +42,14 @@ public static void main(String[] args) { try { vcs.deleteBranch(args[2]); } catch (VCSException | IOException e) { - System.out.println(e.getMessage()); + printException(e); } } else { try { vcs.createBranch(args[1]); } catch (IOException e) { - System.out.println(e.getMessage()); + printException(e); } } break; @@ -62,10 +61,8 @@ public static void main(String[] args) { System.out.print(record); System.out.println("---------------------"); } - } catch (IOException e) { - System.out.println(e.getMessage()); - } catch (ClassNotFoundException e) { - e.printStackTrace(); + } catch (IOException | ClassNotFoundException e) { + printException(e); } break; } @@ -73,7 +70,7 @@ public static void main(String[] args) { try { vcs.checkout(args[1]); } catch (Exception e) { - System.out.println(e.getMessage()); + printException(e); } break; } @@ -88,9 +85,13 @@ public static void main(String[] args) { conflicts.forEach(System.out::println); } } catch (Exception e) { - System.out.println(e.getMessage()); + printException(e); } } } } + + private static void printException(Exception e) { + System.out.println(e.getClass().toString() + " " + e.getMessage()); + } } diff --git a/src/main/java/vcs/Branches.java b/src/main/java/vcs/Branches.java index 9826815..39922af 100644 --- a/src/main/java/vcs/Branches.java +++ b/src/main/java/vcs/Branches.java @@ -1,5 +1,7 @@ package vcs; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; @@ -23,6 +25,7 @@ static void delete(String branchName) throws IOException, BranchNotFoundExceptio VCSFiles.delete(getPath(branchName)); } + @NotNull static Branch get(String branchName) throws BranchNotFoundException, IOException, VCSFilesCorruptedException { if (!exists(branchName)) { throw new BranchNotFoundException(branchName); diff --git a/src/main/java/vcs/Commit.java b/src/main/java/vcs/Commit.java index 28b289f..f034757 100644 --- a/src/main/java/vcs/Commit.java +++ b/src/main/java/vcs/Commit.java @@ -1,5 +1,7 @@ package vcs; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; @@ -7,15 +9,16 @@ import java.security.NoSuchAlgorithmException; import java.util.List; -public class Commit implements GitObject { +class Commit implements GitObject { private static final Path COMMIT_DIR = Paths.get("commits"); + @NotNull private List files; private CommitSHARef prevCommit; private long time; private String message; - Commit(String message, List files, CommitSHARef prevCommit) throws IOException { + Commit(String message, @NotNull List files, CommitSHARef prevCommit) throws IOException { this.message = message; this.files = files; this.time = System.currentTimeMillis(); @@ -43,7 +46,7 @@ private String getSHA() { } } - public static Commit get(String revision) throws IOException, ClassNotFoundException { + static Commit get(String revision) throws IOException, ClassNotFoundException { return (Commit) VCSFiles.readObject(COMMIT_DIR.resolve(revision)); } @@ -52,6 +55,7 @@ public CommitSHARef getSHARef() { return new CommitSHARef(getSHA()); } + @NotNull List getFiles() { return files; } diff --git a/src/main/java/vcs/EmptyCommitMessageException.java b/src/main/java/vcs/EmptyCommitMessageException.java index 463a5d4..83561d1 100644 --- a/src/main/java/vcs/EmptyCommitMessageException.java +++ b/src/main/java/vcs/EmptyCommitMessageException.java @@ -1,4 +1,4 @@ package vcs; -class EmptyCommitMessageException extends VCSException { +public class EmptyCommitMessageException extends VCSException { } diff --git a/src/main/java/vcs/RepoState.java b/src/main/java/vcs/RepoState.java index 808f183..ea894de 100644 --- a/src/main/java/vcs/RepoState.java +++ b/src/main/java/vcs/RepoState.java @@ -32,9 +32,7 @@ void add(ContentlessBlob blob) throws IOException { return; } files.add(blob); - if (filePath != null) { - VCSFiles.writeObject(filePath, files); - } + flush(); } @SuppressWarnings("unused") public void remove(ContentlessBlob blob) throws IOException { @@ -42,17 +40,16 @@ public void remove(ContentlessBlob blob) throws IOException { return; // TODO: throw exception? } files.remove(blob); - if (filePath != null) { - VCSFiles.writeObject(filePath, files); - } + flush(); } boolean empty() { return files.isEmpty(); } - void updateWith(RepoState delta) { + void updateWith(RepoState delta) throws IOException { files.addAll(delta.getFiles()); + flush(); } List getFiles() { @@ -60,14 +57,32 @@ List getFiles() { } static RepoState getFromCommit(Commit commit) throws IOException, ClassNotFoundException { + System.out.println("getFromCommit-1"); RepoState result = new RepoState(); while (commit != null) { + System.out.println("getFromCommit-2"); List blobs = commit.getFiles(); + System.out.println("getFromCommit-3"); for (ContentlessBlob blob : blobs) { result.add(blob); } + System.out.println("getFromCommit-4"); + if (commit.getPrevCommit() == null) { + break; + } commit = commit.getPrevCommit().getCommit(); } return result; } + + void clear() throws IOException { + files.clear(); + flush(); + } + + private void flush() throws IOException { + if (filePath != null) { + VCSFiles.writeObject(filePath, files); + } + } } diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index 2a3add4..42d5678 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -49,6 +49,13 @@ public void checkout(String revision) throws CheckoutStagedNotEmptyException, VC head = headCommit.getSHARef(); index = RepoState.getFromCommit(headCommit); } + List indexFiles = index.getFiles(); + System.out.println(indexFiles.size()); + for (ContentlessBlob blob : indexFiles) { + System.out.println(blob.getPath()); + byte[] content = blob.getContentfulBlob().getContent(); + VCSFiles.write(Paths.get(blob.getPath()), content); + } VCSFiles.writeObject(Paths.get(HEAD_FILENAME), head); } public void add(String pathString) throws IOException { @@ -76,6 +83,7 @@ public void commit(String commitMessage) throws NothingToCommitException, EmptyC throw new NothingToCommitException(); } index.updateWith(staged); + staged.clear(); Commit commit = new Commit(commitMessage, staged.getFiles(), head.getCommitSHA()); head = head.addCommitAfter(commit); } diff --git a/src/main/java/vcs/VCSFiles.java b/src/main/java/vcs/VCSFiles.java index a0527c7..89721b4 100644 --- a/src/main/java/vcs/VCSFiles.java +++ b/src/main/java/vcs/VCSFiles.java @@ -26,8 +26,14 @@ static void create(Path path) throws IOException { } public static void write(Path path, String s) throws IOException { + write(path, s.getBytes()); + } + public static void write(Path path, byte[] bytes) throws IOException { path = VCS_DIR.resolve(path); - Files.write(path, s.getBytes()); + if (!Files.exists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + Files.write(path, bytes); } static boolean exists(Path path) { From e12ea35fbf85392b025ad275183ab76301fed8ea Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 13 Apr 2017 15:21:35 +0300 Subject: [PATCH 20/22] Bugs fixed. Tests enabled --- src/main/java/console/Main.java | 2 +- src/main/java/vcs/Commit.java | 4 +++- src/main/java/vcs/ContentfulBlob.java | 5 +++++ src/main/java/vcs/ContentlessBlob.java | 1 + src/main/java/vcs/RepoState.java | 31 ++++++++++++-------------- src/main/java/vcs/VCS.java | 22 ++++++++---------- src/main/java/vcs/VCSFiles.java | 16 ++++++++----- src/test/java/vcs/VCSTest.java | 19 +++++++++++----- 8 files changed, 57 insertions(+), 43 deletions(-) diff --git a/src/main/java/console/Main.java b/src/main/java/console/Main.java index 283c542..fb9e2f2 100644 --- a/src/main/java/console/Main.java +++ b/src/main/java/console/Main.java @@ -59,7 +59,7 @@ public static void main(String[] args) { List log = vcs.log(); for (String record : log) { System.out.print(record); - System.out.println("---------------------"); + System.out.println("\n---------------------\n"); } } catch (IOException | ClassNotFoundException e) { printException(e); diff --git a/src/main/java/vcs/Commit.java b/src/main/java/vcs/Commit.java index f034757..493fb60 100644 --- a/src/main/java/vcs/Commit.java +++ b/src/main/java/vcs/Commit.java @@ -28,7 +28,9 @@ class Commit implements GitObject { @Override public String toString() { - throw new UnsupportedOperationException(); + @SuppressWarnings("StringBufferReplaceableByString") StringBuilder result = new StringBuilder(getSHA() + "\n"); + result.append(message); + return result.toString(); } CommitSHARef getPrevCommit() { diff --git a/src/main/java/vcs/ContentfulBlob.java b/src/main/java/vcs/ContentfulBlob.java index 08720b5..b980943 100644 --- a/src/main/java/vcs/ContentfulBlob.java +++ b/src/main/java/vcs/ContentfulBlob.java @@ -1,5 +1,7 @@ package vcs; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -8,6 +10,7 @@ class ContentfulBlob implements Blob{ private String path; + @NotNull private byte[] content; private String blobSHA; @@ -23,6 +26,7 @@ class ContentfulBlob implements Blob{ } catch (NoSuchAlgorithmException e) { throw new RuntimeException("SHA-1 hashing algorithm not implemented!"); } + VCSFiles.writeObject(BLOB_DIR.resolve(blobSHA), this); } @@ -46,6 +50,7 @@ public ContentlessBlob getContentlessBlob() { return new ContentlessBlob(path, getSHARef()); } + @NotNull byte[] getContent() { return content; } diff --git a/src/main/java/vcs/ContentlessBlob.java b/src/main/java/vcs/ContentlessBlob.java index eeaf1a1..2ccb018 100644 --- a/src/main/java/vcs/ContentlessBlob.java +++ b/src/main/java/vcs/ContentlessBlob.java @@ -27,6 +27,7 @@ public String getPath() { return path; } + @NotNull @Override public ContentfulBlob getContentfulBlob() throws IOException, ClassNotFoundException { return (ContentfulBlob) VCSFiles.readObject(BLOB_DIR.resolve(blobRef.toString())); diff --git a/src/main/java/vcs/RepoState.java b/src/main/java/vcs/RepoState.java index ea894de..4b2c99b 100644 --- a/src/main/java/vcs/RepoState.java +++ b/src/main/java/vcs/RepoState.java @@ -3,43 +3,42 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.HashSet; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; class RepoState { - private HashSet files; + private HashMap files; private Path filePath = null; private RepoState() { - files = new HashSet<>(); + files = new HashMap<>(); } RepoState(String stateName) throws IOException, ClassNotFoundException { filePath = Paths.get(stateName); if (VCSFiles.exists(filePath)) { //noinspection unchecked - files = (HashSet) VCSFiles.readObject(filePath); + files = (HashMap) VCSFiles.readObject(filePath); } else { - files = new HashSet<>(); + files = new HashMap<>(); VCSFiles.create(filePath); VCSFiles.writeObject(filePath, files); } } void add(ContentlessBlob blob) throws IOException { - if (files.contains(blob)) { + if (files.containsKey(blob.getPath())) { return; } - files.add(blob); + files.put(blob.getPath(), blob); flush(); } @SuppressWarnings("unused") public void remove(ContentlessBlob blob) throws IOException { - if (!files.contains(blob)) { + if (!files.containsKey(blob)) { return; // TODO: throw exception? } - files.remove(blob); + files.remove(blob.getPath()); flush(); } @@ -48,25 +47,23 @@ boolean empty() { } void updateWith(RepoState delta) throws IOException { - files.addAll(delta.getFiles()); + for (Map.Entry entry : delta.getFiles().entrySet()) { + files.put(entry.getKey(), entry.getValue()); + } flush(); } - List getFiles() { - return files.stream().collect(Collectors.toList()); + HashMap getFiles() { + return files; } static RepoState getFromCommit(Commit commit) throws IOException, ClassNotFoundException { - System.out.println("getFromCommit-1"); RepoState result = new RepoState(); while (commit != null) { - System.out.println("getFromCommit-2"); List blobs = commit.getFiles(); - System.out.println("getFromCommit-3"); for (ContentlessBlob blob : blobs) { result.add(blob); } - System.out.println("getFromCommit-4"); if (commit.getPrevCommit() == null) { break; } diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index 42d5678..1ee40c5 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -2,10 +2,7 @@ import java.io.IOException; import java.nio.file.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -49,12 +46,11 @@ public void checkout(String revision) throws CheckoutStagedNotEmptyException, VC head = headCommit.getSHARef(); index = RepoState.getFromCommit(headCommit); } - List indexFiles = index.getFiles(); - System.out.println(indexFiles.size()); - for (ContentlessBlob blob : indexFiles) { - System.out.println(blob.getPath()); - byte[] content = blob.getContentfulBlob().getContent(); - VCSFiles.write(Paths.get(blob.getPath()), content); + // TODO: move to method of RepoState + HashMap indexFiles = index.getFiles(); + for (Map.Entry blob : indexFiles.entrySet()) { + byte[] content = blob.getValue().getContentfulBlob().getContent(); + VCSFiles.writeToRoot(Paths.get(blob.getKey()), content); } VCSFiles.writeObject(Paths.get(HEAD_FILENAME), head); } @@ -83,8 +79,8 @@ public void commit(String commitMessage) throws NothingToCommitException, EmptyC throw new NothingToCommitException(); } index.updateWith(staged); + Commit commit = new Commit(commitMessage, staged.getFiles().values().stream().collect(Collectors.toList()), head.getCommitSHA()); staged.clear(); - Commit commit = new Commit(commitMessage, staged.getFiles(), head.getCommitSHA()); head = head.addCommitAfter(commit); } public List log() throws IOException, ClassNotFoundException { @@ -106,8 +102,8 @@ public List merge(String branchName) throws MergeWhenStagedNotEmptyExcep Branch mergingBranch = Branches.get(branchName); RepoState curState = index; RepoState mergingState = RepoState.getFromCommit(mergingBranch.getCommit()); - Map curBlobs = curState.getFiles().stream().collect(Collectors.toMap(ContentlessBlob::getPath, Function.identity())); - List mergingBlobs = mergingState.getFiles(); + Map curBlobs = curState.getFiles(); + List mergingBlobs = mergingState.getFiles().values().stream().collect(Collectors.toList()); for (ContentlessBlob newBlob : mergingBlobs) { if (curBlobs.containsKey(newBlob.getPath())) { ContentlessBlob oldBlob = curBlobs.get(newBlob.getPath()); diff --git a/src/main/java/vcs/VCSFiles.java b/src/main/java/vcs/VCSFiles.java index 89721b4..dbb387f 100644 --- a/src/main/java/vcs/VCSFiles.java +++ b/src/main/java/vcs/VCSFiles.java @@ -29,11 +29,7 @@ public static void write(Path path, String s) throws IOException { write(path, s.getBytes()); } public static void write(Path path, byte[] bytes) throws IOException { - path = VCS_DIR.resolve(path); - if (!Files.exists(path.getParent())) { - Files.createDirectories(path.getParent()); - } - Files.write(path, bytes); + writeToRoot(VCS_DIR.resolve(path), bytes); } static boolean exists(Path path) { @@ -63,4 +59,14 @@ static Object readObject(Path path) throws IOException, ClassNotFoundException { return object; } + static void writeToRoot(Path path, byte[] content) throws IOException { + Path parentPath = path.getParent(); + if (parentPath != null && !Files.exists(parentPath)) { + Files.createDirectories(parentPath); + } + if (!Files.exists(path)) { + Files.createFile(path); + } + Files.write(path, content); + } } diff --git a/src/test/java/vcs/VCSTest.java b/src/test/java/vcs/VCSTest.java index 03f0a2a..e8e0290 100644 --- a/src/test/java/vcs/VCSTest.java +++ b/src/test/java/vcs/VCSTest.java @@ -14,6 +14,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class VCSTest { private static final TestFile A = new TestFile("A.txt", "A text"); @@ -45,7 +46,7 @@ public class VCSTest { */ -// @Test + @Test public void simpleLog() throws Exception, NothingToCommitException { deleteRecursive(VCS_PATH); deleteRecursive(TestFile.TEST_DIR); @@ -60,8 +61,10 @@ public void simpleLog() throws Exception, NothingToCommitException { vcs.commit(commitMessage); commitMessages.add(commitMessage); - assertArrayEquals(commitMessages.toArray(), - vcs.log().toArray()); + List log = vcs.log(); + for (int i = 0; i < commitMessages.size(); i++) { + compareMessages(commitMessages.get(i), log.get(i)); + } } } @@ -101,7 +104,7 @@ public void simpleLog() throws Exception, NothingToCommitException { 1111 cat C */ -// @Test + @Test public void branchesCheckout() throws Exception, NothingToCommitException, BranchAlreadyExistsException { deleteRecursive(TestFile.TEST_DIR); deleteRecursive(VCS_PATH); @@ -121,8 +124,8 @@ public void branchesCheckout() throws Exception, NothingToCommitException, Branc vcs.commit(commitMessage); List log = vcs.log(); assertEquals(2, log.size()); - assertEquals(INITIAL_MSG, log.get(0)); - assertEquals(commitMessage, log.get(1)); + compareMessages(INITIAL_MSG, log.get(0)); + compareMessages(commitMessage, log.get(1)); } vcs.checkout(MASTER_BRANCH); for (TestFile file : files) { @@ -137,6 +140,10 @@ public void branchesCheckout() throws Exception, NothingToCommitException, Branc } } + private void compareMessages(String handmadeMessage, String logMessage) { + assertTrue(logMessage.endsWith(handmadeMessage)); + } + private void deleteRecursive(Path path) throws IOException { if (Files.notExists(path)) { return; From af71eea161096f49d56a055e024451fb0bf8aa23 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 13 Apr 2017 15:31:00 +0300 Subject: [PATCH 21/22] Some JavaDocs --- src/main/java/vcs/Branches.java | 5 +- src/main/java/vcs/VCS.java | 92 +++++++++++++++++++++++++++++---- src/test/java/vcs/VCSTest.java | 3 -- 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/src/main/java/vcs/Branches.java b/src/main/java/vcs/Branches.java index 39922af..90d81d2 100644 --- a/src/main/java/vcs/Branches.java +++ b/src/main/java/vcs/Branches.java @@ -11,7 +11,10 @@ class Branches { static boolean exists(String branchName) { return VCSFiles.exists(getPath(branchName)); } - static Branch create(String branchName, CommitSHARef headCommit) throws IOException { + static Branch create(String branchName, CommitSHARef headCommit) throws IOException, BranchAlreadyExistsException { + if (exists(branchName)) { + throw new BranchAlreadyExistsException(branchName); + } Path branchPath = getPath(branchName); VCSFiles.create(branchPath); Branch newBranch = new Branch(branchName, headCommit); diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index 1ee40c5..60705b0 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -6,6 +6,10 @@ import java.util.function.Function; import java.util.stream.Collectors; +/** + * Class that contains all VCS interface methods. + */ + public class VCS { private static final String INDEX_FILENAME = "index"; private static final String STAGED_FILENAME = "staged"; @@ -18,19 +22,43 @@ public class VCS { // TODO: take String[] as args and check them - public VCS() throws IOException, ClassNotFoundException { + /** + * Creates new VCS instance in current directory or loads one if it already exists. + * @throws VCSFilesCorruptedException if VCS files are absent or corrupted + * @throws IOException if for some reason VCS files cannot be read or written + */ + public VCS() throws IOException, VCSFilesCorruptedException { VCSFiles.init(); - index = new RepoState(INDEX_FILENAME); - staged = new RepoState(STAGED_FILENAME); + try { + index = new RepoState(INDEX_FILENAME); + staged = new RepoState(STAGED_FILENAME); + } catch (ClassNotFoundException e) { + throw new VCSFilesCorruptedException(); + } Path headPath = Paths.get(HEAD_FILENAME); if (!VCSFiles.exists(headPath)) { VCSFiles.create(headPath); Commit initial = new Commit("initial", new ArrayList<>(), null); - VCSFiles.writeObject(headPath, Branches.create(MASTER_BRANCH, initial.getSHARef())); + try { + VCSFiles.writeObject(headPath, Branches.create(MASTER_BRANCH, initial.getSHARef())); + } catch (BranchAlreadyExistsException e) { + throw new VCSFilesCorruptedException(); + } + } + try { + head = CommitRef.readRef(HEAD_FILENAME); + } catch (ClassNotFoundException e) { + throw new VCSFilesCorruptedException(); } - head = CommitRef.readRef(HEAD_FILENAME); } + /** + * Sets HEAD to revison and makes files consistent with revision state. + * @param revision name of a commit or a branch to checkout to + * @throws IOException if for some reason VCS files cannot be read or written + * @throws BranchNotFoundException if passed revision is branch and there's no such branch + * @throws VCSFilesCorruptedException if VCS files are absent or corrupted + */ public void checkout(String revision) throws CheckoutStagedNotEmptyException, VCSFilesCorruptedException, BranchNotFoundException, IOException, ClassNotFoundException { if (!staged.empty()) { @@ -54,6 +82,11 @@ public void checkout(String revision) throws CheckoutStagedNotEmptyException, VC } VCSFiles.writeObject(Paths.get(HEAD_FILENAME), head); } + /** + * Adds file to stage for the next commit. + * @param pathString file to add + * @throws IOException if for some reason VCS files cannot be read or written + */ public void add(String pathString) throws IOException { Path filePath = Paths.get(pathString); if (Files.isDirectory(filePath)) { @@ -62,15 +95,36 @@ public void add(String pathString) throws IOException { ContentfulBlob newBlob = new ContentfulBlob(filePath); staged.add(newBlob.getContentlessBlob()); } - public void createBranch(String branchName) throws IOException { - Branches.create(branchName, head.getCommitSHA()); + /** + * Creates new branch pointing to same commit as HEAD. + * @param branchName name for a newly created branch + * @throws IOException if for some reason VCS files cannot be read or written + * @throws BranchAlreadyExistsException if branchName already exists + */ + public void createBranch(String branchName) throws IOException, BranchAlreadyExistsException { + Branches.create(branchName, head.getCommitSHA()) } - public void deleteBranch(String branchName) throws BranchNotFoundException, IOException, VCSFilesCorruptedException { + + /** + * Deletes specified branch. + * @param branchName branch to be deleted + * @throws IOException if for some reason VCS files cannot be read or written + * @throws DeleteActiveBranchException if branchName is active branch + * @throws VCSFilesCorruptedException if VCS files are absent or corrupted + * @throws BranchNotFoundException if branchName does not exist + */ public void deleteBranch(String branchName) throws BranchNotFoundException, IOException, + VCSFilesCorruptedException, DeleteActiveBranchException { if (head.equals(Branches.get(branchName))) { - throw new BranchNotFoundException(branchName); + throw new DeleteActiveBranchException(branchName); } Branches.delete(branchName); } + /** + * Creates new commit from all staged files. + * @param commitMessage a commit message + * @throws NothingToCommitException if there no files staged for commit + * @throws IOException if for some reason VCS files cannot be read or written + */ public void commit(String commitMessage) throws NothingToCommitException, EmptyCommitMessageException, IOException { if (commitMessage.isEmpty()) { throw new EmptyCommitMessageException(); @@ -83,6 +137,11 @@ public void commit(String commitMessage) throws NothingToCommitException, EmptyC staged.clear(); head = head.addCommitAfter(commit); } + /** + * Returns list of all commits in the current branch. + * @return list of all commits in the current branch + * @throws IOException if for some reason VCS files cannot be read or written + */ public List log() throws IOException, ClassNotFoundException { CommitRef currentCommitRef = head; List result = new ArrayList<>(); @@ -94,6 +153,16 @@ public List log() throws IOException, ClassNotFoundException { Collections.reverse(result); return result; } + /** + * Merges branchName into current branch. + * + * Conflicting file contents are merged with separator. + * @param branchName name of branch to be merge into current + * @return list of conflicting paths + * @throws BranchNotFoundException if there's no branch with the specified name + * @throws VCSFilesCorruptedException if one of .vcs/ files is absent or contains corrupted data + * @throws IOException if for some reason VCS files cannot be read or written + */ public List merge(String branchName) throws MergeWhenStagedNotEmptyException, VCSFilesCorruptedException, BranchNotFoundException, IOException, ClassNotFoundException { if (!staged.empty()) { @@ -104,12 +173,15 @@ public List merge(String branchName) throws MergeWhenStagedNotEmptyExcep RepoState mergingState = RepoState.getFromCommit(mergingBranch.getCommit()); Map curBlobs = curState.getFiles(); List mergingBlobs = mergingState.getFiles().values().stream().collect(Collectors.toList()); + + List conflicts = new ArrayList<>(); for (ContentlessBlob newBlob : mergingBlobs) { if (curBlobs.containsKey(newBlob.getPath())) { ContentlessBlob oldBlob = curBlobs.get(newBlob.getPath()); if (!oldBlob.getSHARef().equals(newBlob.getSHARef())) { // TODO: Bad from design point of view mergeBlobs(oldBlob, newBlob); + conflicts.add(oldBlob.getPath()); } } else { @@ -117,7 +189,7 @@ public List merge(String branchName) throws MergeWhenStagedNotEmptyExcep } } - return null; + return conflicts; } private void mergeBlobs(ContentlessBlob oldBlob, ContentlessBlob newBlob) throws IOException, ClassNotFoundException { diff --git a/src/test/java/vcs/VCSTest.java b/src/test/java/vcs/VCSTest.java index e8e0290..ffee521 100644 --- a/src/test/java/vcs/VCSTest.java +++ b/src/test/java/vcs/VCSTest.java @@ -29,7 +29,6 @@ public class VCSTest { private static final Path VCS_PATH = Paths.get(".vcs/"); /* - 2016 touch a 2017 touch b 2018 touch c @@ -42,8 +41,6 @@ public class VCSTest { 2025 vcs add c 2026 vcs commit add_C 2027 vcs log - - */ @Test From 8f9f9838d615dc36f12883c5c2ac95710b431767 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 13 Apr 2017 15:35:36 +0300 Subject: [PATCH 22/22] CE fixed --- src/main/java/console/Main.java | 2 +- src/main/java/vcs/VCS.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/console/Main.java b/src/main/java/console/Main.java index fb9e2f2..4951493 100644 --- a/src/main/java/console/Main.java +++ b/src/main/java/console/Main.java @@ -48,7 +48,7 @@ public static void main(String[] args) { else { try { vcs.createBranch(args[1]); - } catch (IOException e) { + } catch (Exception e) { printException(e); } } diff --git a/src/main/java/vcs/VCS.java b/src/main/java/vcs/VCS.java index 60705b0..f489b64 100644 --- a/src/main/java/vcs/VCS.java +++ b/src/main/java/vcs/VCS.java @@ -102,7 +102,7 @@ public void add(String pathString) throws IOException { * @throws BranchAlreadyExistsException if branchName already exists */ public void createBranch(String branchName) throws IOException, BranchAlreadyExistsException { - Branches.create(branchName, head.getCommitSHA()) + Branches.create(branchName, head.getCommitSHA()); } /**