diff --git a/plugin/src/main/java/git4idea/GitUtil.java b/plugin/src/main/java/git4idea/GitUtil.java index 18ef970..71f5a30 100644 --- a/plugin/src/main/java/git4idea/GitUtil.java +++ b/plugin/src/main/java/git4idea/GitUtil.java @@ -20,6 +20,7 @@ import consulo.git.localize.GitLocalize; import consulo.logging.Logger; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.ui.ex.awt.DialogBuilder; import consulo.ui.ex.awt.MultiLineLabel; import consulo.ui.ex.awt.UIUtil; @@ -202,7 +203,7 @@ private static String readContent(@Nonnull VirtualFile dotGit) { */ @Nonnull public static String readFile(@Nonnull VirtualFile file) throws IOException { - final int ATTEMPTS = 3; + int ATTEMPTS = 3; for (int attempt = 0; attempt < ATTEMPTS; attempt++) { try { return new String(file.contentsToByteArray()); @@ -244,7 +245,7 @@ public static Map> sortFilesByGitRoot( Map> result = new HashMap<>(); for (VirtualFile file : virtualFiles) { // directory is reported only when it is a submodule => it should be treated in the context of super-root - final VirtualFile vcsRoot = gitRootOrNull(file.isDirectory() ? file.getParent() : file); + VirtualFile vcsRoot = gitRootOrNull(file.isDirectory() ? file.getParent() : file); if (vcsRoot == null) { if (ignoreNonGit) { continue; @@ -270,7 +271,7 @@ public static Map> sortFilesByGitRoot( * @return the map from root to the files under the root * @throws VcsException if non git files are passed */ - public static Map> sortFilePathsByGitRoot(final Collection files) throws VcsException { + public static Map> sortFilePathsByGitRoot(Collection files) throws VcsException { return sortFilePathsByGitRoot(files, false); } @@ -315,7 +316,7 @@ public static Map> sortFilePathsByGitRoot( * @return timestamp as {@link Date} object */ public static Date parseTimestamp(String value) { - final long parsed; + long parsed; parsed = Long.parseLong(value.trim()); return new Date(parsed * 1000); } @@ -346,7 +347,7 @@ public static Date parseTimestampWithNFEReport(String value, GitHandler handler, * @param roots git content roots * @return a content root */ - public static Set gitRootsForPaths(final Collection roots) { + public static Set gitRootsForPaths(Collection roots) { HashSet rc = new HashSet<>(); for (VirtualFile root : roots) { VirtualFile f = root; @@ -390,7 +391,7 @@ public static VirtualFile getGitRoot(@Nonnull FilePath filePath) throws VcsExcep */ @Deprecated @Nullable - public static VirtualFile getGitRootOrNull(@Nonnull final FilePath filePath) { + public static VirtualFile getGitRootOrNull(@Nonnull FilePath filePath) { return getGitRootOrNull(filePath.getIOFile()); } @@ -404,7 +405,7 @@ public static boolean isGitRoot(@Nonnull File folder) { */ @Deprecated @Nullable - public static VirtualFile getGitRootOrNull(final File file) { + public static VirtualFile getGitRootOrNull(File file) { File root = file; while (root != null && (!root.exists() || !root.isDirectory() || !new File(root, DOT_GIT).exists())) { root = root.getParentFile(); @@ -421,8 +422,8 @@ public static VirtualFile getGitRootOrNull(final File file) { * @use GitRepositoryManager#getRepositoryForFile(). * @deprecated because uses the java.io.File. */ - public static VirtualFile getGitRoot(@Nonnull final VirtualFile file) throws VcsException { - final VirtualFile root = gitRootOrNull(file); + public static VirtualFile getGitRoot(@Nonnull VirtualFile file) throws VcsException { + VirtualFile root = gitRootOrNull(file); if (root != null) { return root; } @@ -440,7 +441,7 @@ public static VirtualFile getGitRoot(@Nonnull final VirtualFile file) throws Vcs * @deprecated because uses the java.io.File. */ @Nullable - public static VirtualFile gitRootOrNull(final VirtualFile file) { + public static VirtualFile gitRootOrNull(VirtualFile file) { if (file instanceof AbstractVcsVirtualFile) { return getGitRootOrNull(VcsUtil.getFilePath(file.getPath())); } @@ -466,11 +467,11 @@ public static VirtualFile gitRootOrNull(final VirtualFile file) { */ @Nonnull public static List getGitRoots(Project project, GitVcs vcs) throws VcsException { - final VirtualFile[] contentRoots = ProjectLevelVcsManager.getInstance(project).getRootsUnderVcs(vcs); + VirtualFile[] contentRoots = ProjectLevelVcsManager.getInstance(project).getRootsUnderVcs(vcs); if (contentRoots == null || contentRoots.length == 0) { throw new VcsException(GitLocalize.repositoryActionMissingRootsUnconfiguredMessage()); } - final List sortedRoots = DvcsUtil.sortVirtualFilesByPresentation(gitRootsForPaths(Arrays.asList(contentRoots))); + List sortedRoots = DvcsUtil.sortVirtualFilesByPresentation(gitRootsForPaths(Arrays.asList(contentRoots))); if (sortedRoots.size() == 0) { throw new VcsException(GitLocalize.repositoryActionMissingRootsMisconfigured()); } @@ -483,7 +484,7 @@ public static List getGitRoots(Project project, GitVcs vcs) throws * @param vFile a virtual file * @return true if the file is under git */ - public static boolean isUnderGit(final VirtualFile vFile) { + public static boolean isUnderGit(VirtualFile vFile) { return gitRootOrNull(vFile) != null; } @@ -494,7 +495,7 @@ public static boolean isUnderGit(final VirtualFile vFile) { * @param committerName the name of committer * @return just a name if they are equal, or name that includes both author and committer */ - public static String adjustAuthorName(final String authorName, String committerName) { + public static String adjustAuthorName(String authorName, String committerName) { if (!authorName.equals(committerName)) { //noinspection HardCodedStringLiteral committerName = authorName + ", via " + committerName; @@ -508,7 +509,7 @@ public static String adjustAuthorName(final String authorName, String committerN * @param path the path * @return true if the file path is under git */ - public static boolean isUnderGit(final FilePath path) { + public static boolean isUnderGit(FilePath path) { return getGitRootOrNull(path) != null; } @@ -518,10 +519,10 @@ public static boolean isUnderGit(final FilePath path) { * @param filePaths the context paths * @return a set of git roots */ - public static Set gitRoots(final Collection filePaths) { + public static Set gitRoots(Collection filePaths) { HashSet rc = new HashSet<>(); for (FilePath path : filePaths) { - final VirtualFile root = getGitRootOrNull(path); + VirtualFile root = getGitRootOrNull(path); if (root != null) { rc.add(root); } @@ -551,10 +552,10 @@ public static String formatLongRev(long rev) { } public static void getLocalCommittedChanges( - final Project project, - final VirtualFile root, - final Consumer parametersSpecifier, - final Consumer consumer, + Project project, + VirtualFile root, + Consumer parametersSpecifier, + Consumer consumer, boolean skipDiffsForMerge ) throws VcsException { GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG); @@ -565,13 +566,13 @@ public static void getLocalCommittedChanges( String output = h.run(); LOG.debug("getLocalCommittedChanges output: '" + output + "'"); StringScanner s = new StringScanner(output); - final StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); boolean firstStep = true; while (s.hasMoreData()) { - final String line = s.line(); - final boolean lineIsAStart = line.startsWith("\u0004\u0001"); - if ((!firstStep) && lineIsAStart) { - final StringScanner innerScanner = new StringScanner(sb.toString()); + String line = s.line(); + boolean lineIsAStart = line.startsWith("\u0004\u0001"); + if (!firstStep && lineIsAStart) { + StringScanner innerScanner = new StringScanner(sb.toString()); sb.setLength(0); consumer.accept(GitChangeUtils.parseChangeList(project, root, innerScanner, skipDiffsForMerge, h, false, false)); } @@ -579,21 +580,21 @@ public static void getLocalCommittedChanges( firstStep = false; } if (sb.length() > 0) { - final StringScanner innerScanner = new StringScanner(sb.toString()); + StringScanner innerScanner = new StringScanner(sb.toString()); sb.setLength(0); consumer.accept(GitChangeUtils.parseChangeList(project, root, innerScanner, skipDiffsForMerge, h, false, false)); } if (s.hasMoreData()) { - throw new IllegalStateException("More input is avaialble: " + s.line()); + throw new IllegalStateException("More input is available: " + s.line()); } } public static List getLocalCommittedChanges( - final Project project, - final VirtualFile root, - final Consumer parametersSpecifier + Project project, + VirtualFile root, + Consumer parametersSpecifier ) throws VcsException { - final List rc = new ArrayList<>(); + List rc = new ArrayList<>(); getLocalCommittedChanges(project, root, parametersSpecifier, rc::add, false); @@ -616,12 +617,12 @@ public static List getLocalCommittedChanges( */ @Nonnull public static String unescapePath(@Nonnull String path) throws VcsException { - final String QUOTE = "\""; + String QUOTE = "\""; if (path.startsWith(QUOTE) && path.endsWith(QUOTE)) { path = path.substring(1, path.length() - 1); } - final int l = path.length(); + int l = path.length(); StringBuilder rc = new StringBuilder(l); for (int i = 0; i < path.length(); i++) { char c = path.charAt(i); @@ -631,7 +632,7 @@ public static String unescapePath(@Nonnull String path) throws VcsException { if (i >= l) { throw new VcsException("Unterminated escape sequence in the path: " + path); } - final char e = path.charAt(i); + char e = path.charAt(i); switch (e) { case '\\': rc.append('\\'); @@ -675,7 +676,7 @@ public static String unescapePath(@Nonnull String path) throws VcsException { //noinspection AssignmentToForLoopParameter i++; } - b[n++] = (byte)code; + b[n++] = (byte) code; } if (i + 1 >= l || path.charAt(i) != '\\' || !VcsFileUtil.isOctal(path.charAt(i + 1))) { break; @@ -687,12 +688,12 @@ public static String unescapePath(@Nonnull String path) throws VcsException { i--; assert n == b.length; // add them to string - final String encoding = GitConfigUtil.getFileNameEncoding(); + String encoding = GitConfigUtil.getFileNameEncoding(); try { rc.append(new String(b, encoding)); } catch (UnsupportedEncodingException e1) { - throw new IllegalStateException("The file name encoding is unsuported: " + encoding); + throw new IllegalStateException("The file name encoding is unsupported: " + encoding); } } else { @@ -716,20 +717,20 @@ public static boolean justOneGitRepository(Project project) { } @Nullable - public static GitRemote findRemoteByName(@Nonnull GitRepository repository, @Nonnull final String name) { + public static GitRemote findRemoteByName(@Nonnull GitRepository repository, @Nonnull String name) { return findRemoteByName(repository.getRemotes(), name); } @Nullable - public static GitRemote findRemoteByName(Collection remotes, @Nonnull final String name) { + public static GitRemote findRemoteByName(Collection remotes, @Nonnull String name) { return ContainerUtil.find(remotes, remote -> remote.getName().equals(name)); } @Nullable public static GitRemoteBranch findRemoteBranch( @Nonnull GitRepository repository, - @Nonnull final GitRemote remote, - @Nonnull final String nameAtRemote + @Nonnull GitRemote remote, + @Nonnull String nameAtRemote ) { return ContainerUtil.find( repository.getBranches().getRemoteBranches(), @@ -825,13 +826,13 @@ public static Collection getPathsDiffBetweenRefs( return Collections.emptyList(); } - final Collection remoteChanges = new HashSet<>(); + Collection remoteChanges = new HashSet<>(); for (StringScanner s = new StringScanner(result.getOutputAsJoinedString()); s.hasMoreData(); ) { - final String relative = s.line(); + String relative = s.line(); if (StringUtil.isEmptyOrSpaces(relative)) { continue; } - final String path = repository.getRoot().getPath() + "/" + unescapePath(relative); + String path = repository.getRoot().getPath() + "/" + unescapePath(relative); remoteChanges.add(path); } return remoteChanges; @@ -880,16 +881,13 @@ public void run(@Nonnull ProgressIndicator indicator) { indicator.setIndeterminate(true); try { VirtualFile vcsRoot = getGitRoot(file); - final CommittedChangeList changeList = - GitChangeUtils.getRevisionChanges(project, vcsRoot, revision, true, local, revertible); - if (changeList != null) { - UIUtil.invokeLaterIfNeeded( - () -> AbstractVcsHelper.getInstance(project) - .showChangesListBrowser(changeList, GitLocalize.pathsAffectedTitle(revision)) - ); - } + CommittedChangeList changeList = GitChangeUtils.getRevisionChanges(project, vcsRoot, revision, true, local, revertible); + UIUtil.invokeLaterIfNeeded( + () -> AbstractVcsHelper.getInstance(project) + .showChangesListBrowser(changeList, GitLocalize.pathsAffectedTitle(revision)) + ); } - catch (final VcsException e) { + catch (VcsException e) { UIUtil.invokeLaterIfNeeded(() -> GitUIUtil.showOperationError(project, e, "git show")); } } @@ -911,7 +909,7 @@ public static GitBranchTrackInfo getTrackInfoForCurrentBranch(@Nonnull GitReposi @Nonnull public static Collection getRepositoriesForFiles(@Nonnull Project project, @Nonnull Collection files) { - final GitRepositoryManager manager = getRepositoryManager(project); + GitRepositoryManager manager = getRepositoryManager(project); Function rootToRepo = root -> root != null ? manager.getRepositoryForRoot(root) : null; return ContainerUtil.filter( ContainerUtil.map(sortFilesByGitRootsIgnoringOthers(files).keySet(), rootToRepo), @@ -939,7 +937,7 @@ public static Map> sortFilesByGitRootsIgnoringOth * @return true if there is anything in the unstaged/staging area, false if the unstaged/staging area is empty. */ public static boolean hasLocalChanges(boolean staged, Project project, VirtualFile root) throws VcsException { - final GitSimpleHandler diff = new GitSimpleHandler(project, root, GitCommand.DIFF); + GitSimpleHandler diff = new GitSimpleHandler(project, root, GitCommand.DIFF); diff.addParameters("--name-only"); if (staged) { diff.addParameters("--cached"); @@ -947,7 +945,7 @@ public static boolean hasLocalChanges(boolean staged, Project project, VirtualFi diff.setStdoutSuppressed(true); diff.setStderrSuppressed(true); diff.setSilent(true); - final String output = diff.run(); + String output = diff.run(); return !output.trim().isEmpty(); } @@ -969,7 +967,7 @@ public static String toAbsolute(@Nonnull VirtualFile root, @Nonnull String relat } @Nonnull - public static Collection toAbsolute(@Nonnull final VirtualFile root, @Nonnull Collection relativePaths) { + public static Collection toAbsolute(@Nonnull VirtualFile root, @Nonnull Collection relativePaths) { return ContainerUtil.map(relativePaths, s -> toAbsolute(root, s)); } @@ -1011,6 +1009,7 @@ public static List findLocalChangesForPaths( return affectedChanges; } + @RequiredUIAccess public static void showPathsInDialog( @Nonnull Project project, @Nonnull Collection absolutePaths, @@ -1029,7 +1028,7 @@ public static void showPathsInDialog( @Nonnull public static String cleanupErrorPrefixes(@Nonnull String msg) { - final String[] PREFIXES = { + String[] PREFIXES = { "fatal:", "error:" }; @@ -1111,21 +1110,21 @@ public static boolean isCaseOnlyChange(@Nonnull String oldPath, @Nonnull String @Nonnull public static String getLogString(@Nonnull String root, @Nonnull Collection changes) { - return StringUtil.join(changes, change -> - { - ContentRevision after = change.getAfterRevision(); - ContentRevision before = change.getBeforeRevision(); - switch (change.getType()) { - case NEW: - return "A: " + getRelativePath(root, assertNotNull(after)); - case DELETED: - return "D: " + getRelativePath(root, assertNotNull(before)); - case MOVED: - return "M: " + getRelativePath(root, assertNotNull(before)) + " -> " + getRelativePath(root, assertNotNull(after)); - default: - return "M: " + getRelativePath(root, assertNotNull(after)); - } - }, ", "); + return StringUtil.join( + changes, + change -> { + ContentRevision after = change.getAfterRevision(); + ContentRevision before = change.getBeforeRevision(); + return switch (change.getType()) { + case NEW -> "A: " + getRelativePath(root, assertNotNull(after)); + case DELETED -> "D: " + getRelativePath(root, assertNotNull(before)); + case MOVED -> + "M: " + getRelativePath(root, assertNotNull(before)) + " -> " + getRelativePath(root, assertNotNull(after)); + default -> "M: " + getRelativePath(root, assertNotNull(after)); + }; + }, + ", " + ); } @Nullable diff --git a/plugin/src/main/java/git4idea/actions/GitCreateNewBranchAction.java b/plugin/src/main/java/git4idea/actions/GitCreateNewBranchAction.java index 768da0e..215a393 100644 --- a/plugin/src/main/java/git4idea/actions/GitCreateNewBranchAction.java +++ b/plugin/src/main/java/git4idea/actions/GitCreateNewBranchAction.java @@ -20,6 +20,7 @@ import consulo.annotation.component.ActionImpl; import consulo.git.localize.GitLocalize; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.versionControlSystem.log.Hash; import git4idea.branch.GitBranchUtil; import git4idea.branch.GitBrancher; @@ -34,13 +35,14 @@ public GitCreateNewBranchAction() { } @Override + @RequiredUIAccess protected void actionPerformed(@Nonnull GitRepository repository, @Nonnull Hash commit) { Project project = repository.getProject(); String reference = commit.asString(); String name = GitBranchUtil.getNewBranchNameFromUser( project, Collections.singleton(repository), - GitLocalize.dialogCheckoutNewBranchFrom0Title(reference).get() + GitLocalize.dialogTitleCheckoutNewBranchFrom0(reference) ); if (name != null) { GitBrancher brancher = project.getInstance(GitBrancher.class); diff --git a/plugin/src/main/java/git4idea/branch/GitBranchUtil.java b/plugin/src/main/java/git4idea/branch/GitBranchUtil.java index 5a1f661..f9641a0 100644 --- a/plugin/src/main/java/git4idea/branch/GitBranchUtil.java +++ b/plugin/src/main/java/git4idea/branch/GitBranchUtil.java @@ -16,9 +16,13 @@ package git4idea.branch; import consulo.fileEditor.FileEditorManager; +import consulo.git.localize.GitLocalize; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.ui.ex.awt.Messages; +import consulo.ui.ex.awt.UIUtil; import consulo.util.collection.ContainerUtil; import consulo.versionControlSystem.VcsException; import consulo.versionControlSystem.distributed.DvcsUtil; @@ -37,6 +41,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -53,402 +58,410 @@ * @author Kirill Likhodedov */ public class GitBranchUtil { - private static final Logger LOG = Logger.getInstance(GitBranchUtil.class); - - private static final Function BRANCH_TO_NAME = input -> - { - assert input != null; - return input.getName(); - }; - // The name that specifies that git is on specific commit rather then on some branch ({@value}) - private static final String NO_BRANCH_NAME = "(no branch)"; - - private GitBranchUtil() { - } - - /** - * Returns the tracking information about the given branch in the given repository, - * or null if there is no such information (i.e. if the branch doesn't have a tracking branch). - */ - @Nullable - public static GitBranchTrackInfo getTrackInfoForBranch(@Nonnull GitRepository repository, @Nonnull GitLocalBranch branch) { - for (GitBranchTrackInfo trackInfo : repository.getBranchTrackInfos()) { - if (trackInfo.getLocalBranch().equals(branch)) { - return trackInfo; - } - } - return null; - } - - @Nullable - public static GitBranchTrackInfo getTrackInfo(@Nonnull GitRepository repository, @Nonnull String localBranchName) { - return ContainerUtil.find(repository.getBranchTrackInfos(), it -> it.getLocalBranch().getName().equals(localBranchName)); - } - - @Nonnull - static String getCurrentBranchOrRev(@Nonnull Collection repositories) { - if (repositories.size() > 1) { - GitMultiRootBranchConfig multiRootBranchConfig = new GitMultiRootBranchConfig(repositories); - String currentBranch = multiRootBranchConfig.getCurrentBranch(); - LOG.assertTrue(currentBranch != null, "Repositories have unexpectedly diverged. " + multiRootBranchConfig); - return currentBranch; - } - else { - assert !repositories.isEmpty() : "No repositories passed to GitBranchOperationsProcessor."; - GitRepository repository = repositories.iterator().next(); - return getBranchNameOrRev(repository); - } - } - - @Nonnull - public static Collection convertBranchesToNames(@Nonnull Collection branches) { - return branches.stream().map(BRANCH_TO_NAME).toList(); - } - - /** - * Returns the current branch in the given repository, or null if either repository is not on the branch, or in case of error. - * - * @deprecated Use {@link GitRepository#getCurrentBranch()} - */ - @Deprecated - @Nullable - public static GitLocalBranch getCurrentBranch(@Nonnull Project project, @Nonnull VirtualFile root) { - GitRepository repository = GitUtil.getRepositoryManager(project).getRepositoryForRoot(root); - if (repository != null) { - return repository.getCurrentBranch(); - } - else { - LOG.info("getCurrentBranch: Repository is null for root " + root); - return getCurrentBranchFromGit(project, root); + private static final Logger LOG = Logger.getInstance(GitBranchUtil.class); + + private static final Function BRANCH_TO_NAME = input -> { + assert input != null; + return input.getName(); + }; + // The name that specifies that git is on specific commit rather then on some branch ({@value}) + private static final String NO_BRANCH_NAME = "(no branch)"; + + private GitBranchUtil() { } - } - - @Nullable - private static GitLocalBranch getCurrentBranchFromGit(@Nonnull Project project, @Nonnull VirtualFile root) { - GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.REV_PARSE); - handler.addParameters("--abbrev-ref", "HEAD"); - handler.setSilent(true); - try { - String name = handler.run(); - if (!name.equals("HEAD")) { - return new GitLocalBranch(name); - } - else { + + /** + * Returns the tracking information about the given branch in the given repository, + * or null if there is no such information (i.e. if the branch doesn't have a tracking branch). + */ + @Nullable + public static GitBranchTrackInfo getTrackInfoForBranch(@Nonnull GitRepository repository, @Nonnull GitLocalBranch branch) { + for (GitBranchTrackInfo trackInfo : repository.getBranchTrackInfos()) { + if (trackInfo.getLocalBranch().equals(branch)) { + return trackInfo; + } + } return null; - } - } - catch (VcsException e) { - LOG.info("git rev-parse --abbrev-ref HEAD", e); - return null; - } - } - - /** - * Get tracked remote for the branch - */ - @Nullable - public static String getTrackedRemoteName(Project project, VirtualFile root, String branchName) throws VcsException { - return GitConfigUtil.getValue(project, root, trackedRemoteKey(branchName)); - } - - /** - * Get tracked branch of the given branch - */ - @Nullable - public static String getTrackedBranchName(Project project, VirtualFile root, String branchName) throws VcsException { - return GitConfigUtil.getValue(project, root, trackedBranchKey(branchName)); - } - - @Nonnull - private static String trackedBranchKey(String branchName) { - return "branch." + branchName + ".merge"; - } - - @Nonnull - private static String trackedRemoteKey(String branchName) { - return "branch." + branchName + ".remote"; - } - - /** - * Get the tracking branch for the given branch, or null if the given branch doesn't track anything. - * - * @deprecated Use {@link GitConfig#getBranchTrackInfos()} - */ - @Deprecated - @Nullable - public static GitRemoteBranch tracked(@Nonnull Project project, - @Nonnull VirtualFile root, - @Nonnull String branchName) throws VcsException { - final HashMap result = new HashMap<>(); - GitConfigUtil.getValues(project, root, null, result); - String remoteName = result.get(trackedRemoteKey(branchName)); - if (remoteName == null) { - return null; - } - String branch = result.get(trackedBranchKey(branchName)); - if (branch == null) { - return null; } - if (".".equals(remoteName)) { - return new GitSvnRemoteBranch(branch); + @Nullable + public static GitBranchTrackInfo getTrackInfo(@Nonnull GitRepository repository, @Nonnull String localBranchName) { + return ContainerUtil.find(repository.getBranchTrackInfos(), it -> it.getLocalBranch().getName().equals(localBranchName)); } - GitRemote remote = findRemoteByNameOrLogError(project, root, remoteName); - if (remote == null) { - return null; + @Nonnull + static String getCurrentBranchOrRev(@Nonnull Collection repositories) { + if (repositories.size() > 1) { + GitMultiRootBranchConfig multiRootBranchConfig = new GitMultiRootBranchConfig(repositories); + String currentBranch = multiRootBranchConfig.getCurrentBranch(); + LOG.assertTrue(currentBranch != null, "Repositories have unexpectedly diverged. " + multiRootBranchConfig); + return currentBranch; + } + else { + assert !repositories.isEmpty() : "No repositories passed to GitBranchOperationsProcessor."; + GitRepository repository = repositories.iterator().next(); + return getBranchNameOrRev(repository); + } } - return new GitStandardRemoteBranch(remote, branch); - } - - @Nullable - @Deprecated - public static GitRemote findRemoteByNameOrLogError(@Nonnull Project project, @Nonnull VirtualFile root, @Nonnull String remoteName) { - GitRepository repository = GitUtil.getRepositoryForRootOrLogError(project, root); - if (repository == null) { - return null; + + @Nonnull + public static Collection convertBranchesToNames(@Nonnull Collection branches) { + return branches.stream().map(BRANCH_TO_NAME).toList(); } - GitRemote remote = GitUtil.findRemoteByName(repository, remoteName); - if (remote == null) { - LOG.warn("Couldn't find remote with name " + remoteName); - return null; + /** + * Returns the current branch in the given repository, or null if either repository is not on the branch, or in case of error. + * + * @deprecated Use {@link GitRepository#getCurrentBranch()} + */ + @Deprecated + @Nullable + public static GitLocalBranch getCurrentBranch(@Nonnull Project project, @Nonnull VirtualFile root) { + GitRepository repository = GitUtil.getRepositoryManager(project).getRepositoryForRoot(root); + if (repository != null) { + return repository.getCurrentBranch(); + } + else { + LOG.info("getCurrentBranch: Repository is null for root " + root); + return getCurrentBranchFromGit(project, root); + } } - return remote; - } - - /** - * Convert {@link GitRemoteBranch GitRemoteBranches} to their names, and remove remote HEAD pointers: origin/HEAD. - */ - @Nonnull - public static Collection getBranchNamesWithoutRemoteHead(@Nonnull Collection remoteBranches) { - return ContainerUtil.filter(convertBranchesToNames(remoteBranches), input -> { - assert input != null; - return !input.equals("HEAD"); - }); - } - - @Nonnull - public static String stripRefsPrefix(@Nonnull String branchName) { - if (branchName.startsWith(GitBranch.REFS_HEADS_PREFIX)) { - return branchName.substring(GitBranch.REFS_HEADS_PREFIX.length()); + + @Nullable + private static GitLocalBranch getCurrentBranchFromGit(@Nonnull Project project, @Nonnull VirtualFile root) { + GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.REV_PARSE); + handler.addParameters("--abbrev-ref", "HEAD"); + handler.setSilent(true); + try { + String name = handler.run(); + if (!name.equals("HEAD")) { + return new GitLocalBranch(name); + } + else { + return null; + } + } + catch (VcsException e) { + LOG.info("git rev-parse --abbrev-ref HEAD", e); + return null; + } } - else if (branchName.startsWith(GitBranch.REFS_REMOTES_PREFIX)) { - return branchName.substring(GitBranch.REFS_REMOTES_PREFIX.length()); + + /** + * Get tracked remote for the branch + */ + @Nullable + public static String getTrackedRemoteName(Project project, VirtualFile root, String branchName) throws VcsException { + return GitConfigUtil.getValue(project, root, trackedRemoteKey(branchName)); } - else if (branchName.startsWith(GitTag.REFS_TAGS_PREFIX)) { - return branchName.substring(GitTag.REFS_TAGS_PREFIX.length()); + + /** + * Get tracked branch of the given branch + */ + @Nullable + public static String getTrackedBranchName(Project project, VirtualFile root, String branchName) throws VcsException { + return GitConfigUtil.getValue(project, root, trackedBranchKey(branchName)); } - return branchName; - } - - /** - * Returns current branch name (if on branch) or current revision otherwise. - * For fresh repository returns an empty string. - */ - @Nonnull - public static String getBranchNameOrRev(@Nonnull GitRepository repository) { - if (repository.isOnBranch()) { - GitBranch currentBranch = repository.getCurrentBranch(); - assert currentBranch != null; - return currentBranch.getName(); + + @Nonnull + private static String trackedBranchKey(String branchName) { + return "branch." + branchName + ".merge"; } - else { - String currentRevision = repository.getCurrentRevision(); - return currentRevision != null ? currentRevision.substring(0, 7) : ""; + + @Nonnull + private static String trackedRemoteKey(String branchName) { + return "branch." + branchName + ".remote"; } - } - - /** - * Shows a message dialog to enter the name of new branch. - * - * @return name of new branch or {@code null} if user has cancelled the dialog. - */ - @Nullable - public static String getNewBranchNameFromUser(@Nonnull Project project, - @Nonnull Collection repositories, - @Nonnull String dialogTitle) { - return Messages.showInputDialog(project, - "Enter the name of new branch:", - dialogTitle, - Messages.getQuestionIcon(), - "", - GitNewBranchNameValidator.newInstance(repositories)); - } - - /** - * Returns the text that is displaying current branch. - * In the simple case it is just the branch name, but in detached HEAD state it displays the hash or "rebasing master". - */ - @Nonnull - public static String getDisplayableBranchText(@Nonnull GitRepository repository) { - GitRepository.State state = repository.getState(); - if (state == GitRepository.State.DETACHED) { - String currentRevision = repository.getCurrentRevision(); - assert currentRevision != null : "Current revision can't be null in DETACHED state, only on the fresh repository."; - return DvcsUtil.getShortHash(currentRevision); + + /** + * Get the tracking branch for the given branch, or null if the given branch doesn't track anything. + * + * @deprecated Use {@link GitConfig#getBranchTrackInfos()} + */ + @Deprecated + @Nullable + public static GitRemoteBranch tracked(@Nonnull Project project, @Nonnull VirtualFile root, @Nonnull String branchName) + throws VcsException { + HashMap result = new HashMap<>(); + GitConfigUtil.getValues(project, root, null, result); + String remoteName = result.get(trackedRemoteKey(branchName)); + if (remoteName == null) { + return null; + } + String branch = result.get(trackedBranchKey(branchName)); + if (branch == null) { + return null; + } + + if (".".equals(remoteName)) { + return new GitSvnRemoteBranch(branch); + } + + GitRemote remote = findRemoteByNameOrLogError(project, root, remoteName); + if (remote == null) { + return null; + } + return new GitStandardRemoteBranch(remote, branch); } - String prefix = ""; - if (state == GitRepository.State.MERGING || state == GitRepository.State.REBASING) { - prefix = state.toString() + " "; + @Nullable + @Deprecated + public static GitRemote findRemoteByNameOrLogError(@Nonnull Project project, @Nonnull VirtualFile root, @Nonnull String remoteName) { + GitRepository repository = GitUtil.getRepositoryForRootOrLogError(project, root); + if (repository == null) { + return null; + } + + GitRemote remote = GitUtil.findRemoteByName(repository, remoteName); + if (remote == null) { + LOG.warn("Couldn't find remote with name " + remoteName); + return null; + } + return remote; } - GitBranch branch = repository.getCurrentBranch(); - String branchName = (branch == null ? "" : branch.getName()); - return prefix + branchName; - } - - /** - * Guesses the Git root on which a Git action is to be invoked. - *
    - *
  1. - * Returns the root for the selected file. Selected file is determined by {@link DvcsUtil#getSelectedFile(Project)}. - * If selected file is unknown (for example, no file is selected in the Project View or Changes View and no file is open in the editor), - * continues guessing. Otherwise returns the Git root for the selected file. If the file is not under a known Git root, - * but there is at least one git root, continues guessing, otherwise - * null will be returned - the file is definitely determined, but it is not under Git and no git roots exists in project. - *
  2. - *
  3. - * Takes all Git roots registered in the Project. If there is only one, it is returned. - *
  4. - *
  5. - * If there are several Git roots, - *
  6. - *
- *

- *

- * NB: This method has to be accessed from the read action, because it may query - * {@link FileEditorManager#getSelectedTextEditor()}. - *

- * - * @param project current project - * @return Git root that may be considered as "current". - * null is returned if a file not under Git was explicitly selected, if there are no Git roots in the project, - * or if the current Git root couldn't be determined. - */ - @Nullable - public static GitRepository getCurrentRepository(@Nonnull Project project) { - return getRepositoryOrGuess(project, DvcsUtil.getSelectedFile(project)); - } - - @Nullable - public static GitRepository getRepositoryOrGuess(@Nonnull Project project, @Nullable VirtualFile file) { - if (project.isDisposed()) { - return null; + /** + * Convert {@link GitRemoteBranch GitRemoteBranches} to their names, and remove remote HEAD pointers: origin/HEAD. + */ + @Nonnull + public static Collection getBranchNamesWithoutRemoteHead(@Nonnull Collection remoteBranches) { + return ContainerUtil.filter(convertBranchesToNames(remoteBranches), input -> { + assert input != null; + return !input.equals("HEAD"); + }); } - return DvcsUtil.guessRepositoryForFile(project, - GitUtil.getRepositoryManager(project), - file, - GitVcsSettings.getInstance(project).getRecentRootPath()); - } - - @Nonnull - public static Collection getCommonBranches(Collection repositories, boolean local) { - Collection commonBranches = null; - for (GitRepository repository : repositories) { - GitBranchesCollection branchesCollection = repository.getBranches(); - - Collection names = - local ? convertBranchesToNames(branchesCollection.getLocalBranches()) : getBranchNamesWithoutRemoteHead(branchesCollection.getRemoteBranches()); - if (commonBranches == null) { - commonBranches = names; - } - else { - commonBranches = ContainerUtil.intersection(commonBranches, names); - } + + @Nonnull + public static String stripRefsPrefix(@Nonnull String branchName) { + if (branchName.startsWith(GitBranch.REFS_HEADS_PREFIX)) { + return branchName.substring(GitBranch.REFS_HEADS_PREFIX.length()); + } + else if (branchName.startsWith(GitBranch.REFS_REMOTES_PREFIX)) { + return branchName.substring(GitBranch.REFS_REMOTES_PREFIX.length()); + } + else if (branchName.startsWith(GitTag.REFS_TAGS_PREFIX)) { + return branchName.substring(GitTag.REFS_TAGS_PREFIX.length()); + } + return branchName; } - if (commonBranches != null) { - ArrayList common = new ArrayList<>(commonBranches); - Collections.sort(common); - return common; + /** + * Returns current branch name (if on branch) or current revision otherwise. + * For fresh repository returns an empty string. + */ + @Nonnull + public static String getBranchNameOrRev(@Nonnull GitRepository repository) { + if (repository.isOnBranch()) { + GitBranch currentBranch = repository.getCurrentBranch(); + assert currentBranch != null; + return currentBranch.getName(); + } + else { + String currentRevision = repository.getCurrentRevision(); + return currentRevision != null ? currentRevision.substring(0, 7) : ""; + } } - else { - return Collections.emptyList(); + + /** + * Shows a message dialog to enter the name of new branch. + * + * @return name of new branch or {@code null} if user has cancelled the dialog. + */ + @Nullable + @RequiredUIAccess + public static String getNewBranchNameFromUser( + @Nonnull Project project, + @Nonnull Collection repositories, + @Nonnull LocalizeValue dialogTitle + ) { + return Messages.showInputDialog( + project, + GitLocalize.dialogMessageEnterTheNameOfNewBranch().get(), + dialogTitle.get(), + UIUtil.getQuestionIcon(), + "", + GitNewBranchNameValidator.newInstance(repositories) + ); } - } - - /** - * List branches containing a commit. Specify null if no commit filtering is needed. - */ - @Nonnull - public static Collection getBranches(@Nonnull Project project, - @Nonnull VirtualFile root, - boolean localWanted, - boolean remoteWanted, - @Nullable String containingCommit) throws VcsException { - // preparing native command executor - final GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.BRANCH); - handler.setSilent(true); - handler.addParameters("--no-color"); - boolean remoteOnly = false; - if (remoteWanted && localWanted) { - handler.addParameters("-a"); - remoteOnly = false; + + /** + * Returns the text that is displaying current branch. + * In the simple case it is just the branch name, but in detached HEAD state it displays the hash or "rebasing master". + */ + @Nonnull + public static String getDisplayableBranchText(@Nonnull GitRepository repository) { + GitRepository.State state = repository.getState(); + if (state == GitRepository.State.DETACHED) { + String currentRevision = repository.getCurrentRevision(); + assert currentRevision != null : "Current revision can't be null in DETACHED state, only on the fresh repository."; + return DvcsUtil.getShortHash(currentRevision); + } + + String prefix = ""; + if (state == GitRepository.State.MERGING || state == GitRepository.State.REBASING) { + prefix = state.toString() + " "; + } + + GitBranch branch = repository.getCurrentBranch(); + String branchName = (branch == null ? "" : branch.getName()); + return prefix + branchName; } - else if (remoteWanted) { - handler.addParameters("-r"); - remoteOnly = true; + + /** + * Guesses the Git root on which a Git action is to be invoked. + *
    + *
  1. + * Returns the root for the selected file. Selected file is determined by {@link DvcsUtil#getSelectedFile(Project)}. + * If selected file is unknown (for example, no file is selected in the Project View or Changes View and no file is open in the editor), + * continues guessing. Otherwise returns the Git root for the selected file. If the file is not under a known Git root, + * but there is at least one git root, continues guessing, otherwise + * null will be returned - the file is definitely determined, but it is not under Git and no git roots exists in project. + *
  2. + *
  3. + * Takes all Git roots registered in the Project. If there is only one, it is returned. + *
  4. + *
  5. + * If there are several Git roots, + *
  6. + *
+ *

+ *

+ * NB: This method has to be accessed from the read action, because it may query + * {@link FileEditorManager#getSelectedTextEditor()}. + *

+ * + * @param project current project + * @return Git root that may be considered as "current". + * null is returned if a file not under Git was explicitly selected, if there are no Git roots in the project, + * or if the current Git root couldn't be determined. + */ + @Nullable + public static GitRepository getCurrentRepository(@Nonnull Project project) { + return getRepositoryOrGuess(project, DvcsUtil.getSelectedFile(project)); } - if (containingCommit != null) { - handler.addParameters("--contains", containingCommit); + + @Nullable + public static GitRepository getRepositoryOrGuess(@Nonnull Project project, @Nullable VirtualFile file) { + if (project.isDisposed()) { + return null; + } + return DvcsUtil.guessRepositoryForFile( + project, + GitUtil.getRepositoryManager(project), + file, + GitVcsSettings.getInstance(project).getRecentRootPath() + ); } - final String output = handler.run(); - - if (output.trim().length() == 0) { - // the case after git init and before first commit - there is no branch and no output, and we'll take refs/heads/master - String head; - try { - File headFile = assertNotNull(GitUtil.getRepositoryManager(project).getRepositoryForRoot(root)).getRepositoryFiles().getHeadFile(); - head = Files.readString(headFile.toPath(), StandardCharsets.UTF_8).trim(); - final String prefix = "ref: refs/heads/"; - return head.startsWith(prefix) ? Collections.singletonList(head.substring(prefix.length())) : Collections.emptyList(); - } - catch (IOException e) { - LOG.info(e); - return Collections.emptyList(); - } + + @Nonnull + public static Collection getCommonBranches(Collection repositories, boolean local) { + Collection commonBranches = null; + for (GitRepository repository : repositories) { + GitBranchesCollection branchesCollection = repository.getBranches(); + + Collection names = + local ? convertBranchesToNames(branchesCollection.getLocalBranches()) : getBranchNamesWithoutRemoteHead(branchesCollection.getRemoteBranches()); + if (commonBranches == null) { + commonBranches = names; + } + else { + commonBranches = ContainerUtil.intersection(commonBranches, names); + } + } + + if (commonBranches != null) { + ArrayList common = new ArrayList<>(commonBranches); + Collections.sort(common); + return common; + } + else { + return Collections.emptyList(); + } } - Collection branches = ContainerUtil.newArrayList(); - // standard situation. output example: - // master - //* my_feature - // remotes/origin/HEAD -> origin/master - // remotes/origin/eap - // remotes/origin/feature - // remotes/origin/master - // also possible: - //* (no branch) - // and if we call with -r instead of -a, remotes/ prefix is omitted: - // origin/HEAD -> origin/master - final String[] split = output.split("\n"); - for (String b : split) { - b = b.substring(2).trim(); - if (b.equals(NO_BRANCH_NAME)) { - continue; - } - - String remotePrefix = null; - if (b.startsWith("remotes/")) { - remotePrefix = "remotes/"; - } - else if (b.startsWith(GitBranch.REFS_REMOTES_PREFIX)) { - remotePrefix = GitBranch.REFS_REMOTES_PREFIX; - } - boolean isRemote = remotePrefix != null || remoteOnly; - if (isRemote) { - if (!remoteOnly) { - b = b.substring(remotePrefix.length()); + /** + * List branches containing a commit. Specify null if no commit filtering is needed. + */ + @Nonnull + public static Collection getBranches( + @Nonnull Project project, + @Nonnull VirtualFile root, + boolean localWanted, + boolean remoteWanted, + @Nullable String containingCommit + ) throws VcsException { + // preparing native command executor + GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.BRANCH); + handler.setSilent(true); + handler.addParameters("--no-color"); + boolean remoteOnly = false; + if (remoteWanted && localWanted) { + handler.addParameters("-a"); + remoteOnly = false; + } + else if (remoteWanted) { + handler.addParameters("-r"); + remoteOnly = true; } - final int idx = b.indexOf("HEAD ->"); - if (idx > 0) { - continue; + if (containingCommit != null) { + handler.addParameters("--contains", containingCommit); + } + String output = handler.run(); + + if (output.trim().length() == 0) { + // the case after git init and before first commit - there is no branch and no output, and we'll take refs/heads/master + String head; + try { + File headFile = + assertNotNull(GitUtil.getRepositoryManager(project).getRepositoryForRoot(root)).getRepositoryFiles().getHeadFile(); + head = Files.readString(headFile.toPath(), StandardCharsets.UTF_8).trim(); + String prefix = "ref: refs/heads/"; + return head.startsWith(prefix) ? Collections.singletonList(head.substring(prefix.length())) : Collections.emptyList(); + } + catch (IOException e) { + LOG.info(e); + return Collections.emptyList(); + } + } + + Collection branches = new ArrayList<>(); + // standard situation. output example: + // master + //* my_feature + // remotes/origin/HEAD -> origin/master + // remotes/origin/eap + // remotes/origin/feature + // remotes/origin/master + // also possible: + //* (no branch) + // and if we call with -r instead of -a, remotes/ prefix is omitted: + // origin/HEAD -> origin/master + String[] split = output.split("\n"); + for (String b : split) { + b = b.substring(2).trim(); + if (b.equals(NO_BRANCH_NAME)) { + continue; + } + + String remotePrefix = null; + if (b.startsWith("remotes/")) { + remotePrefix = "remotes/"; + } + else if (b.startsWith(GitBranch.REFS_REMOTES_PREFIX)) { + remotePrefix = GitBranch.REFS_REMOTES_PREFIX; + } + boolean isRemote = remotePrefix != null || remoteOnly; + if (isRemote) { + if (!remoteOnly) { + b = b.substring(remotePrefix.length()); + } + int idx = b.indexOf("HEAD ->"); + if (idx > 0) { + continue; + } + } + branches.add(b); } - } - branches.add(b); + return branches; } - return branches; - } } diff --git a/plugin/src/main/java/git4idea/checkin/GitCheckinEnvironment.java b/plugin/src/main/java/git4idea/checkin/GitCheckinEnvironment.java index 5da933e..2ee09f2 100644 --- a/plugin/src/main/java/git4idea/checkin/GitCheckinEnvironment.java +++ b/plugin/src/main/java/git4idea/checkin/GitCheckinEnvironment.java @@ -35,10 +35,9 @@ import consulo.ui.ex.awtUnsafe.TargetAWT; import consulo.util.io.FileUtil; import consulo.util.lang.StringUtil; -import consulo.util.lang.function.Condition; import consulo.util.lang.function.Functions; import consulo.util.lang.function.PairConsumer; -import consulo.util.lang.ref.Ref; +import consulo.util.lang.ref.SimpleReference; import consulo.versionControlSystem.FilePath; import consulo.versionControlSystem.VcsException; import consulo.versionControlSystem.change.*; @@ -86,6 +85,7 @@ import java.util.List; import java.util.*; import java.util.function.Function; +import java.util.function.Predicate; import static consulo.util.collection.ContainerUtil.*; import static consulo.util.lang.ObjectUtil.assertNotNull; @@ -105,8 +105,10 @@ public class GitCheckinEnvironment implements CheckinEnvironment { private static final String GIT_COMMIT_MSG_FILE_PREFIX = "git-commit-msg-"; // the file name prefix for commit message file private static final String GIT_COMMIT_MSG_FILE_EXT = ".txt"; // the file extension for commit message file + @Nonnull private final Project myProject; public static final SimpleDateFormat COMMIT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + @Nonnull private final VcsDirtyScopeManager myDirtyScopeManager; private final GitVcsSettings mySettings; @@ -119,8 +121,8 @@ public class GitCheckinEnvironment implements CheckinEnvironment { @Inject public GitCheckinEnvironment( @Nonnull Project project, - @Nonnull final VcsDirtyScopeManager dirtyScopeManager, - final GitVcsSettings settings + @Nonnull VcsDirtyScopeManager dirtyScopeManager, + GitVcsSettings settings ) { myProject = project; myDirtyScopeManager = dirtyScopeManager; @@ -190,9 +192,10 @@ public String getHelpId() { return null; } + @Nonnull @Override - public String getCheckinOperationName() { - return GitLocalize.commitActionName().get(); + public LocalizeValue getCheckinOperationName() { + return GitLocalize.commitActionName(); } @Override @@ -219,7 +222,7 @@ public List commit( Set added = new HashSet<>(); Set removed = new HashSet<>(); - final Set caseOnlyRenames = new HashSet<>(); + Set caseOnlyRenames = new HashSet<>(); for (Change change : entry.getValue()) { switch (change.getType()) { case NEW: @@ -281,8 +284,8 @@ public List commit( if (myNextCommitIsPushed != null && myNextCommitIsPushed && exceptions.isEmpty()) { GitRepositoryManager manager = getRepositoryManager(myProject); Collection repositories = GitUtil.getRepositoriesFromRoots(manager, sortedChanges.keySet()); - final List preselectedRepositories = newArrayList(repositories); - Application application = Application.get(); + List preselectedRepositories = new ArrayList<>(repositories); + Application application = myProject.getApplication(); application.invokeLater( () -> { DistributedVersionControlHelper helper = application.getInstance(DistributedVersionControlHelper.class); @@ -368,7 +371,7 @@ private List commitWithCaseOnlyRename( if (!excludedStagedChanges.isEmpty()) { LOG.debug("Restoring changes which were unstaged before commit: " + getLogString(rootPath, excludedStagedChanges)); Set toAdd = map2SetNotNull(excludedStagedChanges, ChangesUtil::getAfterPath); - Condition isMovedOrDeleted = + Predicate isMovedOrDeleted = change -> change.getType() == Change.Type.MOVED || change.getType() == Change.Type.DELETED; Set toRemove = map2SetNotNull(filter(excludedStagedChanges, isMovedOrDeleted), ChangesUtil::getBeforePath); updateIndex(project, root, toAdd, toRemove, exceptions); @@ -396,7 +399,7 @@ private static void reset( private static VcsException cleanupExceptionText(VcsException original) { String msg = original.getMessage(); msg = GitUtil.cleanupErrorPrefixes(msg); - final String DURING_EXECUTING_SUFFIX = GitSimpleHandler.DURING_EXECUTING_ERROR_MESSAGE; + String DURING_EXECUTING_SUFFIX = GitSimpleHandler.DURING_EXECUTING_ERROR_MESSAGE; int suffix = msg.indexOf(DURING_EXECUTING_SUFFIX); if (suffix > 0) { msg = msg.substring(0, suffix); @@ -423,14 +426,14 @@ public List commit(List changes, String preparedComment) { * @return true if merge commit was successful */ private boolean mergeCommit( - final Project project, - final VirtualFile root, - final Set added, - final Set removed, - final File messageFile, - final String author, + Project project, + VirtualFile root, + Set added, + Set removed, + File messageFile, + String author, List exceptions, - @Nonnull final PartialOperation partialOperation + @Nonnull PartialOperation partialOperation ) { HashSet realAdded = new HashSet<>(); HashSet realRemoved = new HashSet<>(); @@ -470,11 +473,10 @@ private boolean mergeCommit( realAdded.removeAll(added); realRemoved.removeAll(removed); if (realAdded.size() != 0 || realRemoved.size() != 0) { - - final List files = new ArrayList<>(); + List files = new ArrayList<>(); files.addAll(realAdded); files.addAll(realRemoved); - final Ref mergeAll = new Ref<>(); + SimpleReference mergeAll = new SimpleReference<>(); try { LegacyComponentFactory legacyComponentFactory = project.getApplication().getInstance(LegacyComponentFactory.class); @@ -552,7 +554,7 @@ private void commitWithoutPaths( * @param ex an exception to examine * @return true if exception means that there is a partial commit during merge */ - private static PartialOperation isMergeCommit(final VcsException ex) { + private static PartialOperation isMergeCommit(VcsException ex) { String message = ex.getMessage(); if (message.contains("fatal: cannot do a partial commit during a merge")) { return PartialOperation.MERGE; @@ -574,11 +576,11 @@ private static PartialOperation isMergeCommit(final VcsException ex) { * @return true if index was updated successfully */ private static boolean updateIndex( - final Project project, - final VirtualFile root, - final Collection added, - final Collection removed, - final List exceptions + Project project, + VirtualFile root, + Collection added, + Collection removed, + List exceptions ) { boolean rc = true; if (!added.isEmpty()) { @@ -610,7 +612,7 @@ private static boolean updateIndex( * @return a file reference * @throws IOException if file cannot be created */ - private File createMessageFile(VirtualFile root, final String message) throws IOException { + private File createMessageFile(VirtualFile root, String message) throws IOException { // filter comment lines File file = FileUtil.createTempFile(GIT_COMMIT_MSG_FILE_PREFIX, GIT_COMMIT_MSG_FILE_EXT); file.deleteOnExit(); @@ -634,7 +636,7 @@ public List scheduleMissingFileForDeletion(List files) { } for (Map.Entry> e : sortedFiles.entrySet()) { try { - final VirtualFile root = e.getKey(); + VirtualFile root = e.getKey(); GitFileUtils.delete(myProject, root, e.getValue()); markRootDirty(root); } @@ -694,7 +696,7 @@ public List scheduleUnversionedFilesForAddition(List } for (Map.Entry> e : sortedFiles.entrySet()) { try { - final VirtualFile root = e.getKey(); + VirtualFile root = e.getKey(); GitFileUtils.addFiles(myProject, root, e.getValue()); markRootDirty(root); } @@ -724,13 +726,13 @@ String getName() { private static Map> sortChangesByGitRoot(@Nonnull List changes, List exceptions) { Map> result = new HashMap<>(); for (Change change : changes) { - final ContentRevision afterRevision = change.getAfterRevision(); - final ContentRevision beforeRevision = change.getBeforeRevision(); + ContentRevision afterRevision = change.getAfterRevision(); + ContentRevision beforeRevision = change.getBeforeRevision(); // nothing-to-nothing change cannot happen. assert beforeRevision != null || afterRevision != null; // note that any path will work, because changes could happen within single vcs root - final FilePath filePath = afterRevision != null ? afterRevision.getFile() : beforeRevision.getFile(); - final VirtualFile vcsRoot; + FilePath filePath = afterRevision != null ? afterRevision.getFile() : beforeRevision.getFile(); + VirtualFile vcsRoot; try { // the parent paths for calculating roots in order to account for submodules that contribute // to the parent change. The path "." is never is valid change, so there should be no problem diff --git a/plugin/src/main/java/git4idea/checkin/GitCheckinHandlerFactory.java b/plugin/src/main/java/git4idea/checkin/GitCheckinHandlerFactory.java index 6eb5491..0ae20e5 100644 --- a/plugin/src/main/java/git4idea/checkin/GitCheckinHandlerFactory.java +++ b/plugin/src/main/java/git4idea/checkin/GitCheckinHandlerFactory.java @@ -28,7 +28,6 @@ import consulo.ui.ex.awt.UIUtil; import consulo.util.lang.Couple; import consulo.util.lang.StringUtil; -import consulo.util.lang.function.PairConsumer; import consulo.util.lang.xml.XmlStringUtil; import consulo.versionControlSystem.FilePath; import consulo.versionControlSystem.ProjectLevelVcsManager; @@ -52,11 +51,11 @@ import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; import jakarta.annotation.Nonnull; - import jakarta.annotation.Nullable; import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; /** * Prohibits committing with an empty message, warns if committing into detached HEAD, checks if user name and correct CRLF attributes @@ -74,7 +73,7 @@ public GitCheckinHandlerFactory() { @Nonnull @Override - protected CheckinHandler createVcsHandler(final CheckinProjectPanel panel) { + protected CheckinHandler createVcsHandler(CheckinProjectPanel panel) { return new MyCheckinHandler(panel); } @@ -92,7 +91,7 @@ public MyCheckinHandler(@Nonnull CheckinProjectPanel panel) { @Override @RequiredUIAccess - public ReturnResult beforeCheckin(@Nullable CommitExecutor executor, PairConsumer additionalDataConsumer) { + public ReturnResult beforeCheckin(@Nullable CommitExecutor executor, BiConsumer additionalDataConsumer) { if (emptyCommitMessage()) { return ReturnResult.CANCEL; } @@ -140,7 +139,7 @@ public void run(@Nonnull ProgressIndicator indicator) { } if (crlfHelper.get().shouldWarn()) { - final GitCrlfDialog dialog = new GitCrlfDialog(myProject); + GitCrlfDialog dialog = new GitCrlfDialog(myProject); UIUtil.invokeAndWaitIfNeeded((Runnable)dialog::show); int decision = dialog.getExitCode(); if (decision == GitCrlfDialog.CANCEL) { @@ -309,10 +308,10 @@ private ReturnResult warnAboutDetachedHeadIfNeeded() { return ReturnResult.COMMIT; } - final String title; - final String message; - final CharSequence rootPath = StringUtil.last(detachedRoot.myRoot.getPresentableUrl(), 50, true); - final String messageCommonStart = "The Git repository " + rootPath + ""; + String title; + String message; + CharSequence rootPath = StringUtil.last(detachedRoot.myRoot.getPresentableUrl(), 50, true); + String messageCommonStart = "The Git repository " + rootPath + ""; if (detachedRoot.myRebase) { title = "Unfinished rebase process"; message = messageCommonStart + "
has an unfinished rebase process.
" + @@ -328,7 +327,7 @@ private ReturnResult warnAboutDetachedHeadIfNeeded() { readMore("http://sitaramc.github.com/concepts/detached-head.html", "Read more about detached HEAD"); } - final int choice = Messages.showOkCancelDialog( + int choice = Messages.showOkCancelDialog( myPanel.getComponent(), XmlStringUtil.wrapInHtml(message), title, @@ -395,7 +394,5 @@ public DetachedRoot(@Nonnull VirtualFile root, boolean rebase) { myRebase = rebase; } } - } - } diff --git a/plugin/src/main/java/git4idea/checkin/GitCommitAndPushExecutor.java b/plugin/src/main/java/git4idea/checkin/GitCommitAndPushExecutor.java index 8956d60..e202e44 100644 --- a/plugin/src/main/java/git4idea/checkin/GitCommitAndPushExecutor.java +++ b/plugin/src/main/java/git4idea/checkin/GitCommitAndPushExecutor.java @@ -15,29 +15,31 @@ */ package git4idea.checkin; +import consulo.localize.LocalizeValue; import consulo.versionControlSystem.change.CommitExecutor; import consulo.versionControlSystem.change.CommitSession; import jakarta.annotation.Nonnull; -import org.jetbrains.annotations.Nls; /** * @author yole */ public class GitCommitAndPushExecutor implements CommitExecutor { - private final GitCheckinEnvironment myCheckinEnvironment; + private final GitCheckinEnvironment myCheckinEnvironment; - public GitCommitAndPushExecutor(GitCheckinEnvironment checkinEnvironment) { - myCheckinEnvironment = checkinEnvironment; - } + public GitCommitAndPushExecutor(GitCheckinEnvironment checkinEnvironment) { + myCheckinEnvironment = checkinEnvironment; + } - @Nls - public String getActionText() { - return "Commit and &Push..."; - } + @Nonnull + @Override + public LocalizeValue getActionText() { + return LocalizeValue.localizeTODO("Commit and &Push..."); + } - @Nonnull - public CommitSession createCommitSession() { - myCheckinEnvironment.setNextCommitIsPushed(true); - return CommitSession.VCS_COMMIT; - } + @Nonnull + @Override + public CommitSession createCommitSession() { + myCheckinEnvironment.setNextCommitIsPushed(true); + return CommitSession.VCS_COMMIT; + } } diff --git a/plugin/src/main/java/git4idea/repo/GitConfig.java b/plugin/src/main/java/git4idea/repo/GitConfig.java index 62284ff..5549c16 100644 --- a/plugin/src/main/java/git4idea/repo/GitConfig.java +++ b/plugin/src/main/java/git4idea/repo/GitConfig.java @@ -1,18 +1,18 @@ /* -* Copyright 2000-2011 JetBrains s.r.o. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package git4idea.repo; import consulo.logging.Logger; @@ -20,15 +20,14 @@ import consulo.util.collection.ContainerUtil; import consulo.util.lang.Pair; import consulo.util.lang.StringUtil; -import consulo.util.lang.function.Condition; import git4idea.GitLocalBranch; import git4idea.GitRemoteBranch; import git4idea.branch.GitBranchUtil; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import org.ini4j.Ini; import org.ini4j.Profile; -import jakarta.annotation.Nonnull; -import jakarta.annotation.Nullable; import java.io.File; import java.io.IOException; import java.util.*; @@ -44,522 +43,470 @@ *

* 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); - - private static final Pattern REMOTE_SECTION = Pattern.compile("(?:svn-)?remote \"(.*)\""); - private static final Pattern URL_SECTION = Pattern.compile("url \"(.*)\""); - private static final Pattern BRANCH_INFO_SECTION = Pattern.compile("branch \"(.*)\""); - private static final Pattern BRANCH_COMMON_PARAMS_SECTION = Pattern.compile("branch"); - - @Nonnull - private final Collection myRemotes; - @Nonnull - private final Collection myUrls; - @Nonnull - private final Collection myTrackedInfos; - - - private GitConfig(@Nonnull Collection remotes, @Nonnull Collection urls, @Nonnull Collection trackedInfos) - { - myRemotes = remotes; - myUrls = urls; - myTrackedInfos = trackedInfos; - } - - /** - *

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}.

- * - * @return Git remotes defined in {@code .git/config}. - */ - @Nonnull - Collection parseRemotes() - { - // populate GitRemotes with substituting urls when needed - return ContainerUtil.map(myRemotes, remote -> - { - assert remote != null; - return convertRemoteToGitRemote(myUrls, remote); - }); - } - - @Nonnull - private static GitRemote convertRemoteToGitRemote(@Nonnull Collection urls, @Nonnull Remote remote) - { - UrlsAndPushUrls substitutedUrls = substituteUrls(urls, remote); - return new GitRemote(remote.myName, substitutedUrls.getUrls(), substitutedUrls.getPushUrls(), remote.getFetchSpecs(), remote.getPushSpec(), remote.getPuttyKeys()); - } - - /** - * Create branch tracking information based on the information defined in {@code .git/config}. - */ - @Nonnull - Collection parseTrackInfos(@Nonnull final Collection localBranches, @Nonnull final Collection remoteBranches) - { - return ContainerUtil.mapNotNull(myTrackedInfos, config -> - { - if(config != null) - { - return convertBranchConfig(config, localBranches, remoteBranches); - } - 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. - */ - @Nonnull - static GitConfig read(@Nonnull File configFile) - { - GitConfig emptyConfig = new GitConfig(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - if(!configFile.exists()) - { - LOG.info("No .git/config file at " + configFile.getPath()); - return emptyConfig; - } - - Ini ini; - try - { - ini = GitConfigHelper.loadIniFile(configFile); - } - catch(IOException e) - { - return emptyConfig; - } - - Pair, Collection> remotesAndUrls = parseRemotes(ini, GitConfig.class.getClassLoader()); - Collection trackedInfos = parseTrackedInfos(ini, GitConfig.class.getClassLoader()); - - return new GitConfig(remotesAndUrls.getFirst(), remotesAndUrls.getSecond(), trackedInfos); - } - - @Nonnull - private static Collection parseTrackedInfos(@Nonnull Ini ini, @Nonnull ClassLoader classLoader) - { - Collection configs = new ArrayList<>(); - for(Map.Entry stringSectionEntry : ini.entrySet()) - { - String sectionName = stringSectionEntry.getKey(); - Profile.Section section = stringSectionEntry.getValue(); - if(sectionName.startsWith("branch")) - { - BranchConfig branchConfig = parseBranchSection(sectionName, section, classLoader); - if(branchConfig != null) - { - configs.add(branchConfig); - } - } - } - return configs; - } - - @Nullable - private static GitBranchTrackInfo convertBranchConfig(@Nullable BranchConfig branchConfig, @Nonnull Collection localBranches, @Nonnull Collection remoteBranches) - { - if(branchConfig == null) - { - return null; - } - final String branchName = branchConfig.getName(); - String remoteName = branchConfig.getBean().getRemote(); - String mergeName = branchConfig.getBean().getMerge(); - String rebaseName = branchConfig.getBean().getRebase(); - - if(StringUtil.isEmptyOrSpaces(mergeName) && StringUtil.isEmptyOrSpaces(rebaseName)) - { - LOG.info("No branch." + branchName + ".merge/rebase item in the .git/config"); - return null; - } - if(StringUtil.isEmptyOrSpaces(remoteName)) - { - LOG.info("No branch." + branchName + ".remote item in the .git/config"); - return null; - } - - boolean merge = mergeName != null; - final String remoteBranchName = StringUtil.unquoteString(merge ? mergeName : rebaseName); - - GitLocalBranch localBranch = findLocalBranch(branchName, localBranches); - GitRemoteBranch remoteBranch = findRemoteBranch(remoteBranchName, remoteName, remoteBranches); - if(localBranch == null || remoteBranch == null) - { - // obsolete record in .git/config: local or remote branch doesn't exist, but the tracking information wasn't removed - LOG.debug("localBranch: " + localBranch + ", remoteBranch: " + remoteBranch); - return null; - } - return new GitBranchTrackInfo(localBranch, remoteBranch, merge); - } - - @Nullable - private static GitLocalBranch findLocalBranch(@Nonnull String branchName, @Nonnull Collection localBranches) - { - final String name = GitBranchUtil.stripRefsPrefix(branchName); - return ContainerUtil.find(localBranches, new Condition() - { - @Override - public boolean value(@Nullable GitLocalBranch input) - { - assert input != null; - return input.getName().equals(name); - } - }); - } - - @Nullable - public static GitRemoteBranch findRemoteBranch(@Nonnull String remoteBranchName, @Nonnull final String remoteName, @Nonnull final Collection remoteBranches) - { - final String branchName = GitBranchUtil.stripRefsPrefix(remoteBranchName); - return ContainerUtil.find(remoteBranches, new Condition() - { - @Override - public boolean value(GitRemoteBranch branch) - { - return branch.getNameForRemoteOperations().equals(branchName) && branch.getRemote().getName().equals(remoteName); - } - }); - } - - @Nullable - private static BranchConfig parseBranchSection(String sectionName, Profile.Section section, @Nonnull ClassLoader classLoader) - { - BranchBean branchBean = section.as(BranchBean.class, classLoader); - Matcher matcher = BRANCH_INFO_SECTION.matcher(sectionName); - if(matcher.matches()) - { - return new BranchConfig(matcher.group(1), branchBean); - } - if(BRANCH_COMMON_PARAMS_SECTION.matcher(sectionName).matches()) - { - LOG.debug(String.format("Common branch option(s) defined .git/config. sectionName: %s%n section: %s", sectionName, section)); - return null; - } - LOG.error(String.format("Invalid branch section format in .git/config. sectionName: %s%n section: %s", sectionName, section)); - return null; - } - - @Nonnull - private static Pair, Collection> parseRemotes(@Nonnull Ini ini, @Nonnull ClassLoader classLoader) - { - Collection remotes = new ArrayList<>(); - Collection urls = new ArrayList<>(); - for(Map.Entry stringSectionEntry : ini.entrySet()) - { - String sectionName = stringSectionEntry.getKey(); - Profile.Section section = stringSectionEntry.getValue(); - - Remote remote = parseRemoteSection(sectionName, section, classLoader); - if(remote != null) - { - remotes.add(remote); - } - else - { - Url url = parseUrlSection(sectionName, section, classLoader); - if(url != null) - { - urls.add(url); - } - } - } - return Pair.create(remotes, urls); - } - - /** - *

- * 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}, - * {@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. - *

- */ - @Nonnull - private static UrlsAndPushUrls substituteUrls(@Nonnull Collection urlSections, @Nonnull Remote remote) - { - List urls = new ArrayList<>(remote.getUrls().size()); - Collection pushUrls = new ArrayList<>(); - - // urls are substituted by insteadOf - // if there are no pushUrls, we create a pushUrl for pushInsteadOf substitutions - for(final String remoteUrl : remote.getUrls()) - { - boolean substituted = false; - for(Url url : urlSections) - { - String insteadOf = url.getInsteadOf(); - String pushInsteadOf = url.getPushInsteadOf(); - // null means no entry, i.e. nothing to substitute. Empty string means substituting everything - if(insteadOf != null && remoteUrl.startsWith(insteadOf)) - { - urls.add(substituteUrl(remoteUrl, url, insteadOf)); - substituted = true; - break; - } - else if(pushInsteadOf != null && remoteUrl.startsWith(pushInsteadOf)) - { - if(remote.getPushUrls().isEmpty()) - { // only if there are no explicit pushUrls - pushUrls.add(substituteUrl(remoteUrl, url, pushInsteadOf)); // pushUrl is different - } - urls.add(remoteUrl); // but url is left intact - substituted = true; - break; - } - } - if(!substituted) - { - urls.add(remoteUrl); - } - } - - // pushUrls are substituted only by insteadOf, not by pushInsteadOf - for(final String remotePushUrl : remote.getPushUrls()) - { - boolean substituted = false; - for(Url url : urlSections) - { - String insteadOf = url.getInsteadOf(); - // null means no entry, i.e. nothing to substitute. Empty string means substituting everything - if(insteadOf != null && remotePushUrl.startsWith(insteadOf)) - { - pushUrls.add(substituteUrl(remotePushUrl, url, insteadOf)); - substituted = true; - break; - } - } - if(!substituted) - { - pushUrls.add(remotePushUrl); - } - } - - // if no pushUrls are explicitly defined yet via pushUrl or url..pushInsteadOf, they are the same as urls. - if(pushUrls.isEmpty()) - { - pushUrls = new ArrayList<>(urls); - } - - return new UrlsAndPushUrls(urls, pushUrls); - } - - private static class UrlsAndPushUrls - { - final List myUrls; - final Collection myPushUrls; - - private UrlsAndPushUrls(List urls, Collection pushUrls) - { - myPushUrls = pushUrls; - myUrls = urls; - } - - public Collection getPushUrls() - { - return myPushUrls; - } - - public List getUrls() - { - return myUrls; - } - } - - @Nonnull - private static String substituteUrl(@Nonnull String remoteUrl, @Nonnull Url url, @Nonnull String insteadOf) - { - return url.myName + remoteUrl.substring(insteadOf.length()); - } - - @Nullable - private static Remote parseRemoteSection(@Nonnull String sectionName, @Nonnull Profile.Section section, @Nonnull ClassLoader classLoader) - { - Matcher matcher = REMOTE_SECTION.matcher(sectionName); - if(matcher.matches() && matcher.groupCount() == 1) - { - return new Remote(matcher.group(1), section.as(RemoteBean.class, classLoader)); - } - return null; - } - - @Nullable - private static Url parseUrlSection(@Nonnull String sectionName, @Nonnull Profile.Section section, @Nonnull ClassLoader classLoader) - { - Matcher matcher = URL_SECTION.matcher(sectionName); - if(matcher.matches() && matcher.groupCount() == 1) - { - return new Url(matcher.group(1), section.as(UrlBean.class, classLoader)); - } - return null; - } - - private static class Remote - { - private final String myName; - private final RemoteBean myRemoteBean; - - private Remote(@Nonnull String name, @Nonnull RemoteBean remoteBean) - { - myRemoteBean = remoteBean; - myName = name; - } - - @Nonnull - private Collection getUrls() - { - return nonNullCollection(myRemoteBean.getUrl()); - } - - @Nonnull - private Collection getPushUrls() - { - return nonNullCollection(myRemoteBean.getPushUrl()); - } - - @Nonnull - private Collection getPuttyKeys() - { - return nonNullCollection(myRemoteBean.getPuttykeyfile()); - } - - @Nonnull - private List getPushSpec() - { - String[] push = myRemoteBean.getPush(); - return push == null ? Collections.emptyList() : Arrays.asList(push); - } - - @Nonnull - private List getFetchSpecs() - { - return Arrays.asList(notNull(myRemoteBean.getFetch())); - } - - } - - private interface RemoteBean - { - @Nullable - String[] getFetch(); - - @Nullable - String[] getPush(); - - @Nullable - String[] getUrl(); - - @Nullable - String[] getPushUrl(); - - @Nullable - String[] getPuttykeyfile(); - } - - private static class Url - { - private final String myName; - private final UrlBean myUrlBean; - - private Url(String name, UrlBean urlBean) - { - myUrlBean = urlBean; - myName = name; - } - - @Nullable - // null means to entry, i.e. nothing to substitute. Empty string means substituting everything - public String getInsteadOf() - { - return myUrlBean.getInsteadOf(); - } - - @Nullable - // null means to entry, i.e. nothing to substitute. Empty string means substituting everything - public String getPushInsteadOf() - { - return myUrlBean.getPushInsteadOf(); - } - } - - private interface UrlBean - { - @Nullable - String getInsteadOf(); - - @Nullable - String getPushInsteadOf(); - } - - private static class BranchConfig - { - private final String myName; - private final BranchBean myBean; - - public BranchConfig(String name, BranchBean bean) - { - myName = name; - myBean = bean; - } - - public String getName() - { - return myName; - } - - public BranchBean getBean() - { - return myBean; - } - } - - private interface BranchBean - { - @Nullable - String getRemote(); - - @Nullable - String getMerge(); - - @Nullable - String getRebase(); - } - - @Nonnull - private static String[] notNull(@Nullable String[] s) - { - return s == null ? ArrayUtil.EMPTY_STRING_ARRAY : s; - } - - @Nonnull - private static Collection nonNullCollection(@Nullable String[] array) - { - return array == null ? Collections.emptyList() : new ArrayList<>(Arrays.asList(array)); - } +public class GitConfig { + private static final Logger LOG = Logger.getInstance(GitConfig.class); + + private static final Pattern REMOTE_SECTION = Pattern.compile("(?:svn-)?remote \"(.*)\""); + private static final Pattern URL_SECTION = Pattern.compile("url \"(.*)\""); + private static final Pattern BRANCH_INFO_SECTION = Pattern.compile("branch \"(.*)\""); + private static final Pattern BRANCH_COMMON_PARAMS_SECTION = Pattern.compile("branch"); + + @Nonnull + private final Collection myRemotes; + @Nonnull + private final Collection myUrls; + @Nonnull + private final Collection myTrackedInfos; + + + private GitConfig(@Nonnull Collection remotes, @Nonnull Collection urls, @Nonnull Collection trackedInfos) { + myRemotes = remotes; + myUrls = urls; + myTrackedInfos = trackedInfos; + } + + /** + *

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}.

+ * + * @return Git remotes defined in {@code .git/config}. + */ + @Nonnull + Collection parseRemotes() { + // populate GitRemotes with substituting urls when needed + return ContainerUtil.map(myRemotes, remote -> + { + assert remote != null; + return convertRemoteToGitRemote(myUrls, remote); + }); + } + + @Nonnull + private static GitRemote convertRemoteToGitRemote(@Nonnull Collection urls, @Nonnull Remote remote) { + UrlsAndPushUrls substitutedUrls = substituteUrls(urls, remote); + return new GitRemote( + remote.myName, + substitutedUrls.getUrls(), + substitutedUrls.getPushUrls(), + remote.getFetchSpecs(), + remote.getPushSpec(), + remote.getPuttyKeys() + ); + } + + /** + * Create branch tracking information based on the information defined in {@code .git/config}. + */ + @Nonnull + Collection parseTrackInfos( + @Nonnull Collection localBranches, + @Nonnull Collection remoteBranches + ) { + return ContainerUtil.mapNotNull(myTrackedInfos, config -> + { + if (config != null) { + return convertBranchConfig(config, localBranches, remoteBranches); + } + 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. + */ + @Nonnull + static GitConfig read(@Nonnull File configFile) { + GitConfig emptyConfig = + new GitConfig(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + if (!configFile.exists()) { + LOG.info("No .git/config file at " + configFile.getPath()); + return emptyConfig; + } + + Ini ini; + try { + ini = GitConfigHelper.loadIniFile(configFile); + } + catch (IOException e) { + return emptyConfig; + } + + Pair, Collection> remotesAndUrls = parseRemotes(ini, GitConfig.class.getClassLoader()); + Collection trackedInfos = parseTrackedInfos(ini, GitConfig.class.getClassLoader()); + + return new GitConfig(remotesAndUrls.getFirst(), remotesAndUrls.getSecond(), trackedInfos); + } + + @Nonnull + private static Collection parseTrackedInfos(@Nonnull Ini ini, @Nonnull ClassLoader classLoader) { + Collection configs = new ArrayList<>(); + for (Map.Entry stringSectionEntry : ini.entrySet()) { + String sectionName = stringSectionEntry.getKey(); + Profile.Section section = stringSectionEntry.getValue(); + if (sectionName.startsWith("branch")) { + BranchConfig branchConfig = parseBranchSection(sectionName, section, classLoader); + if (branchConfig != null) { + configs.add(branchConfig); + } + } + } + return configs; + } + + @Nullable + private static GitBranchTrackInfo convertBranchConfig( + @Nullable BranchConfig branchConfig, + @Nonnull Collection localBranches, + @Nonnull Collection remoteBranches + ) { + if (branchConfig == null) { + return null; + } + String branchName = branchConfig.getName(); + String remoteName = branchConfig.getBean().getRemote(); + String mergeName = branchConfig.getBean().getMerge(); + String rebaseName = branchConfig.getBean().getRebase(); + + if (StringUtil.isEmptyOrSpaces(mergeName) && StringUtil.isEmptyOrSpaces(rebaseName)) { + LOG.info("No branch." + branchName + ".merge/rebase item in the .git/config"); + return null; + } + if (StringUtil.isEmptyOrSpaces(remoteName)) { + LOG.info("No branch." + branchName + ".remote item in the .git/config"); + return null; + } + + boolean merge = mergeName != null; + String remoteBranchName = StringUtil.unquoteString(merge ? mergeName : rebaseName); + + GitLocalBranch localBranch = findLocalBranch(branchName, localBranches); + GitRemoteBranch remoteBranch = findRemoteBranch(remoteBranchName, remoteName, remoteBranches); + if (localBranch == null || remoteBranch == null) { + // obsolete record in .git/config: local or remote branch doesn't exist, but the tracking information wasn't removed + LOG.debug("localBranch: " + localBranch + ", remoteBranch: " + remoteBranch); + return null; + } + return new GitBranchTrackInfo(localBranch, remoteBranch, merge); + } + + @Nullable + private static GitLocalBranch findLocalBranch(@Nonnull String branchName, @Nonnull Collection localBranches) { + String name = GitBranchUtil.stripRefsPrefix(branchName); + return ContainerUtil.find( + localBranches, + input -> { + assert input != null; + return input.getName().equals(name); + } + ); + } + + @Nullable + public static GitRemoteBranch findRemoteBranch( + @Nonnull String remoteBranchName, + @Nonnull String remoteName, + @Nonnull Collection remoteBranches + ) { + String branchName = GitBranchUtil.stripRefsPrefix(remoteBranchName); + return ContainerUtil.find( + remoteBranches, + branch -> branch.getNameForRemoteOperations().equals(branchName) + && branch.getRemote().getName().equals(remoteName) + ); + } + + @Nullable + private static BranchConfig parseBranchSection(String sectionName, Profile.Section section, @Nonnull ClassLoader classLoader) { + BranchBean branchBean = section.as(BranchBean.class, classLoader); + Matcher matcher = BRANCH_INFO_SECTION.matcher(sectionName); + if (matcher.matches()) { + return new BranchConfig(matcher.group(1), branchBean); + } + if (BRANCH_COMMON_PARAMS_SECTION.matcher(sectionName).matches()) { + LOG.debug(String.format("Common branch option(s) defined .git/config. sectionName: %s%n section: %s", sectionName, section)); + return null; + } + LOG.error(String.format("Invalid branch section format in .git/config. sectionName: %s%n section: %s", sectionName, section)); + return null; + } + + @Nonnull + private static Pair, Collection> parseRemotes(@Nonnull Ini ini, @Nonnull ClassLoader classLoader) { + Collection remotes = new ArrayList<>(); + Collection urls = new ArrayList<>(); + for (Map.Entry stringSectionEntry : ini.entrySet()) { + String sectionName = stringSectionEntry.getKey(); + Profile.Section section = stringSectionEntry.getValue(); + + Remote remote = parseRemoteSection(sectionName, section, classLoader); + if (remote != null) { + remotes.add(remote); + } + else { + Url url = parseUrlSection(sectionName, section, classLoader); + if (url != null) { + urls.add(url); + } + } + } + return Pair.create(remotes, urls); + } + + /** + *

+ * 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}, + * {@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. + *

+ */ + @Nonnull + private static UrlsAndPushUrls substituteUrls(@Nonnull Collection urlSections, @Nonnull Remote remote) { + List urls = new ArrayList<>(remote.getUrls().size()); + Collection pushUrls = new ArrayList<>(); + + // urls are substituted by insteadOf + // if there are no pushUrls, we create a pushUrl for pushInsteadOf substitutions + for (String remoteUrl : remote.getUrls()) { + boolean substituted = false; + for (Url url : urlSections) { + String insteadOf = url.getInsteadOf(); + String pushInsteadOf = url.getPushInsteadOf(); + // null means no entry, i.e. nothing to substitute. Empty string means substituting everything + if (insteadOf != null && remoteUrl.startsWith(insteadOf)) { + urls.add(substituteUrl(remoteUrl, url, insteadOf)); + substituted = true; + break; + } + else if (pushInsteadOf != null && remoteUrl.startsWith(pushInsteadOf)) { + if (remote.getPushUrls().isEmpty()) { // only if there are no explicit pushUrls + pushUrls.add(substituteUrl(remoteUrl, url, pushInsteadOf)); // pushUrl is different + } + urls.add(remoteUrl); // but url is left intact + substituted = true; + break; + } + } + if (!substituted) { + urls.add(remoteUrl); + } + } + + // pushUrls are substituted only by insteadOf, not by pushInsteadOf + for (String remotePushUrl : remote.getPushUrls()) { + boolean substituted = false; + for (Url url : urlSections) { + String insteadOf = url.getInsteadOf(); + // null means no entry, i.e. nothing to substitute. Empty string means substituting everything + if (insteadOf != null && remotePushUrl.startsWith(insteadOf)) { + pushUrls.add(substituteUrl(remotePushUrl, url, insteadOf)); + substituted = true; + break; + } + } + if (!substituted) { + pushUrls.add(remotePushUrl); + } + } + + // if no pushUrls are explicitly defined yet via pushUrl or url..pushInsteadOf, they are the same as urls. + if (pushUrls.isEmpty()) { + pushUrls = new ArrayList<>(urls); + } + + return new UrlsAndPushUrls(urls, pushUrls); + } + + private static class UrlsAndPushUrls { + final List myUrls; + final Collection myPushUrls; + + private UrlsAndPushUrls(List urls, Collection pushUrls) { + myPushUrls = pushUrls; + myUrls = urls; + } + + public Collection getPushUrls() { + return myPushUrls; + } + + public List getUrls() { + return myUrls; + } + } + + @Nonnull + private static String substituteUrl(@Nonnull String remoteUrl, @Nonnull Url url, @Nonnull String insteadOf) { + return url.myName + remoteUrl.substring(insteadOf.length()); + } + + @Nullable + private static Remote parseRemoteSection( + @Nonnull String sectionName, + @Nonnull Profile.Section section, + @Nonnull ClassLoader classLoader + ) { + Matcher matcher = REMOTE_SECTION.matcher(sectionName); + if (matcher.matches() && matcher.groupCount() == 1) { + return new Remote(matcher.group(1), section.as(RemoteBean.class, classLoader)); + } + return null; + } + + @Nullable + private static Url parseUrlSection(@Nonnull String sectionName, @Nonnull Profile.Section section, @Nonnull ClassLoader classLoader) { + Matcher matcher = URL_SECTION.matcher(sectionName); + if (matcher.matches() && matcher.groupCount() == 1) { + return new Url(matcher.group(1), section.as(UrlBean.class, classLoader)); + } + return null; + } + + private static class Remote { + private final String myName; + private final RemoteBean myRemoteBean; + + private Remote(@Nonnull String name, @Nonnull RemoteBean remoteBean) { + myRemoteBean = remoteBean; + myName = name; + } + + @Nonnull + private Collection getUrls() { + return nonNullCollection(myRemoteBean.getUrl()); + } + + @Nonnull + private Collection getPushUrls() { + return nonNullCollection(myRemoteBean.getPushUrl()); + } + + @Nonnull + private Collection getPuttyKeys() { + return nonNullCollection(myRemoteBean.getPuttykeyfile()); + } + + @Nonnull + private List getPushSpec() { + String[] push = myRemoteBean.getPush(); + return push == null ? Collections.emptyList() : Arrays.asList(push); + } + + @Nonnull + private List getFetchSpecs() { + return Arrays.asList(notNull(myRemoteBean.getFetch())); + } + + } + + private interface RemoteBean { + @Nullable + String[] getFetch(); + + @Nullable + String[] getPush(); + + @Nullable + String[] getUrl(); + + @Nullable + String[] getPushUrl(); + + @Nullable + String[] getPuttykeyfile(); + } + + private static class Url { + private final String myName; + private final UrlBean myUrlBean; + + private Url(String name, UrlBean urlBean) { + myUrlBean = urlBean; + myName = name; + } + + @Nullable + // null means to entry, i.e. nothing to substitute. Empty string means substituting everything + public String getInsteadOf() { + return myUrlBean.getInsteadOf(); + } + + @Nullable + // null means to entry, i.e. nothing to substitute. Empty string means substituting everything + public String getPushInsteadOf() { + return myUrlBean.getPushInsteadOf(); + } + } + + private interface UrlBean { + @Nullable + String getInsteadOf(); + + @Nullable + String getPushInsteadOf(); + } + + private static class BranchConfig { + private final String myName; + private final BranchBean myBean; + + public BranchConfig(String name, BranchBean bean) { + myName = name; + myBean = bean; + } + + public String getName() { + return myName; + } + + public BranchBean getBean() { + return myBean; + } + } + + private interface BranchBean { + @Nullable + String getRemote(); + + @Nullable + String getMerge(); + + @Nullable + String getRebase(); + } + + @Nonnull + private static String[] notNull(@Nullable String[] s) { + return s == null ? ArrayUtil.EMPTY_STRING_ARRAY : s; + } + + @Nonnull + private static Collection nonNullCollection(@Nullable String[] array) { + return array == null ? Collections.emptyList() : new ArrayList<>(Arrays.asList(array)); + } } \ No newline at end of file diff --git a/plugin/src/main/java/git4idea/reset/GitResetAction.java b/plugin/src/main/java/git4idea/reset/GitResetAction.java index b4d91da..e7f40b3 100644 --- a/plugin/src/main/java/git4idea/reset/GitResetAction.java +++ b/plugin/src/main/java/git4idea/reset/GitResetAction.java @@ -48,7 +48,7 @@ protected void actionPerformed(@Nonnull final Project project, @Nonnull final Ma if (dialog.showAndGet()) { final GitResetMode selectedMode = dialog.getResetMode(); settings.setResetMode(selectedMode); - new Task.Backgroundable(project, GitLocalize.dialogResetTitle(), true) { + new Task.Backgroundable(project, GitLocalize.dialogTitleReset(), true) { @Override public void run(@Nonnull ProgressIndicator indicator) { Map hashes = commits.keySet().stream() diff --git a/plugin/src/main/java/git4idea/ui/branch/GitBranchManager.java b/plugin/src/main/java/git4idea/ui/branch/GitBranchManager.java index 3132c84..8a388bd 100644 --- a/plugin/src/main/java/git4idea/ui/branch/GitBranchManager.java +++ b/plugin/src/main/java/git4idea/ui/branch/GitBranchManager.java @@ -91,13 +91,11 @@ public void setFavorite( mySettings.addToFavorites(branchType, repository, branchName); mySettings.removeFromExcluded(branchType, repository, branchName); } - else { - if (mySettings.isFavorite(branchType, repository, branchName)) { - mySettings.removeFromFavorites(branchType, repository, branchName); - } - else if (myPredefinedFavoriteBranches.contains(branchType.toString(), repository, branchName)) { - mySettings.excludedFromFavorites(branchType, repository, branchName); - } + else if (mySettings.isFavorite(branchType, repository, branchName)) { + mySettings.removeFromFavorites(branchType, repository, branchName); + } + else if (myPredefinedFavoriteBranches.contains(branchType.toString(), repository, branchName)) { + mySettings.excludedFromFavorites(branchType, repository, branchName); } } } diff --git a/plugin/src/main/java/git4idea/ui/branch/GitBranchPopup.java b/plugin/src/main/java/git4idea/ui/branch/GitBranchPopup.java index 7f3e63d..2c03a29 100644 --- a/plugin/src/main/java/git4idea/ui/branch/GitBranchPopup.java +++ b/plugin/src/main/java/git4idea/ui/branch/GitBranchPopup.java @@ -15,7 +15,7 @@ */ package git4idea.ui.branch; -import consulo.localize.LocalizeValue; +import consulo.git.localize.GitBranchesLocalize; import consulo.project.Project; import consulo.ui.ex.action.ActionGroup; import consulo.ui.ex.action.AnAction; @@ -45,7 +45,6 @@ /** * The popup which allows to quickly switch and control Git branches. - *

*/ class GitBranchPopup extends DvcsBranchPopup { private static final String DIMENSION_SERVICE_KEY = "Git.Branch.Popup"; @@ -58,8 +57,8 @@ class GitBranchPopup extends DvcsBranchPopup { * In the case of synchronized branch operations current repository matter much less, but sometimes is used, * for example, it is preselected in the repositories combobox in the compare branches dialog. */ - static GitBranchPopup getInstance(@Nonnull final Project project, @Nonnull GitRepository currentRepository) { - final GitVcsSettings vcsSettings = GitVcsSettings.getInstance(project); + static GitBranchPopup getInstance(@Nonnull Project project, @Nonnull GitRepository currentRepository) { + GitVcsSettings vcsSettings = GitVcsSettings.getInstance(project); Predicate preselectActionCondition = action -> { if (action instanceof GitBranchPopupActions.LocalBranchActions) { GitBranchPopupActions.LocalBranchActions branchAction = (GitBranchPopupActions.LocalBranchActions)action; @@ -110,7 +109,7 @@ protected void fillWithCommonRepositoryActions( popupGroup.addAll(createRepositoriesActions()); - popupGroup.addSeparator(LocalizeValue.localizeTODO("Common Local Branches")); + popupGroup.addSeparator(GitBranchesLocalize.actionCommonLocalBranchesText()); List localBranchActions = myMultiRootBranchConfig.getLocalBranchNames() .stream() .map(l -> createLocalBranchActions(allRepositories, l)) @@ -123,7 +122,7 @@ protected void fillWithCommonRepositoryActions( BranchActionUtil.getNumOfTopShownBranches(localBranchActions), SHOW_ALL_LOCALS_KEY ); - popupGroup.addSeparator(LocalizeValue.localizeTODO("Common Remote Branches")); + popupGroup.addSeparator(GitBranchesLocalize.actionCommonRemoteBranchesText()); List remoteBranchActions = map( ((GitMultiRootBranchConfig)myMultiRootBranchConfig).getRemoteBranches(), remoteBranch -> new GitBranchPopupActions.RemoteBranchActions(myProject, allRepositories, remoteBranch, myCurrentRepository) @@ -143,19 +142,16 @@ private GitBranchPopupActions.LocalBranchActions createLocalBranchActions( @Nonnull String branch ) { List repositories = filterRepositoriesNotOnThisBranch(branch, allRepositories); - return repositories.isEmpty() ? null : new GitBranchPopupActions.LocalBranchActions( - myProject, - repositories, - branch, - myCurrentRepository - ); + return repositories.isEmpty() + ? null + : new GitBranchPopupActions.LocalBranchActions(myProject, repositories, branch, myCurrentRepository); } @Nonnull @Override protected ActionGroup createRepositoriesActions() { ActionGroup.Builder popupGroup = ActionGroup.newImmutableBuilder(); - popupGroup.addSeparator(LocalizeValue.localizeTODO("Repositories")); + popupGroup.addSeparator(GitBranchesLocalize.actionRepositoriesText()); List rootActions = DvcsUtil.sortRepositories(myRepositoryManager.getRepositories()) .stream() .map(repo -> new RootAction<>( @@ -177,10 +173,7 @@ protected ActionGroup createRepositoriesActions() { @Override protected void fillPopupWithCurrentRepositoryActions(@Nonnull ActionGroup.Builder popupGroup, @Nullable ActionGroup actions) { - popupGroup.addAll(new GitBranchPopupActions(myCurrentRepository.getProject(), myCurrentRepository).createActions( - actions, - myRepoTitleInfo, - true - )); + popupGroup.addAll(new GitBranchPopupActions(myCurrentRepository.getProject(), myCurrentRepository) + .createActions(actions, myRepoTitleInfo, true)); } } diff --git a/plugin/src/main/java/git4idea/ui/branch/GitBranchPopupActions.java b/plugin/src/main/java/git4idea/ui/branch/GitBranchPopupActions.java index dcac63d..dc80d1a 100644 --- a/plugin/src/main/java/git4idea/ui/branch/GitBranchPopupActions.java +++ b/plugin/src/main/java/git4idea/ui/branch/GitBranchPopupActions.java @@ -15,7 +15,7 @@ */ package git4idea.ui.branch; -import consulo.ide.ServiceManager; +import consulo.git.localize.GitBranchesLocalize; import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.ui.annotation.RequiredUIAccess; @@ -68,7 +68,7 @@ ActionGroup createActions(@Nullable ActionGroup toInsert, @Nonnull String repoIn popupGroup.addAll(toInsert); } - popupGroup.addSeparator(LocalizeValue.localizeTODO("Local Branches" + repoInfo)); + popupGroup.addSeparator(GitBranchesLocalize.actionLocalBranches0Text(repoInfo)); List localBranchActions = myRepository.getBranches() .getLocalBranches() .stream() @@ -86,7 +86,7 @@ ActionGroup createActions(@Nullable ActionGroup toInsert, @Nonnull String repoIn firstLevelGroup ); - popupGroup.addSeparator(LocalizeValue.localizeTODO("Remote Branches" + repoInfo)); + popupGroup.addSeparator(GitBranchesLocalize.actionRemoteBranches0Text(repoInfo)); List remoteBranchActions = myRepository.getBranches() .getRemoteBranches() @@ -114,12 +114,12 @@ public GitNewBranchAction(@Nonnull Project project, @Nonnull List super(project, repositories); } - @RequiredUIAccess @Override + @RequiredUIAccess public void actionPerformed(@Nonnull AnActionEvent e) { - final String name = GitBranchUtil.getNewBranchNameFromUser(myProject, myRepositories, "Create New Branch"); + String name = GitBranchUtil.getNewBranchNameFromUser(myProject, myRepositories, GitBranchesLocalize.dialogTitleCreateNewBranch()); if (name != null) { - GitBrancher brancher = ServiceManager.getService(myProject, GitBrancher.class); + GitBrancher brancher = myProject.getInstance(GitBrancher.class); brancher.checkoutNewBranch(name, myRepositories); reportUsage("git.branch.create.new"); } @@ -134,7 +134,7 @@ public static class CheckoutRevisionActions extends DumbAwareAction { private final List myRepositories; CheckoutRevisionActions(Project project, List repositories) { - super("Checkout Tag or Revision..."); + super(GitBranchesLocalize.actionCheckoutTagOrRevisionText()); myProject = project; myRepositories = repositories; } @@ -144,8 +144,12 @@ public static class CheckoutRevisionActions extends DumbAwareAction { public void actionPerformed(@Nonnull AnActionEvent e) { // TODO autocomplete branches, tags. // on type check ref validity, on OK check ref existence. - String reference = - Messages.showInputDialog(myProject, "Enter reference (branch, tag) name or commit hash:", "Checkout", null); + String reference = Messages.showInputDialog( + myProject, + GitBranchesLocalize.dialogMessageEnterReferenceBranchTagNameOrCommitHash().get(), + GitBranchesLocalize.dialogTitleCheckout().get(), + null + ); if (reference != null) { GitBrancher brancher = myProject.getInstance(GitBrancher.class); brancher.checkout(reference, true, myRepositories, null); @@ -159,7 +163,7 @@ public void update(@Nonnull AnActionEvent e) { boolean isFresh = ContainerUtil.and(myRepositories, Repository::isFresh); if (isFresh) { e.getPresentation().setEnabled(false); - e.getPresentation().setDescription("Checkout is not possible before the first commit"); + e.getPresentation().setDescriptionValue(GitBranchesLocalize.actionCheckoutTagOrRevisionImpossibleDescription()); } } } @@ -167,15 +171,21 @@ public void update(@Nonnull AnActionEvent e) { /** * Actions available for local branches. */ - static class LocalBranchActions extends BranchActionGroup implements PopupElementWithAdditionalInfo { - private final Project myProject; - private final List myRepositories; - private final String myBranchName; + private abstract static class AbstractBranchActions extends BranchActionGroup { + @Nonnull + protected final Project myProject; + @Nonnull + protected final List myRepositories; + @Nonnull + protected final String myBranchName; @Nonnull - private final GitRepository mySelectedRepository; - private final GitBranchManager myGitBranchManager; + protected final GitRepository mySelectedRepository; + @Nonnull + protected final GitBrancher myBrancher; + @Nonnull + protected final GitBranchManager myGitBranchManager; - LocalBranchActions( + protected AbstractBranchActions( @Nonnull Project project, @Nonnull List repositories, @Nonnull String branchName, @@ -185,15 +195,89 @@ static class LocalBranchActions extends BranchActionGroup implements PopupElemen myRepositories = repositories; myBranchName = branchName; mySelectedRepository = selectedRepository; + + myBrancher = myProject.getInstance(GitBrancher.class); myGitBranchManager = project.getInstance(GitBranchManager.class); + getTemplatePresentation().setDisabledMnemonic(true); - getTemplatePresentation().setTextValue(LocalizeValue.of(calcBranchText())); - setFavorite(myGitBranchManager.isFavorite(LOCAL, repositories.size() > 1 ? null : mySelectedRepository, myBranchName)); + getTemplatePresentation().setTextValue(LocalizeValue.of(branchName)); } - @Nonnull - private String calcBranchText() { - return myBranchName; + protected class CompareAction extends DumbAwareAction { + public CompareAction() { + super(GitBranchesLocalize.actionCompareText()); + } + + @Override + @RequiredUIAccess + public void actionPerformed(@Nonnull AnActionEvent e) { + myBrancher.compare(myBranchName, myRepositories, mySelectedRepository); + reportUsage("git.branch.compare"); + } + } + + protected class MergeAction extends DumbAwareAction { + private final boolean myLocalBranch; + + public MergeAction(boolean localBranch) { + super(GitBranchesLocalize.actionMergeText()); + myLocalBranch = localBranch; + } + + @Override + @RequiredUIAccess + public void actionPerformed(@Nonnull AnActionEvent e) { + myBrancher.merge(myBranchName, deleteOnMerge(), myRepositories); + reportUsage("git.branch.merge"); + } + + private GitBrancher.DeleteOnMergeOption deleteOnMerge() { + if (myLocalBranch && !myBranchName.equals("master")) { + return GitBrancher.DeleteOnMergeOption.PROPOSE; + } + return GitBrancher.DeleteOnMergeOption.NOTHING; + } + } + + protected class RebaseAction extends DumbAwareAction { + public RebaseAction() { + super(GitBranchesLocalize.actionRebaseOntoText()); + } + + @Override + @RequiredUIAccess + public void actionPerformed(@Nonnull AnActionEvent e) { + myBrancher.rebase(myRepositories, myBranchName); + reportUsage("git.branch.rebase"); + } + } + + protected class CheckoutWithRebaseAction extends DumbAwareAction { + public CheckoutWithRebaseAction() { + super( + GitBranchesLocalize.actionCheckoutWithRebaseText(), + GitBranchesLocalize.actionCheckoutWithRebase0Description(myBranchName) + ); + } + + @Override + @RequiredUIAccess + public void actionPerformed(@Nonnull AnActionEvent e) { + myBrancher.rebaseOnCurrent(myRepositories, myBranchName); + reportUsage("git.branch.checkout.with.rebase"); + } + } + } + + static class LocalBranchActions extends AbstractBranchActions implements PopupElementWithAdditionalInfo { + LocalBranchActions( + @Nonnull Project project, + @Nonnull List repositories, + @Nonnull String branchName, + @Nonnull GitRepository selectedRepository + ) { + super(project, repositories, branchName, selectedRepository); + setFavorite(myGitBranchManager.isFavorite(LOCAL, repositories.size() > 1 ? null : mySelectedRepository, myBranchName)); } @Nonnull @@ -210,14 +294,14 @@ public String getBranchName() { @Override public AnAction[] getChildren(@Nullable AnActionEvent e) { return new AnAction[]{ - new CheckoutAction(myProject, myRepositories, myBranchName), - new CheckoutAsNewBranch(myProject, myRepositories, myBranchName), - new CompareAction(myProject, myRepositories, myBranchName, mySelectedRepository), - new RebaseAction(myProject, myRepositories, myBranchName), - new CheckoutWithRebaseAction(myProject, myRepositories, myBranchName), - new MergeAction(myProject, myRepositories, myBranchName, true), - new RenameBranchAction(myProject, myRepositories, myBranchName), - new DeleteAction(myProject, myRepositories, myBranchName) + new CheckoutAction(), + new CheckoutAsNewBranch(), + new CompareAction(), + new RebaseAction(), + new CheckoutWithRebaseAction(), + new MergeAction(true), + new RenameBranchAction(), + new DeleteAction() }; } @@ -233,75 +317,45 @@ public void toggle() { myGitBranchManager.setFavorite(LOCAL, myRepositories.size() > 1 ? null : mySelectedRepository, myBranchName, isFavorite()); } - static class CheckoutAction extends DumbAwareAction { - private final Project myProject; - private final List myRepositories; - private final String myBranchName; - - CheckoutAction(@Nonnull Project project, @Nonnull List repositories, @Nonnull String branchName) { - super("Checkout"); - myProject = project; - myRepositories = repositories; - myBranchName = branchName; + private class CheckoutAction extends DumbAwareAction { + CheckoutAction() { + super(GitBranchesLocalize.actionCheckoutText()); } @Override @RequiredUIAccess public void actionPerformed(@Nonnull AnActionEvent e) { - GitBrancher brancher = ServiceManager.getService(myProject, GitBrancher.class); - brancher.checkout(myBranchName, false, myRepositories, null); + myBrancher.checkout(myBranchName, false, myRepositories, null); reportUsage("git.branch.checkout.local"); } } - private static class CheckoutAsNewBranch extends DumbAwareAction { - private final Project myProject; - private final List myRepositories; - private final String myBranchName; - - CheckoutAsNewBranch(@Nonnull Project project, @Nonnull List repositories, @Nonnull String branchName) { - super("Checkout as New Branch"); - myProject = project; - myRepositories = repositories; - myBranchName = branchName; + private class CheckoutAsNewBranch extends DumbAwareAction { + CheckoutAsNewBranch() { + super(GitBranchesLocalize.actionCheckoutAsNewBranchText()); } @Override @RequiredUIAccess public void actionPerformed(@Nonnull AnActionEvent e) { - final String name = Messages.showInputDialog( + String name = Messages.showInputDialog( myProject, - "New branch name:", - "Checkout New Branch From " + myBranchName, + GitBranchesLocalize.dialogMessageNewBranchName().get(), + GitBranchesLocalize.dialogTitleCheckoutNewBranchFrom0(myBranchName).get(), null, "", GitNewBranchNameValidator.newInstance(myRepositories) ); if (name != null) { - GitBrancher brancher = ServiceManager.getService(myProject, GitBrancher.class); - brancher.checkoutNewBranchStartingFrom(name, myBranchName, myRepositories, null); + myBrancher.checkoutNewBranchStartingFrom(name, myBranchName, myRepositories, null); } reportUsage("git.checkout.as.new.branch"); } } - private static class RenameBranchAction extends DumbAwareAction { - @Nonnull - private final Project myProject; - @Nonnull - private final List myRepositories; - @Nonnull - private final String myCurrentBranchName; - - public RenameBranchAction( - @Nonnull Project project, - @Nonnull List repositories, - @Nonnull String currentBranchName - ) { + private class RenameBranchAction extends DumbAwareAction { + public RenameBranchAction() { super("Rename"); - myProject = project; - myRepositories = repositories; - myCurrentBranchName = currentBranchName; } @Override @@ -309,37 +363,28 @@ public RenameBranchAction( public void actionPerformed(@Nonnull AnActionEvent e) { String newName = Messages.showInputDialog( myProject, - "New name for the branch '" + myCurrentBranchName + "':", - "Rename Branch " + myCurrentBranchName, + GitBranchesLocalize.dialogMessageNewNameForTheBranch0(myBranchName).get(), + GitBranchesLocalize.dialogTitleRenameBranch0(myBranchName).get(), null, - myCurrentBranchName, + myBranchName, GitNewBranchNameValidator.newInstance(myRepositories) ); if (newName != null) { - GitBrancher brancher = ServiceManager.getService(myProject, GitBrancher.class); - brancher.renameBranch(myCurrentBranchName, newName, myRepositories); + myBrancher.renameBranch(myBranchName, newName, myRepositories); reportUsage("git.branch.rename"); } } } - private static class DeleteAction extends DumbAwareAction { - private final Project myProject; - private final List myRepositories; - private final String myBranchName; - - DeleteAction(Project project, List repositories, String branchName) { - super("Delete"); - myProject = project; - myRepositories = repositories; - myBranchName = branchName; + private class DeleteAction extends DumbAwareAction { + DeleteAction() { + super(GitBranchesLocalize.actionDeleteText()); } @Override @RequiredUIAccess public void actionPerformed(@Nonnull AnActionEvent e) { - GitBrancher brancher = myProject.getInstance(GitBrancher.class); - brancher.deleteBranch(myBranchName, myRepositories); + myBrancher.deleteBranch(myBranchName, myRepositories); reportUsage("git.branch.delete.local"); } } @@ -348,30 +393,14 @@ public void actionPerformed(@Nonnull AnActionEvent e) { /** * Actions available for remote branches */ - static class RemoteBranchActions extends BranchActionGroup { - - private final Project myProject; - private final List myRepositories; - private final String myBranchName; - @Nonnull - private final GitRepository mySelectedRepository; - @Nonnull - private final GitBranchManager myGitBranchManager; - + static class RemoteBranchActions extends AbstractBranchActions { RemoteBranchActions( @Nonnull Project project, @Nonnull List repositories, @Nonnull String branchName, @Nonnull GitRepository selectedRepository ) { - - myProject = project; - myRepositories = repositories; - myBranchName = branchName; - mySelectedRepository = selectedRepository; - myGitBranchManager = ServiceManager.getService(project, GitBranchManager.class); - getTemplatePresentation().setDisabledMnemonic(true); - getTemplatePresentation().setTextValue(LocalizeValue.of(branchName)); + super(project, repositories, branchName, selectedRepository); setFavorite(myGitBranchManager.isFavorite(REMOTE, repositories.size() > 1 ? null : mySelectedRepository, myBranchName)); } @@ -385,44 +414,32 @@ public void toggle() { @Override public AnAction[] getChildren(@Nullable AnActionEvent e) { return new AnAction[]{ - new CheckoutRemoteBranchAction(myProject, myRepositories, myBranchName), - new CompareAction(myProject, myRepositories, myBranchName, mySelectedRepository), - new RebaseAction(myProject, myRepositories, myBranchName), - new MergeAction(myProject, myRepositories, myBranchName, false), - new RemoteDeleteAction(myProject, myRepositories, myBranchName) + new CheckoutRemoteBranchAction(), + new CompareAction(), + new RebaseAction(), + new MergeAction(false), + new RemoteDeleteAction() }; } - static class CheckoutRemoteBranchAction extends DumbAwareAction { - private final Project myProject; - private final List myRepositories; - private final String myRemoteBranchName; - - public CheckoutRemoteBranchAction( - @Nonnull Project project, - @Nonnull List repositories, - @Nonnull String remoteBranchName - ) { - super("Checkout as new local branch"); - myProject = project; - myRepositories = repositories; - myRemoteBranchName = remoteBranchName; + private class CheckoutRemoteBranchAction extends DumbAwareAction { + public CheckoutRemoteBranchAction() { + super(GitBranchesLocalize.actionCheckoutAsNewLocalBranchText()); } @Override @RequiredUIAccess public void actionPerformed(@Nonnull AnActionEvent e) { - final String name = Messages.showInputDialog( + String name = Messages.showInputDialog( myProject, - "New branch name:", - "Checkout Remote Branch", + GitBranchesLocalize.dialogMessageNewBranchName().get(), + GitBranchesLocalize.dialogTitleCheckoutRemoteBranch().get(), null, guessBranchName(), GitNewBranchNameValidator.newInstance(myRepositories) ); if (name != null) { - GitBrancher brancher = ServiceManager.getService(myProject, GitBrancher.class); - brancher.checkoutNewBranchStartingFrom(name, myRemoteBranchName, myRepositories, null); + myBrancher.checkoutNewBranchStartingFrom(name, myBranchName, myRepositories, null); reportUsage("git.branch.checkout.remote"); } } @@ -430,141 +447,23 @@ public void actionPerformed(@Nonnull AnActionEvent e) { private String guessBranchName() { // TODO: check if we already have a branch with that name; // TODO: check if that branch tracks this remote branch. Show different messages - int slashPosition = myRemoteBranchName.indexOf("/"); + int slashPosition = myBranchName.indexOf("/"); // if no slash is found (for example, in the case of git-svn remote branches), propose the whole name. - return myRemoteBranchName.substring(slashPosition + 1); + return myBranchName.substring(slashPosition + 1); } } - private static class RemoteDeleteAction extends DumbAwareAction { - private final Project myProject; - private final List myRepositories; - private final String myBranchName; - - RemoteDeleteAction(@Nonnull Project project, @Nonnull List repositories, @Nonnull String branchName) { - super("Delete"); - myProject = project; - myRepositories = repositories; - myBranchName = branchName; + private class RemoteDeleteAction extends DumbAwareAction { + RemoteDeleteAction() { + super(GitBranchesLocalize.actionDeleteText()); } @Override @RequiredUIAccess public void actionPerformed(@Nonnull AnActionEvent e) { - GitBrancher brancher = ServiceManager.getService(myProject, GitBrancher.class); - brancher.deleteRemoteBranch(myBranchName, myRepositories); + myBrancher.deleteRemoteBranch(myBranchName, myRepositories); reportUsage("git.branch.delete.remote"); } } } - - private static class CompareAction extends DumbAwareAction { - private final Project myProject; - private final List myRepositories; - private final String myBranchName; - private final GitRepository mySelectedRepository; - - public CompareAction( - @Nonnull Project project, - @Nonnull List repositories, - @Nonnull String branchName, - @Nonnull GitRepository selectedRepository - ) { - super("Compare"); - myProject = project; - myRepositories = repositories; - myBranchName = branchName; - mySelectedRepository = selectedRepository; - } - - @Override - @RequiredUIAccess - public void actionPerformed(@Nonnull AnActionEvent e) { - GitBrancher brancher = ServiceManager.getService(myProject, GitBrancher.class); - brancher.compare(myBranchName, myRepositories, mySelectedRepository); - reportUsage("git.branch.compare"); - } - } - - private static class MergeAction extends DumbAwareAction { - private final Project myProject; - private final List myRepositories; - private final String myBranchName; - private final boolean myLocalBranch; - - public MergeAction( - @Nonnull Project project, - @Nonnull List repositories, - @Nonnull String branchName, - boolean localBranch - ) { - super("Merge"); - myProject = project; - myRepositories = repositories; - myBranchName = branchName; - myLocalBranch = localBranch; - } - - @Override - @RequiredUIAccess - public void actionPerformed(@Nonnull AnActionEvent e) { - GitBrancher brancher = ServiceManager.getService(myProject, GitBrancher.class); - brancher.merge(myBranchName, deleteOnMerge(), myRepositories); - reportUsage("git.branch.merge"); - } - - private GitBrancher.DeleteOnMergeOption deleteOnMerge() { - if (myLocalBranch && !myBranchName.equals("master")) { - return GitBrancher.DeleteOnMergeOption.PROPOSE; - } - return GitBrancher.DeleteOnMergeOption.NOTHING; - } - } - - private static class RebaseAction extends DumbAwareAction { - private final Project myProject; - private final List myRepositories; - private final String myBranchName; - - public RebaseAction(@Nonnull Project project, @Nonnull List repositories, @Nonnull String branchName) { - super("Rebase onto"); - myProject = project; - myRepositories = repositories; - myBranchName = branchName; - } - - @Override - @RequiredUIAccess - public void actionPerformed(@Nonnull AnActionEvent e) { - GitBrancher brancher = ServiceManager.getService(myProject, GitBrancher.class); - brancher.rebase(myRepositories, myBranchName); - reportUsage("git.branch.rebase"); - } - } - - private static class CheckoutWithRebaseAction extends DumbAwareAction { - private final Project myProject; - private final List myRepositories; - private final String myBranchName; - - public CheckoutWithRebaseAction(@Nonnull Project project, @Nonnull List repositories, @Nonnull String branchName) { - super( - "Checkout with Rebase", - "Checkout the given branch, and rebase it on current branch in one step, " + - "just like `git rebase HEAD " + branchName + "` would do.", - null - ); - myProject = project; - myRepositories = repositories; - myBranchName = branchName; - } - - @Override - @RequiredUIAccess - public void actionPerformed(@Nonnull AnActionEvent e) { - GitBrancher brancher = myProject.getInstance(GitBrancher.class); - brancher.rebaseOnCurrent(myRepositories, myBranchName); - reportUsage("git.branch.checkout.with.rebase"); - } - } } diff --git a/plugin/src/main/java/git4idea/ui/branch/GitBranchesAction.java b/plugin/src/main/java/git4idea/ui/branch/GitBranchesAction.java index 90d2cf3..ec6c900 100644 --- a/plugin/src/main/java/git4idea/ui/branch/GitBranchesAction.java +++ b/plugin/src/main/java/git4idea/ui/branch/GitBranchesAction.java @@ -29,9 +29,7 @@ import jakarta.annotation.Nonnull; /** - *

* Invokes a {@link GitBranchPopup} to checkout and control Git branches. - *

*/ @ActionImpl(id = "Git.Branches") public class GitBranchesAction extends DumbAwareAction { diff --git a/plugin/src/main/java/git4idea/ui/branch/GitCompareBranchesDiffPanel.java b/plugin/src/main/java/git4idea/ui/branch/GitCompareBranchesDiffPanel.java index f0b719e..d42b094 100644 --- a/plugin/src/main/java/git4idea/ui/branch/GitCompareBranchesDiffPanel.java +++ b/plugin/src/main/java/git4idea/ui/branch/GitCompareBranchesDiffPanel.java @@ -65,7 +65,7 @@ private JComponent createNorthPanel() { private JComponent createCenterPanel() { List diff = myCompareInfo.getTotalDiff(); ChangesBrowserFactory browserFactory = Application.get().getInstance(ChangesBrowserFactory.class); - final ChangesBrowser changesBrowser = browserFactory.createChangeBrowser( + ChangesBrowser changesBrowser = browserFactory.createChangeBrowser( myProject, null, diff, diff --git a/plugin/src/main/java/git4idea/ui/branch/GitCompareBranchesLogPanel.java b/plugin/src/main/java/git4idea/ui/branch/GitCompareBranchesLogPanel.java index a5a151b..d095275 100644 --- a/plugin/src/main/java/git4idea/ui/branch/GitCompareBranchesLogPanel.java +++ b/plugin/src/main/java/git4idea/ui/branch/GitCompareBranchesLogPanel.java @@ -41,10 +41,15 @@ * @author Kirill Likhodedov */ class GitCompareBranchesLogPanel extends JPanel { + @Nonnull private final Project myProject; + @Nonnull private final String myBranchName; + @Nonnull private final String myCurrentBranchName; + @Nonnull private final GitCommitCompareInfo myCompareInfo; + @Nonnull private final GitRepository myInitialRepo; private GitCommitListPanel myHeadToBranchListPanel; @@ -71,7 +76,7 @@ class GitCompareBranchesLogPanel extends JPanel { private JComponent createCenterPanel() { ChangesBrowserFactory browserFactory = Application.get().getInstance(ChangesBrowserFactory.class); - final ChangesBrowser changesBrowser = browserFactory.createChangeBrowser( + ChangesBrowser changesBrowser = browserFactory.createChangeBrowser( myProject, null, Collections.emptyList(), @@ -101,20 +106,16 @@ private JComponent createCenterPanel() { JPanel htb = layoutCommitListPanel(myCurrentBranchName, true); JPanel bth = layoutCommitListPanel(myCurrentBranchName, false); - JPanel listPanel = null; - switch (getInfoType()) { - case HEAD_TO_BRANCH: - listPanel = htb; - break; - case BRANCH_TO_HEAD: - listPanel = bth; - break; - case BOTH: + JPanel listPanel = switch (getInfoType()) { + case HEAD_TO_BRANCH -> htb; + case BRANCH_TO_HEAD -> bth; + case BOTH -> { Splitter lists = new Splitter(true, 0.5f); lists.setFirstComponent(htb); lists.setSecondComponent(bth); - listPanel = lists; - } + yield lists; + } + }; Splitter rootPanel = new Splitter(false, 0.7f); rootPanel.setSecondComponent(changesBrowser.getComponent()); @@ -123,7 +124,7 @@ private JComponent createCenterPanel() { } private JComponent createNorthPanel() { - final JComboBox repoSelector = + JComboBox repoSelector = new JComboBox<>(ArrayUtil.toObjectArray(myCompareInfo.getRepositories(), GitRepository.class)); repoSelector.setRenderer(new GitRepositoryComboboxListCellRenderer(repoSelector)); repoSelector.setSelectedItem(myInitialRepo); @@ -161,8 +162,8 @@ private GitCommitCompareInfo.InfoType getInfoType() { private static void addSelectionListener( @Nonnull GitCommitListPanel sourcePanel, - @Nonnull final GitCommitListPanel otherPanel, - @Nonnull final ChangesBrowser changesBrowser + @Nonnull GitCommitListPanel otherPanel, + @Nonnull ChangesBrowser changesBrowser ) { sourcePanel.addListMultipleSelectionListener(changes -> { changesBrowser.setChangesToDisplay(changes); diff --git a/plugin/src/main/java/git4idea/util/GitCommitCompareInfo.java b/plugin/src/main/java/git4idea/util/GitCommitCompareInfo.java index 590fd82..b337eab 100644 --- a/plugin/src/main/java/git4idea/util/GitCommitCompareInfo.java +++ b/plugin/src/main/java/git4idea/util/GitCommitCompareInfo.java @@ -15,103 +15,85 @@ */ package git4idea.util; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import jakarta.annotation.Nonnull; import consulo.logging.Logger; -import consulo.util.lang.Pair; +import consulo.util.lang.Couple; import consulo.versionControlSystem.change.Change; import git4idea.GitCommit; import git4idea.repo.GitRepository; +import jakarta.annotation.Nonnull; + +import java.util.*; /** * @author Kirill Likhodedov */ -public class GitCommitCompareInfo -{ - private static final Logger LOG = Logger.getInstance(GitCommitCompareInfo.class); - private final Map, List>> myInfo = new HashMap, List>>(); - private final Map> myTotalDiff = new HashMap>(); - private final InfoType myInfoType; +public class GitCommitCompareInfo { + private static final Logger LOG = Logger.getInstance(GitCommitCompareInfo.class); + private final Map>> myInfo = new HashMap<>(); + private final Map> myTotalDiff = new HashMap<>(); + private final InfoType myInfoType; - public GitCommitCompareInfo() - { - this(InfoType.BOTH); - } + public GitCommitCompareInfo() { + this(InfoType.BOTH); + } - public GitCommitCompareInfo(@Nonnull InfoType infoType) - { - myInfoType = infoType; - } + public GitCommitCompareInfo(@Nonnull InfoType infoType) { + myInfoType = infoType; + } - public void put(@Nonnull GitRepository repository, @Nonnull Pair, List> commits) - { - myInfo.put(repository, commits); - } + public void put(@Nonnull GitRepository repository, @Nonnull Couple> commits) { + myInfo.put(repository, commits); + } - public void put(@Nonnull GitRepository repository, @Nonnull Collection totalDiff) - { - myTotalDiff.put(repository, totalDiff); - } + public void put(@Nonnull GitRepository repository, @Nonnull Collection totalDiff) { + myTotalDiff.put(repository, totalDiff); + } - @Nonnull - public List getHeadToBranchCommits(@Nonnull GitRepository repo) - { - return getCompareInfo(repo).getFirst(); - } + @Nonnull + public List getHeadToBranchCommits(@Nonnull GitRepository repo) { + return getCompareInfo(repo).getFirst(); + } - @Nonnull - public List getBranchToHeadCommits(@Nonnull GitRepository repo) - { - return getCompareInfo(repo).getSecond(); - } + @Nonnull + public List getBranchToHeadCommits(@Nonnull GitRepository repo) { + return getCompareInfo(repo).getSecond(); + } - @Nonnull - private Pair, List> getCompareInfo(@Nonnull GitRepository repo) - { - Pair, List> pair = myInfo.get(repo); - if(pair == null) - { - LOG.error("Compare info not found for repository " + repo); - return Pair.create(Collections.emptyList(), Collections.emptyList()); - } - return pair; - } + @Nonnull + private Couple> getCompareInfo(@Nonnull GitRepository repo) { + Couple> pair = myInfo.get(repo); + if (pair == null) { + LOG.error("Compare info not found for repository " + repo); + return Couple.of(Collections.emptyList(), Collections.emptyList()); + } + return pair; + } - @Nonnull - public Collection getRepositories() - { - return myInfo.keySet(); - } + @Nonnull + public Collection getRepositories() { + return myInfo.keySet(); + } - public boolean isEmpty() - { - return myInfo.isEmpty(); - } + public boolean isEmpty() { + return myInfo.isEmpty(); + } - public InfoType getInfoType() - { - return myInfoType; - } + public InfoType getInfoType() { + return myInfoType; + } - @Nonnull - public List getTotalDiff() - { - List changes = new ArrayList(); - for(Collection changeCollection : myTotalDiff.values()) - { - changes.addAll(changeCollection); - } - return changes; - } + @Nonnull + public List getTotalDiff() { + List changes = new ArrayList<>(); + for (Collection changeCollection : myTotalDiff.values()) { + changes.addAll(changeCollection); + } + return changes; + } - public enum InfoType - { - BOTH, HEAD_TO_BRANCH, BRANCH_TO_HEAD - } + public enum InfoType { + BOTH, + HEAD_TO_BRANCH, + BRANCH_TO_HEAD + } } diff --git a/plugin/src/main/resources/LOCALIZE-LIB/en_US/consulo.git.GitBranchesLocalize.yaml b/plugin/src/main/resources/LOCALIZE-LIB/en_US/consulo.git.GitBranchesLocalize.yaml new file mode 100644 index 0000000..a61cc45 --- /dev/null +++ b/plugin/src/main/resources/LOCALIZE-LIB/en_US/consulo.git.GitBranchesLocalize.yaml @@ -0,0 +1,48 @@ +action.checkout.as.new.branch.text: + text: Checkout as New Branch +action.checkout.as.new.local.branch.text: + text: Checkout as New Local Branch +action.checkout.tag.or.revision.impossible.description: + text: Checkout is not possible before the first commit +action.checkout.tag.or.revision.text: + text: Checkout Tag or Revision… +action.checkout.text: + text: Checkout +action.checkout.with.rebase.0.description: + text: Checkout the given branch, and rebase it on current branch in one step, just like `git rebase HEAD {0}` would do. +action.checkout.with.rebase.text: + text: Checkout with Rebase +action.common.local.branches.text: + text: Common Local Branches +action.common.remote.branches.text: + text: Common Remote Branches +action.compare.text: + text: Compare +action.delete.text: + text: Delete +action.local.branches.0.text: + text: Local Branches{0} +action.merge.text: + text: Merge +action.rebase.onto.text: + text: Rebase onto +action.remote.branches.0.text: + text: Remote Branches{0} +action.repositories.text: + text: Repositories +dialog.message.enter.reference.branch.tag.name.or.commit.hash: + text: 'Enter reference (branch, tag) name or commit hash:' +dialog.message.new.branch.name: + text: 'New branch name:' +dialog.message.new.name.for.the.branch.0: + text: 'New name for the branch ''''{0}'''':' +dialog.title.checkout: + text: Checkout +dialog.title.checkout.new.branch.from.0: + text: Checkout New Branch From {0} +dialog.title.checkout.remote.branch: + text: Checkout Remote Branch +dialog.title.create.new.branch: + text: Create New Branch +dialog.title.rename.branch.0: + text: Rename Branch {0} diff --git a/plugin/src/main/resources/LOCALIZE-LIB/en_US/consulo.git.GitLocalize.yaml b/plugin/src/main/resources/LOCALIZE-LIB/en_US/consulo.git.GitLocalize.yaml index cd01edf..ef58e03 100644 --- a/plugin/src/main/resources/LOCALIZE-LIB/en_US/consulo.git.GitLocalize.yaml +++ b/plugin/src/main/resources/LOCALIZE-LIB/en_US/consulo.git.GitLocalize.yaml @@ -14,8 +14,6 @@ action.branches.text: text: _Branches... action.checkout.revision.text: text: Checkout Revision -action.init.text: - text: Create Git Repository... action.compare.with.branch.text: text: Compare with Branch or Tag... action.create.new.branch.description: @@ -28,6 +26,8 @@ action.create.new.tag.text: text: New Tag... action.fetch.text: text: Fetch +action.init.text: + text: Create Git Repository... action.log.deep.compare.description: text: Dims those commits which have equivalent changes in the current branch action.log.deep.compare.text: @@ -294,10 +294,12 @@ debug.git.exec: text: 'DEBUG: work-dir: [{0}] exec: [{1}]' delete.action.name: text: Delete -dialog.checkout.new.branch.from.0.title: - text: Checkout New Branch From {0} -dialog.reset.title: +dialog.message.enter.the.name.of.new.branch: + text: 'Enter the name of new branch:' +dialog.title.reset: text: Git Reset +dialog.title.checkout.new.branch.from.0: + text: Checkout New Branch From {0} diff.find.error: text: 'Finding revision for diff: {0}' error.commit.cant.create.message.file: