| // Copyright 2008-2010 Victor Iacoban |
| // |
| // 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 org.zmlx.hg4idea.command; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Couple; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vcs.FilePath; |
| import com.intellij.openapi.vcs.ObjectsConvertor; |
| import com.intellij.openapi.vcs.changes.ChangeListManager; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.zmlx.hg4idea.HgRevisionNumber; |
| import org.zmlx.hg4idea.execution.HgCommandExecutor; |
| import org.zmlx.hg4idea.execution.HgCommandResult; |
| import org.zmlx.hg4idea.util.HgChangesetUtil; |
| import org.zmlx.hg4idea.util.HgUtil; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| /** |
| * Commands to get revision numbers. These are: parents, id, tip. |
| */ |
| public class HgWorkingCopyRevisionsCommand { |
| |
| private final Project myProject; |
| private static final Logger LOG = Logger.getInstance(HgWorkingCopyRevisionsCommand.class); |
| |
| public HgWorkingCopyRevisionsCommand(Project project) { |
| myProject = project; |
| } |
| |
| /** |
| * Current repository revision(s). |
| * @param repo repository to work on. |
| * @return List of parent's revision numbers. |
| * @see #parents(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.vfs.VirtualFile, org.zmlx.hg4idea.HgRevisionNumber) |
| * TODO: return Pair |
| */ |
| @NotNull |
| public List<HgRevisionNumber> parents(@NotNull VirtualFile repo) { |
| return getRevisions(repo, "parents", null, null, true); |
| } |
| |
| /** |
| * @see #parents(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.vfs.VirtualFile, org.zmlx.hg4idea.HgRevisionNumber) |
| */ |
| @NotNull |
| public Couple<HgRevisionNumber> parents(@NotNull VirtualFile repo, @Nullable VirtualFile file) { |
| return parents(repo, file, null); |
| } |
| |
| /** |
| * Parent(s) of the given revision of the given file. If there are two of them (in the case of merge) the first element of the pair |
| * is the latest parent (i.e. having greater revision number), second one is the earlier parent (having smaller revision number). |
| * @param repo repository to work on. |
| * @param file file which revision's parents we are interested in. If null, the history of the whole repository is considered. |
| * @param revision revision number which parent is wanted. If null, the last revision is taken. |
| * @return One or two (in case of a merge commit) parents of the given revision. Or even zero in case of a fresh repository. |
| * So one should check pair elements for null. |
| */ |
| @NotNull |
| public Couple<HgRevisionNumber> parents(@NotNull VirtualFile repo, @Nullable VirtualFile file, @Nullable HgRevisionNumber revision) { |
| return parents(repo, ObjectsConvertor.VIRTUAL_FILEPATH.convert(file), revision); |
| } |
| |
| /** |
| * @see #parents(VirtualFile, FilePath, HgRevisionNumber) |
| */ |
| @NotNull |
| public Couple<HgRevisionNumber> parents(@NotNull VirtualFile repo, @Nullable FilePath file) { |
| return parents(repo, file, null); |
| } |
| |
| /** |
| * Parent(s) of the given revision of the given file. If there are two of them (in the case of merge) the first element of the pair |
| * is the latest parent (i.e. having greater revision number), second one is the earlier parent (having smaller revision number). |
| * @param repo repository to work on. |
| * @param file filepath which revision's parents we are interested in. If null, the history of the whole repository is considered. |
| * @param revision revision number which parent is wanted. If null, the last revision is taken. |
| * @return One or two (in case of a merge commit) parents of the given revision. Or even zero in case of a fresh repository. |
| * So one should check pair elements for null. |
| */ |
| @NotNull |
| public Couple<HgRevisionNumber> parents(@NotNull VirtualFile repo, @Nullable FilePath file, @Nullable HgRevisionNumber revision) { |
| final List<HgRevisionNumber> revisions = getRevisions(repo, "parents", file, revision, true); |
| switch (revisions.size()) { |
| case 1: return Couple.of(revisions.get(0), null); |
| case 2: return Couple.of(revisions.get(0), revisions.get(1)); |
| default: return Couple.of(null, null); |
| } |
| } |
| |
| @Nullable |
| public HgRevisionNumber firstParent(@NotNull VirtualFile repo) { |
| List<HgRevisionNumber> parents = parents(repo); |
| if (parents.isEmpty()) { |
| //this is possible when we have a freshly initialized mercurial repository |
| return HgRevisionNumber.NULL_REVISION_NUMBER; |
| } |
| else { |
| return parents.get(0); |
| } |
| } |
| |
| @Nullable |
| public HgRevisionNumber tip(@NotNull VirtualFile repo) { |
| List<HgRevisionNumber> tips = getRevisions(repo, "tip", null, null, true); |
| if (tips.size() > 1) { |
| throw new IllegalStateException("There cannot be multiple tips"); |
| } |
| if(!tips.isEmpty()) { |
| return tips.get(0); |
| } |
| else return HgRevisionNumber.NULL_REVISION_NUMBER; |
| } |
| |
| /** |
| * Returns the result of 'hg id' execution, i.e. current state of the repository. |
| * @return one or two revision numbers. Two revisions is the case of unresolved merge. In other cases there are only one revision. |
| */ |
| @NotNull |
| public Couple<HgRevisionNumber> identify(@NotNull VirtualFile repo) { |
| HgCommandExecutor commandExecutor = new HgCommandExecutor(myProject); |
| commandExecutor.setSilent(true); |
| HgCommandResult result = commandExecutor.executeInCurrentThread(repo, "identify", Arrays.asList("--num", "--id")); |
| if (result == null) { |
| return Couple.of(HgRevisionNumber.NULL_REVISION_NUMBER, null); |
| } |
| |
| final List<String> lines = result.getOutputLines(); |
| if (lines != null && !lines.isEmpty()) { |
| List<String> parts = StringUtil.split(lines.get(0), " "); |
| String changesets = parts.get(0); |
| String revisions = parts.get(1); |
| if (parts.size() >= 2) { |
| if (changesets.indexOf('+') != changesets.lastIndexOf('+')) { |
| // in the case of unresolved merge we have 2 revisions at once, both current, so with "+" |
| // 9f2e6c02913c+b311eb4eb004+ 186+183+ |
| List<String> chsets = StringUtil.split(changesets, "+"); |
| List<String> revs = StringUtil.split(revisions, "+"); |
| return Couple.of(HgRevisionNumber.getInstance(revs.get(0) + "+", chsets.get(0) + "+"), |
| HgRevisionNumber.getInstance(revs.get(1) + "+", chsets.get(1) + "+")); |
| } else { |
| return Couple.of(HgRevisionNumber.getInstance(revisions, changesets), null); |
| } |
| } |
| } |
| return Couple.of(HgRevisionNumber.NULL_REVISION_NUMBER, null); |
| } |
| |
| /** |
| * Returns the list of revisions returned by one mercurial commands (parents, identify, tip). |
| * Executed a command on the whole repository or on the given file. |
| * During a merge, the returned list contains 2 revision numbers. The order of these numbers is |
| * important: the first parent was the parent of the working directory from <em>before</em> |
| * the merge, the second parent is the changeset that was merged in. |
| * @param repo repository to execute on. |
| * @param command command to execute. |
| * @param file file which revisions are wanted. If <code><b>null</b></code> then repository revisions are considered. |
| * @param revision revision to execute on. If <code><b>null</b></code> then executed without the '-r' parameter, i.e. on the latest revision. |
| * @param silent pass true if this command shouldn't be mentioned in the VCS console. |
| * @return List of revisions. |
| */ |
| public @NotNull List<HgRevisionNumber> getRevisions(@NotNull VirtualFile repo, |
| @NotNull String command, |
| @Nullable FilePath file, |
| @Nullable HgRevisionNumber revision, |
| boolean silent) { |
| final List<String> args = new LinkedList<String>(); |
| args.add("--template"); |
| args.add(HgChangesetUtil.makeTemplate("{rev}", "{node}")); |
| if (revision != null) { |
| args.add("-r"); |
| args.add(revision.getChangeset()); |
| } |
| if (file != null) { // NB: this must be the last argument |
| args.add(HgUtil.getOriginalFileName(file, ChangeListManager.getInstance(myProject)).getPath()); |
| } |
| final HgCommandExecutor executor = new HgCommandExecutor(myProject); |
| executor.setSilent(silent); |
| final HgCommandResult result = executor.executeInCurrentThread(repo, command, args); |
| |
| if (result == null) { |
| return new ArrayList<HgRevisionNumber>(0); |
| } |
| |
| final List<String> lines = new ArrayList<String>(); |
| for (String line : result.getRawOutput().split(HgChangesetUtil.CHANGESET_SEPARATOR)) { |
| if (!line.trim().isEmpty()) { // filter out empty lines |
| lines.add(line); |
| } |
| } |
| if (lines.isEmpty()) { |
| return new ArrayList<HgRevisionNumber>(); |
| } |
| |
| final List<HgRevisionNumber> revisions = new ArrayList<HgRevisionNumber>(lines.size()); |
| for(String line: lines) { |
| final List<String> parts = StringUtil.split(line, HgChangesetUtil.ITEM_SEPARATOR); |
| if (parts.size() < 2) { |
| LOG.error("getRevisions output parse error in line [" + line + "]\n All lines: \n" + lines); |
| continue; |
| } |
| revisions.add(HgRevisionNumber.getInstance(parts.get(0), parts.get(1))); |
| } |
| return revisions; |
| } |
| } |