Driver class for Gitlet, a subset of the Git version-control system.
Account for validating the number of arguments and invoking package-private methods according to received commands.
The cache write back method Cache.writeBack() which enabling the persistence of Gitlet is also invoked in this class.
This class contains only static methods since Main should not be instantiated.
static final File localCWD = new File(System.getProperty("user.dir"))The current working directoryFileobject.static String currCommandA static variable that holds the current command. Used duringpullcommand.public static void main(String[] args)The main method of Gitlet.private static void assertArgsNum(String[] args, int n)Throw a GitletException if args don't have exactly n elements.private static void assertNotArgsNum(String[] args, int n)Throw a GitletException if args have exactly n elements.private static String[] getOperands(String[] args)Strip the first element of the input array and return the rest.private static void assertString(String expected, String actual)Assert twoStringare equal.
This class is used to house static methods that facilitate lazy loading and caching of persistence. This file will set up data structures for caching, load necessary objects, and write back the cache at the very end of execution. This class will never be instantiated.
This class defers all HashObject and its subclasses' logic to them.
For example, instead of deserialize and serialize objects directly,
Cache class will invoke methods from the corresponding class to do that.
On the other hand, the Cache class will do all the getxxx() methods which retrieving desired objects lazily
from the cache.
- Caching
HashObjectstatic final Map<String, HashObject> cachedHashObjectsAMapthat stores cached ID andHashObjectpairs.static Map<String, HashObject> cachedRemoteHashObjectsThe cache for the remote repository.private static HashObject getHashObject(String id)Lazy loading and caching of HashObjects. Beingprivatebecause aHashObjectwill never be requested asHashObject(asCommitorTreeorBlobinstead). Special case: returnnullif requesting a commit withnullor"".static Commit getCommit(String id)A method that lazy-load aCommitwithidutilizinggetHashObject(String id).static Tree getTree(String id)A method that lazy-load aTreewithidutilizinggetHashObject(String id).static Blob getBlob(String id)A method that lazy-load aBlobwithidutilizinggetHashObject(String id).static Commit getLatestCommit()Get theCommitobject of the latest commit utilizinggetCommit(String id).static final Set<String> queuedForWriteHashObjectsNew HashObjects' IDs that are queued for writing to filesystem.static String cacheAndQueueForWriteHashObject(HashObject object)Manually cache aHashObjectby put aHashObjectinto the cache, and queue it for writing to filesystem. Return its ID.static void writeBackAllQueuedHashObject()Write back all queued-for-writingHashObjectsto filesystem. Invoked upon exit.static final Set<String> queuedForDeleteHashObjectDeprecatedHashObjects' IDs that are queued for deletion from filesystem.static void queueForDeleteHashObject(String id)Given QaHashObject's ID, queue it for deletion.static void deleteAllQueuedHashObject()Delete all queued-for-deletionHashObjects. Invoked upon exit.
- Caching Branches
static final Map<String, String> cachedBranchesAMapthat stores cached branch name and commit ID pairs.static Map<String, String> cachedRemoteBranchesThe cache for the remote repository.static String getBranch(String branchName)Lazy loading and caching of branches.static String getLatestCommitID()A method that lazy-load the ID of the latest commit bygetBranch(getHEAD()).static void cacheBranch(String branchName, String commitID)Manually cache aBranchby putting abranchName-commitIDpair into the cache.static void wipeBranch(String branchName)Manually wipe the pointer of a designated branch.static void writeBackAllBranches()Write back (update) all branches to filesystem. Invoked upon exit. If a branch's pointer is wiped out, delete the branch file in the filesystem. Special case: ignore branch with empty name.
- Caching
HEADstatic String cachedHEADAStringthat stores cachedHEAD, the current branch's name.static String cachedRemoteHEADThe cache for the remote repository.static String getHEAD()Lazy loading and caching ofHEAD.static void cacheHEAD(String branchName)Manually cache theHEADby assigning thecachedHEADto a givenbranchName.static void writeBackHEAD()Write back (update) theHEADfile. Invoked upon exit.
- Caching
STAGE(Stage ID)static String cachedStageIDAStringthat stores cachedSTAGE, the ID of the current staging area.static String cachedRemoteStageIDThe cache for the remote repository.static String getStageID()Lazy loading and caching of STAGE (the ID of the saved staging area). Notice: this DOES NOT point to the current staging area after the staging area is modified and before write back.static void cacheStageID(String newStageID)Manually cache theSTAGEby assigning thecachedStageIDto a givenstageID.static void writeBackStageID()Write back STAGE file. Invoked upon exit.
- Caching the Stage Area
static Tree cachedStageATreethat stores cached staging area.static Tree cachedRemoteStageThe cache for the remote repository.static Tree getStage()Get theTreeobject representing the staging area utilizinggetTree(getStageID()).static void cacheStage(Tree stage)Queue the previous staging area for deletion and manually cache the passed-in Stage. Special case: queue the previous staging area for deletion only if there is a commit, and the previous staging area is different from the Tree of the latest commit, and the previous staging area is not empty.
- MISC
static void writeBack()Write back all caches. Invoked upon exit.static void cleanCache()Reset all caches. Used for testing proposes.private static boolean inRemoteRepo()Returntrueif currently operating on the remote repository.
A class houses static methods related to the whole repository. This class will handle all actual Gitlet commands by invoking methods in other classes correctly. It also sets up persistence and do additional error checking.
- Static Variables
static File CWDThe Current Working Directory. A package-private static variable.static File GITLET_DIRThe.gitletdirectory, where all the state of the repository will be stored. Package-private.static File HEADThe.gitlet/HEADfile. This file stores the name of the active branch.static File STAGEThe.gitlet/STAGEfile, where the ID of the current staging area is stored.static File ALL_COMMITS_IDThe.gitlet/allCommitsIDfile, which is a serializedTreethat holds all the IDs of existing commits.static File OBJECTS_DIRThe.gilet/objectsdirectory. This is the object database where allHashObjectlive.static File BRANCHES_DIRThe.gitlet/branchesdirectory. Each branch is stored as a file under this directory.static void assignStaticVariables(File cwd)Assign the above static variables according to the givenCWD. This is useful dealing with local and remote repositories. The current working directory is passed in asCWDfor default, but the remote repository directory will be passed in when manipulating it.
initcommandpublic static void init()The method which handles theinitcommand. Implementation details in the Algorithms section.static void setUpPersistence()A helper method ofinitcommand, set up the persistence directories. Implementation details in the Algorithms section. This method also checks if there is an existing.gitletdirectory and abort the execution if so.
addcommandpublic static void add(String fileName)Execute the add command by adding a copy of the file as it currently exists to the staging area.
commitcommandpublic static void commit(String message)Execute the commit command.
rmcommandpublic static void rm(String fileName)Execute the rm command. Implementation details in the Algorithms section.
logcommandpublic static void log()Execute the log command. Implementation details in the Algorithms section.private static void log(String CommitID)Print log information recursively. Starting from the commit with the given commit ID, to the initial commit.
global-logcommandpublic static void globalLog()Print log information about all commits ever made. Implementation details in the Algorithms section.
findcommandprivate static final List<String> foundCommitIDA list of commit IDs that have the designated commit message.public static void find(String commitMessage)Execute thefindcommand. Implementation details in the Algorithms section.private static void findCheck(String CommitID, String commitMessage)Check if the designated commit has the designated commit message.
statuscommandpublic static void status()Execute the status command. Implementation details in the Algorithms section.- "Modifications Not Staged For Commit"
private static void modificationStatus()Print the "Modifications Not Staged For Commit" status.private static List<String> modifiedNotStagedFiles()A private helper method that construct a list of "modified but not staged" files. Implementation details in the Algorithms section.private static Set<String> modifiedStatusFocusFiles()Return a stringSetthat contains all file names that should be checked (file names in theCWDor the Stage or tracked by the head Commit).private static boolean modifiedNotStagedFiles1(String fileName)Returntrueif a file is tracked in the current commit, changed in the working directory, but not staged (modified).private static boolean modifiedNotStagedFiles2(String fileName)Returntrueif a file is staged for addition, but with different contents than in the working directory (modified).private static boolean modifiedNotStagedFiles3(String fileName)Returntrueif a file is staged for addition, but deleted in the working directory (deleted).private static boolean modifiedNotStagedFiles4(String fileName)Returntrueif a file is not staged for removal, but tracked in the current commit and deleted from the working directory (deleted).static boolean trackedInHeadCommit(String fileName)Returntrueif a file is tracked in the head commit.static boolean changedInCWD(String fileName)Returntrueif a file is changed in theCWD(different from its version in the head commit).static boolean addDiffContent(String fileName)Returntrueif a file's version in the stage is different from the working one.static boolean notInCWD(String fileName)Returntrueif a file is not in theCWD.
- "Untracked Files"
private static void untrackedStatus()Print the "Untracked Files" status.private static List<String> untrackedFiles()Return a list of files that is untracked (neither staged for addition nor tracked by the head commit).
checkoutcommandpublic static void checkout1(String fileName)Execute checkout command usage 1 (checkout a file to the latest commit). Implementation details in the Algorithms section.public static void checkout2(String commitID, String fileName)Execute checkout command usage 2 (checkout a file to the given commit). Implementation details in the Algorithms section.public static void checkout3(String branchName)Execute checkout command usage 3 (checkout all files to the designated branch). Implementation details in the Algorithms section.static void checkoutToCommit(String commitID)A helper method that checkout to aCommit(with designated ID).private static void checkoutAllCommitFile(String commitID)A private helper method that checkout all files that aCommit(with designated ID) tracked.private static void checkoutCommitFile(Commit commit, String fileName)A private helper method that checkout a file withfileNamefrom a givenCommit.
branchcommandpublic static void branch(String branchName)Execute the branch command. Implementation details in the Algorithms section.
rm-branchcommandpublic static void rmBranch(String branchName)Execute the rm-branch command. Implementation details in the Algorithms section.
resetcommandpublic static void reset(String commitID)Execute the reset command. Implementation details in the Algorithms section. Abbreviated commit ID will be handled, and branches will always point to full IDs.
mergecommandpublic static void merge(String branchName)Execute the merge command (merge files from the given branch into the current branch). Implementation details in the Algorithms section.private static void mergeModifyCWD(Commit curr, Commit other, Map<String, Set<String>> mergeModifications)Modify files in theCWD(either use the version in the other branch, or make a conflict file) accordingly.private static void makeConflict(Set<String> files, Commit curr, Commit other)Modify all conflict files and add them to the stage.private static String makeConflictContent(String fileName, Commit curr, Commit other)Return the right content for a conflict file after merging.private static void useOther(Set<String> files, Commit other)Modify files inCWDto their versions in the other commit, and stage the change (add or rm).private static Map<String, Set<String>> mergeWillModify(Commit split, Commit curr, Commit other)Perform the checks for the merge command and return aMapof necessary modifications.private static Map<String, Set<String>> mergeLogic(Set<String> focusFiles, Commit split, Commit curr, Commit other)A private helper method that captures the logic of the merge command.private static void mergeChecks1(Commit curr, Commit other)Perform checks for the merge command.private static void fastForward(Commit other)Fast-forward the current branch to the designated commit and print information. Only called when the split commit is the same as the current commit. Special case: do not print fast-forward info when pulling.private static void mergeChecks2(Set<String> changingFiles)Perform checks for the merge command.
- misc
private static void assertGITLET()Assert theCWDcontains a.gitletdirectory.private static void overwriteCWDFile(String fileName, Blob overwriteSrc)Overwrite the file inCWDof designated file name with the content in the givenBlobobject.static void sortLexico(List<String> list)Sort a stringListin lexicographical order in place.static void deleteCWDFiles()Delete all files in theCWD.private static Set<String> CWDFilesSet()Return a Set of all files' names in theCWD.private static<T> Set<T> combineSets(Set<T>... sets)Generic method to merge (union) multiple sets in Java.static void printAndExit(String msg)Print a message and exit the execution with status0.
This class houses static methods that related to branch and HEAD. It contains methods for loading and writing branch files and the HEAD file. This class will never be instantiated since there are only static methods.
static String loadBranch(String branchName)Load a branch file from filesystem with designated name. Returnnullif the branch name is""(nothing) or there is no branch. Invoked by the Cache class.static boolean existBranch(String branchName)Returntrueif a branch exists.static List<String> loadAllBranches()Load all branch files from the filesystem. Return aListcontains all commit IDs that are pointed by a branch.static void branchStatus()Print the "Branches" status. Implementation details in the Algorithms section.static void writeBranch(String branchName)Get a branch's information from cache and write it back to filesystem. Invoked by the Cache class.static void deleteBranch(String branchName)Delete the designated branch in the filesystem. Invoked by the Cache class.static void mkNewBranch(String branchName, String commitID)Make a new branch with designated name at the latest commit by caching it manually.static void moveCurrBranch(String commitID)Make the current branch pointing to a designated commit.static void moveBranch(String branchName, String commitID)Move the designated branch to point to a commit with designated ID.static String loadHEAD()Load theHEADfile and return the current branch's name. Invoked by the Cache class.static void writeHEAD()Get theHEADfrom cache and write it back to filesystem. Invoked by the Cache class.static void moveHEAD(String branchName)Make theHEADpointing to a designated branch.private static File branchFile(String branchName)Get theFileobject of a branch with designated name.private static List<String> allBranches()Return aListof all branches' names. Support fetched remote branches.
This class houses static methods that related to Stage (the staging area).
It contains methods for loading and writing the STAGE file, as well as making a new staging area.
This class will never be instantiated since there are only static methods.
static String loadStageID()Return the ID of the current staging area (aTreeobject). Invoked by the Cache class.static void putInStage(String fileName, String BlobID)Copy the staging area and add afileName-BlobIDpair. Mark the previous staging areaTreefor deletion. This function should only be invoked once per run.static void removeFromStage(String fileName)Copy the staging area and remove the entry with a specificfileName(if exists) from it. Mark the previous staging areaTreefor deletion. This function should only be invoked once per run.static void writeStageID()Write the stage ID in cache back to filesystem. Invoked by the Cache class.static void mkNewStage()Make a new stage (aTreeobject) and cache its ID.static void addToStage(String fileName)Add a file to the current staging area. Implementation details in the Algorithms section.static void stageStatus()Print the status information related with the staging area.private static List<String> stagedFiles()Return a sorted List of file names in the current staging area.private static void stagedFilesStatus()Print the "Staged Files" status. Implementation details in the Algorithms section.private static void removedFilesStatus()Print the "Removed Files" status. Implementation details in the Algorithms section.static boolean isStagedForAdd(String fileName)Returntrueif a designated file is staged for addition.static boolean isStagedForRemoval(String fileName)Returntrueif a designated file is staged for removal.
This class represents a HashObject that will be serialized within .gitlet/objects, named after its SHA-1.
HashObject is an implementation of Serializable and Dumpable.
This file has helper methods that will return the SHA-1 (ID) of a HashObject.
As well as static methods that returning the HashObject object corresponding to its ID (SHA-1),
and write to or delete from the object database a HashObject.
private static final Boolean OPTIMIZATIONAllow you to switch between flatobjectsdirectory (easy to debug) and HashTableobjectsdirectory (better performance). Notice: this should be consistence for a single Gitlet repository.String id()Get the SHA-1 ofTHIS.public void dump()Print the type of this object on System.out.static HashObject loadHashObject(String id)Load a type object with its ID. Special case: returnnullif told to load an object that does not exist.static void writeCachedHashObject(String id)Write a cached HashObject with ID in cachedObjects to filesystem.static void deleteHashObject(String id)Delete a HashObject from filesystem.static private File optimizedObjectIDFolder(String id)Helper method that returns the housing directory of aHashObjectwith the given ID. Used in the optimized object database.static private File optimizedObjectIDFile(String id)Helper method that returns the file of aHashObjectwith the given ID. Used in the optimized object database.static private File optimizedObjectAbbrevIDFile(String id)Helper method that return the file of aHashObjectwith the given abbreviated ID. Used in the optimized object database.
Despite HashObject should be instantiated very often, it has no constructor method(s).
Any HashObject is designed to be instantiated as a more specific subclass, namely Commit, Tree, or Blob.
This class represents a Commit in Gitlet, it extends the HashObject class.
Each instance of Commit have several instance variables such as commit message and time stamp.
This file also has helper methods that unlocks instance variable
as well as static method that carry out the procedure to make a new commit.
private final String _messageThe commit message.private final String _parentCommitIDThe ID of the parent commit.private final String _parentMergeCommitIDThe ID of the second parent (if any).private final Date _timeStampA time stamp of the commit been made.private final String _treeIDThe ID of the associatedTreeobject.private Commit(String parentCommitID, String message, String treeRef)The constructor ofCommitclass. This method isprivatebecause no "naked" instantiation ofCommitis allowed outside theCommitclass. Additionally, the time stamp is set to1970.01.01 00:00:00for initial commit.private Commit(String firstCommitID, String secondCommitID, String message, String treeRef)Constructor for merge commits.public String toString()Content-addressable overridingtoString()method.String logString()Return the log information of thisCommit.public void dump()Print information of thisCommitonSystem.out.String getMessage()Get the message of thisCommit.String getParentCommitID()Get the ID of the parent commit.String getParentMergeCommitID()Get the ID of the second parent commit.Commit getParentCommit()Get theCommitobject of the parent commit.Commit getParentMergeCommit()Get theCommitobject ot the second parent commit.String getCommitTreeID()Get the ID of the associatingTreeof this commit.Tree getCommitTree()Get the associatingTreeof this commit.String getBlobID(String fileName)Get the ID of theBlobof a designated file name in this commit.String getFileContent(String fileName)Return the content of a designated file name in this commit. Special case: return an emptyStringif there is no correspondingBlob.Boolean trackedFile(String fileName)Return whether thisCommitcontains a file withfileName.Set<String> trackedFiles()Return a stringSetof tracked files of this commit.static void mkCommit(String message)Factory method. Make a newCommit. Implementation details in the Algorithm section.static void mkMergeCommit(String givenBranchName, Boolean conflicted)Factory method. Make a new merge Commit.static Commit lca(Commit commit1, Commit commit2)Return the latest common ancestor (LCA) of twoCommits.static Set<String> ancestors(Commit commit)Recursively collect and return aSetof all ancestors' ID of the givenCommitobject, including merge parents. Special case: return an emptySetif the givenCommitisnull.static void recordCommitID(String commitID)Record a new commit's ID to the.gitlet/allCommitsIDfile.static Tree getAllCommitsID()Return aTreeobject that captures all IDs of commits ever made.
Represent a Gitlet Tree, corresponding to UNIX directory entries.
Implements Iterable<String>, extends HashObject.
An instance of Tree object contains a TreeMap as instance variable, which has zero or more entries.
Each of these entries is a fileName - BlobID pair.
This class also contains Tree related static methods.
private final Map<String, String> _structureTheTreeMapthat storesfileName-blobIDpairs.Tree()The constructor ofTreeclass.Tree(Tree another)A constructor that deep-copy the passed-inTree.public String toString()Content-addressable overridingtoString()method.public void dump()Print information of thisTreeonSystem.out.boolean isEmpty()Return whether thisTreeis empty.boolean containsFile(String fileName)Returntrueif aTreecontains a file withfileName.List<String> trackedFiles()Return the sorted list of file names in thisTreefollowing a Java string-comparison order.void putBlobID(String fileName, String blobRef)Record afileName-blobIDpairs.void removeBlobID(String fileName)Remove an entry withfileNameas the key from thisTree.String getBlobID(String fileName)Return the ID of aBlobaccording to a givenfileName(if exists).Blob getBlob(String fileName)Return aBlobaccording to a givenfileName(if exist).public Iterator<String> iterator()Returns anIteratorof thisTree, namely thekeySet()of itsTreeMap.void updateWith(Tree updater)Update thisTreewith the entries in the givenTree. Special case: remove the corresponding pair fromthisif the value to a key in the updater isnull.static String mkNewEmptyTree()Factory method. Creates an emptyTree, cache it and return its ID.static Tree getLatestCommitTree()Factory method. Return the copy of theTreeof the latest commit if exists. Special case: returnnullif there is no latest commit.static String mkCommitTree()Factory method. Return aTreethat capture theTreefrom the latest commit as well as current addition and removal status. Implementation details in the Algorithm section. Special cases: make a new empty tree if there is noTreein the latest commit.private static Tree copyLatestCommitTree()Factory method. Return a deep-copy of theTreein the latest commit.static Teww CWDFiles()Return a temporaryTreethat capture information of files inCWD.
Represent a Gitlet Blob, corresponding to UNIX files.
Extends HashObject.
Blob has one instance variable _content, which holding the content of a file.
This variable enables a Blob to represent a version of such file.
This class also has Blob related static methods.
private final String _contentThe instance variable that hold the content of a version of a file.Blob(String content)The private constructor ofBlob. No "naked" instantiation ofBlobis allowed.String getContent()Unlocks the content of aBlob.public String toString()Content-addressable overridingtoString()method.public void dump()Print information of thisTreeonSystem.out.static String mkBlob(String fileName)Factory method. Make a newBlobwith a designated file. Cache it and queue it for writing to filesystem. Special case: adding a file that not exists in theCWDmeans adding it for removal.static String currFileID(String fileName)Return theIDof a designated file'sBlobwithout cache or saving aBlob.
Represent a remote Gitlet repository and accommodating remote commands related methods.
- Non-static methods
File _remoteWDStores the working directory of this remote repository as aFileobject.private Remote(File remoteGitlet)Construct a remote repository representation.private void remoteRunner()Change theCWDin the Gitlet running environment to the remote repository's working directory. Must call before commanding the remote repository.private void localRunner()Change theCWDin Gitlet running environment to the local repository's working directory.- Methods that simply calling
remoteRunner()andlocalRunner()before and after calling the method with the same signature from other classes.private String getHEAD()private Commit getLatestCommit()private String getBranch(String branchName)private Commit getCommit(String id)private String cacheAndQueueForWriteHashObject(HashObject object)private void writeBack()private Set<String> commitAncestors(Commit commit)private void moveHEAD(String branchName)private void moveCurrBranch(String commitID)private void checkoutToCommit(String commitID)private boolean existBranch(String branchName)private void mkNewBranch(String branchName)private void mkNewStage()private Tree getCommitTree(Commit commit)private Blob getBlob(String blobID)private void recordCommitID(String commitID)
- Static methods
add-remotecommandpublic static void addRemote(String remoteName, String path)Execute the add-remote command by creating a reference to the remote repository.private static void writeRemote(File remoteFile, String path)Write a remote repository reference.static File readRemote(String remoteName)Get theFilereferencing the remote (in the local repository).
rm-remotecommandstatic File getRemoteGitlet(String remoteName)Get theFileof the remote.gitletdirectory.
pushcommandpublic static void push(String remoteName, String remoteBranchName)Executing thepushcommand.private static void pushReset(Remote remote, String commitID, String remoteBranchName)Fast-forward the remote repository.private static Set<String> commitsToPush(Commit localC, Commit remoteC, Remote remote)Return aSetofStringcontaining the IDs of commits that should be pushed to the remote repo.private static void pushCommits(Set<String> commitIDs, Remote remote)Push allCommitwith the designated ID in theSet, and its associatingTreeandBlobto the remote repository.private static void pushCommit(Commit commit, Remote remote)Push a singleCommitand its associatingTreeandBlobto the remote repository.
fetchcommandpublic static void fetch(String remoteName, String remoteBranchName)Execute thefetchcommand. Implementation details in the Algorithms section.private static Set<String> commitsToFetch(Commit localC, Commit remoteC, Remote remote)Return aSetof String containing the IDs of commits that should be fetched from the remote repo.private static void fetchCommits(Remote remote, Set<String> commitIDs)Fetch commits that their IDs in theSetto the local repo.private static void fetchCommit(Remote remote, String commitID)Fetch a commit to the local repo.
pullcommandpublic static void pull(String remoteName, String remoteBranchName)Execute thepullcommand. Implementation details in the Algorithms section.
This class contains JUnit tests and some helper methods for Gitlet.
initcommandpublic void initCommandSanityTest()Sanity test for init command.
addcommandpublic void addCommandSanityTest()Sanity test for add command.public void addCommandTwiceTest()Test using add command twice.
commitcommandpublic void commitSanityTest()Sanity test for commit command.public void dummyCommitTest()Dummy commit test (commit without adding anything).public void commitAndAddTest()Add a file, make a commit, and add another file.public void addAndRestoreTest()Make a commit, change the file and add, then change back and add. The staging area should be empty.
rmcommandpublic void rmUnstageTest()The rm command should unstage the added file.public void rmCommitTest()Add a file, commit, and rm it, commit again. The latest commit should have an empty commit tree. The file in theCWDshould be deleted.
logcommandpublic void logSanityTest()Sanity test for log command. Init and log.public void simpleLogTest()Simple test for log command. Init, commit, and log.public void normalLogTest()Normal test for log command. Init, commit, commit, and log.
global-logcommandpublic void globalLogSanityTest()Sanity test for global-log command.public void globalLogBranchTest()Test for global-log command with branching. Need implementation.
findcommandpublic void findSanityTest()Sanity test for find command.public void findBranchTest()Test for find command with branching. Need implementation.
statuscommandpublic void statusBasicTest()Basic test for status command.public void statusModification3Test()Test extra functions ("Modification Not Staged For Commit") condition 3 of status command.public void statusModification4Test()Test extra functions ("Modification Not Staged For Commit") condition 4 of status command.public void statusUntrackedTest()Test extra functions ("Untracked Files") of status command.
checkoutcommandpublic void checkoutHeadFileSanityTest()Sanity test for checkout usage 1 (checkout a file to the latest commit).public void checkoutCommitFileSanityTest()Sanity test for checkout usage 2 (checkout a file to the given commit).public void checkoutBranchSanityTest()Sanity test for checkout usage 3 (checkout to a branch).
branchcommandpublic void branchSanityTest()Sanity test for branch command.
rm-branchcommandpublic void rmBranchSanityTest()Sanity test for rm-branch command.
resetcommandpublic void resetSanityTest()Sanity test for reset command.
mergecommandpublic void lcaTest()Test the lca method.public void mergeSanityTest()A sanity test for the merge command.public void mergeConflictTest()Test merging two branches with conflict.public void mergeTest()A hard (and comprehensive) test for the merge command.
add-remotecommandpublic void addRemoteTest()A sanity test foradd-remote.
pushcommandpublic void pushTest()A sanity test for add-remote command.
fetchcommandpublic void fetchTest()A sanity test for fetch command.
pullcommandpublic void pullTest()A sanity test for pull command.
- Auto grader debug tests
public void test20_status_after_commit()public void test24_global_log_prev()public void test29_bad_checkouts_err()public void test35_merge_rm_conflicts()public void test36a_merge_parent2()
- misc
static final File CWDThe local repository's working directory.private static void GitletExecute(String... command)Execute commands with Gitlet and clean the cache after execution. Special case: make sure there is no.gitletdirectory before the init command. Implemented for testing purposes.private static void writeTestFile(String fileName, String content)Write content into a designated file name. Overwriting or creating file as needed.private static void deleteTestFile(String fileName)Delete the file with the designated name.private static String readTestFile(String fileName)Read the designated file as String and return it.private static void deleteDirectory(File directoryToBeDeleted)Delete a directory recursively.private static void writeAndAdd(String fileName, String content)Write a test file with the designated file name and content, then add it to the stage.private static void assertFile(String fileName, String content)Assert a designated file has the designated content.
Lazy Loading: Only retrieve information from your file system when you need it, not all at once in the beginning.
Caching: Once you load something from your file system, save it in your Java program, so you don’t need to load it again. (E.g. as an attribute or an entry in a Map.)
Writing back: If you cached something and then modified it, make sure at the end of your Java program, you write the changes to your file system.
In this implementation of Gitlet, I used a standalone java Class Cache.java to accommodate code for lazy-loading and
caching.
During lazy loading, the load****() method for specific Class is invoked to retrieve an instance of that Class by
specifying the object's ID. Additionally, Commit, Tree, and Blob don't have standalone load****() methods
because they are subclasses of HashObject.
After loading, the cached object is saved into the corresponding static variable.
Namely, a TreeMap cachedHashObjects will store ID to HashObject pairs,
another TreeMap cachedBranches will store branchName to commitID pairs,
a String cachedHEAD will store the content of .gitlet/HEAD (the current branch's name),
and a String cachedStageID will store the content of .gitlet/STAGE (the ID of the staging area Tree).
Additionally, there is List queuedForWriteHashObjects and queuedForDeleteHashObject that hold IDs that should be
(re)write to or delete from the filesystem. These Lists are updated along the course of execution by
cacheAndQueueForWriteHashObject(HashObject object) and queueForDeleteHashObject(String id).
Note that a HashObject will never be modified after its creation.
Therefore, no modification of existing HashObjects will be carried out
thus there is no such queuedForModifyHashObjects data structure.
At the very end of execution, caches will be written back to filesystem.
Entries in cachedHashObjects, will be written to or delete from filesystem based on the IDs contained in
queuedForWriteHashObjects and queuedForDeleteHashObject.
Additionally, cachedBranches, cachedHEAD, cachedStageID will be rewritten anyway since the size of related
persistence are trivial for the most time.
Every HashObject need to be serialized and saved in filesystem, thus a unique file name (ID) is indispensable.
We use SHA-1 (Secure Hashcode Algorithm 1) hashcode as a content-addressable ID of every HashOject.
In order to achieve content-addressability, the following two characteristics is necessary:
- Different
HashObjects with identical contents should have the same ID. - A
HashObject's ID should change after it is modified in terms of its contents.
To accomplish such requirements, ID of a HashObject is generated from applying SHA-1 on its string representation.
And subclasses of the HashObject class overrides the default toString() method to make it content-addressable.
If the static variable OPTIMIZATION in HashObject class is set to true,
Gitlet will construct a HashTable-liked structure in the .gitlet/objects directory.
That is, all HashObject (with a 40-character ID) will be stored under the .gitlet/objects/xx directory
and named after xxx,
where xx is the leading two characters of its ID and xxx is the left 38 characters.
The point of this optimization is speeding up retrieving Commit
when the user abbreviate commit ID with a unique prefix.
The real Git is also utilizing this technique.
When the user provide an abbreviated commit ID, Gitlet will go to the corresponding .gitlet/objects/xx directory
and iterate through a list of file names in that directory in order to figure out the comprehensive commit ID.
On the other hand, if OPTIMIZATION is set to false,
all HashObject will be stored flatly under the .gitlet/objects directory and named after the corresponding ID.
This set up is might be more convenient when digging into the object database for debugging purposes.
Due to performance concerns, referring commits with abbreviated IDs is not allowed when OPTIMIZATION is set to false.
- Set up the repository
- Create an initial commit
- Set up persistence directories
- Make the default branch "master" which is pointing null for now (no pun intended)
- Make the HEAD pointing to the master branch
- Make a new staging area
- Initialize and serialize a
Treeto.gitlet/allCommitsID
- Get the ID of the latest commit
- Make a commit
Treeby copying from the latest commit, and update it with the staging area - Construct a new
Commitobject with theprivateconstructor - Cache the new Commit and queue it for write back
- Move the current branch pointing the new commit
- Make a new staging area
- Record the new commit's ID
A commit Tree is a Tree that every commit uses to record the associated file names and file versions (Blob).
- Get a copy of the
Treeof the latest commit - Get the staging area
Tree - Update that copy with the staging area
(Special case: remove the corresponding pair from that copy if the value to a key in the staging area is
null, i.e., staged for removal) - Cache it and queue it for writing
- Get the file as its current version, cache it as a Blob (don't queue for write back yet)
- Get the version of the designated file from the latest commit
- Special case: If the current version of the file is identical to the version in the latest commit (by comparing IDs), do not stage it, and remove it from the staging area if it is already there. End the execution.
- Modify cached staging area
- Abort if the file is neither staged nor tracked by the head commit.
- If the file is currently staged for addition, unstage it.
- If the file is tracked in the current commit, stage it for removal and remove it from the
CWD.
When it comes to the design decision of representing "staged for removal",
the chosen solution is to treat pairs in the staging tree with "" (an empty String) value as staged for removal.
That is, when a file is staged for removal:
- It is deleted from the
CWDif the user haven't done that. - It is "added" to the staging area.
Given the fact that there is no such file in the
CWD, a {fileName-""} pair will be written into the staging area. - When making a commit
Tree, staged for removal file will be handled and the new commitTreewill not include the staged-for-removal files.
In this manner, problems with naive approaches (such as introduce a "staging area for removal" Tree)
is avoided, and the amount of codes to implement the rm command is trivial.
- Get the ID of the latest commit
- Print log information starting from that commit to the initial commit recursively
- Get the Commit object with the given CommitID
- Print its log information
- Recursively print its ascendants' log information
- Get the allCommitsID
Treewhich holds all commits' IDs. - Print log information for each of the IDs.
This command has similar algorithm with the global-log command.
Both of these commands cover all commits ever made by the same manner.
- Get the allCommitsID
Treewhich holds all commits' IDs. - Check each commit whether it has the designated commit message.
The status information is consist of the following five parts.
- "Branches"
- Get a list of all branches by reading the filenames in the
.gitlet/branchesdirectory. - Sort the list in lexicographical order.
- Print the header and all branches, print an asterisk before printing the current branch.
- Get a list of all branches by reading the filenames in the
- "Staged Files" and "Removed Files"
- Get the current staging area, and get a lexicographical sorted list of filenames it currently holds.
- If a
filenamehas an empty correspondingBlobIDin the staging area, print it under "Removed Files"; if a filename has a valid correspondingBlobIDin the staging area, print it under "Staged Files".
- "Modifications Not Staged For Commit"
- Get a Set of all file names that should be checked (file names in the
CWD, the staging area, and the head commit). - Check each file name and fill a List for "modified but not staged files".
Conditions are described below.
- Record the file name concatenates
(modified)if it satisfies condition 1 or 2. - Record the file name concatenates
(deleted)if it satisfies condition 3 or 4. A file name is either marked as modified or marked as deleted or not marked.
- Record the file name concatenates
- Print the List.
- Get a Set of all file names that should be checked (file names in the
- "Untracked Files"
- Get a list of all untracked files. A file is untracked if it is neither staged for addition nor tracked by the head commit.
- Print the file names.
Conditions for "Modifications Not Staged For Commit":
- Tracked in the current commit, changed in the working directory, but not staged.
- Staged for addition, but with different contents than in the working directory.
- Staged for addition, but deleted in the working directory.
- Not staged for removal, but tracked in the current commit and deleted from the working directory.
- Get the information of files in the
CWDas aTreeobject. - Get the head
Commitobject. - Iterate through the file names in the
CWD, add it to a list of untracked files if it:- is not staged for addition (not contained in the staging area or its corresponding
BlobID is empty) - is not tracked by the head commit
- is not staged for addition (not contained in the staging area or its corresponding
- Sort the untracked files list in lexicographical order
- Get the ID of the latest commit
- Invoke
checkout2(String commitID, String fileName)method with the ID of the latest commit.
- Get the
Commitobject with the designated commit ID - Get the designated file's
Blobobject form that commit - Overwrite the file with that name in the
CWD
- Perform checks: Gitlet will abort if no branch with the given name exists, or that branch is the current branch, or a working file is untracked.
- Move the
HEADto that branch. - Checkout to the commit that the branch is pointing to.
- Delete all files in the
CWD. - Checkout all files tracked by that commit.
- Clean the staging area.
Creating new branches is carried out when branch or init command is given.
When creating new branches, the operation under the hood is no more than writing a branchName - CommitID pair
into the cachedBranches which is then written back to the filesystem upon exit.
The CommitID assigned to the new branch is always the latest commit (head commit) if there is one.
For the default "master" branch which is created right before the initial commit,
its corresponding is null at the very first (but pointed to the initial commit after the initial commit is created).
This command delete the branch with the given name. It does not delete any commits under that branch.
- Abort if the designated branch does not exist
- Abort if the designated branch is the current branch
- Wipe the branch's pointer in the cache and delete the branch file upon exit
- Perform the checks: the commit with the designated ID exists, and there is no working untracked file.
- Checkout to the designated commit.
- Move the current branch to that commit (The biggest difference between
resetandcheckout [branch name]command).
The merge command is one of the most complicated commands in Gitlet.
Therefore, the execution of this command is divided to multiple helper methods.
Generally, the following procedure is followed to execute this command.
- Get the latest
Commitobject of the current branch, the given branch, and the common ancestors (split commit). - Perform checks.
- Calculate which files will be changed in what manners, and perform checks.
- Modify the
CWDfollowing the result from step 2, staging for addition or removal as we go. - Make a merge commit.
- Get a
Setof all ancestors' ID of a commit. This is accomplished by recursively collect all parent commit(s)' ID(s) and their parent(s)' ID(s), like breadth-first-search. Note that a merge commit has two parents, both will be collected as its ancestor. - Starting from the other commit, breadth-first-search the first commit that its ID is in the
Set.
- Get a Set of file names that should be checked by combining sets of file names tracked by the split, current, and the other branch's head commits.
- Construct a Map of String to Set of String
Map<String, Set<String>>, where"other"is mapped to a Set of files' file names that should use the version in the other (given) branch's head commit, and"conflict"is mapped to a Set of files' file names that conflict after merging. - For file names in the Set need to be checked,
- Add to
"other"'s value Set if the version of such file in the split commit is the same of it in the current commit (has the same content or both not exists). - Add to
"conflictt"'s value Set if the version in the split commit, the current commit, and the other branch's head commit is all different from each other.
- Add to
- Abort merging if a branch with the given name does not exist.
- Abort merging if there are staged additions or removals present.
- Abort merging if attempting to merge a branch with itself.
- Exit if the split point is the same commit as the given branch's head commit. The merge is complete.
- Fast-forward and exit if the split point is the same commit as the current branch.
- Abort merging if an untracked file in the current commit would be overwritten or deleted by the merge. This is checked after which files will be changed is determined.
- Abort merging if there are unstaged changes to file that would be changed by the merge. This is checked after which files will be changed is determined.
The Map recoding necessary modification is disassembled into two Sets
(one holding file names that should be changed to their version in the given branch's head commit,
and one holding file names that resulting in conflicts).
Two helper methods are utilized to carry out the modifications.
The changes will be staged immediately.
Making a merge Commit is not so different from making a normal commit,
despite the new commit will have two parent commit IDs,
the first is the current commit ID and the second is the ID of the given branch's head commit.
Lastly, Gitlet will print a message to the console if any conflict is made.
Commands related with remote repository requires reuse of existing code for the local repository.
To fulfill this demand, static variables (such as CWD, or GITLET_DIR)
in the Repository class are no longer final and will be assigned each time.
That is, when executing commands on the local repository, static variables will be assigned normally;
while executing commands on the remote repository, these static variables will be assigned according to the remote
working directory, thus reuse the existing code to manipulating the remote repository.
This command only involved manipulation to the local repository (creating path reference to the remote repository).
- Get the
Commitobject of the local repository's head commit and the front commit of the remote repository's given branch. Create a new branch at the remote repository if such branch does not exist. - Calculate the commits need to be pushed by contracting the ancestors of the two commits.
- Push the
Commits (and their associatingTreeandBlob) to the remote repository. Specifically, using the caching and writing back mechanisms developed for the local repository. Commit's IDs are added to the remoteallCommitsIDfile upon pushing. - Reset the remote repository (change it to the given branch and fast-forward that branch).
- Get the
Commitobject of the local repository's fetched branch's head commit and the head commit of the remote repository's designated branch. - Calculate the commits need to be fetched by contracting the ancestors of the two commits.
- Fetch the
Commits (and their associatingTreeandBlob) to the local repository. Specifically, using the caching and writing back mechanisms of the local repository. Commit's IDs are added to the localallCommitsIDfile upon fetching.
This command is executed simply fetch the designated remote branch using the fetch command,
and then merge the fetched branch into the current branch using the merge command.
The directory structure looks like this:
CWD <==== Whatever the current working directory is
└── .gitlet <==== All persistant data is stored within here
├── HEAD <==== The name of the current branch
├── STAGE <==== A hash pointer to the serialized staging area Tree
├── allCommitsID <==== A serialized Tree that contains all commits' IDs
├── objects <==== The object database (all HashObject lives here)
│ ├── d9 <==== Saves all HashObject with ID stating with "d9"
│ │ ├── 91f6cad12cc1bfb64791e893fa01ac5bf8358e <==== A saved HashObject, named after its ID without the first two letters
│ │ └── ...
│ └── ...
├── branches <==== Store all the branch references
│ ├── R1 <==== Directory of branches fetched from a remote repository
│ │ ├── master <==== A branch fetched from a remote, will be displayed as "R1/master"
│ │ └── ...
│ ├── master <==== The default branch. Contains a hash pointer to a commit
│ └── ...
└── remotes <==== Store all the remote references
├── R1 <==== A remote references in String, e.g. [remote directory]/.gitlet
└── ...
The Repository.setUpPersistence() method will set up all persistence. It will:
- Abort if there is already a Gitlet version-control system in the current directory
- Create
.gitlet/branchesand.gitlet/objectsfolders - Create
.gitlet/HEAD,.gitlet/STAGE, and.gitlet/allCommitsID
After setting up all persistence, the init command will do its jobs.
Finally, all changes that should be persistent (including branching, HEAD, new commit, and Tree for that commit)
will be written to filesystem automatically by the Cache.writeBack method invoked in Main.main(String[] args).
This command will modify persistence in the following two cases:
- If the current version of the added file is identical to the version in the latest commit, that file will be removed from the staging area if it is already there. (as can happen when a file is changed, added, and then changed back to its original version)
- If case 1 isn't happening and the added file is different from the version that is already in the staging area (if it exists), a new staging area containing the added file is saved to filesystem.
The commit command will modify persistence following the following rules (no pun intended):
- Save a serialized
Commitobject in the object database - Overwrite the current branch's file, make it contains the new commit's ID
- Make a new staging area and overwrite the
STAGEfile - Record the new commits' ID to
.gitlet/allCommitsID Delete the previous staging area if it is not empty, and there is a commit already (subtle bug may exist)
The rm command will change the current staging area Tree
if the designated file is added (removing from the staging area)
or exists in the head commit (staging for removal).
This command will write the current working directory, but only read persistence. An exception is that when checking out to a branch, the staging area will be cleared.
When a branch is created, a branchName - CommitID pair will be written into the cachedBranches data structure.
Upon exit, the cachedBranches will be written back to the filesystem, i.e. the persistence will be modified
according to cached information.
When a branch is removed, the corresponding file under the .gitlet/branches directory will be deleted.
This command will write the current working directory and clear the staging area.
If the merging is carried out successfully, this command will change the persistence just like the commit command.
These two commands will create/delete files in .gitlet/remotes/ directory.
These two commands will add serialized HashObject to the object database,
as well as the branch files, the .gitlet/HEAD file, and the .gitlet/allCommitsID file.