Package com.urswolfer.intellij.plugin.gerrit.git

Source Code of com.urswolfer.intellij.plugin.gerrit.git.GerritGitUtil$CherryPickConflictResolver

/*
* Copyright 2013 Urs Wolfer
* Copyright 2000-2010 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 com.urswolfer.intellij.plugin.gerrit.git;

import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.inject.Inject;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.urswolfer.intellij.plugin.gerrit.GerritSettings;
import com.urswolfer.intellij.plugin.gerrit.util.NotificationBuilder;
import com.urswolfer.intellij.plugin.gerrit.util.NotificationService;
import com.urswolfer.intellij.plugin.gerrit.util.UrlUtils;
import git4idea.GitExecutionException;
import git4idea.GitPlatformFacade;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.commands.*;
import git4idea.history.GitHistoryUtils;
import git4idea.history.browser.GitCommit;
import git4idea.history.browser.SHAHash;
import git4idea.history.wholeTree.AbstractHash;
import git4idea.merge.GitConflictResolver;
import git4idea.repo.GitRemote;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import git4idea.update.GitFetchResult;
import git4idea.util.GitCommitCompareInfo;
import git4idea.util.UntrackedFilesNotifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;

import static git4idea.commands.GitSimpleEventDetector.Event.CHERRY_PICK_CONFLICT;
import static git4idea.commands.GitSimpleEventDetector.Event.LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK;

/**
* @author Urs Wolfer
*/
public class GerritGitUtil {
    @Inject
    private Logger log;
    @Inject
    private Git git;
    @Inject
    private GitPlatformFacade platformFacade;
    @Inject
    private FileDocumentManager fileDocumentManager;
    @Inject
    private Application application;
    @Inject
    private VirtualFileManager virtualFileManager;
    @Inject
    private GerritSettings gerritSettings;
    @Inject
    private NotificationService notificationService;

    public Iterable<GitRepository> getRepositories(Project project) {
        GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(project);
        return repositoryManager.getRepositories();
    }

    public Optional<GitRepository> getRepositoryForGerritProject(Project project, String gerritProjectName) {
        final Iterable<GitRepository> repositoriesFromRoots = getRepositories(project);
        for (GitRepository repository : repositoriesFromRoots) {
            for (GitRemote remote : repository.getRemotes()) {
                if (remote.getName().equals(gerritProjectName)) {
                    return Optional.of(repository);
                }
                for (String remoteUrl : remote.getUrls()) {
                    remoteUrl = UrlUtils.stripGitExtension(remoteUrl);
                    if (remoteUrl.endsWith(gerritProjectName)) {
                        return Optional.of(repository);
                    }
                }
            }
        }
        NotificationBuilder notification = new NotificationBuilder(project, "Error",
                String.format("No repository found for Gerrit project: '%s'.", gerritProjectName));
        notificationService.notifyError(notification);
        return Optional.absent();
    }

    public void fetchChange(final Project project,
                            final GitRepository gitRepository,
                            final String url,
                            final String branch,
                            @Nullable final Callable<Void> successCallable) {
        GitVcs.runInBackground(new Task.Backgroundable(project, "Fetching...", false) {
            @Override
            public void onSuccess() {
                super.onSuccess();
                try {
                    if (successCallable != null) {
                        successCallable.call();
                    }
                } catch (Exception e) {
                    throw Throwables.propagate(e);
                }
            }

            @Override
            public void run(@NotNull ProgressIndicator indicator) {
                for (GitRemote remote : gitRepository.getRemotes()) {
                    for (String repositoryUrl : remote.getUrls()) {
                        if (UrlUtils.urlHasSameHost(repositoryUrl, url)
                                || UrlUtils.urlHasSameHost(repositoryUrl, gerritSettings.getHost())) {
                            fetchNatively(gitRepository.getGitDir(), remote, repositoryUrl, branch, project, indicator);
                            return;
                        }
                    }
                }
                NotificationBuilder notification = new NotificationBuilder(project, "Error",
                        String.format("Could not fetch commit because no remote url matches Gerrit host.<br/>" +
                                "Git repository: '%s'.", gitRepository.getPresentableUrl()));
                notificationService.notifyError(notification);
            }
        });
    }

