| /* |
| * Copyright 2000-2012 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.branch; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.Collections2; |
| import com.google.common.collect.Iterables; |
| import com.intellij.dvcs.DvcsUtil; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.*; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.vcs.AbstractVcs; |
| import com.intellij.openapi.vcs.ProjectLevelVcsManager; |
| import com.intellij.openapi.vcs.VcsException; |
| import com.intellij.openapi.vfs.VfsUtilCore; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.vcs.log.Hash; |
| import com.intellij.vcsUtil.VcsUtil; |
| import git4idea.*; |
| import git4idea.commands.GitCommand; |
| import git4idea.commands.GitSimpleHandler; |
| import git4idea.config.GitConfigUtil; |
| import git4idea.config.GitVcsSettings; |
| import git4idea.repo.*; |
| import git4idea.ui.branch.GitMultiRootBranchConfig; |
| import git4idea.validators.GitNewBranchNameValidator; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.*; |
| |
| /** |
| * @author Kirill Likhodedov |
| */ |
| public class GitBranchUtil { |
| |
| private static final Logger LOG = Logger.getInstance(GitBranchUtil.class); |
| |
| private static final Function<GitBranch,String> BRANCH_TO_NAME = new Function<GitBranch, String>() { |
| @Override |
| public String apply(@Nullable GitBranch 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(@NotNull GitRepository repository, @NotNull GitLocalBranch branch) { |
| for (GitBranchTrackInfo trackInfo : repository.getBranchTrackInfos()) { |
| if (trackInfo.getLocalBranch().equals(branch)) { |
| return trackInfo; |
| } |
| } |
| return null; |
| } |
| |
| @NotNull |
| static String getCurrentBranchOrRev(@NotNull Collection<GitRepository> 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); |
| } |
| } |
| |
| @NotNull |
| public static Collection<String> convertBranchesToNames(@NotNull Collection<? extends GitBranch> branches) { |
| return Collections2.transform(branches, BRANCH_TO_NAME); |
| } |
| |
| /** |
| * 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(@NotNull Project project, @NotNull 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); |
| } |
| } |
| |
| @Nullable |
| private static GitLocalBranch getCurrentBranchFromGit(@NotNull Project project, @NotNull 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, GitBranch.DUMMY_HASH); |
| } |
| else { |
| 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)); |
| } |
| |
| @NotNull |
| private static String trackedBranchKey(String branchName) { |
| return "branch." + branchName + ".merge"; |
| } |
| |
| @NotNull |
| 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(@NotNull Project project, @NotNull VirtualFile root, @NotNull String branchName) throws VcsException { |
| final HashMap<String, String> result = new HashMap<String, String>(); |
| 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, GitBranch.DUMMY_HASH); |
| } |
| |
| GitRemote remote = findRemoteByNameOrLogError(project, root, remoteName); |
| if (remote == null) return null; |
| return new GitStandardRemoteBranch(remote, branch, GitBranch.DUMMY_HASH); |
| } |
| |
| @Nullable |
| @Deprecated |
| public static GitRemote findRemoteByNameOrLogError(@NotNull Project project, @NotNull VirtualFile root, @NotNull 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; |
| } |
| |
| /** |
| * |
| * @return {@link git4idea.GitStandardRemoteBranch} or {@link GitSvnRemoteBranch}, or null in case of an error. The error is logged in this method. |
| * @deprecated Should be used only in the GitRepositoryReader, i. e. moved there once all other usages are removed. |
| */ |
| @Deprecated |
| @Nullable |
| public static GitRemoteBranch parseRemoteBranch(@NotNull String fullBranchName, @NotNull Hash hash, |
| @NotNull Collection<GitRemote> remotes) { |
| String stdName = stripRefsPrefix(fullBranchName); |
| |
| int slash = stdName.indexOf('/'); |
| if (slash == -1) { // .git/refs/remotes/my_branch => git-svn |
| return new GitSvnRemoteBranch(fullBranchName, hash); |
| } |
| else { |
| String remoteName = stdName.substring(0, slash); |
| String branchName = stdName.substring(slash + 1); |
| GitRemote remote = findRemoteByName(remoteName, remotes); |
| if (remote == null) { |
| // user may remove the remote section from .git/config, but leave remote refs untouched in .git/refs/remotes |
| LOG.info(String.format("No remote found with the name [%s]. All remotes: %s", remoteName, remotes)); |
| GitRemote fakeRemote = new GitRemote(remoteName, ContainerUtil.<String>emptyList(), Collections.<String>emptyList(), |
| Collections.<String>emptyList(), Collections.<String>emptyList()); |
| return new GitStandardRemoteBranch(fakeRemote, branchName, hash); |
| } |
| return new GitStandardRemoteBranch(remote, branchName, hash); |
| } |
| } |
| |
| @Nullable |
| private static GitRemote findRemoteByName(@NotNull String remoteName, @NotNull Collection<GitRemote> remotes) { |
| for (GitRemote remote : remotes) { |
| if (remote.getName().equals(remoteName)) { |
| return remote; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Convert {@link git4idea.GitRemoteBranch GitRemoteBranches} to their names, and remove remote HEAD pointers: origin/HEAD. |
| */ |
| @NotNull |
| public static Collection<String> getBranchNamesWithoutRemoteHead(@NotNull Collection<GitRemoteBranch> remoteBranches) { |
| return Collections2.filter(convertBranchesToNames(remoteBranches), new Predicate<String>() { |
| @Override |
| public boolean apply(@Nullable String input) { |
| assert input != null; |
| return !input.equals("HEAD"); |
| } |
| }); |
| } |
| |
| /** |
| * @deprecated Don't use names, use {@link GitLocalBranch} objects. |
| */ |
| @Deprecated |
| @Nullable |
| public static GitLocalBranch findLocalBranchByName(@NotNull GitRepository repository, @NotNull final String branchName) { |
| Optional<GitLocalBranch> optional = Iterables.tryFind(repository.getBranches().getLocalBranches(), new Predicate<GitLocalBranch>() { |
| @Override |
| public boolean apply(@Nullable GitLocalBranch input) { |
| assert input != null; |
| return input.getName().equals(branchName); |
| } |
| }); |
| if (optional.isPresent()) { |
| return optional.get(); |
| } |
| LOG.info(String.format("Couldn't find branch with name %s in %s", branchName, repository)); |
| return null; |
| |
| } |
| |
| /** |
| * Looks through the remote branches in the given repository and tries to find the one from the given remote, |
| * which the given name. |
| * @return remote branch or null if such branch couldn't be found. |
| */ |
| @Nullable |
| public static GitRemoteBranch findRemoteBranchByName(@NotNull String remoteBranchName, @NotNull final String remoteName, |
| @NotNull final Collection<GitRemoteBranch> remoteBranches) { |
| final String branchName = stripRefsPrefix(remoteBranchName); |
| Optional<GitRemoteBranch> optional = Iterables.tryFind(remoteBranches, new Predicate<GitRemoteBranch>() { |
| @Override |
| public boolean apply(@Nullable GitRemoteBranch input) { |
| assert input != null; |
| return input.getNameForRemoteOperations().equals(branchName) && input.getRemote().getName().equals(remoteName); |
| } |
| }); |
| if (optional.isPresent()) { |
| return optional.get(); |
| } |
| LOG.info(String.format("Couldn't find branch with name %s", branchName)); |
| return null; |
| } |
| |
| @NotNull |
| public static String stripRefsPrefix(@NotNull 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; |
| } |
| |
| /** |
| * Returns current branch name (if on branch) or current revision otherwise. |
| * For fresh repository returns an empty string. |
| */ |
| @NotNull |
| public static String getBranchNameOrRev(@NotNull 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) : ""; |
| } |
| } |
| |
| /** |
| * 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(@NotNull Project project, @NotNull Collection<GitRepository> repositories, @NotNull 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". |
| */ |
| public static String getDisplayableBranchText(@NotNull 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 currentRevision.substring(0, 7); |
| } |
| |
| 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; |
| } |
| |
| /** |
| * Guesses the Git root on which a Git action is to be invoked. |
| * <ol> |
| * <li> |
| * Returns the root for the selected file. Selected file is determined by {@link DvcsUtil#getSelectedFile(com.intellij.openapi.project.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, |
| * <code>null</code> will be returned - the file is definitely determined, but it is not under Git. |
| * </li> |
| * <li> |
| * Takes all Git roots registered in the Project. If there is only one, it is returned. |
| * </li> |
| * <li> |
| * If there are several Git roots, |
| * </li> |
| * </ol> |
| * |
| * <p> |
| * NB: This method has to be accessed from the <b>read action</b>, because it may query |
| * {@link com.intellij.openapi.fileEditor.FileEditorManager#getSelectedTextEditor()}. |
| * </p> |
| * @param project current project |
| * @return Git root that may be considered as "current". |
| * <code>null</code> 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(@NotNull Project project) { |
| GitRepositoryManager manager = GitUtil.getRepositoryManager(project); |
| VirtualFile file = DvcsUtil.getSelectedFile(project); |
| VirtualFile root = getVcsRootOrGuess(project, file); |
| return manager.getRepositoryForRoot(root); |
| } |
| |
| @Nullable |
| public static VirtualFile getVcsRootOrGuess(@NotNull Project project, @Nullable VirtualFile file) { |
| VirtualFile root = null; |
| ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); |
| if (file != null) { |
| if (fileIndex.isInLibrarySource(file) || fileIndex.isInLibraryClasses(file)) { |
| LOG.debug("File is in library sources " + file); |
| root = getVcsRootForLibraryFile(project, file); |
| } |
| else { |
| LOG.debug("File is not in library sources " + file); |
| root = ProjectLevelVcsManager.getInstance(project).getVcsRootFor(file); |
| } |
| } |
| return root != null ? root : guessGitRoot(project); |
| } |
| |
| @Nullable |
| private static VirtualFile getVcsRootForLibraryFile(@NotNull Project project, @NotNull VirtualFile file) { |
| ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(project); |
| // for a file inside .jar/.zip consider the .jar/.zip file itself |
| VirtualFile root = vcsManager.getVcsRootFor(VfsUtilCore.getVirtualFileForJar(file)); |
| if (root != null) { |
| LOG.debug("Found root for zip/jar file: " + root); |
| return root; |
| } |
| |
| // for other libs which don't have jars inside the project dir (such as JDK) take the owner module of the lib |
| List<OrderEntry> entries = ProjectRootManager.getInstance(project).getFileIndex().getOrderEntriesForFile(file); |
| Set<VirtualFile> libraryRoots = new HashSet<VirtualFile>(); |
| for (OrderEntry entry : entries) { |
| if (entry instanceof LibraryOrderEntry || entry instanceof JdkOrderEntry) { |
| VirtualFile moduleRoot = vcsManager.getVcsRootFor(entry.getOwnerModule().getModuleFile()); |
| if (moduleRoot != null) { |
| libraryRoots.add(moduleRoot); |
| } |
| } |
| } |
| |
| if (libraryRoots.size() == 0) { |
| LOG.debug("No library roots"); |
| return null; |
| } |
| |
| // if the lib is used in several modules, take the top module |
| // (for modules of the same level we can't guess anything => take the first one) |
| Iterator<VirtualFile> libIterator = libraryRoots.iterator(); |
| VirtualFile topLibraryRoot = libIterator.next(); |
| while (libIterator.hasNext()) { |
| VirtualFile libRoot = libIterator.next(); |
| if (VfsUtilCore.isAncestor(libRoot, topLibraryRoot, true)) { |
| topLibraryRoot = libRoot; |
| } |
| } |
| LOG.debug("Several library roots, returning " + topLibraryRoot); |
| return topLibraryRoot; |
| } |
| |
| @Nullable |
| private static VirtualFile guessGitRoot(@NotNull Project project) { |
| LOG.debug("Guessing Git root..."); |
| ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(project); |
| AbstractVcs gitVcs = GitVcs.getInstance(project); |
| if (gitVcs == null) { |
| LOG.debug("GitVcs not found."); |
| return null; |
| } |
| VirtualFile[] gitRoots = vcsManager.getRootsUnderVcs(gitVcs); |
| if (gitRoots.length == 0) { |
| LOG.debug("No Git roots in the project."); |
| return null; |
| } |
| |
| if (gitRoots.length == 1) { |
| VirtualFile onlyRoot = gitRoots[0]; |
| LOG.debug("Only one Git root in the project, returning: " + onlyRoot); |
| return onlyRoot; |
| } |
| |
| // remember the last visited Git root |
| GitVcsSettings settings = GitVcsSettings.getInstance(project); |
| if (settings != null) { |
| String recentRootPath = settings.getRecentRootPath(); |
| if (recentRootPath != null) { |
| VirtualFile recentRoot = VcsUtil.getVirtualFile(recentRootPath); |
| if (recentRoot != null) { |
| LOG.debug("Returning the recent root: " + recentRoot); |
| return recentRoot; |
| } |
| } |
| } |
| |
| // otherwise return the root of the project dir or the root containing the project dir, if there is such |
| VirtualFile projectBaseDir = project.getBaseDir(); |
| if (projectBaseDir == null) { |
| VirtualFile firstRoot = gitRoots[0]; |
| LOG.debug("Project base dir is null, returning the first root: " + firstRoot); |
| return firstRoot; |
| } |
| VirtualFile rootCandidate = null; |
| for (VirtualFile root : gitRoots) { |
| if (root.equals(projectBaseDir)) { |
| return root; |
| } |
| else if (VfsUtilCore.isAncestor(root, projectBaseDir, true)) { |
| rootCandidate = root; |
| } |
| } |
| LOG.debug("Returning the best candidate: " + rootCandidate); |
| return rootCandidate; |
| } |
| |
| @NotNull |
| public static Collection<String> getCommonBranches(Collection<GitRepository> repositories, |
| boolean local) { |
| Collection<String> commonBranches = null; |
| for (GitRepository repository : repositories) { |
| GitBranchesCollection branchesCollection = repository.getBranches(); |
| |
| Collection<String> names = local |
| ? convertBranchesToNames(branchesCollection.getLocalBranches()) |
| : getBranchNamesWithoutRemoteHead(branchesCollection.getRemoteBranches()); |
| if (commonBranches == null) { |
| commonBranches = names; |
| } |
| else { |
| commonBranches = ContainerUtil.intersection(commonBranches, names); |
| } |
| } |
| |
| if (commonBranches != null) { |
| ArrayList<String> common = new ArrayList<String>(commonBranches); |
| Collections.sort(common); |
| return common; |
| } |
| else { |
| return Collections.emptyList(); |
| } |
| } |
| |
| /** |
| * List branches containing a commit. Specify null if no commit filtering is needed. |
| */ |
| @NotNull |
| public static Collection<String> getBranches(@NotNull Project project, @NotNull 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; |
| } else if (remoteWanted) { |
| handler.addParameters("-r"); |
| remoteOnly = true; |
| } |
| if (containingCommit != null) { |
| handler.addParameters("--contains", containingCommit); |
| } |
| 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 { |
| head = FileUtil.loadFile(new File(root.getPath(), GitRepositoryFiles.GIT_HEAD), GitUtil.UTF8_ENCODING).trim(); |
| final String prefix = "ref: refs/heads/"; |
| return head.startsWith(prefix) ? |
| Collections.singletonList(head.substring(prefix.length())) : |
| Collections.<String>emptyList(); |
| } catch (IOException e) { |
| LOG.info(e); |
| return Collections.emptyList(); |
| } |
| } |
| |
| Collection<String> 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()); |
| } |
| final int idx = b.indexOf("HEAD ->"); |
| if (idx > 0) { |
| continue; |
| } |
| } |
| branches.add(b); |
| } |
| return branches; |
| } |
| } |