blob: 4b232f751afc826f85ccaee720c45e69638e4f3f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015-2016 Ericsson AB.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ericsson - initial API and implementation
*******************************************************************************/
package org.eclipse.egerrit.internal.ui.editors;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.egerrit.internal.core.EGerritCorePlugin;
import org.eclipse.egerrit.internal.core.GerritClient;
import org.eclipse.egerrit.internal.core.command.ChangeOption;
import org.eclipse.egerrit.internal.core.command.ChangeStatus;
import org.eclipse.egerrit.internal.core.command.DeleteDraftCommand;
import org.eclipse.egerrit.internal.core.command.GetChangeCommand;
import org.eclipse.egerrit.internal.core.command.GetFilesCommand;
import org.eclipse.egerrit.internal.core.command.GetIncludedInCommand;
import org.eclipse.egerrit.internal.core.command.GetMergeableCommand;
import org.eclipse.egerrit.internal.core.command.GetRelatedChangesCommand;
import org.eclipse.egerrit.internal.core.command.GetReviewedFilesCommand;
import org.eclipse.egerrit.internal.core.command.ListCommentsCommand;
import org.eclipse.egerrit.internal.core.command.ListDraftsCommand;
import org.eclipse.egerrit.internal.core.command.ListReviewersCommand;
import org.eclipse.egerrit.internal.core.command.QueryChangesCommand;
import org.eclipse.egerrit.internal.core.exception.EGerritException;
import org.eclipse.egerrit.internal.model.AccountInfo;
import org.eclipse.egerrit.internal.model.ChangeInfo;
import org.eclipse.egerrit.internal.model.CommentInfo;
import org.eclipse.egerrit.internal.model.FileInfo;
import org.eclipse.egerrit.internal.model.IncludedInInfo;
import org.eclipse.egerrit.internal.model.MergeableInfo;
import org.eclipse.egerrit.internal.model.ModelHelpers;
import org.eclipse.egerrit.internal.model.ModelPackage;
import org.eclipse.egerrit.internal.model.RelatedChangesInfo;
import org.eclipse.egerrit.internal.model.ReviewerInfo;
import org.eclipse.egerrit.internal.model.RevisionInfo;
import org.eclipse.egerrit.internal.ui.utils.Messages;
import org.eclipse.emf.common.util.EList;
/**
* A helper class wrapping the common server queries. All calls in this class are expected to be synchronous.
*/
public class QueryHelpers {
/**
* The default constructor. Do not allow to build an object of this class
*/
private QueryHelpers() {
}
/**
* Get the changes that have a given subject
*/
public static ChangeInfo[] lookupPartialChangeInfoFromSubject(GerritClient gerritClient, String subject,
IProgressMonitor monitor) throws MalformedURLException {
try {
monitor.beginTask(Messages.QueryHelpers_executingQuery, IProgressMonitor.UNKNOWN);
QueryChangesCommand command = gerritClient.queryChanges();
command.addOption(ChangeOption.LABELS);
command.addOption(ChangeOption.DETAILED_LABELS);
command.addOption(ChangeOption.DETAILED_ACCOUNTS);
command.addOption(ChangeOption.MESSAGES);
command.addOption(ChangeOption.ALL_REVISIONS);
command.addOption(ChangeOption.ALL_COMMITS);
command.addOption(ChangeOption.CURRENT_ACTIONS);
try {
command.addQuery(setFreeText(subject));
return command.call();
} catch (EGerritException e) {
EGerritCorePlugin.logError(gerritClient.getRepository().formatGerritVersion() + e.getMessage());
}
return new ChangeInfo[0];
} finally {
monitor.done();
}
}
private static String setFreeText(String subject) {
// use the subject to query the server
return "message:\"" + subject + "\""; //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Get a review by changeId
*/
public static ChangeInfo lookupPartialChangeInfoFromChangeId(GerritClient gerrit, String changeId,
IProgressMonitor monitor) {
try {
monitor.beginTask(Messages.QueryHelpers_executingQuery, IProgressMonitor.UNKNOWN);
GetChangeCommand command = null;
command = gerrit.getChange(changeId);
command.addOption(ChangeOption.LABELS);
command.addOption(ChangeOption.DETAILED_LABELS);
command.addOption(ChangeOption.DETAILED_ACCOUNTS);
command.addOption(ChangeOption.MESSAGES);
command.addOption(ChangeOption.ALL_REVISIONS);
command.addOption(ChangeOption.ALL_COMMITS);
command.addOption(ChangeOption.CURRENT_ACTIONS);
try {
return command.call();
} catch (EGerritException e) {
EGerritCorePlugin.logError(gerrit.getRepository().formatGerritVersion() + e.getMessage());
}
return null;
} finally {
monitor.done();
}
}
/**
* Load comments for the given revision
*/
public static void loadComments(GerritClient gerrit, RevisionInfo revision) {
loadFiles(gerrit, revision);
if (revision.isCommentsLoaded()) {
return;
}
ListCommentsCommand command = gerrit.getListComments(revision.getChangeInfo().getId(), revision.getId());
Map<String, ArrayList<CommentInfo>> comments = null;
try {
comments = command.call();
} catch (EGerritException e) {
EGerritCorePlugin.logError(gerrit.getRepository().formatGerritVersion() + e.getMessage());
return;
}
synchronized (revision.getChangeInfo()) {
//Load the comments and set them in the fileInfo object
for (Entry<String, ArrayList<CommentInfo>> fileComment : comments.entrySet()) {
FileInfo files = revision.getFiles().get(fileComment.getKey());
if (files != null) {
files.getComments().clear();
files.getComments().addAll(fileComment.getValue());
}
}
//We only set the comments as loaded once the values have been set
revision.setCommentsLoaded(true);
}
}
public static void loadDrafts(GerritClient gerrit, RevisionInfo revision) {
if (gerrit.getRepository().getServerInfo().isAnonymous()) {
return;
}
loadFiles(gerrit, revision);
ListDraftsCommand command = gerrit.listDraftsComments(revision.getChangeInfo().getId(), revision.getId());
Map<String, ArrayList<CommentInfo>> drafts = null;
try {
drafts = command.call();
} catch (EGerritException e) {
EGerritCorePlugin.logError(gerrit.getRepository().formatGerritVersion() + e.getMessage());
return;
}
synchronized (revision.getChangeInfo()) {
//There is no more drafts, so we need to clear our data structure
if (drafts.entrySet().isEmpty()) {
Collection<FileInfo> files = revision.getFiles().values();
files.stream().filter(f -> !f.getDraftComments().isEmpty()).forEach(f -> f.getDraftComments().clear());
return;
}
for (Entry<String, ArrayList<CommentInfo>> draftComment : drafts.entrySet()) {
FileInfo files = revision.getFiles().get(draftComment.getKey());
if (files != null) {
if (!files.getDraftComments().isEmpty()) {
files.getDraftComments().clear();
}
files.getDraftComments().addAll(draftComment.getValue());
}
}
}
}
public static void markAsReviewed(GerritClient gerrit, FileInfo fileInfo) {
if (gerrit.getRepository().getServerInfo().isAnonymous()) {
return;
}
synchronized (fileInfo.getRevision().getChangeInfo()) {
if (fileInfo.isReviewed()) {
return;
}
try {
gerrit.setReviewed(fileInfo.getRevision().getChangeInfo().getId(), fileInfo.getRevision().getId(),
fileInfo).call();
} catch (EGerritException ex) {
EGerritCorePlugin.logError(gerrit.getRepository().formatGerritVersion() + ex.getMessage());
}
}
}
public static void markAsNotReviewed(GerritClient gerrit, FileInfo fileInfo) {
if (gerrit.getRepository().getServerInfo().isAnonymous()) {
return;
}
synchronized (fileInfo.getRevision().getChangeInfo()) {
if (!fileInfo.isReviewed()) {
return;
}
try {
gerrit.deleteReviewed(fileInfo.getRevision().getChangeInfo().getId(), fileInfo.getRevision().getId(),
fileInfo).call();
} catch (EGerritException ex) {
EGerritCorePlugin.logError(gerrit.getRepository().formatGerritVersion() + ex.getMessage());
}
}
}
private static boolean fullyLoaded(ChangeInfo toRefresh) {
synchronized (toRefresh) {
if (toRefresh.getLoadingLevel() == 0) {
return false;
}
return true;
}
}
private static void mergeNewInformation(ChangeInfo toRefresh, ChangeInfo newChangeInfo, boolean forceReload) {
if (newChangeInfo == null) {
//When we did not reach the gerrit server, we received a null ChangeInfo
return;
}
synchronized (toRefresh) {
if (!forceReload) {
if (toRefresh.getUpdated() != null && toRefresh.getUpdated().equals(newChangeInfo.getUpdated())
&& fullyLoaded(toRefresh)) {
return;
}
}
toRefresh.set_number(newChangeInfo.get_number());
toRefresh.setChange_id(newChangeInfo.getChange_id());
toRefresh.setStatus(newChangeInfo.getStatus());
toRefresh.setStarred(newChangeInfo.isStarred());
toRefresh.setSubject(newChangeInfo.getSubject());
toRefresh.setCreated(newChangeInfo.getCreated());
toRefresh.setReviewed(newChangeInfo.isReviewed());
toRefresh.setInsertions(newChangeInfo.getInsertions());
toRefresh.setDeletions(newChangeInfo.getDeletions());
toRefresh.setTopic(newChangeInfo.getTopic());
toRefresh.setMergeable(newChangeInfo.isMergeable());
toRefresh.getLabels().clear();
toRefresh.getLabels().addAll(newChangeInfo.getLabels());
mergeRevisions(toRefresh, newChangeInfo); //need to be before setting the actions and the current revision
if (newChangeInfo.getCurrent_revision() != null
&& newChangeInfo.getRevisions().get(newChangeInfo.getCurrent_revision()) != null) {
toRefresh.setCurrent_revision(newChangeInfo.getCurrent_revision());
} else {
toRefresh.setCurrent_revision(toRefresh
.getRevisionByNumber(ModelHelpers.getHighestRevisionNumber(toRefresh.getRevisions().values()))
.getId());
}
//Re-init the user selected revision after a revert
if (toRefresh.getUserSelectedRevision() == null) {
//Initial setting for the user selected revision if not done yet
toRefresh.setUserSelectedRevision(toRefresh.getRevisions().get(toRefresh.getCurrent_revision()));
}
toRefresh.eSet(ModelPackage.Literals.CHANGE_INFO__PERMITTED_LABELS, newChangeInfo.getPermitted_labels());
toRefresh.eSet(ModelPackage.Literals.CHANGE_INFO__ACTIONS, newChangeInfo.getActions());
//Replace all the messages if there are new ones.
if (newChangeInfo.getMessages().size() != toRefresh.getMessages().size()) {
toRefresh.getMessages().clear();
toRefresh.getMessages().addAll(newChangeInfo.getMessages());
}
//Reset the removal reviewers list
toRefresh.eSet(ModelPackage.Literals.CHANGE_INFO__REMOVABLE_REVIEWERS,
newChangeInfo.getRemovable_reviewers());
//Set the date at the end because it is used to trigger other refreshes
if (forceReload && toRefresh.getUpdated().equals(newChangeInfo.getUpdated())) {
//slightly modify the date to make sure that listeners on the "last updated" fields are called
toRefresh.setUpdated(
newChangeInfo.getUpdated().substring(0, newChangeInfo.getUpdated().length() - 2) + "1"); //$NON-NLS-1$
} else {
toRefresh.setUpdated(newChangeInfo.getUpdated());
}
toRefresh.setLoadingLevel(1);
}
}
private static void mergeRevisions(ChangeInfo toRefresh, ChangeInfo newChangeInfo) {
//Reset the flags that indicates that comments have been loaded
Set<String> oldKeys = toRefresh.getRevisions().keySet();
toRefresh.getRevisions().values().forEach(rev -> rev.setCommentsLoaded(false));
//Copy the new values of the actions
toRefresh.getRevisions().values().forEach(rev -> {
String revId = rev.getId();
if (newChangeInfo.getRevisions().get(revId) != null) {
rev.getActions().clear();
rev.getActions().putAll(newChangeInfo.getRevisions().get(revId).getActions());
rev.setCommit(newChangeInfo.getRevisions().get(revId).getCommit());
rev.setRef(newChangeInfo.getRevisions().get(revId).getRef());
rev.setDraft(newChangeInfo.getRevisions().get(revId).isDraft());
}
});
//Identify the revisions that could have been removed
Set<String> deletedKeys = new HashSet<>();
for (String key : oldKeys) {
if (newChangeInfo.getRevisions().get(key) == null) {
deletedKeys.add(key);
}
}
//Delete all the revisions that have been removed from the server
deletedKeys.stream().forEach(toRefresh.getRevisions()::removeKey);
//Isolate the revisions that need to be kept by removing the entries already loaded from the new collection
//Once this is done we simply add the new remaining of the revisions to the old revision
oldKeys.stream().forEach(newChangeInfo.getRevisions()::removeKey);
//Take all the other revisions as they are since they are new
toRefresh.getRevisions().putAll(newChangeInfo.getRevisions());
}
public static void loadBasicInformation(GerritClient gerrit, ChangeInfo toRefresh, boolean forceReload) {
ChangeInfo newChangeInfo = QueryHelpers.queryBasicInformation(gerrit, toRefresh.getId());
mergeNewInformation(toRefresh, newChangeInfo, forceReload);
}
private static ChangeInfo queryBasicInformation(GerritClient gerrit, String id) {
GetChangeCommand command = gerrit.getChange(id);
command.addOption(ChangeOption.LABELS);
command.addOption(ChangeOption.DETAILED_LABELS);
command.addOption(ChangeOption.DETAILED_ACCOUNTS);
command.addOption(ChangeOption.MESSAGES);
command.addOption(ChangeOption.ALL_REVISIONS);
command.addOption(ChangeOption.ALL_COMMITS);
command.addOption(ChangeOption.CURRENT_ACTIONS);
ChangeInfo res = null;
try {
res = command.call();
} catch (EGerritException e) {
EGerritCorePlugin.logError(gerrit.getRepository().formatGerritVersion() + e.getMessage());
}
return res;
}
public static void loadRevisionDetails(GerritClient gerrit, RevisionInfo revision) {
if (revision == null) {
throw new IllegalAccessError("Revision can't be null."); //$NON-NLS-1$
}
loadFiles(gerrit, revision);
loadReviewedFiles(gerrit, revision);
loadComments(gerrit, revision);
loadDrafts(gerrit, revision);
}
private static void loadReviewedFiles(GerritClient gerrit, RevisionInfo revision) {
if (gerrit.getRepository().getServerInfo().isAnonymous()) {
return;
}
GetReviewedFilesCommand command = gerrit.getReviewed(revision.getChangeInfo().getId(), revision.getId());
try {
String[] reviewedFiles = command.call();
synchronized (revision.getChangeInfo()) {
for (String reviewed : reviewedFiles) {
FileInfo fileToMarkReviewed = revision.getFiles().get(reviewed);
if (fileToMarkReviewed != null) {
fileToMarkReviewed.setReviewed(true);
}
}
}
} catch (EGerritException e) {
return;
}
}
public static void loadFiles(GerritClient gerrit, RevisionInfo revision) {
synchronized (revision) {
if (revision.isFilesLoaded()) {
return;
}
}
GetFilesCommand command = gerrit.getFiles(revision.getChangeInfo().getId(), revision.getId());
try {
Map<String, FileInfo> result = command.call();
synchronized (revision) {
revision.getFiles().putAll(result);
revision.setFilesLoaded(true);
}
} catch (EGerritException e) {
return;
}
}
private static void loadSameTopic(GerritClient gerritClient, ChangeInfo element) {
ChangeInfo[] sameTopicChangeInfo = null;
synchronized (element) {
if (element.getTopic() == null) {
element.getSameTopic().clear();
return;
}
}
try {
QueryChangesCommand command = gerritClient.queryChanges();
command.addTopic(element.getTopic());
sameTopicChangeInfo = command.call();
synchronized (element) {
if (sameTopicChangeInfo != null) {
element.getSameTopic().clear();
for (ChangeInfo changeInfo : sameTopicChangeInfo) {
element.getSameTopic().add(changeInfo);
}
}
}
} catch (EGerritException e) {
EGerritCorePlugin.logError(gerritClient.getRepository().formatGerritVersion() + e.getLocalizedMessage(), e);
}
}
private static void loadMergeable(GerritClient gerritClient, ChangeInfo element) {
synchronized (element) {
if ("MERGED".equals(element.getStatus()) || "ABANDONED".equals(element.getStatus())) { //$NON-NLS-1$ //$NON-NLS-2$
return;
}
}
try {
GetMergeableCommand command = gerritClient.getMergeable(element);
MergeableInfo mergeableInfo;
mergeableInfo = command.call();
synchronized (element) {
element.setMergeableInfo(mergeableInfo);
element.setMergeable(mergeableInfo.isMergeable());
}
} catch (EGerritException e) {
EGerritCorePlugin.logError(gerritClient.getRepository().formatGerritVersion() + e.getLocalizedMessage(), e);
}
}
private static void loadIncludedIn(GerritClient gerritClient, ChangeInfo element) {
try {
GetIncludedInCommand command = gerritClient.getIncludedIn(element.getId());
IncludedInInfo res = null;
res = command.call();
synchronized (element) {
element.setIncludedIn(res);
}
} catch (EGerritException e) {
EGerritCorePlugin.logError(gerritClient.getRepository().formatGerritVersion() + e.getLocalizedMessage(), e);
}
}
private static void loadReviewers(GerritClient gerritClient, ChangeInfo element) {
try {
ListReviewersCommand command = gerritClient.getReviewers(element.getId());
ReviewerInfo[] reviewers = command.call();
synchronized (element) {
element.getComputedReviewers().clear();
for (ReviewerInfo reviewerInfo : reviewers) {
adjustRemovableReviewers(element, reviewerInfo);
element.getComputedReviewers().add(reviewerInfo);
}
}
} catch (EGerritException e) {
EGerritCorePlugin.logError(gerritClient.getRepository().formatGerritVersion() + e.getMessage());
}
}
/**
* Set the flag if the reviewer can be deleted
*
* @param changeInfo
* @param reviewerInfo
*/
private static void adjustRemovableReviewers(ChangeInfo changeInfo, ReviewerInfo reviewerInfo) {
EList<AccountInfo> removalList = changeInfo.getRemovable_reviewers();
for (int i = 0; i < removalList.size(); i++) {
if (reviewerInfo.get_account_id() == removalList.get(i).get_account_id()) {
reviewerInfo.setDeleteable(true);
return;
}
}
}
/**
* Helpers to load the related changes according to the selected revision
*
* @param gerritClient
* @param fChangeInfo
* @param revision
*/
public static void loadRelatedChanges(GerritClient gerritClient, ChangeInfo fChangeInfo, String revision) {
if (gerritClient.getRepository().getServerInfo().isAnonymous()) {
return;
}
try {
GetRelatedChangesCommand command = gerritClient.getRelatedChanges(fChangeInfo.getId(), revision);
RelatedChangesInfo res = command.call();
synchronized (fChangeInfo) {
fChangeInfo.setRelatedChanges(res);
}
} catch (EGerritException e) {
EGerritCorePlugin.logError(gerritClient.getRepository().formatGerritVersion() + e.getMessage());
}
}
private static void loadConflictsWith(GerritClient gerritClient, ChangeInfo element) {
ChangeInfo[] conflictsWithChangeInfo = null;
synchronized (element) {
if ("MERGED".equals(element.getStatus()) || "ABANDONED".equals(element.getStatus())) { //$NON-NLS-1$ //$NON-NLS-2$
return;
}
}
try {
QueryChangesCommand command = gerritClient.queryChanges();
command.addConflicts(element.getChange_id()); //Here we keep the change_id because the conflicts call does not accept the full id
command.addMergeable();
command.addStatus(ChangeStatus.OPEN);
conflictsWithChangeInfo = command.call();
List<ChangeInfo> conflictsWithChangeInfolistNew = new ArrayList<ChangeInfo>();
if (conflictsWithChangeInfo != null) {
for (ChangeInfo conflictChangeInfo : conflictsWithChangeInfo) {
if (conflictChangeInfo.getChange_id().compareTo(element.getChange_id()) != 0) {
conflictsWithChangeInfolistNew.add(conflictChangeInfo);
}
}
}
synchronized (element) {
element.getConflictsWith().clear();
element.getConflictsWith().addAll(conflictsWithChangeInfolistNew);
}
} catch (EGerritException e) {
EGerritCorePlugin.logError(gerritClient.getRepository().formatGerritVersion() + e.getMessage());
}
}
static void loadDetailedInformation(GerritClient gerritClient, ChangeInfo toLoad) {
loadSameTopic(gerritClient, toLoad);
loadConflictsWith(gerritClient, toLoad);
loadIncludedIn(gerritClient, toLoad);
loadMergeable(gerritClient, toLoad);
loadReviewers(gerritClient, toLoad);
loadRelatedChanges(gerritClient, toLoad, toLoad.getCurrent_revision());
}
static void deleteDraft(GerritClient gerritClient, CommentInfo toDelete) {
RevisionInfo revision = ModelHelpers.getRevision(toDelete);
DeleteDraftCommand deleteDraft = gerritClient.deleteDraft(revision.getChangeInfo().getId(), revision.getId(),
toDelete.getId());
try {
deleteDraft.call();
ModelHelpers.getFileInfo(toDelete).getDraftComments().remove(toDelete);
} catch (EGerritException e) {
//Nothing to do
}
}
}