    public void cherryPickChange(final Project project, final ChangeInfo changeInfo, final String revisionId) {
        fileDocumentManager.saveAllDocuments();
        platformFacade.getChangeListManager(project).blockModalNotifications();

        new Task.Backgroundable(project, "Cherry-picking...", false) {
            public void run(@NotNull ProgressIndicator indicator) {
                try {
                    Optional<GitRepository> gitRepositoryOptional = getRepositoryForGerritProject(project, changeInfo.project);
                    if (!gitRepositoryOptional.isPresent()) return;
                    GitRepository gitRepository = gitRepositoryOptional.get();

                    final VirtualFile virtualFile = gitRepository.getGitDir();

                    final String notLoaded = "Not loaded";
                    GitCommit gitCommit = new GitCommit(virtualFile, AbstractHash.create(revisionId), new SHAHash(revisionId), notLoaded, notLoaded, new Date(0), notLoaded,
                            notLoaded, Collections.<String>emptySet(), Collections.<FilePath>emptyList(), notLoaded,
                            notLoaded, Collections.<String>emptyList(), Collections.<String>emptyList(), Collections.<String>emptyList(),
                            Collections.<Change>emptyList(), 0);

                    cherryPick(gitRepository, gitCommit, git, platformFacade, project);
                } finally {
                    application.invokeLater(new Runnable() {
                        public void run() {
                            virtualFileManager.syncRefresh();
                            platformFacade.getChangeListManager(project).unblockModalNotifications();
                        }
                    });
                }
            }
        }.queue();
    }

    /**
     * A lot of this code is based on: git4idea.cherrypick.GitCherryPicker#cherryPick() (which is private)
     */
    private boolean cherryPick(@NotNull GitRepository repository, @NotNull GitCommit commit,
                                      @NotNull Git git, @NotNull GitPlatformFacade platformFacade, @NotNull Project project) {
        GitSimpleEventDetector conflictDetector = new GitSimpleEventDetector(CHERRY_PICK_CONFLICT);
        GitSimpleEventDetector localChangesOverwrittenDetector = new GitSimpleEventDetector(LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK);
        GitUntrackedFilesOverwrittenByOperationDetector untrackedFilesDetector =
                new GitUntrackedFilesOverwrittenByOperationDetector(repository.getRoot());
        GitCommandResult result = git.cherryPick(repository, commit.getHash().getValue(), false,
                conflictDetector, localChangesOverwrittenDetector, untrackedFilesDetector);
        if (result.success()) {
            return true;
        } else if (conflictDetector.hasHappened()) {
            return new CherryPickConflictResolver(project, git, platformFacade, repository.getRoot(),
                    commit.getShortHash().getString(), commit.getAuthor(),
                    commit.getSubject()).merge();
        } else if (untrackedFilesDetector.wasMessageDetected()) {
            String description = "Some untracked working tree files would be overwritten by cherry-pick.<br/>" +
                    "Please move, remove or add them before you can cherry-pick. <a href='view'>View them</a>";

            UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(project, platformFacade, untrackedFilesDetector.getFiles(),
                    "cherry-pick", description);
            return false;
        } else if (localChangesOverwrittenDetector.hasHappened()) {
            notificationService.notifyError(new NotificationBuilder(project, "Cherry-Pick Error",
                    "Your local changes would be overwritten by cherry-pick.<br/>Commit your changes or stash them to proceed."));
            return false;
        } else {
            notificationService.notifyError(new NotificationBuilder(project, "Cherry-Pick Error",
                    result.getErrorOutputAsHtmlString()));
            return false;
        }
    }


    /**
     * Copy of: git4idea.cherrypick.GitCherryPicker.CherryPickConflictResolver (which is private)
     */
    private static class CherryPickConflictResolver extends GitConflictResolver {

        public CherryPickConflictResolver(@NotNull Project project, @NotNull Git git, @NotNull GitPlatformFacade facade, @NotNull VirtualFile root,
                                          @NotNull String commitHash, @NotNull String commitAuthor, @NotNull String commitMessage) {
            super(project, git, facade, Collections.singleton(root), makeParams(commitHash, commitAuthor, commitMessage));
        }

        private static Params makeParams(String commitHash, String commitAuthor, String commitMessage) {
            Params params = new Params();
            params.setErrorNotificationTitle("Cherry-picked with conflicts");
            params.setMergeDialogCustomizer(new CherryPickMergeDialogCustomizer(commitHash, commitAuthor, commitMessage));
            return params;
        }

