From ed9e04957036c8cd6871ea71daeb46c1d1ed9717 Mon Sep 17 00:00:00 2001 From: UNV Date: Thu, 20 Nov 2025 00:40:32 +0300 Subject: [PATCH 1/5] Refactoring (part 3). --- .../main/java/git4idea/GitFileRevision.java | 348 ++++--- .../main/java/git4idea/GitLocalBranch.java | 80 +- .../main/java/git4idea/GitRevisionNumber.java | 459 +++++---- .../main/java/git4idea/GitRootConverter.java | 32 +- plugin/src/main/java/git4idea/GitUtil.java | 4 +- .../java/git4idea/branch/GitBranchUtil.java | 12 +- .../branch/GitDeleteBranchOperation.java | 6 +- .../java/git4idea/branch/GitRebaseParams.java | 145 ++- .../java/git4idea/changes/GitChangeUtils.java | 891 ++++++++--------- .../checkin/GitCheckinEnvironment.java | 23 +- .../java/git4idea/commands/GitHandler.java | 2 +- .../commands/GitHttpGuiAuthenticator.java | 460 ++++----- .../config/GitOptionsTopHitProvider.java | 3 +- .../main/java/git4idea/config/GitVersion.java | 22 +- .../git4idea/diff/GitTreeDiffProvider.java | 11 +- .../git4idea/history/GitHistoryUtils.java | 148 +-- .../java/git4idea/log/GitLogProvider.java | 143 +-- .../main/java/git4idea/log/GitRefManager.java | 909 +++++++++--------- .../git4idea/merge/GitConflictResolver.java | 2 +- .../java/git4idea/merge/GitMergeUtil.java | 28 +- .../java/git4idea/merge/GitPullDialog.java | 2 +- .../git4idea/merge/MergeChangeCollector.java | 306 +++--- .../push/GitPushNativeResultParser.java | 119 +-- .../java/git4idea/push/GitPushOperation.java | 737 +++++++------- .../java/git4idea/push/GitPushSupport.java | 338 +++---- .../java/git4idea/push/GitPushTarget.java | 328 +++---- .../main/java/git4idea/push/GitPusher.java | 117 +-- .../rebase/GitInteractiveRebaseFile.java | 138 ++- .../git4idea/rebase/GitRebaseProcess.java | 3 +- .../java/git4idea/rebase/GitRebaseSpec.java | 457 +++++---- .../remote/GitHttpAuthDataProvider.java | 9 +- .../git4idea/remote/GitRememberedInputs.java | 151 ++- .../java/git4idea/repo/GitBranchState.java | 101 +- .../git4idea/repo/GitBranchTrackInfo.java | 67 +- .../main/java/git4idea/repo/GitConfig.java | 70 +- .../java/git4idea/repo/GitConfigHelper.java | 47 +- .../main/java/git4idea/repo/GitHooksInfo.java | 68 +- .../git4idea/repo/GitModulesFileReader.java | 109 +-- .../main/java/git4idea/repo/GitRemote.java | 248 +++-- .../main/java/git4idea/repo/GitRepoInfo.java | 277 ++---- .../java/git4idea/repo/GitRepository.java | 117 ++- .../repo/GitRepositoryChangeListener.java | 5 +- .../git4idea/repo/GitRepositoryCreator.java | 37 +- .../git4idea/repo/GitRepositoryFiles.java | 637 ++++++------ .../java/git4idea/repo/GitRepositoryImpl.java | 435 ++++----- .../git4idea/repo/GitRepositoryManager.java | 6 +- .../git4idea/repo/GitRepositoryReader.java | 817 ++++++++-------- .../git4idea/repo/GitRepositoryUpdater.java | 236 +++-- .../java/git4idea/repo/GitSubmoduleInfo.java | 68 +- .../repo/GitUntrackedFilesHolder.java | 46 +- .../git4idea/reset/GitNewResetDialog.java | 211 ++-- .../reset/GitOneCommitPerRepoLogAction.java | 21 +- .../java/git4idea/reset/GitResetAction.java | 4 +- .../java/git4idea/reset/GitResetMode.java | 84 +- .../rollback/GitRollbackEnvironment.java | 40 +- .../java/git4idea/roots/GitRootChecker.java | 27 +- .../git4idea/settings/GitPushSettings.java | 58 +- .../java/git4idea/stash/GitShelveUtils.java | 3 +- .../java/git4idea/stash/GitStashUtils.java | 77 +- .../java/git4idea/ui/GitUnstashDialog.java | 16 +- .../ui/branch/GitCompareBranchesLogPanel.java | 5 +- .../ui/branch/GitMultiRootBranchConfig.java | 5 +- .../main/java/git4idea/update/GitFetcher.java | 2 +- .../git4idea/update/GitUpdateProcess.java | 2 +- .../java/git4idea/vfs/GitVFSListener.java | 67 +- .../git4idea/util/ScriptGenerator.java | 333 +++---- .../git4idea/history/GitHistoryUtilsTest.java | 888 +++++++++-------- .../java/git4idea/repo/GitConfigTest.java | 4 +- 68 files changed, 5622 insertions(+), 6049 deletions(-) diff --git a/plugin/src/main/java/git4idea/GitFileRevision.java b/plugin/src/main/java/git4idea/GitFileRevision.java index cb735c5..21f8448 100644 --- a/plugin/src/main/java/git4idea/GitFileRevision.java +++ b/plugin/src/main/java/git4idea/GitFileRevision.java @@ -27,198 +27,174 @@ import consulo.versionControlSystem.util.VcsFileUtil; import consulo.virtualFileSystem.VirtualFile; import git4idea.util.GitFileUtils; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.Date; -public class GitFileRevision extends VcsFileRevisionEx implements Comparable, VcsFileRevisionDvcsSpecific -{ - - @Nonnull - private final Project myProject; - @Nonnull - private final FilePath myPath; - @Nonnull - private final GitRevisionNumber myRevision; - @Nullable - private final Couple> myAuthorAndCommitter; - @Nullable - private final String myMessage; - @Nullable - private final String myBranch; - @Nullable - private final Date myAuthorTime; - @Nonnull - private final Collection myParents; - @Nullable - private final VirtualFile myRoot; - - public GitFileRevision(@Nonnull Project project, @Nonnull FilePath path, @Nonnull GitRevisionNumber revision) - { - this(project, null, path, revision, null, null, null, null, Collections.emptyList()); - } - - public GitFileRevision(@Nonnull Project project, - @Nullable VirtualFile root, - @Nonnull FilePath path, - @Nonnull GitRevisionNumber revision, - @Nullable Couple> authorAndCommitter, - @Nullable String message, - @Nullable String branch, - @Nullable final Date authorTime, - @Nonnull Collection parents) - { - myProject = project; - myRoot = root; - myPath = path; - myRevision = revision; - myAuthorAndCommitter = authorAndCommitter; - myMessage = message; - myBranch = branch; - myAuthorTime = authorTime; - myParents = parents; - } - - @Override - @Nonnull - public FilePath getPath() - { - return myPath; - } - - @Nullable - @Override - public RepositoryLocation getChangedRepositoryPath() - { - return null; - } - - @Override - @Nonnull - public VcsRevisionNumber getRevisionNumber() - { - return myRevision; - } - - @Override - public Date getRevisionDate() - { - return myRevision.getTimestamp(); - } - - @Nullable - @Override - public Date getDateForRevisionsOrdering() - { - return myAuthorTime; - } - - @Override - @Nullable - public String getAuthor() - { - if(myAuthorAndCommitter != null) - { - return myAuthorAndCommitter.getFirst().getFirst(); - } - return null; - } - - @Nullable - @Override - public String getAuthorEmail() - { - if(myAuthorAndCommitter != null) - { - return myAuthorAndCommitter.getFirst().getSecond(); - } - return null; - } - - @Nullable - @Override - public String getCommitterName() - { - if(myAuthorAndCommitter != null) - { - return myAuthorAndCommitter.getSecond() == null ? null : myAuthorAndCommitter.getSecond().getFirst(); - } - return null; - } - - @Nullable - @Override - public String getCommitterEmail() - { - if(myAuthorAndCommitter != null) - { - return myAuthorAndCommitter.getSecond() == null ? null : myAuthorAndCommitter.getSecond().getSecond(); - } - return null; - } - - @Override - @Nullable - public String getCommitMessage() - { - return myMessage; - } - - @Override - @Nullable - public String getBranchName() - { - return myBranch; - } - - @Override - public synchronized byte[] loadContent() throws IOException, VcsException - { - VirtualFile root = getRoot(); - return GitFileUtils.getFileContent(myProject, root, myRevision.getRev(), VcsFileUtil.relativePath(root, myPath)); - } - - private VirtualFile getRoot() throws VcsException - { - return myRoot != null ? myRoot : GitUtil.getGitRoot(myPath); - } - - @Override - public synchronized byte[] getContent() throws IOException, VcsException - { - return loadContent(); - } - - @Override - public int compareTo(VcsFileRevision rev) - { - if(rev instanceof GitFileRevision) - { - return myRevision.compareTo(((GitFileRevision) rev).myRevision); - } - return getRevisionDate().compareTo(rev.getRevisionDate()); - } - - @Override - public String toString() - { - return myPath.getName() + ":" + myRevision.getShortRev(); - } - - @Nonnull - public Collection getParents() - { - return myParents; - } - - @Nonnull - public String getHash() - { - return myRevision.getRev(); - } +public class GitFileRevision extends VcsFileRevisionEx implements Comparable, VcsFileRevisionDvcsSpecific { + + @Nonnull + private final Project myProject; + @Nonnull + private final FilePath myPath; + @Nonnull + private final GitRevisionNumber myRevision; + @Nullable + private final Couple> myAuthorAndCommitter; + @Nullable + private final String myMessage; + @Nullable + private final String myBranch; + @Nullable + private final Date myAuthorTime; + @Nonnull + private final Collection myParents; + @Nullable + private final VirtualFile myRoot; + + public GitFileRevision(@Nonnull Project project, @Nonnull FilePath path, @Nonnull GitRevisionNumber revision) { + this(project, null, path, revision, null, null, null, null, Collections.emptyList()); + } + + public GitFileRevision( + @Nonnull Project project, + @Nullable VirtualFile root, + @Nonnull FilePath path, + @Nonnull GitRevisionNumber revision, + @Nullable Couple> authorAndCommitter, + @Nullable String message, + @Nullable String branch, + @Nullable Date authorTime, + @Nonnull Collection parents + ) { + myProject = project; + myRoot = root; + myPath = path; + myRevision = revision; + myAuthorAndCommitter = authorAndCommitter; + myMessage = message; + myBranch = branch; + myAuthorTime = authorTime; + myParents = parents; + } + + @Override + @Nonnull + public FilePath getPath() { + return myPath; + } + + @Nullable + @Override + public RepositoryLocation getChangedRepositoryPath() { + return null; + } + + @Override + @Nonnull + public VcsRevisionNumber getRevisionNumber() { + return myRevision; + } + + @Override + public Date getRevisionDate() { + return myRevision.getTimestamp(); + } + + @Nullable + @Override + public Date getDateForRevisionsOrdering() { + return myAuthorTime; + } + + @Override + @Nullable + public String getAuthor() { + if (myAuthorAndCommitter != null) { + return myAuthorAndCommitter.getFirst().getFirst(); + } + return null; + } + + @Nullable + @Override + public String getAuthorEmail() { + if (myAuthorAndCommitter != null) { + return myAuthorAndCommitter.getFirst().getSecond(); + } + return null; + } + + @Nullable + @Override + public String getCommitterName() { + if (myAuthorAndCommitter != null) { + return myAuthorAndCommitter.getSecond() == null ? null : myAuthorAndCommitter.getSecond().getFirst(); + } + return null; + } + + @Nullable + @Override + public String getCommitterEmail() { + if (myAuthorAndCommitter != null) { + return myAuthorAndCommitter.getSecond() == null ? null : myAuthorAndCommitter.getSecond().getSecond(); + } + return null; + } + + @Override + @Nullable + public String getCommitMessage() { + return myMessage; + } + + @Override + @Nullable + public String getBranchName() { + return myBranch; + } + + @Override + public synchronized byte[] loadContent() throws IOException, VcsException { + VirtualFile root = getRoot(); + return GitFileUtils.getFileContent(myProject, root, myRevision.getRev(), VcsFileUtil.relativePath(root, myPath)); + } + + private VirtualFile getRoot() throws VcsException { + return myRoot != null ? myRoot : GitUtil.getGitRoot(myPath); + } + + @Override + public synchronized byte[] getContent() throws IOException, VcsException { + return loadContent(); + } + + @Override + public int compareTo(VcsFileRevision rev) { + if (rev instanceof GitFileRevision fileRevision) { + return myRevision.compareTo(fileRevision.myRevision); + } + return getRevisionDate().compareTo(rev.getRevisionDate()); + } + + @Override + public String toString() { + return myPath.getName() + ":" + myRevision.getShortRev(); + } + + @Nonnull + public Collection getParents() { + return myParents; + } + + @Nonnull + public String getHash() { + return myRevision.getRev(); + } } diff --git a/plugin/src/main/java/git4idea/GitLocalBranch.java b/plugin/src/main/java/git4idea/GitLocalBranch.java index f37d36b..98f5e12 100644 --- a/plugin/src/main/java/git4idea/GitLocalBranch.java +++ b/plugin/src/main/java/git4idea/GitLocalBranch.java @@ -15,56 +15,46 @@ */ package git4idea; -import jakarta.annotation.Nonnull; import git4idea.repo.GitBranchTrackInfo; import git4idea.repo.GitRepository; +import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; /** * @author Kirill Likhodedov */ -public class GitLocalBranch extends GitBranch -{ - - public GitLocalBranch(@Nonnull String name) - { - super(name); - } - - @Override - public boolean isRemote() - { - return false; - } - - @Override - public boolean equals(Object o) - { - return super.equals(o); - } - - @Override - public int hashCode() - { - return super.hashCode(); - } - - @Override - public String toString() - { - return super.toString(); - } - - @Nullable - public GitRemoteBranch findTrackedBranch(@Nonnull GitRepository repository) - { - for(GitBranchTrackInfo info : repository.getBranchTrackInfos()) - { - if(info.getLocalBranch().equals(this)) - { - return info.getRemoteBranch(); - } - } - return null; - } +public class GitLocalBranch extends GitBranch { + public GitLocalBranch(@Nonnull String name) { + super(name); + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public String toString() { + return super.toString(); + } + + @Nullable + public GitRemoteBranch findTrackedBranch(@Nonnull GitRepository repository) { + for (GitBranchTrackInfo info : repository.getBranchTrackInfos()) { + if (info.localBranch().equals(this)) { + return info.remoteBranch(); + } + } + return null; + } } diff --git a/plugin/src/main/java/git4idea/GitRevisionNumber.java b/plugin/src/main/java/git4idea/GitRevisionNumber.java index 6af9142..98bad96 100644 --- a/plugin/src/main/java/git4idea/GitRevisionNumber.java +++ b/plugin/src/main/java/git4idea/GitRevisionNumber.java @@ -26,7 +26,6 @@ import git4idea.commands.GitCommand; import git4idea.commands.GitSimpleHandler; import jakarta.annotation.Nonnull; -import org.jetbrains.annotations.NonNls; import java.util.Date; import java.util.StringTokenizer; @@ -34,247 +33,219 @@ /** * Git revision number */ -public class GitRevisionNumber implements ShortVcsRevisionNumber -{ - /** - * the hash from 40 zeros representing not yet created commit - */ - public static final String NOT_COMMITTED_HASH = StringUtil.repeat("0", 40); - - public static final GitRevisionNumber HEAD = new GitRevisionNumber("HEAD"); - - /** - * the revision number (40 character hashcode, tag, or reference). In some cases incomplete hashcode could be used. - */ - @Nonnull - private final String myRevisionHash; - /** - * the date when revision created - */ - @Nonnull - private final Date myTimestamp; - - private static final Logger LOG = Logger.getInstance(GitRevisionNumber.class); - - /** - * A constructor from version. The current date is used. - * - * @param version the version number. - */ - public GitRevisionNumber(@NonNls @Nonnull String version) - { - // TODO review usages - myRevisionHash = version; - myTimestamp = new Date(); - } - - /** - * A constructor from version and time - * - * @param version the version number - * @param timeStamp the time when the version has been created - */ - public GitRevisionNumber(@Nonnull String version, @Nonnull Date timeStamp) - { - myTimestamp = timeStamp; - myRevisionHash = version; - } - - @Nonnull - public String asString() - { - return myRevisionHash; - } - - @Override - public String toShortString() - { - return asString().substring(0, 7); - } - - /** - * @return revision time - */ - @Nonnull - public Date getTimestamp() - { - return myTimestamp; - } - - /** - * @return revision number - */ - @Nonnull - public String getRev() - { - return myRevisionHash; - } - - /** - * @return the short revision number. The revision number likely unambiguously identify local revision, however in rare cases there could be conflicts. - */ - @Nonnull - public String getShortRev() - { - return DvcsUtil.getShortHash(myRevisionHash); - } - - /** - * {@inheritDoc} - */ - public int compareTo(VcsRevisionNumber crev) - { - if(this == crev) - { - return 0; - } - - if(crev instanceof GitRevisionNumber) - { - GitRevisionNumber other = (GitRevisionNumber) crev; - if((other.myRevisionHash != null) && myRevisionHash.equals(other.myRevisionHash)) - { - return 0; - } - - if((other.myRevisionHash.indexOf("[") > 0) && (other.myTimestamp != null)) - { - return myTimestamp.compareTo(other.myTimestamp); - } - - // check for parent revs - String otherName = null; - String thisName = null; - int otherParents = -1; - int thisParent = -1; - - if(other.myRevisionHash.contains("~")) - { - int tildeIndex = other.myRevisionHash.indexOf('~'); - otherName = other.myRevisionHash.substring(0, tildeIndex); - otherParents = Integer.parseInt(other.myRevisionHash.substring(tildeIndex)); - } - - if(myRevisionHash.contains("~")) - { - int tildeIndex = myRevisionHash.indexOf('~'); - thisName = myRevisionHash.substring(0, tildeIndex); - thisParent = Integer.parseInt(myRevisionHash.substring(tildeIndex)); - } - - if(otherName == null && thisName == null) - { - final int result = myTimestamp.compareTo(other.myTimestamp); - if(result == 0) - { - // it can NOT be 0 - it would mean that revisions are equal but they have different hash codes - // but this is NOT correct. but we don't know here how to sort - return myRevisionHash.compareTo(other.myRevisionHash); - } - return result; - } - else if(otherName == null) - { - return 1; // I am an ancestor of the compared revision - } - else if(thisName == null) - { - return -1; // the compared revision is my ancestor - } - else - { - return thisParent - otherParents; // higher relative rev numbers are older ancestors - } - } - - return -1; - } - - @Override - public boolean equals(Object obj) - { - if(this == obj) - { - return true; - } - if((obj == null) || (obj.getClass() != getClass())) - { - return false; - } - - GitRevisionNumber test = (GitRevisionNumber) obj; - // TODO normalize revision string? - return myRevisionHash.equals(test.myRevisionHash); - } - - @Override - public int hashCode() - { - return myRevisionHash.hashCode(); - } - - /** - * @return a revision string that refers to the parent revision relatively - * to the current one. The git operator "~" is used. Note that in case of merges, - * the first revision of several will referred. - */ - public String getParentRevisionStr() - { - String rev = myRevisionHash; - int bracketIdx = rev.indexOf("["); - if(bracketIdx > 0) - { - rev = myRevisionHash.substring(bracketIdx + 1, myRevisionHash.indexOf("]")); - } - - int tildeIndex = rev.indexOf("~"); - if(tildeIndex > 0) - { - int n = Integer.parseInt(rev.substring(tildeIndex)) + 1; - return rev.substring(0, tildeIndex) + "~" + n; - } - return rev + "~1"; - } - - /** - * Resolve revision number for the specified revision - * - * @param project a project - * @param vcsRoot a vcs root - * @param rev a revision expression - * @return a resolved revision number with correct time - * @throws VcsException if there is a problem with running git - */ - @Nonnull - public static GitRevisionNumber resolve(Project project, VirtualFile vcsRoot, @NonNls String rev) throws VcsException - { - GitSimpleHandler h = new GitSimpleHandler(project, vcsRoot, GitCommand.REV_LIST); - h.setSilent(true); - h.addParameters("--timestamp", "--max-count=1", rev); - h.endOptions(); - final String output = h.run(); - return parseRevlistOutputAsRevisionNumber(h, output); - } - - @Nonnull - public static GitRevisionNumber parseRevlistOutputAsRevisionNumber(@Nonnull GitSimpleHandler h, @Nonnull String output) throws VcsException - { - try - { - StringTokenizer tokenizer = new StringTokenizer(output, "\n\r \t", false); - LOG.assertTrue(tokenizer.hasMoreTokens(), "No required tokens in the output: \n" + output); - Date timestamp = GitUtil.parseTimestampWithNFEReport(tokenizer.nextToken(), h, output); - return new GitRevisionNumber(tokenizer.nextToken(), timestamp); - } - catch(Exception e) - { - throw new VcsException("Couldn't parse the output: [" + output + "]", e); - } - } - - @Override - public String toString() - { - return myRevisionHash; - } +public class GitRevisionNumber implements ShortVcsRevisionNumber { + /** + * the hash from 40 zeros representing not yet created commit + */ + public static final String NOT_COMMITTED_HASH = StringUtil.repeat("0", 40); + + public static final GitRevisionNumber HEAD = new GitRevisionNumber("HEAD"); + + /** + * the revision number (40 character hashcode, tag, or reference). In some cases incomplete hashcode could be used. + */ + @Nonnull + private final String myRevisionHash; + /** + * the date when revision created + */ + @Nonnull + private final Date myTimestamp; + + private static final Logger LOG = Logger.getInstance(GitRevisionNumber.class); + + /** + * A constructor from version. The current date is used. + * + * @param version the version number. + */ + public GitRevisionNumber(@Nonnull String version) { + // TODO review usages + myRevisionHash = version; + myTimestamp = new Date(); + } + + /** + * A constructor from version and time + * + * @param version the version number + * @param timeStamp the time when the version has been created + */ + public GitRevisionNumber(@Nonnull String version, @Nonnull Date timeStamp) { + myTimestamp = timeStamp; + myRevisionHash = version; + } + + @Nonnull + @Override + public String asString() { + return myRevisionHash; + } + + @Override + public String toShortString() { + return asString().substring(0, 7); + } + + /** + * @return revision time + */ + @Nonnull + public Date getTimestamp() { + return myTimestamp; + } + + /** + * @return revision number + */ + @Nonnull + public String getRev() { + return myRevisionHash; + } + + /** + * @return the short revision number. The revision number likely unambiguously identify local revision, however in rare cases there could be conflicts. + */ + @Nonnull + public String getShortRev() { + return DvcsUtil.getShortHash(myRevisionHash); + } + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(VcsRevisionNumber crev) { + if (this == crev) { + return 0; + } + + if (crev instanceof GitRevisionNumber other) { + if (myRevisionHash.equals(other.myRevisionHash)) { + return 0; + } + + if (other.myRevisionHash.indexOf("[") > 0) { + return myTimestamp.compareTo(other.myTimestamp); + } + + // check for parent revs + String otherName = null; + String thisName = null; + int otherParents = -1; + int thisParent = -1; + + if (other.myRevisionHash.contains("~")) { + int tildeIndex = other.myRevisionHash.indexOf('~'); + otherName = other.myRevisionHash.substring(0, tildeIndex); + otherParents = Integer.parseInt(other.myRevisionHash.substring(tildeIndex)); + } + + if (myRevisionHash.contains("~")) { + int tildeIndex = myRevisionHash.indexOf('~'); + thisName = myRevisionHash.substring(0, tildeIndex); + thisParent = Integer.parseInt(myRevisionHash.substring(tildeIndex)); + } + + if (otherName == null && thisName == null) { + int result = myTimestamp.compareTo(other.myTimestamp); + if (result == 0) { + // it can NOT be 0 - it would mean that revisions are equal but they have different hash codes + // but this is NOT correct. but we don't know here how to sort + return myRevisionHash.compareTo(other.myRevisionHash); + } + return result; + } + else if (otherName == null) { + return 1; // I am an ancestor of the compared revision + } + else if (thisName == null) { + return -1; // the compared revision is my ancestor + } + else { + return thisParent - otherParents; // higher relative rev numbers are older ancestors + } + } + + return -1; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if ((obj == null) || (obj.getClass() != getClass())) { + return false; + } + + GitRevisionNumber test = (GitRevisionNumber) obj; + // TODO normalize revision string? + return myRevisionHash.equals(test.myRevisionHash); + } + + @Override + public int hashCode() { + return myRevisionHash.hashCode(); + } + + /** + * @return a revision string that refers to the parent revision relatively + * to the current one. The git operator "~" is used. Note that in case of merges, + * the first revision of several will referred. + */ + public String getParentRevisionStr() { + String rev = myRevisionHash; + int bracketIdx = rev.indexOf("["); + if (bracketIdx > 0) { + rev = myRevisionHash.substring(bracketIdx + 1, myRevisionHash.indexOf("]")); + } + + int tildeIndex = rev.indexOf("~"); + if (tildeIndex > 0) { + int n = Integer.parseInt(rev.substring(tildeIndex)) + 1; + return rev.substring(0, tildeIndex) + "~" + n; + } + return rev + "~1"; + } + + /** + * Resolve revision number for the specified revision + * + * @param project a project + * @param vcsRoot a vcs root + * @param rev a revision expression + * @return a resolved revision number with correct time + * @throws VcsException if there is a problem with running git + */ + @Nonnull + public static GitRevisionNumber resolve(Project project, VirtualFile vcsRoot, String rev) throws VcsException { + GitSimpleHandler h = new GitSimpleHandler(project, vcsRoot, GitCommand.REV_LIST); + h.setSilent(true); + h.addParameters("--timestamp", "--max-count=1", rev); + h.endOptions(); + String output = h.run(); + return parseRevlistOutputAsRevisionNumber(h, output); + } + + @Nonnull + public static GitRevisionNumber parseRevlistOutputAsRevisionNumber( + @Nonnull GitSimpleHandler h, + @Nonnull String output + ) throws VcsException { + try { + StringTokenizer tokenizer = new StringTokenizer(output, "\n\r \t", false); + LOG.assertTrue(tokenizer.hasMoreTokens(), "No required tokens in the output: \n" + output); + Date timestamp = GitUtil.parseTimestampWithNFEReport(tokenizer.nextToken(), h, output); + return new GitRevisionNumber(tokenizer.nextToken(), timestamp); + } + catch (Exception e) { + throw new VcsException("Couldn't parse the output: [" + output + "]", e); + } + } + + @Override + public String toString() { + return myRevisionHash; + } } diff --git a/plugin/src/main/java/git4idea/GitRootConverter.java b/plugin/src/main/java/git4idea/GitRootConverter.java index 10ab0f0..670bc63 100644 --- a/plugin/src/main/java/git4idea/GitRootConverter.java +++ b/plugin/src/main/java/git4idea/GitRootConverter.java @@ -13,35 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package git4idea; -import consulo.virtualFileSystem.VirtualFile; import consulo.versionControlSystem.AbstractVcs; +import consulo.virtualFileSystem.VirtualFile; import jakarta.annotation.Nonnull; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Given VFS content roots, filters them and returns only those, which are actual Git roots. */ public class GitRootConverter implements AbstractVcs.RootsConvertor { + public static final GitRootConverter INSTANCE = new GitRootConverter(); - public static final GitRootConverter INSTANCE = new GitRootConverter(); - - @Nonnull - public List convertRoots(@Nonnull List result) { - // TODO this should be faster, because it is called rather often. gitRootOrNull could be a bottle-neck. - ArrayList roots = new ArrayList(); - HashSet listed = new HashSet(); - for (VirtualFile f : result) { - VirtualFile r = GitUtil.gitRootOrNull(f); - if (r != null && listed.add(r)) { - roots.add(r); - } + @Nonnull + @Override + public List convertRoots(@Nonnull List result) { + // TODO this should be faster, because it is called rather often. gitRootOrNull could be a bottle-neck. + List roots = new ArrayList<>(); + Set listed = new HashSet<>(); + for (VirtualFile f : result) { + VirtualFile r = GitUtil.gitRootOrNull(f); + if (r != null && listed.add(r)) { + roots.add(r); + } + } + return roots; } - return roots; - } } diff --git a/plugin/src/main/java/git4idea/GitUtil.java b/plugin/src/main/java/git4idea/GitUtil.java index 81855ee..9ee7370 100644 --- a/plugin/src/main/java/git4idea/GitUtil.java +++ b/plugin/src/main/java/git4idea/GitUtil.java @@ -349,7 +349,7 @@ public static Date parseTimestampWithNFEReport(String value, GitHandler handler, * @return a content root */ public static Set gitRootsForPaths(Collection roots) { - HashSet rc = new HashSet<>(); + Set rc = new HashSet<>(); for (VirtualFile root : roots) { VirtualFile f = root; do { @@ -521,7 +521,7 @@ public static boolean isUnderGit(FilePath path) { * @return a set of git roots */ public static Set gitRoots(Collection filePaths) { - HashSet rc = new HashSet<>(); + Set rc = new HashSet<>(); for (FilePath path : filePaths) { VirtualFile root = getGitRootOrNull(path); if (root != null) { diff --git a/plugin/src/main/java/git4idea/branch/GitBranchUtil.java b/plugin/src/main/java/git4idea/branch/GitBranchUtil.java index f9641a0..e83e14e 100644 --- a/plugin/src/main/java/git4idea/branch/GitBranchUtil.java +++ b/plugin/src/main/java/git4idea/branch/GitBranchUtil.java @@ -38,7 +38,6 @@ import git4idea.repo.GitRepository; import git4idea.ui.branch.GitMultiRootBranchConfig; import git4idea.validators.GitNewBranchNameValidator; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -46,10 +45,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; +import java.util.*; import java.util.function.Function; import static consulo.util.lang.ObjectUtil.assertNotNull; @@ -77,7 +73,7 @@ private GitBranchUtil() { @Nullable public static GitBranchTrackInfo getTrackInfoForBranch(@Nonnull GitRepository repository, @Nonnull GitLocalBranch branch) { for (GitBranchTrackInfo trackInfo : repository.getBranchTrackInfos()) { - if (trackInfo.getLocalBranch().equals(branch)) { + if (trackInfo.localBranch().equals(branch)) { return trackInfo; } } @@ -86,7 +82,7 @@ public static GitBranchTrackInfo getTrackInfoForBranch(@Nonnull GitRepository re @Nullable public static GitBranchTrackInfo getTrackInfo(@Nonnull GitRepository repository, @Nonnull String localBranchName) { - return ContainerUtil.find(repository.getBranchTrackInfos(), it -> it.getLocalBranch().getName().equals(localBranchName)); + return ContainerUtil.find(repository.getBranchTrackInfos(), it -> it.localBranch().getName().equals(localBranchName)); } @Nonnull @@ -370,7 +366,7 @@ public static Collection getCommonBranches(Collection rep } if (commonBranches != null) { - ArrayList common = new ArrayList<>(commonBranches); + List common = new ArrayList<>(commonBranches); Collections.sort(common); return common; } diff --git a/plugin/src/main/java/git4idea/branch/GitDeleteBranchOperation.java b/plugin/src/main/java/git4idea/branch/GitDeleteBranchOperation.java index d603ec9..7bf023d 100644 --- a/plugin/src/main/java/git4idea/branch/GitDeleteBranchOperation.java +++ b/plugin/src/main/java/git4idea/branch/GitDeleteBranchOperation.java @@ -178,8 +178,8 @@ private static boolean hasOnlyTrackingBranch(@Nonnull MultiMap !info.getLocalBranch().getName().equals(localBranch) - && info.getRemoteBranch().getName().equals(remoteBranch) + info -> !info.localBranch().getName().equals(localBranch) + && info.remoteBranch().getName().equals(remoteBranch) )) { return false; } @@ -328,7 +328,7 @@ private static MultiMap groupByTrackedBranchName( for (GitRepository repository : repositories) { GitBranchTrackInfo trackInfo = GitBranchUtil.getTrackInfo(repository, branchName); if (trackInfo != null) { - trackedBranchNames.putValue(trackInfo.getRemoteBranch().getNameForLocalOperations(), repository); + trackedBranchNames.putValue(trackInfo.remoteBranch().getNameForLocalOperations(), repository); } } return trackedBranchNames; diff --git a/plugin/src/main/java/git4idea/branch/GitRebaseParams.java b/plugin/src/main/java/git4idea/branch/GitRebaseParams.java index c82acfb..a81950a 100644 --- a/plugin/src/main/java/git4idea/branch/GitRebaseParams.java +++ b/plugin/src/main/java/git4idea/branch/GitRebaseParams.java @@ -15,93 +15,84 @@ */ package git4idea.branch; -import static consulo.util.lang.StringUtil.nullize; -import static java.util.Arrays.asList; - -import java.util.List; - -import jakarta.annotation.Nullable; - -import consulo.util.collection.ContainerUtil; import consulo.util.lang.StringUtil; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; -public class GitRebaseParams -{ +import static consulo.util.lang.StringUtil.nullize; +import static java.util.Arrays.asList; - @Nullable - private final String myBranch; - @Nullable - private final String myNewBase; - @Nonnull - private final String myUpstream; - private final boolean myInteractive; - private final boolean myPreserveMerges; +public class GitRebaseParams { + @Nullable + private final String myBranch; + @Nullable + private final String myNewBase; + @Nonnull + private final String myUpstream; + private final boolean myInteractive; + private final boolean myPreserveMerges; - public GitRebaseParams(@Nonnull String upstream) - { - this(null, null, upstream, false, false); - } + public GitRebaseParams(@Nonnull String upstream) { + this(null, null, upstream, false, false); + } - public GitRebaseParams(@Nullable String branch, @Nullable String newBase, @Nonnull String upstream, boolean interactive, boolean preserveMerges) - { - myBranch = nullize(branch, true); - myNewBase = nullize(newBase, true); - myUpstream = upstream; - myInteractive = interactive; - myPreserveMerges = preserveMerges; - } + public GitRebaseParams( + @Nullable String branch, + @Nullable String newBase, + @Nonnull String upstream, + boolean interactive, + boolean preserveMerges + ) { + myBranch = nullize(branch, true); + myNewBase = nullize(newBase, true); + myUpstream = upstream; + myInteractive = interactive; + myPreserveMerges = preserveMerges; + } - @Nonnull - public List asCommandLineArguments() - { - List args = ContainerUtil.newArrayList(); - if(myInteractive) - { - args.add("--interactive"); - } - if(myPreserveMerges) - { - args.add("--preserve-merges"); - } - if(myNewBase != null) - { - args.addAll(asList("--onto", myNewBase)); - } - args.add(myUpstream); - if(myBranch != null) - { - args.add(myBranch); - } - return args; - } + @Nonnull + public List asCommandLineArguments() { + List args = new ArrayList<>(); + if (myInteractive) { + args.add("--interactive"); + } + if (myPreserveMerges) { + args.add("--preserve-merges"); + } + if (myNewBase != null) { + args.addAll(asList("--onto", myNewBase)); + } + args.add(myUpstream); + if (myBranch != null) { + args.add(myBranch); + } + return args; + } - @Nullable - public String getNewBase() - { - return myNewBase; - } + @Nullable + public String getNewBase() { + return myNewBase; + } - @Nonnull - public String getUpstream() - { - return myUpstream; - } + @Nonnull + public String getUpstream() { + return myUpstream; + } - @Override - public String toString() - { - return StringUtil.join(asCommandLineArguments(), " "); - } + @Override + public String toString() { + return StringUtil.join(asCommandLineArguments(), " "); + } - public boolean isInteractive() - { - return myInteractive; - } + public boolean isInteractive() { + return myInteractive; + } - @Nullable - public String getBranch() - { - return myBranch; - } + @Nullable + public String getBranch() { + return myBranch; + } } diff --git a/plugin/src/main/java/git4idea/changes/GitChangeUtils.java b/plugin/src/main/java/git4idea/changes/GitChangeUtils.java index 42c2f90..504986c 100644 --- a/plugin/src/main/java/git4idea/changes/GitChangeUtils.java +++ b/plugin/src/main/java/git4idea/changes/GitChangeUtils.java @@ -36,10 +36,9 @@ import git4idea.commands.GitSimpleHandler; import git4idea.history.browser.SHAHash; import git4idea.util.StringScanner; -import org.jetbrains.annotations.NonNls; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.io.File; import java.util.*; @@ -48,458 +47,488 @@ /** * Change related utilities */ -public class GitChangeUtils -{ - /** - * the pattern for committed changelist assumed. - */ - public static final String COMMITTED_CHANGELIST_FORMAT = "%ct%n%H%n%P%n%an%x20%x3C%ae%x3E%n%cn%x20%x3C%ce%x3E%n%s%n%x03%n%b%n%x03"; - - private static final Logger LOG = Logger.getInstance(GitChangeUtils.class); +public class GitChangeUtils { + /** + * the pattern for committed changelist assumed. + */ + public static final String COMMITTED_CHANGELIST_FORMAT = "%ct%n%H%n%P%n%an%x20%x3C%ae%x3E%n%cn%x20%x3C%ce%x3E%n%s%n%x03%n%b%n%x03"; - /** - * A private constructor for utility class - */ - private GitChangeUtils() - { - } + private static final Logger LOG = Logger.getInstance(GitChangeUtils.class); - /** - * Parse changes from lines - * - * @param project the context project - * @param vcsRoot the git root - * @param thisRevision the current revision - * @param parentRevision the parent revision for this change list - * @param s the lines to parse - * @param changes a list of changes to update - * @param ignoreNames a set of names ignored during collection of the changes - * @throws VcsException if the input format does not matches expected format - */ - public static void parseChanges(Project project, - VirtualFile vcsRoot, - @Nullable GitRevisionNumber thisRevision, - GitRevisionNumber parentRevision, - String s, - Collection changes, - final Set ignoreNames) throws VcsException - { - StringScanner sc = new StringScanner(s); - parseChanges(project, vcsRoot, thisRevision, parentRevision, sc, changes, ignoreNames); - if(sc.hasMoreData()) - { - throw new IllegalStateException("Unknown file status: " + sc.line()); - } - } + /** + * A private constructor for utility class + */ + private GitChangeUtils() { + } - public static Collection parseDiffForPaths(final String rootPath, final StringScanner s) throws VcsException - { - final Collection result = new ArrayList<>(); + /** + * Parse changes from lines + * + * @param project the context project + * @param vcsRoot the git root + * @param thisRevision the current revision + * @param parentRevision the parent revision for this change list + * @param s the lines to parse + * @param changes a list of changes to update + * @param ignoreNames a set of names ignored during collection of the changes + * @throws VcsException if the input format does not matches expected format + */ + public static void parseChanges( + Project project, + VirtualFile vcsRoot, + @Nullable GitRevisionNumber thisRevision, + GitRevisionNumber parentRevision, + String s, + Collection changes, + Set ignoreNames + ) throws VcsException { + StringScanner sc = new StringScanner(s); + parseChanges(project, vcsRoot, thisRevision, parentRevision, sc, changes, ignoreNames); + if (sc.hasMoreData()) { + throw new IllegalStateException("Unknown file status: " + sc.line()); + } + } - while(s.hasMoreData()) - { - if(s.isEol()) - { - s.nextLine(); - continue; - } - if("CADUMR".indexOf(s.peek()) == -1) - { - // exit if there is no next character - break; - } - assert 'M' != s.peek() : "Moves are not yet handled"; - String[] tokens = s.line().split("\t"); - String path = tokens[tokens.length - 1]; - path = rootPath + File.separator + GitUtil.unescapePath(path); - path = FileUtil.toSystemDependentName(path); - result.add(path); - } - return result; - } + public static Collection parseDiffForPaths(String rootPath, StringScanner s) throws VcsException { + Collection result = new ArrayList<>(); - /** - * Parse changes from lines - * - * @param project the context project - * @param vcsRoot the git root - * @param thisRevision the current revision - * @param parentRevision the parent revision for this change list - * @param s the lines to parse - * @param changes a list of changes to update - * @param ignoreNames a set of names ignored during collection of the changes - * @throws VcsException if the input format does not matches expected format - */ - private static void parseChanges(Project project, - VirtualFile vcsRoot, - @Nullable GitRevisionNumber thisRevision, - @Nullable GitRevisionNumber parentRevision, - StringScanner s, - Collection changes, - final Set ignoreNames) throws VcsException - { - while(s.hasMoreData()) - { - FileStatus status = null; - if(s.isEol()) - { - s.nextLine(); - continue; - } - if("CADUMRT".indexOf(s.peek()) == -1) - { - // exit if there is no next character - return; - } - String[] tokens = s.line().split("\t"); - final ContentRevision before; - final ContentRevision after; - final String path = tokens[tokens.length - 1]; - switch(tokens[0].charAt(0)) - { - case 'C': - case 'A': - before = null; - status = FileStatus.ADDED; - after = GitContentRevision.createRevision(vcsRoot, path, thisRevision, project, false, false, true); - break; - case 'U': - status = FileStatus.MERGED_WITH_CONFLICTS; - case 'M': - if(status == null) - { - status = FileStatus.MODIFIED; - } - before = GitContentRevision.createRevision(vcsRoot, path, parentRevision, project, false, true, true); - after = GitContentRevision.createRevision(vcsRoot, path, thisRevision, project, false, false, true); - break; - case 'D': - status = FileStatus.DELETED; - before = GitContentRevision.createRevision(vcsRoot, path, parentRevision, project, true, true, true); - after = null; - break; - case 'R': - status = FileStatus.MODIFIED; - before = GitContentRevision.createRevision(vcsRoot, tokens[1], parentRevision, project, true, true, true); - after = GitContentRevision.createRevision(vcsRoot, path, thisRevision, project, false, false, true); - break; - case 'T': - status = FileStatus.MODIFIED; - before = GitContentRevision.createRevision(vcsRoot, path, parentRevision, project, true, true, true); - after = GitContentRevision.createRevisionForTypeChange(project, vcsRoot, path, thisRevision, true); - break; - default: - throw new VcsException("Unknown file status: " + Arrays.asList(tokens)); - } - if(ignoreNames == null || !ignoreNames.contains(path)) - { - changes.add(new Change(before, after, status)); - } - } - } + while (s.hasMoreData()) { + if (s.isEol()) { + s.nextLine(); + continue; + } + if ("CADUMR".indexOf(s.peek()) == -1) { + // exit if there is no next character + break; + } + assert 'M' != s.peek() : "Moves are not yet handled"; + String[] tokens = s.line().split("\t"); + String path = tokens[tokens.length - 1]; + path = rootPath + File.separator + GitUtil.unescapePath(path); + path = FileUtil.toSystemDependentName(path); + result.add(path); + } + return result; + } - /** - * Load actual revision number with timestamp basing on a reference: name of a branch or tag, or revision number expression. - */ - @Nonnull - public static GitRevisionNumber resolveReference(@Nonnull Project project, @Nonnull VirtualFile vcsRoot, @Nonnull String reference) throws VcsException - { - GitSimpleHandler handler = createRefResolveHandler(project, vcsRoot, reference); - String output = handler.run(); - StringTokenizer stk = new StringTokenizer(output, "\n\r \t", false); - if(!stk.hasMoreTokens()) - { - try - { - GitSimpleHandler dh = new GitSimpleHandler(project, vcsRoot, GitCommand.LOG); - dh.addParameters("-1", "HEAD"); - dh.setSilent(true); - String out = dh.run(); - LOG.info("Diagnostic output from 'git log -1 HEAD': [" + out + "]"); - dh = createRefResolveHandler(project, vcsRoot, reference); - out = dh.run(); - LOG.info("Diagnostic output from 'git rev-list -1 --timestamp HEAD': [" + out + "]"); - } - catch(VcsException e) - { - LOG.info("Exception while trying to get some diagnostics info", e); - } - throw new VcsException(String.format("The string '%s' does not represent a revision number. Output: [%s]\n Root: %s", reference, output, vcsRoot)); - } - Date timestamp = GitUtil.parseTimestampWithNFEReport(stk.nextToken(), handler, output); - return new GitRevisionNumber(stk.nextToken(), timestamp); - } + /** + * Parse changes from lines + * + * @param project the context project + * @param vcsRoot the git root + * @param thisRevision the current revision + * @param parentRevision the parent revision for this change list + * @param s the lines to parse + * @param changes a list of changes to update + * @param ignoreNames a set of names ignored during collection of the changes + * @throws VcsException if the input format does not matches expected format + */ + private static void parseChanges( + Project project, + VirtualFile vcsRoot, + @Nullable GitRevisionNumber thisRevision, + @Nullable GitRevisionNumber parentRevision, + StringScanner s, + Collection changes, + Set ignoreNames + ) throws VcsException { + while (s.hasMoreData()) { + FileStatus status = null; + if (s.isEol()) { + s.nextLine(); + continue; + } + if ("CADUMRT".indexOf(s.peek()) == -1) { + // exit if there is no next character + return; + } + String[] tokens = s.line().split("\t"); + ContentRevision before; + ContentRevision after; + String path = tokens[tokens.length - 1]; + switch (tokens[0].charAt(0)) { + case 'C': + case 'A': + before = null; + status = FileStatus.ADDED; + after = GitContentRevision.createRevision(vcsRoot, path, thisRevision, project, false, false, true); + break; + case 'U': + status = FileStatus.MERGED_WITH_CONFLICTS; + case 'M': + if (status == null) { + status = FileStatus.MODIFIED; + } + before = GitContentRevision.createRevision(vcsRoot, path, parentRevision, project, false, true, true); + after = GitContentRevision.createRevision(vcsRoot, path, thisRevision, project, false, false, true); + break; + case 'D': + status = FileStatus.DELETED; + before = GitContentRevision.createRevision(vcsRoot, path, parentRevision, project, true, true, true); + after = null; + break; + case 'R': + status = FileStatus.MODIFIED; + before = GitContentRevision.createRevision(vcsRoot, tokens[1], parentRevision, project, true, true, true); + after = GitContentRevision.createRevision(vcsRoot, path, thisRevision, project, false, false, true); + break; + case 'T': + status = FileStatus.MODIFIED; + before = GitContentRevision.createRevision(vcsRoot, path, parentRevision, project, true, true, true); + after = GitContentRevision.createRevisionForTypeChange(project, vcsRoot, path, thisRevision, true); + break; + default: + throw new VcsException("Unknown file status: " + Arrays.asList(tokens)); + } + if (ignoreNames == null || !ignoreNames.contains(path)) { + changes.add(new Change(before, after, status)); + } + } + } - @Nonnull - private static GitSimpleHandler createRefResolveHandler(@Nonnull Project project, @Nonnull VirtualFile root, @Nonnull String reference) - { - GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.REV_LIST); - handler.addParameters("--timestamp", "--max-count=1", reference); - handler.endOptions(); - handler.setSilent(true); - return handler; - } + /** + * Load actual revision number with timestamp basing on a reference: name of a branch or tag, or revision number expression. + */ + @Nonnull + public static GitRevisionNumber resolveReference( + @Nonnull Project project, + @Nonnull VirtualFile vcsRoot, + @Nonnull String reference + ) throws VcsException { + GitSimpleHandler handler = createRefResolveHandler(project, vcsRoot, reference); + String output = handler.run(); + StringTokenizer stk = new StringTokenizer(output, "\n\r \t", false); + if (!stk.hasMoreTokens()) { + try { + GitSimpleHandler dh = new GitSimpleHandler(project, vcsRoot, GitCommand.LOG); + dh.addParameters("-1", "HEAD"); + dh.setSilent(true); + String out = dh.run(); + LOG.info("Diagnostic output from 'git log -1 HEAD': [" + out + "]"); + dh = createRefResolveHandler(project, vcsRoot, reference); + out = dh.run(); + LOG.info("Diagnostic output from 'git rev-list -1 --timestamp HEAD': [" + out + "]"); + } + catch (VcsException e) { + LOG.info("Exception while trying to get some diagnostics info", e); + } + throw new VcsException(String.format( + "The string '%s' does not represent a revision number. Output: [%s]\n Root: %s", + reference, + output, + vcsRoot + )); + } + Date timestamp = GitUtil.parseTimestampWithNFEReport(stk.nextToken(), handler, output); + return new GitRevisionNumber(stk.nextToken(), timestamp); + } - /** - * Check if the exception means that HEAD is missing for the current repository. - * - * @param e the exception to examine - * @return true if the head is missing - */ - public static boolean isHeadMissing(final VcsException e) - { - @NonNls final String errorText = "fatal: bad revision 'HEAD'\n"; - return e.getMessage().equals(errorText); - } + @Nonnull + private static GitSimpleHandler createRefResolveHandler( + @Nonnull Project project, + @Nonnull VirtualFile root, + @Nonnull String reference + ) { + GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.REV_LIST); + handler.addParameters("--timestamp", "--max-count=1", reference); + handler.endOptions(); + handler.setSilent(true); + return handler; + } - /** - * Get list of changes. Because native Git non-linear revision tree structure is not - * supported by the current IDEA interfaces some simplifications are made in the case - * of the merge, so changes are reported as difference with the first revision - * listed on the the merge that has at least some changes. - * - * @param project the project file - * @param root the git root - * @param revisionName the name of revision (might be tag) - * @param skipDiffsForMerge - * @param local - * @param revertible - * @return change list for the respective revision - * @throws VcsException in case of problem with running git - */ - public static GitCommittedChangeList getRevisionChanges(Project project, VirtualFile root, String revisionName, boolean skipDiffsForMerge, boolean local, boolean revertible) throws VcsException - { - GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.SHOW); - h.setSilent(true); - h.addParameters("--name-status", "--first-parent", "--no-abbrev", "-M", "--pretty=format:" + COMMITTED_CHANGELIST_FORMAT, "--encoding=UTF-8", revisionName, "--"); - String output = h.run(); - StringScanner s = new StringScanner(output); - return parseChangeList(project, root, s, skipDiffsForMerge, h, local, revertible); - } + /** + * Check if the exception means that HEAD is missing for the current repository. + * + * @param e the exception to examine + * @return true if the head is missing + */ + public static boolean isHeadMissing(VcsException e) { + String errorText = "fatal: bad revision 'HEAD'\n"; + return e.getMessage().equals(errorText); + } - @Nullable - public static SHAHash commitExists(final Project project, final VirtualFile root, final String anyReference, List paths, final String... parameters) - { - GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG); - h.setSilent(true); - h.addParameters(parameters); - h.addParameters("--max-count=1", "--pretty=%H", "--encoding=UTF-8", anyReference, "--"); - if(paths != null && !paths.isEmpty()) - { - h.addRelativeFiles(paths); - } - try - { - final String output = h.run().trim(); - if(StringUtil.isEmptyOrSpaces(output)) - { - return null; - } - return new SHAHash(output); - } - catch(VcsException e) - { - return null; - } - } + /** + * Get list of changes. Because native Git non-linear revision tree structure is not + * supported by the current IDEA interfaces some simplifications are made in the case + * of the merge, so changes are reported as difference with the first revision + * listed on the the merge that has at least some changes. + * + * @param project the project file + * @param root the git root + * @param revisionName the name of revision (might be tag) + * @param skipDiffsForMerge + * @param local + * @param revertible + * @return change list for the respective revision + * @throws VcsException in case of problem with running git + */ + public static GitCommittedChangeList getRevisionChanges( + Project project, + VirtualFile root, + String revisionName, + boolean skipDiffsForMerge, + boolean local, + boolean revertible + ) throws VcsException { + GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.SHOW); + h.setSilent(true); + h.addParameters( + "--name-status", + "--first-parent", + "--no-abbrev", + "-M", + "--pretty=format:" + COMMITTED_CHANGELIST_FORMAT, + "--encoding=UTF-8", + revisionName, + "--" + ); + String output = h.run(); + StringScanner s = new StringScanner(output); + return parseChangeList(project, root, s, skipDiffsForMerge, h, local, revertible); + } - /** - * Parse changelist - * - * @param project the project - * @param root the git root - * @param s the scanner for log or show command output - * @param skipDiffsForMerge - * @param handler the handler that produced the output to parse. - for debugging purposes. - * @param local pass {@code true} to indicate that this revision should be an editable - * {@link CurrentContentRevision}. - * Pass {@code false} for - * @param revertible - * @return the parsed changelist - * @throws VcsException if there is a problem with running git - */ - public static GitCommittedChangeList parseChangeList(Project project, - VirtualFile root, - StringScanner s, - boolean skipDiffsForMerge, - GitHandler handler, - boolean local, - boolean revertible) throws VcsException - { - ArrayList changes = new ArrayList<>(); - // parse commit information - final Date commitDate = GitUtil.parseTimestampWithNFEReport(s.line(), handler, s.getAllText()); - final String revisionNumber = s.line(); - final String parentsLine = s.line(); - final String[] parents = parentsLine.length() == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : parentsLine.split(" "); - String authorName = s.line(); - String committerName = s.line(); - committerName = GitUtil.adjustAuthorName(authorName, committerName); - String commentSubject = s.boundedToken('\u0003', true); - s.nextLine(); - String commentBody = s.boundedToken('\u0003', true); - // construct full comment - String fullComment; - if(commentSubject.length() == 0) - { - fullComment = commentBody; - } - else if(commentBody.length() == 0) - { - fullComment = commentSubject; - } - else - { - fullComment = commentSubject + "\n" + commentBody; - } - GitRevisionNumber thisRevision = new GitRevisionNumber(revisionNumber, commitDate); + @Nullable + public static SHAHash commitExists( + Project project, + VirtualFile root, + String anyReference, + List paths, + String... parameters + ) { + GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG); + h.setSilent(true); + h.addParameters(parameters); + h.addParameters("--max-count=1", "--pretty=%H", "--encoding=UTF-8", anyReference, "--"); + if (paths != null && !paths.isEmpty()) { + h.addRelativeFiles(paths); + } + try { + String output = h.run().trim(); + if (StringUtil.isEmptyOrSpaces(output)) { + return null; + } + return new SHAHash(output); + } + catch (VcsException e) { + return null; + } + } - if(skipDiffsForMerge || (parents.length <= 1)) - { - final GitRevisionNumber parentRevision = parents.length > 0 ? resolveReference(project, root, parents[0]) : null; - // This is the first or normal commit with the single parent. - // Just parse changes in this commit as returned by the show command. - parseChanges(project, root, thisRevision, local ? null : parentRevision, s, changes, null); - } - else - { - // This is the merge commit. It has multiple parent commits. - // Find the first commit with changes and report it as a change list. - // If no changes are found (why to merge then?). Empty changelist is reported. + /** + * Parse changelist + * + * @param project the project + * @param root the git root + * @param s the scanner for log or show command output + * @param skipDiffsForMerge + * @param handler the handler that produced the output to parse. - for debugging purposes. + * @param local pass {@code true} to indicate that this revision should be an editable + * {@link CurrentContentRevision}. + * Pass {@code false} for + * @param revertible + * @return the parsed changelist + * @throws VcsException if there is a problem with running git + */ + public static GitCommittedChangeList parseChangeList( + Project project, + VirtualFile root, + StringScanner s, + boolean skipDiffsForMerge, + GitHandler handler, + boolean local, + boolean revertible + ) throws VcsException { + List changes = new ArrayList<>(); + // parse commit information + Date commitDate = GitUtil.parseTimestampWithNFEReport(s.line(), handler, s.getAllText()); + String revisionNumber = s.line(); + String parentsLine = s.line(); + String[] parents = parentsLine.length() == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : parentsLine.split(" "); + String authorName = s.line(); + String committerName = s.line(); + committerName = GitUtil.adjustAuthorName(authorName, committerName); + String commentSubject = s.boundedToken('\u0003', true); + s.nextLine(); + String commentBody = s.boundedToken('\u0003', true); + // construct full comment + String fullComment; + if (commentSubject.length() == 0) { + fullComment = commentBody; + } + else if (commentBody.length() == 0) { + fullComment = commentSubject; + } + else { + fullComment = commentSubject + "\n" + commentBody; + } + GitRevisionNumber thisRevision = new GitRevisionNumber(revisionNumber, commitDate); - for(String parent : parents) - { - final GitRevisionNumber parentRevision = resolveReference(project, root, parent); - GitSimpleHandler diffHandler = new GitSimpleHandler(project, root, GitCommand.DIFF); - diffHandler.setSilent(true); - diffHandler.addParameters("--name-status", "-M", parentRevision.getRev(), thisRevision.getRev()); - String diff = diffHandler.run(); - parseChanges(project, root, thisRevision, parentRevision, diff, changes, null); + if (skipDiffsForMerge || (parents.length <= 1)) { + GitRevisionNumber parentRevision = parents.length > 0 ? resolveReference(project, root, parents[0]) : null; + // This is the first or normal commit with the single parent. + // Just parse changes in this commit as returned by the show command. + parseChanges(project, root, thisRevision, local ? null : parentRevision, s, changes, null); + } + else { + // This is the merge commit. It has multiple parent commits. + // Find the first commit with changes and report it as a change list. + // If no changes are found (why to merge then?). Empty changelist is reported. - if(changes.size() > 0) - { - break; - } - } - } - String changeListName = String.format("%s(%s)", commentSubject, revisionNumber); - return new GitCommittedChangeList(changeListName, fullComment, committerName, thisRevision, commitDate, changes, assertNotNull(GitVcs.getInstance(project)), revertible); - } + for (String parent : parents) { + GitRevisionNumber parentRevision = resolveReference(project, root, parent); + GitSimpleHandler diffHandler = new GitSimpleHandler(project, root, GitCommand.DIFF); + diffHandler.setSilent(true); + diffHandler.addParameters("--name-status", "-M", parentRevision.getRev(), thisRevision.getRev()); + String diff = diffHandler.run(); + parseChanges(project, root, thisRevision, parentRevision, diff, changes, null); - public static long longForSHAHash(String revisionNumber) - { - return Long.parseLong(revisionNumber.substring(0, 15), 16) << 4 + Integer.parseInt(revisionNumber.substring(15, 16), 16); - } + if (changes.size() > 0) { + break; + } + } + } + String changeListName = String.format("%s(%s)", commentSubject, revisionNumber); + return new GitCommittedChangeList( + changeListName, + fullComment, + committerName, + thisRevision, + commitDate, + changes, + assertNotNull(GitVcs.getInstance(project)), + revertible + ); + } - @Nonnull - public static Collection getDiff(@Nonnull Project project, - @Nonnull VirtualFile root, - @Nullable String oldRevision, - @Nullable String newRevision, - @Nullable Collection dirtyPaths) throws VcsException - { - LOG.assertTrue(oldRevision != null || newRevision != null, "Both old and new revisions can't be null"); - String range; - GitRevisionNumber newRev; - GitRevisionNumber oldRev; - if(newRevision == null) - { // current revision at the right - range = oldRevision + ".."; - oldRev = resolveReference(project, root, oldRevision); - newRev = null; - } - else if(oldRevision == null) - { // current revision at the left - range = ".." + newRevision; - oldRev = null; - newRev = resolveReference(project, root, newRevision); - } - else - { - range = oldRevision + ".." + newRevision; - oldRev = resolveReference(project, root, oldRevision); - newRev = resolveReference(project, root, newRevision); - } - String output = getDiffOutput(project, root, range, dirtyPaths); + public static long longForSHAHash(String revisionNumber) { + return Long.parseLong(revisionNumber.substring(0, 15), 16) << 4 + Integer.parseInt(revisionNumber.substring(15, 16), 16); + } - Collection changes = new ArrayList<>(); - parseChanges(project, root, newRev, oldRev, output, changes, Collections.emptySet()); - return changes; - } + @Nonnull + public static Collection getDiff( + @Nonnull Project project, + @Nonnull VirtualFile root, + @Nullable String oldRevision, + @Nullable String newRevision, + @Nullable Collection dirtyPaths + ) throws VcsException { + LOG.assertTrue(oldRevision != null || newRevision != null, "Both old and new revisions can't be null"); + String range; + GitRevisionNumber newRev; + GitRevisionNumber oldRev; + if (newRevision == null) { // current revision at the right + range = oldRevision + ".."; + oldRev = resolveReference(project, root, oldRevision); + newRev = null; + } + else if (oldRevision == null) { // current revision at the left + range = ".." + newRevision; + oldRev = null; + newRev = resolveReference(project, root, newRevision); + } + else { + range = oldRevision + ".." + newRevision; + oldRev = resolveReference(project, root, oldRevision); + newRev = resolveReference(project, root, newRevision); + } + String output = getDiffOutput(project, root, range, dirtyPaths); - @Nonnull - public static Collection getStagedChanges(@Nonnull Project project, @Nonnull VirtualFile root) throws VcsException - { - GitSimpleHandler diff = new GitSimpleHandler(project, root, GitCommand.DIFF); - diff.addParameters("--name-status", "--cached", "-M"); - String output = diff.run(); + Collection changes = new ArrayList<>(); + parseChanges(project, root, newRev, oldRev, output, changes, Collections.emptySet()); + return changes; + } - Collection changes = new ArrayList<>(); - parseChanges(project, root, null, GitRevisionNumber.HEAD, output, changes, Collections.emptySet()); - return changes; - } + @Nonnull + public static Collection getStagedChanges(@Nonnull Project project, @Nonnull VirtualFile root) throws VcsException { + GitSimpleHandler diff = new GitSimpleHandler(project, root, GitCommand.DIFF); + diff.addParameters("--name-status", "--cached", "-M"); + String output = diff.run(); - @Nonnull - public static Collection getDiffWithWorkingDir(@Nonnull Project project, - @Nonnull VirtualFile root, - @Nonnull String oldRevision, - @Nullable Collection dirtyPaths, - boolean reverse) throws VcsException - { - String output = getDiffOutput(project, root, oldRevision, dirtyPaths, reverse); - Collection changes = new ArrayList<>(); - final GitRevisionNumber revisionNumber = resolveReference(project, root, oldRevision); - parseChanges(project, root, reverse ? revisionNumber : null, reverse ? null : revisionNumber, output, changes, Collections.emptySet()); - return changes; - } + Collection changes = new ArrayList<>(); + parseChanges(project, root, null, GitRevisionNumber.HEAD, output, changes, Collections.emptySet()); + return changes; + } - /** - * Calls {@code git diff} on the given range. - * - * @param project - * @param root - * @param diffRange range or just revision (will be compared with current working tree). - * @param dirtyPaths limit the command by paths if needed or pass null. - * @param reverse swap two revision; that is, show differences from index or on-disk file to tree contents. - * @return output of the 'git diff' command. - * @throws VcsException - */ - @Nonnull - private static String getDiffOutput(@Nonnull Project project, @Nonnull VirtualFile root, @Nonnull String diffRange, @Nullable Collection dirtyPaths, boolean reverse) throws VcsException - { - GitSimpleHandler handler = getDiffHandler(project, root, diffRange, dirtyPaths, reverse); - if(handler.isLargeCommandLine()) - { - // if there are too much files, just get all changes for the project - handler = getDiffHandler(project, root, diffRange, null, reverse); - } - return handler.run(); - } + @Nonnull + public static Collection getDiffWithWorkingDir( + @Nonnull Project project, + @Nonnull VirtualFile root, + @Nonnull String oldRevision, + @Nullable Collection dirtyPaths, + boolean reverse + ) throws VcsException { + String output = getDiffOutput(project, root, oldRevision, dirtyPaths, reverse); + Collection changes = new ArrayList<>(); + GitRevisionNumber revisionNumber = resolveReference(project, root, oldRevision); + parseChanges( + project, + root, + reverse ? revisionNumber : null, + reverse ? null : revisionNumber, + output, + changes, + Collections.emptySet() + ); + return changes; + } - @Nonnull - public static String getDiffOutput(@Nonnull Project project, @Nonnull VirtualFile root, @Nonnull String diffRange, @Nullable Collection dirtyPaths) throws VcsException - { - return getDiffOutput(project, root, diffRange, dirtyPaths, false); - } + /** + * Calls {@code git diff} on the given range. + * + * @param project + * @param root + * @param diffRange range or just revision (will be compared with current working tree). + * @param dirtyPaths limit the command by paths if needed or pass null. + * @param reverse swap two revision; that is, show differences from index or on-disk file to tree contents. + * @return output of the 'git diff' command. + * @throws VcsException + */ + @Nonnull + private static String getDiffOutput( + @Nonnull Project project, + @Nonnull VirtualFile root, + @Nonnull String diffRange, + @Nullable Collection dirtyPaths, + boolean reverse + ) throws VcsException { + GitSimpleHandler handler = getDiffHandler(project, root, diffRange, dirtyPaths, reverse); + if (handler.isLargeCommandLine()) { + // if there are too much files, just get all changes for the project + handler = getDiffHandler(project, root, diffRange, null, reverse); + } + return handler.run(); + } + @Nonnull + public static String getDiffOutput( + @Nonnull Project project, + @Nonnull VirtualFile root, + @Nonnull String diffRange, + @Nullable Collection dirtyPaths + ) throws VcsException { + return getDiffOutput(project, root, diffRange, dirtyPaths, false); + } - @Nonnull - private static GitSimpleHandler getDiffHandler(@Nonnull Project project, @Nonnull VirtualFile root, @Nonnull String diffRange, @Nullable Collection dirtyPaths, boolean reverse) - { - GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.DIFF); - if(reverse) - { - handler.addParameters("-R"); - } - handler.addParameters("--name-status", "--diff-filter=ADCMRUXT", "-M", diffRange); - handler.setSilent(true); - handler.setStdoutSuppressed(true); - handler.endOptions(); - if(dirtyPaths != null) - { - handler.addRelativePaths(dirtyPaths); - } - return handler; - } + @Nonnull + private static GitSimpleHandler getDiffHandler( + @Nonnull Project project, + @Nonnull VirtualFile root, + @Nonnull String diffRange, + @Nullable Collection dirtyPaths, + boolean reverse + ) { + GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.DIFF); + if (reverse) { + handler.addParameters("-R"); + } + handler.addParameters("--name-status", "--diff-filter=ADCMRUXT", "-M", diffRange); + handler.setSilent(true); + handler.setStdoutSuppressed(true); + handler.endOptions(); + if (dirtyPaths != null) { + handler.addRelativePaths(dirtyPaths); + } + return handler; + } } diff --git a/plugin/src/main/java/git4idea/checkin/GitCheckinEnvironment.java b/plugin/src/main/java/git4idea/checkin/GitCheckinEnvironment.java index 2ee09f2..5a114f0 100644 --- a/plugin/src/main/java/git4idea/checkin/GitCheckinEnvironment.java +++ b/plugin/src/main/java/git4idea/checkin/GitCheckinEnvironment.java @@ -28,6 +28,7 @@ import consulo.platform.base.localize.CommonLocalize; import consulo.project.Project; import consulo.ui.Label; +import consulo.ui.annotation.RequiredUIAccess; import consulo.ui.ex.awt.GridBag; import consulo.ui.ex.awt.JBCheckBox; import consulo.ui.ex.awt.JBUI; @@ -151,7 +152,7 @@ public RefreshableOnComponent createAdditionalOptionsPanel( @Nullable @Override public String getDefaultMessageFor(FilePath[] filesToCheckin) { - LinkedHashSet messages = new LinkedHashSet<>(); + Set messages = new LinkedHashSet<>(); GitRepositoryManager manager = getRepositoryManager(myProject); for (VirtualFile root : GitUtil.gitRoots(asList(filesToCheckin))) { GitRepository repository = manager.getRepositoryForRoot(root); @@ -235,7 +236,8 @@ public List commit( case MOVED: FilePath afterPath = change.getAfterRevision().getFile(); FilePath beforePath = change.getBeforeRevision().getFile(); - if (!Platform.current().fs().isCaseSensitive() && GitUtil.isCaseOnlyChange(beforePath.getPath(), afterPath.getPath())) { + if (!Platform.current().fs().isCaseSensitive() + && GitUtil.isCaseOnlyChange(beforePath.getPath(), afterPath.getPath())) { caseOnlyRenames.add(change); } else { @@ -435,8 +437,8 @@ private boolean mergeCommit( List exceptions, @Nonnull PartialOperation partialOperation ) { - HashSet realAdded = new HashSet<>(); - HashSet realRemoved = new HashSet<>(); + Set realAdded = new HashSet<>(); + Set realRemoved = new HashSet<>(); // perform diff GitSimpleHandler diff = new GitSimpleHandler(project, root, GitCommand.DIFF); diff.setSilent(true); @@ -625,7 +627,7 @@ private File createMessageFile(VirtualFile root, String message) throws IOExcept @Override public List scheduleMissingFileForDeletion(List files) { - ArrayList rc = new ArrayList<>(); + List rc = new ArrayList<>(); Map> sortedFiles; try { sortedFiles = GitUtil.sortFilePathsByGitRoot(files); @@ -685,7 +687,7 @@ private void commit( @Override public List scheduleUnversionedFilesForAddition(List files) { - ArrayList rc = new ArrayList<>(); + List rc = new ArrayList<>(); Map> sortedFiles; try { sortedFiles = GitUtil.sortFilesByGitRoot(files); @@ -753,7 +755,7 @@ private static Map> sortChangesByGitRoot(@Nonnul return result; } - private void markRootDirty(final VirtualFile root) { + private void markRootDirty(VirtualFile root) { // Note that the root is invalidated because changes are detected per-root anyway. // Otherwise it is not possible to detect moves. myDirtyScopeManager.dirDirtyRecursively(root); @@ -866,6 +868,7 @@ private List getUsersList(@Nonnull Project project) { } @Override + @RequiredUIAccess public void refresh() { myAmendComponent.refresh(); myAuthorField.setText(null); @@ -890,15 +893,15 @@ public void saveState() { } @Override + @RequiredUIAccess public void restoreState() { refresh(); } @Override + @RequiredUIAccess public void onChangeListSelected(LocalChangeList list) { - Object data = list.getData(); - if (data instanceof VcsFullCommitDetails) { - VcsFullCommitDetails commit = (VcsFullCommitDetails) data; + if (list.getData() instanceof VcsFullCommitDetails commit) { String author = VcsUserUtil.toExactString(commit.getAuthor()); myAuthorField.setText(author); myAuthorDate = new Date(commit.getAuthorTime()); diff --git a/plugin/src/main/java/git4idea/commands/GitHandler.java b/plugin/src/main/java/git4idea/commands/GitHandler.java index e91a5ca..2f81136 100644 --- a/plugin/src/main/java/git4idea/commands/GitHandler.java +++ b/plugin/src/main/java/git4idea/commands/GitHandler.java @@ -73,7 +73,7 @@ public abstract class GitHandler { protected final Project myProject; protected final GitCommand myCommand; - private final HashSet myIgnoredErrorCodes = new HashSet<>(); // Error codes that are ignored for the handler + private final Set myIgnoredErrorCodes = new HashSet<>(); // Error codes that are ignored for the handler private final List myErrors = Collections.synchronizedList(new ArrayList()); private final List myLastOutput = Collections.synchronizedList(new ArrayList()); private final int LAST_OUTPUT_SIZE = 5; diff --git a/plugin/src/main/java/git4idea/commands/GitHttpGuiAuthenticator.java b/plugin/src/main/java/git4idea/commands/GitHttpGuiAuthenticator.java index de98d59..aa2efe1 100644 --- a/plugin/src/main/java/git4idea/commands/GitHttpGuiAuthenticator.java +++ b/plugin/src/main/java/git4idea/commands/GitHttpGuiAuthenticator.java @@ -16,7 +16,6 @@ package git4idea.commands; import consulo.application.Application; -import consulo.application.ApplicationManager; import consulo.credentialStorage.AuthData; import consulo.credentialStorage.AuthenticationData; import consulo.credentialStorage.PasswordSafe; @@ -24,6 +23,7 @@ import consulo.credentialStorage.ui.PasswordSafePromptDialog; import consulo.logging.Logger; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.util.collection.ContainerUtil; import consulo.util.io.URLUtil; import consulo.util.io.UriUtil; @@ -31,12 +31,13 @@ import consulo.util.lang.ObjectUtil; import consulo.util.lang.Pair; import consulo.util.lang.StringUtil; -import consulo.util.lang.ref.Ref; +import consulo.util.lang.ref.SimpleReference; import git4idea.remote.GitHttpAuthDataProvider; import git4idea.remote.GitRememberedInputs; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + +import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -52,258 +53,273 @@ * @author Kirill Likhodedov */ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { - private static final Logger LOG = Logger.getInstance(GitHttpGuiAuthenticator.class); - private static final Class PASS_REQUESTER = GitHttpAuthenticator.class; - - @Nonnull - private final Project myProject; - @Nonnull - private final String myTitle; - @Nonnull - private final Collection myUrlsFromCommand; - - @Nullable - private String myPassword; - @Nullable - private String myPasswordKey; - @Nullable - private String myUnifiedUrl; - @Nullable - private String myLogin; - private boolean mySaveOnDisk; - @Nullable - private GitHttpAuthDataProvider myDataProvider; - private boolean myWasCancelled; + private static final Logger LOG = Logger.getInstance(GitHttpGuiAuthenticator.class); + private static final Class PASS_REQUESTER = GitHttpAuthenticator.class; - GitHttpGuiAuthenticator(@Nonnull Project project, @Nonnull GitCommand command, @Nonnull Collection url) { - myProject = project; - myTitle = "Git " + StringUtil.capitalize(command.name()); - myUrlsFromCommand = url; - } + @Nonnull + private final Project myProject; + @Nonnull + private final String myTitle; + @Nonnull + private final Collection myUrlsFromCommand; - @Override - @Nonnull - public String askPassword(@Nonnull String url) { - LOG.debug("askPassword. url=" + url + ", passwordKnown=" + (myPassword != null) + ", wasCancelled=" + myWasCancelled); - if (myPassword != null) { // already asked in askUsername - return myPassword; - } - if (myWasCancelled) { // already pressed cancel in askUsername - return ""; - } - myUnifiedUrl = getUnifiedUrl(url); - Pair authData = findBestAuthData(getUnifiedUrl(url)); - if (authData != null && authData.second.getPassword() != null) { - String password = authData.second.getPassword(); - myDataProvider = authData.first; - myPassword = password; - LOG.debug("askPassword. dataProvider=" + getCurrentDataProviderName() + ", unifiedUrl= " + getUnifiedUrl(url) + - ", login=" + authData.second.getLogin() + ", passwordKnown=" + (password != null)); - return password; - } + @Nullable + private String myPassword; + @Nullable + private String myPasswordKey; + @Nullable + private String myUnifiedUrl; + @Nullable + private String myLogin; + private boolean mySaveOnDisk; + @Nullable + private GitHttpAuthDataProvider myDataProvider; + private boolean myWasCancelled; - myPasswordKey = getUnifiedUrl(url); - PasswordSafePromptDialog passwordSafePromptDialog = myProject.getInstance(PasswordSafePromptDialog.class); - String password = passwordSafePromptDialog.askPassword(myTitle, - "Enter the password for " + getDisplayableUrl(url), - PASS_REQUESTER, - myPasswordKey, - false, - null); - LOG.debug("askPassword. Password was asked and returned: " + (password == null ? "NULL" : password.isEmpty() ? "EMPTY" : "NOT EMPTY")); - if (password == null) { - myWasCancelled = true; - return ""; + GitHttpGuiAuthenticator(@Nonnull Project project, @Nonnull GitCommand command, @Nonnull Collection url) { + myProject = project; + myTitle = "Git " + StringUtil.capitalize(command.name()); + myUrlsFromCommand = url; } - // Password is stored in the safe in PasswordSafePromptDialog.askPassword, - // but it is not the right behavior (incorrect password is stored too because of that) and should be fixed separately. - // We store it here manually, to let it work after that behavior is fixed. - myPassword = password; - myDataProvider = new GitDefaultHttpAuthDataProvider(); // workaround: askPassword remembers the password even it is not correct - return password; - } - @Override - @Nonnull - public String askUsername(@Nonnull String url) { - myUnifiedUrl = getUnifiedUrl(url); - Pair authData = findBestAuthData(getUnifiedUrl(url)); - String login = null; - String password = null; - if (authData != null) { - login = authData.second.getLogin(); - password = authData.second.getPassword(); - myDataProvider = authData.first; - } - LOG.debug("askUsername. dataProvider=" + getCurrentDataProviderName() + ", unifiedUrl= " + getUnifiedUrl(url) + - ", login=" + login + ", passwordKnown=" + (password != null)); - if (login != null && password != null) { - myPassword = password; - return login; - } + @Override + @Nonnull + public String askPassword(@Nonnull String url) { + LOG.debug("askPassword. url=" + url + ", passwordKnown=" + (myPassword != null) + ", wasCancelled=" + myWasCancelled); + if (myPassword != null) { // already asked in askUsername + return myPassword; + } + if (myWasCancelled) { // already pressed cancel in askUsername + return ""; + } + myUnifiedUrl = getUnifiedUrl(url); + Pair authData = findBestAuthData(getUnifiedUrl(url)); + if (authData != null && authData.second.getPassword() != null) { + String password = authData.second.getPassword(); + myDataProvider = authData.first; + myPassword = password; + LOG.debug( + "askPassword. dataProvider=" + getCurrentDataProviderName() + + ", unifiedUrl= " + getUnifiedUrl(url) + + ", login=" + authData.second.getLogin() + + ", passwordKnown=" + (password != null) + ); + return password; + } - AuthenticationData data = showAuthDialog(getDisplayableUrl(url), login); - LOG.debug("askUsername. Showed dialog:" + (data == null ? "OK" : "Cancel")); - if (data == null) { - myWasCancelled = true; - return ""; + myPasswordKey = getUnifiedUrl(url); + PasswordSafePromptDialog passwordSafePromptDialog = myProject.getInstance(PasswordSafePromptDialog.class); + String password = passwordSafePromptDialog.askPassword( + myTitle, + "Enter the password for " + getDisplayableUrl(url), + PASS_REQUESTER, + myPasswordKey, + false, + null + ); + LOG.debug("askPassword. Password was asked and returned: " + (password == null ? "NULL" : password.isEmpty() ? "EMPTY" : "NOT EMPTY")); + if (password == null) { + myWasCancelled = true; + return ""; + } + // Password is stored in the safe in PasswordSafePromptDialog.askPassword, + // but it is not the right behavior (incorrect password is stored too because of that) and should be fixed separately. + // We store it here manually, to let it work after that behavior is fixed. + myPassword = password; + myDataProvider = new GitDefaultHttpAuthDataProvider(); // workaround: askPassword remembers the password even it is not correct + return password; } - // remember values to store in the database afterwards, if authentication succeeds - myPassword = new String(data.getPassword()); - myLogin = data.getLogin(); - mySaveOnDisk = data.isRememberPassword(); - myPasswordKey = makeKey(myUnifiedUrl, myLogin); + @Nonnull + @Override + @RequiredUIAccess + public String askUsername(@Nonnull String url) { + myUnifiedUrl = getUnifiedUrl(url); + Pair authData = findBestAuthData(getUnifiedUrl(url)); + String login = null; + String password = null; + if (authData != null) { + login = authData.second.getLogin(); + password = authData.second.getPassword(); + myDataProvider = authData.first; + } + LOG.debug("askUsername. dataProvider=" + getCurrentDataProviderName() + ", unifiedUrl= " + getUnifiedUrl(url) + + ", login=" + login + ", passwordKnown=" + (password != null)); + if (login != null && password != null) { + myPassword = password; + return login; + } - return myLogin; - } + AuthenticationData data = showAuthDialog(getDisplayableUrl(url), login); + LOG.debug("askUsername. Showed dialog:" + (data == null ? "OK" : "Cancel")); + if (data == null) { + myWasCancelled = true; + return ""; + } - @Nullable - private AuthenticationData showAuthDialog(final String url, final String login) { - final Ref dialog = Ref.create(); - ApplicationManager.getApplication().invokeAndWait(new Runnable() { - @Override - public void run() { - AuthDialog authDialog = myProject.getInstance(AuthDialog.class); - AuthenticationData data = authDialog.show(myTitle, "Enter credentials for " + url, login, null, true); - dialog.set(data); - } - }, Application.get().getAnyModalityState()); - return dialog.get(); - } + // remember values to store in the database afterwards, if authentication succeeds + myPassword = new String(data.getPassword()); + myLogin = data.getLogin(); + mySaveOnDisk = data.isRememberPassword(); + myPasswordKey = makeKey(myUnifiedUrl, myLogin); - @Override - public void saveAuthData() { - // save login and url - if (myUnifiedUrl != null && myLogin != null) { - GitRememberedInputs.getInstance().addUrl(myUnifiedUrl, myLogin); + return myLogin; } - // save password - if (myPasswordKey != null && myPassword != null && mySaveOnDisk) { - PasswordSafe passwordSafe = PasswordSafe.getInstance(); - passwordSafe.storePassword(myProject, PASS_REQUESTER, myPasswordKey, myPassword); + @Nullable + @RequiredUIAccess + private AuthenticationData showAuthDialog(String url, String login) { + SimpleReference dialog = SimpleReference.create(); + Application application = Application.get(); + application.invokeAndWait( + () -> { + AuthDialog authDialog = myProject.getInstance(AuthDialog.class); + AuthenticationData data = authDialog.show(myTitle, "Enter credentials for " + url, login, null, true); + dialog.set(data); + }, + application.getAnyModalityState() + ); + return dialog.get(); } - } - @Override - public void forgetPassword() { - LOG.debug("forgetPassword. dataProvider=" + getCurrentDataProviderName() + ", unifiedUrl=" + myUnifiedUrl); - if (myDataProvider != null && myUnifiedUrl != null) { - myDataProvider.forgetPassword(myUnifiedUrl); + @Override + public void saveAuthData() { + // save login and url + if (myUnifiedUrl != null && myLogin != null) { + GitRememberedInputs.getInstance().addUrl(myUnifiedUrl, myLogin); + } + + // save password + if (myPasswordKey != null && myPassword != null && mySaveOnDisk) { + PasswordSafe passwordSafe = PasswordSafe.getInstance(); + passwordSafe.storePassword(myProject, PASS_REQUESTER, myPasswordKey, myPassword); + } } - } - @Nullable - private String getCurrentDataProviderName() { - return myDataProvider == null ? null : myDataProvider.getClass().getName(); - } + @Override + public void forgetPassword() { + LOG.debug("forgetPassword. dataProvider=" + getCurrentDataProviderName() + ", unifiedUrl=" + myUnifiedUrl); + if (myDataProvider != null && myUnifiedUrl != null) { + myDataProvider.forgetPassword(myUnifiedUrl); + } + } - @Override - public boolean wasCancelled() { - return myWasCancelled; - } + @Nullable + private String getCurrentDataProviderName() { + return myDataProvider == null ? null : myDataProvider.getClass().getName(); + } - /** - * Get the URL to display to the user in the authentication dialog. - */ - @Nonnull - private String getDisplayableUrl(@Nullable String urlFromGit) { - return !StringUtil.isEmptyOrSpaces(urlFromGit) ? urlFromGit : findPresetHttpUrl(); - } + @Override + public boolean wasCancelled() { + return myWasCancelled; + } - /** - * Get the URL to be used as the authentication data identifier in the password safe and the settings. - */ - @Nonnull - private String getUnifiedUrl(@Nullable String urlFromGit) { - return changeHttpsToHttp(StringUtil.isEmptyOrSpaces(urlFromGit) ? findPresetHttpUrl() : urlFromGit); - } + /** + * Get the URL to display to the user in the authentication dialog. + */ + @Nonnull + private String getDisplayableUrl(@Nullable String urlFromGit) { + return !StringUtil.isEmptyOrSpaces(urlFromGit) ? urlFromGit : findPresetHttpUrl(); + } - @Nonnull - private String findPresetHttpUrl() { - return ObjectUtil.chooseNotNull(ContainerUtil.find(myUrlsFromCommand, url -> { - String scheme = UriUtil.splitScheme(url).getFirst(); - return scheme.startsWith("http"); - }), ContainerUtil.getFirstItem(myUrlsFromCommand)); - } + /** + * Get the URL to be used as the authentication data identifier in the password safe and the settings. + */ + @Nonnull + private String getUnifiedUrl(@Nullable String urlFromGit) { + return changeHttpsToHttp(StringUtil.isEmptyOrSpaces(urlFromGit) ? findPresetHttpUrl() : urlFromGit); + } - /** - * If the url scheme is HTTPS, store it as HTTP in the database, not to make user enter and remember same credentials twice. - */ - @Nonnull - private static String changeHttpsToHttp(@Nonnull String url) { - String prefix = "https"; - if (url.startsWith(prefix)) { - return "http" + url.substring(prefix.length()); + @Nonnull + private String findPresetHttpUrl() { + return ObjectUtil.chooseNotNull( + ContainerUtil.find( + myUrlsFromCommand, + url -> { + String scheme = UriUtil.splitScheme(url).getFirst(); + return scheme.startsWith("http"); + } + ), + ContainerUtil.getFirstItem(myUrlsFromCommand) + ); } - return url; - } - // return the first that knows username + password; otherwise return the first that knows just the username - @Nullable - private Pair findBestAuthData(@Nonnull String url) { - Pair candidate = null; - for (GitHttpAuthDataProvider provider : getProviders()) { - AuthData data = provider.getAuthData(url); - if (data != null) { - Pair pair = Pair.create(provider, data); - if (data.getPassword() != null) { - return pair; + /** + * If the url scheme is HTTPS, store it as HTTP in the database, not to make user enter and remember same credentials twice. + */ + @Nonnull + private static String changeHttpsToHttp(@Nonnull String url) { + String prefix = "https"; + if (url.startsWith(prefix)) { + return "http" + url.substring(prefix.length()); } - if (candidate == null) { - candidate = pair; - } - } + return url; } - return candidate; - } - @Nonnull - private List getProviders() { - List providers = ContainerUtil.newArrayList(); - providers.add(new GitDefaultHttpAuthDataProvider()); - providers.addAll(GitHttpAuthDataProvider.EP_NAME.getExtensionList()); - return providers; - } + // return the first that knows username + password; otherwise return the first that knows just the username + @Nullable + private Pair findBestAuthData(@Nonnull String url) { + Pair candidate = null; + for (GitHttpAuthDataProvider provider : getProviders()) { + AuthData data = provider.getAuthData(url); + if (data != null) { + Pair pair = Pair.create(provider, data); + if (data.getPassword() != null) { + return pair; + } + if (candidate == null) { + candidate = pair; + } + } + } + return candidate; + } - /** - * Makes the password database key for the URL: inserts the login after the scheme: http://login@url. - */ - @Nonnull - private static String makeKey(@Nonnull String url, @Nullable String login) { - if (login == null) { - return url; + @Nonnull + private List getProviders() { + List providers = new ArrayList<>(); + providers.add(new GitDefaultHttpAuthDataProvider()); + providers.addAll(Application.get().getExtensionList(GitHttpAuthDataProvider.class)); + return providers; } - Couple pair = UriUtil.splitScheme(url); - String scheme = pair.getFirst(); - if (!StringUtil.isEmpty(scheme)) { - return scheme + URLUtil.SCHEME_SEPARATOR + login + "@" + pair.getSecond(); + + /** + * Makes the password database key for the URL: inserts the login after the scheme: http://login@url. + */ + @Nonnull + private static String makeKey(@Nonnull String url, @Nullable String login) { + if (login == null) { + return url; + } + Couple pair = UriUtil.splitScheme(url); + String scheme = pair.getFirst(); + if (!StringUtil.isEmpty(scheme)) { + return scheme + URLUtil.SCHEME_SEPARATOR + login + "@" + pair.getSecond(); + } + return login + "@" + url; } - return login + "@" + url; - } - public class GitDefaultHttpAuthDataProvider implements GitHttpAuthDataProvider { + public class GitDefaultHttpAuthDataProvider implements GitHttpAuthDataProvider { - @Nullable - @Override - public AuthData getAuthData(@Nonnull String url) { - String userName = getUsername(url); - String key = makeKey(url, userName); - final PasswordSafe passwordSafe = PasswordSafe.getInstance(); - String password = passwordSafe.getPassword(myProject, PASS_REQUESTER, key); - return new AuthData(StringUtil.notNullize(userName), password); - } + @Nullable + @Override + public AuthData getAuthData(@Nonnull String url) { + String userName = getUsername(url); + String key = makeKey(url, userName); + PasswordSafe passwordSafe = PasswordSafe.getInstance(); + String password = passwordSafe.getPassword(myProject, PASS_REQUESTER, key); + return new AuthData(StringUtil.notNullize(userName), password); + } - @Nullable - private String getUsername(@Nonnull String url) { - return GitRememberedInputs.getInstance().getUserNameForUrl(url); - } + @Nullable + private String getUsername(@Nonnull String url) { + return GitRememberedInputs.getInstance().getUserNameForUrl(url); + } - @Override - public void forgetPassword(@Nonnull String url) { - String key = myPasswordKey != null ? myPasswordKey : makeKey(url, getUsername(url)); - PasswordSafe.getInstance().storePassword(myProject, PASS_REQUESTER, key, null); + @Override + public void forgetPassword(@Nonnull String url) { + String key = myPasswordKey != null ? myPasswordKey : makeKey(url, getUsername(url)); + PasswordSafe.getInstance().storePassword(myProject, PASS_REQUESTER, key, null); + } } - } } diff --git a/plugin/src/main/java/git4idea/config/GitOptionsTopHitProvider.java b/plugin/src/main/java/git4idea/config/GitOptionsTopHitProvider.java index f933e07..ca804b0 100644 --- a/plugin/src/main/java/git4idea/config/GitOptionsTopHitProvider.java +++ b/plugin/src/main/java/git4idea/config/GitOptionsTopHitProvider.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.function.BiConsumer; import java.util.function.Function; @@ -53,7 +54,7 @@ public Collection getOptions(@Nullable ComponentManage for (VcsDescriptor descriptor : ProjectLevelVcsManager.getInstance(project).getAllVcss()) { if (GitVcs.ID.equals(descriptor.getId())) { final GitVcsSettings settings = GitVcsSettings.getInstance(project); - ArrayList options = new ArrayList<>(); + List options = new ArrayList<>(); options.add(option( project, "Git: Commit automatically on cherry-pick", diff --git a/plugin/src/main/java/git4idea/config/GitVersion.java b/plugin/src/main/java/git4idea/config/GitVersion.java index cb81d5f..4131302 100644 --- a/plugin/src/main/java/git4idea/config/GitVersion.java +++ b/plugin/src/main/java/git4idea/config/GitVersion.java @@ -138,7 +138,7 @@ private static int getIntGroup(@Nonnull Matcher matcher, int group) { if (group > matcher.groupCount() + 1) { return 0; } - final String match = matcher.group(group); + String match = matcher.group(group); if (match == null) { return 0; } @@ -152,10 +152,8 @@ public static GitVersion identifyVersion(@Nonnull String gitExecutable) throws T } @Nonnull - public static GitVersion identifyVersion( - @Nonnull String gitExecutable, - @Nullable ProgressIndicator indicator - ) throws TimeoutException, ExecutionException, ParseException { + public static GitVersion identifyVersion(@Nonnull String gitExecutable, @Nullable ProgressIndicator indicator) + throws TimeoutException, ExecutionException, ParseException { UIAccess.assetIsNotUIThread(); GeneralCommandLine commandLine = new GeneralCommandLine(); @@ -199,14 +197,10 @@ public boolean isSupported() { */ @Override public boolean equals(Object obj) { - if (!(obj instanceof GitVersion)) { - return false; - } - GitVersion other = (GitVersion)obj; - if (compareTo(other) != 0) { - return false; - } - return myType == Type.UNDEFINED || other.myType == Type.UNDEFINED || myType == other.myType; + return obj == this + || obj instanceof GitVersion other + && compareTo(other) == 0 + && (myType == Type.UNDEFINED || other.myType == Type.UNDEFINED || myType == other.myType); } /** @@ -273,7 +267,7 @@ public String getSemanticPresentation() { /** * @return true if this version is older or the same than the given one. */ - public boolean isOlderOrEqual(final GitVersion gitVersion) { + public boolean isOlderOrEqual(GitVersion gitVersion) { return gitVersion != null && compareTo(gitVersion) <= 0; } diff --git a/plugin/src/main/java/git4idea/diff/GitTreeDiffProvider.java b/plugin/src/main/java/git4idea/diff/GitTreeDiffProvider.java index aeb8626..25de99b 100644 --- a/plugin/src/main/java/git4idea/diff/GitTreeDiffProvider.java +++ b/plugin/src/main/java/git4idea/diff/GitTreeDiffProvider.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package git4idea.diff; import consulo.logging.Logger; @@ -39,19 +38,19 @@ public class GitTreeDiffProvider implements TreeDiffProvider { private final static Logger LOG = Logger.getInstance(GitTreeDiffProvider.class); private final Project myProject; - public GitTreeDiffProvider(final Project project) { + public GitTreeDiffProvider(Project project) { myProject = project; } @Override - public Collection getRemotelyChanged(final VirtualFile vcsRoot, final Collection paths) { + public Collection getRemotelyChanged(VirtualFile vcsRoot, Collection paths) { try { - final GitBranchesSearcher searcher = new GitBranchesSearcher(myProject, vcsRoot, true); + GitBranchesSearcher searcher = new GitBranchesSearcher(myProject, vcsRoot, true); if (searcher.getLocal() == null || searcher.getRemote() == null) { return Collections.emptyList(); } - ArrayList rc = new ArrayList<>(); - final Collection files = new ArrayList<>(paths.size()); + List rc = new ArrayList<>(); + Collection files = new ArrayList<>(paths.size()); for (String path : paths) { files.add(VcsUtil.getFilePath(path)); } diff --git a/plugin/src/main/java/git4idea/history/GitHistoryUtils.java b/plugin/src/main/java/git4idea/history/GitHistoryUtils.java index 7620af4..75727e4 100644 --- a/plugin/src/main/java/git4idea/history/GitHistoryUtils.java +++ b/plugin/src/main/java/git4idea/history/GitHistoryUtils.java @@ -17,7 +17,6 @@ import consulo.application.Application; import consulo.application.util.Semaphore; -import consulo.application.util.function.Computable; import consulo.application.util.registry.Registry; import consulo.component.ProcessCanceledException; import consulo.git.localize.GitLocalize; @@ -31,7 +30,7 @@ import consulo.util.collection.SmartList; import consulo.util.dataholder.Key; import consulo.util.lang.*; -import consulo.util.lang.ref.Ref; +import consulo.util.lang.ref.SimpleReference; import consulo.versionControlSystem.FilePath; import consulo.versionControlSystem.VcsException; import consulo.versionControlSystem.change.Change; @@ -66,6 +65,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import static git4idea.history.GitLogParser.GitLogOption.*; @@ -110,7 +110,7 @@ public static VcsRevisionNumber getCurrentRevision( if (result.length() == 0) { return null; } - final GitLogRecord record = parser.parseOneRecord(result); + GitLogRecord record = parser.parseOneRecord(result); if (record == null) { return null; } @@ -135,13 +135,13 @@ public static VcsRevisionDescription getCurrentRevisionDescription( if (result.length() == 0) { return null; } - final GitLogRecord record = parser.parseOneRecord(result); + GitLogRecord record = parser.parseOneRecord(result); if (record == null) { return null; } record.setUsedHandler(h); - final String author = Comparing.equal(record.getAuthorName(), record.getCommitterName()) + String author = Comparing.equal(record.getAuthorName(), record.getCommitterName()) ? record.getAuthorName() : record.getAuthorName() + " (" + record.getCommitterName() + ")"; return new VcsRevisionDescriptionImpl( @@ -183,7 +183,7 @@ public static ItemLatestState getLastRevision(@Nonnull Project project, @Nonnull if (record == null) { return null; } - final List changes = record.parseChanges(project, root); + List changes = record.parseChanges(project, root); boolean exists = changes.isEmpty() || !FileStatus.DELETED.equals(changes.get(0).getFileStatus()); record.setUsedHandler(h); return new ItemLatestState(new GitRevisionNumber(record.getHash(), record.getDate()), exists, false); @@ -246,8 +246,8 @@ public static void history( String... parameters ) { // adjust path using change manager - final FilePath filePath = getLastCommitName(project, path); - final VirtualFile finalRoot; + FilePath filePath = getLastCommitName(project, path); + VirtualFile finalRoot; try { finalRoot = (root == null ? GitUtil.getGitRoot(filePath) : root); } @@ -255,7 +255,7 @@ public static void history( exceptionConsumer.accept(e); return; } - final GitLogParser logParser = new GitLogParser( + GitLogParser logParser = new GitLogParser( project, GitLogParser.NameStatus.STATUS, HASH, @@ -271,11 +271,11 @@ public static void history( AUTHOR_TIME ); - final AtomicReference firstCommit = new AtomicReference<>(startingRevision.asString()); - final AtomicReference firstCommitParent = new AtomicReference<>(firstCommit.get()); - final AtomicReference currentPath = new AtomicReference<>(filePath); - final AtomicReference logHandler = new AtomicReference<>(); - final AtomicBoolean skipFurtherOutput = new AtomicBoolean(); + AtomicReference firstCommit = new AtomicReference<>(startingRevision.asString()); + AtomicReference firstCommitParent = new AtomicReference<>(firstCommit.get()); + AtomicReference currentPath = new AtomicReference<>(filePath); + AtomicReference logHandler = new AtomicReference<>(); + AtomicBoolean skipFurtherOutput = new AtomicBoolean(); final Consumer resultAdapter = record -> { @@ -287,20 +287,20 @@ public static void history( return; } record.setUsedHandler(logHandler.get()); - final GitRevisionNumber revision = new GitRevisionNumber(record.getHash(), record.getDate()); + GitRevisionNumber revision = new GitRevisionNumber(record.getHash(), record.getDate()); firstCommit.set(record.getHash()); - final String[] parentHashes = record.getParentsHashes(); + String[] parentHashes = record.getParentsHashes(); if (parentHashes.length < 1) { firstCommitParent.set(null); } else { firstCommitParent.set(parentHashes[0]); } - final String message = record.getFullMessage(); + String message = record.getFullMessage(); FilePath revisionPath; try { - final List paths = record.getFilePaths(finalRoot); + List paths = record.getFilePaths(finalRoot); if (paths.size() > 0) { revisionPath = paths.get(0); } @@ -348,7 +348,7 @@ public static void history( logHandler.get().addLineListener(new GitLineHandlerAdapter() { @Override public void onLineAvailable(String line, Key outputType) { - final GitLogRecord record = accumulator.acceptLine(line); + GitLogRecord record = accumulator.acceptLine(line); if (record != null) { resultAdapter.accept(record); } @@ -370,7 +370,7 @@ public void startFailed(Throwable exception) { public void processTerminated(int exitCode) { try { super.processTerminated(exitCode); - final GitLogRecord record = accumulator.processLast(); + GitLogRecord record = accumulator.processLast(); if (record != null) { resultAdapter.accept(record); } @@ -417,7 +417,7 @@ private static GitLineHandler getLogHandler( @Nonnull String lastCommit, String... parameters ) { - final GitLineHandler h = new GitLineHandler(project, root, GitCommand.LOG); + GitLineHandler h = new GitLineHandler(project, root, GitCommand.LOG); h.setStdoutSuppressed(true); h.addParameters("--name-status", parser.getPretty(), "--encoding=UTF-8", lastCommit); if (GitVersionSpecialty.FULL_HISTORY_SIMPLIFY_MERGES_WORKS_CORRECTLY.existsIn(version) @@ -447,8 +447,8 @@ private static Pair getFirstCommitParentAndPathIfRename( ) throws VcsException { // 'git show -M --name-status ' returns the information about commit and detects renames. // NB: we can't specify the filepath, because then rename detection will work only with the '--follow' option, which we don't wanna use. - final GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.SHOW); - final GitLogParser parser = new GitLogParser(project, GitLogParser.NameStatus.STATUS, HASH, COMMIT_TIME, PARENTS); + GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.SHOW); + GitLogParser parser = new GitLogParser(project, GitLogParser.NameStatus.STATUS, HASH, COMMIT_TIME, PARENTS); h.setStdoutSuppressed(true); h.addParameters("-M", "--name-status", parser.getPretty(), "--encoding=UTF-8", commit); if (!GitVersionSpecialty.FOLLOW_IS_BUGGY_IN_THE_LOG.existsIn(version)) { @@ -459,18 +459,18 @@ private static Pair getFirstCommitParentAndPathIfRename( else { h.endOptions(); } - final String output = h.run(); - final List records = parser.parse(output); + String output = h.run(); + List records = parser.parse(output); if (records.isEmpty()) { return null; } // we have information about all changed files of the commit. Extracting information about the file we need. GitLogRecord record = records.get(0); - final List changes = record.parseChanges(project, root); + List changes = record.parseChanges(project, root); for (Change change : changes) { if ((change.isMoved() || change.isRenamed()) && filePath.equals(ObjectUtil.notNull(change.getAfterRevision()).getFile())) { - final String[] parents = record.getParentsHashes(); + String[] parents = record.getParentsHashes(); String parent = parents.length > 0 ? parents[0] : null; return Pair.create(parent, ObjectUtil.notNull(change.getBeforeRevision()).getFile()); } @@ -484,7 +484,7 @@ public static List readMiniDetails( @Nonnull VirtualFile root, @Nonnull List hashes ) throws VcsException { - final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); + VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); if (factory == null) { return Collections.emptyList(); } @@ -540,7 +540,7 @@ public static List readLastCommits( @Nonnull VirtualFile root, @Nonnull String... refs ) throws VcsException { - final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); + VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); if (factory == null) { return null; } @@ -597,7 +597,7 @@ private static void processHandlerOutputByLine( @Nonnull GitLogParser parser, @Nonnull Consumer recordConsumer ) throws VcsException { - Ref parseError = new Ref<>(); + SimpleReference parseError = new SimpleReference<>(); processHandlerOutputByLine( handler, builder -> { @@ -646,13 +646,13 @@ public static void readCommits( @Nonnull Consumer refConsumer, @Nonnull Consumer commitConsumer ) throws VcsException { - final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); + VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); if (factory == null) { return; } GitLineHandler h = new GitLineHandler(project, root, GitCommand.LOG); - final GitLogParser parser = + GitLogParser parser = new GitLogParser(project, GitLogParser.NameStatus.NONE, HASH, PARENTS, COMMIT_TIME, AUTHOR_NAME, AUTHOR_EMAIL, REF_NAMES); h.setStdoutSuppressed(true); h.addParameters(parser.getPretty(), "--encoding=UTF-8"); @@ -660,7 +660,7 @@ public static void readCommits( h.addParameters(parameters); h.endOptions(); - final int COMMIT_BUFFER = 1000; + int COMMIT_BUFFER = 1000; processHandlerOutputByLine( h, buffer -> { @@ -731,7 +731,7 @@ private static Collection parseRefs( @Nullable private static VcsLogObjectsFactory getObjectsFactoryWithDisposeCheck(@Nonnull Project project) { - return Application.get().runReadAction((Computable)() -> { + return Application.get().runReadAction((Supplier) () -> { if (!project.isDisposed()) { return ServiceManager.getService(project, VcsLogObjectsFactory.class); } @@ -753,7 +753,7 @@ public MyTokenAccumulator(@Nonnull GitLogParser parser) { @Nullable public GitLogRecord acceptLine(String s) { - final boolean recordStart = s.startsWith(GitLogParser.RECORD_START); + boolean recordStart = s.startsWith(GitLogParser.RECORD_START); if (recordStart) { s = s.substring(GitLogParser.RECORD_START.length()); } @@ -766,7 +766,7 @@ public GitLogRecord acceptLine(String s) { return null; } else if (recordStart) { - final String line = myBuffer.toString(); + String line = myBuffer.toString(); myBuffer.setLength(0); myBuffer.append(s); @@ -801,12 +801,9 @@ private GitLogRecord processResult(@Nonnull String line) { * @throws VcsException if there is problem with running git */ @Nonnull - public static List history( - @Nonnull Project project, - @Nonnull FilePath path, - String... parameters - ) throws VcsException { - final VirtualFile root = GitUtil.getGitRoot(path); + public static List history(@Nonnull Project project, @Nonnull FilePath path, String... parameters) + throws VcsException { + VirtualFile root = GitUtil.getGitRoot(path); return history(project, path, root, parameters); } @@ -828,8 +825,8 @@ public static List history( @Nonnull VcsRevisionNumber startingFrom, String... parameters ) throws VcsException { - final List rc = new ArrayList<>(); - final List exceptions = new ArrayList<>(); + List rc = new ArrayList<>(); + List exceptions = new ArrayList<>(); history(project, path, root, startingFrom, rc::add, exceptions::add, parameters); if (!exceptions.isEmpty()) { @@ -849,7 +846,7 @@ public static List> onlyHashesHistory( @Nonnull FilePath path, String... parameters ) throws VcsException { - final VirtualFile root = GitUtil.getGitRoot(path); + VirtualFile root = GitUtil.getGitRoot(path); return onlyHashesHistory(project, path, root, parameters); } @@ -875,7 +872,7 @@ public static List> onlyHashesHistory( h.addRelativePaths(path); String output = h.run(); - final List> rc = new ArrayList<>(); + List> rc = new ArrayList<>(); for (GitLogRecord record : parser.parse(output)) { record.setUsedHandler(h); rc.add(Pair.create(new SHAHash(record.getHash()), record.getDate())); @@ -885,27 +882,32 @@ public static List> onlyHashesHistory( @Nonnull public static VcsLogProvider.DetailedLogData loadMetadata( - @Nonnull final Project project, - @Nonnull final VirtualFile root, + @Nonnull Project project, + @Nonnull VirtualFile root, String... params ) throws VcsException { - final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); + VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); if (factory == null) { return LogDataImpl.empty(); } - final Set refs = Sets.newHashSet(GitLogProvider.DONT_CONSIDER_SHA); - final List commits = collectDetails(project, root, true, false, record -> - { - GitCommit commit = createCommit(project, root, record, factory); - Collection refsInRecord = parseRefs(record.getRefs(), commit.getId(), factory, root); - for (VcsRef ref : refsInRecord) { - if (!refs.add(ref)) { - VcsRef otherRef = ContainerUtil.find(refs, r -> GitLogProvider.DONT_CONSIDER_SHA.equals(r, ref)); - LOG.error("Adding duplicate element " + ref + " to the set containing " + otherRef); + Set refs = Sets.newHashSet(GitLogProvider.DONT_CONSIDER_SHA); + List commits = collectDetails(project, + root, + true, + false, + record -> { + GitCommit commit = createCommit(project, root, record, factory); + Collection refsInRecord = parseRefs(record.getRefs(), commit.getId(), factory, root); + for (VcsRef ref : refsInRecord) { + if (!refs.add(ref)) { + VcsRef otherRef = ContainerUtil.find(refs, r -> GitLogProvider.DONT_CONSIDER_SHA.equals(r, ref)); + LOG.error("Adding duplicate element " + ref + " to the set containing " + otherRef); + } } - } - return commit; - }, params); + return commit; + }, + params + ); return new LogDataImpl(refs, commits); } @@ -917,7 +919,7 @@ public static VcsLogProvider.DetailedLogData loadMetadata( */ @Nonnull public static List history(@Nonnull Project project, @Nonnull VirtualFile root, String... parameters) throws VcsException { - final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); + VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); if (factory == null) { return Collections.emptyList(); } @@ -990,7 +992,7 @@ public static void loadDetails( @Nonnull Consumer commitConsumer, @Nonnull String... parameters ) throws VcsException { - final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); + VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); if (factory == null) { return; } @@ -1051,11 +1053,11 @@ private static GitHeavyCommit createCommit( @Nonnull VirtualFile root, @Nonnull GitLogRecord record ) throws VcsException { - final Collection currentRefs = record.getRefs(); + Collection currentRefs = record.getRefs(); List locals = new ArrayList<>(); List remotes = new ArrayList<>(); List tags = new ArrayList<>(); - final String s = parseRefs(refs, currentRefs, locals, remotes, tags); + String s = parseRefs(refs, currentRefs, locals, remotes, tags); GitHeavyCommit gitCommit = new GitHeavyCommit( root, @@ -1092,7 +1094,7 @@ private static String parseRefs( return null; } for (String ref : currentRefs) { - final SymbolicRefs.Kind kind = refs.getKind(ref); + SymbolicRefs.Kind kind = refs.getKind(ref); if (SymbolicRefs.Kind.LOCAL.equals(kind)) { locals.add(ref); } @@ -1142,9 +1144,9 @@ public static List commitsDetails( h.addParameters(new ArrayList<>(commitsIds)); String output = h.run(); - final List rc = new ArrayList<>(); + List rc = new ArrayList<>(); for (GitLogRecord record : parser.parse(output)) { - final GitHeavyCommit gitCommit = createCommit(project, refs, root, record); + GitHeavyCommit gitCommit = createCommit(project, refs, root, record); rc.add(gitCommit); } return rc; @@ -1153,7 +1155,7 @@ public static List commitsDetails( public static long getAuthorTime(@Nonnull Project project, @Nonnull FilePath path, @Nonnull String commitsId) throws VcsException { // adjust path using change manager path = getLastCommitName(project, path); - final VirtualFile root = GitUtil.getGitRoot(path); + VirtualFile root = GitUtil.getGitRoot(path); GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.SHOW); GitLogParser parser = new GitLogParser(project, GitLogParser.NameStatus.STATUS, AUTHOR_TIME); h.setSilent(true); @@ -1179,8 +1181,8 @@ public static FilePath getLastCommitName(@Nonnull Project project, FilePath path if (project.isDefault()) { return path; } - final ChangeListManager changeManager = ChangeListManager.getInstance(project); - final Change change = changeManager.getChange(path); + ChangeListManager changeManager = ChangeListManager.getInstance(project); + Change change = changeManager.getChange(path); if (change != null && change.getType() == Change.Type.MOVED) { // GitContentRevision r = (GitContentRevision)change.getBeforeRevision(); assert change.getBeforeRevision() != null : "Move change always have beforeRevision"; @@ -1312,8 +1314,8 @@ public void startFailed(Throwable exception) { public void reportErrors() throws VcsException { if (myException != null) { - if (myException.getCause() instanceof ProcessCanceledException) { - throw (ProcessCanceledException)myException.getCause(); + if (myException.getCause() instanceof ProcessCanceledException processCanceledException) { + throw processCanceledException; } throw myException; } diff --git a/plugin/src/main/java/git4idea/log/GitLogProvider.java b/plugin/src/main/java/git4idea/log/GitLogProvider.java index d6f2dc0..efc993f 100644 --- a/plugin/src/main/java/git4idea/log/GitLogProvider.java +++ b/plugin/src/main/java/git4idea/log/GitLogProvider.java @@ -60,10 +60,9 @@ @ExtensionImpl public class GitLogProvider implements VcsLogProvider { - private static final Logger LOG = Logger.getInstance(GitLogProvider.class); public static final Function GET_TAG_NAME = ref -> ref.getType() == GitRefManager.TAG ? ref.getName() : null; - public static final HashingStrategy DONT_CONSIDER_SHA = new HashingStrategy() { + public static final HashingStrategy DONT_CONSIDER_SHA = new HashingStrategy<>() { @Override public int hashCode(@Nonnull VcsRef ref) { return 31 * ref.getName().hashCode() + ref.getType().hashCode(); @@ -89,9 +88,11 @@ public boolean equals(@Nonnull VcsRef ref1, @Nonnull VcsRef ref2) { private final VcsLogObjectsFactory myVcsObjectsFactory; @Inject - public GitLogProvider(@Nonnull Project project, - @Nonnull VcsLogObjectsFactory factory, - @Nonnull GitUserRegistry userRegistry) { + public GitLogProvider( + @Nonnull Project project, + @Nonnull VcsLogObjectsFactory factory, + @Nonnull GitUserRegistry userRegistry + ) { myProject = project; myRepositoryManager = GitRepositoryManager.getInstance(project); myUserRegistry = userRegistry; @@ -120,7 +121,7 @@ public DetailedLogData readFirstBlock(@Nonnull VirtualFile root, @Nonnull Requir // NB: not specifying --tags, because it introduces great slowdown if there are many tags, // but makes sense only if there are heads without branch or HEAD labels (rare case). Such cases are partially handled below. - boolean refresh = requirements instanceof VcsLogProviderRequirementsEx && ((VcsLogProviderRequirementsEx) requirements).isRefresh(); + boolean refresh = requirements instanceof VcsLogProviderRequirementsEx requirementsEx && requirementsEx.isRefresh(); DetailedLogData data = GitHistoryUtils.loadMetadata(myProject, root, params); @@ -170,13 +171,15 @@ public DetailedLogData readFirstBlock(@Nonnull VirtualFile root, @Nonnull Requir return new LogDataImpl(allRefs.getValues(), sortedCommits); } - private static void validateDataAndReportError(@Nonnull final VirtualFile root, - @Nonnull final Interner allRefs, - @Nonnull final List sortedCommits, - @Nonnull final DetailedLogData firstBlockSyncData, - @Nonnull final Set manuallyReadBranches, - @Nullable final Set currentTagNames, - @Nullable final DetailedLogData commitsFromTags) { + private static void validateDataAndReportError( + @Nonnull final VirtualFile root, + @Nonnull final Interner allRefs, + @Nonnull final List sortedCommits, + @Nonnull final DetailedLogData firstBlockSyncData, + @Nonnull final Set manuallyReadBranches, + @Nullable final Set currentTagNames, + @Nullable final DetailedLogData commitsFromTags + ) { StopWatch sw = StopWatch.start("validating data in " + root.getName()); final Set refs = ContainerUtil.map2Set(allRefs.getValues(), VcsRef::getCommitHash); @@ -194,14 +197,18 @@ public int getColorOfFragment(@Nonnull Hash headCommit, int magicIndex) { @Override public int compareHeads(@Nonnull Hash head1, @Nonnull Hash head2) { if (!refs.contains(head1) || !refs.contains(head2)) { - LOG.error("GitLogProvider returned inconsistent data", - AttachmentFactory.get().create("error-details.txt", printErrorDetails(root, + LOG.error( + "GitLogProvider returned inconsistent data", + AttachmentFactory.get().create("error-details.txt", printErrorDetails( + root, allRefs, sortedCommits, firstBlockSyncData, manuallyReadBranches, currentTagNames, - commitsFromTags))); + commitsFromTags + )) + ); } return 0; } @@ -210,13 +217,15 @@ public int compareHeads(@Nonnull Hash head1, @Nonnull Hash head2) { } @SuppressWarnings("StringConcatenationInsideStringBufferAppend") - private static String printErrorDetails(@Nonnull VirtualFile root, - @Nonnull Interner allRefs, - @Nonnull List sortedCommits, - @Nonnull DetailedLogData firstBlockSyncData, - @Nonnull Set manuallyReadBranches, - @Nullable Set currentTagNames, - @Nullable DetailedLogData commitsFromTags) { + private static String printErrorDetails( + @Nonnull VirtualFile root, + @Nonnull Interner allRefs, + @Nonnull List sortedCommits, + @Nonnull DetailedLogData firstBlockSyncData, + @Nonnull Set manuallyReadBranches, + @Nullable Set currentTagNames, + @Nullable DetailedLogData commitsFromTags + ) { StringBuilder sb = new StringBuilder(); sb.append("[" + root.getName() + "]\n"); @@ -248,9 +257,11 @@ private static String printErrorDetails(@Nonnull VirtualFile root, @Nonnull private static String printLogData(@Nonnull DetailedLogData firstBlockSyncData) { - return String.format("Last 100 commits:\n%s\nRefs:\n%s", + return String.format( + "Last 100 commits:\n%s\nRefs:\n%s", printCommits(firstBlockSyncData.getCommits()), - printRefs(firstBlockSyncData.getRefs())); + printRefs(firstBlockSyncData.getRefs()) + ); } @Nonnull @@ -258,9 +269,11 @@ private static String printCommits(@Nonnull List commits) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < Math.min(commits.size(), 100); i++) { GraphCommit commit = commits.get(i); - sb.append(String.format("%s -> %s\n", + sb.append(String.format( + "%s -> %s\n", commit.getId().toShortString(), - StringUtil.join(commit.getParents(), Hash::toShortString, ", "))); + StringUtil.join(commit.getParents(), Hash::toShortString, ", ") + )); } return sb.toString(); } @@ -270,9 +283,11 @@ private static String printRefs(@Nonnull Set refs) { return StringUtil.join(refs, ref -> ref.getCommitHash().toShortString() + " : " + ref.getName(), "\n"); } - private static void addOldStillExistingTags(@Nonnull Interner allRefs, - @Nonnull Set currentTags, - @Nonnull Collection previousRefs) { + private static void addOldStillExistingTags( + @Nonnull Interner allRefs, + @Nonnull Set currentTags, + @Nonnull Collection previousRefs + ) { for (VcsRef ref : previousRefs) { VcsRef t = allRefs.get(ref); if (t == null && currentTags.contains(ref.getName())) { @@ -291,6 +306,7 @@ private Set readCurrentTagNames(@Nonnull VirtualFile root) throws VcsExc } @Nonnull + @SafeVarargs private static Set remove(@Nonnull Set original, @Nonnull Set... toRemove) { Set result = newHashSet(original); for (Set set : toRemove) { @@ -317,9 +333,11 @@ private static void addNewElements(@Nonnull Collection original, @Nonnull } @Nonnull - private DetailedLogData loadSomeCommitsOnTaggedBranches(@Nonnull VirtualFile root, - int commitCount, - @Nonnull Collection unmatchedTags) throws VcsException { + private DetailedLogData loadSomeCommitsOnTaggedBranches( + @Nonnull VirtualFile root, + int commitCount, + @Nonnull Collection unmatchedTags + ) throws VcsException { StopWatch sw = StopWatch.start("loading commits on tagged branch in " + root.getName()); List params = new ArrayList<>(); params.add("--max-count=" + commitCount); @@ -330,7 +348,7 @@ private DetailedLogData loadSomeCommitsOnTaggedBranches(@Nonnull VirtualFile roo @Override @Nonnull - public LogData readAllHashes(@Nonnull VirtualFile root, @Nonnull final Consumer commitConsumer) throws VcsException { + public LogData readAllHashes(@Nonnull VirtualFile root, @Nonnull Consumer commitConsumer) throws VcsException { if (!isRepositoryReady(root)) { return LogDataImpl.empty(); } @@ -338,15 +356,17 @@ public LogData readAllHashes(@Nonnull VirtualFile root, @Nonnull final Consumer< List parameters = new ArrayList<>(GitHistoryUtils.LOG_ALL); parameters.add("--date-order"); - final GitBekParentFixer parentFixer = GitBekParentFixer.prepare(root, this); + GitBekParentFixer parentFixer = GitBekParentFixer.prepare(root, this); Set userRegistry = newHashSet(); Set refs = newHashSet(); - GitHistoryUtils.readCommits(myProject, + GitHistoryUtils.readCommits( + myProject, root, parameters, userRegistry::add, refs::add, - commit -> commitConsumer.accept(parentFixer.fixCommit(commit))); + commit -> commitConsumer.accept(parentFixer.fixCommit(commit)) + ); return new LogDataImpl(refs, userRegistry); } @@ -360,9 +380,11 @@ public void readAllFullDetails(@Nonnull VirtualFile root, @Nonnull Consumer hashes, - @Nonnull Consumer commitConsumer) throws VcsException { + public void readFullDetails( + @Nonnull VirtualFile root, + @Nonnull List hashes, + @Nonnull Consumer commitConsumer + ) throws VcsException { if (!isRepositoryReady(root)) { return; } @@ -378,8 +400,10 @@ public void readFullDetails(@Nonnull VirtualFile root, @Nonnull @Override - public List readShortDetails(@Nonnull final VirtualFile root, - @Nonnull List hashes) throws VcsException { + public List readShortDetails( + @Nonnull final VirtualFile root, + @Nonnull List hashes + ) throws VcsException { //noinspection Convert2Lambda return VcsFileUtil.foreachChunk(hashes, new ThrowableFunction, List, VcsException>() { @Nonnull @@ -431,36 +455,42 @@ public VcsLogRefManager getReferenceManager() { @Nonnull @Override - public Disposable subscribeToRootRefreshEvents(@Nonnull final Collection roots, @Nonnull final VcsLogRefresher refresher) { + public Disposable subscribeToRootRefreshEvents(@Nonnull Collection roots, @Nonnull VcsLogRefresher refresher) { MessageBusConnection connection = myProject.getMessageBus().connect(myProject); - connection.subscribe(GitRepositoryChangeListener.class, repository -> - { - VirtualFile root = repository.getRoot(); - if (roots.contains(root)) { - refresher.refresh(root); + connection.subscribe( + GitRepositoryChangeListener.class, + repository -> { + VirtualFile root = repository.getRoot(); + if (roots.contains(root)) { + refresher.refresh(root); + } } - }); + ); return connection::disconnect; } @Nonnull @Override - public List getCommitsMatchingFilter(@Nonnull final VirtualFile root, - @Nonnull VcsLogFilterCollection filterCollection, - int maxCount) throws VcsException { + public List getCommitsMatchingFilter( + @Nonnull VirtualFile root, + @Nonnull VcsLogFilterCollection filterCollection, + int maxCount + ) throws VcsException { if (!isRepositoryReady(root)) { return Collections.emptyList(); } - List filterParameters = ContainerUtil.newArrayList(); + List filterParameters = new ArrayList<>(); VcsLogBranchFilter branchFilter = filterCollection.getBranchFilter(); if (branchFilter != null) { GitRepository repository = getRepository(root); assert repository != null : "repository is null for root " + root + " but was previously reported as 'ready'"; - Collection branches = ContainerUtil.newArrayList(ContainerUtil.concat(repository.getBranches().getLocalBranches(), - repository.getBranches().getRemoteBranches())); + Collection branches = ContainerUtil.newArrayList(ContainerUtil.concat( + repository.getBranches().getLocalBranches(), + repository.getBranches().getRemoteBranches() + )); Collection branchNames = GitBranchUtil.convertBranchesToNames(branches); Collection predefinedNames = ContainerUtil.list("HEAD"); @@ -538,7 +568,8 @@ public List getCommitsMatchingFilter(@Nonnull final VirtualFile } List commits = new ArrayList<>(); - GitHistoryUtils.readCommits(myProject, + GitHistoryUtils.readCommits( + myProject, root, filterParameters, vcsUser -> { diff --git a/plugin/src/main/java/git4idea/log/GitRefManager.java b/plugin/src/main/java/git4idea/log/GitRefManager.java index 520a2ff..013c7e5 100644 --- a/plugin/src/main/java/git4idea/log/GitRefManager.java +++ b/plugin/src/main/java/git4idea/log/GitRefManager.java @@ -5,7 +5,6 @@ import consulo.util.collection.ContainerUtil; import consulo.util.collection.MultiMap; import consulo.util.lang.ObjectUtil; -import consulo.util.lang.function.Condition; import consulo.versionControlSystem.distributed.repository.RepositoryManager; import consulo.versionControlSystem.log.*; import consulo.versionControlSystem.log.base.SimpleRefGroup; @@ -13,14 +12,15 @@ import consulo.versionControlSystem.log.util.VcsLogUtil; import consulo.virtualFileSystem.VirtualFile; import git4idea.GitBranch; +import git4idea.GitReference; import git4idea.GitRemoteBranch; import git4idea.GitTag; import git4idea.repo.GitBranchTrackInfo; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.awt.*; import java.io.DataInput; import java.io.DataOutput; @@ -32,521 +32,526 @@ * @author Kirill Likhodedov */ public class GitRefManager implements VcsLogRefManager { - public static final VcsRefType HEAD = new SimpleRefType(true, VcsLogStandardColors.Refs.TIP, "HEAD"); - public static final VcsRefType LOCAL_BRANCH = new SimpleRefType(true, VcsLogStandardColors.Refs.BRANCH, "LOCAL_BRANCH"); - public static final VcsRefType REMOTE_BRANCH = new SimpleRefType(true, VcsLogStandardColors.Refs.BRANCH_REF, "REMOTE_BRANCH"); - public static final VcsRefType TAG = new SimpleRefType(false, VcsLogStandardColors.Refs.TAG, "TAG"); - public static final VcsRefType OTHER = new SimpleRefType(false, VcsLogStandardColors.Refs.TAG, "OTHER"); - - private static final List REF_TYPE_INDEX = Arrays.asList(HEAD, LOCAL_BRANCH, REMOTE_BRANCH, TAG, OTHER); - - public static final String MASTER = "master"; - public static final String ORIGIN_MASTER = "origin/master"; - private static final Logger LOG = Logger.getInstance(GitRefManager.class); - private static final String REMOTE_TABLE_SEPARATOR = " & "; - private static final String SEPARATOR = "/"; - - protected enum RefType { - OTHER, - HEAD, - TAG, - NON_TRACKING_LOCAL_BRANCH, - NON_TRACKED_REMOTE_BRANCH, - TRACKING_LOCAL_BRANCH, - MASTER, - TRACKED_REMOTE_BRANCH, - ORIGIN_MASTER - } - - @Nonnull - private final RepositoryManager myRepositoryManager; - @Nonnull - private final Comparator myLabelsComparator; - @Nonnull - private final Comparator myBranchLayoutComparator; - - public GitRefManager(@Nonnull RepositoryManager repositoryManager) { - myRepositoryManager = repositoryManager; - myBranchLayoutComparator = new GitBranchLayoutComparator(repositoryManager); - myLabelsComparator = new GitLabelComparator(repositoryManager); - } - - @Nonnull - @Override - public Comparator getLabelsOrderComparator() { - return myLabelsComparator; - } - - @Nonnull - @Override - public Comparator getBranchLayoutComparator() { - return myBranchLayoutComparator; - } - - @Nonnull - @Override - public List groupForBranchFilter(@Nonnull Collection refs) { - List simpleGroups = ContainerUtil.newArrayList(); - List localBranches = ContainerUtil.newArrayList(); - List trackedBranches = ContainerUtil.newArrayList(); - MultiMap remoteRefGroups = MultiMap.create(); - - MultiMap refsByRoot = groupRefsByRoot(refs); - for (Map.Entry> entry : refsByRoot.entrySet()) { - VirtualFile root = entry.getKey(); - List refsInRoot = ContainerUtil.sorted(entry.getValue(), myLabelsComparator); - - GitRepository repository = myRepositoryManager.getRepositoryForRoot(root); - if (repository == null) { - LOG.warn("No repository for root: " + root); - continue; - } - - Set locals = getLocalBranches(repository); - Set tracked = getTrackedRemoteBranches(repository); - Map allRemote = getAllRemoteBranches(repository); - - for (VcsRef ref : refsInRoot) { - if (ref.getType() == HEAD) { - simpleGroups.add(new SingletonRefGroup(ref)); - continue; - } - - String refName = ref.getName(); - if (locals.contains(refName)) { - localBranches.add(ref); - } - else if (allRemote.containsKey(refName)) { - remoteRefGroups.putValue(allRemote.get(refName), ref); - if (tracked.contains(refName)) { - trackedBranches.add(ref); - } - } - else { - LOG.debug("Didn't find ref neither in local nor in remote branches: " + ref); - } - } + public static final VcsRefType HEAD = new SimpleRefType(true, VcsLogStandardColors.Refs.TIP, "HEAD"); + public static final VcsRefType LOCAL_BRANCH = new SimpleRefType(true, VcsLogStandardColors.Refs.BRANCH, "LOCAL_BRANCH"); + public static final VcsRefType REMOTE_BRANCH = new SimpleRefType(true, VcsLogStandardColors.Refs.BRANCH_REF, "REMOTE_BRANCH"); + public static final VcsRefType TAG = new SimpleRefType(false, VcsLogStandardColors.Refs.TAG, "TAG"); + public static final VcsRefType OTHER = new SimpleRefType(false, VcsLogStandardColors.Refs.TAG, "OTHER"); + + private static final List REF_TYPE_INDEX = Arrays.asList(HEAD, LOCAL_BRANCH, REMOTE_BRANCH, TAG, OTHER); + + public static final String MASTER = "master"; + public static final String ORIGIN_MASTER = "origin/master"; + private static final Logger LOG = Logger.getInstance(GitRefManager.class); + private static final String REMOTE_TABLE_SEPARATOR = " & "; + private static final String SEPARATOR = "/"; + + protected enum RefType { + OTHER, + HEAD, + TAG, + NON_TRACKING_LOCAL_BRANCH, + NON_TRACKED_REMOTE_BRANCH, + TRACKING_LOCAL_BRANCH, + MASTER, + TRACKED_REMOTE_BRANCH, + ORIGIN_MASTER } - List result = ContainerUtil.newArrayList(); - result.addAll(simpleGroups); - if (!localBranches.isEmpty()) { - result.add(new LogicalRefGroup("Local", localBranches)); - } - if (!trackedBranches.isEmpty()) { - result.add(new LogicalRefGroup("Tracked", trackedBranches)); - } - for (Map.Entry> entry : remoteRefGroups.entrySet()) { - final GitRemote remote = entry.getKey(); - final Collection branches = entry.getValue(); - result.add(new RemoteRefGroup(remote, branches)); - } - return result; - } - - @Nonnull - @Override - public List groupForTable(@Nonnull Collection references, boolean compact, boolean showTagNames) { - List sortedReferences = ContainerUtil.sorted(references, myLabelsComparator); - MultiMap groupedRefs = ContainerUtil.groupBy(sortedReferences, VcsRef::getType); - - List result = ContainerUtil.newArrayList(); - if (groupedRefs.isEmpty()) { - return result; - } + @Nonnull + private final RepositoryManager myRepositoryManager; + @Nonnull + private final Comparator myLabelsComparator; + @Nonnull + private final Comparator myBranchLayoutComparator; - VcsRef head = null; - Map.Entry> firstGroup = ObjectUtil.notNull(ContainerUtil.getFirstItem(groupedRefs.entrySet())); - if (firstGroup.getKey().equals(HEAD)) { - head = ObjectUtil.assertNotNull(ContainerUtil.getFirstItem(firstGroup.getValue())); - groupedRefs.remove(HEAD, head); + public GitRefManager(@Nonnull RepositoryManager repositoryManager) { + myRepositoryManager = repositoryManager; + myBranchLayoutComparator = new GitBranchLayoutComparator(repositoryManager); + myLabelsComparator = new GitLabelComparator(repositoryManager); } - GitRepository repository = getRepository(references); - if (repository != null) { - result.addAll(getTrackedRefs(groupedRefs, repository)); - } - result.forEach(refGroup -> - { - groupedRefs.remove(LOCAL_BRANCH, refGroup.getRefs().get(0)); - groupedRefs.remove(REMOTE_BRANCH, refGroup.getRefs().get(1)); - }); - - SimpleRefGroup.buildGroups(groupedRefs, compact, showTagNames, result); - - if (head != null) { - if (repository != null && !repository.isOnBranch()) { - result.add(0, new SimpleRefGroup("!", Collections.singletonList(head))); - } - else { - if (!result.isEmpty()) { - RefGroup first = ObjectUtil.assertNotNull(ContainerUtil.getFirstItem(result)); - first.getRefs().add(0, head); - } - else { - result.add(0, new SimpleRefGroup("", Collections.singletonList(head))); - } - } + @Nonnull + @Override + public Comparator getLabelsOrderComparator() { + return myLabelsComparator; } - return result; - } - - @Nonnull - private static List getTrackedRefs(@Nonnull MultiMap groupedRefs, @Nonnull GitRepository repository) { - List result = ContainerUtil.newArrayList(); - - Collection locals = groupedRefs.get(LOCAL_BRANCH); - Collection remotes = groupedRefs.get(REMOTE_BRANCH); - - for (VcsRef localRef : locals) { - SimpleRefGroup group = createTrackedGroup(repository, remotes, localRef); - if (group != null) { - result.add(group); - } + @Nonnull + @Override + public Comparator getBranchLayoutComparator() { + return myBranchLayoutComparator; } - return result; - } - - @Nullable - private static SimpleRefGroup createTrackedGroup(@Nonnull GitRepository repository, - @Nonnull Collection references, - @Nonnull VcsRef localRef) { - List remoteBranches = ContainerUtil.filter(references, ref -> ref.getType().equals(REMOTE_BRANCH)); - - GitBranchTrackInfo trackInfo = - ContainerUtil.find(repository.getBranchTrackInfos(), info -> info.getLocalBranch().getName().equals(localRef.getName())); - if (trackInfo != null) { - VcsRef trackedRef = ContainerUtil.find(remoteBranches, ref -> ref.getName().equals(trackInfo.getRemoteBranch().getName())); - if (trackedRef != null) { - return new SimpleRefGroup(trackInfo.getRemote().getName() + REMOTE_TABLE_SEPARATOR + localRef.getName(), - ContainerUtil.newArrayList(localRef, trackedRef)); - } - } + @Nonnull + @Override + public List groupForBranchFilter(@Nonnull Collection refs) { + List simpleGroups = new ArrayList<>(); + List localBranches = new ArrayList<>(); + List trackedBranches = new ArrayList<>(); + MultiMap remoteRefGroups = MultiMap.create(); + + MultiMap refsByRoot = groupRefsByRoot(refs); + for (Map.Entry> entry : refsByRoot.entrySet()) { + VirtualFile root = entry.getKey(); + List refsInRoot = ContainerUtil.sorted(entry.getValue(), myLabelsComparator); + + GitRepository repository = myRepositoryManager.getRepositoryForRoot(root); + if (repository == null) { + LOG.warn("No repository for root: " + root); + continue; + } + + Set locals = getLocalBranches(repository); + Set tracked = getTrackedRemoteBranches(repository); + Map allRemote = getAllRemoteBranches(repository); + + for (VcsRef ref : refsInRoot) { + if (ref.getType() == HEAD) { + simpleGroups.add(new SingletonRefGroup(ref)); + continue; + } + + String refName = ref.getName(); + if (locals.contains(refName)) { + localBranches.add(ref); + } + else if (allRemote.containsKey(refName)) { + remoteRefGroups.putValue(allRemote.get(refName), ref); + if (tracked.contains(refName)) { + trackedBranches.add(ref); + } + } + else { + LOG.debug("Didn't find ref neither in local nor in remote branches: " + ref); + } + } + } - List trackingCandidates = ContainerUtil.filter(remoteBranches, ref -> ref.getName().endsWith(SEPARATOR + localRef.getName())); - for (GitRemote remote : repository.getRemotes()) { - for (VcsRef candidate : trackingCandidates) { - if (candidate.getName().equals(remote.getName() + SEPARATOR + localRef.getName())) { - return new SimpleRefGroup(remote.getName() + REMOTE_TABLE_SEPARATOR + localRef.getName(), - ContainerUtil.newArrayList(localRef, candidate)); + List result = new ArrayList<>(); + result.addAll(simpleGroups); + if (!localBranches.isEmpty()) { + result.add(new LogicalRefGroup("Local", localBranches)); + } + if (!trackedBranches.isEmpty()) { + result.add(new LogicalRefGroup("Tracked", trackedBranches)); } - } + for (Map.Entry> entry : remoteRefGroups.entrySet()) { + GitRemote remote = entry.getKey(); + Collection branches = entry.getValue(); + result.add(new RemoteRefGroup(remote, branches)); + } + return result; } - return null; - } + @Nonnull + @Override + public List groupForTable(@Nonnull Collection references, boolean compact, boolean showTagNames) { + List sortedReferences = ContainerUtil.sorted(references, myLabelsComparator); + MultiMap groupedRefs = ContainerUtil.groupBy(sortedReferences, VcsRef::getType); - @Nullable - private GitRepository getRepository(@Nonnull Collection references) { - if (references.isEmpty()) { - return null; - } + List result = new ArrayList<>(); + if (groupedRefs.isEmpty()) { + return result; + } - VcsRef ref = ObjectUtil.assertNotNull(ContainerUtil.getFirstItem(references)); - GitRepository repository = myRepositoryManager.getRepositoryForRoot(ref.getRoot()); - if (repository == null) { - LOG.warn("No repository for root: " + ref.getRoot()); - } - return repository; - } - - @Override - public void serialize(@Nonnull DataOutput out, @Nonnull VcsRefType type) throws IOException { - out.writeInt(REF_TYPE_INDEX.indexOf(type)); - } - - @Nonnull - @Override - public VcsRefType deserialize(@Nonnull DataInput in) throws IOException { - int id = in.readInt(); - if (id < 0 || id > REF_TYPE_INDEX.size() - 1) { - throw new IOException("Reference type by id " + id + " does not exist"); - } - return REF_TYPE_INDEX.get(id); - } - - private static Set getLocalBranches(GitRepository repository) { - return ContainerUtil.map2Set(repository.getBranches().getLocalBranches(), branch -> branch.getName()); - } - - @Nonnull - private static Set getTrackedRemoteBranches(@Nonnull GitRepository repository) { - Set all = new HashSet<>(repository.getBranches().getRemoteBranches()); - Set tracked = new HashSet<>(); - for (GitBranchTrackInfo info : repository.getBranchTrackInfos()) { - GitRemoteBranch trackedRemoteBranch = info.getRemoteBranch(); - if (all.contains(trackedRemoteBranch)) { // check that this branch really exists, not just written in .git/config - tracked.add(trackedRemoteBranch.getName()); - } - } - return tracked; - } - - @Nonnull - private static Map getAllRemoteBranches(@Nonnull GitRepository repository) { - Set all = new HashSet<>(repository.getBranches().getRemoteBranches()); - Map allRemote = new HashMap<>(); - for (GitRemoteBranch remoteBranch : all) { - allRemote.put(remoteBranch.getName(), remoteBranch.getRemote()); - } - return allRemote; - } - - private static Set getTrackedRemoteBranchesFromConfig(GitRepository repository) { - return ContainerUtil.map2Set(repository.getBranchTrackInfos(), trackInfo -> trackInfo.getRemoteBranch().getName()); - } - - @Nonnull - private static MultiMap groupRefsByRoot(@Nonnull Iterable refs) { - MultiMap grouped = MultiMap.create(); - for (VcsRef ref : refs) { - grouped.putValue(ref.getRoot(), ref); - } - return grouped; - } + VcsRef head = null; + Map.Entry> firstGroup = ObjectUtil.notNull(ContainerUtil.getFirstItem(groupedRefs.entrySet())); + if (firstGroup.getKey().equals(HEAD)) { + head = ObjectUtil.assertNotNull(ContainerUtil.getFirstItem(firstGroup.getValue())); + groupedRefs.remove(HEAD, head); + } - @Nonnull - public static VcsRefType getRefType(@Nonnull String refName) { - if (refName.startsWith(GitBranch.REFS_HEADS_PREFIX)) { - return LOCAL_BRANCH; - } - if (refName.startsWith(GitBranch.REFS_REMOTES_PREFIX)) { - return REMOTE_BRANCH; - } - if (refName.startsWith(GitTag.REFS_TAGS_PREFIX)) { - return TAG; - } - if (refName.startsWith("HEAD")) { - return HEAD; + GitRepository repository = getRepository(references); + if (repository != null) { + result.addAll(getTrackedRefs(groupedRefs, repository)); + } + result.forEach(refGroup -> + { + groupedRefs.remove(LOCAL_BRANCH, refGroup.getRefs().get(0)); + groupedRefs.remove(REMOTE_BRANCH, refGroup.getRefs().get(1)); + }); + + SimpleRefGroup.buildGroups(groupedRefs, compact, showTagNames, result); + + if (head != null) { + if (repository != null && !repository.isOnBranch()) { + result.add(0, new SimpleRefGroup("!", Collections.singletonList(head))); + } + else { + if (!result.isEmpty()) { + RefGroup first = ObjectUtil.assertNotNull(ContainerUtil.getFirstItem(result)); + first.getRefs().add(0, head); + } + else { + result.add(0, new SimpleRefGroup("", Collections.singletonList(head))); + } + } + } + + return result; } - return OTHER; - } - private static class SimpleRefType implements VcsRefType { - private final boolean myIsBranch; - @Nonnull - private final Color myColor; @Nonnull - private final String myName; + private static List getTrackedRefs(@Nonnull MultiMap groupedRefs, @Nonnull GitRepository repository) { + List result = new ArrayList<>(); - public SimpleRefType(boolean isBranch, @Nonnull Color color, @Nonnull String typeName) { - myIsBranch = isBranch; - myColor = color; - myName = typeName; - } - - @Override - public boolean isBranch() { - return myIsBranch; - } + Collection locals = groupedRefs.get(LOCAL_BRANCH); + Collection remotes = groupedRefs.get(REMOTE_BRANCH); - @Nonnull - @Override - public Color getBackgroundColor() { - return myColor; - } + for (VcsRef localRef : locals) { + SimpleRefGroup group = createTrackedGroup(repository, remotes, localRef); + if (group != null) { + result.add(group); + } + } - @Override - public String toString() { - return myName; - } + return result; + } + + @Nullable + private static SimpleRefGroup createTrackedGroup( + @Nonnull GitRepository repository, + @Nonnull Collection references, + @Nonnull VcsRef localRef + ) { + List remoteBranches = ContainerUtil.filter(references, ref -> ref.getType().equals(REMOTE_BRANCH)); + + GitBranchTrackInfo trackInfo = + ContainerUtil.find(repository.getBranchTrackInfos(), info -> info.localBranch().getName().equals(localRef.getName())); + if (trackInfo != null) { + VcsRef trackedRef = ContainerUtil.find(remoteBranches, ref -> ref.getName().equals(trackInfo.remoteBranch().getName())); + if (trackedRef != null) { + return new SimpleRefGroup( + trackInfo.getRemote().getName() + REMOTE_TABLE_SEPARATOR + localRef.getName(), + Arrays.asList(localRef, trackedRef) + ); + } + } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - SimpleRefType type = (SimpleRefType)o; - return myIsBranch == type.myIsBranch && Objects.equals(myName, type.myName); - } + List trackingCandidates = + ContainerUtil.filter(remoteBranches, ref -> ref.getName().endsWith(SEPARATOR + localRef.getName())); + for (GitRemote remote : repository.getRemotes()) { + for (VcsRef candidate : trackingCandidates) { + if (candidate.getName().equals(remote.getName() + SEPARATOR + localRef.getName())) { + return new SimpleRefGroup( + remote.getName() + REMOTE_TABLE_SEPARATOR + localRef.getName(), + Arrays.asList(localRef, candidate) + ); + } + } + } - @Override - public int hashCode() { - return Objects.hash(myIsBranch, myName); + return null; } - } - private static class LogicalRefGroup implements RefGroup { - private final String myGroupName; - private final List myRefs; + @Nullable + private GitRepository getRepository(@Nonnull Collection references) { + if (references.isEmpty()) { + return null; + } - private LogicalRefGroup(String groupName, List refs) { - myGroupName = groupName; - myRefs = refs; + VcsRef ref = ObjectUtil.assertNotNull(ContainerUtil.getFirstItem(references)); + GitRepository repository = myRepositoryManager.getRepositoryForRoot(ref.getRoot()); + if (repository == null) { + LOG.warn("No repository for root: " + ref.getRoot()); + } + return repository; } @Override - public boolean isExpanded() { - return true; + public void serialize(@Nonnull DataOutput out, @Nonnull VcsRefType type) throws IOException { + out.writeInt(REF_TYPE_INDEX.indexOf(type)); } @Nonnull @Override - public String getName() { - return myGroupName; + public VcsRefType deserialize(@Nonnull DataInput in) throws IOException { + int id = in.readInt(); + if (id < 0 || id > REF_TYPE_INDEX.size() - 1) { + throw new IOException("Reference type by id " + id + " does not exist"); + } + return REF_TYPE_INDEX.get(id); } - @Nonnull - @Override - public List getRefs() { - return myRefs; + private static Set getLocalBranches(GitRepository repository) { + return ContainerUtil.map2Set(repository.getBranches().getLocalBranches(), GitReference::getName); } @Nonnull - @Override - public List getColors() { - return Collections.singletonList(VcsLogStandardColors.Refs.TIP); + private static Set getTrackedRemoteBranches(@Nonnull GitRepository repository) { + Set all = new HashSet<>(repository.getBranches().getRemoteBranches()); + Set tracked = new HashSet<>(); + for (GitBranchTrackInfo info : repository.getBranchTrackInfos()) { + GitRemoteBranch trackedRemoteBranch = info.remoteBranch(); + if (all.contains(trackedRemoteBranch)) { // check that this branch really exists, not just written in .git/config + tracked.add(trackedRemoteBranch.getName()); + } + } + return tracked; } - } - private class RemoteRefGroup implements RefGroup { - private final GitRemote myRemote; - private final Collection myBranches; - - public RemoteRefGroup(GitRemote remote, Collection branches) { - myRemote = remote; - myBranches = branches; + @Nonnull + private static Map getAllRemoteBranches(@Nonnull GitRepository repository) { + Set all = new HashSet<>(repository.getBranches().getRemoteBranches()); + Map allRemote = new HashMap<>(); + for (GitRemoteBranch remoteBranch : all) { + allRemote.put(remoteBranch.getName(), remoteBranch.getRemote()); + } + return allRemote; } - @Override - public boolean isExpanded() { - return false; + private static Set getTrackedRemoteBranchesFromConfig(GitRepository repository) { + return ContainerUtil.map2Set(repository.getBranchTrackInfos(), trackInfo -> trackInfo.remoteBranch().getName()); } @Nonnull - @Override - public String getName() { - return myRemote.getName() + "/..."; + private static MultiMap groupRefsByRoot(@Nonnull Iterable refs) { + MultiMap grouped = MultiMap.create(); + for (VcsRef ref : refs) { + grouped.putValue(ref.getRoot(), ref); + } + return grouped; } @Nonnull - @Override - public List getRefs() { - return ContainerUtil.sorted(myBranches, getLabelsOrderComparator()); + public static VcsRefType getRefType(@Nonnull String refName) { + if (refName.startsWith(GitBranch.REFS_HEADS_PREFIX)) { + return LOCAL_BRANCH; + } + if (refName.startsWith(GitBranch.REFS_REMOTES_PREFIX)) { + return REMOTE_BRANCH; + } + if (refName.startsWith(GitTag.REFS_TAGS_PREFIX)) { + return TAG; + } + if (refName.startsWith("HEAD")) { + return HEAD; + } + return OTHER; } - @Nonnull - @Override - public List getColors() { - return Collections.singletonList(VcsLogStandardColors.Refs.BRANCH_REF); - } - } - - private static class GitLabelComparator extends GitRefComparator { - private static final RefType[] ORDERED_TYPES = { - RefType.HEAD, - RefType.MASTER, - RefType.TRACKING_LOCAL_BRANCH, - RefType.NON_TRACKING_LOCAL_BRANCH, - RefType.ORIGIN_MASTER, - RefType.TRACKED_REMOTE_BRANCH, - RefType.NON_TRACKED_REMOTE_BRANCH, - RefType.TAG, - RefType.OTHER - }; - - GitLabelComparator(@Nonnull RepositoryManager repositoryManager) { - super(repositoryManager); - } + private static class SimpleRefType implements VcsRefType { + private final boolean myIsBranch; + @Nonnull + private final Color myColor; + @Nonnull + private final String myName; - @Override - protected RefType[] getOrderedTypes() { - return ORDERED_TYPES; - } - } - - private static class GitBranchLayoutComparator extends GitRefComparator { - private static final RefType[] ORDERED_TYPES = { - RefType.ORIGIN_MASTER, - RefType.TRACKED_REMOTE_BRANCH, - RefType.MASTER, - RefType.TRACKING_LOCAL_BRANCH, - RefType.NON_TRACKING_LOCAL_BRANCH, - RefType.NON_TRACKED_REMOTE_BRANCH, - RefType.TAG, - RefType.HEAD, - RefType.OTHER - }; - - GitBranchLayoutComparator(@Nonnull RepositoryManager repositoryManager) { - super(repositoryManager); + public SimpleRefType(boolean isBranch, @Nonnull Color color, @Nonnull String typeName) { + myIsBranch = isBranch; + myColor = color; + myName = typeName; + } + + @Override + public boolean isBranch() { + return myIsBranch; + } + + @Nonnull + @Override + public Color getBackgroundColor() { + return myColor; + } + + @Override + public String toString() { + return myName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SimpleRefType type = (SimpleRefType) o; + return myIsBranch == type.myIsBranch && Objects.equals(myName, type.myName); + } + + @Override + public int hashCode() { + return Objects.hash(myIsBranch, myName); + } } - @Override - protected RefType[] getOrderedTypes() { - return ORDERED_TYPES; + private static class LogicalRefGroup implements RefGroup { + private final String myGroupName; + private final List myRefs; + + private LogicalRefGroup(String groupName, List refs) { + myGroupName = groupName; + myRefs = refs; + } + + @Override + public boolean isExpanded() { + return true; + } + + @Nonnull + @Override + public String getName() { + return myGroupName; + } + + @Nonnull + @Override + public List getRefs() { + return myRefs; + } + + @Nonnull + @Override + public List getColors() { + return Collections.singletonList(VcsLogStandardColors.Refs.TIP); + } } - } - private abstract static class GitRefComparator implements Comparator { - @Nonnull - private final RepositoryManager myRepositoryManager; + private class RemoteRefGroup implements RefGroup { + private final GitRemote myRemote; + private final Collection myBranches; + + public RemoteRefGroup(GitRemote remote, Collection branches) { + myRemote = remote; + myBranches = branches; + } + + @Override + public boolean isExpanded() { + return false; + } + + @Nonnull + @Override + public String getName() { + return myRemote.getName() + "/..."; + } - GitRefComparator(@Nonnull RepositoryManager repositoryManager) { - myRepositoryManager = repositoryManager; + @Nonnull + @Override + public List getRefs() { + return ContainerUtil.sorted(myBranches, getLabelsOrderComparator()); + } + + @Nonnull + @Override + public List getColors() { + return Collections.singletonList(VcsLogStandardColors.Refs.BRANCH_REF); + } } - @Override - public int compare(@Nonnull VcsRef ref1, @Nonnull VcsRef ref2) { - int power1 = ArrayUtil.find(getOrderedTypes(), getType(ref1)); - int power2 = ArrayUtil.find(getOrderedTypes(), getType(ref2)); - if (power1 != power2) { - return power1 - power2; - } - int namesComparison = ref1.getName().compareTo(ref2.getName()); - if (namesComparison != 0) { - return namesComparison; - } - return VcsLogUtil.compareRoots(ref1.getRoot(), ref2.getRoot()); + private static class GitLabelComparator extends GitRefComparator { + private static final RefType[] ORDERED_TYPES = { + RefType.HEAD, + RefType.MASTER, + RefType.TRACKING_LOCAL_BRANCH, + RefType.NON_TRACKING_LOCAL_BRANCH, + RefType.ORIGIN_MASTER, + RefType.TRACKED_REMOTE_BRANCH, + RefType.NON_TRACKED_REMOTE_BRANCH, + RefType.TAG, + RefType.OTHER + }; + + GitLabelComparator(@Nonnull RepositoryManager repositoryManager) { + super(repositoryManager); + } + + @Override + protected RefType[] getOrderedTypes() { + return ORDERED_TYPES; + } } - protected abstract RefType[] getOrderedTypes(); + private static class GitBranchLayoutComparator extends GitRefComparator { + private static final RefType[] ORDERED_TYPES = { + RefType.ORIGIN_MASTER, + RefType.TRACKED_REMOTE_BRANCH, + RefType.MASTER, + RefType.TRACKING_LOCAL_BRANCH, + RefType.NON_TRACKING_LOCAL_BRANCH, + RefType.NON_TRACKED_REMOTE_BRANCH, + RefType.TAG, + RefType.HEAD, + RefType.OTHER + }; + + GitBranchLayoutComparator(@Nonnull RepositoryManager repositoryManager) { + super(repositoryManager); + } - @Nonnull - private RefType getType(@Nonnull VcsRef ref) { - VcsRefType type = ref.getType(); - if (type == HEAD) { - return RefType.HEAD; - } - else if (type == TAG) { - return RefType.TAG; - } - else if (type == LOCAL_BRANCH) { - if (ref.getName().equals(MASTER)) { - return RefType.MASTER; - } - return isTracked(ref, false) ? RefType.TRACKING_LOCAL_BRANCH : RefType.NON_TRACKING_LOCAL_BRANCH; - } - else if (type == REMOTE_BRANCH) { - if (ref.getName().equals(ORIGIN_MASTER)) { - return RefType.ORIGIN_MASTER; - } - return isTracked(ref, true) ? RefType.TRACKED_REMOTE_BRANCH : RefType.NON_TRACKED_REMOTE_BRANCH; - } - else { - return RefType.OTHER; - } + @Override + protected RefType[] getOrderedTypes() { + return ORDERED_TYPES; + } } - private boolean isTracked(@Nonnull final VcsRef ref, final boolean remoteBranch) { - GitRepository repo = myRepositoryManager.getRepositoryForRoot(ref.getRoot()); - if (repo == null) { - LOG.error("Undefined root " + ref.getRoot()); - return false; - } - return ContainerUtil.exists(repo.getBranchTrackInfos(), new Condition() { + private abstract static class GitRefComparator implements Comparator { + @Nonnull + private final RepositoryManager myRepositoryManager; + + GitRefComparator(@Nonnull RepositoryManager repositoryManager) { + myRepositoryManager = repositoryManager; + } + @Override - public boolean value(GitBranchTrackInfo info) { - return remoteBranch ? info.getRemoteBranch().getNameForLocalOperations().equals(ref.getName()) : info.getLocalBranch() - .getName() - .equals(ref.getName()); + public int compare(@Nonnull VcsRef ref1, @Nonnull VcsRef ref2) { + int power1 = ArrayUtil.find(getOrderedTypes(), getType(ref1)); + int power2 = ArrayUtil.find(getOrderedTypes(), getType(ref2)); + if (power1 != power2) { + return power1 - power2; + } + int namesComparison = ref1.getName().compareTo(ref2.getName()); + if (namesComparison != 0) { + return namesComparison; + } + return VcsLogUtil.compareRoots(ref1.getRoot(), ref2.getRoot()); + } + + protected abstract RefType[] getOrderedTypes(); + + @Nonnull + private RefType getType(@Nonnull VcsRef ref) { + VcsRefType type = ref.getType(); + if (type == HEAD) { + return RefType.HEAD; + } + else if (type == TAG) { + return RefType.TAG; + } + else if (type == LOCAL_BRANCH) { + if (ref.getName().equals(MASTER)) { + return RefType.MASTER; + } + return isTracked(ref, false) ? RefType.TRACKING_LOCAL_BRANCH : RefType.NON_TRACKING_LOCAL_BRANCH; + } + else if (type == REMOTE_BRANCH) { + if (ref.getName().equals(ORIGIN_MASTER)) { + return RefType.ORIGIN_MASTER; + } + return isTracked(ref, true) ? RefType.TRACKED_REMOTE_BRANCH : RefType.NON_TRACKED_REMOTE_BRANCH; + } + else { + return RefType.OTHER; + } + } + + private boolean isTracked(@Nonnull VcsRef ref, boolean remoteBranch) { + GitRepository repo = myRepositoryManager.getRepositoryForRoot(ref.getRoot()); + if (repo == null) { + LOG.error("Undefined root " + ref.getRoot()); + return false; + } + return ContainerUtil.exists( + repo.getBranchTrackInfos(), + info -> remoteBranch + ? info.remoteBranch().getNameForLocalOperations().equals(ref.getName()) + : info.localBranch().getName().equals(ref.getName()) + ); } - }); } - } } diff --git a/plugin/src/main/java/git4idea/merge/GitConflictResolver.java b/plugin/src/main/java/git4idea/merge/GitConflictResolver.java index f9aac68..167346a 100644 --- a/plugin/src/main/java/git4idea/merge/GitConflictResolver.java +++ b/plugin/src/main/java/git4idea/merge/GitConflictResolver.java @@ -325,7 +325,7 @@ private List unmergedFiles(VirtualFile root) throws VcsException { } String output = StringUtil.join(result.getOutput(), "\n"); - HashSet unmergedPaths = new HashSet<>(); + Set unmergedPaths = new HashSet<>(); for (StringScanner s = new StringScanner(output); s.hasMoreData(); ) { if (s.isEol()) { s.nextLine(); diff --git a/plugin/src/main/java/git4idea/merge/GitMergeUtil.java b/plugin/src/main/java/git4idea/merge/GitMergeUtil.java index 7494da1..d1cf1ec 100644 --- a/plugin/src/main/java/git4idea/merge/GitMergeUtil.java +++ b/plugin/src/main/java/git4idea/merge/GitMergeUtil.java @@ -57,8 +57,8 @@ private GitMergeUtil() { * @param strategy a strategy selector */ public static void setupStrategies(final ElementsChooser branchChooser, final JComboBox strategy) { - final ElementsChooser.ElementsMarkListener listener = new ElementsChooser.ElementsMarkListener<>() { - private void updateStrategies(final List elements) { + ElementsChooser.ElementsMarkListener listener = new ElementsChooser.ElementsMarkListener<>() { + private void updateStrategies(List elements) { strategy.removeAllItems(); for (GitMergeStrategy s : GitMergeStrategy.getMergeStrategies(elements.size())) { strategy.addItem(s); @@ -67,8 +67,8 @@ private void updateStrategies(final List elements) { } @Override - public void elementMarkChanged(final String element, final boolean isMarked) { - final List elements = branchChooser.getMarkedElements(); + public void elementMarkChanged(String element, boolean isMarked) { + List elements = branchChooser.getMarkedElements(); if (elements.size() == 0) { strategy.setEnabled(false); updateStrategies(elements); @@ -96,15 +96,15 @@ public void elementMarkChanged(final String element, final boolean isMarked) { */ public static void showUpdates( GitRepositoryAction action, - final Project project, - final List exceptions, - final VirtualFile root, - final GitRevisionNumber currentRev, - final Label beforeLabel, - final LocalizeValue actionName, - final ActionInfo actionInfo + Project project, + List exceptions, + VirtualFile root, + GitRevisionNumber currentRev, + Label beforeLabel, + LocalizeValue actionName, + ActionInfo actionInfo ) { - final UpdatedFiles files = UpdatedFiles.create(); + UpdatedFiles files = UpdatedFiles.create(); MergeChangeCollector collector = new MergeChangeCollector(project, root, currentRev); collector.collect(files, exceptions); if (exceptions.size() != 0) { @@ -118,11 +118,11 @@ public static void showUpdates( tree.setAfter(LocalHistory.getInstance().putSystemLabel(project, "After update")); ViewUpdateInfoNotification.focusUpdateInfoTree(project, tree); })); - final Collection unmergedNames = files.getGroupById(FileGroup.MERGED_WITH_CONFLICT_ID).getFiles(); + Collection unmergedNames = files.getGroupById(FileGroup.MERGED_WITH_CONFLICT_ID).getFiles(); if (!unmergedNames.isEmpty()) { action.delayTask(exceptionList -> { LocalFileSystem lfs = LocalFileSystem.getInstance(); - final ArrayList unmerged = new ArrayList<>(); + List unmerged = new ArrayList<>(); for (String fileName : unmergedNames) { VirtualFile f = lfs.findFileByPath(fileName); if (f != null) { diff --git a/plugin/src/main/java/git4idea/merge/GitPullDialog.java b/plugin/src/main/java/git4idea/merge/GitPullDialog.java index a14625c..0490979 100644 --- a/plugin/src/main/java/git4idea/merge/GitPullDialog.java +++ b/plugin/src/main/java/git4idea/merge/GitPullDialog.java @@ -202,7 +202,7 @@ private void updateBranches() { } GitBranchTrackInfo trackInfo = GitUtil.getTrackInfoForCurrentBranch(repository); - String currentRemoteBranch = trackInfo == null ? null : trackInfo.getRemoteBranch().getNameForLocalOperations(); + String currentRemoteBranch = trackInfo == null ? null : trackInfo.remoteBranch().getNameForLocalOperations(); List remoteBranches = new ArrayList<>(repository.getBranches().getRemoteBranches()); Collections.sort(remoteBranches); for (GitBranch remoteBranch : remoteBranches) { diff --git a/plugin/src/main/java/git4idea/merge/MergeChangeCollector.java b/plugin/src/main/java/git4idea/merge/MergeChangeCollector.java index 985e6c4..74f29d2 100644 --- a/plugin/src/main/java/git4idea/merge/MergeChangeCollector.java +++ b/plugin/src/main/java/git4idea/merge/MergeChangeCollector.java @@ -43,172 +43,174 @@ * Collect change for merge or pull operations */ public class MergeChangeCollector { - private final HashSet myUnmergedPaths = new HashSet<>(); - private final Project myProject; - private final VirtualFile myRoot; - private final GitRevisionNumber myStart; // Revision number before update (used for diff) - @Nonnull - private final GitRepository myRepository; + private final Set myUnmergedPaths = new HashSet<>(); + private final Project myProject; + private final VirtualFile myRoot; + private final GitRevisionNumber myStart; // Revision number before update (used for diff) + @Nonnull + private final GitRepository myRepository; - public MergeChangeCollector(final Project project, final VirtualFile root, final GitRevisionNumber start) { - myStart = start; - myProject = project; - myRoot = root; - myRepository = assertNotNull(GitUtil.getRepositoryManager(project).getRepositoryForRoot(root)); - } + public MergeChangeCollector(Project project, VirtualFile root, GitRevisionNumber start) { + myStart = start; + myProject = project; + myRoot = root; + myRepository = assertNotNull(GitUtil.getRepositoryManager(project).getRepositoryForRoot(root)); + } - /** - * Collects changed files during or after merge operation to the supplied updates container. - */ - public void collect(final UpdatedFiles updates, List exceptions) { - try { - // collect unmerged - Set paths = getUnmergedPaths(); - addAll(updates, FileGroup.MERGED_WITH_CONFLICT_ID, paths); + /** + * Collects changed files during or after merge operation to the supplied updates container. + */ + public void collect(UpdatedFiles updates, List exceptions) { + try { + // collect unmerged + Set paths = getUnmergedPaths(); + addAll(updates, FileGroup.MERGED_WITH_CONFLICT_ID, paths); - // collect other changes (ignoring unmerged) - TreeSet updated = new TreeSet<>(); - TreeSet created = new TreeSet<>(); - TreeSet removed = new TreeSet<>(); + // collect other changes (ignoring unmerged) + TreeSet updated = new TreeSet<>(); + TreeSet created = new TreeSet<>(); + TreeSet removed = new TreeSet<>(); - String revisionsForDiff = getRevisionsForDiff(); - if (revisionsForDiff == null) { - return; - } - getChangedFilesExceptUnmerged(updated, created, removed, revisionsForDiff); - addAll(updates, FileGroup.UPDATED_ID, updated); - addAll(updates, FileGroup.CREATED_ID, created); - addAll(updates, FileGroup.REMOVED_FROM_REPOSITORY_ID, removed); - } - catch (VcsException e) { - exceptions.add(e); + String revisionsForDiff = getRevisionsForDiff(); + if (revisionsForDiff == null) { + return; + } + getChangedFilesExceptUnmerged(updated, created, removed, revisionsForDiff); + addAll(updates, FileGroup.UPDATED_ID, updated); + addAll(updates, FileGroup.CREATED_ID, created); + addAll(updates, FileGroup.REMOVED_FROM_REPOSITORY_ID, removed); + } + catch (VcsException e) { + exceptions.add(e); + } } - } - /** - * Returns absolute paths to files which are currently unmerged, and also populates myUnmergedPaths with relative paths. - */ - public - @Nonnull - Set getUnmergedPaths() throws VcsException { - String root = myRoot.getPath(); - final GitSimpleHandler h = new GitSimpleHandler(myProject, myRoot, GitCommand.LS_FILES); - h.setSilent(true); - h.addParameters("--unmerged"); - final String result = h.run(); + /** + * Returns absolute paths to files which are currently unmerged, and also populates myUnmergedPaths with relative paths. + */ + public + @Nonnull + Set getUnmergedPaths() throws VcsException { + String root = myRoot.getPath(); + GitSimpleHandler h = new GitSimpleHandler(myProject, myRoot, GitCommand.LS_FILES); + h.setSilent(true); + h.addParameters("--unmerged"); + String result = h.run(); - final Set paths = new HashSet<>(); - for (StringScanner s = new StringScanner(result); s.hasMoreData(); ) { - if (s.isEol()) { - s.nextLine(); - continue; - } - s.boundedToken('\t'); - final String relative = s.line(); - if (!myUnmergedPaths.add(relative)) { - continue; - } - String path = root + "/" + GitUtil.unescapePath(relative); - paths.add(path); + Set paths = new HashSet<>(); + for (StringScanner s = new StringScanner(result); s.hasMoreData(); ) { + if (s.isEol()) { + s.nextLine(); + continue; + } + s.boundedToken('\t'); + String relative = s.line(); + if (!myUnmergedPaths.add(relative)) { + continue; + } + String path = root + "/" + GitUtil.unescapePath(relative); + paths.add(path); + } + return paths; } - return paths; - } - /** - * @return The revision range which will be used to find merge diff (merge may be just finished, or in progress) - * or null in case of error or inconsistency. - */ - @Nullable - public String getRevisionsForDiff() throws VcsException { - String root = myRoot.getPath(); - GitRevisionNumber currentHead = GitRevisionNumber.resolve(myProject, myRoot, "HEAD"); - if (currentHead.equals(myStart)) { - // The head has not advanced. This means that this is a merge that did not commit. - // This could be caused by --no-commit option or by failed two-head merge. The MERGE_HEAD - // should be available. In case of --no-commit option, the MERGE_HEAD might contain - // multiple heads separated by newline. The changes are collected separately for each head - // and they are merged using TreeSet class (that also sorts the changes). - File mergeHeadsFile = myRepository.getRepositoryFiles().getMergeHeadFile(); - try { - if (mergeHeadsFile.exists()) { - String mergeHeads = Files.readString(mergeHeadsFile.toPath(), StandardCharsets.UTF_8); - for (StringScanner s = new StringScanner(mergeHeads); s.hasMoreData(); ) { - String head = s.line(); - if (head.length() == 0) { - continue; + /** + * @return The revision range which will be used to find merge diff (merge may be just finished, or in progress) + * or null in case of error or inconsistency. + */ + @Nullable + public String getRevisionsForDiff() throws VcsException { + String root = myRoot.getPath(); + GitRevisionNumber currentHead = GitRevisionNumber.resolve(myProject, myRoot, "HEAD"); + if (currentHead.equals(myStart)) { + // The head has not advanced. This means that this is a merge that did not commit. + // This could be caused by --no-commit option or by failed two-head merge. The MERGE_HEAD + // should be available. In case of --no-commit option, the MERGE_HEAD might contain + // multiple heads separated by newline. The changes are collected separately for each head + // and they are merged using TreeSet class (that also sorts the changes). + File mergeHeadsFile = myRepository.getRepositoryFiles().getMergeHeadFile(); + try { + if (mergeHeadsFile.exists()) { + String mergeHeads = Files.readString(mergeHeadsFile.toPath(), StandardCharsets.UTF_8); + for (StringScanner s = new StringScanner(mergeHeads); s.hasMoreData(); ) { + String head = s.line(); + if (head.length() == 0) { + continue; + } + // note that "..." cause the diff to start from common parent between head and merge head + return myStart.getRev() + "..." + head; + } + } + } + catch (IOException e) { + //noinspection ThrowableInstanceNeverThrown + throw new VcsException("Unable to read the file " + mergeHeadsFile + ": " + e.getMessage(), e); } - // note that "..." cause the diff to start from common parent between head and merge head - return myStart.getRev() + "..." + head; - } } - } - catch (IOException e) { - //noinspection ThrowableInstanceNeverThrown - throw new VcsException("Unable to read the file " + mergeHeadsFile + ": " + e.getMessage(), e); - } - } - else { - // Otherwise this is a merge that did created a commit. And because of this the incoming changes - // are diffs between old head and new head. The commit could have been multihead commit, - // and the expression below considers it as well. - return myStart.getRev() + "..HEAD"; + else { + // Otherwise this is a merge that did created a commit. And because of this the incoming changes + // are diffs between old head and new head. The commit could have been multihead commit, + // and the expression below considers it as well. + return myStart.getRev() + "..HEAD"; + } + return null; } - return null; - } - /** - * Populates the supplied collections of modified, created and removed files returned by 'git diff #revisions' command, - * where revisions is the range of revisions to check. - */ - public void getChangedFilesExceptUnmerged(Collection updated, - Collection created, - Collection removed, - String revisions) throws VcsException { - if (revisions == null) { - return; - } - String root = myRoot.getPath(); - GitSimpleHandler h = new GitSimpleHandler(myProject, myRoot, GitCommand.DIFF); - h.setSilent(true); - // note that moves are not detected here - h.addParameters("--name-status", "--diff-filter=ADMRUX", "--no-renames", revisions); - for (StringScanner s = new StringScanner(h.run()); s.hasMoreData(); ) { - if (s.isEol()) { - s.nextLine(); - continue; - } - char status = s.peek(); - s.boundedToken('\t'); - final String relative = s.line(); - // eliminate conflicts - if (myUnmergedPaths.contains(relative)) { - continue; - } - String path = root + "/" + GitUtil.unescapePath(relative); - switch (status) { - case 'M': - updated.add(path); - break; - case 'A': - created.add(path); - break; - case 'D': - removed.add(path); - break; - default: - throw new IllegalStateException("Unexpected status: " + status); - } + /** + * Populates the supplied collections of modified, created and removed files returned by 'git diff #revisions' command, + * where revisions is the range of revisions to check. + */ + public void getChangedFilesExceptUnmerged( + Collection updated, + Collection created, + Collection removed, + String revisions + ) throws VcsException { + if (revisions == null) { + return; + } + String root = myRoot.getPath(); + GitSimpleHandler h = new GitSimpleHandler(myProject, myRoot, GitCommand.DIFF); + h.setSilent(true); + // note that moves are not detected here + h.addParameters("--name-status", "--diff-filter=ADMRUX", "--no-renames", revisions); + for (StringScanner s = new StringScanner(h.run()); s.hasMoreData(); ) { + if (s.isEol()) { + s.nextLine(); + continue; + } + char status = s.peek(); + s.boundedToken('\t'); + String relative = s.line(); + // eliminate conflicts + if (myUnmergedPaths.contains(relative)) { + continue; + } + String path = root + "/" + GitUtil.unescapePath(relative); + switch (status) { + case 'M': + updated.add(path); + break; + case 'A': + created.add(path); + break; + case 'D': + removed.add(path); + break; + default: + throw new IllegalStateException("Unexpected status: " + status); + } + } } - } - /** - * Add all paths to the group - */ - private static void addAll(final UpdatedFiles updates, String group_id, Set paths) { - FileGroup fileGroup = updates.getGroupById(group_id); - final VcsKey vcsKey = GitVcs.getKey(); - for (String path : paths) { - fileGroup.add(path, vcsKey, null); + /** + * Add all paths to the group + */ + private static void addAll(UpdatedFiles updates, String groupId, Set paths) { + FileGroup fileGroup = updates.getGroupById(groupId); + VcsKey vcsKey = GitVcs.getKey(); + for (String path : paths) { + fileGroup.add(path, vcsKey, null); + } } - } } diff --git a/plugin/src/main/java/git4idea/push/GitPushNativeResultParser.java b/plugin/src/main/java/git4idea/push/GitPushNativeResultParser.java index b80bf77..0e4806a 100644 --- a/plugin/src/main/java/git4idea/push/GitPushNativeResultParser.java +++ b/plugin/src/main/java/git4idea/push/GitPushNativeResultParser.java @@ -15,15 +15,15 @@ */ package git4idea.push; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import consulo.logging.Logger; -import consulo.util.collection.ContainerUtil; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * Parses the output received from git push and returns a result. * NB: It is assumed that only one ref is pushed => there is only one result in the output. @@ -86,71 +86,56 @@ * For a failed ref, the reason for failure is described. * */ -public class GitPushNativeResultParser -{ +public class GitPushNativeResultParser { - private static final Logger LOG = Logger.getInstance(GitPushNativeResultParser.class); - private static final Pattern PATTERN = Pattern.compile("^.*([ +\\-\\*!=])\t" + // flag - "(\\S+):(\\S+)\t" + // from:to - "([^(]+)" + // summary maybe with a trailing space - "(?:\\((.+)\\))?.*$"); // reason - private static final Pattern RANGE = Pattern.compile("[0-9a-f]+[\\.]{2,3}[0-9a-f]+"); + private static final Logger LOG = Logger.getInstance(GitPushNativeResultParser.class); + private static final Pattern PATTERN = Pattern.compile("^.*([ +\\-\\*!=])\t" + // flag + "(\\S+):(\\S+)\t" + // from:to + "([^(]+)" + // summary maybe with a trailing space + "(?:\\((.+)\\))?.*$"); // reason + private static final Pattern RANGE = Pattern.compile("[0-9a-f]+[\\.]{2,3}[0-9a-f]+"); - @Nonnull - public static List parse(@Nonnull List output) - { - List results = ContainerUtil.newArrayList(); - for(String line : output) - { - Matcher matcher = PATTERN.matcher(line); - if(matcher.matches()) - { - results.add(parseRefResult(matcher, line)); - } - } - return results; - } + @Nonnull + public static List parse(@Nonnull List output) { + List results = new ArrayList<>(); + for (String line : output) { + Matcher matcher = PATTERN.matcher(line); + if (matcher.matches()) { + results.add(parseRefResult(matcher, line)); + } + } + return results; + } - @Nullable - private static GitPushNativeResult parseRefResult(Matcher matcher, String line) - { - String flag = matcher.group(1); - String from = matcher.group(2); - String to = matcher.group(3); - String summary = matcher.group(4).trim(); // the summary can have a trailing space (to simplify the regexp) - @Nullable String reason = matcher.group(5); + @Nullable + private static GitPushNativeResult parseRefResult(Matcher matcher, String line) { + String flag = matcher.group(1); + String from = matcher.group(2); + String to = matcher.group(3); + String summary = matcher.group(4).trim(); // the summary can have a trailing space (to simplify the regexp) + @Nullable String reason = matcher.group(5); - GitPushNativeResult.Type type = parseType(flag); - if(type == null) - { - LOG.error("Couldn't parse push result type from flag [" + flag + "] in [" + line + "]"); - return null; - } - if(matcher.groupCount() < 4) - { - return null; - } - String range = RANGE.matcher(summary).matches() ? summary : null; - return new GitPushNativeResult(type, from, reason, range); - } + GitPushNativeResult.Type type = parseType(flag); + if (type == null) { + LOG.error("Couldn't parse push result type from flag [" + flag + "] in [" + line + "]"); + return null; + } + if (matcher.groupCount() < 4) { + return null; + } + String range = RANGE.matcher(summary).matches() ? summary : null; + return new GitPushNativeResult(type, from, reason, range); + } - private static GitPushNativeResult.Type parseType(String flag) - { - switch(flag.charAt(0)) - { - case ' ': - return GitPushNativeResult.Type.SUCCESS; - case '+': - return GitPushNativeResult.Type.FORCED_UPDATE; - case '-': - return GitPushNativeResult.Type.DELETED; - case '*': - return GitPushNativeResult.Type.NEW_REF; - case '!': - return GitPushNativeResult.Type.REJECTED; - case '=': - return GitPushNativeResult.Type.UP_TO_DATE; - } - return null; - } + private static GitPushNativeResult.Type parseType(String flag) { + return switch (flag.charAt(0)) { + case ' ' -> GitPushNativeResult.Type.SUCCESS; + case '+' -> GitPushNativeResult.Type.FORCED_UPDATE; + case '-' -> GitPushNativeResult.Type.DELETED; + case '*' -> GitPushNativeResult.Type.NEW_REF; + case '!' -> GitPushNativeResult.Type.REJECTED; + case '=' -> GitPushNativeResult.Type.UP_TO_DATE; + default -> null; + }; + } } diff --git a/plugin/src/main/java/git4idea/push/GitPushOperation.java b/plugin/src/main/java/git4idea/push/GitPushOperation.java index fa95ce5..9bad237 100644 --- a/plugin/src/main/java/git4idea/push/GitPushOperation.java +++ b/plugin/src/main/java/git4idea/push/GitPushOperation.java @@ -15,7 +15,7 @@ */ package git4idea.push; -import consulo.application.ApplicationManager; +import consulo.application.Application; import consulo.application.progress.EmptyProgressIndicator; import consulo.application.progress.ProgressIndicator; import consulo.application.progress.ProgressManager; @@ -23,10 +23,11 @@ import consulo.localHistory.LocalHistory; import consulo.logging.Logger; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.ui.ex.awt.DialogWrapper; import consulo.util.collection.ContainerUtil; import consulo.util.lang.ObjectUtil; -import consulo.util.lang.ref.Ref; +import consulo.util.lang.ref.SimpleReference; import consulo.versionControlSystem.VcsException; import consulo.versionControlSystem.distributed.DvcsUtil; import consulo.versionControlSystem.distributed.push.PushSpec; @@ -53,9 +54,9 @@ import git4idea.update.GitUpdateProcess; import git4idea.update.GitUpdateResult; import git4idea.update.GitUpdater; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; import java.util.stream.Collectors; @@ -75,392 +76,412 @@ * */ public class GitPushOperation { - private static final Logger LOG = Logger.getInstance(GitPushOperation.class); - private static final int MAX_PUSH_ATTEMPTS = 10; - - private final Project myProject; - @Nonnull - private final GitPushSupport myPushSupport; - private final Map> myPushSpecs; - @Nullable - private final GitPushTagMode myTagMode; - private final boolean myForce; - private final boolean mySkipHook; - private final Git myGit; - private final ProgressIndicator myProgressIndicator; - private final GitVcsSettings mySettings; - private final GitRepositoryManager myRepositoryManager; - - public GitPushOperation(@Nonnull Project project, - @Nonnull GitPushSupport pushSupport, - @Nonnull Map> pushSpecs, - @Nullable GitPushTagMode tagMode, - boolean force, - boolean skipHook) { - myProject = project; - myPushSupport = pushSupport; - myPushSpecs = pushSpecs; - myTagMode = tagMode; - myForce = force; - mySkipHook = skipHook; - myGit = Git.getInstance(); - myProgressIndicator = ObjectUtil.notNull(ProgressManager.getInstance().getProgressIndicator(), new EmptyProgressIndicator()); - mySettings = GitVcsSettings.getInstance(myProject); - myRepositoryManager = GitRepositoryManager.getInstance(myProject); - - Map currentHeads = new HashMap<>(); - for (GitRepository repository : pushSpecs.keySet()) { - repository.update(); - String head = repository.getCurrentRevision(); - if (head == null) { - LOG.error("This repository has no commits"); - } - else { - currentHeads.put(repository, new GitRevisionNumber(head)); - } - } - } - - @Nonnull - public GitPushResult execute() { - PushUpdateSettings updateSettings = readPushUpdateSettings(); - Label beforePushLabel = null; - Label afterPushLabel = null; - Map preUpdatePositions = updateRootInfoAndRememberPositions(); - Boolean rebaseOverMergeProblemDetected = null; - - final Map results = new HashMap<>(); - Map updatedRoots = new HashMap<>(); - - try { - Collection remainingRoots = myPushSpecs.keySet(); - for (int pushAttempt = 0; pushAttempt < MAX_PUSH_ATTEMPTS && !remainingRoots.isEmpty(); pushAttempt++, remainingRoots = - getRejectedAndNotPushed(results)) { - Map resultMap = push(myRepositoryManager.sortByDependency(remainingRoots)); - results.putAll(resultMap); - - GroupedPushResult result = GroupedPushResult.group(resultMap); - - // stop if error happens, or if push is rejected for a custom reason (not because a pull is needed) - if (!result.errors.isEmpty() || !result.customRejected.isEmpty()) { - break; - } + private static final Logger LOG = Logger.getInstance(GitPushOperation.class); + private static final int MAX_PUSH_ATTEMPTS = 10; - // propose to update if rejected - if (!result.rejected.isEmpty()) { - boolean shouldUpdate = true; - if (myForce || pushingToNotTrackedBranch(result.rejected)) { - shouldUpdate = false; - } - else if (pushAttempt == 0 && !mySettings.autoUpdateIfPushRejected()) { - // the dialog will be shown => check for rebase-over-merge problem in advance to avoid showing several dialogs in a row - rebaseOverMergeProblemDetected = - !findRootsWithMergeCommits(getRootsToUpdate(updateSettings, result.rejected.keySet())).isEmpty(); - - updateSettings = - showDialogAndGetExitCode(result.rejected.keySet(), updateSettings, rebaseOverMergeProblemDetected.booleanValue()); - if (updateSettings != null) { - savePushUpdateSettings(updateSettings, rebaseOverMergeProblemDetected.booleanValue()); + private final Project myProject; + @Nonnull + private final GitPushSupport myPushSupport; + private final Map> myPushSpecs; + @Nullable + private final GitPushTagMode myTagMode; + private final boolean myForce; + private final boolean mySkipHook; + private final Git myGit; + private final ProgressIndicator myProgressIndicator; + private final GitVcsSettings mySettings; + private final GitRepositoryManager myRepositoryManager; + + public GitPushOperation( + @Nonnull Project project, + @Nonnull GitPushSupport pushSupport, + @Nonnull Map> pushSpecs, + @Nullable GitPushTagMode tagMode, + boolean force, + boolean skipHook + ) { + myProject = project; + myPushSupport = pushSupport; + myPushSpecs = pushSpecs; + myTagMode = tagMode; + myForce = force; + mySkipHook = skipHook; + myGit = Git.getInstance(); + myProgressIndicator = ObjectUtil.notNull(ProgressManager.getInstance().getProgressIndicator(), new EmptyProgressIndicator()); + mySettings = GitVcsSettings.getInstance(myProject); + myRepositoryManager = GitRepositoryManager.getInstance(myProject); + + Map currentHeads = new HashMap<>(); + for (GitRepository repository : pushSpecs.keySet()) { + repository.update(); + String head = repository.getCurrentRevision(); + if (head == null) { + LOG.error("This repository has no commits"); } else { - shouldUpdate = false; + currentHeads.put(repository, new GitRevisionNumber(head)); } - } - - if (!shouldUpdate) { - break; - } - - if (beforePushLabel == null) { // put the label only before the very first update - beforePushLabel = LocalHistory.getInstance().putSystemLabel(myProject, "Before push"); - } - Collection rootsToUpdate = getRootsToUpdate(updateSettings, result.rejected.keySet()); - GitUpdateResult updateResult = update(rootsToUpdate, updateSettings.getUpdateMethod(), rebaseOverMergeProblemDetected == null); - for (GitRepository repository : rootsToUpdate) { - updatedRoots.put(repository, updateResult); // TODO update result in GitUpdateProcess is a single for several roots - } - if (!updateResult.isSuccess() || updateResult == GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS || updateResult == GitUpdateResult.INCOMPLETE) { - break; - } } - } } - finally { - if (beforePushLabel != null) { - afterPushLabel = LocalHistory.getInstance().putSystemLabel(myProject, "After push"); - } - for (GitRepository repository : myPushSpecs.keySet()) { - repository.update(); - } - } - return prepareCombinedResult(results, updatedRoots, preUpdatePositions, beforePushLabel, afterPushLabel); - } - - @Nonnull - private Collection getRootsToUpdate(@Nonnull PushUpdateSettings updateSettings, - @Nonnull Set rejectedRepositories) { - return updateSettings.shouldUpdateAllRoots() ? myRepositoryManager.getRepositories() : rejectedRepositories; - } - - @Nonnull - private Collection findRootsWithMergeCommits(@Nonnull Collection rootsToSearch) { - return ContainerUtil.mapNotNull(rootsToSearch, repo -> - { - PushSpec pushSpec = myPushSpecs.get(repo); - if (pushSpec == null) { // repository is not selected to be pushed, but can be rebased - GitPushSource source = myPushSupport.getSource(repo); - GitPushTarget target = myPushSupport.getDefaultTarget(repo); - if (target == null) { - return null; + + @Nonnull + @RequiredUIAccess + public GitPushResult execute() { + PushUpdateSettings updateSettings = readPushUpdateSettings(); + Label beforePushLabel = null; + Label afterPushLabel = null; + Map preUpdatePositions = updateRootInfoAndRememberPositions(); + Boolean rebaseOverMergeProblemDetected = null; + + Map results = new HashMap<>(); + Map updatedRoots = new HashMap<>(); + + try { + Collection remainingRoots = myPushSpecs.keySet(); + for (int pushAttempt = 0; pushAttempt < MAX_PUSH_ATTEMPTS && !remainingRoots.isEmpty(); + pushAttempt++, remainingRoots = getRejectedAndNotPushed(results)) { + Map resultMap = push(myRepositoryManager.sortByDependency(remainingRoots)); + results.putAll(resultMap); + + GroupedPushResult result = GroupedPushResult.group(resultMap); + + // stop if error happens, or if push is rejected for a custom reason (not because a pull is needed) + if (!result.errors.isEmpty() || !result.customRejected.isEmpty()) { + break; + } + + // propose to update if rejected + if (!result.rejected.isEmpty()) { + boolean shouldUpdate = true; + if (myForce || pushingToNotTrackedBranch(result.rejected)) { + shouldUpdate = false; + } + else if (pushAttempt == 0 && !mySettings.autoUpdateIfPushRejected()) { + // the dialog will be shown => check for rebase-over-merge problem in advance to avoid showing several dialogs in a row + rebaseOverMergeProblemDetected = + !findRootsWithMergeCommits(getRootsToUpdate(updateSettings, result.rejected.keySet())).isEmpty(); + + updateSettings = showDialogAndGetExitCode(result.rejected.keySet(), updateSettings, rebaseOverMergeProblemDetected); + if (updateSettings != null) { + savePushUpdateSettings(updateSettings, rebaseOverMergeProblemDetected); + } + else { + shouldUpdate = false; + } + } + + if (!shouldUpdate) { + break; + } + + if (beforePushLabel == null) { // put the label only before the very first update + beforePushLabel = LocalHistory.getInstance().putSystemLabel(myProject, "Before push"); + } + Collection rootsToUpdate = getRootsToUpdate(updateSettings, result.rejected.keySet()); + GitUpdateResult updateResult = + update(rootsToUpdate, updateSettings.getUpdateMethod(), rebaseOverMergeProblemDetected == null); + for (GitRepository repository : rootsToUpdate) { + updatedRoots.put(repository, updateResult); // TODO update result in GitUpdateProcess is a single for several roots + } + if (!updateResult.isSuccess() || updateResult == GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS || updateResult == GitUpdateResult.INCOMPLETE) { + break; + } + } + } } - pushSpec = new PushSpec<>(source, target); - } - String baseRef = pushSpec.getTarget().getBranch().getFullName(); - String currentRef = pushSpec.getSource().getBranch().getFullName(); - return GitRebaseOverMergeProblem.hasProblem(myProject, repo.getRoot(), baseRef, currentRef) ? repo.getRoot() : null; - }); - } - - private static boolean pushingToNotTrackedBranch(@Nonnull Map rejected) { - return ContainerUtil.exists(rejected.entrySet(), entry -> - { - GitRepository repository = entry.getKey(); - GitLocalBranch currentBranch = repository.getCurrentBranch(); - assert currentBranch != null; - GitBranchTrackInfo trackInfo = GitBranchUtil.getTrackInfoForBranch(repository, currentBranch); - return trackInfo == null || !trackInfo.getRemoteBranch().getFullName().equals(entry.getValue().getTargetBranch()); - }); - } - - @Nonnull - private static List getRejectedAndNotPushed(@Nonnull final Map results) { - return filter(results.keySet(), - repository -> results.get(repository).getType() == REJECTED_NO_FF || results.get(repository).getType() == NOT_PUSHED); - } - - @Nonnull - private Map updateRootInfoAndRememberPositions() { - Set repositories = myPushSpecs.keySet(); - repositories.forEach(GitRepository::update); - - return repositories.stream().collect(Collectors.toMap(it -> it, Repository::getCurrentRevision)); - } - - @Nonnull - private GitPushResult prepareCombinedResult(@Nonnull Map allRoots, - @Nonnull Map updatedRoots, - @Nonnull Map preUpdatePositions, - @Nullable Label beforeUpdateLabel, - @Nullable Label afterUpdateLabel) { - Map results = new HashMap<>(); - UpdatedFiles updatedFiles = UpdatedFiles.create(); - for (Map.Entry entry : allRoots.entrySet()) { - GitRepository repository = entry.getKey(); - GitPushRepoResult simpleResult = entry.getValue(); - GitUpdateResult updateResult = updatedRoots.get(repository); - if (updateResult == null) { - results.put(repository, simpleResult); - } - else { - collectUpdatedFiles(updatedFiles, repository, preUpdatePositions.get(repository)); - results.put(repository, GitPushRepoResult.addUpdateResult(simpleResult, updateResult)); - } - } - return new GitPushResult(results, updatedFiles, beforeUpdateLabel, afterUpdateLabel); - } - - @Nonnull - private Map push(@Nonnull List repositories) { - Map results = new LinkedHashMap<>(); - for (GitRepository repository : repositories) { - PushSpec spec = myPushSpecs.get(repository); - ResultWithOutput resultWithOutput = doPush(repository, spec); - LOG.debug("Pushed to " + DvcsUtil.getShortRepositoryName(repository) + ": " + resultWithOutput); - - GitLocalBranch source = spec.getSource().getBranch(); - GitPushTarget target = spec.getTarget(); - GitPushRepoResult repoResult; - if (resultWithOutput.isError()) { - repoResult = GitPushRepoResult.error(source, target.getBranch(), resultWithOutput.getErrorAsString()); - } - else { - List nativeResults = resultWithOutput.parsedResults; - final GitPushNativeResult branchResult = getBranchResult(nativeResults); - if (branchResult == null) { - LOG.error("No result for branch among: [" + nativeResults + "]\n" + "Full result: " + resultWithOutput); - continue; + finally { + if (beforePushLabel != null) { + afterPushLabel = LocalHistory.getInstance().putSystemLabel(myProject, "After push"); + } + for (GitRepository repository : myPushSpecs.keySet()) { + repository.update(); + } } - List tagResults = filter(nativeResults, - result -> !result.equals(branchResult) && (result.getType() == NEW_REF || result.getType() == FORCED_UPDATE)); - int commits = collectNumberOfPushedCommits(repository.getRoot(), branchResult); - repoResult = GitPushRepoResult.convertFromNative(branchResult, tagResults, commits, source, target.getBranch()); - } - - LOG.debug("Converted result: " + repoResult); - results.put(repository, repoResult); + return prepareCombinedResult(results, updatedRoots, preUpdatePositions, beforePushLabel, afterPushLabel); } - // fill other not-processed repositories as not-pushed - for (GitRepository repository : repositories) { - if (!results.containsKey(repository)) { - PushSpec spec = myPushSpecs.get(repository); - results.put(repository, GitPushRepoResult.notPushed(spec.getSource().getBranch(), spec.getTarget().getBranch())); - } + @Nonnull + private Collection getRootsToUpdate( + @Nonnull PushUpdateSettings updateSettings, + @Nonnull Set rejectedRepositories + ) { + return updateSettings.shouldUpdateAllRoots() ? myRepositoryManager.getRepositories() : rejectedRepositories; } - return results; - } - @Nullable - private static GitPushNativeResult getBranchResult(@Nonnull List results) { - return ContainerUtil.find(results, result -> result.getSourceRef().startsWith("refs/heads/")); - } - - private int collectNumberOfPushedCommits(@Nonnull VirtualFile root, @Nonnull GitPushNativeResult result) { - if (result.getType() != GitPushNativeResult.Type.SUCCESS) { - return -1; + @Nonnull + private Collection findRootsWithMergeCommits(@Nonnull Collection rootsToSearch) { + return ContainerUtil.mapNotNull(rootsToSearch, repo -> + { + PushSpec pushSpec = myPushSpecs.get(repo); + if (pushSpec == null) { // repository is not selected to be pushed, but can be rebased + GitPushSource source = myPushSupport.getSource(repo); + GitPushTarget target = myPushSupport.getDefaultTarget(repo); + if (target == null) { + return null; + } + pushSpec = new PushSpec<>(source, target); + } + String baseRef = pushSpec.getTarget().getBranch().getFullName(); + String currentRef = pushSpec.getSource().getBranch().getFullName(); + return GitRebaseOverMergeProblem.hasProblem(myProject, repo.getRoot(), baseRef, currentRef) ? repo.getRoot() : null; + }); } - String range = result.getRange(); - if (range == null) { - LOG.error("Range of pushed commits not reported in " + result); - return -1; + + private static boolean pushingToNotTrackedBranch(@Nonnull Map rejected) { + return ContainerUtil.exists(rejected.entrySet(), entry -> + { + GitRepository repository = entry.getKey(); + GitLocalBranch currentBranch = repository.getCurrentBranch(); + assert currentBranch != null; + GitBranchTrackInfo trackInfo = GitBranchUtil.getTrackInfoForBranch(repository, currentBranch); + return trackInfo == null || !trackInfo.remoteBranch().getFullName().equals(entry.getValue().getTargetBranch()); + }); } - try { - return GitHistoryUtils.history(myProject, root, range).size(); + + @Nonnull + private static List getRejectedAndNotPushed(@Nonnull Map results) { + return filter( + results.keySet(), + repository -> results.get(repository).getType() == REJECTED_NO_FF || results.get(repository).getType() == NOT_PUSHED + ); } - catch (VcsException e) { - LOG.error("Couldn't collect commits from range " + range); - return -1; + + @Nonnull + private Map updateRootInfoAndRememberPositions() { + Set repositories = myPushSpecs.keySet(); + repositories.forEach(GitRepository::update); + + return repositories.stream().collect(Collectors.toMap(it -> it, Repository::getCurrentRevision)); } - } - - private void collectUpdatedFiles(@Nonnull UpdatedFiles updatedFiles, - @Nonnull GitRepository repository, - @Nonnull String preUpdatePosition) { - MergeChangeCollector collector = new MergeChangeCollector(myProject, repository.getRoot(), new GitRevisionNumber(preUpdatePosition)); - ArrayList exceptions = new ArrayList<>(); - collector.collect(updatedFiles, exceptions); - for (VcsException exception : exceptions) { - LOG.info(exception); + + @Nonnull + private GitPushResult prepareCombinedResult( + @Nonnull Map allRoots, + @Nonnull Map updatedRoots, + @Nonnull Map preUpdatePositions, + @Nullable Label beforeUpdateLabel, + @Nullable Label afterUpdateLabel + ) { + Map results = new HashMap<>(); + UpdatedFiles updatedFiles = UpdatedFiles.create(); + for (Map.Entry entry : allRoots.entrySet()) { + GitRepository repository = entry.getKey(); + GitPushRepoResult simpleResult = entry.getValue(); + GitUpdateResult updateResult = updatedRoots.get(repository); + if (updateResult == null) { + results.put(repository, simpleResult); + } + else { + collectUpdatedFiles(updatedFiles, repository, preUpdatePositions.get(repository)); + results.put(repository, GitPushRepoResult.addUpdateResult(simpleResult, updateResult)); + } + } + return new GitPushResult(results, updatedFiles, beforeUpdateLabel, afterUpdateLabel); } - } - - @Nonnull - private ResultWithOutput doPush(@Nonnull GitRepository repository, @Nonnull PushSpec pushSpec) { - GitPushTarget target = pushSpec.getTarget(); - GitLocalBranch sourceBranch = pushSpec.getSource().getBranch(); - GitRemoteBranch targetBranch = target.getBranch(); - - GitLineHandlerListener progressListener = GitStandardProgressAnalyzer.createListener(myProgressIndicator); - boolean setUpstream = pushSpec.getTarget().isNewBranchCreated() && !branchTrackingInfoIsSet(repository, sourceBranch); - String tagMode = myTagMode == null ? null : myTagMode.getArgument(); - - String spec = sourceBranch.getFullName() + ":" + targetBranch.getNameForRemoteOperations(); - GitCommandResult res = - myGit.push(repository, targetBranch.getRemote(), spec, myForce, setUpstream, mySkipHook, tagMode, progressListener); - return new ResultWithOutput(res); - } - - private static boolean branchTrackingInfoIsSet(@Nonnull GitRepository repository, @Nonnull final GitLocalBranch source) { - return ContainerUtil.exists(repository.getBranchTrackInfos(), info -> info.getLocalBranch().equals(source)); - } - - private void savePushUpdateSettings(@Nonnull PushUpdateSettings settings, boolean rebaseOverMergeDetected) { - UpdateMethod updateMethod = settings.getUpdateMethod(); - mySettings.setUpdateAllRootsIfPushRejected(settings.shouldUpdateAllRoots()); - if (!rebaseOverMergeDetected // don't overwrite explicit "rebase" with temporary "merge" caused by merge commits - && mySettings.getUpdateType() != updateMethod && mySettings.getUpdateType() != UpdateMethod.BRANCH_DEFAULT) { // don't overwrite "branch default" setting - mySettings.setUpdateType(updateMethod); + + @Nonnull + private Map push(@Nonnull List repositories) { + Map results = new LinkedHashMap<>(); + for (GitRepository repository : repositories) { + PushSpec spec = myPushSpecs.get(repository); + ResultWithOutput resultWithOutput = doPush(repository, spec); + LOG.debug("Pushed to " + DvcsUtil.getShortRepositoryName(repository) + ": " + resultWithOutput); + + GitLocalBranch source = spec.getSource().getBranch(); + GitPushTarget target = spec.getTarget(); + GitPushRepoResult repoResult; + if (resultWithOutput.isError()) { + repoResult = GitPushRepoResult.error(source, target.getBranch(), resultWithOutput.getErrorAsString()); + } + else { + List nativeResults = resultWithOutput.parsedResults; + GitPushNativeResult branchResult = getBranchResult(nativeResults); + if (branchResult == null) { + LOG.error("No result for branch among: [" + nativeResults + "]\n" + "Full result: " + resultWithOutput); + continue; + } + List tagResults = filter( + nativeResults, + result -> !result.equals(branchResult) && (result.getType() == NEW_REF || result.getType() == FORCED_UPDATE) + ); + int commits = collectNumberOfPushedCommits(repository.getRoot(), branchResult); + repoResult = GitPushRepoResult.convertFromNative(branchResult, tagResults, commits, source, target.getBranch()); + } + + LOG.debug("Converted result: " + repoResult); + results.put(repository, repoResult); + } + + // fill other not-processed repositories as not-pushed + for (GitRepository repository : repositories) { + if (!results.containsKey(repository)) { + PushSpec spec = myPushSpecs.get(repository); + results.put(repository, GitPushRepoResult.notPushed(spec.getSource().getBranch(), spec.getTarget().getBranch())); + } + } + return results; } - } - - @Nonnull - private PushUpdateSettings readPushUpdateSettings() { - boolean updateAllRoots = mySettings.shouldUpdateAllRootsIfPushRejected(); - UpdateMethod updateMethod = mySettings.getUpdateType(); - if (updateMethod == UpdateMethod.BRANCH_DEFAULT) { - // deliberate limitation: we have only 2 buttons => choose method from the 1st repo if different - updateMethod = GitUpdater.resolveUpdateMethod(myPushSpecs.keySet().iterator().next()); + + @Nullable + private static GitPushNativeResult getBranchResult(@Nonnull List results) { + return ContainerUtil.find(results, result -> result.getSourceRef().startsWith("refs/heads/")); } - return new PushUpdateSettings(updateAllRoots, updateMethod); - } - - @Nullable - private PushUpdateSettings showDialogAndGetExitCode(@Nonnull final Set repositories, - @Nonnull final PushUpdateSettings initialSettings, - final boolean - rebaseOverMergeProblemDetected) { - Ref updateSettings = Ref.create(); - ApplicationManager.getApplication().invokeAndWait(() -> - { - GitRejectedPushUpdateDialog dialog = new GitRejectedPushUpdateDialog(myProject, - repositories, - initialSettings, - rebaseOverMergeProblemDetected); - DialogManager.show(dialog); - int exitCode = dialog.getExitCode(); - if (exitCode != DialogWrapper.CANCEL_EXIT_CODE) { - mySettings.setAutoUpdateIfPushRejected(dialog.shouldAutoUpdateInFuture()); - updateSettings.set(new PushUpdateSettings(dialog.shouldUpdateAll(), - convertUpdateMethodFromDialogExitCode( - exitCode))); - } - }); - return updateSettings.get(); - } - - @Nonnull - private static UpdateMethod convertUpdateMethodFromDialogExitCode(int exitCode) { - switch (exitCode) { - case GitRejectedPushUpdateDialog.MERGE_EXIT_CODE: - return UpdateMethod.MERGE; - case GitRejectedPushUpdateDialog.REBASE_EXIT_CODE: - return UpdateMethod.REBASE; - default: - throw new IllegalStateException("Unexpected exit code: " + exitCode); + + private int collectNumberOfPushedCommits(@Nonnull VirtualFile root, @Nonnull GitPushNativeResult result) { + if (result.getType() != GitPushNativeResult.Type.SUCCESS) { + return -1; + } + String range = result.getRange(); + if (range == null) { + LOG.error("Range of pushed commits not reported in " + result); + return -1; + } + try { + return GitHistoryUtils.history(myProject, root, range).size(); + } + catch (VcsException e) { + LOG.error("Couldn't collect commits from range " + range); + return -1; + } } - } - - @Nonnull - protected GitUpdateResult update(@Nonnull Collection rootsToUpdate, - @Nonnull UpdateMethod updateMethod, - boolean checkForRebaseOverMergeProblem) { - GitUpdateResult updateResult = new GitUpdateProcess(myProject, - myProgressIndicator, - new HashSet<>(rootsToUpdate), - UpdatedFiles.create(), - checkForRebaseOverMergeProblem, - false).update - (updateMethod); - for (GitRepository repository : rootsToUpdate) { - repository.getRoot().refresh(true, true); - repository.update(); + + private void collectUpdatedFiles( + @Nonnull UpdatedFiles updatedFiles, + @Nonnull GitRepository repository, + @Nonnull String preUpdatePosition + ) { + MergeChangeCollector collector = + new MergeChangeCollector(myProject, repository.getRoot(), new GitRevisionNumber(preUpdatePosition)); + List exceptions = new ArrayList<>(); + collector.collect(updatedFiles, exceptions); + for (VcsException exception : exceptions) { + LOG.info(exception); + } } - return updateResult; - } - private static class ResultWithOutput { @Nonnull - private final List parsedResults; + private ResultWithOutput doPush(@Nonnull GitRepository repository, @Nonnull PushSpec pushSpec) { + GitPushTarget target = pushSpec.getTarget(); + GitLocalBranch sourceBranch = pushSpec.getSource().getBranch(); + GitRemoteBranch targetBranch = target.getBranch(); + + GitLineHandlerListener progressListener = GitStandardProgressAnalyzer.createListener(myProgressIndicator); + boolean setUpstream = pushSpec.getTarget().isNewBranchCreated() && !branchTrackingInfoIsSet(repository, sourceBranch); + String tagMode = myTagMode == null ? null : myTagMode.getArgument(); + + String spec = sourceBranch.getFullName() + ":" + targetBranch.getNameForRemoteOperations(); + GitCommandResult res = + myGit.push(repository, targetBranch.getRemote(), spec, myForce, setUpstream, mySkipHook, tagMode, progressListener); + return new ResultWithOutput(res); + } + + private static boolean branchTrackingInfoIsSet(@Nonnull GitRepository repository, @Nonnull GitLocalBranch source) { + return ContainerUtil.exists(repository.getBranchTrackInfos(), info -> info.localBranch().equals(source)); + } + + private void savePushUpdateSettings(@Nonnull PushUpdateSettings settings, boolean rebaseOverMergeDetected) { + UpdateMethod updateMethod = settings.getUpdateMethod(); + mySettings.setUpdateAllRootsIfPushRejected(settings.shouldUpdateAllRoots()); + if (!rebaseOverMergeDetected // don't overwrite explicit "rebase" with temporary "merge" caused by merge commits + && mySettings.getUpdateType() != updateMethod && mySettings.getUpdateType() != UpdateMethod.BRANCH_DEFAULT) { // don't overwrite "branch default" setting + mySettings.setUpdateType(updateMethod); + } + } + @Nonnull - private final GitCommandResult resultOutput; + private PushUpdateSettings readPushUpdateSettings() { + boolean updateAllRoots = mySettings.shouldUpdateAllRootsIfPushRejected(); + UpdateMethod updateMethod = mySettings.getUpdateType(); + if (updateMethod == UpdateMethod.BRANCH_DEFAULT) { + // deliberate limitation: we have only 2 buttons => choose method from the 1st repo if different + updateMethod = GitUpdater.resolveUpdateMethod(myPushSpecs.keySet().iterator().next()); + } + return new PushUpdateSettings(updateAllRoots, updateMethod); + } - ResultWithOutput(@Nonnull GitCommandResult resultOutput) { - this.resultOutput = resultOutput; - this.parsedResults = GitPushNativeResultParser.parse(resultOutput.getOutput()); + @Nullable + @RequiredUIAccess + private PushUpdateSettings showDialogAndGetExitCode( + @Nonnull Set repositories, + @Nonnull PushUpdateSettings initialSettings, + boolean rebaseOverMergeProblemDetected + ) { + SimpleReference updateSettings = SimpleReference.create(); + Application.get().invokeAndWait(() -> { + GitRejectedPushUpdateDialog dialog = new GitRejectedPushUpdateDialog( + myProject, + repositories, + initialSettings, + rebaseOverMergeProblemDetected + ); + DialogManager.show(dialog); + int exitCode = dialog.getExitCode(); + if (exitCode != DialogWrapper.CANCEL_EXIT_CODE) { + mySettings.setAutoUpdateIfPushRejected(dialog.shouldAutoUpdateInFuture()); + updateSettings.set(new PushUpdateSettings( + dialog.shouldUpdateAll(), + convertUpdateMethodFromDialogExitCode( + exitCode) + )); + } + }); + return updateSettings.get(); } - boolean isError() { - return parsedResults.isEmpty(); + @Nonnull + private static UpdateMethod convertUpdateMethodFromDialogExitCode(int exitCode) { + return switch (exitCode) { + case GitRejectedPushUpdateDialog.MERGE_EXIT_CODE -> UpdateMethod.MERGE; + case GitRejectedPushUpdateDialog.REBASE_EXIT_CODE -> UpdateMethod.REBASE; + default -> throw new IllegalStateException("Unexpected exit code: " + exitCode); + }; } @Nonnull - String getErrorAsString() { - return resultOutput.getErrorOutputAsJoinedString(); + @RequiredUIAccess + protected GitUpdateResult update( + @Nonnull Collection rootsToUpdate, + @Nonnull UpdateMethod updateMethod, + boolean checkForRebaseOverMergeProblem + ) { + GitUpdateResult updateResult = new GitUpdateProcess( + myProject, + myProgressIndicator, + new HashSet<>(rootsToUpdate), + UpdatedFiles.create(), + checkForRebaseOverMergeProblem, + false + ).update(updateMethod); + for (GitRepository repository : rootsToUpdate) { + repository.getRoot().refresh(true, true); + repository.update(); + } + return updateResult; } - @Override - public String toString() { - return "Parsed results: " + parsedResults + "\nCommand output:" + resultOutput; + private static class ResultWithOutput { + @Nonnull + private final List parsedResults; + @Nonnull + private final GitCommandResult resultOutput; + + ResultWithOutput(@Nonnull GitCommandResult resultOutput) { + this.resultOutput = resultOutput; + this.parsedResults = GitPushNativeResultParser.parse(resultOutput.getOutput()); + } + + boolean isError() { + return parsedResults.isEmpty(); + } + + @Nonnull + String getErrorAsString() { + return resultOutput.getErrorOutputAsJoinedString(); + } + + @Override + public String toString() { + return "Parsed results: " + parsedResults + "\nCommand output:" + resultOutput; + } } - } } diff --git a/plugin/src/main/java/git4idea/push/GitPushSupport.java b/plugin/src/main/java/git4idea/push/GitPushSupport.java index 6f84733..21335a8 100644 --- a/plugin/src/main/java/git4idea/push/GitPushSupport.java +++ b/plugin/src/main/java/git4idea/push/GitPushSupport.java @@ -35,170 +35,176 @@ @ExtensionImpl public class GitPushSupport extends PushSupport { - @Nonnull - private final GitRepositoryManager myRepositoryManager; - @Nonnull - private final GitVcs myVcs; - @Nonnull - private final Pusher myPusher; - @Nonnull - private final OutgoingCommitsProvider myOutgoingCommitsProvider; - @Nonnull - private final GitVcsSettings mySettings; - private final GitSharedSettings mySharedSettings; - @Nonnull - private final PushSettings myCommonPushSettings; - - @Inject - public GitPushSupport(@Nonnull Project project) { - myRepositoryManager = GitRepositoryManager.getInstance(project); - myVcs = GitVcs.getInstance(project); - mySettings = GitVcsSettings.getInstance(project); - myPusher = new GitPusher(project, mySettings, this); - myOutgoingCommitsProvider = new GitOutgoingCommitsProvider(project); - mySharedSettings = project.getInstance(GitSharedSettings.class); - myCommonPushSettings = project.getInstance(PushSettings.class); - } - - @Nonnull - @Override - public AbstractVcs getVcs() { - return myVcs; - } - - @Nonnull - @Override - public Pusher getPusher() { - return myPusher; - } - - @Nonnull - @Override - public OutgoingCommitsProvider getOutgoingCommitsProvider() { - return myOutgoingCommitsProvider; - } - - @Nullable - @Override - public GitPushTarget getDefaultTarget(@Nonnull GitRepository repository) { - if (repository.isFresh()) { - return null; - } - GitLocalBranch currentBranch = repository.getCurrentBranch(); - if (currentBranch == null) { - return null; - } - - GitPushTarget persistedTarget = getPersistedTarget(repository, currentBranch); - if (persistedTarget != null) { - return persistedTarget; - } - - GitPushTarget pushSpecTarget = GitPushTarget.getFromPushSpec(repository, currentBranch); - if (pushSpecTarget != null) { - return pushSpecTarget; - } - - GitBranchTrackInfo trackInfo = GitBranchUtil.getTrackInfoForBranch(repository, currentBranch); - if (trackInfo != null) { - return new GitPushTarget(trackInfo.getRemoteBranch(), false); - } - return proposeTargetForNewBranch(repository, currentBranch); - } - - @Nullable - private GitPushTarget getPersistedTarget(@Nonnull GitRepository repository, @Nonnull GitLocalBranch branch) { - GitRemoteBranch target = mySettings.getPushTarget(repository, branch.getName()); - return target == null ? null : new GitPushTarget(target, !repository.getBranches().getRemoteBranches().contains(target)); - } - - private static GitPushTarget proposeTargetForNewBranch(GitRepository repository, GitLocalBranch currentBranch) { - Collection remotes = repository.getRemotes(); - if (remotes.isEmpty()) { - return null; // TODO need to propose to declare new remote - } - else if (remotes.size() == 1) { - return makeTargetForNewBranch(repository, remotes.iterator().next(), currentBranch); - } - else { - GitRemote remote = GitUtil.getDefaultRemote(remotes); - if (remote == null) { - remote = remotes.iterator().next(); - } - return makeTargetForNewBranch(repository, remote, currentBranch); - } - } - - @Nonnull - private static GitPushTarget makeTargetForNewBranch(@Nonnull GitRepository repository, - @Nonnull GitRemote remote, - @Nonnull GitLocalBranch currentBranch) { - GitRemoteBranch existingRemoteBranch = GitUtil.findRemoteBranch(repository, remote, currentBranch.getName()); - if (existingRemoteBranch != null) { - return new GitPushTarget(existingRemoteBranch, false); - } - return new GitPushTarget(new GitStandardRemoteBranch(remote, currentBranch.getName()), true); - } - - @Nonnull - @Override - public GitPushSource getSource(@Nonnull GitRepository repository) { - GitLocalBranch currentBranch = repository.getCurrentBranch(); - return currentBranch != null ? GitPushSource.create(currentBranch) : GitPushSource.create(ObjectUtil.assertNotNull(repository.getCurrentRevision())); // fresh repository is on branch - } - - @Nonnull - @Override - public RepositoryManager getRepositoryManager() { - return myRepositoryManager; - } - - @Nonnull - @Override - public PushTargetPanel createTargetPanel(@Nonnull GitRepository repository, @Nullable GitPushTarget defaultTarget) { - return new GitPushTargetPanel(this, repository, defaultTarget); - } - - @Override - public boolean isForcePushAllowed(@Nonnull GitRepository repo, @Nonnull GitPushTarget target) { - final String targetBranch = target.getBranch().getNameForRemoteOperations(); - return !mySharedSettings.isBranchProtected(targetBranch); - } - - @Override - public boolean isForcePushEnabled() { - return true; - } - - @Nullable - @Override - public VcsPushOptionsPanel createOptionsPanel() { - return new GitPushOptionsPanel(mySettings.getPushTagMode(), - GitVersionSpecialty.SUPPORTS_FOLLOW_TAGS.existsIn(myVcs.getVersion()), - shouldShowSkipHookOption()); - } - - private boolean shouldShowSkipHookOption() { - return GitVersionSpecialty.PRE_PUSH_HOOK.existsIn(myVcs.getVersion()) && getRepositoryManager().getRepositories() - .stream() - .map(e -> e.getInfo().getHooksInfo()) - .anyMatch - (GitHooksInfo::isPrePushHookAvailable); - } - - @Override - public boolean isSilentForcePushAllowed(@Nonnull GitPushTarget target) { - return myCommonPushSettings.containsForcePushTarget(target.getBranch().getRemote().getName(), - target.getBranch().getNameForRemoteOperations()); - } - - @Override - public void saveSilentForcePushTarget(@Nonnull GitPushTarget target) { - myCommonPushSettings.addForcePushTarget(target.getBranch().getRemote().getName(), target.getBranch().getNameForRemoteOperations()); - } - - @Override - public boolean mayChangeTargetsSync() { - return true; - } + @Nonnull + private final GitRepositoryManager myRepositoryManager; + @Nonnull + private final GitVcs myVcs; + @Nonnull + private final Pusher myPusher; + @Nonnull + private final OutgoingCommitsProvider myOutgoingCommitsProvider; + @Nonnull + private final GitVcsSettings mySettings; + private final GitSharedSettings mySharedSettings; + @Nonnull + private final PushSettings myCommonPushSettings; + + @Inject + public GitPushSupport(@Nonnull Project project) { + myRepositoryManager = GitRepositoryManager.getInstance(project); + myVcs = GitVcs.getInstance(project); + mySettings = GitVcsSettings.getInstance(project); + myPusher = new GitPusher(project, mySettings, this); + myOutgoingCommitsProvider = new GitOutgoingCommitsProvider(project); + mySharedSettings = project.getInstance(GitSharedSettings.class); + myCommonPushSettings = project.getInstance(PushSettings.class); + } + + @Nonnull + @Override + public AbstractVcs getVcs() { + return myVcs; + } + + @Nonnull + @Override + public Pusher getPusher() { + return myPusher; + } + + @Nonnull + @Override + public OutgoingCommitsProvider getOutgoingCommitsProvider() { + return myOutgoingCommitsProvider; + } + + @Nullable + @Override + public GitPushTarget getDefaultTarget(@Nonnull GitRepository repository) { + if (repository.isFresh()) { + return null; + } + GitLocalBranch currentBranch = repository.getCurrentBranch(); + if (currentBranch == null) { + return null; + } + + GitPushTarget persistedTarget = getPersistedTarget(repository, currentBranch); + if (persistedTarget != null) { + return persistedTarget; + } + + GitPushTarget pushSpecTarget = GitPushTarget.getFromPushSpec(repository, currentBranch); + if (pushSpecTarget != null) { + return pushSpecTarget; + } + + GitBranchTrackInfo trackInfo = GitBranchUtil.getTrackInfoForBranch(repository, currentBranch); + if (trackInfo != null) { + return new GitPushTarget(trackInfo.remoteBranch(), false); + } + return proposeTargetForNewBranch(repository, currentBranch); + } + + @Nullable + private GitPushTarget getPersistedTarget(@Nonnull GitRepository repository, @Nonnull GitLocalBranch branch) { + GitRemoteBranch target = mySettings.getPushTarget(repository, branch.getName()); + return target == null ? null : new GitPushTarget(target, !repository.getBranches().getRemoteBranches().contains(target)); + } + + private static GitPushTarget proposeTargetForNewBranch(GitRepository repository, GitLocalBranch currentBranch) { + Collection remotes = repository.getRemotes(); + if (remotes.isEmpty()) { + return null; // TODO need to propose to declare new remote + } + else if (remotes.size() == 1) { + return makeTargetForNewBranch(repository, remotes.iterator().next(), currentBranch); + } + else { + GitRemote remote = GitUtil.getDefaultRemote(remotes); + if (remote == null) { + remote = remotes.iterator().next(); + } + return makeTargetForNewBranch(repository, remote, currentBranch); + } + } + + @Nonnull + private static GitPushTarget makeTargetForNewBranch( + @Nonnull GitRepository repository, + @Nonnull GitRemote remote, + @Nonnull GitLocalBranch currentBranch + ) { + GitRemoteBranch existingRemoteBranch = GitUtil.findRemoteBranch(repository, remote, currentBranch.getName()); + if (existingRemoteBranch != null) { + return new GitPushTarget(existingRemoteBranch, false); + } + return new GitPushTarget(new GitStandardRemoteBranch(remote, currentBranch.getName()), true); + } + + @Nonnull + @Override + public GitPushSource getSource(@Nonnull GitRepository repository) { + GitLocalBranch currentBranch = repository.getCurrentBranch(); + return currentBranch != null ? GitPushSource.create(currentBranch) : GitPushSource.create(ObjectUtil.assertNotNull(repository.getCurrentRevision())); // fresh repository is on branch + } + + @Nonnull + @Override + public RepositoryManager getRepositoryManager() { + return myRepositoryManager; + } + + @Nonnull + @Override + public PushTargetPanel createTargetPanel(@Nonnull GitRepository repository, @Nullable GitPushTarget defaultTarget) { + return new GitPushTargetPanel(this, repository, defaultTarget); + } + + @Override + public boolean isForcePushAllowed(@Nonnull GitRepository repo, @Nonnull GitPushTarget target) { + final String targetBranch = target.getBranch().getNameForRemoteOperations(); + return !mySharedSettings.isBranchProtected(targetBranch); + } + + @Override + public boolean isForcePushEnabled() { + return true; + } + + @Nullable + @Override + public VcsPushOptionsPanel createOptionsPanel() { + return new GitPushOptionsPanel( + mySettings.getPushTagMode(), + GitVersionSpecialty.SUPPORTS_FOLLOW_TAGS.existsIn(myVcs.getVersion()), + shouldShowSkipHookOption() + ); + } + + private boolean shouldShowSkipHookOption() { + return GitVersionSpecialty.PRE_PUSH_HOOK.existsIn(myVcs.getVersion()) + && getRepositoryManager().getRepositories() + .stream() + .map(e -> e.getInfo().hooksInfo()) + .anyMatch(GitHooksInfo::prePushHookAvailable); + } + + @Override + public boolean isSilentForcePushAllowed(@Nonnull GitPushTarget target) { + return myCommonPushSettings.containsForcePushTarget( + target.getBranch().getRemote().getName(), + target.getBranch().getNameForRemoteOperations() + ); + } + + @Override + public void saveSilentForcePushTarget(@Nonnull GitPushTarget target) { + myCommonPushSettings.addForcePushTarget(target.getBranch().getRemote().getName(), target.getBranch().getNameForRemoteOperations()); + } + + @Override + public boolean mayChangeTargetsSync() { + return true; + } } diff --git a/plugin/src/main/java/git4idea/push/GitPushTarget.java b/plugin/src/main/java/git4idea/push/GitPushTarget.java index a0bdbc9..fc1c02e 100644 --- a/plugin/src/main/java/git4idea/push/GitPushTarget.java +++ b/plugin/src/main/java/git4idea/push/GitPushTarget.java @@ -15,19 +15,9 @@ */ package git4idea.push; -import static git4idea.GitBranch.REFS_REMOTES_PREFIX; -import static git4idea.GitUtil.findRemoteBranch; - -import java.text.ParseException; -import java.util.Collection; -import java.util.List; - -import jakarta.annotation.Nonnull; - -import consulo.versionControlSystem.distributed.push.PushTarget; -import org.jetbrains.annotations.TestOnly; -import consulo.util.collection.ContainerUtil; import consulo.logging.Logger; +import consulo.util.collection.ContainerUtil; +import consulo.versionControlSystem.distributed.push.PushTarget; import git4idea.GitLocalBranch; import git4idea.GitRemoteBranch; import git4idea.GitStandardRemoteBranch; @@ -37,178 +27,148 @@ import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; import git4idea.validators.GitRefNameValidator; - +import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.text.ParseException; +import java.util.Collection; +import java.util.List; + +import static git4idea.GitBranch.REFS_REMOTES_PREFIX; +import static git4idea.GitUtil.findRemoteBranch; -public class GitPushTarget implements PushTarget -{ - - private static final Logger LOG = Logger.getInstance(GitPushTarget.class); - - @Nonnull - private final GitRemoteBranch myRemoteBranch; - private final boolean myIsNewBranchCreated; - private final boolean myPushingToSpecialRef; - - public GitPushTarget(@Nonnull GitRemoteBranch remoteBranch, boolean isNewBranchCreated) - { - this(remoteBranch, isNewBranchCreated, false); - } - - public GitPushTarget(@Nonnull GitRemoteBranch remoteBranch, boolean isNewBranchCreated, boolean isPushingToSpecialRef) - { - myRemoteBranch = remoteBranch; - myIsNewBranchCreated = isNewBranchCreated; - myPushingToSpecialRef = isPushingToSpecialRef; - } - - @Nonnull - public GitRemoteBranch getBranch() - { - return myRemoteBranch; - } - - @Override - public boolean hasSomethingToPush() - { - return isNewBranchCreated(); - } - - @Nonnull - @Override - public String getPresentation() - { - return myPushingToSpecialRef ? myRemoteBranch.getFullName() : myRemoteBranch.getNameForRemoteOperations(); - } - - public boolean isNewBranchCreated() - { - return myIsNewBranchCreated; - } - - @TestOnly - boolean isSpecialRef() - { - return myPushingToSpecialRef; - } - - @Nonnull - public static GitPushTarget parse(@Nonnull GitRepository repository, @Nullable String remoteName, @Nonnull String branchName) throws ParseException - { - if(remoteName == null) - { - throw new ParseException("No remotes defined", -1); - } - - if(!GitRefNameValidator.getInstance().checkInput(branchName)) - { - throw new ParseException("Invalid destination branch name: " + branchName, -1); - } - - GitRemote remote = findRemote(repository.getRemotes(), remoteName); - if(remote == null) - { - LOG.error("Remote [" + remoteName + "] is not found among " + repository.getRemotes()); - throw new ParseException("Invalid remote: " + remoteName, -1); - } - - GitRemoteBranch existingRemoteBranch = findRemoteBranch(repository, remote, branchName); - if(existingRemoteBranch != null) - { - return new GitPushTarget(existingRemoteBranch, false); - } - GitRemoteBranch rb = new GitStandardRemoteBranch(remote, branchName); - return new GitPushTarget(rb, true); - } - - @Nullable - private static GitRemote findRemote(@Nonnull Collection remotes, @Nonnull final String candidate) - { - return ContainerUtil.find(remotes, remote -> remote.getName().equals(candidate)); - } - - @Nullable - public static GitPushTarget getFromPushSpec(@Nonnull GitRepository repository, @Nonnull GitLocalBranch sourceBranch) - { - final GitRemote remote = getRemoteToPush(repository, GitBranchUtil.getTrackInfoForBranch(repository, sourceBranch)); - if(remote == null) - { - return null; - } - List specs = remote.getPushRefSpecs(); - if(specs.isEmpty()) - { - return null; - } - - String targetRef = GitPushSpecParser.getTargetRef(repository, sourceBranch.getName(), specs); - if(targetRef == null) - { - return null; - } - - String remotePrefix = REFS_REMOTES_PREFIX + remote.getName() + "/"; - if(targetRef.startsWith(remotePrefix)) - { - targetRef = targetRef.substring(remotePrefix.length()); - GitRemoteBranch remoteBranch = GitUtil.findOrCreateRemoteBranch(repository, remote, targetRef); - boolean existingBranch = repository.getBranches().getRemoteBranches().contains(remoteBranch); - return new GitPushTarget(remoteBranch, !existingBranch, false); - } - else - { - GitRemoteBranch remoteBranch = new GitSpecialRefRemoteBranch(targetRef, remote); - return new GitPushTarget(remoteBranch, true, true); - } - } - - @Nullable - private static GitRemote getRemoteToPush(@Nonnull GitRepository repository, @Nullable GitBranchTrackInfo trackInfo) - { - if(trackInfo != null) - { - return trackInfo.getRemote(); - } - return GitUtil.findOrigin(repository.getRemotes()); - } - - @Override - public boolean equals(Object o) - { - if(this == o) - { - return true; - } - if(!(o instanceof GitPushTarget)) - { - return false; - } - - GitPushTarget target = (GitPushTarget) o; - - if(myIsNewBranchCreated != target.myIsNewBranchCreated) - { - return false; - } - if(!myRemoteBranch.equals(target.myRemoteBranch)) - { - return false; - } - - return true; - } - - @Override - public int hashCode() - { - int result = myRemoteBranch.hashCode(); - result = 31 * result + (myIsNewBranchCreated ? 1 : 0); - return result; - } - - @Override - public String toString() - { - return myRemoteBranch.getNameForLocalOperations(); - } +public class GitPushTarget implements PushTarget { + + private static final Logger LOG = Logger.getInstance(GitPushTarget.class); + + @Nonnull + private final GitRemoteBranch myRemoteBranch; + private final boolean myIsNewBranchCreated; + private final boolean myPushingToSpecialRef; + + public GitPushTarget(@Nonnull GitRemoteBranch remoteBranch, boolean isNewBranchCreated) { + this(remoteBranch, isNewBranchCreated, false); + } + + public GitPushTarget(@Nonnull GitRemoteBranch remoteBranch, boolean isNewBranchCreated, boolean isPushingToSpecialRef) { + myRemoteBranch = remoteBranch; + myIsNewBranchCreated = isNewBranchCreated; + myPushingToSpecialRef = isPushingToSpecialRef; + } + + @Nonnull + public GitRemoteBranch getBranch() { + return myRemoteBranch; + } + + @Override + public boolean hasSomethingToPush() { + return isNewBranchCreated(); + } + + @Nonnull + @Override + public String getPresentation() { + return myPushingToSpecialRef ? myRemoteBranch.getFullName() : myRemoteBranch.getNameForRemoteOperations(); + } + + public boolean isNewBranchCreated() { + return myIsNewBranchCreated; + } + + @TestOnly + boolean isSpecialRef() { + return myPushingToSpecialRef; + } + + @Nonnull + public static GitPushTarget parse( + @Nonnull GitRepository repository, + @Nullable String remoteName, + @Nonnull String branchName + ) throws ParseException { + if (remoteName == null) { + throw new ParseException("No remotes defined", -1); + } + + if (!GitRefNameValidator.getInstance().checkInput(branchName)) { + throw new ParseException("Invalid destination branch name: " + branchName, -1); + } + + GitRemote remote = findRemote(repository.getRemotes(), remoteName); + if (remote == null) { + LOG.error("Remote [" + remoteName + "] is not found among " + repository.getRemotes()); + throw new ParseException("Invalid remote: " + remoteName, -1); + } + + GitRemoteBranch existingRemoteBranch = findRemoteBranch(repository, remote, branchName); + if (existingRemoteBranch != null) { + return new GitPushTarget(existingRemoteBranch, false); + } + GitRemoteBranch rb = new GitStandardRemoteBranch(remote, branchName); + return new GitPushTarget(rb, true); + } + + @Nullable + private static GitRemote findRemote(@Nonnull Collection remotes, @Nonnull String candidate) { + return ContainerUtil.find(remotes, remote -> remote.getName().equals(candidate)); + } + + @Nullable + public static GitPushTarget getFromPushSpec(@Nonnull GitRepository repository, @Nonnull GitLocalBranch sourceBranch) { + GitRemote remote = getRemoteToPush(repository, GitBranchUtil.getTrackInfoForBranch(repository, sourceBranch)); + if (remote == null) { + return null; + } + List specs = remote.getPushRefSpecs(); + if (specs.isEmpty()) { + return null; + } + + String targetRef = GitPushSpecParser.getTargetRef(repository, sourceBranch.getName(), specs); + if (targetRef == null) { + return null; + } + + String remotePrefix = REFS_REMOTES_PREFIX + remote.getName() + "/"; + if (targetRef.startsWith(remotePrefix)) { + targetRef = targetRef.substring(remotePrefix.length()); + GitRemoteBranch remoteBranch = GitUtil.findOrCreateRemoteBranch(repository, remote, targetRef); + boolean existingBranch = repository.getBranches().getRemoteBranches().contains(remoteBranch); + return new GitPushTarget(remoteBranch, !existingBranch, false); + } + else { + GitRemoteBranch remoteBranch = new GitSpecialRefRemoteBranch(targetRef, remote); + return new GitPushTarget(remoteBranch, true, true); + } + } + + @Nullable + private static GitRemote getRemoteToPush(@Nonnull GitRepository repository, @Nullable GitBranchTrackInfo trackInfo) { + if (trackInfo != null) { + return trackInfo.getRemote(); + } + return GitUtil.findOrigin(repository.getRemotes()); + } + + @Override + public boolean equals(Object o) { + return this == o + || o instanceof GitPushTarget target + && myIsNewBranchCreated == target.myIsNewBranchCreated + && myRemoteBranch.equals(target.myRemoteBranch); + } + + @Override + public int hashCode() { + int result = myRemoteBranch.hashCode(); + result = 31 * result + (myIsNewBranchCreated ? 1 : 0); + return result; + } + + @Override + public String toString() { + return myRemoteBranch.getNameForLocalOperations(); + } } diff --git a/plugin/src/main/java/git4idea/push/GitPusher.java b/plugin/src/main/java/git4idea/push/GitPusher.java index 3930a2d..0bbf74c 100644 --- a/plugin/src/main/java/git4idea/push/GitPusher.java +++ b/plugin/src/main/java/git4idea/push/GitPusher.java @@ -18,6 +18,7 @@ import consulo.project.Project; import consulo.project.ui.notification.NotificationType; import consulo.project.ui.notification.NotificationsManager; +import consulo.ui.annotation.RequiredUIAccess; import consulo.versionControlSystem.distributed.push.PushSpec; import consulo.versionControlSystem.distributed.push.Pusher; import consulo.versionControlSystem.distributed.push.VcsPushOptionValue; @@ -31,68 +32,72 @@ import java.util.Map; class GitPusher extends Pusher { + @Nonnull + private final Project myProject; + @Nonnull + private final GitVcsSettings mySettings; + @Nonnull + private final GitPushSupport myPushSupport; + @Nonnull + private final GitRepositoryManager myRepositoryManager; - @Nonnull - private final Project myProject; - @Nonnull - private final GitVcsSettings mySettings; - @Nonnull - private final GitPushSupport myPushSupport; - @Nonnull - private final GitRepositoryManager myRepositoryManager; + GitPusher(@Nonnull Project project, @Nonnull GitVcsSettings settings, @Nonnull GitPushSupport pushSupport) { + myProject = project; + mySettings = settings; + myPushSupport = pushSupport; + myRepositoryManager = GitUtil.getRepositoryManager(project); + } - GitPusher(@Nonnull Project project, @Nonnull GitVcsSettings settings, @Nonnull GitPushSupport pushSupport) { - myProject = project; - mySettings = settings; - myPushSupport = pushSupport; - myRepositoryManager = GitUtil.getRepositoryManager(project); - } + @Override + @RequiredUIAccess + public void push( + @Nonnull Map> pushSpecs, + @Nullable VcsPushOptionValue optionValue, + boolean force + ) { + expireExistingErrorsAndWarnings(); + GitPushTagMode pushTagMode; + boolean skipHook; + if (optionValue instanceof GitVcsPushOptionValue pushOptionValue) { + pushTagMode = pushOptionValue.getPushTagMode(); + skipHook = pushOptionValue.isSkipHook(); + } + else { + pushTagMode = null; + skipHook = false; + } - @Override - public void push(@Nonnull Map> pushSpecs, - @Nullable VcsPushOptionValue optionValue, - boolean force) { - expireExistingErrorsAndWarnings(); - GitPushTagMode pushTagMode; - boolean skipHook; - if (optionValue instanceof GitVcsPushOptionValue) { - pushTagMode = ((GitVcsPushOptionValue)optionValue).getPushTagMode(); - skipHook = ((GitVcsPushOptionValue)optionValue).isSkipHook(); - } - else { - pushTagMode = null; - skipHook = false; + GitPushResult result = new GitPushOperation(myProject, myPushSupport, pushSpecs, pushTagMode, force, skipHook).execute(); + GitPushResultNotification notification = GitPushResultNotification.create(myProject, result, myRepositoryManager.moreThanOneRoot()); + notification.notify(myProject); + mySettings.setPushTagMode(pushTagMode); + rememberTargets(pushSpecs); } - GitPushResult result = new GitPushOperation(myProject, myPushSupport, pushSpecs, pushTagMode, force, skipHook).execute(); - GitPushResultNotification notification = GitPushResultNotification.create(myProject, result, myRepositoryManager.moreThanOneRoot()); - notification.notify(myProject); - mySettings.setPushTagMode(pushTagMode); - rememberTargets(pushSpecs); - } - - protected void expireExistingErrorsAndWarnings() { - GitPushResultNotification[] existingNotifications = - NotificationsManager.getNotificationsManager().getNotificationsOfType(GitPushResultNotification.class, myProject); - for (GitPushResultNotification notification : existingNotifications) { - if (notification.getType() != NotificationType.INFORMATION) { - notification.expire(); - } + protected void expireExistingErrorsAndWarnings() { + GitPushResultNotification[] existingNotifications = + NotificationsManager.getNotificationsManager().getNotificationsOfType(GitPushResultNotification.class, myProject); + for (GitPushResultNotification notification : existingNotifications) { + if (notification.getType() != NotificationType.INFORMATION) { + notification.expire(); + } + } } - } - private void rememberTargets(@Nonnull Map> pushSpecs) { - for (Map.Entry> entry : pushSpecs.entrySet()) { - GitRepository repository = entry.getKey(); - GitPushSource source = entry.getValue().getSource(); - GitPushTarget target = entry.getValue().getTarget(); - GitPushTarget defaultTarget = myPushSupport.getDefaultTarget(repository); - if (defaultTarget == null || !target.getBranch().equals(defaultTarget.getBranch())) { - mySettings.setPushTarget(repository, - source.getBranch().getName(), - target.getBranch().getRemote().getName(), - target.getBranch().getNameForRemoteOperations()); - } + private void rememberTargets(@Nonnull Map> pushSpecs) { + for (Map.Entry> entry : pushSpecs.entrySet()) { + GitRepository repository = entry.getKey(); + GitPushSource source = entry.getValue().getSource(); + GitPushTarget target = entry.getValue().getTarget(); + GitPushTarget defaultTarget = myPushSupport.getDefaultTarget(repository); + if (defaultTarget == null || !target.getBranch().equals(defaultTarget.getBranch())) { + mySettings.setPushTarget( + repository, + source.getBranch().getName(), + target.getBranch().getRemote().getName(), + target.getBranch().getNameForRemoteOperations() + ); + } + } } - } } diff --git a/plugin/src/main/java/git4idea/rebase/GitInteractiveRebaseFile.java b/plugin/src/main/java/git4idea/rebase/GitInteractiveRebaseFile.java index 577faa6..dc129b4 100644 --- a/plugin/src/main/java/git4idea/rebase/GitInteractiveRebaseFile.java +++ b/plugin/src/main/java/git4idea/rebase/GitInteractiveRebaseFile.java @@ -15,99 +15,97 @@ */ package git4idea.rebase; -import consulo.application.util.SystemInfo; +import consulo.platform.Platform; import consulo.project.Project; -import consulo.util.collection.ContainerUtil; import consulo.virtualFileSystem.VirtualFile; import git4idea.config.GitConfigUtil; import git4idea.util.StringScanner; import jakarta.annotation.Nonnull; -import org.jetbrains.annotations.NonNls; import java.io.*; import java.nio.charset.Charset; import java.nio.file.Files; +import java.util.ArrayList; import java.util.List; class GitInteractiveRebaseFile { - @NonNls - private static final String CYGDRIVE_PREFIX = "/cygdrive/"; + private static final String CYGDRIVE_PREFIX = "/cygdrive/"; - @Nonnull - private final Project myProject; - @Nonnull - private final VirtualFile myRoot; - @Nonnull - private final String myFile; + @Nonnull + private final Project myProject; + @Nonnull + private final VirtualFile myRoot; + @Nonnull + private final String myFile; - GitInteractiveRebaseFile(@Nonnull Project project, @Nonnull VirtualFile root, @Nonnull String rebaseFilePath) { - myProject = project; - myRoot = root; - myFile = adjustFilePath(rebaseFilePath); - } + GitInteractiveRebaseFile(@Nonnull Project project, @Nonnull VirtualFile root, @Nonnull String rebaseFilePath) { + myProject = project; + myRoot = root; + myFile = adjustFilePath(rebaseFilePath); + } - @Nonnull - public List load() throws IOException, NoopException { - String encoding = GitConfigUtil.getLogEncoding(myProject, myRoot); - List entries = ContainerUtil.newArrayList(); - final StringScanner s = new StringScanner(Files.readString(new File(myFile).toPath(), Charset.forName(encoding))); - boolean noop = false; - while (s.hasMoreData()) { - if (s.isEol() || s.startsWith('#')) { - s.nextLine(); - continue; - } - if (s.startsWith("noop")) { - noop = true; - s.nextLine(); - continue; - } - String action = s.spaceToken(); - String hash = s.spaceToken(); - String comment = s.line(); + @Nonnull + public List load() throws IOException, NoopException { + String encoding = GitConfigUtil.getLogEncoding(myProject, myRoot); + List entries = new ArrayList<>(); + StringScanner s = new StringScanner(Files.readString(new File(myFile).toPath(), Charset.forName(encoding))); + boolean noop = false; + while (s.hasMoreData()) { + if (s.isEol() || s.startsWith('#')) { + s.nextLine(); + continue; + } + if (s.startsWith("noop")) { + noop = true; + s.nextLine(); + continue; + } + String action = s.spaceToken(); + String hash = s.spaceToken(); + String comment = s.line(); - entries.add(new GitRebaseEntry(action, hash, comment)); - } - if (noop && entries.isEmpty()) { - throw new NoopException(); + entries.add(new GitRebaseEntry(action, hash, comment)); + } + if (noop && entries.isEmpty()) { + throw new NoopException(); + } + return entries; } - return entries; - } - public void cancel() throws IOException { - PrintWriter out = new PrintWriter(new FileWriter(myFile)); - try { - out.println("# rebase is cancelled"); - } - finally { - out.close(); + public void cancel() throws IOException { + PrintWriter out = new PrintWriter(new FileWriter(myFile)); + try { + out.println("# rebase is cancelled"); + } + finally { + out.close(); + } } - } - public void save(@Nonnull List entries) throws IOException { - String encoding = GitConfigUtil.getLogEncoding(myProject, myRoot); - PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(myFile), encoding)); - try { - for (GitRebaseEntry e : entries) { - if (e.getAction() != GitRebaseEntry.Action.skip) { - out.println(e.getAction().toString() + " " + e.getCommit() + " " + e.getSubject()); + public void save(@Nonnull List entries) throws IOException { + String encoding = GitConfigUtil.getLogEncoding(myProject, myRoot); + PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(myFile), encoding)); + try { + for (GitRebaseEntry e : entries) { + if (e.getAction() != GitRebaseEntry.Action.skip) { + out.println(e.getAction().toString() + " " + e.getCommit() + " " + e.getSubject()); + } + } + } + finally { + out.close(); } - } - } - finally { - out.close(); } - } - @Nonnull - private static String adjustFilePath(@Nonnull String file) { - if (SystemInfo.isWindows && file.startsWith(CYGDRIVE_PREFIX)) { - final int prefixSize = CYGDRIVE_PREFIX.length(); - return file.substring(prefixSize, prefixSize + 1) + ":" + file.substring(prefixSize + 1); + @Nonnull + private static String adjustFilePath(@Nonnull String file) { + if (Platform.current().os().isWindows() && file.startsWith(CYGDRIVE_PREFIX)) { + int prefixSize = CYGDRIVE_PREFIX.length(); + return file.substring(prefixSize, prefixSize + 1) + ":" + file.substring(prefixSize + 1); + } + return file; } - return file; - } - static class NoopException extends Exception { - } + static class NoopException extends Exception { + } } diff --git a/plugin/src/main/java/git4idea/rebase/GitRebaseProcess.java b/plugin/src/main/java/git4idea/rebase/GitRebaseProcess.java index 635ccd3..9f68e94 100644 --- a/plugin/src/main/java/git4idea/rebase/GitRebaseProcess.java +++ b/plugin/src/main/java/git4idea/rebase/GitRebaseProcess.java @@ -625,8 +625,9 @@ else if ("stash".equals(href)) { } private void abort() { - myProgressManager.run(new Task.Backgroundable(myProject, "Aborting Rebase Process...") { + myProgressManager.run(new Task.Backgroundable(myProject, LocalizeValue.localizeTODO("Aborting Rebase Process...")) { @Override + @RequiredUIAccess public void run(@Nonnull ProgressIndicator indicator) { GitRebaseUtils.abort((Project) myProject, indicator); } diff --git a/plugin/src/main/java/git4idea/rebase/GitRebaseSpec.java b/plugin/src/main/java/git4idea/rebase/GitRebaseSpec.java index be1e213..71903e9 100644 --- a/plugin/src/main/java/git4idea/rebase/GitRebaseSpec.java +++ b/plugin/src/main/java/git4idea/rebase/GitRebaseSpec.java @@ -22,7 +22,6 @@ import consulo.util.collection.ContainerUtil; import consulo.util.lang.Pair; import consulo.util.lang.StringUtil; -import consulo.util.lang.function.Condition; import consulo.versionControlSystem.distributed.DvcsUtil; import consulo.versionControlSystem.log.Hash; import git4idea.GitLocalBranch; @@ -32,267 +31,255 @@ import git4idea.repo.GitRepository; import git4idea.stash.GitChangesSaver; import git4idea.stash.GitStashChangesSaver; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; import static consulo.versionControlSystem.distributed.DvcsUtil.getShortNames; -public class GitRebaseSpec -{ - private static final Logger LOG = Logger.getInstance(GitRebaseSpec.class); +public class GitRebaseSpec { + private static final Logger LOG = Logger.getInstance(GitRebaseSpec.class); - @Nullable - private final GitRebaseParams myParams; - @Nonnull - private final Map myStatuses; - @Nonnull - private final Map myInitialHeadPositions; - @Nonnull - private final Map myInitialBranchNames; - @Nonnull - private final GitChangesSaver mySaver; - private final boolean myShouldBeSaved; + @Nullable + private final GitRebaseParams myParams; + @Nonnull + private final Map myStatuses; + @Nonnull + private final Map myInitialHeadPositions; + @Nonnull + private final Map myInitialBranchNames; + @Nonnull + private final GitChangesSaver mySaver; + private final boolean myShouldBeSaved; - public GitRebaseSpec(@Nullable GitRebaseParams params, - @Nonnull Map statuses, - @Nonnull Map initialHeadPositions, - @Nonnull Map initialBranchNames, - @Nonnull GitChangesSaver saver, - boolean shouldBeSaved) - { - myParams = params; - myStatuses = statuses; - myInitialHeadPositions = initialHeadPositions; - myInitialBranchNames = initialBranchNames; - mySaver = saver; - myShouldBeSaved = shouldBeSaved; - } + public GitRebaseSpec( + @Nullable GitRebaseParams params, + @Nonnull Map statuses, + @Nonnull Map initialHeadPositions, + @Nonnull Map initialBranchNames, + @Nonnull GitChangesSaver saver, + boolean shouldBeSaved + ) { + myParams = params; + myStatuses = statuses; + myInitialHeadPositions = initialHeadPositions; + myInitialBranchNames = initialBranchNames; + mySaver = saver; + myShouldBeSaved = shouldBeSaved; + } - @Nonnull - public static GitRebaseSpec forNewRebase(@Nonnull Project project, @Nonnull GitRebaseParams params, @Nonnull Collection repositories, @Nonnull ProgressIndicator indicator) - { - GitUtil.updateRepositories(repositories); - Map initialHeadPositions = findInitialHeadPositions(repositories, params.getBranch()); - Map initialBranchNames = findInitialBranchNames(repositories); - Map initialStatusMap = new TreeMap<>(DvcsUtil.REPOSITORY_COMPARATOR); - for(GitRepository repository : repositories) - { - initialStatusMap.put(repository, GitRebaseStatus.notStarted()); - } - return new GitRebaseSpec(params, initialStatusMap, initialHeadPositions, initialBranchNames, newSaver(project, indicator), true); - } + @Nonnull + public static GitRebaseSpec forNewRebase( + @Nonnull Project project, + @Nonnull GitRebaseParams params, + @Nonnull Collection repositories, + @Nonnull ProgressIndicator indicator + ) { + GitUtil.updateRepositories(repositories); + Map initialHeadPositions = findInitialHeadPositions(repositories, params.getBranch()); + Map initialBranchNames = findInitialBranchNames(repositories); + Map initialStatusMap = new TreeMap<>(DvcsUtil.REPOSITORY_COMPARATOR); + for (GitRepository repository : repositories) { + initialStatusMap.put(repository, GitRebaseStatus.notStarted()); + } + return new GitRebaseSpec(params, initialStatusMap, initialHeadPositions, initialBranchNames, newSaver(project, indicator), true); + } - @Nullable - public static GitRebaseSpec forResumeInSingleRepository(@Nonnull Project project, @Nonnull GitRepository repository, @Nonnull ProgressIndicator indicator) - { - if(!repository.isRebaseInProgress()) - { - return null; - } - GitRebaseStatus suspended = new GitRebaseStatus(GitRebaseStatus.Type.SUSPENDED, Collections.emptyList()); - return new GitRebaseSpec(null, Collections.singletonMap(repository, suspended), Collections.emptyMap(), Collections.emptyMap(), - newSaver(project, indicator), false); - } + @Nullable + public static GitRebaseSpec forResumeInSingleRepository( + @Nonnull Project project, + @Nonnull GitRepository repository, + @Nonnull ProgressIndicator indicator + ) { + if (!repository.isRebaseInProgress()) { + return null; + } + GitRebaseStatus suspended = new GitRebaseStatus(GitRebaseStatus.Type.SUSPENDED, Collections.emptyList()); + return new GitRebaseSpec( + null, + Collections.singletonMap(repository, suspended), + Collections.emptyMap(), + Collections.emptyMap(), + newSaver(project, indicator), + false + ); + } - public boolean isValid() - { - return singleOngoingRebase() && rebaseStatusesMatch(); - } + public boolean isValid() { + return singleOngoingRebase() && rebaseStatusesMatch(); + } - @Nonnull - public GitChangesSaver getSaver() - { - return mySaver; - } + @Nonnull + public GitChangesSaver getSaver() { + return mySaver; + } - @Nonnull - public Collection getAllRepositories() - { - return myStatuses.keySet(); - } + @Nonnull + public Collection getAllRepositories() { + return myStatuses.keySet(); + } - @Nullable - public GitRepository getOngoingRebase() - { - return ContainerUtil.getFirstItem(getOngoingRebases()); - } + @Nullable + public GitRepository getOngoingRebase() { + return ContainerUtil.getFirstItem(getOngoingRebases()); + } - @Nullable - public GitRebaseParams getParams() - { - return myParams; - } + @Nullable + public GitRebaseParams getParams() { + return myParams; + } - @Nonnull - public Map getStatuses() - { - return Collections.unmodifiableMap(myStatuses); - } + @Nonnull + public Map getStatuses() { + return Collections.unmodifiableMap(myStatuses); + } - @Nonnull - public Map getHeadPositionsToRollback() - { - return ContainerUtil.filter(myInitialHeadPositions, new Condition() - { - @Override - public boolean value(GitRepository repository) - { - return myStatuses.get(repository).getType() == GitRebaseStatus.Type.SUCCESS; - } - }); - } + @Nonnull + public Map getHeadPositionsToRollback() { + return ContainerUtil.filter( + myInitialHeadPositions, + repository -> myStatuses.get(repository).getType() == GitRebaseStatus.Type.SUCCESS + ); + } - /** - * Returns names of branches which were current at the moment of this GitRebaseSpec creation.
- * The map may contain null elements, if some repositories were in the detached HEAD state. - */ - @Nonnull - public Map getInitialBranchNames() - { - return myInitialBranchNames; - } + /** + * Returns names of branches which were current at the moment of this GitRebaseSpec creation.
+ * The map may contain null elements, if some repositories were in the detached HEAD state. + */ + @Nonnull + public Map getInitialBranchNames() { + return myInitialBranchNames; + } - @Nonnull - public GitRebaseSpec cloneWithNewStatuses(@Nonnull Map statuses) - { - return new GitRebaseSpec(myParams, statuses, myInitialHeadPositions, myInitialBranchNames, mySaver, true); - } + @Nonnull + public GitRebaseSpec cloneWithNewStatuses(@Nonnull Map statuses) { + return new GitRebaseSpec(myParams, statuses, myInitialHeadPositions, myInitialBranchNames, mySaver, true); + } - public boolean shouldBeSaved() - { - return myShouldBeSaved; - } + public boolean shouldBeSaved() { + return myShouldBeSaved; + } - /** - * Returns repositories for which rebase is in progress, has failed and we want to retry, or didn't start yet.
- * It is guaranteed that if there is a rebase in progress (returned by {@link #getOngoingRebase()}, it will be the first in the list. - */ - @Nonnull - public List getIncompleteRepositories() - { - List incompleteRepositories = ContainerUtil.newArrayList(); - final GitRepository ongoingRebase = getOngoingRebase(); - if(ongoingRebase != null) - { - incompleteRepositories.add(ongoingRebase); - } - incompleteRepositories.addAll(DvcsUtil.sortRepositories(ContainerUtil.filter(myStatuses.keySet(), new Condition() - { - @Override - public boolean value(@Nonnull GitRepository repository) - { - return !repository.equals(ongoingRebase) && myStatuses.get(repository).getType() != GitRebaseStatus.Type.SUCCESS; - } - }))); - return incompleteRepositories; - } + /** + * Returns repositories for which rebase is in progress, has failed and we want to retry, or didn't start yet.
+ * It is guaranteed that if there is a rebase in progress (returned by {@link #getOngoingRebase()}, it will be the first in the list. + */ + @Nonnull + public List getIncompleteRepositories() { + List incompleteRepositories = new ArrayList<>(); + GitRepository ongoingRebase = getOngoingRebase(); + if (ongoingRebase != null) { + incompleteRepositories.add(ongoingRebase); + } + incompleteRepositories.addAll(DvcsUtil.sortRepositories(ContainerUtil.filter( + myStatuses.keySet(), + repository -> !repository.equals(ongoingRebase) + && myStatuses.get(repository).getType() != GitRebaseStatus.Type.SUCCESS + ))); + return incompleteRepositories; + } - @Nonnull - private static GitStashChangesSaver newSaver(@Nonnull Project project, @Nonnull ProgressIndicator indicator) - { - Git git = ServiceManager.getService(Git.class); - return new GitStashChangesSaver(project, git, indicator, "Uncommitted changes before rebase"); - } + @Nonnull + private static GitStashChangesSaver newSaver(@Nonnull Project project, @Nonnull ProgressIndicator indicator) { + Git git = ServiceManager.getService(Git.class); + return new GitStashChangesSaver(project, git, indicator, "Uncommitted changes before rebase"); + } - @Nonnull - private static Map findInitialHeadPositions(@Nonnull Collection repositories, @Nullable final String branchToCheckout) - { - return ContainerUtil.map2Map(repositories, repository -> - { - String currentRevision = findCurrentRevision(repository, branchToCheckout); - LOG.debug("Current revision in [" + repository.getRoot().getName() + "] is [" + currentRevision + "]"); - return Pair.create(repository, currentRevision); - }); - } + @Nonnull + private static Map findInitialHeadPositions( + @Nonnull Collection repositories, + @Nullable String branchToCheckout + ) { + return ContainerUtil.map2Map(repositories, repository -> + { + String currentRevision = findCurrentRevision(repository, branchToCheckout); + LOG.debug("Current revision in [" + repository.getRoot().getName() + "] is [" + currentRevision + "]"); + return Pair.create(repository, currentRevision); + }); + } - @Nullable - private static String findCurrentRevision(@Nonnull GitRepository repository, @Nullable String branchToCheckout) - { - if(branchToCheckout != null) - { - GitLocalBranch branch = repository.getBranches().findLocalBranch(branchToCheckout); - if(branch != null) - { - Hash hash = repository.getBranches().getHash(branch); - if(hash != null) - { - return hash.asString(); - } - else - { - LOG.warn("The hash for branch [" + branchToCheckout + "] is not known!"); - } - } - else - { - LOG.warn("The branch [" + branchToCheckout + "] is not known!"); - } - } - return repository.getCurrentRevision(); - } + @Nullable + private static String findCurrentRevision(@Nonnull GitRepository repository, @Nullable String branchToCheckout) { + if (branchToCheckout != null) { + GitLocalBranch branch = repository.getBranches().findLocalBranch(branchToCheckout); + if (branch != null) { + Hash hash = repository.getBranches().getHash(branch); + if (hash != null) { + return hash.asString(); + } + else { + LOG.warn("The hash for branch [" + branchToCheckout + "] is not known!"); + } + } + else { + LOG.warn("The branch [" + branchToCheckout + "] is not known!"); + } + } + return repository.getCurrentRevision(); + } - @Nonnull - private static Map findInitialBranchNames(@Nonnull Collection repositories) - { - return ContainerUtil.map2Map(repositories, repository -> - { - String currentBranchName = repository.getCurrentBranchName(); - LOG.debug("Current branch in [" + repository.getRoot().getName() + "] is [" + currentBranchName + "]"); - return Pair.create(repository, currentBranchName); - }); - } + @Nonnull + private static Map findInitialBranchNames(@Nonnull Collection repositories) { + return ContainerUtil.map2Map(repositories, repository -> + { + String currentBranchName = repository.getCurrentBranchName(); + LOG.debug("Current branch in [" + repository.getRoot().getName() + "] is [" + currentBranchName + "]"); + return Pair.create(repository, currentBranchName); + }); + } - @Nonnull - private Collection getOngoingRebases() - { - return ContainerUtil.filter(myStatuses.keySet(), new Condition() - { - @Override - public boolean value(@Nonnull GitRepository repository) - { - return myStatuses.get(repository).getType() == GitRebaseStatus.Type.SUSPENDED; - } - }); - } + @Nonnull + private Collection getOngoingRebases() { + return ContainerUtil.filter( + myStatuses.keySet(), + repository -> myStatuses.get(repository).getType() == GitRebaseStatus.Type.SUSPENDED + ); + } - private boolean singleOngoingRebase() - { - Collection ongoingRebases = getOngoingRebases(); - if(ongoingRebases.size() > 1) - { - LOG.warn("Invalid rebase spec: rebase is in progress in " + getShortNames(ongoingRebases)); - return false; - } - return true; - } + private boolean singleOngoingRebase() { + Collection ongoingRebases = getOngoingRebases(); + if (ongoingRebases.size() > 1) { + LOG.warn("Invalid rebase spec: rebase is in progress in " + getShortNames(ongoingRebases)); + return false; + } + return true; + } - private boolean rebaseStatusesMatch() - { - for(GitRepository repository : myStatuses.keySet()) - { - GitRebaseStatus.Type savedStatus = myStatuses.get(repository).getType(); - if(repository.isRebaseInProgress() && savedStatus != GitRebaseStatus.Type.SUSPENDED) - { - LOG.warn("Invalid rebase spec: rebase is in progress in " + - DvcsUtil.getShortRepositoryName(repository) + ", but it is saved as " + savedStatus); - return false; - } - else if(!repository.isRebaseInProgress() && savedStatus == GitRebaseStatus.Type.SUSPENDED) - { - LOG.warn("Invalid rebase spec: rebase is not in progress in " + DvcsUtil.getShortRepositoryName(repository)); - return false; - } - } - return true; - } + private boolean rebaseStatusesMatch() { + for (GitRepository repository : myStatuses.keySet()) { + GitRebaseStatus.Type savedStatus = myStatuses.get(repository).getType(); + if (repository.isRebaseInProgress() && savedStatus != GitRebaseStatus.Type.SUSPENDED) { + LOG.warn("Invalid rebase spec: rebase is in progress in " + + DvcsUtil.getShortRepositoryName(repository) + ", but it is saved as " + savedStatus); + return false; + } + else if (!repository.isRebaseInProgress() && savedStatus == GitRebaseStatus.Type.SUSPENDED) { + LOG.warn("Invalid rebase spec: rebase is not in progress in " + DvcsUtil.getShortRepositoryName(repository)); + return false; + } + } + return true; + } - @Override - public String toString() - { - String initialHeadPositions = StringUtil.join(myInitialHeadPositions.keySet(), repository -> DvcsUtil.getShortRepositoryName(repository) + ": " + myInitialHeadPositions.get(repository), ", "); - String statuses = StringUtil.join(myStatuses.keySet(), repository -> DvcsUtil.getShortRepositoryName(repository) + ": " + myStatuses.get(repository), ", "); - return String.format("{Params: [%s].\nInitial positions: %s.\nStatuses: %s.\nSaver: %s}", myParams, initialHeadPositions, statuses, mySaver); - } + @Override + public String toString() { + String initialHeadPositions = StringUtil.join( + myInitialHeadPositions.keySet(), + repository -> DvcsUtil.getShortRepositoryName(repository) + ": " + myInitialHeadPositions.get(repository), + ", " + ); + String statuses = StringUtil.join( + myStatuses.keySet(), + repository -> DvcsUtil.getShortRepositoryName(repository) + ": " + myStatuses.get(repository), + ", " + ); + return String.format( + "{Params: [%s].\nInitial positions: %s.\nStatuses: %s.\nSaver: %s}", + myParams, + initialHeadPositions, + statuses, + mySaver + ); + } } diff --git a/plugin/src/main/java/git4idea/remote/GitHttpAuthDataProvider.java b/plugin/src/main/java/git4idea/remote/GitHttpAuthDataProvider.java index dcef2e8..dc8086e 100644 --- a/plugin/src/main/java/git4idea/remote/GitHttpAuthDataProvider.java +++ b/plugin/src/main/java/git4idea/remote/GitHttpAuthDataProvider.java @@ -20,7 +20,6 @@ import consulo.component.extension.ExtensionPointName; import consulo.credentialStorage.AuthData; import git4idea.commands.GitHttpAuthenticator; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -32,10 +31,10 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface GitHttpAuthDataProvider { - ExtensionPointName EP_NAME = ExtensionPointName.create(GitHttpAuthDataProvider.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(GitHttpAuthDataProvider.class); - @Nullable - AuthData getAuthData(@Nonnull String url); + @Nullable + AuthData getAuthData(@Nonnull String url); - void forgetPassword(@Nonnull String url); + void forgetPassword(@Nonnull String url); } diff --git a/plugin/src/main/java/git4idea/remote/GitRememberedInputs.java b/plugin/src/main/java/git4idea/remote/GitRememberedInputs.java index 45aff7a..a6b6e93 100644 --- a/plugin/src/main/java/git4idea/remote/GitRememberedInputs.java +++ b/plugin/src/main/java/git4idea/remote/GitRememberedInputs.java @@ -23,10 +23,10 @@ import consulo.component.persist.Storage; import consulo.component.persist.StoragePathMacros; import consulo.ide.ServiceManager; -import jakarta.inject.Singleton; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.inject.Singleton; + import java.util.ArrayList; import java.util.List; @@ -35,93 +35,92 @@ */ @Singleton @State( - name = "GitRememberedInputs", - storages = @Storage(file = StoragePathMacros.APP_CONFIG + "/vcs.xml") + name = "GitRememberedInputs", + storages = @Storage(file = StoragePathMacros.APP_CONFIG + "/vcs.xml") ) @ServiceAPI(ComponentScope.APPLICATION) @ServiceImpl public class GitRememberedInputs implements PersistentStateComponent { + private State myState = new State(); + + public static class State { + public List visitedUrls = new ArrayList<>(); + public String cloneParentDir = ""; + public String puttyKey = ""; + } + + public static class UrlAndUserName { + public String url; + public String userName; + } + + public static GitRememberedInputs getInstance() { + return ServiceManager.getService(GitRememberedInputs.class); + } + + @Override + public State getState() { + return myState; + } - private State myState = new State(); - - public static class State { - public List visitedUrls = new ArrayList(); - public String cloneParentDir = ""; - public String puttyKey = ""; - } - - public static class UrlAndUserName { - public String url; - public String userName; - } - - public static GitRememberedInputs getInstance() { - return ServiceManager.getService(GitRememberedInputs.class); - } - - @Override - public State getState() { - return myState; - } - - @Override - public void loadState(State state) { - myState = state; - } - - public void addUrl(@Nonnull String url) { - addUrl(url, ""); - } - - public void addUrl(@Nonnull String url, @Nonnull String userName) { - for (UrlAndUserName visitedUrl : myState.visitedUrls) { - if (visitedUrl.url.equalsIgnoreCase(url)) { // don't add multiple entries for a single url - if (!userName.isEmpty()) { // rewrite username, unless no username is specified - visitedUrl.userName = userName; + @Override + public void loadState(State state) { + myState = state; + } + + public void addUrl(@Nonnull String url) { + addUrl(url, ""); + } + + public void addUrl(@Nonnull String url, @Nonnull String userName) { + for (UrlAndUserName visitedUrl : myState.visitedUrls) { + if (visitedUrl.url.equalsIgnoreCase(url)) { // don't add multiple entries for a single url + if (!userName.isEmpty()) { // rewrite username, unless no username is specified + visitedUrl.userName = userName; + } + return; + } } - return; - } + + UrlAndUserName urlAndUserName = new UrlAndUserName(); + urlAndUserName.url = url; + urlAndUserName.userName = userName; + myState.visitedUrls.add(urlAndUserName); } - UrlAndUserName urlAndUserName = new UrlAndUserName(); - urlAndUserName.url = url; - urlAndUserName.userName = userName; - myState.visitedUrls.add(urlAndUserName); - } - - @Nullable - public String getUserNameForUrl(String url) { - for (UrlAndUserName urlAndUserName : myState.visitedUrls) { - if (urlAndUserName.url.equalsIgnoreCase(url)) { - return urlAndUserName.userName; - } + @Nullable + public String getUserNameForUrl(String url) { + for (UrlAndUserName urlAndUserName : myState.visitedUrls) { + if (urlAndUserName.url.equalsIgnoreCase(url)) { + return urlAndUserName.userName; + } + } + return null; } - return null; - } - - @Nonnull - public List getVisitedUrls() { - List urls = new ArrayList(myState.visitedUrls.size()); - for (UrlAndUserName urlAndUserName : myState.visitedUrls) { - urls.add(urlAndUserName.url); + + @Nonnull + public List getVisitedUrls() { + List urls = new ArrayList<>(myState.visitedUrls.size()); + for (UrlAndUserName urlAndUserName : myState.visitedUrls) { + urls.add(urlAndUserName.url); + } + return urls; } - return urls; - } - public void setPuttyKey(String puttyKey) { - myState.puttyKey = puttyKey; - } + public void setPuttyKey(String puttyKey) { + myState.puttyKey = puttyKey; + } - public String getPuttyKey() { - return myState.puttyKey; - } + public String getPuttyKey() { + return myState.puttyKey; + } - public String getCloneParentDir() { - return myState.cloneParentDir; - } + public String getCloneParentDir() { + return myState.cloneParentDir; + } - public void setCloneParentDir(String cloneParentDir) { - myState.cloneParentDir = cloneParentDir; - } + public void setCloneParentDir(String cloneParentDir) { + myState.cloneParentDir = cloneParentDir; + } } diff --git a/plugin/src/main/java/git4idea/repo/GitBranchState.java b/plugin/src/main/java/git4idea/repo/GitBranchState.java index 02363e7..458b583 100644 --- a/plugin/src/main/java/git4idea/repo/GitBranchState.java +++ b/plugin/src/main/java/git4idea/repo/GitBranchState.java @@ -15,70 +15,63 @@ */ package git4idea.repo; -import java.util.Map; - -import jakarta.annotation.Nonnull; - import consulo.versionControlSystem.distributed.repository.Repository; import consulo.versionControlSystem.log.Hash; import git4idea.GitLocalBranch; import git4idea.GitRemoteBranch; - +import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; -class GitBranchState -{ - @Nullable - private final String currentRevision; - @Nullable - private final GitLocalBranch currentBranch; - @Nonnull - private final Repository.State state; - @Nonnull - private final Map localBranches; - @Nonnull - private final Map remoteBranches; +import java.util.Map; + +class GitBranchState { + @Nullable + private final String currentRevision; + @Nullable + private final GitLocalBranch currentBranch; + @Nonnull + private final Repository.State state; + @Nonnull + private final Map localBranches; + @Nonnull + private final Map remoteBranches; - GitBranchState(@Nullable String currentRevision, - @Nullable GitLocalBranch currentBranch, - @Nonnull Repository.State state, - @Nonnull Map localBranches, - @Nonnull Map remoteBranches) - { - this.currentRevision = currentRevision; - this.currentBranch = currentBranch; - this.state = state; - this.localBranches = localBranches; - this.remoteBranches = remoteBranches; - } + GitBranchState( + @Nullable String currentRevision, + @Nullable GitLocalBranch currentBranch, + @Nonnull Repository.State state, + @Nonnull Map localBranches, + @Nonnull Map remoteBranches + ) { + this.currentRevision = currentRevision; + this.currentBranch = currentBranch; + this.state = state; + this.localBranches = localBranches; + this.remoteBranches = remoteBranches; + } - @Nullable - public String getCurrentRevision() - { - return currentRevision; - } + @Nullable + public String getCurrentRevision() { + return currentRevision; + } - @Nullable - public GitLocalBranch getCurrentBranch() - { - return currentBranch; - } + @Nullable + public GitLocalBranch getCurrentBranch() { + return currentBranch; + } - @Nonnull - public Repository.State getState() - { - return state; - } + @Nonnull + public Repository.State getState() { + return state; + } - @Nonnull - public Map getLocalBranches() - { - return localBranches; - } + @Nonnull + public Map getLocalBranches() { + return localBranches; + } - @Nonnull - public Map getRemoteBranches() - { - return remoteBranches; - } + @Nonnull + public Map getRemoteBranches() { + return remoteBranches; + } } diff --git a/plugin/src/main/java/git4idea/repo/GitBranchTrackInfo.java b/plugin/src/main/java/git4idea/repo/GitBranchTrackInfo.java index 2ee78c2..64aa043 100644 --- a/plugin/src/main/java/git4idea/repo/GitBranchTrackInfo.java +++ b/plugin/src/main/java/git4idea/repo/GitBranchTrackInfo.java @@ -22,61 +22,14 @@ /** * @author Kirill Likhodedov */ -public class GitBranchTrackInfo { - - @Nonnull - private final GitLocalBranch myLocalBranch; - @Nonnull - private final GitRemoteBranch myRemoteBranch; - private final boolean myMerge; - - GitBranchTrackInfo(@Nonnull GitLocalBranch localBranch, @Nonnull GitRemoteBranch remoteBranch, boolean merge) { - myLocalBranch = localBranch; - myRemoteBranch = remoteBranch; - myMerge = merge; - } - - @Nonnull - public GitLocalBranch getLocalBranch() { - return myLocalBranch; - } - - @Nonnull - public GitRemote getRemote() { - return myRemoteBranch.getRemote(); - } - - @Nonnull - public GitRemoteBranch getRemoteBranch() { - return myRemoteBranch; - } - - @Override - public String toString() { - return String.format("%s->%s", myLocalBranch.getName(), myRemoteBranch.getName()); - } - - @SuppressWarnings("ConstantConditions") // fields may possibly become null in future - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - GitBranchTrackInfo that = (GitBranchTrackInfo)o; - - if (myMerge != that.myMerge) return false; - if (myLocalBranch != null ? !myLocalBranch.equals(that.myLocalBranch) : that.myLocalBranch != null) return false; - if (myRemoteBranch != null ? !myRemoteBranch.equals(that.myRemoteBranch) : that.myRemoteBranch != null) return false; - - return true; - } - - @SuppressWarnings("ConstantConditions") // fields may possibly become null in future - @Override - public int hashCode() { - int result = myLocalBranch != null ? myLocalBranch.hashCode() : 0; - result = 31 * result + (myRemoteBranch != null ? myRemoteBranch.hashCode() : 0); - result = 31 * result + (myMerge ? 1 : 0); - return result; - } +public record GitBranchTrackInfo(@Nonnull GitLocalBranch localBranch, @Nonnull GitRemoteBranch remoteBranch, boolean merge) { + @Nonnull + public GitRemote getRemote() { + return remoteBranch.getRemote(); + } + + @Override + public String toString() { + return String.format("%s->%s", localBranch.getName(), remoteBranch.getName()); + } } diff --git a/plugin/src/main/java/git4idea/repo/GitConfig.java b/plugin/src/main/java/git4idea/repo/GitConfig.java index f617099..3cfd2ae 100644 --- a/plugin/src/main/java/git4idea/repo/GitConfig.java +++ b/plugin/src/main/java/git4idea/repo/GitConfig.java @@ -36,12 +36,12 @@ /** * Reads information from the {@code .git/config} file, and parses it to actual objects. - *

- * Currently doesn't read all the information: just general information about remotes and branch tracking. - *

- * Parsing is performed with the help of ini4j library. - *

- * TODO: note, that other git configuration files (such as ~/.gitconfig) are not handled yet. + * + *

Currently doesn't read all the information: just general information about remotes and branch tracking.

+ * + *

Parsing is performed with the help of ini4j library.

+ * + *

TODO: note, that other git configuration files (such as ~/.gitconfig) are not handled yet.

*/ public class GitConfig { private static final Logger LOG = Logger.getInstance(GitConfig.class); @@ -67,10 +67,10 @@ private GitConfig(@Nonnull Collection remotes, @Nonnull Collection /** *

Returns Git remotes defined in {@code .git/config}.

- *

+ * *

Remote is returned with all transformations (such as {@code pushUrl, url..insteadOf}) already applied to it. * See {@link GitRemote} for details.

- *

+ * *

Note: remotes can be defined separately in {@code .git/remotes} directory, by creating a file for each remote with * remote parameters written in the file. This method returns ONLY remotes defined in {@code .git/config}.

* @@ -79,11 +79,13 @@ private GitConfig(@Nonnull Collection remotes, @Nonnull Collection @Nonnull Collection parseRemotes() { // populate GitRemotes with substituting urls when needed - return ContainerUtil.map(myRemotes, remote -> - { - assert remote != null; - return convertRemoteToGitRemote(myUrls, remote); - }); + return ContainerUtil.map( + myRemotes, + remote -> { + assert remote != null; + return convertRemoteToGitRemote(myUrls, remote); + } + ); } @Nonnull @@ -107,19 +109,21 @@ Collection parseTrackInfos( @Nonnull Collection localBranches, @Nonnull Collection remoteBranches ) { - return ContainerUtil.mapNotNull(myTrackedInfos, config -> - { - if (config != null) { - return convertBranchConfig(config, localBranches, remoteBranches); + return ContainerUtil.mapNotNull( + myTrackedInfos, + config -> { + if (config != null) { + return convertBranchConfig(config, localBranches, remoteBranches); + } + return null; } - return null; - }); + ); } /** * Creates an instance of GitConfig by reading information from the specified {@code .git/config} file. - *

- * If some section is invalid, it is skipped, and a warning is reported. + * + *

If some section is invalid, it is skipped, and a warning is reported.

*/ @Nonnull static GitConfig read(@Nonnull File configFile) { @@ -260,30 +264,25 @@ private static Pair, Collection> parseRemotes(@Nonnull I } /** - *

- * Applies {@code url..insteadOf} and {@code url..pushInsteadOf} transformations to {@code url} and {@code pushUrl} of - * the given remote. - *

- *

- * The logic, is as follows: + *

Applies {@code url..insteadOf} and {@code url..pushInsteadOf} transformations to {@code url} and {@code pushUrl} of + * the given remote.

+ * + *

The logic, is as follows: *

    *
  • If remote.url starts with url.insteadOf, it it substituted.
  • *
  • If remote.pushUrl starts with url.insteadOf, it is substituted.
  • *
  • If remote.pushUrl starts with url.pushInsteadOf, it is not substituted.
  • *
  • If remote.url starts with url.pushInsteadOf, but remote.pushUrl is given, additional push url is not added.
  • - *
- *

- *

+ *

+ * *

* TODO: if there are several matches in url sections, the longest should be applied. // currently only one is applied *

- *

- *

- * This is according to {@code man git-config ("url..insteadOf" and "url..pushInsteadOf" sections}, + * + *

* This is according to {@code man git-config ("url..insteadOf" and "url..pushInsteadOf" sections}, * {@code man git-push ("URLS" section)} and the following discussions in the Git mailing list: * insteadOf override urls and pushUrls, - * pushInsteadOf doesn't override explicit pushUrl. - *

+ * pushInsteadOf doesn't override explicit pushUrl.

*/ @Nonnull private static UrlsAndPushUrls substituteUrls(@Nonnull Collection urlSections, @Nonnull Remote remote) { @@ -421,7 +420,6 @@ private List getPushSpec() { private List getFetchSpecs() { return Arrays.asList(notNull(myRemoteBean.getFetch())); } - } private interface RemoteBean { diff --git a/plugin/src/main/java/git4idea/repo/GitConfigHelper.java b/plugin/src/main/java/git4idea/repo/GitConfigHelper.java index 872cc3c..74be525 100644 --- a/plugin/src/main/java/git4idea/repo/GitConfigHelper.java +++ b/plugin/src/main/java/git4idea/repo/GitConfigHelper.java @@ -1,36 +1,33 @@ package git4idea.repo; -import java.io.File; -import java.io.IOException; - +import consulo.logging.Logger; import jakarta.annotation.Nonnull; import org.ini4j.Ini; -import consulo.logging.Logger; +import java.io.File; +import java.io.IOException; /** * from kotlin */ -class GitConfigHelper -{ - private static final Logger LOGGER = Logger.getInstance(GitConfigHelper.class); +class GitConfigHelper { + private static final Logger LOGGER = Logger.getInstance(GitConfigHelper.class); - @Nonnull - static Ini loadIniFile(@Nonnull File configFile) throws IOException - { - Ini ini = new Ini(); - ini.getConfig().setMultiOption(true); // duplicate keys (e.g. url in [remote]) - ini.getConfig().setTree(false); // don't need tree structure: it corrupts url in section name (e.g. [url "http://github.com/"] - ini.getConfig().setLowerCaseOption(false); - try - { - ini.load(configFile); - } - catch(IOException e) - { - LOGGER.warn("Couldn't load config file at " + configFile.getPath(), e); - throw e; - } - return ini; - } + @Nonnull + static Ini loadIniFile(@Nonnull File configFile) throws IOException { + Ini ini = new Ini(); + // duplicate keys (e.g. url in [remote]) + ini.getConfig().setMultiOption(true); + // don't need tree structure: it corrupts url in section name (e.g. [url "http://github.com/"] + ini.getConfig().setTree(false); + ini.getConfig().setLowerCaseOption(false); + try { + ini.load(configFile); + } + catch (IOException e) { + LOGGER.warn("Couldn't load config file at " + configFile.getPath(), e); + throw e; + } + return ini; + } } diff --git a/plugin/src/main/java/git4idea/repo/GitHooksInfo.java b/plugin/src/main/java/git4idea/repo/GitHooksInfo.java index dd55028..4866b37 100644 --- a/plugin/src/main/java/git4idea/repo/GitHooksInfo.java +++ b/plugin/src/main/java/git4idea/repo/GitHooksInfo.java @@ -1,70 +1,4 @@ package git4idea.repo; -/** - * from kotlin - */ -public class GitHooksInfo -{ - private final boolean myPreCommitHookAvailable; - private final boolean myPrePushHookAvailable; - - public GitHooksInfo(boolean preCommitHookAvailable, boolean prePushHookAvailable) - { - myPreCommitHookAvailable = preCommitHookAvailable; - myPrePushHookAvailable = prePushHookAvailable; - } - - public boolean isPreCommitHookAvailable() - { - return myPreCommitHookAvailable; - } - - public boolean isPrePushHookAvailable() - { - return myPrePushHookAvailable; - } - - @Override - public String toString() - { - final StringBuilder sb = new StringBuilder("GitHooksInfo{"); - sb.append("myPreCommitHookAvailable=").append(myPreCommitHookAvailable); - sb.append(", myPrePushHookAvailable=").append(myPrePushHookAvailable); - sb.append('}'); - return sb.toString(); - } - - @Override - public boolean equals(Object o) - { - if(this == o) - { - return true; - } - if(o == null || getClass() != o.getClass()) - { - return false; - } - - GitHooksInfo that = (GitHooksInfo) o; - - if(myPreCommitHookAvailable != that.myPreCommitHookAvailable) - { - return false; - } - if(myPrePushHookAvailable != that.myPrePushHookAvailable) - { - return false; - } - - return true; - } - - @Override - public int hashCode() - { - int result = (myPreCommitHookAvailable ? 1 : 0); - result = 31 * result + (myPrePushHookAvailable ? 1 : 0); - return result; - } +public record GitHooksInfo(boolean preCommitHookAvailable, boolean prePushHookAvailable) { } diff --git a/plugin/src/main/java/git4idea/repo/GitModulesFileReader.java b/plugin/src/main/java/git4idea/repo/GitModulesFileReader.java index d8f6506..d3bd819 100644 --- a/plugin/src/main/java/git4idea/repo/GitModulesFileReader.java +++ b/plugin/src/main/java/git4idea/repo/GitModulesFileReader.java @@ -1,80 +1,65 @@ package git4idea.repo; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - +import consulo.logging.Logger; import jakarta.annotation.Nonnull; import org.ini4j.Ini; import org.ini4j.Profile; -import consulo.logging.Logger; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * from kotlin */ -public class GitModulesFileReader -{ - private static interface ModuleBean - { - String getPath(); +public class GitModulesFileReader { + private static interface ModuleBean { + String getPath(); - String getUrl(); - } + String getUrl(); + } - private static final Logger LOGGER = Logger.getInstance(GitModulesFileReader.class); - private static final Pattern MODULE_SECTION = Pattern.compile("submodule \"(.*)\"", Pattern.CASE_INSENSITIVE); + private static final Logger LOGGER = Logger.getInstance(GitModulesFileReader.class); + private static final Pattern MODULE_SECTION = Pattern.compile("submodule \"(.*)\"", Pattern.CASE_INSENSITIVE); - @Nonnull - public static Collection read(File file) - { - if(!file.exists()) - { - return Collections.emptyList(); - } + @Nonnull + public static Collection read(File file) { + if (!file.exists()) { + return Collections.emptyList(); + } - Ini ini; - try - { - ini = GitConfigHelper.loadIniFile(file); - } - catch(IOException e) - { - return Collections.emptyList(); - } + Ini ini; + try { + ini = GitConfigHelper.loadIniFile(file); + } + catch (IOException e) { + return Collections.emptyList(); + } - List modules = new ArrayList<>(); - ClassLoader classLoader = GitConfig.class.getClassLoader(); - for(Map.Entry entry : ini.entrySet()) - { - String sectionName = entry.getKey(); - Profile.Section section = entry.getValue(); + List modules = new ArrayList<>(); + ClassLoader classLoader = GitConfig.class.getClassLoader(); + for (Map.Entry entry : ini.entrySet()) { + String sectionName = entry.getKey(); + Profile.Section section = entry.getValue(); - Matcher matcher = MODULE_SECTION.matcher(sectionName); - if(matcher.matches() && matcher.groupCount() == 1) - { - ModuleBean bean = section.as(ModuleBean.class, classLoader); - String path = bean.getPath(); - String url = bean.getUrl(); - if(path == null || url == null) - { - LOGGER.warn("Partially defined submodule: " + section.toString()); - } - else - { - GitSubmoduleInfo module = new GitSubmoduleInfo(path, url); - LOGGER.debug("Found submodule " + module); - modules.add(module); - } - } - } + Matcher matcher = MODULE_SECTION.matcher(sectionName); + if (matcher.matches() && matcher.groupCount() == 1) { + ModuleBean bean = section.as(ModuleBean.class, classLoader); + String path = bean.getPath(); + String url = bean.getUrl(); + if (path == null || url == null) { + LOGGER.warn("Partially defined submodule: " + section.toString()); + } + else { + GitSubmoduleInfo module = new GitSubmoduleInfo(path, url); + LOGGER.debug("Found submodule " + module); + modules.add(module); + } + } + } - return modules; - } + return modules; + } } diff --git a/plugin/src/main/java/git4idea/repo/GitRemote.java b/plugin/src/main/java/git4idea/repo/GitRemote.java index a213c09..b4b4ee8 100644 --- a/plugin/src/main/java/git4idea/repo/GitRemote.java +++ b/plugin/src/main/java/git4idea/repo/GitRemote.java @@ -17,8 +17,8 @@ import consulo.util.collection.ContainerUtil; import jakarta.annotation.Nonnull; - import jakarta.annotation.Nullable; + import java.io.File; import java.util.Collection; import java.util.Collections; @@ -57,133 +57,121 @@ * * @author Kirill Likhodedov */ -public final class GitRemote implements Comparable -{ - - /** - * This is a special instance of GitRemote used in typical git-svn configurations like: - * [branch "trunk"] - * remote = . - * merge = refs/remotes/git-svn - */ - public static final GitRemote DOT = new GitRemote(".", Collections.singletonList("."), Collections.emptyList(), - Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - - /** - * Default remote name in Git is "origin". - * Usually all Git repositories have an "origin" remote, so it can be used as a default value in some cases. - */ - public static final String ORIGIN_NAME = "origin"; - - @Nonnull - private final String myName; - @Nonnull - private final List myUrls; - @Nonnull - private final Collection myPushUrls; - @Nonnull - final List myFetchRefSpecs; - @Nonnull - private final List myPushRefSpecs; - @Nonnull - private final Collection myPuttyKeyFiles; - - GitRemote(@Nonnull String name, @Nonnull List urls, @Nonnull Collection pushUrls, @Nonnull List fetchRefSpecs, - @Nonnull List pushRefSpecs, @Nonnull Collection puttyKeyFiles) - { - myName = name; - myUrls = urls; - myPushUrls = pushUrls; - myFetchRefSpecs = fetchRefSpecs; - myPushRefSpecs = pushRefSpecs; - myPuttyKeyFiles = puttyKeyFiles; - } - - @Nonnull - public String getName() - { - return myName; - } - - /** - * Returns all urls specified in gitconfig in {@code remote..url}. - * If you need url to fetch, use {@link #getFirstUrl()}, because only the first url is fetched by Git, - * others are ignored. - */ - @Nonnull - public List getUrls() - { - return myUrls; - } - - /** - * @return the first url (to fetch) or null if and only if there are no urls defined for the remote. - */ - @Nullable - public String getFirstUrl() - { - return myUrls.isEmpty() ? null : myUrls.get(0); - } - - @Nonnull - public Collection getPushUrls() - { - return myPushUrls; - } - - @Nonnull - public List getFetchRefSpecs() - { - return myFetchRefSpecs; - } - - @Nonnull - public List getPushRefSpecs() - { - return myPushRefSpecs; - } - - @Override - public boolean equals(Object o) - { - if(this == o) - { - return true; - } - if(o == null || getClass() != o.getClass()) - { - return false; - } - - GitRemote gitRemote = (GitRemote) o; - return myName.equals(gitRemote.myName); - - // other parameters don't count: remotes are equal if their names are equal - // TODO: LOG.warn if other parameters differ - } - - @Override - public int hashCode() - { - return myName.hashCode(); - } - - @Override - public String toString() - { - return String.format("GitRemote{myName='%s', myUrls=%s, myPushUrls=%s, myFetchRefSpec='%s', myPushRefSpec='%s'}", myName, myUrls, - myPushUrls, myFetchRefSpecs, myPushRefSpecs); - } - - @Override - public int compareTo(@Nonnull GitRemote o) - { - return getName().compareTo(o.getName()); - } - - @Nullable - public String getPuttyKeyFile() - { - return ContainerUtil.getFirstItem(myPuttyKeyFiles); - } +public final class GitRemote implements Comparable { + /** + * This is a special instance of GitRemote used in typical git-svn configurations like: + * [branch "trunk"] + * remote = . + * merge = refs/remotes/git-svn + */ + public static final GitRemote DOT = new GitRemote(".", Collections.singletonList("."), Collections.emptyList(), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList() + ); + + /** + * Default remote name in Git is "origin". + * Usually all Git repositories have an "origin" remote, so it can be used as a default value in some cases. + */ + public static final String ORIGIN_NAME = "origin"; + + @Nonnull + private final String myName; + @Nonnull + private final List myUrls; + @Nonnull + private final Collection myPushUrls; + @Nonnull + final List myFetchRefSpecs; + @Nonnull + private final List myPushRefSpecs; + @Nonnull + private final Collection myPuttyKeyFiles; + + GitRemote( + @Nonnull String name, @Nonnull List urls, @Nonnull Collection pushUrls, @Nonnull List fetchRefSpecs, + @Nonnull List pushRefSpecs, @Nonnull Collection puttyKeyFiles + ) { + myName = name; + myUrls = urls; + myPushUrls = pushUrls; + myFetchRefSpecs = fetchRefSpecs; + myPushRefSpecs = pushRefSpecs; + myPuttyKeyFiles = puttyKeyFiles; + } + + @Nonnull + public String getName() { + return myName; + } + + /** + * Returns all urls specified in gitconfig in {@code remote..url}. + * If you need url to fetch, use {@link #getFirstUrl()}, because only the first url is fetched by Git, + * others are ignored. + */ + @Nonnull + public List getUrls() { + return myUrls; + } + + /** + * @return the first url (to fetch) or null if and only if there are no urls defined for the remote. + */ + @Nullable + public String getFirstUrl() { + return myUrls.isEmpty() ? null : myUrls.get(0); + } + + @Nonnull + public Collection getPushUrls() { + return myPushUrls; + } + + @Nonnull + public List getFetchRefSpecs() { + return myFetchRefSpecs; + } + + @Nonnull + public List getPushRefSpecs() { + return myPushRefSpecs; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + GitRemote gitRemote = (GitRemote) o; + return myName.equals(gitRemote.myName); + + // other parameters don't count: remotes are equal if their names are equal + // TODO: LOG.warn if other parameters differ + } + + @Override + public int hashCode() { + return myName.hashCode(); + } + + @Override + public String toString() { + return String.format("GitRemote{myName='%s', myUrls=%s, myPushUrls=%s, myFetchRefSpec='%s', myPushRefSpec='%s'}", myName, myUrls, + myPushUrls, myFetchRefSpecs, myPushRefSpecs + ); + } + + @Override + public int compareTo(@Nonnull GitRemote o) { + return getName().compareTo(o.getName()); + } + + @Nullable + public String getPuttyKeyFile() { + return ContainerUtil.getFirstItem(myPuttyKeyFiles); + } } diff --git a/plugin/src/main/java/git4idea/repo/GitRepoInfo.java b/plugin/src/main/java/git4idea/repo/GitRepoInfo.java index 6542b4e..3879752 100644 --- a/plugin/src/main/java/git4idea/repo/GitRepoInfo.java +++ b/plugin/src/main/java/git4idea/repo/GitRepoInfo.java @@ -15,229 +15,72 @@ */ package git4idea.repo; -import consulo.versionControlSystem.distributed.repository.Repository; -import consulo.versionControlSystem.log.Hash; import consulo.util.collection.HashingStrategy; import consulo.util.collection.Sets; +import consulo.versionControlSystem.distributed.repository.Repository; +import consulo.versionControlSystem.log.Hash; import git4idea.GitBranch; import git4idea.GitLocalBranch; import git4idea.GitRemoteBranch; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; -import java.util.*; - -public class GitRepoInfo -{ - - @Nullable - private final GitLocalBranch myCurrentBranch; - @Nullable - private final String myCurrentRevision; - @Nonnull - private final Repository.State myState; - @Nonnull - private final Set myRemotes; - @Nonnull - private final Map myLocalBranches; - @Nonnull - private final Map myRemoteBranches; - @Nonnull - private final Set myBranchTrackInfos; - @Nonnull - private final Collection mySubmodules; - @Nonnull - private final GitHooksInfo myHooksInfo; - - public GitRepoInfo(@Nullable GitLocalBranch currentBranch, - @Nullable String currentRevision, - @Nonnull Repository.State state, - @Nonnull Collection remotes, - @Nonnull Map localBranches, - @Nonnull Map remoteBranches, - @Nonnull Collection branchTrackInfos, - @Nonnull Collection submodules, - @Nonnull GitHooksInfo hooksInfo) - { - myCurrentBranch = currentBranch; - myCurrentRevision = currentRevision; - myState = state; - myRemotes = new LinkedHashSet<>(remotes); - myLocalBranches = new LinkedHashMap<>(localBranches); - myRemoteBranches = new LinkedHashMap<>(remoteBranches); - myBranchTrackInfos = new LinkedHashSet<>(branchTrackInfos); - mySubmodules = submodules; - myHooksInfo = hooksInfo; - } - - @Nullable - public GitLocalBranch getCurrentBranch() - { - return myCurrentBranch; - } - - @Nonnull - public Collection getRemotes() - { - return myRemotes; - } - - @Nonnull - public Map getLocalBranchesWithHashes() - { - return myLocalBranches; - } - - @Nonnull - public Map getRemoteBranchesWithHashes() - { - return myRemoteBranches; - } - - @Nonnull - @Deprecated - public Collection getRemoteBranches() - { - return myRemoteBranches.keySet(); - } - - @Nonnull - public Collection getBranchTrackInfos() - { - return myBranchTrackInfos; - } - - @Nullable - public String getCurrentRevision() - { - return myCurrentRevision; - } - - @Nonnull - public Repository.State getState() - { - return myState; - } - - @Nonnull - public Collection getSubmodules() - { - return mySubmodules; - } - - @Nonnull - public GitHooksInfo getHooksInfo() - { - return myHooksInfo; - } - - @Override - public boolean equals(Object o) - { - if(this == o) - { - return true; - } - if(o == null || getClass() != o.getClass()) - { - return false; - } - - GitRepoInfo info = (GitRepoInfo) o; - - if(myState != info.myState) - { - return false; - } - if(myCurrentRevision != null ? !myCurrentRevision.equals(info.myCurrentRevision) : info.myCurrentRevision != null) - { - return false; - } - if(myCurrentBranch != null ? !myCurrentBranch.equals(info.myCurrentBranch) : info.myCurrentBranch != null) - { - return false; - } - if(!myRemotes.equals(info.myRemotes)) - { - return false; - } - if(!myBranchTrackInfos.equals(info.myBranchTrackInfos)) - { - return false; - } - if(!areEqual(myLocalBranches, info.myLocalBranches)) - { - return false; - } - if(!areEqual(myRemoteBranches, info.myRemoteBranches)) - { - return false; - } - if(!mySubmodules.equals(info.mySubmodules)) - { - return false; - } - if(!myHooksInfo.equals(info.myHooksInfo)) - { - return false; - } - - return true; - } - - @Override - public int hashCode() - { - int result = myCurrentBranch != null ? myCurrentBranch.hashCode() : 0; - result = 31 * result + (myCurrentRevision != null ? myCurrentRevision.hashCode() : 0); - result = 31 * result + myState.hashCode(); - result = 31 * result + myRemotes.hashCode(); - result = 31 * result + myLocalBranches.hashCode(); - result = 31 * result + myRemoteBranches.hashCode(); - result = 31 * result + myBranchTrackInfos.hashCode(); - result = 31 * result + mySubmodules.hashCode(); - result = 31 * result + myHooksInfo.hashCode(); - return result; - } - - @Override - public String toString() - { - return String.format("GitRepoInfo{current=%s, remotes=%s, localBranches=%s, remoteBranches=%s, trackInfos=%s, submodules=%s, hooks=%s}", myCurrentBranch, myRemotes, myLocalBranches, - myRemoteBranches, myBranchTrackInfos, mySubmodules, myHooksInfo); - } - - private static boolean areEqual(Map c1, Map c2) - { - // GitBranch has perverted equals contract (see the comment there) - // until GitBranch is created only from a single place with correctly defined Hash, we can't change its equals - Set> set1 = Sets.newHashSet(c1.entrySet(), new BranchesComparingStrategy()); - Set> set2 = Sets.newHashSet(c2.entrySet(), new BranchesComparingStrategy()); - return set1.equals(set2); - } - - private static class BranchesComparingStrategy implements HashingStrategy> - { - - @Override - public int hashCode(@Nonnull Map.Entry branchEntry) - { - return 31 * branchEntry.getKey().getName().hashCode() + branchEntry.getValue().hashCode(); - } - - @Override - public boolean equals(@Nonnull Map.Entry b1, @Nonnull Map.Entry b2) - { - if(b1 == b2) - { - return true; - } - if(b1.getClass() != b2.getClass()) - { - return false; - } - return b1.getKey().getName().equals(b2.getKey().getName()) && b1.getValue().equals(b2.getValue()); - } - } +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +public record GitRepoInfo( + @Nullable GitLocalBranch currentBranch, + @Nullable String currentRevision, + @Nonnull Repository.State state, + @Nonnull Collection remotes, + @Nonnull Map localBranches, + @Nonnull Map remoteBranches, + @Nonnull Collection branchTrackInfos, + @Nonnull Collection submodules, + @Nonnull GitHooksInfo hooksInfo +) { + @Nullable + public GitLocalBranch getCurrentBranch() { + return currentBranch(); + } + + @Override + public boolean equals(Object o) { + return this == o + || o instanceof GitRepoInfo info + && state == info.state + && Objects.equals(currentRevision, info.currentRevision) + && Objects.equals(currentBranch, info.currentBranch) + && remotes.equals(info.remotes) + && branchTrackInfos.equals(info.branchTrackInfos) + && areEqual(localBranches, info.localBranches) + && areEqual(remoteBranches, info.remoteBranches) + && submodules.equals(info.submodules) + && hooksInfo.equals(info.hooksInfo); + } + + private static boolean areEqual(Map c1, Map c2) { + // GitBranch has perverted equals contract (see the comment there) + // until GitBranch is created only from a single place with correctly defined Hash, we can't change its equals + Set> set1 = Sets.newHashSet(c1.entrySet(), new BranchesComparingStrategy()); + Set> set2 = Sets.newHashSet(c2.entrySet(), new BranchesComparingStrategy()); + return set1.equals(set2); + } + + private static class BranchesComparingStrategy implements HashingStrategy> { + @Override + public int hashCode(@Nonnull Map.Entry branchEntry) { + return 31 * branchEntry.getKey().getName().hashCode() + branchEntry.getValue().hashCode(); + } + + @Override + public boolean equals(@Nonnull Map.Entry b1, @Nonnull Map.Entry b2) { + return b1 == b2 + || b1.getClass() == b2.getClass() + && b1.getKey().getName().equals(b2.getKey().getName()) + && b1.getValue().equals(b2.getValue()); + } + } } diff --git a/plugin/src/main/java/git4idea/repo/GitRepository.java b/plugin/src/main/java/git4idea/repo/GitRepository.java index e10fcde..f642b07 100644 --- a/plugin/src/main/java/git4idea/repo/GitRepository.java +++ b/plugin/src/main/java/git4idea/repo/GitRepository.java @@ -20,7 +20,6 @@ import git4idea.GitLocalBranch; import git4idea.GitVcs; import git4idea.branch.GitBranchesCollection; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -60,62 +59,62 @@ * @author Kirill Likhodedov */ public interface GitRepository extends Repository { - /** - * @deprecated Use #getRepositoryFiles(), since there will be two administrative directories if user uses git worktrees. - */ - @Deprecated - @Nonnull - VirtualFile getGitDir(); - - @Nonnull - GitRepositoryFiles getRepositoryFiles(); - - @Nonnull - GitUntrackedFilesHolder getUntrackedFilesHolder(); - - - @Nonnull - GitRepoInfo getInfo(); - - /** - * Returns the current branch of this Git repository. - * If the repository is being rebased, then the current branch is the branch being rebased (which was current before the rebase - * operation has started). - * Returns null, if the repository is not on a branch and not in the REBASING state. - */ - @Nullable - GitLocalBranch getCurrentBranch(); - - @Nonnull - GitBranchesCollection getBranches(); - - /** - * Returns remotes defined in this Git repository. - * It is different from {@link GitConfig#getRemotes()} because remotes may be defined not only in {@code .git/config}, - * but in {@code .git/remotes/} or even {@code .git/branches} as well. - * On the other hand, it is a very old way to define remotes and we are not going to implement this until needed. - * See discussion in the Git mailing list that confirms - * that remotes a defined in {@code .git/config} only nowadays. - * - * @return GitRemotes defined for this repository. - */ - @Nonnull - Collection getRemotes(); - - @Nonnull - Collection getBranchTrackInfos(); - - boolean isRebaseInProgress(); - - boolean isOnBranch(); - - @Nonnull - @Override - GitVcs getVcs(); - - /** - * Returns direct submodules of this repository. - */ - @Nonnull - Collection getSubmodules(); + /** + * @deprecated Use #getRepositoryFiles(), since there will be two administrative directories if user uses git worktrees. + */ + @Deprecated + @Nonnull + VirtualFile getGitDir(); + + @Nonnull + GitRepositoryFiles getRepositoryFiles(); + + @Nonnull + GitUntrackedFilesHolder getUntrackedFilesHolder(); + + + @Nonnull + GitRepoInfo getInfo(); + + /** + * Returns the current branch of this Git repository. + * If the repository is being rebased, then the current branch is the branch being rebased (which was current before the rebase + * operation has started). + * Returns null, if the repository is not on a branch and not in the REBASING state. + */ + @Nullable + GitLocalBranch getCurrentBranch(); + + @Nonnull + GitBranchesCollection getBranches(); + + /** + * Returns remotes defined in this Git repository. + * It is different from {@link GitConfig#getRemotes()} because remotes may be defined not only in {@code .git/config}, + * but in {@code .git/remotes/} or even {@code .git/branches} as well. + * On the other hand, it is a very old way to define remotes and we are not going to implement this until needed. + * See discussion in the Git mailing list that confirms + * that remotes a defined in {@code .git/config} only nowadays. + * + * @return GitRemotes defined for this repository. + */ + @Nonnull + Collection getRemotes(); + + @Nonnull + Collection getBranchTrackInfos(); + + boolean isRebaseInProgress(); + + boolean isOnBranch(); + + @Nonnull + @Override + GitVcs getVcs(); + + /** + * Returns direct submodules of this repository. + */ + @Nonnull + Collection getSubmodules(); } diff --git a/plugin/src/main/java/git4idea/repo/GitRepositoryChangeListener.java b/plugin/src/main/java/git4idea/repo/GitRepositoryChangeListener.java index d411b3c..369ea50 100644 --- a/plugin/src/main/java/git4idea/repo/GitRepositoryChangeListener.java +++ b/plugin/src/main/java/git4idea/repo/GitRepositoryChangeListener.java @@ -21,11 +21,10 @@ /** * {@link #repositoryChanged(GitRepository)} is called on every {@link GitRepository} change. + * * @author Kirill Likhodedov */ @TopicAPI(ComponentScope.PROJECT) public interface GitRepositoryChangeListener { - - void repositoryChanged(@Nonnull GitRepository repository); - + void repositoryChanged(@Nonnull GitRepository repository); } diff --git a/plugin/src/main/java/git4idea/repo/GitRepositoryCreator.java b/plugin/src/main/java/git4idea/repo/GitRepositoryCreator.java index 790076b..705cd36 100644 --- a/plugin/src/main/java/git4idea/repo/GitRepositoryCreator.java +++ b/plugin/src/main/java/git4idea/repo/GitRepositoryCreator.java @@ -23,31 +23,30 @@ import consulo.virtualFileSystem.VirtualFile; import git4idea.GitUtil; import git4idea.GitVcs; -import jakarta.inject.Inject; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.inject.Inject; @ExtensionImpl public class GitRepositoryCreator implements VcsRepositoryCreator { - @Nonnull - private final Project myProject; + @Nonnull + private final Project myProject; - @Inject - public GitRepositoryCreator(@Nonnull Project project) { - myProject = project; - } + @Inject + public GitRepositoryCreator(@Nonnull Project project) { + myProject = project; + } - @Override - @Nullable - public Repository createRepositoryIfValid(@Nonnull VirtualFile root) { - VirtualFile gitDir = GitUtil.findGitDir(root); - return gitDir == null ? null : GitRepositoryImpl.getInstance(root, gitDir, myProject, true); - } + @Override + @Nullable + public Repository createRepositoryIfValid(@Nonnull VirtualFile root) { + VirtualFile gitDir = GitUtil.findGitDir(root); + return gitDir == null ? null : GitRepositoryImpl.getInstance(root, gitDir, myProject, true); + } - @Nonnull - @Override - public VcsKey getVcsKey() { - return GitVcs.getKey(); - } + @Nonnull + @Override + public VcsKey getVcsKey() { + return GitVcs.getKey(); + } } diff --git a/plugin/src/main/java/git4idea/repo/GitRepositoryFiles.java b/plugin/src/main/java/git4idea/repo/GitRepositoryFiles.java index dd04c76..2f1b8ae 100644 --- a/plugin/src/main/java/git4idea/repo/GitRepositoryFiles.java +++ b/plugin/src/main/java/git4idea/repo/GitRepositoryFiles.java @@ -20,7 +20,6 @@ import consulo.virtualFileSystem.LocalFileSystem; import consulo.virtualFileSystem.VirtualFile; import consulo.virtualFileSystem.util.VirtualFileUtil; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -36,322 +35,322 @@ * matches once of them. */ public class GitRepositoryFiles { - private static final Logger LOG = Logger.getInstance(GitRepositoryFiles.class); - - private static final String CHERRY_PICK_HEAD = "CHERRY_PICK_HEAD"; - private static final String COMMIT_EDITMSG = "COMMIT_EDITMSG"; - private static final String CONFIG = "config"; - private static final String HEAD = "HEAD"; - private static final String INDEX = "index"; - private static final String INFO = "info"; - private static final String INFO_EXCLUDE = INFO + "/exclude"; - private static final String MERGE_HEAD = "MERGE_HEAD"; - private static final String MERGE_MSG = "MERGE_MSG"; - private static final String ORIG_HEAD = "ORIG_HEAD"; - private static final String REBASE_APPLY = "rebase-apply"; - private static final String REBASE_MERGE = "rebase-merge"; - private static final String PACKED_REFS = "packed-refs"; - private static final String REFS = "refs"; - private static final String HEADS = "heads"; - private static final String TAGS = "tags"; - private static final String REMOTES = "remotes"; - private static final String SQUASH_MSG = "SQUASH_MSG"; - private static final String HOOKS = "hooks"; - private static final String PRE_COMMIT_HOOK = "pre-commit"; - private static final String PRE_PUSH_HOOK = "pre-push"; - - private final VirtualFile myMainDir; - private final VirtualFile myWorktreeDir; - - private final String myConfigFilePath; - private final String myHeadFilePath; - private final String myIndexFilePath; - private final String myMergeHeadPath; - private final String myCherryPickHeadPath; - private final String myOrigHeadPath; - private final String myRebaseApplyPath; - private final String myRebaseMergePath; - private final String myPackedRefsPath; - private final String myRefsHeadsDirPath; - private final String myRefsRemotesDirPath; - private final String myRefsTagsPath; - private final String myCommitMessagePath; - private final String myMergeMessagePath; - private final String myMergeSquashPath; - private final String myInfoDirPath; - private final String myExcludePath; - private final String myHooksDirPath; - - private GitRepositoryFiles(@Nonnull VirtualFile mainDir, @Nonnull VirtualFile worktreeDir) { - myMainDir = mainDir; - myWorktreeDir = worktreeDir; - - String mainPath = myMainDir.getPath(); - myConfigFilePath = mainPath + slash(CONFIG); - myPackedRefsPath = mainPath + slash(PACKED_REFS); - String refsPath = mainPath + slash(REFS); - myRefsHeadsDirPath = refsPath + slash(HEADS); - myRefsTagsPath = refsPath + slash(TAGS); - myRefsRemotesDirPath = refsPath + slash(REMOTES); - myInfoDirPath = mainPath + slash(INFO); - myExcludePath = mainPath + slash(INFO_EXCLUDE); - myHooksDirPath = mainPath + slash(HOOKS); - - String worktreePath = myWorktreeDir.getPath(); - myHeadFilePath = worktreePath + slash(HEAD); - myIndexFilePath = worktreePath + slash(INDEX); - myMergeHeadPath = worktreePath + slash(MERGE_HEAD); - myCherryPickHeadPath = worktreePath + slash(CHERRY_PICK_HEAD); - myOrigHeadPath = worktreePath + slash(ORIG_HEAD); - myCommitMessagePath = worktreePath + slash(COMMIT_EDITMSG); - myMergeMessagePath = worktreePath + slash(MERGE_MSG); - myMergeSquashPath = worktreePath + slash(SQUASH_MSG); - myRebaseApplyPath = worktreePath + slash(REBASE_APPLY); - myRebaseMergePath = worktreePath + slash(REBASE_MERGE); - } - - @Nonnull - public static GitRepositoryFiles getInstance(@Nonnull VirtualFile gitDir) { - VirtualFile gitDirForWorktree = getMainGitDirForWorktree(gitDir); - VirtualFile mainDir = gitDirForWorktree == null ? gitDir : gitDirForWorktree; - return new GitRepositoryFiles(mainDir, gitDir); - } - - /** - * Checks if the given .git directory is actually a worktree's git directory, and returns the main .git directory if it is true. - * If it is not a worktree, returns null. - *

- * Worktree's ".git" file references {@code /.git/worktrees/} - */ - @Nullable - private static VirtualFile getMainGitDirForWorktree(@Nonnull VirtualFile gitDir) { - File gitDirFile = VirtualFileUtil.virtualToIoFile(gitDir); - File commonDir = new File(gitDirFile, "commondir"); - if (!commonDir.exists()) { - return null; - } - String pathToMain; - try { - pathToMain = Files.readString(commonDir.toPath()).trim(); - } - catch (IOException e) { - LOG.error("Couldn't load " + commonDir, e); - return null; - } - String mainDir = FileUtil.toCanonicalPath(gitDirFile.getPath() + File.separator + pathToMain, true); - LocalFileSystem lfs = LocalFileSystem.getInstance(); - VirtualFile mainDirVF = lfs.refreshAndFindFileByPath(mainDir); - if (mainDirVF != null) { - return mainDirVF; - } - return lfs.refreshAndFindFileByPath(pathToMain); // absolute path is also possible - } - - @Nonnull - private static String slash(@Nonnull String s) { - return "/" + s; - } - - /** - * Returns subdirectories of .git which we are interested in - they should be watched by VFS. - */ - @Nonnull - Collection getDirsToWatch() { - return Arrays.asList(myRefsHeadsDirPath, myRefsRemotesDirPath, myRefsTagsPath, myInfoDirPath, myHooksDirPath); - } - - @Nonnull - File getRefsHeadsFile() { - return file(myRefsHeadsDirPath); - } - - @Nonnull - File getRefsRemotesFile() { - return file(myRefsRemotesDirPath); - } - - @Nonnull - File getRefsTagsFile() { - return file(myRefsTagsPath); - } - - @Nonnull - File getPackedRefsPath() { - return file(myPackedRefsPath); - } - - @Nonnull - public File getHeadFile() { - return file(myHeadFilePath); - } - - @Nonnull - File getConfigFile() { - return file(myConfigFilePath); - } - - @Nonnull - public File getRebaseMergeDir() { - return file(myRebaseMergePath); - } - - @Nonnull - public File getRebaseApplyDir() { - return file(myRebaseApplyPath); - } - - @Nonnull - public File getMergeHeadFile() { - return file(myMergeHeadPath); - } - - @Nonnull - public File getCherryPickHead() { - return file(myCherryPickHeadPath); - } - - @Nonnull - public File getMergeMessageFile() { - return file(myMergeMessagePath); - } - - @Nonnull - public File getSquashMessageFile() { - return file(myMergeSquashPath); - } - - @Nonnull - public File getPreCommitHookFile() { - return file(myHooksDirPath + slash(PRE_COMMIT_HOOK)); - } - - @Nonnull - public File getPrePushHookFile() { - return file(myHooksDirPath + slash(PRE_PUSH_HOOK)); - } - - @Nonnull - private static File file(@Nonnull String filePath) { - return new File(FileUtil.toSystemDependentName(filePath)); - } - - /** - * {@code .git/config} - */ - public boolean isConfigFile(String filePath) { - return filePath.equals(myConfigFilePath); - } - - /** - * .git/index - */ - public boolean isIndexFile(String filePath) { - return filePath.equals(myIndexFilePath); - } - - /** - * .git/HEAD - */ - public boolean isHeadFile(String file) { - return file.equals(myHeadFilePath); - } - - /** - * .git/ORIG_HEAD - */ - public boolean isOrigHeadFile(@Nonnull String file) { - return file.equals(myOrigHeadPath); - } - - /** - * Any file in .git/refs/heads, i.e. a branch reference file. - */ - public boolean isBranchFile(String filePath) { - return filePath.startsWith(myRefsHeadsDirPath); - } - - /** - * Checks if the given filePath represents the ref file of the given branch. - * - * @param filePath the path to check, in system-independent format (e.g. with "/"). - * @param fullBranchName full name of a ref, e.g. {@code refs/heads/master}. - * @return true iff the filePath represents the .git/refs/heads... file for the given branch. - */ - public boolean isBranchFile(@Nonnull String filePath, @Nonnull String fullBranchName) { - return FileUtil.pathsEqual(filePath, myMainDir.getPath() + slash(fullBranchName)); - } - - /** - * Any file in .git/refs/remotes, i.e. a remote branch reference file. - */ - public boolean isRemoteBranchFile(String filePath) { - return filePath.startsWith(myRefsRemotesDirPath); - } - - /** - * .git/refs/tags/* - */ - public boolean isTagFile(@Nonnull String path) { - return path.startsWith(myRefsTagsPath); - } - - /** - * .git/rebase-merge or .git/rebase-apply - */ - public boolean isRebaseFile(String path) { - return path.equals(myRebaseApplyPath) || path.equals(myRebaseMergePath); - } - - /** - * .git/MERGE_HEAD - */ - public boolean isMergeFile(String file) { - return file.equals(myMergeHeadPath); - } - - /** - * .git/packed-refs - */ - public boolean isPackedRefs(String file) { - return file.equals(myPackedRefsPath); - } - - public boolean isCommitMessageFile(@Nonnull String file) { - return file.equals(myCommitMessagePath); - } - - /** - * {@code $GIT_DIR/info/exclude} - */ - public boolean isExclude(@Nonnull String path) { - return path.equals(myExcludePath); - } - - /** - * Refresh all .git repository files asynchronously and recursively. - * - * @see #refreshNonTrackedData() if you need the "main" data (branches, HEAD, etc.) to be updated synchronously. - */ - public void refresh() { - VirtualFileUtil.markDirtyAndRefresh(true, true, false, myMainDir, myWorktreeDir); - } - - /** - * Refresh that part of .git repository files, which is not covered by {@link GitRepository#update()}, e.g. the {@code refs/tags/} dir. - *

- * The call to this method should be probably be done together with a call to update(): thus all information will be updated, - * but some of it will be updated synchronously, the rest - asynchronously. - */ - public void refreshNonTrackedData() { - VirtualFile tagsDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(myRefsTagsPath); - VirtualFileUtil.markDirtyAndRefresh(true, true, false, tagsDir); - } - - @Nonnull - Collection getRootDirs() { - if (myMainDir == myWorktreeDir) { - return Set.of(myMainDir); - } - return Set.of(myMainDir, myWorktreeDir); - } + private static final Logger LOG = Logger.getInstance(GitRepositoryFiles.class); + + private static final String CHERRY_PICK_HEAD = "CHERRY_PICK_HEAD"; + private static final String COMMIT_EDITMSG = "COMMIT_EDITMSG"; + private static final String CONFIG = "config"; + private static final String HEAD = "HEAD"; + private static final String INDEX = "index"; + private static final String INFO = "info"; + private static final String INFO_EXCLUDE = INFO + "/exclude"; + private static final String MERGE_HEAD = "MERGE_HEAD"; + private static final String MERGE_MSG = "MERGE_MSG"; + private static final String ORIG_HEAD = "ORIG_HEAD"; + private static final String REBASE_APPLY = "rebase-apply"; + private static final String REBASE_MERGE = "rebase-merge"; + private static final String PACKED_REFS = "packed-refs"; + private static final String REFS = "refs"; + private static final String HEADS = "heads"; + private static final String TAGS = "tags"; + private static final String REMOTES = "remotes"; + private static final String SQUASH_MSG = "SQUASH_MSG"; + private static final String HOOKS = "hooks"; + private static final String PRE_COMMIT_HOOK = "pre-commit"; + private static final String PRE_PUSH_HOOK = "pre-push"; + + private final VirtualFile myMainDir; + private final VirtualFile myWorktreeDir; + + private final String myConfigFilePath; + private final String myHeadFilePath; + private final String myIndexFilePath; + private final String myMergeHeadPath; + private final String myCherryPickHeadPath; + private final String myOrigHeadPath; + private final String myRebaseApplyPath; + private final String myRebaseMergePath; + private final String myPackedRefsPath; + private final String myRefsHeadsDirPath; + private final String myRefsRemotesDirPath; + private final String myRefsTagsPath; + private final String myCommitMessagePath; + private final String myMergeMessagePath; + private final String myMergeSquashPath; + private final String myInfoDirPath; + private final String myExcludePath; + private final String myHooksDirPath; + + private GitRepositoryFiles(@Nonnull VirtualFile mainDir, @Nonnull VirtualFile worktreeDir) { + myMainDir = mainDir; + myWorktreeDir = worktreeDir; + + String mainPath = myMainDir.getPath(); + myConfigFilePath = mainPath + slash(CONFIG); + myPackedRefsPath = mainPath + slash(PACKED_REFS); + String refsPath = mainPath + slash(REFS); + myRefsHeadsDirPath = refsPath + slash(HEADS); + myRefsTagsPath = refsPath + slash(TAGS); + myRefsRemotesDirPath = refsPath + slash(REMOTES); + myInfoDirPath = mainPath + slash(INFO); + myExcludePath = mainPath + slash(INFO_EXCLUDE); + myHooksDirPath = mainPath + slash(HOOKS); + + String worktreePath = myWorktreeDir.getPath(); + myHeadFilePath = worktreePath + slash(HEAD); + myIndexFilePath = worktreePath + slash(INDEX); + myMergeHeadPath = worktreePath + slash(MERGE_HEAD); + myCherryPickHeadPath = worktreePath + slash(CHERRY_PICK_HEAD); + myOrigHeadPath = worktreePath + slash(ORIG_HEAD); + myCommitMessagePath = worktreePath + slash(COMMIT_EDITMSG); + myMergeMessagePath = worktreePath + slash(MERGE_MSG); + myMergeSquashPath = worktreePath + slash(SQUASH_MSG); + myRebaseApplyPath = worktreePath + slash(REBASE_APPLY); + myRebaseMergePath = worktreePath + slash(REBASE_MERGE); + } + + @Nonnull + public static GitRepositoryFiles getInstance(@Nonnull VirtualFile gitDir) { + VirtualFile gitDirForWorktree = getMainGitDirForWorktree(gitDir); + VirtualFile mainDir = gitDirForWorktree == null ? gitDir : gitDirForWorktree; + return new GitRepositoryFiles(mainDir, gitDir); + } + + /** + * Checks if the given .git directory is actually a worktree's git directory, and returns the main .git directory if it is true. + * If it is not a worktree, returns null. + *

+ * Worktree's ".git" file references {@code /.git/worktrees/} + */ + @Nullable + private static VirtualFile getMainGitDirForWorktree(@Nonnull VirtualFile gitDir) { + File gitDirFile = VirtualFileUtil.virtualToIoFile(gitDir); + File commonDir = new File(gitDirFile, "commondir"); + if (!commonDir.exists()) { + return null; + } + String pathToMain; + try { + pathToMain = Files.readString(commonDir.toPath()).trim(); + } + catch (IOException e) { + LOG.error("Couldn't load " + commonDir, e); + return null; + } + String mainDir = FileUtil.toCanonicalPath(gitDirFile.getPath() + File.separator + pathToMain, true); + LocalFileSystem lfs = LocalFileSystem.getInstance(); + VirtualFile mainDirVF = lfs.refreshAndFindFileByPath(mainDir); + if (mainDirVF != null) { + return mainDirVF; + } + return lfs.refreshAndFindFileByPath(pathToMain); // absolute path is also possible + } + + @Nonnull + private static String slash(@Nonnull String s) { + return "/" + s; + } + + /** + * Returns subdirectories of .git which we are interested in - they should be watched by VFS. + */ + @Nonnull + Collection getDirsToWatch() { + return Arrays.asList(myRefsHeadsDirPath, myRefsRemotesDirPath, myRefsTagsPath, myInfoDirPath, myHooksDirPath); + } + + @Nonnull + File getRefsHeadsFile() { + return file(myRefsHeadsDirPath); + } + + @Nonnull + File getRefsRemotesFile() { + return file(myRefsRemotesDirPath); + } + + @Nonnull + File getRefsTagsFile() { + return file(myRefsTagsPath); + } + + @Nonnull + File getPackedRefsPath() { + return file(myPackedRefsPath); + } + + @Nonnull + public File getHeadFile() { + return file(myHeadFilePath); + } + + @Nonnull + File getConfigFile() { + return file(myConfigFilePath); + } + + @Nonnull + public File getRebaseMergeDir() { + return file(myRebaseMergePath); + } + + @Nonnull + public File getRebaseApplyDir() { + return file(myRebaseApplyPath); + } + + @Nonnull + public File getMergeHeadFile() { + return file(myMergeHeadPath); + } + + @Nonnull + public File getCherryPickHead() { + return file(myCherryPickHeadPath); + } + + @Nonnull + public File getMergeMessageFile() { + return file(myMergeMessagePath); + } + + @Nonnull + public File getSquashMessageFile() { + return file(myMergeSquashPath); + } + + @Nonnull + public File getPreCommitHookFile() { + return file(myHooksDirPath + slash(PRE_COMMIT_HOOK)); + } + + @Nonnull + public File getPrePushHookFile() { + return file(myHooksDirPath + slash(PRE_PUSH_HOOK)); + } + + @Nonnull + private static File file(@Nonnull String filePath) { + return new File(FileUtil.toSystemDependentName(filePath)); + } + + /** + * {@code .git/config} + */ + public boolean isConfigFile(String filePath) { + return filePath.equals(myConfigFilePath); + } + + /** + * .git/index + */ + public boolean isIndexFile(String filePath) { + return filePath.equals(myIndexFilePath); + } + + /** + * .git/HEAD + */ + public boolean isHeadFile(String file) { + return file.equals(myHeadFilePath); + } + + /** + * .git/ORIG_HEAD + */ + public boolean isOrigHeadFile(@Nonnull String file) { + return file.equals(myOrigHeadPath); + } + + /** + * Any file in .git/refs/heads, i.e. a branch reference file. + */ + public boolean isBranchFile(String filePath) { + return filePath.startsWith(myRefsHeadsDirPath); + } + + /** + * Checks if the given filePath represents the ref file of the given branch. + * + * @param filePath the path to check, in system-independent format (e.g. with "/"). + * @param fullBranchName full name of a ref, e.g. {@code refs/heads/master}. + * @return true iff the filePath represents the .git/refs/heads... file for the given branch. + */ + public boolean isBranchFile(@Nonnull String filePath, @Nonnull String fullBranchName) { + return FileUtil.pathsEqual(filePath, myMainDir.getPath() + slash(fullBranchName)); + } + + /** + * Any file in .git/refs/remotes, i.e. a remote branch reference file. + */ + public boolean isRemoteBranchFile(String filePath) { + return filePath.startsWith(myRefsRemotesDirPath); + } + + /** + * .git/refs/tags/* + */ + public boolean isTagFile(@Nonnull String path) { + return path.startsWith(myRefsTagsPath); + } + + /** + * .git/rebase-merge or .git/rebase-apply + */ + public boolean isRebaseFile(String path) { + return path.equals(myRebaseApplyPath) || path.equals(myRebaseMergePath); + } + + /** + * .git/MERGE_HEAD + */ + public boolean isMergeFile(String file) { + return file.equals(myMergeHeadPath); + } + + /** + * .git/packed-refs + */ + public boolean isPackedRefs(String file) { + return file.equals(myPackedRefsPath); + } + + public boolean isCommitMessageFile(@Nonnull String file) { + return file.equals(myCommitMessagePath); + } + + /** + * {@code $GIT_DIR/info/exclude} + */ + public boolean isExclude(@Nonnull String path) { + return path.equals(myExcludePath); + } + + /** + * Refresh all .git repository files asynchronously and recursively. + * + * @see #refreshNonTrackedData() if you need the "main" data (branches, HEAD, etc.) to be updated synchronously. + */ + public void refresh() { + VirtualFileUtil.markDirtyAndRefresh(true, true, false, myMainDir, myWorktreeDir); + } + + /** + * Refresh that part of .git repository files, which is not covered by {@link GitRepository#update()}, e.g. the {@code refs/tags/} dir. + *

+ * The call to this method should be probably be done together with a call to update(): thus all information will be updated, + * but some of it will be updated synchronously, the rest - asynchronously. + */ + public void refreshNonTrackedData() { + VirtualFile tagsDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(myRefsTagsPath); + VirtualFileUtil.markDirtyAndRefresh(true, true, false, tagsDir); + } + + @Nonnull + Collection getRootDirs() { + if (myMainDir == myWorktreeDir) { + return Set.of(myMainDir); + } + return Set.of(myMainDir, myWorktreeDir); + } } diff --git a/plugin/src/main/java/git4idea/repo/GitRepositoryImpl.java b/plugin/src/main/java/git4idea/repo/GitRepositoryImpl.java index 2e112fa..c5d1d58 100644 --- a/plugin/src/main/java/git4idea/repo/GitRepositoryImpl.java +++ b/plugin/src/main/java/git4idea/repo/GitRepositoryImpl.java @@ -15,7 +15,6 @@ */ package git4idea.repo; -import consulo.application.ApplicationManager; import consulo.disposer.Disposable; import consulo.disposer.Disposer; import consulo.project.Project; @@ -37,224 +36,230 @@ import static consulo.versionControlSystem.distributed.DvcsUtil.getShortRepositoryName; public class GitRepositoryImpl extends RepositoryImpl implements GitRepository { - @Nonnull - private final GitVcs myVcs; - @Nonnull - private final GitRepositoryReader myReader; - @Nonnull - private final VirtualFile myGitDir; - @Nonnull - private final GitRepositoryFiles myRepositoryFiles; - - @Nullable - private final GitUntrackedFilesHolder myUntrackedFilesHolder; - - @Nonnull - private volatile GitRepoInfo myInfo; - - private GitRepositoryImpl(@Nonnull VirtualFile rootDir, - @Nonnull VirtualFile gitDir, - @Nonnull Project project, - @Nonnull Disposable parentDisposable, - final boolean light) { - super(project, rootDir, parentDisposable); - myVcs = assertNotNull(GitVcs.getInstance(project)); - myGitDir = gitDir; - myRepositoryFiles = GitRepositoryFiles.getInstance(gitDir); - myReader = new GitRepositoryReader(myRepositoryFiles); - myInfo = readRepoInfo(); - if (!light) { - myUntrackedFilesHolder = new GitUntrackedFilesHolder(this, myRepositoryFiles); - Disposer.register(this, myUntrackedFilesHolder); + @Nonnull + private final GitVcs myVcs; + @Nonnull + private final GitRepositoryReader myReader; + @Nonnull + private final VirtualFile myGitDir; + @Nonnull + private final GitRepositoryFiles myRepositoryFiles; + + @Nullable + private final GitUntrackedFilesHolder myUntrackedFilesHolder; + + @Nonnull + private volatile GitRepoInfo myInfo; + + private GitRepositoryImpl( + @Nonnull VirtualFile rootDir, + @Nonnull VirtualFile gitDir, + @Nonnull Project project, + @Nonnull Disposable parentDisposable, + boolean light + ) { + super(project, rootDir, parentDisposable); + myVcs = assertNotNull(GitVcs.getInstance(project)); + myGitDir = gitDir; + myRepositoryFiles = GitRepositoryFiles.getInstance(gitDir); + myReader = new GitRepositoryReader(myRepositoryFiles); + myInfo = readRepoInfo(); + if (!light) { + myUntrackedFilesHolder = new GitUntrackedFilesHolder(this, myRepositoryFiles); + Disposer.register(this, myUntrackedFilesHolder); + } + else { + myUntrackedFilesHolder = null; + } + } + + @Nonnull + public static GitRepository getInstance(@Nonnull VirtualFile root, @Nonnull Project project, boolean listenToRepoChanges) { + return getInstance(root, assertNotNull(GitUtil.findGitDir(root)), project, listenToRepoChanges); + } + + @Nonnull + public static GitRepository getInstance( + @Nonnull VirtualFile root, + @Nonnull VirtualFile gitDir, + @Nonnull Project project, + boolean listenToRepoChanges + ) { + GitRepositoryImpl repository = new GitRepositoryImpl(root, gitDir, project, project, !listenToRepoChanges); + if (listenToRepoChanges) { + repository.getUntrackedFilesHolder().setupVfsListener(project); + repository.setupUpdater(); + notifyListenersAsync(repository); + } + return repository; + } + + private void setupUpdater() { + GitRepositoryUpdater updater = new GitRepositoryUpdater(this, myRepositoryFiles); + Disposer.register(this, updater); + } + + @Deprecated + @Nonnull + @Override + public VirtualFile getGitDir() { + return myGitDir; + } + + @Nonnull + @Override + public GitRepositoryFiles getRepositoryFiles() { + return myRepositoryFiles; + } + + @Nonnull + @Override + public GitUntrackedFilesHolder getUntrackedFilesHolder() { + if (myUntrackedFilesHolder == null) { + throw new IllegalStateException("Using untracked files holder with light git repository instance " + this); + } + return myUntrackedFilesHolder; + } + + @Nonnull + @Override + public GitRepoInfo getInfo() { + return myInfo; + } + + @Nullable + @Override + public GitLocalBranch getCurrentBranch() { + return myInfo.currentBranch(); + } + + @Nullable + @Override + public String getCurrentRevision() { + return myInfo.currentRevision(); + } + + @Nonnull + @Override + public State getState() { + return myInfo.state(); + } + + @Nullable + @Override + public String getCurrentBranchName() { + GitLocalBranch currentBranch = getCurrentBranch(); + return currentBranch == null ? null : currentBranch.getName(); } - else { - myUntrackedFilesHolder = null; + + @Nonnull + @Override + public GitVcs getVcs() { + return myVcs; } - } - - @Nonnull - public static GitRepository getInstance(@Nonnull VirtualFile root, @Nonnull Project project, boolean listenToRepoChanges) { - return getInstance(root, assertNotNull(GitUtil.findGitDir(root)), project, listenToRepoChanges); - } - - @Nonnull - public static GitRepository getInstance(@Nonnull VirtualFile root, - @Nonnull VirtualFile gitDir, - @Nonnull Project project, - boolean listenToRepoChanges) { - GitRepositoryImpl repository = new GitRepositoryImpl(root, gitDir, project, project, !listenToRepoChanges); - if (listenToRepoChanges) { - repository.getUntrackedFilesHolder().setupVfsListener(project); - repository.setupUpdater(); - notifyListenersAsync(repository); + + @Nonnull + @Override + public Collection getSubmodules() { + return myInfo.submodules(); } - return repository; - } - - private void setupUpdater() { - GitRepositoryUpdater updater = new GitRepositoryUpdater(this, myRepositoryFiles); - Disposer.register(this, updater); - } - - @Deprecated - @Nonnull - @Override - public VirtualFile getGitDir() { - return myGitDir; - } - - @Nonnull - @Override - public GitRepositoryFiles getRepositoryFiles() { - return myRepositoryFiles; - } - - @Override - @Nonnull - public GitUntrackedFilesHolder getUntrackedFilesHolder() { - if (myUntrackedFilesHolder == null) { - throw new IllegalStateException("Using untracked files holder with light git repository instance " + this); + + /** + * @return local and remote branches in this repository. + */ + @Nonnull + @Override + public GitBranchesCollection getBranches() { + GitRepoInfo info = myInfo; + return new GitBranchesCollection(info.localBranches(), info.remoteBranches()); } - return myUntrackedFilesHolder; - } - - @Override - @Nonnull - public GitRepoInfo getInfo() { - return myInfo; - } - - @Override - @Nullable - public GitLocalBranch getCurrentBranch() { - return myInfo.getCurrentBranch(); - } - - @Nullable - @Override - public String getCurrentRevision() { - return myInfo.getCurrentRevision(); - } - - @Nonnull - @Override - public State getState() { - return myInfo.getState(); - } - - @Nullable - @Override - public String getCurrentBranchName() { - GitLocalBranch currentBranch = getCurrentBranch(); - return currentBranch == null ? null : currentBranch.getName(); - } - - @Nonnull - @Override - public GitVcs getVcs() { - return myVcs; - } - - @Nonnull - @Override - public Collection getSubmodules() { - return myInfo.getSubmodules(); - } - - /** - * @return local and remote branches in this repository. - */ - @Override - @Nonnull - public GitBranchesCollection getBranches() { - GitRepoInfo info = myInfo; - return new GitBranchesCollection(info.getLocalBranchesWithHashes(), info.getRemoteBranchesWithHashes()); - } - - @Override - @Nonnull - public Collection getRemotes() { - return myInfo.getRemotes(); - } - - @Override - @Nonnull - public Collection getBranchTrackInfos() { - return myInfo.getBranchTrackInfos(); - } - - @Override - public boolean isRebaseInProgress() { - return getState() == State.REBASING; - } - - @Override - public boolean isOnBranch() { - return getState() != State.DETACHED && getState() != State.REBASING; - } - - @Override - public boolean isFresh() { - return getCurrentRevision() == null; - } - - @Override - public void update() { - GitRepoInfo previousInfo = myInfo; - myInfo = readRepoInfo(); - notifyIfRepoChanged(this, previousInfo, myInfo); - } - - @Nonnull - private GitRepoInfo readRepoInfo() { - StopWatch sw = StopWatch.start("Reading Git repo info in " + getShortRepositoryName(this)); - File configFile = myRepositoryFiles.getConfigFile(); - GitConfig config = GitConfig.read(configFile); - Collection remotes = config.parseRemotes(); - GitBranchState state = myReader.readState(remotes); - Collection trackInfos = - config.parseTrackInfos(state.getLocalBranches().keySet(), state.getRemoteBranches().keySet()); - GitHooksInfo hooksInfo = myReader.readHooksInfo(); - Collection submodules = new GitModulesFileReader().read(getSubmoduleFile()); - sw.report(); - return new GitRepoInfo(state.getCurrentBranch(), - state.getCurrentRevision(), - state.getState(), - remotes, - state.getLocalBranches(), - state.getRemoteBranches(), - trackInfos, - submodules, - hooksInfo); - } - - @Nonnull - private File getSubmoduleFile() { - return new File(VirtualFileUtil.virtualToIoFile(getRoot()), ".gitmodules"); - } - - private static void notifyIfRepoChanged(@Nonnull final GitRepository repository, - @Nonnull GitRepoInfo previousInfo, - @Nonnull GitRepoInfo info) { - if (!repository.getProject().isDisposed() && !info.equals(previousInfo)) { - notifyListenersAsync(repository); + + @Override + @Nonnull + public Collection getRemotes() { + return myInfo.remotes(); + } + + @Nonnull + @Override + public Collection getBranchTrackInfos() { + return myInfo.branchTrackInfos(); + } + + @Override + public boolean isRebaseInProgress() { + return getState() == State.REBASING; + } + + @Override + public boolean isOnBranch() { + return getState() != State.DETACHED && getState() != State.REBASING; + } + + @Override + public boolean isFresh() { + return getCurrentRevision() == null; } - } - - private static void notifyListenersAsync(@Nonnull final GitRepository repository) { - ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { - public void run() { - Project project = repository.getProject(); - if (!project.isDisposed()) { - project.getMessageBus().syncPublisher(GitRepositoryChangeListener.class).repositoryChanged(repository); + + @Override + public void update() { + GitRepoInfo previousInfo = myInfo; + myInfo = readRepoInfo(); + notifyIfRepoChanged(this, previousInfo, myInfo); + } + + @Nonnull + private GitRepoInfo readRepoInfo() { + StopWatch sw = StopWatch.start("Reading Git repo info in " + getShortRepositoryName(this)); + File configFile = myRepositoryFiles.getConfigFile(); + GitConfig config = GitConfig.read(configFile); + Collection remotes = config.parseRemotes(); + GitBranchState state = myReader.readState(remotes); + Collection trackInfos = + config.parseTrackInfos(state.getLocalBranches().keySet(), state.getRemoteBranches().keySet()); + GitHooksInfo hooksInfo = myReader.readHooksInfo(); + Collection submodules = new GitModulesFileReader().read(getSubmoduleFile()); + sw.report(); + return new GitRepoInfo( + state.getCurrentBranch(), + state.getCurrentRevision(), + state.getState(), + remotes, + state.getLocalBranches(), + state.getRemoteBranches(), + trackInfos, + submodules, + hooksInfo + ); + } + + @Nonnull + private File getSubmoduleFile() { + return new File(VirtualFileUtil.virtualToIoFile(getRoot()), ".gitmodules"); + } + + private static void notifyIfRepoChanged( + @Nonnull GitRepository repository, + @Nonnull GitRepoInfo previousInfo, + @Nonnull GitRepoInfo info + ) { + if (!repository.getProject().isDisposed() && !info.equals(previousInfo)) { + notifyListenersAsync(repository); } - } - }); - } - - @Nonnull - @Override - public String toLogString() { - return "GitRepository " + getRoot() + " : " + myInfo; - } + } + + private static void notifyListenersAsync(@Nonnull GitRepository repository) { + repository.getProject().getApplication().executeOnPooledThread((Runnable) () -> { + Project project = repository.getProject(); + if (!project.isDisposed()) { + project.getMessageBus().syncPublisher(GitRepositoryChangeListener.class).repositoryChanged(repository); + } + }); + } + + @Nonnull + @Override + public String toLogString() { + return "GitRepository " + getRoot() + " : " + myInfo; + } } diff --git a/plugin/src/main/java/git4idea/repo/GitRepositoryManager.java b/plugin/src/main/java/git4idea/repo/GitRepositoryManager.java index e52467a..2a11dd4 100644 --- a/plugin/src/main/java/git4idea/repo/GitRepositoryManager.java +++ b/plugin/src/main/java/git4idea/repo/GitRepositoryManager.java @@ -42,7 +42,7 @@ public class GitRepositoryManager extends AbstractRepositoryManager { @Nullable public static GitRepositoryManager getInstance(@Nonnull Project project) { - return (GitRepositoryManager)RepositoryManager.getInstance(project, GitVcs.getKey()); + return (GitRepositoryManager) RepositoryManager.getInstance(project, GitVcs.getKey()); } private static final Logger LOG = Logger.getInstance(GitRepositoryManager.class); @@ -90,9 +90,9 @@ public Collection getDirectSubmodules(@Nonnull GitRepository supe Collection modules = superProject.getSubmodules(); return ContainerUtil.mapNotNull(modules, module -> { - VirtualFile submoduleDir = superProject.getRoot().findFileByRelativePath(module.getPath()); + VirtualFile submoduleDir = superProject.getRoot().findFileByRelativePath(module.path()); if (submoduleDir == null) { - LOG.debug("submodule dir not found at declared path [" + module.getPath() + "] of root [" + superProject.getRoot() + "]"); + LOG.debug("submodule dir not found at declared path [" + module.path() + "] of root [" + superProject.getRoot() + "]"); return null; } GitRepository repository = getRepositoryForRoot(submoduleDir); diff --git a/plugin/src/main/java/git4idea/repo/GitRepositoryReader.java b/plugin/src/main/java/git4idea/repo/GitRepositoryReader.java index f8d0380..8d6aa75 100644 --- a/plugin/src/main/java/git4idea/repo/GitRepositoryReader.java +++ b/plugin/src/main/java/git4idea/repo/GitRepositoryReader.java @@ -28,10 +28,10 @@ import git4idea.*; import git4idea.branch.GitBranchUtil; import git4idea.validators.GitRefNameValidator; -import org.jetbrains.annotations.NonNls; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import org.jetbrains.annotations.NonNls; + import java.io.File; import java.nio.charset.StandardCharsets; import java.util.*; @@ -49,449 +49,456 @@ */ public class GitRepositoryReader { - private static final Logger LOG = Logger.getInstance(GitRepositoryReader.class); - - private static Pattern BRANCH_PATTERN = Pattern.compile(" *(?:ref:)? */?((?:refs/heads/|refs/remotes/)?\\S+)"); - - @NonNls - private static final String REFS_HEADS_PREFIX = "refs/heads/"; - @NonNls - private static final String REFS_REMOTES_PREFIX = "refs/remotes/"; - - @Nonnull - private final File myHeadFile; // .git/HEAD - @Nonnull - private final File myRefsHeadsDir; // .git/refs/heads/ - @Nonnull - private final File myRefsRemotesDir; // .git/refs/remotes/ - @Nonnull - private final File myPackedRefsFile; // .git/packed-refs - @Nonnull - private final GitRepositoryFiles myGitFiles; - - GitRepositoryReader(@Nonnull GitRepositoryFiles gitFiles) { - myGitFiles = gitFiles; - myHeadFile = gitFiles.getHeadFile(); - DvcsUtil.assertFileExists(myHeadFile, ".git/HEAD file not found at " + myHeadFile); - myRefsHeadsDir = gitFiles.getRefsHeadsFile(); - myRefsRemotesDir = gitFiles.getRefsRemotesFile(); - myPackedRefsFile = gitFiles.getPackedRefsPath(); - } - - @Nonnull - GitBranchState readState(@Nonnull Collection remotes) { - Pair, Map> branches = readBranches(remotes); - Map localBranches = branches.first; - - HeadInfo headInfo = readHead(); - Repository.State state = readRepositoryState(headInfo); - - GitLocalBranch currentBranch; - String currentRevision; - if (!headInfo.isBranch || !localBranches.isEmpty()) { - currentBranch = findCurrentBranch(headInfo, state, localBranches.keySet()); - currentRevision = getCurrentRevision(headInfo, currentBranch == null ? null : localBranches.get(currentBranch)); + private static final Logger LOG = Logger.getInstance(GitRepositoryReader.class); + + private static Pattern BRANCH_PATTERN = Pattern.compile(" *(?:ref:)? */?((?:refs/heads/|refs/remotes/)?\\S+)"); + + @NonNls + private static final String REFS_HEADS_PREFIX = "refs/heads/"; + @NonNls + private static final String REFS_REMOTES_PREFIX = "refs/remotes/"; + + @Nonnull + private final File myHeadFile; // .git/HEAD + @Nonnull + private final File myRefsHeadsDir; // .git/refs/heads/ + @Nonnull + private final File myRefsRemotesDir; // .git/refs/remotes/ + @Nonnull + private final File myPackedRefsFile; // .git/packed-refs + @Nonnull + private final GitRepositoryFiles myGitFiles; + + GitRepositoryReader(@Nonnull GitRepositoryFiles gitFiles) { + myGitFiles = gitFiles; + myHeadFile = gitFiles.getHeadFile(); + DvcsUtil.assertFileExists(myHeadFile, ".git/HEAD file not found at " + myHeadFile); + myRefsHeadsDir = gitFiles.getRefsHeadsFile(); + myRefsRemotesDir = gitFiles.getRefsRemotesFile(); + myPackedRefsFile = gitFiles.getPackedRefsPath(); + } + + @Nonnull + GitBranchState readState(@Nonnull Collection remotes) { + Pair, Map> branches = readBranches(remotes); + Map localBranches = branches.first; + + HeadInfo headInfo = readHead(); + Repository.State state = readRepositoryState(headInfo); + + GitLocalBranch currentBranch; + String currentRevision; + if (!headInfo.isBranch || !localBranches.isEmpty()) { + currentBranch = findCurrentBranch(headInfo, state, localBranches.keySet()); + currentRevision = getCurrentRevision(headInfo, currentBranch == null ? null : localBranches.get(currentBranch)); + } + else if (headInfo.content != null) { + currentBranch = new GitLocalBranch(headInfo.content); + currentRevision = null; + } + else { + currentBranch = null; + currentRevision = null; + } + if (currentBranch == null && currentRevision == null) { + LOG.error("Couldn't identify neither current branch nor current revision. .git/HEAD content: [" + headInfo.content + "]"); + } + return new GitBranchState(currentRevision, currentBranch, state, localBranches, branches.second); } - else if (headInfo.content != null) { - currentBranch = new GitLocalBranch(headInfo.content); - currentRevision = null; + + @Nonnull + GitHooksInfo readHooksInfo() { + return new GitHooksInfo( + isExistingExecutableFile(myGitFiles.getPreCommitHookFile()), + isExistingExecutableFile(myGitFiles.getPrePushHookFile()) + ); } - else { - currentBranch = null; - currentRevision = null; + + private static boolean isExistingExecutableFile(@Nonnull File file) { + return file.exists() && file.canExecute(); } - if (currentBranch == null && currentRevision == null) { - LOG.error("Couldn't identify neither current branch nor current revision. .git/HEAD content: [" + headInfo.content + "]"); + + @Nullable + private static String getCurrentRevision(@Nonnull HeadInfo headInfo, @Nullable Hash currentBranchHash) { + String currentRevision; + if (!headInfo.isBranch) { + currentRevision = headInfo.content; + } + else if (currentBranchHash == null) { + currentRevision = null; + } + else { + currentRevision = currentBranchHash.asString(); + } + return currentRevision; } - return new GitBranchState(currentRevision, currentBranch, state, localBranches, branches.second); - } - - @Nonnull - GitHooksInfo readHooksInfo() { - return new GitHooksInfo(isExistingExecutableFile(myGitFiles.getPreCommitHookFile()), - isExistingExecutableFile(myGitFiles.getPrePushHookFile())); - } - - private static boolean isExistingExecutableFile(@Nonnull File file) { - return file.exists() && file.canExecute(); - } - - @Nullable - private static String getCurrentRevision(@Nonnull HeadInfo headInfo, @Nullable Hash currentBranchHash) { - String currentRevision; - if (!headInfo.isBranch) { - currentRevision = headInfo.content; + + @Nullable + private GitLocalBranch findCurrentBranch( + @Nonnull HeadInfo headInfo, + @Nonnull Repository.State state, + @Nonnull Set localBranches + ) { + final String currentBranchName = findCurrentBranchName(state, headInfo); + if (currentBranchName == null) { + return null; + } + return ContainerUtil.find(localBranches, branch -> BRANCH_NAME_HASHING_STRATEGY.equals(branch.getFullName(), currentBranchName)); } - else if (currentBranchHash == null) { - currentRevision = null; + + @Nonnull + private Repository.State readRepositoryState(@Nonnull HeadInfo headInfo) { + if (isMergeInProgress()) { + return Repository.State.MERGING; + } + if (isRebaseInProgress()) { + return Repository.State.REBASING; + } + if (!headInfo.isBranch) { + return Repository.State.DETACHED; + } + return Repository.State.NORMAL; } - else { - currentRevision = currentBranchHash.asString(); + + @Nullable + private String findCurrentBranchName(@Nonnull Repository.State state, @Nonnull HeadInfo headInfo) { + String currentBranch = null; + if (headInfo.isBranch) { + currentBranch = headInfo.content; + } + else if (state == Repository.State.REBASING) { + currentBranch = readRebaseDirBranchFile(myGitFiles.getRebaseApplyDir()); + if (currentBranch == null) { + currentBranch = readRebaseDirBranchFile(myGitFiles.getRebaseMergeDir()); + } + } + return addRefsHeadsPrefixIfNeeded(currentBranch); } - return currentRevision; - } - - @Nullable - private GitLocalBranch findCurrentBranch(@Nonnull HeadInfo headInfo, - @Nonnull Repository.State state, - @Nonnull Set localBranches) { - final String currentBranchName = findCurrentBranchName(state, headInfo); - if (currentBranchName == null) { - return null; + + @Nullable + private static String readRebaseDirBranchFile(@NonNls File rebaseDir) { + if (rebaseDir.exists()) { + File headName = new File(rebaseDir, "head-name"); + if (headName.exists()) { + return DvcsUtil.tryLoadFileOrReturn(headName, null, StandardCharsets.UTF_8); + } + } + return null; } - return ContainerUtil.find(localBranches, branch -> BRANCH_NAME_HASHING_STRATEGY.equals(branch.getFullName(), currentBranchName)); - } - @Nonnull - private Repository.State readRepositoryState(@Nonnull HeadInfo headInfo) { - if (isMergeInProgress()) { - return Repository.State.MERGING; + @Nullable + private static String addRefsHeadsPrefixIfNeeded(@Nullable String branchName) { + if (branchName != null && !branchName.startsWith(REFS_HEADS_PREFIX)) { + return REFS_HEADS_PREFIX + branchName; + } + return branchName; } - if (isRebaseInProgress()) { - return Repository.State.REBASING; + + private boolean isMergeInProgress() { + return myGitFiles.getMergeHeadFile().exists(); } - if (!headInfo.isBranch) { - return Repository.State.DETACHED; + + private boolean isRebaseInProgress() { + return myGitFiles.getRebaseApplyDir().exists() || myGitFiles.getRebaseMergeDir().exists(); } - return Repository.State.NORMAL; - } - - @Nullable - private String findCurrentBranchName(@Nonnull Repository.State state, @Nonnull HeadInfo headInfo) { - String currentBranch = null; - if (headInfo.isBranch) { - currentBranch = headInfo.content; + + @Nonnull + private Map readPackedBranches() { + if (!myPackedRefsFile.exists()) { + return Collections.emptyMap(); + } + try { + String content = DvcsUtil.tryLoadFile(myPackedRefsFile, StandardCharsets.UTF_8); + return Arrays.stream(LineTokenizer.tokenize(content, false)) + .map(it -> GitRepositoryReader.parsePackedRefsLine(it)) + .filter(Objects::nonNull) + .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); + } + catch (RepoStateException e) { + return Collections.emptyMap(); + } } - else if (state == Repository.State.REBASING) { - currentBranch = readRebaseDirBranchFile(myGitFiles.getRebaseApplyDir()); - if (currentBranch == null) { - currentBranch = readRebaseDirBranchFile(myGitFiles.getRebaseMergeDir()); - } + + @Nonnull + private Pair, Map> readBranches(@Nonnull Collection remotes) { + Map data = readBranchRefsFromFiles(); + Map resolvedRefs = resolveRefs(data); + return createBranchesFromData(remotes, resolvedRefs); + } + + @Nonnull + private Map readBranchRefsFromFiles() { + Map result = + new HashMap<>(readPackedBranches()); // reading from packed-refs first to overwrite values by values from unpacked refs + result.putAll(readFromBranchFiles(myRefsHeadsDir, REFS_HEADS_PREFIX)); + result.putAll(readFromBranchFiles(myRefsRemotesDir, REFS_REMOTES_PREFIX)); + result.remove(REFS_REMOTES_PREFIX + GitUtil.ORIGIN_HEAD); + return result; + } + + @Nonnull + private static Pair, Map> createBranchesFromData( + @Nonnull Collection remotes, + @Nonnull Map data + ) { + Map localBranches = new HashMap<>(); + Map remoteBranches = new HashMap<>(); + for (Map.Entry entry : data.entrySet()) { + String refName = entry.getKey(); + Hash hash = entry.getValue(); + if (refName.startsWith(REFS_HEADS_PREFIX)) { + localBranches.put(new GitLocalBranch(refName), hash); + } + else if (refName.startsWith(REFS_REMOTES_PREFIX)) { + remoteBranches.put(parseRemoteBranch(refName, remotes), hash); + } + else { + LOG.warn("Unexpected ref format: " + refName); + } + } + return Pair.create(localBranches, remoteBranches); } - return addRefsHeadsPrefixIfNeeded(currentBranch); - } - - @Nullable - private static String readRebaseDirBranchFile(@NonNls File rebaseDir) { - if (rebaseDir.exists()) { - File headName = new File(rebaseDir, "head-name"); - if (headName.exists()) { - return DvcsUtil.tryLoadFileOrReturn(headName, null, StandardCharsets.UTF_8); - } + + @Nullable + private static String loadHashFromBranchFile(@Nonnull File branchFile) { + return DvcsUtil.tryLoadFileOrReturn(branchFile, null); } - return null; - } - @Nullable - private static String addRefsHeadsPrefixIfNeeded(@Nullable String branchName) { - if (branchName != null && !branchName.startsWith(REFS_HEADS_PREFIX)) { - return REFS_HEADS_PREFIX + branchName; + @Nonnull + private static Map readFromBranchFiles(@Nonnull final File refsRootDir, @Nonnull final String prefix) { + if (!refsRootDir.exists()) { + return Collections.emptyMap(); + } + final Map result = new HashMap<>(); + FileUtil.processFilesRecursively(refsRootDir, file -> + { + if (!file.isDirectory() && !isHidden(file)) { + String relativePath = FileUtil.getRelativePath(refsRootDir, file); + if (relativePath != null) { + String branchName = prefix + FileUtil.toSystemIndependentName(relativePath); + boolean isBranchNameValid = GitRefNameValidator.getInstance().checkInput(branchName); + if (isBranchNameValid) { + String hash = loadHashFromBranchFile(file); + if (hash != null) { + result.put(branchName, hash); + } + } + } + } + return true; + }, dir -> !isHidden(dir)); + return result; } - return branchName; - } - private boolean isMergeInProgress() { - return myGitFiles.getMergeHeadFile().exists(); - } + private static boolean isHidden(@Nonnull File file) { + return file.getName().startsWith("."); + } - private boolean isRebaseInProgress() { - return myGitFiles.getRebaseApplyDir().exists() || myGitFiles.getRebaseMergeDir().exists(); - } + @Nonnull + private static GitRemoteBranch parseRemoteBranch(@Nonnull String fullBranchName, @Nonnull Collection remotes) { + String stdName = GitBranchUtil.stripRefsPrefix(fullBranchName); - @Nonnull - private Map readPackedBranches() { - if (!myPackedRefsFile.exists()) { - return Collections.emptyMap(); - } - try { - String content = DvcsUtil.tryLoadFile(myPackedRefsFile, StandardCharsets.UTF_8); - return Arrays.stream(LineTokenizer.tokenize(content, false)) - .map(it -> GitRepositoryReader.parsePackedRefsLine(it)) - .filter(Objects::nonNull) - .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); - } - catch (RepoStateException e) { - return Collections.emptyMap(); - } - } - - @Nonnull - private Pair, Map> readBranches(@Nonnull Collection remotes) { - Map data = readBranchRefsFromFiles(); - Map resolvedRefs = resolveRefs(data); - return createBranchesFromData(remotes, resolvedRefs); - } - - @Nonnull - private Map readBranchRefsFromFiles() { - Map result = - new HashMap<>(readPackedBranches()); // reading from packed-refs first to overwrite values by values from unpacked refs - result.putAll(readFromBranchFiles(myRefsHeadsDir, REFS_HEADS_PREFIX)); - result.putAll(readFromBranchFiles(myRefsRemotesDir, REFS_REMOTES_PREFIX)); - result.remove(REFS_REMOTES_PREFIX + GitUtil.ORIGIN_HEAD); - return result; - } - - @Nonnull - private static Pair, Map> createBranchesFromData(@Nonnull Collection remotes, - @Nonnull Map data) { - Map localBranches = new HashMap<>(); - Map remoteBranches = new HashMap<>(); - for (Map.Entry entry : data.entrySet()) { - String refName = entry.getKey(); - Hash hash = entry.getValue(); - if (refName.startsWith(REFS_HEADS_PREFIX)) { - localBranches.put(new GitLocalBranch(refName), hash); - } - else if (refName.startsWith(REFS_REMOTES_PREFIX)) { - remoteBranches.put(parseRemoteBranch(refName, remotes), hash); - } - else { - LOG.warn("Unexpected ref format: " + refName); - } - } - return Pair.create(localBranches, remoteBranches); - } - - @Nullable - private static String loadHashFromBranchFile(@Nonnull File branchFile) { - return DvcsUtil.tryLoadFileOrReturn(branchFile, null); - } - - @Nonnull - private static Map readFromBranchFiles(@Nonnull final File refsRootDir, @Nonnull final String prefix) { - if (!refsRootDir.exists()) { - return Collections.emptyMap(); - } - final Map result = new HashMap<>(); - FileUtil.processFilesRecursively(refsRootDir, file -> - { - if (!file.isDirectory() && !isHidden(file)) { - String relativePath = FileUtil.getRelativePath(refsRootDir, file); - if (relativePath != null) { - String branchName = prefix + FileUtil.toSystemIndependentName(relativePath); - boolean isBranchNameValid = GitRefNameValidator.getInstance().checkInput(branchName); - if (isBranchNameValid) { - String hash = loadHashFromBranchFile(file); - if (hash != null) { - result.put(branchName, hash); - } - } + int slash = stdName.indexOf('/'); + if (slash == -1) { // .git/refs/remotes/my_branch => git-svn + return new GitSvnRemoteBranch(fullBranchName); } - } - return true; - }, dir -> !isHidden(dir)); - return result; - } + else { + GitRemote remote; + String remoteName; + String branchName; + do { + remoteName = stdName.substring(0, slash); + branchName = stdName.substring(slash + 1); + remote = GitUtil.findRemoteByName(remotes, remoteName); + slash = stdName.indexOf('/', slash + 1); + } + while (remote == null && slash >= 0); - private static boolean isHidden(@Nonnull File file) { - return file.getName().startsWith("."); - } + if (remote == null) { + // user may remove the remote section from .git/config, but leave remote refs untouched in .git/refs/remotes + LOG.debug(String.format("No remote found with the name [%s]. All remotes: %s", remoteName, remotes)); + GitRemote fakeRemote = new GitRemote(remoteName, emptyList(), emptyList(), emptyList(), emptyList(), emptyList()); + return new GitStandardRemoteBranch(fakeRemote, branchName); + } + return new GitStandardRemoteBranch(remote, branchName); + } + } - @Nonnull - private static GitRemoteBranch parseRemoteBranch(@Nonnull String fullBranchName, @Nonnull Collection remotes) { - String stdName = GitBranchUtil.stripRefsPrefix(fullBranchName); + @Nonnull + private HeadInfo readHead() { + String headContent; + try { + headContent = DvcsUtil.tryLoadFile(myHeadFile, StandardCharsets.UTF_8); + } + catch (RepoStateException e) { + LOG.error(e); + return new HeadInfo(false, null); + } - int slash = stdName.indexOf('/'); - if (slash == -1) { // .git/refs/remotes/my_branch => git-svn - return new GitSvnRemoteBranch(fullBranchName); - } - else { - GitRemote remote; - String remoteName; - String branchName; - do { - remoteName = stdName.substring(0, slash); - branchName = stdName.substring(slash + 1); - remote = GitUtil.findRemoteByName(remotes, remoteName); - slash = stdName.indexOf('/', slash + 1); - } - while (remote == null && slash >= 0); - - if (remote == null) { - // user may remove the remote section from .git/config, but leave remote refs untouched in .git/refs/remotes - LOG.debug(String.format("No remote found with the name [%s]. All remotes: %s", remoteName, remotes)); - GitRemote fakeRemote = new GitRemote(remoteName, emptyList(), emptyList(), emptyList(), emptyList(), emptyList()); - return new GitStandardRemoteBranch(fakeRemote, branchName); - } - return new GitStandardRemoteBranch(remote, branchName); + Hash hash = parseHash(headContent); + if (hash != null) { + return new HeadInfo(false, headContent); + } + String target = getTarget(headContent); + if (target != null) { + return new HeadInfo(true, target); + } + LOG.error(new RepoStateException("Invalid format of the .git/HEAD file: [" + headContent + "]")); // including "refs/tags/v1" + return new HeadInfo(false, null); } - } - @Nonnull - private HeadInfo readHead() { - String headContent; - try { - headContent = DvcsUtil.tryLoadFile(myHeadFile, StandardCharsets.UTF_8); - } - catch (RepoStateException e) { - LOG.error(e); - return new HeadInfo(false, null); - } + /** + * Parses a line from the .git/packed-refs file returning a pair of hash and ref name. + * Comments and tags are ignored, and null is returned. + * Incorrectly formatted lines are ignored, a warning is printed to the log, null is returned. + * A line indicating a hash which an annotated tag (specified in the previous line) points to, is ignored: null is returned. + */ + @Nullable + private static Pair parsePackedRefsLine(@Nonnull String line) { + line = line.trim(); + if (line.isEmpty()) { + return null; + } + char firstChar = line.charAt(0); + if (firstChar == '#') { // ignoring comments + return null; + } + if (firstChar == '^') { + // ignoring the hash which an annotated tag above points to + return null; + } + String hash = null; + int i; + for (i = 0; i < line.length(); i++) { + char c = line.charAt(i); + if (!Character.isLetterOrDigit(c)) { + hash = line.substring(0, i); + break; + } + } + if (hash == null) { + LOG.warn("Ignoring invalid packed-refs line: [" + line + "]"); + return null; + } - Hash hash = parseHash(headContent); - if (hash != null) { - return new HeadInfo(false, headContent); - } - String target = getTarget(headContent); - if (target != null) { - return new HeadInfo(true, target); - } - LOG.error(new RepoStateException("Invalid format of the .git/HEAD file: [" + headContent + "]")); // including "refs/tags/v1" - return new HeadInfo(false, null); - } - - /** - * Parses a line from the .git/packed-refs file returning a pair of hash and ref name. - * Comments and tags are ignored, and null is returned. - * Incorrectly formatted lines are ignored, a warning is printed to the log, null is returned. - * A line indicating a hash which an annotated tag (specified in the previous line) points to, is ignored: null is returned. - */ - @Nullable - private static Pair parsePackedRefsLine(@Nonnull String line) { - line = line.trim(); - if (line.isEmpty()) { - return null; - } - char firstChar = line.charAt(0); - if (firstChar == '#') { // ignoring comments - return null; - } - if (firstChar == '^') { - // ignoring the hash which an annotated tag above points to - return null; - } - String hash = null; - int i; - for (i = 0; i < line.length(); i++) { - char c = line.charAt(i); - if (!Character.isLetterOrDigit(c)) { - hash = line.substring(0, i); - break; - } - } - if (hash == null) { - LOG.warn("Ignoring invalid packed-refs line: [" + line + "]"); - return null; - } + String branch = null; + int start = i; + if (start < line.length() && line.charAt(start++) == ' ') { + for (i = start; i < line.length(); i++) { + char c = line.charAt(i); + if (Character.isWhitespace(c)) { + break; + } + } + branch = line.substring(start, i); + } - String branch = null; - int start = i; - if (start < line.length() && line.charAt(start++) == ' ') { - for (i = start; i < line.length(); i++) { - char c = line.charAt(i); - if (Character.isWhitespace(c)) { - break; + if (branch == null || !branch.startsWith(REFS_HEADS_PREFIX) && !branch.startsWith(REFS_REMOTES_PREFIX)) { + return null; } - } - branch = line.substring(start, i); + return Pair.create(shortBuffer(branch), shortBuffer(hash.trim())); + } + + @Nonnull + private static String shortBuffer(String raw) { + return new String(raw); + } + + @Nonnull + private static Map resolveRefs(@Nonnull Map data) { + final Map resolved = getResolvedHashes(data); + Map unresolved = ContainerUtil.filter(data, refName -> !resolved.containsKey(refName)); + + boolean progressed = true; + while (progressed && !unresolved.isEmpty()) { + progressed = false; + for (Iterator> iterator = unresolved.entrySet().iterator(); iterator.hasNext(); ) { + Map.Entry entry = iterator.next(); + String refName = entry.getKey(); + String refValue = entry.getValue(); + String link = getTarget(refValue); + if (link != null) { + if (duplicateEntry(resolved, refName, refValue)) { + iterator.remove(); + } + else if (!resolved.containsKey(link)) { + LOG.debug("Unresolved symbolic link [" + refName + "] pointing to [" + refValue + "]"); // transitive link + } + else { + Hash targetValue = resolved.get(link); + resolved.put(refName, targetValue); + iterator.remove(); + progressed = true; + } + } + else { + LOG.warn("Unexpected record [" + refName + "] -> [" + refValue + "]"); + iterator.remove(); + } + } + } + if (!unresolved.isEmpty()) { + LOG.warn("Cyclic symbolic links among .git/refs: " + unresolved); + } + return resolved; } - if (branch == null || !branch.startsWith(REFS_HEADS_PREFIX) && !branch.startsWith(REFS_REMOTES_PREFIX)) { - return null; + @Nonnull + private static Map getResolvedHashes(@Nonnull Map data) { + Map resolved = new HashMap<>(); + for (Map.Entry entry : data.entrySet()) { + String refName = entry.getKey(); + Hash hash = parseHash(entry.getValue()); + if (hash != null && !duplicateEntry(resolved, refName, hash)) { + resolved.put(refName, hash); + } + } + return resolved; } - return Pair.create(shortBuffer(branch), shortBuffer(hash.trim())); - } - - @Nonnull - private static String shortBuffer(String raw) { - return new String(raw); - } - - @Nonnull - private static Map resolveRefs(@Nonnull Map data) { - final Map resolved = getResolvedHashes(data); - Map unresolved = ContainerUtil.filter(data, refName -> !resolved.containsKey(refName)); - - boolean progressed = true; - while (progressed && !unresolved.isEmpty()) { - progressed = false; - for (Iterator> iterator = unresolved.entrySet().iterator(); iterator.hasNext(); ) { - Map.Entry entry = iterator.next(); - String refName = entry.getKey(); - String refValue = entry.getValue(); - String link = getTarget(refValue); - if (link != null) { - if (duplicateEntry(resolved, refName, refValue)) { - iterator.remove(); - } - else if (!resolved.containsKey(link)) { - LOG.debug("Unresolved symbolic link [" + refName + "] pointing to [" + refValue + "]"); // transitive link - } - else { - Hash targetValue = resolved.get(link); - resolved.put(refName, targetValue); - iterator.remove(); - progressed = true; - } + + @Nullable + private static String getTarget(@Nonnull String refName) { + Matcher matcher = BRANCH_PATTERN.matcher(refName); + if (!matcher.matches()) { + return null; } - else { - LOG.warn("Unexpected record [" + refName + "] -> [" + refValue + "]"); - iterator.remove(); + String target = matcher.group(1); + if (!target.startsWith(REFS_HEADS_PREFIX) && !target.startsWith(REFS_REMOTES_PREFIX)) { + target = REFS_HEADS_PREFIX + target; } - } - } - if (!unresolved.isEmpty()) { - LOG.warn("Cyclic symbolic links among .git/refs: " + unresolved); - } - return resolved; - } - - @Nonnull - private static Map getResolvedHashes(@Nonnull Map data) { - Map resolved = new HashMap<>(); - for (Map.Entry entry : data.entrySet()) { - String refName = entry.getKey(); - Hash hash = parseHash(entry.getValue()); - if (hash != null && !duplicateEntry(resolved, refName, hash)) { - resolved.put(refName, hash); - } + return target; } - return resolved; - } - - @Nullable - private static String getTarget(@Nonnull String refName) { - Matcher matcher = BRANCH_PATTERN.matcher(refName); - if (!matcher.matches()) { - return null; - } - String target = matcher.group(1); - if (!target.startsWith(REFS_HEADS_PREFIX) && !target.startsWith(REFS_REMOTES_PREFIX)) { - target = REFS_HEADS_PREFIX + target; - } - return target; - } - @Nullable - private static Hash parseHash(@Nonnull String value) { - try { - return HashImpl.build(value); - } - catch (Exception e) { - return null; + @Nullable + private static Hash parseHash(@Nonnull String value) { + try { + return HashImpl.build(value); + } + catch (Exception e) { + return null; + } } - } - private static boolean duplicateEntry(@Nonnull Map resolved, @Nonnull String refName, @Nonnull Object newValue) { - if (resolved.containsKey(refName)) { - LOG.error("Duplicate entry for [" + refName + "]. resolved: [" + resolved.get(refName).asString() + "], current: " + newValue + "]"); - return true; + private static boolean duplicateEntry(@Nonnull Map resolved, @Nonnull String refName, @Nonnull Object newValue) { + if (resolved.containsKey(refName)) { + LOG.error("Duplicate entry for [" + refName + "]. resolved: [" + resolved.get(refName) + .asString() + "], current: " + newValue + "]"); + return true; + } + return false; } - return false; - } - /** - * Container to hold two information items: current .git/HEAD value and is Git on branch. - */ - private static class HeadInfo { - @Nullable - private final String content; - private final boolean isBranch; + /** + * Container to hold two information items: current .git/HEAD value and is Git on branch. + */ + private static class HeadInfo { + @Nullable + private final String content; + private final boolean isBranch; - HeadInfo(boolean branch, @Nullable String content) { - isBranch = branch; - this.content = content; + HeadInfo(boolean branch, @Nullable String content) { + isBranch = branch; + this.content = content; + } } - } } diff --git a/plugin/src/main/java/git4idea/repo/GitRepositoryUpdater.java b/plugin/src/main/java/git4idea/repo/GitRepositoryUpdater.java index e8d4bb5..a2a6f60 100644 --- a/plugin/src/main/java/git4idea/repo/GitRepositoryUpdater.java +++ b/plugin/src/main/java/git4idea/repo/GitRepositoryUpdater.java @@ -27,7 +27,6 @@ import consulo.virtualFileSystem.event.BulkFileListener; import consulo.virtualFileSystem.event.VFileEvent; import git4idea.util.GitFileUtils; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -38,140 +37,119 @@ /** * Listens to .git service files changes and updates {@link GitRepository} when needed. */ -final class GitRepositoryUpdater implements Disposable, BulkFileListener -{ - @Nonnull - private final GitRepository myRepository; - @Nonnull - private final GitRepositoryFiles myRepositoryFiles; - @Nullable - private final MessageBusConnection myMessageBusConnection; - @Nonnull - private final QueueProcessor myUpdateQueue; - @Nonnull - private final Object DUMMY_UPDATE_OBJECT = new Object(); - @Nullable - private final VirtualFile myRemotesDir; - @Nullable - private final VirtualFile myHeadsDir; - @Nullable - private final VirtualFile myTagsDir; - @Nullable - private final Set myWatchRequests; +final class GitRepositoryUpdater implements Disposable, BulkFileListener { + @Nonnull + private final GitRepository myRepository; + @Nonnull + private final GitRepositoryFiles myRepositoryFiles; + @Nullable + private final MessageBusConnection myMessageBusConnection; + @Nonnull + private final QueueProcessor myUpdateQueue; + @Nonnull + private final Object DUMMY_UPDATE_OBJECT = new Object(); + @Nullable + private final VirtualFile myRemotesDir; + @Nullable + private final VirtualFile myHeadsDir; + @Nullable + private final VirtualFile myTagsDir; + @Nullable + private final Set myWatchRequests; - GitRepositoryUpdater(@Nonnull GitRepository repository, @Nonnull GitRepositoryFiles gitFiles) - { - myRepository = repository; - Collection rootPaths = ContainerUtil.map(gitFiles.getRootDirs(), file -> file.getPath()); - myWatchRequests = LocalFileSystem.getInstance().addRootsToWatch(rootPaths, true); + GitRepositoryUpdater(@Nonnull GitRepository repository, @Nonnull GitRepositoryFiles gitFiles) { + myRepository = repository; + Collection rootPaths = ContainerUtil.map(gitFiles.getRootDirs(), VirtualFile::getPath); + myWatchRequests = LocalFileSystem.getInstance().addRootsToWatch(rootPaths, true); - myRepositoryFiles = gitFiles; - visitSubDirsInVfs(); - myHeadsDir = VcsUtil.getVirtualFile(myRepositoryFiles.getRefsHeadsFile()); - myRemotesDir = VcsUtil.getVirtualFile(myRepositoryFiles.getRefsRemotesFile()); - myTagsDir = VcsUtil.getVirtualFile(myRepositoryFiles.getRefsTagsFile()); + myRepositoryFiles = gitFiles; + visitSubDirsInVfs(); + myHeadsDir = VcsUtil.getVirtualFile(myRepositoryFiles.getRefsHeadsFile()); + myRemotesDir = VcsUtil.getVirtualFile(myRepositoryFiles.getRefsRemotesFile()); + myTagsDir = VcsUtil.getVirtualFile(myRepositoryFiles.getRefsTagsFile()); - Project project = repository.getProject(); - myUpdateQueue = new QueueProcessor<>(new DvcsUtil.Updater(repository), project.getDisposed()); - if(!project.isDisposed()) - { - myMessageBusConnection = project.getMessageBus().connect(); - myMessageBusConnection.subscribe(BulkFileListener.class, this); - } - else - { - myMessageBusConnection = null; - } - } + Project project = repository.getProject(); + myUpdateQueue = new QueueProcessor<>(new DvcsUtil.Updater(repository), project.getDisposed()); + if (!project.isDisposed()) { + myMessageBusConnection = project.getMessageBus().connect(); + myMessageBusConnection.subscribe(BulkFileListener.class, this); + } + else { + myMessageBusConnection = null; + } + } - @Override - public void dispose() - { - LocalFileSystem.getInstance().removeWatchedRoots(myWatchRequests); - if(myMessageBusConnection != null) - { - myMessageBusConnection.disconnect(); - } - } + @Override + public void dispose() { + LocalFileSystem.getInstance().removeWatchedRoots(myWatchRequests); + if (myMessageBusConnection != null) { + myMessageBusConnection.disconnect(); + } + } - @Override - public void before(@Nonnull List events) - { - // everything is handled in #after() - } + @Override + public void before(@Nonnull List events) { + // everything is handled in #after() + } - @Override - public void after(@Nonnull List events) - { - // which files in .git were changed - boolean configChanged = false; - boolean headChanged = false; - boolean branchFileChanged = false; - boolean packedRefsChanged = false; - boolean rebaseFileChanged = false; - boolean mergeFileChanged = false; - boolean tagChanged = false; - for(VFileEvent event : events) - { - String filePath = GitFileUtils.stripFileProtocolPrefix(event.getPath()); - if(myRepositoryFiles.isConfigFile(filePath)) - { - configChanged = true; - } - else if(myRepositoryFiles.isHeadFile(filePath)) - { - headChanged = true; - } - else if(myRepositoryFiles.isBranchFile(filePath)) - { - // it is also possible, that a local branch with complex name ("folder/branch") was created => the folder also to be watched. - branchFileChanged = true; - DvcsUtil.ensureAllChildrenInVfs(myHeadsDir); - } - else if(myRepositoryFiles.isRemoteBranchFile(filePath)) - { - // it is possible, that a branch from a new remote was fetch => we need to add new remote folder to the VFS - branchFileChanged = true; - DvcsUtil.ensureAllChildrenInVfs(myRemotesDir); - } - else if(myRepositoryFiles.isPackedRefs(filePath)) - { - packedRefsChanged = true; - } - else if(myRepositoryFiles.isRebaseFile(filePath)) - { - rebaseFileChanged = true; - } - else if(myRepositoryFiles.isMergeFile(filePath)) - { - mergeFileChanged = true; - } - else if(myRepositoryFiles.isTagFile(filePath)) - { - DvcsUtil.ensureAllChildrenInVfs(myTagsDir); - tagChanged = true; - } - } + @Override + public void after(@Nonnull List events) { + // which files in .git were changed + boolean configChanged = false; + boolean headChanged = false; + boolean branchFileChanged = false; + boolean packedRefsChanged = false; + boolean rebaseFileChanged = false; + boolean mergeFileChanged = false; + boolean tagChanged = false; + for (VFileEvent event : events) { + String filePath = GitFileUtils.stripFileProtocolPrefix(event.getPath()); + if (myRepositoryFiles.isConfigFile(filePath)) { + configChanged = true; + } + else if (myRepositoryFiles.isHeadFile(filePath)) { + headChanged = true; + } + else if (myRepositoryFiles.isBranchFile(filePath)) { + // it is also possible, that a local branch with complex name ("folder/branch") + // was created => the folder also to be watched. + branchFileChanged = true; + DvcsUtil.ensureAllChildrenInVfs(myHeadsDir); + } + else if (myRepositoryFiles.isRemoteBranchFile(filePath)) { + // it is possible, that a branch from a new remote was fetch => we need to add new remote folder to the VFS + branchFileChanged = true; + DvcsUtil.ensureAllChildrenInVfs(myRemotesDir); + } + else if (myRepositoryFiles.isPackedRefs(filePath)) { + packedRefsChanged = true; + } + else if (myRepositoryFiles.isRebaseFile(filePath)) { + rebaseFileChanged = true; + } + else if (myRepositoryFiles.isMergeFile(filePath)) { + mergeFileChanged = true; + } + else if (myRepositoryFiles.isTagFile(filePath)) { + DvcsUtil.ensureAllChildrenInVfs(myTagsDir); + tagChanged = true; + } + } - if(headChanged || configChanged || branchFileChanged || packedRefsChanged || rebaseFileChanged || mergeFileChanged) - { - myUpdateQueue.add(DUMMY_UPDATE_OBJECT); - } - else if(tagChanged) - { - myRepository.getProject().getMessageBus().syncPublisher(GitRepositoryChangeListener.class).repositoryChanged(myRepository); - } - } + if (headChanged || configChanged || branchFileChanged || packedRefsChanged || rebaseFileChanged || mergeFileChanged) { + myUpdateQueue.add(DUMMY_UPDATE_OBJECT); + } + else if (tagChanged) { + myRepository.getProject().getMessageBus().syncPublisher(GitRepositoryChangeListener.class).repositoryChanged(myRepository); + } + } - private void visitSubDirsInVfs() - { - for(VirtualFile rootDir : myRepositoryFiles.getRootDirs()) - { - rootDir.getChildren(); - } - for(String path : myRepositoryFiles.getDirsToWatch()) - { - DvcsUtil.ensureAllChildrenInVfs(LocalFileSystem.getInstance().refreshAndFindFileByPath(path)); - } - } + private void visitSubDirsInVfs() { + for (VirtualFile rootDir : myRepositoryFiles.getRootDirs()) { + rootDir.getChildren(); + } + for (String path : myRepositoryFiles.getDirsToWatch()) { + DvcsUtil.ensureAllChildrenInVfs(LocalFileSystem.getInstance().refreshAndFindFileByPath(path)); + } + } } diff --git a/plugin/src/main/java/git4idea/repo/GitSubmoduleInfo.java b/plugin/src/main/java/git4idea/repo/GitSubmoduleInfo.java index 9207c5b..307a57d 100644 --- a/plugin/src/main/java/git4idea/repo/GitSubmoduleInfo.java +++ b/plugin/src/main/java/git4idea/repo/GitSubmoduleInfo.java @@ -2,71 +2,5 @@ import jakarta.annotation.Nonnull; -/** - * from kotlin - */ -public class GitSubmoduleInfo -{ - private final String myPath; - private final String myUrl; - - public GitSubmoduleInfo(@Nonnull String path, @Nonnull String url) - { - myPath = path; - myUrl = url; - } - - public String getPath() - { - return myPath; - } - - public String getUrl() - { - return myUrl; - } - - @Override - public boolean equals(Object o) - { - if(this == o) - { - return true; - } - if(o == null || getClass() != o.getClass()) - { - return false; - } - - GitSubmoduleInfo that = (GitSubmoduleInfo) o; - - if(!myPath.equals(that.myPath)) - { - return false; - } - if(!myUrl.equals(that.myUrl)) - { - return false; - } - - return true; - } - - @Override - public int hashCode() - { - int result = myPath.hashCode(); - result = 31 * result + myUrl.hashCode(); - return result; - } - - @Override - public String toString() - { - final StringBuilder sb = new StringBuilder("GitSubmoduleInfo{"); - sb.append("myPath='").append(myPath).append('\''); - sb.append(", myUrl='").append(myUrl).append('\''); - sb.append('}'); - return sb.toString(); - } +public record GitSubmoduleInfo(@Nonnull String path, @Nonnull String url) { } diff --git a/plugin/src/main/java/git4idea/repo/GitUntrackedFilesHolder.java b/plugin/src/main/java/git4idea/repo/GitUntrackedFilesHolder.java index c075cbd..40af0eb 100644 --- a/plugin/src/main/java/git4idea/repo/GitUntrackedFilesHolder.java +++ b/plugin/src/main/java/git4idea/repo/GitUntrackedFilesHolder.java @@ -36,22 +36,17 @@ import java.util.*; /** - *

- * Stores files which are untracked by the Git repository. + *

Stores files which are untracked by the Git repository. * Should be updated by calling {@link #add(VirtualFile)} and {@link #remove(Collection)} * whenever the list of unversioned files changes. - * Able to get the list of unversioned files from Git. - *

- *

- *

- * This class is used by {@link GitNewChangesCollector}. + * Able to get the list of unversioned files from Git.

+ * + *

This class is used by {@link GitNewChangesCollector}. * By keeping track of unversioned files in the Git repository we may invoke * 'git status --porcelain --untracked-files=no' which gives a significant speed boost: the command gets more than twice - * faster, because it doesn't need to seek for untracked files. - *

- *

- *

- * "Keeping track" means the following: + * faster, because it doesn't need to seek for untracked files.

+ * + *

"Keeping track" means the following: *

    *
  • * Once a file is created, it is added to untracked (by this class). @@ -63,22 +58,19 @@ *
  • *
*

- *

- * In some cases (file creation/deletion) the file is not silently added/removed from the list - instead the file is marked as + * + *

In some cases (file creation/deletion) the file is not silently added/removed from the list - instead the file is marked as * "possibly untracked" and Git is asked for the exact status of this file. - * It is needed, since the file may be created and added to the index independently, and events may race. - *

- *

- * Also, if .git/index changes, then a full refresh is initiated. The reason is not only untracked files tracking, but also handling - * committing outside IDEA, etc. - *

- *

- * Synchronization policy used in this class:
+ * It is needed, since the file may be created and added to the index independently, and events may race.

+ * + *

Also, if .git/index changes, then a full refresh is initiated. The reason is not only untracked files tracking, but also handling + * committing outside IDEA, etc.

+ * + *

Synchronization policy used in this class:
* myDefinitelyUntrackedFiles is accessed under the myDefinitelyUntrackedFiles lock.
* myPossiblyUntrackedFiles and myReady is accessed under the LOCK lock.
* This is done so, because the latter two variables are accessed from the AWT in after() and we don't want to lock the AWT long, - * while myDefinitelyUntrackedFiles is modified along with native request to Git. - *

+ * while myDefinitelyUntrackedFiles is modified along with native request to Git.

* * @author Kirill Likhodedov */ @@ -316,15 +308,15 @@ private static VirtualFile getAffectedFile(@Nonnull VFileEvent event) { if (event instanceof VFileCreateEvent || event instanceof VFileDeleteEvent || event instanceof VFileMoveEvent || isRename(event)) { return event.getFile(); } - else if (event instanceof VFileCopyEvent) { - VFileCopyEvent copyEvent = (VFileCopyEvent) event; + else if (event instanceof VFileCopyEvent copyEvent) { return copyEvent.getNewParent().findChild(copyEvent.getNewChildName()); } return null; } private static boolean isRename(@Nonnull VFileEvent event) { - return event instanceof VFilePropertyChangeEvent && ((VFilePropertyChangeEvent) event).getPropertyName().equals(VirtualFile.PROP_NAME); + return event instanceof VFilePropertyChangeEvent propertyChangeEvent + && propertyChangeEvent.getPropertyName().equals(VirtualFile.PROP_NAME); } private boolean notIgnored(@Nullable VirtualFile file) { diff --git a/plugin/src/main/java/git4idea/reset/GitNewResetDialog.java b/plugin/src/main/java/git4idea/reset/GitNewResetDialog.java index 2ca2d32..562a68c 100644 --- a/plugin/src/main/java/git4idea/reset/GitNewResetDialog.java +++ b/plugin/src/main/java/git4idea/reset/GitNewResetDialog.java @@ -15,6 +15,7 @@ */ package git4idea.reset; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.ui.ex.awt.*; import consulo.util.lang.StringUtil; @@ -34,113 +35,117 @@ public class GitNewResetDialog extends DialogWrapper { - private static final String DIALOG_ID = "git.new.reset.dialog"; - - @Nonnull - private final Project myProject; - @Nonnull - private final Map myCommits; - @Nonnull - private final GitResetMode myDefaultMode; - @Nonnull - private final ButtonGroup myButtonGroup; - - private RadioButtonEnumModel myEnumModel; - - protected GitNewResetDialog(@Nonnull Project project, - @Nonnull Map commits, - @Nonnull GitResetMode defaultMode) { - super(project); - myProject = project; - myCommits = commits; - myDefaultMode = defaultMode; - myButtonGroup = new ButtonGroup(); - - init(); - setTitle("Git Reset"); - setOKButtonText("Reset"); - setOKButtonMnemonic('R'); - setResizable(false); - } - - @Nullable - @Override - protected JComponent createCenterPanel() { - JPanel panel = new JPanel(new GridBagLayout()); - GridBag gb = new GridBag(). - setDefaultAnchor(GridBagConstraints.LINE_START). - setDefaultInsets(0, UIUtil.DEFAULT_HGAP, UIUtil.LARGE_VGAP, 0); - - String description = prepareDescription(myProject, myCommits); - panel.add(new JBLabel(XmlStringUtil.wrapInHtml(description)), gb.nextLine().next().coverLine()); - - String explanation = - "This will reset the current branch head to the selected commit,
" + "and update the working tree and the index " + - "according to the selected mode:"; - panel.add(new JBLabel(XmlStringUtil.wrapInHtml(explanation), UIUtil.ComponentStyle.SMALL), gb.nextLine().next().coverLine()); - - for (GitResetMode mode : GitResetMode.values()) { - JBRadioButton button = new JBRadioButton(mode.getName()); - button.setMnemonic(mode.getName().charAt(0)); - myButtonGroup.add(button); - panel.add(button, gb.nextLine().next()); - panel.add(new JBLabel(XmlStringUtil.wrapInHtml(mode.getDescription()), UIUtil.ComponentStyle.SMALL), gb.next()); + private static final String DIALOG_ID = "git.new.reset.dialog"; + + @Nonnull + private final Project myProject; + @Nonnull + private final Map myCommits; + @Nonnull + private final GitResetMode myDefaultMode; + @Nonnull + private final ButtonGroup myButtonGroup; + + private RadioButtonEnumModel myEnumModel; + + protected GitNewResetDialog( + @Nonnull Project project, + @Nonnull Map commits, + @Nonnull GitResetMode defaultMode + ) { + super(project); + myProject = project; + myCommits = commits; + myDefaultMode = defaultMode; + myButtonGroup = new ButtonGroup(); + + init(); + setTitle(LocalizeValue.localizeTODO("Git Reset")); + setOKButtonText(LocalizeValue.localizeTODO("Reset")); + setOKButtonMnemonic('R'); + setResizable(false); } - myEnumModel = RadioButtonEnumModel.bindEnum(GitResetMode.class, myButtonGroup); - myEnumModel.setSelected(myDefaultMode); - return panel; - } - - @Nullable - @Override - protected String getHelpId() { - return DIALOG_ID; - } - - @Nonnull - private static String prepareDescription(@Nonnull Project project, @Nonnull Map commits) { - if (commits.size() == 1 && !isMultiRepo(project)) { - Map.Entry entry = commits.entrySet().iterator().next(); - return String.format("%s -> %s", getSourceText(entry.getKey()), getTargetText(entry.getValue())); + @Nullable + @Override + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + GridBag gb = new GridBag() + .setDefaultAnchor(GridBagConstraints.LINE_START) + .setDefaultInsets(0, UIUtil.DEFAULT_HGAP, UIUtil.LARGE_VGAP, 0); + + String description = prepareDescription(myProject, myCommits); + panel.add(new JBLabel(XmlStringUtil.wrapInHtml(description)), gb.nextLine().next().coverLine()); + + String explanation = "This will reset the current branch head to the selected commit,
" + + "and update the working tree and the index according to the selected mode:"; + panel.add(new JBLabel(XmlStringUtil.wrapInHtml(explanation), UIUtil.ComponentStyle.SMALL), gb.nextLine().next().coverLine()); + + for (GitResetMode mode : GitResetMode.values()) { + JBRadioButton button = new JBRadioButton(mode.getName()); + button.setMnemonic(mode.getName().charAt(0)); + myButtonGroup.add(button); + panel.add(button, gb.nextLine().next()); + panel.add(new JBLabel(XmlStringUtil.wrapInHtml(mode.getDescription()), UIUtil.ComponentStyle.SMALL), gb.next()); + } + + myEnumModel = RadioButtonEnumModel.bindEnum(GitResetMode.class, myButtonGroup); + myEnumModel.setSelected(myDefaultMode); + return panel; } - StringBuilder desc = new StringBuilder(""); - for (Map.Entry entry : commits.entrySet()) { - GitRepository repository = entry.getKey(); - VcsFullCommitDetails commit = entry.getValue(); - desc.append(String.format("%s in %s -> %s
", - getSourceText(repository), - getShortRepositoryName(repository), - getTargetText(commit))); + @Nullable + @Override + protected String getHelpId() { + return DIALOG_ID; + } + + @Nonnull + private static String prepareDescription(@Nonnull Project project, @Nonnull Map commits) { + if (commits.size() == 1 && !isMultiRepo(project)) { + Map.Entry entry = commits.entrySet().iterator().next(); + return String.format("%s -> %s", getSourceText(entry.getKey()), getTargetText(entry.getValue())); + } + + StringBuilder desc = new StringBuilder(""); + for (Map.Entry entry : commits.entrySet()) { + GitRepository repository = entry.getKey(); + VcsFullCommitDetails commit = entry.getValue(); + desc.append(String.format( + "%s in %s -> %s
", + getSourceText(repository), + getShortRepositoryName(repository), + getTargetText(commit) + )); + } + return desc.toString(); + } + + @Nonnull + private static String getTargetText(@Nonnull VcsFullCommitDetails commit) { + String commitMessage = StringUtil.escapeXml(StringUtil.shortenTextWithEllipsis(commit.getSubject(), 20, 0)); + return String.format("%s \"%s\" by %s", commit.getId().toShortString(), commitMessage, + commit.getAuthor().getName() + ); + } + + @Nonnull + private static String getSourceText(@Nonnull GitRepository repository) { + String currentRevision = repository.getCurrentRevision(); + assert currentRevision != null; + String text = repository.getCurrentBranch() == null + ? "HEAD (" + DvcsUtil.getShortHash(currentRevision) + ")" + : repository.getCurrentBranch().getName(); + return "" + text + ""; + } + + private static boolean isMultiRepo(@Nonnull Project project) { + return GitRepositoryManager.getInstance(project).moreThanOneRoot(); + } + + @Nonnull + public GitResetMode getResetMode() { + return myEnumModel.getSelected(); } - return desc.toString(); - } - - @Nonnull - private static String getTargetText(@Nonnull VcsFullCommitDetails commit) { - String commitMessage = StringUtil.escapeXml(StringUtil.shortenTextWithEllipsis(commit.getSubject(), 20, 0)); - return String.format("%s \"%s\" by %s", commit.getId().toShortString(), commitMessage, - commit.getAuthor().getName()); - } - - @Nonnull - private static String getSourceText(@Nonnull GitRepository repository) { - String currentRevision = repository.getCurrentRevision(); - assert currentRevision != null; - String text = - repository.getCurrentBranch() == null ? "HEAD (" + DvcsUtil.getShortHash(currentRevision) + ")" : repository.getCurrentBranch - ().getName(); - return "" + text + ""; - } - - private static boolean isMultiRepo(@Nonnull Project project) { - return GitRepositoryManager.getInstance(project).moreThanOneRoot(); - } - - @Nonnull - public GitResetMode getResetMode() { - return myEnumModel.getSelected(); - } } diff --git a/plugin/src/main/java/git4idea/reset/GitOneCommitPerRepoLogAction.java b/plugin/src/main/java/git4idea/reset/GitOneCommitPerRepoLogAction.java index abbaea8..326c763 100644 --- a/plugin/src/main/java/git4idea/reset/GitOneCommitPerRepoLogAction.java +++ b/plugin/src/main/java/git4idea/reset/GitOneCommitPerRepoLogAction.java @@ -10,16 +10,15 @@ import jakarta.annotation.Nullable; public abstract class GitOneCommitPerRepoLogAction extends VcsLogOneCommitPerRepoAction { + @Nonnull + @Override + protected AbstractRepositoryManager getRepositoryManager(@Nonnull Project project) { + return GitRepositoryManager.getInstance(project); + } - @Nonnull - @Override - protected AbstractRepositoryManager getRepositoryManager(@Nonnull Project project) { - return GitRepositoryManager.getInstance(project); - } - - @Nullable - @Override - protected GitRepository getRepositoryForRoot(@Nonnull Project project, @Nonnull VirtualFile root) { - return getRepositoryManager(project).getRepositoryForRoot(root); - } + @Nullable + @Override + protected GitRepository getRepositoryForRoot(@Nonnull Project project, @Nonnull VirtualFile root) { + return getRepositoryManager(project).getRepositoryForRoot(root); + } } diff --git a/plugin/src/main/java/git4idea/reset/GitResetAction.java b/plugin/src/main/java/git4idea/reset/GitResetAction.java index e7f40b3..aa663ec 100644 --- a/plugin/src/main/java/git4idea/reset/GitResetAction.java +++ b/plugin/src/main/java/git4idea/reset/GitResetAction.java @@ -26,7 +26,6 @@ import consulo.versionControlSystem.log.VcsFullCommitDetails; import git4idea.config.GitVcsSettings; import git4idea.repo.GitRepository; - import jakarta.annotation.Nonnull; import java.util.Map; @@ -39,8 +38,8 @@ public GitResetAction() { getTemplatePresentation().setTextValue(GitLocalize.actionLogResetText()); } - @RequiredUIAccess @Override + @RequiredUIAccess protected void actionPerformed(@Nonnull final Project project, @Nonnull final Map commits) { GitVcsSettings settings = GitVcsSettings.getInstance(project); GitResetMode defaultMode = ObjectUtil.notNull(settings.getResetMode(), GitResetMode.getDefault()); @@ -50,6 +49,7 @@ protected void actionPerformed(@Nonnull final Project project, @Nonnull final Ma settings.setResetMode(selectedMode); new Task.Backgroundable(project, GitLocalize.dialogTitleReset(), true) { @Override + @RequiredUIAccess public void run(@Nonnull ProgressIndicator indicator) { Map hashes = commits.keySet().stream() .collect(Collectors.toMap(Function.identity(), repo -> commits.get(repo).getId())); diff --git a/plugin/src/main/java/git4idea/reset/GitResetMode.java b/plugin/src/main/java/git4idea/reset/GitResetMode.java index 809abf3..0d4badb 100644 --- a/plugin/src/main/java/git4idea/reset/GitResetMode.java +++ b/plugin/src/main/java/git4idea/reset/GitResetMode.java @@ -17,50 +17,42 @@ import jakarta.annotation.Nonnull; -public enum GitResetMode -{ - - SOFT("Soft", "--soft", "Files won't change, differences will be staged for commit."), - MIXED("Mixed", "--mixed", "Files won't change, differences won't be staged."), - HARD("Hard", "--hard", "Files will be reverted to the state of the selected commit.
" + "Warning: any local changes will be lost."), - KEEP("Keep", "--keep", "Files will be reverted to the state of the selected commit,
" + "but local changes will be kept intact."); - - @Nonnull - private final String myName; - @Nonnull - private final String myArgument; - @Nonnull - private final String myDescription; - - GitResetMode(@Nonnull String name, @Nonnull String argument, @Nonnull String description) - { - myName = name; - myArgument = argument; - myDescription = description; - } - - @Nonnull - public static GitResetMode getDefault() - { - return MIXED; - } - - @Nonnull - public String getName() - { - return myName; - } - - @Nonnull - public String getArgument() - { - return myArgument; - } - - @Nonnull - public String getDescription() - { - return myDescription; - } - +public enum GitResetMode { + SOFT("Soft", "--soft", "Files won't change, differences will be staged for commit."), + MIXED("Mixed", "--mixed", "Files won't change, differences won't be staged."), + HARD("Hard", "--hard", "Files will be reverted to the state of the selected commit.
" + "Warning: any local changes will be lost."), + KEEP("Keep", "--keep", "Files will be reverted to the state of the selected commit,
" + "but local changes will be kept intact."); + + @Nonnull + private final String myName; + @Nonnull + private final String myArgument; + @Nonnull + private final String myDescription; + + GitResetMode(@Nonnull String name, @Nonnull String argument, @Nonnull String description) { + myName = name; + myArgument = argument; + myDescription = description; + } + + @Nonnull + public static GitResetMode getDefault() { + return MIXED; + } + + @Nonnull + public String getName() { + return myName; + } + + @Nonnull + public String getArgument() { + return myArgument; + } + + @Nonnull + public String getDescription() { + return myDescription; + } } diff --git a/plugin/src/main/java/git4idea/rollback/GitRollbackEnvironment.java b/plugin/src/main/java/git4idea/rollback/GitRollbackEnvironment.java index 3b6573c..d9d91f5 100644 --- a/plugin/src/main/java/git4idea/rollback/GitRollbackEnvironment.java +++ b/plugin/src/main/java/git4idea/rollback/GitRollbackEnvironment.java @@ -68,8 +68,8 @@ public String getRollbackOperationName() { @Override public void rollbackModifiedWithoutCheckout( @Nonnull List files, - final List exceptions, - final RollbackProgressListener listener + List exceptions, + RollbackProgressListener listener ) { throw new UnsupportedOperationException("Explicit file checkout is not supported by GIT."); } @@ -77,8 +77,8 @@ public void rollbackModifiedWithoutCheckout( @Override public void rollbackMissingFileDeletion( @Nonnull List files, - final List exceptions, - final RollbackProgressListener listener + List exceptions, + RollbackProgressListener listener ) { throw new UnsupportedOperationException("Missing file delete is not reported by GIT."); } @@ -91,8 +91,8 @@ public void rollbackIfUnchanged(@Nonnull VirtualFile file) { @Override public void rollbackChanges( @Nonnull List changes, - final List exceptions, - @Nonnull final RollbackProgressListener listener + List exceptions, + @Nonnull RollbackProgressListener listener ) { HashMap> toUnindex = new HashMap<>(); HashMap> toUnversion = new HashMap<>(); @@ -147,12 +147,10 @@ public void rollbackChanges( for (FilePath file : toDelete) { listener.accept(file); try { - final File ioFile = file.getIOFile(); - if (ioFile.exists()) { - if (!ioFile.delete()) { - //noinspection ThrowableInstanceNeverThrown - exceptions.add(new VcsException("Unable to delete file: " + file)); - } + File ioFile = file.getIOFile(); + if (ioFile.exists() && !ioFile.delete()) { + //noinspection ThrowableInstanceNeverThrown + exceptions.add(new VcsException("Unable to delete file: " + file)); } } catch (Exception e) { @@ -171,7 +169,7 @@ public void rollbackChanges( } } LocalFileSystem lfs = LocalFileSystem.getInstance(); - HashSet filesToRefresh = new HashSet<>(); + Set filesToRefresh = new HashSet<>(); for (Change c : changes) { ContentRevision before = c.getBeforeRevision(); if (before != null) { @@ -196,7 +194,7 @@ public void rollbackChanges( * @param files The array of files to revert. * @throws VcsException Id it breaks. */ - public void revert(final VirtualFile root, final List files) throws VcsException { + public void revert(VirtualFile root, List files) throws VcsException { for (List paths : VcsFileUtil.chunkPaths(root, files)) { GitSimpleHandler handler = new GitSimpleHandler(myProject, root, GitCommand.CHECKOUT); handler.addParameters("HEAD"); @@ -214,14 +212,14 @@ public void revert(final VirtualFile root, final List files) throws Vc * @param toUnversioned passed true if the file will be unversioned after unindexing, i.e. it was added before the revert operation. * @throws VcsException if there is a problem with running git */ - private void unindex(final VirtualFile root, final List files, boolean toUnversioned) throws VcsException { + private void unindex(VirtualFile root, List files, boolean toUnversioned) throws VcsException { GitFileUtils.delete(myProject, root, files, "--cached", "-f"); if (toUnversioned) { - final GitRepository repo = GitUtil.getRepositoryManager(myProject).getRepositoryForRoot(root); - final GitUntrackedFilesHolder untrackedFilesHolder = (repo == null ? null : repo.getUntrackedFilesHolder()); + GitRepository repo = GitUtil.getRepositoryManager(myProject).getRepositoryForRoot(root); + GitUntrackedFilesHolder untrackedFilesHolder = (repo == null ? null : repo.getUntrackedFilesHolder()); for (FilePath path : files) { - final VirtualFile vf = VcsUtil.getVirtualFile(path.getIOFile()); + VirtualFile vf = VcsUtil.getVirtualFile(path.getIOFile()); if (untrackedFilesHolder != null && vf != null) { untrackedFilesHolder.add(vf); } @@ -237,7 +235,7 @@ private void unindex(final VirtualFile root, final List files, boolean * @param exceptions the list of exceptions to update */ private static void registerFile(Map> files, FilePath file, List exceptions) { - final VirtualFile root; + VirtualFile root; try { root = GitUtil.getGitRoot(file); } @@ -259,11 +257,11 @@ private static void registerFile(Map> files, FilePat * @param project a context project * @return a project-specific instance of the service */ - public static GitRollbackEnvironment getInstance(final Project project) { + public static GitRollbackEnvironment getInstance(Project project) { return ServiceManager.getService(project, GitRollbackEnvironment.class); } - public static void resetHardLocal(final Project project, final VirtualFile root) { + public static void resetHardLocal(Project project, VirtualFile root) { GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.RESET); handler.addParameters("--hard"); handler.endOptions(); diff --git a/plugin/src/main/java/git4idea/roots/GitRootChecker.java b/plugin/src/main/java/git4idea/roots/GitRootChecker.java index 44edc95..ce37dc2 100644 --- a/plugin/src/main/java/git4idea/roots/GitRootChecker.java +++ b/plugin/src/main/java/git4idea/roots/GitRootChecker.java @@ -20,7 +20,6 @@ import consulo.versionControlSystem.VcsRootChecker; import git4idea.GitUtil; import git4idea.GitVcs; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -31,19 +30,19 @@ */ @ExtensionImpl public class GitRootChecker extends VcsRootChecker { - @Override - public boolean isRoot(@Nonnull String path) { - return GitUtil.isGitRoot(new File(path)); - } + @Override + public boolean isRoot(@Nonnull String path) { + return GitUtil.isGitRoot(new File(path)); + } - @Override - @Nonnull - public VcsKey getSupportedVcs() { - return GitVcs.getKey(); - } + @Override + @Nonnull + public VcsKey getSupportedVcs() { + return GitVcs.getKey(); + } - @Override - public boolean isVcsDir(@Nullable String path) { - return path != null && path.toLowerCase().endsWith(GitUtil.DOT_GIT); - } + @Override + public boolean isVcsDir(@Nullable String path) { + return path != null && path.toLowerCase().endsWith(GitUtil.DOT_GIT); + } } diff --git a/plugin/src/main/java/git4idea/settings/GitPushSettings.java b/plugin/src/main/java/git4idea/settings/GitPushSettings.java index 4518584..1df3159 100644 --- a/plugin/src/main/java/git4idea/settings/GitPushSettings.java +++ b/plugin/src/main/java/git4idea/settings/GitPushSettings.java @@ -27,42 +27,40 @@ */ @consulo.component.persist.State(name = "Git.Push.Settings", storages = {@Storage(file = StoragePathMacros.WORKSPACE_FILE)}) public class GitPushSettings implements PersistentStateComponent { + private State myState = new State(); - private State myState = new State(); + public static class State { + public boolean myUpdateAllRoots = true; + public UpdateMethod myUpdateMethod = UpdateMethod.MERGE; + } - public static class State { - public boolean myUpdateAllRoots = true; - public UpdateMethod myUpdateMethod = UpdateMethod.MERGE; - } + public static GitPushSettings getInstance(Project project) { + return ServiceManager.getService(project, GitPushSettings.class); + } - public static GitPushSettings getInstance(Project project) { - return ServiceManager.getService(project, GitPushSettings.class); - } + @Override + public State getState() { + return myState; + } - @Override - public State getState() { - return myState; - } + @Override + public void loadState(State state) { + myState = state; + } - @Override - public void loadState(State state) { - myState = state; - } + public boolean shouldUpdateAllRoots() { + return myState.myUpdateAllRoots; + } - public boolean shouldUpdateAllRoots() { - return myState.myUpdateAllRoots; - } - - public void setUpdateAllRoots(boolean updateAllRoots) { - myState.myUpdateAllRoots = updateAllRoots; - } + public void setUpdateAllRoots(boolean updateAllRoots) { + myState.myUpdateAllRoots = updateAllRoots; + } - public UpdateMethod getUpdateMethod() { - return myState.myUpdateMethod; - } - - public void setUpdateMethod(UpdateMethod updateMethod) { - myState.myUpdateMethod = updateMethod; - } + public UpdateMethod getUpdateMethod() { + return myState.myUpdateMethod; + } + public void setUpdateMethod(UpdateMethod updateMethod) { + myState.myUpdateMethod = updateMethod; + } } diff --git a/plugin/src/main/java/git4idea/stash/GitShelveUtils.java b/plugin/src/main/java/git4idea/stash/GitShelveUtils.java index 4de46ed..95ae7f2 100644 --- a/plugin/src/main/java/git4idea/stash/GitShelveUtils.java +++ b/plugin/src/main/java/git4idea/stash/GitShelveUtils.java @@ -39,6 +39,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Set; public class GitShelveUtils { private static final Logger LOG = Logger.getInstance(GitShelveUtils.class); @@ -96,7 +97,7 @@ private static void markUnshelvedFilesNonUndoable(@Nonnull Project project, @Non } private static void refreshFilesBeforeUnshelve(Project project, ShelvedChangeList shelvedChangeList, String projectPath) { - HashSet filesToRefresh = new HashSet<>(); + Set filesToRefresh = new HashSet<>(); for (ShelvedChange c : shelvedChangeList.getChanges(project)) { if (c.getBeforePath() != null) { filesToRefresh.add(new File(projectPath + c.getBeforePath())); diff --git a/plugin/src/main/java/git4idea/stash/GitStashUtils.java b/plugin/src/main/java/git4idea/stash/GitStashUtils.java index 8db0947..0b84ce6 100644 --- a/plugin/src/main/java/git4idea/stash/GitStashUtils.java +++ b/plugin/src/main/java/git4idea/stash/GitStashUtils.java @@ -15,8 +15,8 @@ */ package git4idea.stash; -import consulo.logging.Logger; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.versionControlSystem.VcsException; import consulo.virtualFileSystem.VirtualFile; import git4idea.commands.Git; @@ -28,54 +28,49 @@ import git4idea.ui.StashInfo; import git4idea.util.GitUIUtil; import git4idea.util.StringScanner; - import jakarta.annotation.Nonnull; + import java.nio.charset.Charset; import java.util.function.Consumer; /** * The class contains utilities for creating and removing stashes. */ -public class GitStashUtils -{ - - private static final Logger LOG = Logger.getInstance(GitStashUtils.class); - - private GitStashUtils() - { - } +public class GitStashUtils { + private GitStashUtils() { + } - public static boolean saveStash(@Nonnull Git git, @Nonnull GitRepository repository, final String message) - { - GitCommandResult result = git.stashSave(repository, message); - return result.success() && !result.getErrorOutputAsJoinedString().contains("No local changes to save"); - } + public static boolean saveStash(@Nonnull Git git, @Nonnull GitRepository repository, String message) { + GitCommandResult result = git.stashSave(repository, message); + return result.success() && !result.getErrorOutputAsJoinedString().contains("No local changes to save"); + } - public static void loadStashStack(@Nonnull Project project, @Nonnull VirtualFile root, Consumer consumer) - { - loadStashStack(project, root, Charset.forName(GitConfigUtil.getLogEncoding(project, root)), consumer); - } + @RequiredUIAccess + public static void loadStashStack(@Nonnull Project project, @Nonnull VirtualFile root, Consumer consumer) { + loadStashStack(project, root, Charset.forName(GitConfigUtil.getLogEncoding(project, root)), consumer); + } - public static void loadStashStack(@Nonnull Project project, @Nonnull VirtualFile root, final Charset charset, - final Consumer consumer) - { - GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.STASH.readLockingCommand()); - h.setSilent(true); - h.addParameters("list"); - String out; - try - { - h.setCharset(charset); - out = h.run(); - } - catch(VcsException e) - { - GitUIUtil.showOperationError(project, e, h.printableCommandLine()); - return; - } - for(StringScanner s = new StringScanner(out); s.hasMoreData(); ) - { - consumer.accept(new StashInfo(s.boundedToken(':'), s.boundedToken(':'), s.line().trim())); - } - } + @RequiredUIAccess + public static void loadStashStack( + @Nonnull Project project, + @Nonnull VirtualFile root, + Charset charset, + Consumer consumer + ) { + GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.STASH.readLockingCommand()); + h.setSilent(true); + h.addParameters("list"); + String out; + try { + h.setCharset(charset); + out = h.run(); + } + catch (VcsException e) { + GitUIUtil.showOperationError(project, e, h.printableCommandLine()); + return; + } + for (StringScanner s = new StringScanner(out); s.hasMoreData(); ) { + consumer.accept(new StashInfo(s.boundedToken(':'), s.boundedToken(':'), s.line().trim())); + } + } } diff --git a/plugin/src/main/java/git4idea/ui/GitUnstashDialog.java b/plugin/src/main/java/git4idea/ui/GitUnstashDialog.java index 25b616d..2e100cf 100644 --- a/plugin/src/main/java/git4idea/ui/GitUnstashDialog.java +++ b/plugin/src/main/java/git4idea/ui/GitUnstashDialog.java @@ -59,10 +59,8 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.List; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -104,7 +102,7 @@ public class GitUnstashDialog extends DialogWrapper { /** * The stash list */ - private JList myStashList; + private JList myStashList; /** * If this checkbox is selected, the index is reinstated as well as working tree */ @@ -112,7 +110,7 @@ public class GitUnstashDialog extends DialogWrapper { /** * Set of branches for the current root */ - private final HashSet myBranches = new HashSet<>(); + private final Set myBranches = new HashSet<>(); @Nonnull private final Project myProject; @@ -126,6 +124,7 @@ public class GitUnstashDialog extends DialogWrapper { * @param roots the list of the roots * @param defaultRoot the default root to select */ + @RequiredUIAccess public GitUnstashDialog(@Nonnull Project project, List roots, VirtualFile defaultRoot) { super(project, true); setModal(false); @@ -135,7 +134,7 @@ public GitUnstashDialog(@Nonnull Project project, List roots, Virtu setOKButtonText(GitLocalize.unstashButtonApply()); setCancelButtonText(CommonLocalize.buttonClose()); GitUIUtil.setupRootChooser(project, roots, defaultRoot, myGitRootComboBox, myCurrentBranch); - myStashList.setModel(new DefaultListModel()); + myStashList.setModel(new DefaultListModel<>()); refreshStashList(); myGitRootComboBox.addActionListener(e -> { refreshStashList(); @@ -294,6 +293,7 @@ private void updateDialogState() { /** * Refresh stash list */ + @RequiredUIAccess private void refreshStashList() { DefaultListModel listModel = (DefaultListModel) myStashList.getModel(); listModel.clear(); @@ -342,7 +342,7 @@ private GitLineHandler handler() { * @throws NullPointerException if no stash is selected */ private StashInfo getSelectedStash() { - return (StashInfo) myStashList.getSelectedValue(); + return myStashList.getSelectedValue(); } /** @@ -687,7 +687,7 @@ public static void showUnstashDialog(Project project, List gitRoots false ) ); - myStashList = new JBList(); + myStashList = new JBList<>(); myStashList.setSelectionMode(0); jBScrollPane1.setViewportView(myStashList); JPanel panel2 = new JPanel(); diff --git a/plugin/src/main/java/git4idea/ui/branch/GitCompareBranchesLogPanel.java b/plugin/src/main/java/git4idea/ui/branch/GitCompareBranchesLogPanel.java index d095275..24be4f9 100644 --- a/plugin/src/main/java/git4idea/ui/branch/GitCompareBranchesLogPanel.java +++ b/plugin/src/main/java/git4idea/ui/branch/GitCompareBranchesLogPanel.java @@ -36,6 +36,7 @@ import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Collections; +import java.util.List; /** * @author Kirill Likhodedov @@ -148,11 +149,11 @@ private JComponent createNorthPanel() { return repoSelectorPanel; } - private ArrayList getBranchToHeadCommits(GitRepository selectedRepo) { + private List getBranchToHeadCommits(GitRepository selectedRepo) { return new ArrayList<>(myCompareInfo.getBranchToHeadCommits(selectedRepo)); } - private ArrayList getHeadToBranchCommits(GitRepository selectedRepo) { + private List getHeadToBranchCommits(GitRepository selectedRepo) { return new ArrayList<>(myCompareInfo.getHeadToBranchCommits(selectedRepo)); } diff --git a/plugin/src/main/java/git4idea/ui/branch/GitMultiRootBranchConfig.java b/plugin/src/main/java/git4idea/ui/branch/GitMultiRootBranchConfig.java index 9e51e89..0f12651 100644 --- a/plugin/src/main/java/git4idea/ui/branch/GitMultiRootBranchConfig.java +++ b/plugin/src/main/java/git4idea/ui/branch/GitMultiRootBranchConfig.java @@ -22,7 +22,6 @@ import git4idea.branch.GitBranchUtil; import git4idea.repo.GitBranchTrackInfo; import git4idea.repo.GitRepository; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -94,8 +93,8 @@ public Collection getTrackingBranches(@Nonnull String remoteBranch) { public static Collection getTrackingBranches(@Nonnull GitRepository repository, @Nonnull String remoteBranch) { Collection trackingBranches = new ArrayList<>(1); for (GitBranchTrackInfo trackInfo : repository.getBranchTrackInfos()) { - if (remoteBranch.equals(trackInfo.getRemote().getName() + "/" + trackInfo.getRemoteBranch())) { - trackingBranches.add(trackInfo.getLocalBranch().getName()); + if (remoteBranch.equals(trackInfo.getRemote().getName() + "/" + trackInfo.remoteBranch())) { + trackingBranches.add(trackInfo.localBranch().getName()); } } return trackingBranches; diff --git a/plugin/src/main/java/git4idea/update/GitFetcher.java b/plugin/src/main/java/git4idea/update/GitFetcher.java index 89d4c72..59d5839 100644 --- a/plugin/src/main/java/git4idea/update/GitFetcher.java +++ b/plugin/src/main/java/git4idea/update/GitFetcher.java @@ -186,7 +186,7 @@ private static FetchParams getFetchParams(@Nonnull GitRepository repository) { return new FetchParams(GitFetchResult.error(new Exception(message))); } - return new FetchParams(remote, trackInfo.getRemoteBranch(), url); + return new FetchParams(remote, trackInfo.remoteBranch(), url); } @Nonnull diff --git a/plugin/src/main/java/git4idea/update/GitUpdateProcess.java b/plugin/src/main/java/git4idea/update/GitUpdateProcess.java index cdade7e..4386e06 100644 --- a/plugin/src/main/java/git4idea/update/GitUpdateProcess.java +++ b/plugin/src/main/java/git4idea/update/GitUpdateProcess.java @@ -377,7 +377,7 @@ private Map checkTrackedBranchesConfiguration() { } } else { - trackedBranches.put(root, new GitBranchPair(branch, trackInfo.getRemoteBranch())); + trackedBranches.put(root, new GitBranchPair(branch, trackInfo.remoteBranch())); } } return trackedBranches; diff --git a/plugin/src/main/java/git4idea/vfs/GitVFSListener.java b/plugin/src/main/java/git4idea/vfs/GitVFSListener.java index 64cad77..c1fe992 100644 --- a/plugin/src/main/java/git4idea/vfs/GitVFSListener.java +++ b/plugin/src/main/java/git4idea/vfs/GitVFSListener.java @@ -41,7 +41,6 @@ import git4idea.commands.GitSimpleHandler; import git4idea.util.GitFileUtils; import git4idea.util.GitVcsConsoleWriter; - import jakarta.annotation.Nonnull; import java.io.File; @@ -53,7 +52,7 @@ public class GitVFSListener extends VcsVFSListener { private final Git myGit; - public GitVFSListener(final Project project, final GitVcs vcs, Git git) { + public GitVFSListener(Project project, GitVcs vcs, Git git) { super(project, vcs); myGit = git; } @@ -86,8 +85,8 @@ protected void executeAdd(@Nonnull final List addedFiles, @Nonnull catch (VcsException e) { throw new RuntimeException("The exception is not expected here", e); } - final HashSet retainedFiles = new HashSet<>(); - final ProgressManager progressManager = ProgressManager.getInstance(); + final Set retainedFiles = new HashSet<>(); + ProgressManager progressManager = ProgressManager.getInstance(); progressManager.run(new Task.Backgroundable(myProject, GitLocalize.vfsListenerCheckingIgnored(), true) { @Override public void run(@Nonnull ProgressIndicator pi) { @@ -96,15 +95,15 @@ public void run(@Nonnull ProgressIndicator pi) { List files = e.getValue(); pi.setText(root.getPresentableUrl()); try { - retainedFiles.addAll(myGit.untrackedFiles((Project)myProject, root, files)); + retainedFiles.addAll(myGit.untrackedFiles((Project) myProject, root, files)); } catch (VcsException ex) { - GitVcsConsoleWriter.getInstance((Project)myProject).showMessage(ex.getMessage()); + GitVcsConsoleWriter.getInstance((Project) myProject).showMessage(ex.getMessage()); } } addedFiles.retainAll(retainedFiles); - AppUIUtil.invokeLaterIfProjectAlive((Project)myProject, () -> originalExecuteAdd(addedFiles, copiedFiles)); + AppUIUtil.invokeLaterIfProjectAlive((Project) myProject, () -> originalExecuteAdd(addedFiles, copiedFiles)); } }); } @@ -115,18 +114,18 @@ public void run(@Nonnull ProgressIndicator pi) { * @param addedFiles the added files * @param copiedFiles the copied files */ - private void originalExecuteAdd(List addedFiles, final Map copiedFiles) { + private void originalExecuteAdd(List addedFiles, Map copiedFiles) { super.executeAdd(addedFiles, copiedFiles); } @Override - protected void performAdding(final Collection addedFiles, final Map copyFromMap) { + protected void performAdding(Collection addedFiles, Map copyFromMap) { // copied files (copyFromMap) are ignored, because they are included into added files. performAdding(ObjectsConvertor.vf2fp(new ArrayList<>(addedFiles))); } private GitVcs gitVcs() { - return ((GitVcs)myVcs); + return ((GitVcs) myVcs); } private void performAdding(Collection filesToAdd) { @@ -164,33 +163,37 @@ protected Function getSingleFileDeletePromptGenerator() { } @Override - protected void performDeletion(final List filesToDelete) { - performBackgroundOperation(filesToDelete, GitLocalize.removeRemoving(), new LongOperationPerRootExecutor() { - HashSet filesToRefresh = new HashSet<>(); + protected void performDeletion(List filesToDelete) { + performBackgroundOperation( + filesToDelete, + GitLocalize.removeRemoving(), + new LongOperationPerRootExecutor() { + Set filesToRefresh = new HashSet<>(); - @Override - public void execute(@Nonnull VirtualFile root, @Nonnull List files) throws VcsException { - GitFileUtils.delete(myProject, root, files, "--ignore-unmatch", "--cached"); - if (!myProject.isDisposed()) { - VcsFileUtil.markFilesDirty(myProject, files); - } - File rootFile = new File(root.getPath()); - for (FilePath p : files) { - for (File f = p.getIOFile(); f != null && !FileUtil.filesEqual(f, rootFile); f = f.getParentFile()) { - filesToRefresh.add(f); + @Override + public void execute(@Nonnull VirtualFile root, @Nonnull List files) throws VcsException { + GitFileUtils.delete(myProject, root, files, "--ignore-unmatch", "--cached"); + if (!myProject.isDisposed()) { + VcsFileUtil.markFilesDirty(myProject, files); + } + File rootFile = new File(root.getPath()); + for (FilePath p : files) { + for (File f = p.getIOFile(); f != null && !FileUtil.filesEqual(f, rootFile); f = f.getParentFile()) { + filesToRefresh.add(f); + } } } - } - @Override - public Collection getFilesToRefresh() { - return filesToRefresh; + @Override + public Collection getFilesToRefresh() { + return filesToRefresh; + } } - }); + ); } @Override - protected void performMoveRename(final List movedFiles) { + protected void performMoveRename(List movedFiles) { List toAdd = new ArrayList<>(); List toRemove = new ArrayList<>(); List toForceMove = new ArrayList<>(); @@ -249,7 +252,7 @@ protected boolean isFileCopyingFromTrackingSupported() { @Nonnull @Override - protected List selectFilePathsToDelete(final List deletedFiles) { + protected List selectFilePathsToDelete(List deletedFiles) { // For git asking about vcs delete does not make much sense. The result is practically identical. return deletedFiles; } @@ -275,8 +278,8 @@ public void run(@Nonnull ProgressIndicator indicator) { try { executor.execute(e.getKey(), e.getValue()); } - catch (final VcsException ex) { - GitVcsConsoleWriter.getInstance((Project)myProject).showMessage(ex.getMessage()); + catch (VcsException ex) { + GitVcsConsoleWriter.getInstance((Project) myProject).showMessage(ex.getMessage()); } } RefreshVFsSynchronously.refreshFiles(executor.getFilesToRefresh()); diff --git a/plugin/src/main/java/org/jetbrains/git4idea/util/ScriptGenerator.java b/plugin/src/main/java/org/jetbrains/git4idea/util/ScriptGenerator.java index 0dab80c..1412117 100644 --- a/plugin/src/main/java/org/jetbrains/git4idea/util/ScriptGenerator.java +++ b/plugin/src/main/java/org/jetbrains/git4idea/util/ScriptGenerator.java @@ -15,7 +15,7 @@ */ package org.jetbrains.git4idea.util; -import consulo.application.util.SystemInfo; +import consulo.platform.Platform; import consulo.util.collection.ContainerUtil; import consulo.util.io.ClassPathUtil; import consulo.util.io.FileUtil; @@ -23,9 +23,8 @@ import consulo.util.io.URLUtil; import consulo.util.lang.Pair; import jakarta.annotation.Nonnull; -import org.jetbrains.annotations.NonNls; - import jakarta.annotation.Nullable; + import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -36,201 +35,181 @@ import java.net.URL; import java.security.CodeSource; import java.util.ArrayList; +import java.util.List; /** * Script generator utility class. It uses to generate a temporary scripts that * are removed after application ends. */ public class ScriptGenerator { - /** - * The extension of the ssh script name - */ - public static final String SCRIPT_EXT; + /** + * The extension of the ssh script name + */ + public static final String SCRIPT_EXT = Platform.current().os().isWindows() ? ".bat" : ".sh"; + /** + * The script prefix + */ + private final String myPrefix; + /** + * The scripts may class + */ + private final Class myMainClass; + /** + * The class paths for the script + */ + private final List myPaths = new ArrayList<>(); + /** + * The internal parameters for the script + */ + private final List myInternalParameters = new ArrayList<>(); - static { - if (SystemInfo.isWindows) { - SCRIPT_EXT = ".bat"; - } - else { - SCRIPT_EXT = ".sh"; + /** + * A constructor + * + * @param prefix the script prefix + * @param mainClass the script main class + */ + public ScriptGenerator(String prefix, Class mainClass) { + myPrefix = prefix; + myMainClass = mainClass; + addClasses(myMainClass); } - } - /** - * The script prefix - */ - private final String myPrefix; - /** - * The scripts may class - */ - private final Class myMainClass; - /** - * The class paths for the script - */ - private final ArrayList myPaths = new ArrayList(); - /** - * The internal parameters for the script - */ - private final ArrayList myInternalParameters = new ArrayList(); + /** + * Add jar or directory that contains the class to the classpath + * + * @param classes classes which sources will be added + * @return this script generator + */ + public ScriptGenerator addClasses(Class... classes) { + for (Class c : classes) { + addPath(getJarPathForClass(c)); + } + return this; + } - /** - * A constructor - * - * @param prefix the script prefix - * @param mainClass the script main class - */ - public ScriptGenerator(final String prefix, final Class mainClass) { - myPrefix = prefix; - myMainClass = mainClass; - addClasses(myMainClass); - } + @Nullable + public static String getJarPathForClass(@Nonnull Class aClass) { + try { + CodeSource codeSource = aClass.getProtectionDomain().getCodeSource(); + if (codeSource != null) { + URL location = codeSource.getLocation(); + if (location != null) { + URI uri = location.toURI(); + Pair pair = URLUtil.splitJarUrl(uri.toURL().toString()); + if (pair == null) { + // FIXME [VISTALL] our classloader return wrong uri + return uri.getPath(); + } + return pair.getFirst(); + } + } - /** - * Add jar or directory that contains the class to the classpath - * - * @param classes classes which sources will be added - * @return this script generator - */ - public ScriptGenerator addClasses(final Class... classes) { - for (Class c : classes) { - addPath(getJarPathForClass(c)); + throw new IllegalArgumentException(aClass.getName()); + } + catch (URISyntaxException | MalformedURLException e) { + throw new RuntimeException(e); + } } - return this; - } - @Nullable - public static String getJarPathForClass(@Nonnull Class aClass) { - try { - CodeSource codeSource = aClass.getProtectionDomain().getCodeSource(); - if (codeSource != null) { - URL location = codeSource.getLocation(); - if (location != null) { - URI uri = location.toURI(); - Pair pair = URLUtil.splitJarUrl(uri.toURL().toString()); - if (pair == null) { - // FIXME [VISTALL] our classloader return wrong uri - return uri.getPath(); - } - return pair.getFirst(); + /** + * Add path to class path. The methods checks if the path has been already added to the classpath. + * + * @param path the path to add + */ + private void addPath(String path) { + if (!myPaths.contains(path)) { + // the size of path is expected to be quite small, so no optimization is done here + myPaths.add(path); } - } - - throw new IllegalArgumentException(aClass.getName()); } - catch (URISyntaxException | MalformedURLException e) { - throw new RuntimeException(e); - } - } - /** - * Add path to class path. The methods checks if the path has been already added to the classpath. - * - * @param path the path to add - */ - private void addPath(final String path) { - if (!myPaths.contains(path)) { - // the size of path is expected to be quite small, so no optimization is done here - myPaths.add(path); + /** + * Add source for the specified resource + * + * @param base the resource base + * @param resource the resource name + * @return this script generator + */ + public ScriptGenerator addResource(Class base, String resource) { + addPath(getJarForResource(base, resource)); + return this; } - } - - /** - * Add source for the specified resource - * - * @param base the resource base - * @param resource the resource name - * @return this script generator - */ - public ScriptGenerator addResource(final Class base, @NonNls String resource) { - addPath(getJarForResource(base, resource)); - return this; - } - /** - * Add internal parameters for the script - * - * @param parameters internal parameters - * @return this script generator - */ - public ScriptGenerator addInternal(String... parameters) { - ContainerUtil.addAll(myInternalParameters, parameters); - return this; - } - - /** - * Generate script according to specified parameters - * - * @return the path to generated script - * @throws IOException if there is a problem with creating script - */ - @SuppressWarnings({"HardCodedStringLiteral"}) - public File generate() throws IOException { - File scriptPath = FileUtil.createTempFile(myPrefix, SCRIPT_EXT); - scriptPath.deleteOnExit(); - PrintWriter out = new PrintWriter(new FileWriter(scriptPath)); - try { - if (SystemInfo.isWindows) { - out.println("@echo off"); - } - else { - out.println("#!/bin/sh"); - } - String line = commandLine(); - if (SystemInfo.isWindows) { - line += " %*"; - } - else { - line += " \"$@\""; - } - out.println(line); - } - finally { - out.close(); + /** + * Add internal parameters for the script + * + * @param parameters internal parameters + * @return this script generator + */ + public ScriptGenerator addInternal(String... parameters) { + ContainerUtil.addAll(myInternalParameters, parameters); + return this; } - NioFiles.setExecutable(scriptPath.toPath()); - return scriptPath; - } - /** - * @return a command line for the the executable program - */ - public String commandLine() { - StringBuilder cmd = new StringBuilder(); - cmd.append('\"').append(System.getProperty("java.home")).append(File.separatorChar).append("bin").append(File.separatorChar) - .append("java\" -cp \""); - boolean first = true; - for (String p : myPaths) { - if (!first) { - cmd.append(File.pathSeparatorChar); - } - else { - first = false; - } - cmd.append(p); - } - cmd.append("\" "); - cmd.append(myMainClass.getName()); - for (String p : myInternalParameters) { - cmd.append(' '); - cmd.append(p); + /** + * Generate script according to specified parameters + * + * @return the path to generated script + * @throws IOException if there is a problem with creating script + */ + @SuppressWarnings({"HardCodedStringLiteral"}) + public File generate() throws IOException { + File scriptPath = FileUtil.createTempFile(myPrefix, SCRIPT_EXT); + scriptPath.deleteOnExit(); + PrintWriter out = new PrintWriter(new FileWriter(scriptPath)); + try { + boolean isWindows = Platform.current().os().isWindows(); + out.println(isWindows ? "@echo off" : "#!/bin/sh"); + out.println(commandLine() + (isWindows ? " %*" : " \"$@\"")); + } + finally { + out.close(); + } + NioFiles.setExecutable(scriptPath.toPath()); + return scriptPath; } - String line = cmd.toString(); - if (SystemInfo.isWindows) { - line = line.replace('\\', '/'); + + /** + * @return a command line for the the executable program + */ + public String commandLine() { + StringBuilder cmd = new StringBuilder(); + cmd.append('\"').append(System.getProperty("java.home")).append(File.separatorChar).append("bin").append(File.separatorChar) + .append("java\" -cp \""); + boolean first = true; + for (String p : myPaths) { + if (!first) { + cmd.append(File.pathSeparatorChar); + } + else { + first = false; + } + cmd.append(p); + } + cmd.append("\" "); + cmd.append(myMainClass.getName()); + for (String p : myInternalParameters) { + cmd.append(' '); + cmd.append(p); + } + String line = cmd.toString(); + if (Platform.current().os().isWindows()) { + line = line.replace('\\', '/'); + } + return line; } - return line; - } - /** - * Get path for resources.jar - * - * @param context a context class - * @param res a resource - * @return a path to classpath entry - */ - @SuppressWarnings({"SameParameterValue"}) - public static String getJarForResource(Class context, String res) { - String resourceRoot = ClassPathUtil.getResourceRoot(context, res); - return new File(resourceRoot).getAbsoluteFile().getAbsolutePath(); - } + /** + * Get path for resources.jar + * + * @param context a context class + * @param res a resource + * @return a path to classpath entry + */ + @SuppressWarnings({"SameParameterValue"}) + public static String getJarForResource(Class context, String res) { + String resourceRoot = ClassPathUtil.getResourceRoot(context, res); + return new File(resourceRoot).getAbsoluteFile().getAbsolutePath(); + } } diff --git a/plugin/src/test1/java/git4idea/history/GitHistoryUtilsTest.java b/plugin/src/test1/java/git4idea/history/GitHistoryUtilsTest.java index 152934b..2625f37 100644 --- a/plugin/src/test1/java/git4idea/history/GitHistoryUtilsTest.java +++ b/plugin/src/test1/java/git4idea/history/GitHistoryUtilsTest.java @@ -15,19 +15,19 @@ */ package git4idea.history; +import consulo.application.util.function.AsynchConsumer; import consulo.ide.impl.idea.openapi.util.io.FileUtil; -import consulo.versionControlSystem.FilePath; import consulo.ide.impl.idea.openapi.vcs.FilePathImpl; -import consulo.versionControlSystem.VcsException; -import consulo.versionControlSystem.history.VcsFileRevision; -import consulo.versionControlSystem.util.VcsUtil; -import consulo.virtualFileSystem.VirtualFile; -import consulo.util.collection.ArrayUtil; -import consulo.application.util.function.AsynchConsumer; import consulo.ide.impl.idea.util.Consumer; import consulo.ide.impl.idea.util.Function; +import consulo.util.collection.ArrayUtil; import consulo.util.lang.Pair; +import consulo.versionControlSystem.FilePath; +import consulo.versionControlSystem.VcsException; import consulo.versionControlSystem.diff.ItemLatestState; +import consulo.versionControlSystem.history.VcsFileRevision; +import consulo.versionControlSystem.util.VcsUtil; +import consulo.virtualFileSystem.VirtualFile; import git4idea.GitFileRevision; import git4idea.GitRevisionNumber; import git4idea.history.browser.GitCommit; @@ -56,451 +56,485 @@ */ public class GitHistoryUtilsTest extends GitTest { - private VirtualFile afile; - private FilePath bfilePath; - private VirtualFile bfile; - private List myRevisions; - private List myRevisionsAfterRename; - - @Before - @Override - public void setUp(Method testMethod) throws Exception { - super.setUp(testMethod); - myRevisions = new ArrayList(7); - myRevisionsAfterRename = new ArrayList(4); - - // 1. create a file - // 2. simple edit with a simple comit message - // 3. move & rename - // 4. make 4 edits with commit message of different complexity - // (note: after rename, because some GitHistoryUtils methods don't follow renames). - - final String[] commitMessages = { - "initial commit", - "simple commit", - "moved a.txt to dir/b.txt", - "simple commit after rename", - "commit with {%n} some [%ct] special characters including \"--pretty=tformat:%x00%x01%x00%H%x00%ct%x00%an%x20%x3C%ae%x3E%x00%cn%x20%x3C%ce%x3E%x00%x02%x00%s%x00%b%x00%x02%x01\"", - "commit subject\n\ncommit body which is \n multilined.", - "first line\nsecond line\nthird line\n\nfifth line\n\nseventh line & the end.", - }; - final String[] contents = { - "initial content", - "second content", - "second content", // content is the same after rename - "fourth content", - "fifth content", - "sixth content", - "seventh content", - }; - - int commitIndex = 0; - afile = myRepo.createVFile("a.txt", contents[commitIndex]); - myRepo.addCommit(commitMessages[commitIndex]); - commitIndex++; - - editFileInCommand(myProject, afile, contents[commitIndex]); - myRepo.addCommit(commitMessages[commitIndex]); - int RENAME_COMMIT_INDEX = commitIndex; - commitIndex++; - - VirtualFile dir = myRepo.createVDir("dir"); - myRepo.mv(afile, "dir/b.txt"); - myRepo.refresh(); - final File bIOFile = new File(dir.getPath(), "b.txt"); - bfilePath = VcsUtil.getFilePath(bIOFile); - bfile = VcsUtil.getVirtualFile(bIOFile); - myRepo.commit(commitMessages[commitIndex]); - commitIndex++; - - for (int i = 0; i < 4; i++) { - editFileInCommand(myProject, bfile, contents[commitIndex]); - myRepo.addCommit(commitMessages[commitIndex]); - commitIndex++; + private VirtualFile afile; + private FilePath bfilePath; + private VirtualFile bfile; + private List myRevisions; + private List myRevisionsAfterRename; + + @Before + @Override + public void setUp(Method testMethod) throws Exception { + super.setUp(testMethod); + myRevisions = new ArrayList(7); + myRevisionsAfterRename = new ArrayList(4); + + // 1. create a file + // 2. simple edit with a simple commit message + // 3. move & rename + // 4. make 4 edits with commit message of different complexity + // (note: after rename, because some GitHistoryUtils methods don't follow renames). + + String[] commitMessages = { + "initial commit", + "simple commit", + "moved a.txt to dir/b.txt", + "simple commit after rename", + "commit with {%n} some [%ct] special characters including \"--pretty=tformat:%x00%x01%x00%H%x00%ct%x00%an%x20%x3C%ae%x3E%x00%cn%x20%x3C%ce%x3E%x00%x02%x00%s%x00%b%x00%x02%x01\"", + "commit subject\n\ncommit body which is \n multilined.", + "first line\nsecond line\nthird line\n\nfifth line\n\nseventh line & the end.", + }; + String[] contents = { + "initial content", + "second content", + "second content", // content is the same after rename + "fourth content", + "fifth content", + "sixth content", + "seventh content", + }; + + int commitIndex = 0; + afile = myRepo.createVFile("a.txt", contents[commitIndex]); + myRepo.addCommit(commitMessages[commitIndex]); + commitIndex++; + + editFileInCommand(myProject, afile, contents[commitIndex]); + myRepo.addCommit(commitMessages[commitIndex]); + int RENAME_COMMIT_INDEX = commitIndex; + commitIndex++; + + VirtualFile dir = myRepo.createVDir("dir"); + myRepo.mv(afile, "dir/b.txt"); + myRepo.refresh(); + File bIOFile = new File(dir.getPath(), "b.txt"); + bfilePath = VcsUtil.getFilePath(bIOFile); + bfile = VcsUtil.getVirtualFile(bIOFile); + myRepo.commit(commitMessages[commitIndex]); + commitIndex++; + + for (int i = 0; i < 4; i++) { + editFileInCommand(myProject, bfile, contents[commitIndex]); + myRepo.addCommit(commitMessages[commitIndex]); + commitIndex++; + } + + // Retrieve hashes and timestamps + String[] revisions = myRepo.log("--pretty=format:%H#%at#%P", "-M").split("\n"); + // newer revisions go first in the log output + for (int i = revisions.length - 1, j = 0; i >= 0; i--, j++) { + String[] details = revisions[j].trim().split("#"); + String[] parents; + if (details.length > 2) { + parents = details[2].split(" "); + } + else { + parents = ArrayUtil.EMPTY_STRING_ARRAY; + } + GitTestRevision revision = new GitTestRevision( + details[0], + details[1], + parents, + commitMessages[i], + MAIN_USER_NAME, + MAIN_USER_EMAIL, + MAIN_USER_NAME, + MAIN_USER_EMAIL, + null, + contents[i] + ); + myRevisions.add(revision); + if (i > RENAME_COMMIT_INDEX) { + myRevisionsAfterRename.add(revision); + } + } + + assertEquals(myRevisionsAfterRename.size(), 5); } - // Retrieve hashes and timestamps - String[] revisions = myRepo.log("--pretty=format:%H#%at#%P", "-M").split("\n"); - // newer revisions go first in the log output - for (int i = revisions.length - 1, j = 0; i >= 0; i--, j++) { - String[] details = revisions[j].trim().split("#"); - String[] parents; - if (details.length > 2) { - parents = details[2].split(" "); - } else { - parents = ArrayUtil.EMPTY_STRING_ARRAY; - } - final GitTestRevision revision = new GitTestRevision(details[0], details[1], parents, commitMessages[i], - MAIN_USER_NAME, MAIN_USER_EMAIL, MAIN_USER_NAME, MAIN_USER_EMAIL, null, - contents[i]); - myRevisions.add(revision); - if (i > RENAME_COMMIT_INDEX) { - myRevisionsAfterRename.add(revision); - } + // Inspired by IDEA-89347 + @Test + void testCyclicRename() throws Exception { + List commits = new ArrayList<>(); + + File source = myRepo.createDir("source"); + File initialFile = createFile(source, "PostHighlightingPass.java", "Initial content"); + String initMessage = "Created PostHighlightingPass.java in source"; + String hash = myRepo.addCommit(initMessage); + commits.add(new TestCommit(hash, initMessage, initialFile.getPath())); + + String filePath = initialFile.getPath(); + + commits.add(modify(filePath)); + + TestCommit commit = move(filePath, myRepo.createDir("codeInside-impl"), "Moved from source to codeInside-impl"); + filePath = commit.myPath; + commits.add(commit); + commits.add(modify(filePath)); + + commit = move(filePath, myRepo.createDir("codeInside"), "Moved from codeInside-impl to codeInside"); + filePath = commit.myPath; + commits.add(commit); + commits.add(modify(filePath)); + + commit = move(filePath, myRepo.createDir("lang-impl"), "Moved from codeInside to lang-impl"); + filePath = commit.myPath; + commits.add(commit); + commits.add(modify(filePath)); + + commit = move(filePath, source, "Moved from lang-impl back to source"); + filePath = commit.myPath; + commits.add(commit); + commits.add(modify(filePath)); + + commit = move(filePath, myRepo.createDir("java"), "Moved from source to java"); + filePath = commit.myPath; + commits.add(commit); + commits.add(modify(filePath)); + + Collections.reverse(commits); + myRepo.refresh(); + VirtualFile vFile = VcsUtil.getVirtualFile(filePath); + assertNotNull(vFile); + List history = GitHistoryUtils.history(myProject, new consulo.ide.impl.idea.openapi.vcs.FilePathImpl(vFile)); + assertEquals(history.size(), commits.size(), "History size doesn't match. Actual history: \n" + toReadable(history)); + assertEquals(toReadable(history), toReadable(commits), "History is different."); } - assertEquals(myRevisionsAfterRename.size(), 5); - } - - // Inspired by IDEA-89347 - @Test - void testCyclicRename() throws Exception { - List commits = new ArrayList(); - - File source = myRepo.createDir("source"); - File initialFile = createFile(source, "PostHighlightingPass.java", "Initial content"); - String initMessage = "Created PostHighlightingPass.java in source"; - String hash = myRepo.addCommit(initMessage); - commits.add(new TestCommit(hash, initMessage, initialFile.getPath())); - - String filePath = initialFile.getPath(); - - commits.add(modify(filePath)); - - TestCommit commit = move(filePath, myRepo.createDir("codeInside-impl"), "Moved from source to codeInside-impl"); - filePath = commit.myPath; - commits.add(commit); - commits.add(modify(filePath)); - - commit = move(filePath, myRepo.createDir("codeInside"), "Moved from codeInside-impl to codeInside"); - filePath = commit.myPath; - commits.add(commit); - commits.add(modify(filePath)); - - commit = move(filePath, myRepo.createDir("lang-impl"), "Moved from codeInside to lang-impl"); - filePath = commit.myPath; - commits.add(commit); - commits.add(modify(filePath)); - - commit = move(filePath, source, "Moved from lang-impl back to source"); - filePath = commit.myPath; - commits.add(commit); - commits.add(modify(filePath)); - - commit = move(filePath, myRepo.createDir("java"), "Moved from source to java"); - filePath = commit.myPath; - commits.add(commit); - commits.add(modify(filePath)); - - Collections.reverse(commits); - myRepo.refresh(); - VirtualFile vFile = VcsUtil.getVirtualFile(filePath); - assertNotNull(vFile); - List history = GitHistoryUtils.history(myProject, new consulo.ide.impl.idea.openapi.vcs.FilePathImpl(vFile)); - assertEquals(history.size(), commits.size(), "History size doesn't match. Actual history: \n" + toReadable(history)); - assertEquals(toReadable(history), toReadable(commits), "History is different."); - } - - private static class TestCommit { - private final String myHash; - private final String myMessage; - private final String myPath; - - public TestCommit(String hash, String message, String path) { - myHash = hash; - myMessage = message; - myPath = path; + private static class TestCommit { + private final String myHash; + private final String myMessage; + private final String myPath; + + public TestCommit(String hash, String message, String path) { + myHash = hash; + myMessage = message; + myPath = path; + } + + public String getHash() { + return myHash; + } + + public String getCommitMessage() { + return myMessage; + } } - public String getHash() { - return myHash; + private TestCommit move(String file, File dir, String message) throws Exception { + String NAME = "PostHighlightingPass.java"; + myRepo.mv(file, dir.getPath()); + file = new File(dir, NAME).getPath(); + String hash = myRepo.addCommit(message); + return new TestCommit(hash, message, file); } - public String getCommitMessage() { - return myMessage; + private TestCommit modify(String file) throws IOException { + editAppend(file, "Modified"); + String message = "Modified PostHighlightingPass"; + String hash = myRepo.addCommit(message); + return new TestCommit(hash, message, file); } - } - - private TestCommit move(String file, File dir, String message) throws Exception { - final String NAME = "PostHighlightingPass.java"; - myRepo.mv(file, dir.getPath()); - file = new File(dir, NAME).getPath(); - String hash = myRepo.addCommit(message); - return new TestCommit(hash, message, file); - } - - private TestCommit modify(String file) throws IOException { - editAppend(file, "Modified"); - String message = "Modified PostHighlightingPass"; - String hash = myRepo.addCommit(message); - return new TestCommit(hash, message, file); - } - - private static void editAppend(String file, String content) throws IOException { - FileUtil.appendToFile(new File(file), content); - } - - @Nonnull - private String toReadable(@Nonnull Collection history) { - int maxSubjectLength = findMaxLength(history, new consulo.ide.impl.idea.util.Function() { - @Override - public String fun(VcsFileRevision revision) { - return revision.getCommitMessage(); - } - }); - StringBuilder sb = new StringBuilder(); - for (VcsFileRevision revision : history) { - GitFileRevision rev = (GitFileRevision)revision; - String relPath = FileUtil.getRelativePath(myRepo.getRootDir().getPath(), rev.getPath().getPath(), '/'); - sb.append(String.format("%s %-" + maxSubjectLength + "s %s%n", getShortHash(rev.getHash()), rev.getCommitMessage(), relPath)); + + private static void editAppend(String file, String content) throws IOException { + FileUtil.appendToFile(new File(file), content); } - return sb.toString(); - } - - private String toReadable(List commits) { - int maxSubjectLength = findMaxLength(commits, new consulo.ide.impl.idea.util.Function() { - @Override - public String fun(TestCommit revision) { - return revision.getCommitMessage(); - } - }); - StringBuilder sb = new StringBuilder(); - for (TestCommit commit : commits) { - String relPath = FileUtil.getRelativePath(myRepo.getRootDir().getPath(), commit.myPath, '/'); - sb.append(String.format("%s %-" + maxSubjectLength + "s %s%n", getShortHash(commit.getHash()), commit.getCommitMessage(), relPath)); + + @Nonnull + private String toReadable(@Nonnull Collection history) { + int maxSubjectLength = findMaxLength(history, new consulo.ide.impl.idea.util.Function() { + @Override + public String fun(VcsFileRevision revision) { + return revision.getCommitMessage(); + } + }); + StringBuilder sb = new StringBuilder(); + for (VcsFileRevision revision : history) { + GitFileRevision rev = (GitFileRevision) revision; + String relPath = FileUtil.getRelativePath(myRepo.getRootDir().getPath(), rev.getPath().getPath(), '/'); + sb.append(String.format("%s %-" + maxSubjectLength + "s %s%n", getShortHash(rev.getHash()), rev.getCommitMessage(), relPath)); + } + return sb.toString(); } - return sb.toString(); - } - - private static int findMaxLength(@Nonnull Collection list, @Nonnull Function convertor) { - int max = 0; - for (T element : list) { - int length = convertor.fun(element).length(); - if (length > max) { - max = length; - } + + private String toReadable(List commits) { + int maxSubjectLength = findMaxLength(commits, new consulo.ide.impl.idea.util.Function() { + @Override + public String fun(TestCommit revision) { + return revision.getCommitMessage(); + } + }); + StringBuilder sb = new StringBuilder(); + for (TestCommit commit : commits) { + String relPath = FileUtil.getRelativePath(myRepo.getRootDir().getPath(), commit.myPath, '/'); + sb.append(String.format( + "%s %-" + maxSubjectLength + "s %s%n", + getShortHash(commit.getHash()), + commit.getCommitMessage(), + relPath + )); + } + return sb.toString(); } - return max; - } - - @Test - public void testGetCurrentRevision() throws Exception { - GitRevisionNumber revisionNumber = (GitRevisionNumber) GitHistoryUtils.getCurrentRevision(myProject, bfilePath, null); - assertEquals(revisionNumber.getRev(), myRevisions.get(0).myHash); - assertEquals(revisionNumber.getTimestamp(), myRevisions.get(0).myDate); - } - - @Test - public void testGetCurrentRevisionInMasterBranch() throws Exception { - GitRevisionNumber revisionNumber = (GitRevisionNumber) GitHistoryUtils.getCurrentRevision(myProject, bfilePath, "master"); - assertEquals(revisionNumber.getRev(), myRevisions.get(0).myHash); - assertEquals(revisionNumber.getTimestamp(), myRevisions.get(0).myDate); - } - - @Test - public void testGetCurrentRevisionInOtherBranch() throws Exception { - myRepo.checkout("feature"); - editFileInCommand(myProject, bfile, "new content"); - myRepo.addCommit(); - final String[] output = myRepo.log("--pretty=%H#%at", "-n1").trim().split("#"); - - GitRevisionNumber revisionNumber = (GitRevisionNumber) GitHistoryUtils.getCurrentRevision(myProject, bfilePath, "master"); - assertEquals(revisionNumber.getRev(), output[0]); - assertEquals(revisionNumber.getTimestamp(), GitTestRevision.gitTimeStampToDate(output[1])); - } - - @Test(enabled = false) - public void testGetLastRevisionForExistingFile() throws Exception { - final ItemLatestState state = GitHistoryUtils.getLastRevision(myProject, bfilePath); - assertTrue(state.isItemExists()); - final GitRevisionNumber revisionNumber = (GitRevisionNumber) state.getNumber(); - assertEquals(revisionNumber.getRev(), myRevisions.get(0).myHash); - assertEquals(revisionNumber.getTimestamp(), myRevisions.get(0).myDate); - } - - // TODO: need to configure a remote branch to run this test - @Test(enabled = false) - public void testGetLastRevisionForNonExistingFile() throws Exception { - myRepo.config("branch.master.remote", "origin"); - myRepo.config("branch.master.merge", "refs/heads/master"); - myRepo.rm(bfilePath.getPath()); - myRepo.commit(); - VirtualFile dir = myRepo.createVDir("dir"); - createFileInCommand(dir, "b.txt", "content"); - String[] hashAndData = myRepo.log("--pretty=format:%H#%ct", "-n1").split("#"); - - final ItemLatestState state = GitHistoryUtils.getLastRevision(myProject, bfilePath); - assertTrue(!state.isItemExists()); - final GitRevisionNumber revisionNumber = (GitRevisionNumber) state.getNumber(); - assertEquals(revisionNumber.getRev(), hashAndData[0]); - assertEquals(revisionNumber.getTimestamp(), GitTestRevision.gitTimeStampToDate(hashAndData[1])); - } - - @Test - public void testHistory() throws Exception { - List revisions = GitHistoryUtils.history(myProject, bfilePath); - assertEquals(revisions.size(), myRevisions.size()); - for (int i = 0; i < revisions.size(); i++) { - assertEqualRevisions((GitFileRevision) revisions.get(i), myRevisions.get(i)); + + private static int findMaxLength(@Nonnull Collection list, @Nonnull Function convertor) { + int max = 0; + for (T element : list) { + int length = convertor.fun(element).length(); + if (length > max) { + max = length; + } + } + return max; } - } - - @Test - public void testAppendableHistory() throws Exception { - final List revisions = new ArrayList(3); - consulo.ide.impl.idea.util.Consumer consumer = new consulo.ide.impl.idea.util.Consumer() { - @Override - public void consume(GitFileRevision gitFileRevision) { - revisions.add(gitFileRevision); - } - }; - consulo.ide.impl.idea.util.Consumer exceptionConsumer = new consulo.ide.impl.idea.util.Consumer() { - @Override - public void consume(VcsException exception) { - fail("No exception expected", exception); - } - }; - GitHistoryUtils.history(myProject, bfilePath, null, consumer, exceptionConsumer); - assertEquals(revisions.size(), myRevisions.size()); - for (int i = 0; i < revisions.size(); i++) { - assertEqualRevisions(revisions.get(i), myRevisions.get(i)); + + @Test + public void testGetCurrentRevision() throws Exception { + GitRevisionNumber revisionNumber = (GitRevisionNumber) GitHistoryUtils.getCurrentRevision(myProject, bfilePath, null); + assertEquals(revisionNumber.getRev(), myRevisions.get(0).myHash); + assertEquals(revisionNumber.getTimestamp(), myRevisions.get(0).myDate); } - } - - @Test - public void testOnlyHashesHistory() throws Exception { - final List> history = GitHistoryUtils.onlyHashesHistory(myProject, bfilePath, myRepo.getVFRootDir()); - assertEquals(history.size(), myRevisionsAfterRename.size()); - for (Iterator hit = history.iterator(), myIt = myRevisionsAfterRename.iterator(); hit.hasNext(); ) { - Pair pair = (Pair) hit.next(); - GitTestRevision revision = (GitTestRevision)myIt.next(); - assertEquals(pair.first.toString(), revision.myHash); - assertEquals(pair.second, revision.myDate); + + @Test + public void testGetCurrentRevisionInMasterBranch() throws Exception { + GitRevisionNumber revisionNumber = (GitRevisionNumber) GitHistoryUtils.getCurrentRevision(myProject, bfilePath, "master"); + assertEquals(revisionNumber.getRev(), myRevisions.get(0).myHash); + assertEquals(revisionNumber.getTimestamp(), myRevisions.get(0).myDate); } - } - - @Test - public void testHistoryWithLinks() throws Exception { - /*List commits = GitHistoryUtils.historyWithLinks(myProject, bfilePath, Collections.emptySet(), null); - assertEquals(commits.size(), myRevisionsAfterRename.size()); - for (Iterator hit = commits.iterator(), myIt = myRevisionsAfterRename.iterator(); hit.hasNext(); ) { - GitCommit commit = (GitCommit)hit.next(); - GitTestRevision revision = (GitTestRevision)myIt.next(); - assertCommitEqualToTestRevision(commit, revision); - }*/ - } - /*@Test - public void testCommitsDetails() throws Exception { - List ids = new ArrayList(myRevisionsAfterRename.size()); - for (GitTestRevision rev : myRevisionsAfterRename) { - ids.add(rev.myHash); + @Test + public void testGetCurrentRevisionInOtherBranch() throws Exception { + myRepo.checkout("feature"); + editFileInCommand(myProject, bfile, "new content"); + myRepo.addCommit(); + String[] output = myRepo.log("--pretty=%H#%at", "-n1").trim().split("#"); + + GitRevisionNumber revisionNumber = (GitRevisionNumber) GitHistoryUtils.getCurrentRevision(myProject, bfilePath, "master"); + assertEquals(revisionNumber.getRev(), output[0]); + assertEquals(revisionNumber.getTimestamp(), GitTestRevision.gitTimeStampToDate(output[1])); } - final List gitCommits = GitHistoryUtils.commitsDetails(myProject, bfilePath, Collections.emptySet(), ids); - assertCommitsEqualToTestRevisions(gitCommits, myRevisionsAfterRename); - }*/ - - @Test(enabled = false) - public void testHashesWithParents() throws Exception { - final int expectedSize = myRevisionsAfterRename.size(); - - final List hashesWithParents = new ArrayList(3); - AsynchConsumer consumer = new AsynchConsumer() { - @Override - public void consume(CommitHashPlusParents gitFileRevision) { - hashesWithParents.add(gitFileRevision); - } - - @Override - public void finished() { - } - }; - - GitHistoryUtils.hashesWithParents(myProject, bfilePath, consumer, null, null); - - assertEquals(hashesWithParents.size(), expectedSize); - for (Iterator hit = hashesWithParents.iterator(), myIt = myRevisionsAfterRename.iterator(); hit.hasNext(); ) { - CommitHashPlusParents chpp = (CommitHashPlusParents)hit.next(); - GitTestRevision rev = (GitTestRevision)myIt.next(); - assertEquals(chpp.getHash(), rev.myHash); - final List parents = chpp.getParents(); - final ArrayList list = new ArrayList(); - for (AbstractHash parent : parents) { - list.add(parent.getString()); - } - assertEqualHashes(list, Arrays.asList(rev.myParents)); + + @Test(enabled = false) + public void testGetLastRevisionForExistingFile() throws Exception { + ItemLatestState state = GitHistoryUtils.getLastRevision(myProject, bfilePath); + assertTrue(state.isItemExists()); + GitRevisionNumber revisionNumber = (GitRevisionNumber) state.getNumber(); + assertEquals(revisionNumber.getRev(), myRevisions.get(0).myHash); + assertEquals(revisionNumber.getTimestamp(), myRevisions.get(0).myDate); } - } - - private static void assertEqualRevisions(GitFileRevision actual, GitTestRevision expected) throws IOException { - assertEquals(((GitRevisionNumber) actual.getRevisionNumber()).getRev(), expected.myHash); - assertEquals(((GitRevisionNumber) actual.getRevisionNumber()).getTimestamp(), expected.myDate); - // TODO: whitespaces problem is known, remove convertWhitespaces... when it's fixed - assertEquals(convertWhitespacesToSpacesAndRemoveDoubles(actual.getCommitMessage()), convertWhitespacesToSpacesAndRemoveDoubles(expected.myCommitMessage)); - assertEquals(actual.getAuthor(), expected.myAuthorName); - assertEquals(actual.getBranchName(), expected.myBranchName); - try { - assertEquals(actual.getContent(), expected.myContent); + + // TODO: need to configure a remote branch to run this test + @Test(enabled = false) + public void testGetLastRevisionForNonExistingFile() throws Exception { + myRepo.config("branch.master.remote", "origin"); + myRepo.config("branch.master.merge", "refs/heads/master"); + myRepo.rm(bfilePath.getPath()); + myRepo.commit(); + VirtualFile dir = myRepo.createVDir("dir"); + createFileInCommand(dir, "b.txt", "content"); + String[] hashAndData = myRepo.log("--pretty=format:%H#%ct", "-n1").split("#"); + + ItemLatestState state = GitHistoryUtils.getLastRevision(myProject, bfilePath); + assertTrue(!state.isItemExists()); + GitRevisionNumber revisionNumber = (GitRevisionNumber) state.getNumber(); + assertEquals(revisionNumber.getRev(), hashAndData[0]); + assertEquals(revisionNumber.getTimestamp(), GitTestRevision.gitTimeStampToDate(hashAndData[1])); } - catch (VcsException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + + @Test + public void testHistory() throws Exception { + List revisions = GitHistoryUtils.history(myProject, bfilePath); + assertEquals(revisions.size(), myRevisions.size()); + for (int i = 0; i < revisions.size(); i++) { + assertEqualRevisions((GitFileRevision) revisions.get(i), myRevisions.get(i)); + } } - } - - private static void assertCommitEqualToTestRevision(GitCommit commit, GitTestRevision expected) throws IOException { - assertEquals(commit.getHash().toString(), expected.myHash); - assertEquals(commit.getAuthor(), expected.myAuthorName); - assertEquals(commit.getAuthorEmail(), expected.myAuthorEmail); - assertEquals(commit.getCommitter(), expected.myCommitterName); - assertEquals(commit.getCommitterEmail(), expected.myCommitterEmail); - assertEquals(commit.getDate(), expected.myDate); - assertEquals(convertWhitespacesToSpacesAndRemoveDoubles(commit.getDescription()), convertWhitespacesToSpacesAndRemoveDoubles(expected.myCommitMessage)); - assertEqualHashes(commit.getParentsHashes(), Arrays.asList(expected.myParents)); - } - - private static void assertEqualHashes(Collection actualParents, Collection expectedParents) { - assertEquals(actualParents.size(), expectedParents.size()); - for (Iterator ait = actualParents.iterator(), eit = expectedParents.iterator(); ait.hasNext(); ) { - assertTrue(eit.next().startsWith(ait.next())); + + @Test + public void testAppendableHistory() throws Exception { + final List revisions = new ArrayList(3); + consulo.ide.impl.idea.util.Consumer consumer = new consulo.ide.impl.idea.util.Consumer() { + @Override + public void consume(GitFileRevision gitFileRevision) { + revisions.add(gitFileRevision); + } + }; + consulo.ide.impl.idea.util.Consumer exceptionConsumer = new consulo.ide.impl.idea.util.Consumer() { + @Override + public void consume(VcsException exception) { + fail("No exception expected", exception); + } + }; + GitHistoryUtils.history(myProject, bfilePath, null, consumer, exceptionConsumer); + assertEquals(revisions.size(), myRevisions.size()); + for (int i = 0; i < revisions.size(); i++) { + assertEqualRevisions(revisions.get(i), myRevisions.get(i)); + } } - } - - private static void assertCommitsEqualToTestRevisions(Collection actualCommits, Collection expectedRevisions) throws IOException { - assertEquals(actualCommits.size(), expectedRevisions.size()); - for (Iterator hit = actualCommits.iterator(), myIt = expectedRevisions.iterator(); hit.hasNext(); ) { - GitCommit commit = (GitCommit)hit.next(); - GitTestRevision revision = (GitTestRevision)myIt.next(); - assertCommitEqualToTestRevision(commit, revision); + + @Test + public void testOnlyHashesHistory() throws Exception { + List> history = GitHistoryUtils.onlyHashesHistory(myProject, bfilePath, myRepo.getVFRootDir()); + assertEquals(history.size(), myRevisionsAfterRename.size()); + for (Iterator hit = history.iterator(), myIt = myRevisionsAfterRename.iterator(); hit.hasNext(); ) { + Pair pair = (Pair) hit.next(); + GitTestRevision revision = (GitTestRevision) myIt.next(); + assertEquals(pair.first.toString(), revision.myHash); + assertEquals(pair.second, revision.myDate); + } } - } - - private static String convertWhitespacesToSpacesAndRemoveDoubles(String s) { - return s.replaceAll("[\\s^ ]", " ").replaceAll(" +", " "); - } - - private static class GitTestRevision { - final String myHash; - final Date myDate; - final String myCommitMessage; - final String myAuthorName; - final String myAuthorEmail; - final String myCommitterName; - final String myCommitterEmail; - final String myBranchName; - final byte[] myContent; - private String[] myParents; - - public GitTestRevision(String hash, String gitTimestamp, String[] parents, String commitMessage, String authorName, String authorEmail, String committerName, String committerEmail, String branch, String content) { - myHash = hash; - myDate = gitTimeStampToDate(gitTimestamp); - myParents = parents; - myCommitMessage = commitMessage; - myAuthorName = authorName; - myAuthorEmail = authorEmail; - myCommitterName = committerName; - myCommitterEmail = committerEmail; - myBranchName = branch; - myContent = content.getBytes(); + + @Test + public void testHistoryWithLinks() throws Exception { + /*List commits = GitHistoryUtils.historyWithLinks(myProject, bfilePath, Collections.emptySet(), null); + assertEquals(commits.size(), myRevisionsAfterRename.size()); + for (Iterator hit = commits.iterator(), myIt = myRevisionsAfterRename.iterator(); hit.hasNext(); ) { + GitCommit commit = (GitCommit)hit.next(); + GitTestRevision revision = (GitTestRevision)myIt.next(); + assertCommitEqualToTestRevision(commit, revision); + }*/ } - @Override - public String toString() { - return myHash; + /*@Test + public void testCommitsDetails() throws Exception { + List ids = new ArrayList(myRevisionsAfterRename.size()); + for (GitTestRevision rev : myRevisionsAfterRename) { + ids.add(rev.myHash); + } + final List gitCommits = GitHistoryUtils.commitsDetails(myProject, bfilePath, Collections.emptySet(), ids); + assertCommitsEqualToTestRevisions(gitCommits, myRevisionsAfterRename); + }*/ + + @Test(enabled = false) + public void testHashesWithParents() throws Exception { + int expectedSize = myRevisionsAfterRename.size(); + + final List hashesWithParents = new ArrayList(3); + AsynchConsumer consumer = new AsynchConsumer() { + @Override + public void consume(CommitHashPlusParents gitFileRevision) { + hashesWithParents.add(gitFileRevision); + } + + @Override + public void finished() { + } + }; + + GitHistoryUtils.hashesWithParents(myProject, bfilePath, consumer, null, null); + + assertEquals(hashesWithParents.size(), expectedSize); + for (Iterator hit = hashesWithParents.iterator(), myIt = myRevisionsAfterRename.iterator(); hit.hasNext(); ) { + CommitHashPlusParents chpp = (CommitHashPlusParents) hit.next(); + GitTestRevision rev = (GitTestRevision) myIt.next(); + assertEquals(chpp.getHash(), rev.myHash); + List parents = chpp.getParents(); + List list = new ArrayList<>(); + for (AbstractHash parent : parents) { + list.add(parent.getString()); + } + assertEqualHashes(list, Arrays.asList(rev.myParents)); + } + } + + private static void assertEqualRevisions(GitFileRevision actual, GitTestRevision expected) throws IOException { + assertEquals(((GitRevisionNumber) actual.getRevisionNumber()).getRev(), expected.myHash); + assertEquals(((GitRevisionNumber) actual.getRevisionNumber()).getTimestamp(), expected.myDate); + // TODO: whitespaces problem is known, remove convertWhitespaces... when it's fixed + assertEquals( + convertWhitespacesToSpacesAndRemoveDoubles(actual.getCommitMessage()), + convertWhitespacesToSpacesAndRemoveDoubles(expected.myCommitMessage) + ); + assertEquals(actual.getAuthor(), expected.myAuthorName); + assertEquals(actual.getBranchName(), expected.myBranchName); + try { + assertEquals(actual.getContent(), expected.myContent); + } + catch (VcsException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + + private static void assertCommitEqualToTestRevision(GitCommit commit, GitTestRevision expected) throws IOException { + assertEquals(commit.getHash().toString(), expected.myHash); + assertEquals(commit.getAuthor(), expected.myAuthorName); + assertEquals(commit.getAuthorEmail(), expected.myAuthorEmail); + assertEquals(commit.getCommitter(), expected.myCommitterName); + assertEquals(commit.getCommitterEmail(), expected.myCommitterEmail); + assertEquals(commit.getDate(), expected.myDate); + assertEquals( + convertWhitespacesToSpacesAndRemoveDoubles(commit.getDescription()), + convertWhitespacesToSpacesAndRemoveDoubles(expected.myCommitMessage) + ); + assertEqualHashes(commit.getParentsHashes(), Arrays.asList(expected.myParents)); + } + + private static void assertEqualHashes(Collection actualParents, Collection expectedParents) { + assertEquals(actualParents.size(), expectedParents.size()); + for (Iterator ait = actualParents.iterator(), eit = expectedParents.iterator(); ait.hasNext(); ) { + assertTrue(eit.next().startsWith(ait.next())); + } + } + + private static void assertCommitsEqualToTestRevisions( + Collection actualCommits, + Collection expectedRevisions + ) throws IOException { + assertEquals(actualCommits.size(), expectedRevisions.size()); + for (Iterator hit = actualCommits.iterator(), myIt = expectedRevisions.iterator(); hit.hasNext(); ) { + GitCommit commit = (GitCommit) hit.next(); + GitTestRevision revision = (GitTestRevision) myIt.next(); + assertCommitEqualToTestRevision(commit, revision); + } + } + + private static String convertWhitespacesToSpacesAndRemoveDoubles(String s) { + return s.replaceAll("[\\s^ ]", " ").replaceAll(" +", " "); } - public static Date gitTimeStampToDate(String gitTimestamp) { - return new Date(Long.parseLong(gitTimestamp)*1000); + private static class GitTestRevision { + final String myHash; + final Date myDate; + final String myCommitMessage; + final String myAuthorName; + final String myAuthorEmail; + final String myCommitterName; + final String myCommitterEmail; + final String myBranchName; + final byte[] myContent; + private String[] myParents; + + public GitTestRevision( + String hash, + String gitTimestamp, + String[] parents, + String commitMessage, + String authorName, + String authorEmail, + String committerName, + String committerEmail, + String branch, + String content + ) { + myHash = hash; + myDate = gitTimeStampToDate(gitTimestamp); + myParents = parents; + myCommitMessage = commitMessage; + myAuthorName = authorName; + myAuthorEmail = authorEmail; + myCommitterName = committerName; + myCommitterEmail = committerEmail; + myBranchName = branch; + myContent = content.getBytes(); + } + + @Override + public String toString() { + return myHash; + } + + public static Date gitTimeStampToDate(String gitTimestamp) { + return new Date(Long.parseLong(gitTimestamp) * 1000); + } } - } - } diff --git a/plugin/src/test1/java/git4idea/repo/GitConfigTest.java b/plugin/src/test1/java/git4idea/repo/GitConfigTest.java index 6d0d9c0..9c7e77b 100644 --- a/plugin/src/test1/java/git4idea/repo/GitConfigTest.java +++ b/plugin/src/test1/java/git4idea/repo/GitConfigTest.java @@ -65,14 +65,14 @@ public void testBranches(String testName, File configFile, File resultFile) thro @Override public GitLocalBranch apply(@Nullable GitBranchTrackInfo input) { assert input != null; - return input.getLocalBranch(); + return input.localBranch(); } }); Collection remoteBranches = Collections2.transform(expectedInfos, new Function() { @Override public GitRemoteBranch apply(@Nullable GitBranchTrackInfo input) { assert input != null; - return input.getRemoteBranch(); + return input.remoteBranch(); } }); From 88d927d133d20269b0d98f7f401594403a109178 Mon Sep 17 00:00:00 2001 From: UNV Date: Thu, 20 Nov 2025 19:15:17 +0300 Subject: [PATCH 2/5] Reverting simplification of GitVersion.equals(). --- plugin/src/main/java/git4idea/config/GitVersion.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugin/src/main/java/git4idea/config/GitVersion.java b/plugin/src/main/java/git4idea/config/GitVersion.java index 4131302..730fd27 100644 --- a/plugin/src/main/java/git4idea/config/GitVersion.java +++ b/plugin/src/main/java/git4idea/config/GitVersion.java @@ -196,11 +196,15 @@ public boolean isSupported() { * Types are considered equal also if one of them is undefined. Otherwise they are compared. */ @Override + @SuppressWarnings("SimplifiableIfStatement") public boolean equals(Object obj) { - return obj == this - || obj instanceof GitVersion other - && compareTo(other) == 0 - && (myType == Type.UNDEFINED || other.myType == Type.UNDEFINED || myType == other.myType); + if (!(obj instanceof GitVersion that)) { + return false; + } + if (compareTo(that) != 0) { + return false; + } + return myType == Type.UNDEFINED || that.myType == Type.UNDEFINED || myType == that.myType; } /** From b9e8b7a24df7d4d33acbb8526e4e3e454c27dcd4 Mon Sep 17 00:00:00 2001 From: UNV Date: Thu, 20 Nov 2025 19:40:38 +0300 Subject: [PATCH 3/5] GitRepoInfo.equals() formatting. --- plugin/src/main/java/git4idea/repo/GitRepoInfo.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/git4idea/repo/GitRepoInfo.java b/plugin/src/main/java/git4idea/repo/GitRepoInfo.java index 3879752..6848bd0 100644 --- a/plugin/src/main/java/git4idea/repo/GitRepoInfo.java +++ b/plugin/src/main/java/git4idea/repo/GitRepoInfo.java @@ -79,8 +79,8 @@ public int hashCode(@Nonnull Map.Entry branchEntry) { public boolean equals(@Nonnull Map.Entry b1, @Nonnull Map.Entry b2) { return b1 == b2 || b1.getClass() == b2.getClass() - && b1.getKey().getName().equals(b2.getKey().getName()) - && b1.getValue().equals(b2.getValue()); + && b1.getKey().getName().equals(b2.getKey().getName()) + && b1.getValue().equals(b2.getValue()); } } } From 3e3ef9370cca8575982de07b3fd061c38d9551b5 Mon Sep 17 00:00:00 2001 From: UNV Date: Thu, 20 Nov 2025 19:56:25 +0300 Subject: [PATCH 4/5] GitRepoInfo.equals() formatting (take 2). --- plugin/src/main/java/git4idea/repo/GitRepoInfo.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugin/src/main/java/git4idea/repo/GitRepoInfo.java b/plugin/src/main/java/git4idea/repo/GitRepoInfo.java index 6848bd0..f5510f2 100644 --- a/plugin/src/main/java/git4idea/repo/GitRepoInfo.java +++ b/plugin/src/main/java/git4idea/repo/GitRepoInfo.java @@ -77,10 +77,14 @@ public int hashCode(@Nonnull Map.Entry branchEntry) { @Override public boolean equals(@Nonnull Map.Entry b1, @Nonnull Map.Entry b2) { - return b1 == b2 - || b1.getClass() == b2.getClass() - && b1.getKey().getName().equals(b2.getKey().getName()) - && b1.getValue().equals(b2.getValue()); + //noinspection SimplifiableIfStatement + if (b1 == b2) { + return true; + } + + return b1.getClass() == b2.getClass() + && b1.getKey().getName().equals(b2.getKey().getName()) + && b1.getValue().equals(b2.getValue()); } } } From 6a3b873920e59bace5abfdd4e9c134df3ae17b66 Mon Sep 17 00:00:00 2001 From: UNV Date: Thu, 20 Nov 2025 20:03:23 +0300 Subject: [PATCH 5/5] GitRepoInfo.equals() formatting (take 3). --- plugin/src/main/java/git4idea/repo/GitRepoInfo.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/git4idea/repo/GitRepoInfo.java b/plugin/src/main/java/git4idea/repo/GitRepoInfo.java index f5510f2..322bf4b 100644 --- a/plugin/src/main/java/git4idea/repo/GitRepoInfo.java +++ b/plugin/src/main/java/git4idea/repo/GitRepoInfo.java @@ -48,8 +48,11 @@ public GitLocalBranch getCurrentBranch() { @Override public boolean equals(Object o) { - return this == o - || o instanceof GitRepoInfo info + //noinspection SimplifiableIfStatement + if (this == o) { + return true; + } + return o instanceof GitRepoInfo info && state == info.state && Objects.equals(currentRevision, info.currentRevision) && Objects.equals(currentBranch, info.currentBranch)