/// <summary> /// Creates a commit object that represents the current /// state of the index, writes the commit to the `objects` directory /// and points `HEAD` at the commit. /// </summary> public static string Commit(CommitOptions options) { Files.AssertInRepo(); Config.AssertNotBare(); // Write a tree object that represents the current state of the // index. var treeHash = WriteTree(); var headDesc = Refs.IsHeadDetached() ? "detached HEAD" : Refs.HeadBranchName(); // If the hash of the new tree is the same as the hash of the tree // that the `HEAD` commit points at, abort because there is // nothing new to commit. if (Refs.Hash("HEAD") != null && treeHash == Objects.TreeHash(Objects.Read(Refs.Hash("HEAD")))) { throw new Exception("# On " + headDesc + "\nnothing to commit, working directory clean"); } // Abort if the repository is in the merge state and there are // unresolved merge conflicts. string[] conflictedPaths = Index.ConflictedPaths(); if (Core.Merge.IsMergeInProgress() && conflictedPaths.Length > 0) { throw new Exception( string.Join("\n", conflictedPaths.Select(p => "U " + p)) + "\ncannot commit because you have unmerged files"); } // Otherwise, do the commit. // If the repository is in the merge state, use a pre-written // merge commit message. If the repository is not in the // merge state, use the message passed with `-m`. var m = Core.Merge.IsMergeInProgress() ? Files.Read(Path.Combine(Files.GitletPath(), "MERGE_MSG")) : options.m; // Write the new commit to the `objects` directory. var commitHash = Objects.WriteCommit(treeHash, m, Refs.CommitParentHashes()); // Point `HEAD` at new commit. UpdateRef("HEAD", commitHash); // If `MERGE_HEAD` exists, the repository was in the merge // state. Remove `MERGE_HEAD` and `MERGE_MSG`to exit the merge // state. Report that the merge is complete. if (Core.Merge.IsMergeInProgress()) { File.Delete(Path.Combine(Files.GitletPath(), "MERGE_MSG")); Refs.Rm("MERGE_HEAD"); return("Merge made by the three-way strategy"); } // Repository was not in the merge state, so just report that // the commit is complete. return("[" + headDesc + " " + commitHash + "] " + m); }
public static string Merge(string @ref) { Files.AssertInRepo(); Config.AssertNotBare(); // Get the `receiverHash`, the hash of the commit that the // current branch is on. var receiverHash = Refs.Hash("HEAD"); // Get the `giverHash`, the hash for the commit to merge into the // receiver commit. var giverHash = Refs.Hash(@ref); // Abort if head is detached. Merging into a detached head is not // supported. if (Refs.IsHeadDetached()) { throw new Exception("unsupported"); } // Abort if `ref` did not resolve to a hash, or if that hash is // not for a commit object. if (giverHash == null || Objects.Type(Objects.Read(giverHash)) != "commit") { throw new Exception(@ref + ": expected commit type"); } // Do not merge if the current branch - the receiver - already has // the giver's changes. This is the case if the receiver and // giver are the same commit, or if the giver is an ancestor of // the receiver. if (Objects.IsUpToDate(receiverHash, giverHash)) { return("Already up-to-date"); } // Get a list of files changed in the working copy. Get a list // of the files that are different in the receiver and giver. If // any files appear in both lists then abort. var paths = Diff.ChangedFilesCommitWouldOverwrite(giverHash); if (paths.Length > 0) { throw new Exception("local changes would be lost\n" + string.Join("\n", paths) + "\n"); } // If the receiver is an ancestor of the giver, a fast forward // is performed. This is possible because there is already a // commit that incorporates all of the giver's changes into the // receiver. if (Core.Merge.CanFastForward(receiverHash, giverHash)) { // Fast forwarding means making the current branch reflect the // commit that `giverHash` points at. The branch is pointed // at `giverHash`. The index is set to match the contents of // the commit that `giverHash` points at. The working copy is // set to match the contents of that commit. Core.Merge.WriteFastForwardMerge(receiverHash, giverHash); return("Fast-forward"); } // If the receiver is not an ancestor of the giver, a merge // commit must be created. // The repository is put into the merge state. The // `MERGE_HEAD` file is written and its contents set to // `giverHash`. The `MERGE_MSG` file is written and its // contents set to a boilerplate merge commit message. A // merge diff is created that will turn the contents of // receiver into the contents of giver. This contains the // path of every file that is different and whether it was // added, removed or modified, or is in conflict. Added files // are added to the index and working copy. Removed files are // removed from the index and working copy. Modified files // are modified in the index and working copy. Files that are // in conflict are written to the working copy to include the // receiver and giver versions. Both the receiver and giver // versions are written to the index. Core.Merge.WriteNonFastForwardMerge(receiverHash, giverHash, @ref); // If there are any conflicted files, a message is shown to // say that the user must sort them out before the merge can // be completed. if (Core.Merge.HasConflicts(receiverHash, giverHash)) { return("Automatic merge failed. Fix conflicts and commit the result."); } return(Gitlet.Commit(new CommitOptions())); }