        @Override
        protected void notifyUnresolvedRemain() {
            // we show a [possibly] compound notification after cherry-picking all commits.
        }
    }


    /**
     * Copy of: git4idea.cherrypick.GitCherryPicker.CherryPickMergeDialogCustomizer (which is private)
     */
    private static class CherryPickMergeDialogCustomizer extends MergeDialogCustomizer {

        private String myCommitHash;
        private String myCommitAuthor;
        private String myCommitMessage;

        public CherryPickMergeDialogCustomizer(String commitHash, String commitAuthor, String commitMessage) {
            myCommitHash = commitHash;
            myCommitAuthor = commitAuthor;
            myCommitMessage = commitMessage;
        }

        @Override
        public String getMultipleFileMergeDescription(Collection<VirtualFile> files) {
            return "<html>Conflicts during cherry-picking commit <code>" + myCommitHash + "</code> made by " + myCommitAuthor + "<br/>" +
                    "<code>\"" + myCommitMessage + "\"</code></html>";
        }

        @Override
        public String getLeftPanelTitle(VirtualFile file) {
            return "Local changes";
        }

        @Override
        public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) {
            return "<html>Changes from cherry-pick <code>" + myCommitHash + "</code>";
        }
    }

    @NotNull
    public GitFetchResult fetchNatively(@NotNull VirtualFile root,
                                        @NotNull GitRemote remote,
                                        @NotNull String url,
                                        @Nullable String branch,
                                        Project project,
                                        ProgressIndicator progressIndicator) {
        final GitLineHandlerPasswordRequestAware h = new GitLineHandlerPasswordRequestAware(project, root, GitCommand.FETCH);
        h.setUrl(url);
        h.addProgressParameter();

        String remoteName = remote.getName();
        h.addParameters(remoteName);
        if (branch != null) {
            h.addParameters(branch);
        }

        final GitTask fetchTask = new GitTask(project, h, "Fetching " + remote.getFirstUrl());
        fetchTask.setProgressIndicator(progressIndicator);
        fetchTask.setProgressAnalyzer(new GitStandardProgressAnalyzer());

        final AtomicReference<GitFetchResult> result = new AtomicReference<GitFetchResult>();
        fetchTask.execute(true, false, new GitTaskResultHandlerAdapter() {
            @Override
            protected void onSuccess() {
                result.set(GitFetchResult.success());
            }

            @Override
            protected void onCancel() {
                log.info("Cancelled fetch.");
                result.set(GitFetchResult.cancel());
            }

            @Override
            protected void onFailure() {
                log.warn("Error fetching: " + h.errors());
                Collection<Exception> errors = Lists.newArrayList();
                if (!h.hadAuthRequest()) {
                    errors.addAll(h.errors());
                } else {
                    errors.add(new VcsException("Authentication failed"));
                }
                result.set(GitFetchResult.error(errors));
            }
        });

        return result.get();
    }


    @NotNull
    public Pair<List<GitCommit>, List<GitCommit>> loadCommitsToCompare(@NotNull GitRepository repository, @NotNull final String branchName, @NotNull final Project project) {
        final List<GitCommit> headToBranch;
        final List<GitCommit> branchToHead;
        try {
            headToBranch = GitHistoryUtils.history(project, repository.getRoot(), ".." + branchName);
            branchToHead = GitHistoryUtils.history(project, repository.getRoot(), branchName + "..");
        } catch (VcsException e) {
            // we treat it as critical and report an error
            throw new GitExecutionException("Couldn't get [git log .." + branchName + "] on repository [" + repository.getRoot() + "]", e);
        }
        return Pair.create(headToBranch, branchToHead);
    }

    @NotNull
    public GitCommitCompareInfo loadCommitsToCompare(Collection<GitRepository> repositories, String branchName, @NotNull final Project project) {
        GitCommitCompareInfo compareInfo = new GitCommitCompareInfo();
        for (GitRepository repository : repositories) {
            compareInfo.put(repository, loadCommitsToCompare(repository, branchName, project));
//            compareInfo.put(repository, loadTotalDiff(repository, branchName));
        }
        return compareInfo;
    }
}
TOP

Related Classes of com.urswolfer.intellij.plugin.gerrit.git.GerritGitUtil$CherryPickConflictResolver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.