Line data Source code
1 : // Copyright (C) 2011 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.sshd.commands; 16 : 17 : import com.google.common.flogger.FluentLogger; 18 : import com.google.gerrit.entities.Account; 19 : import com.google.gerrit.entities.Change; 20 : import com.google.gerrit.exceptions.StorageException; 21 : import com.google.gerrit.extensions.api.changes.DeleteReviewerInput; 22 : import com.google.gerrit.extensions.api.changes.ReviewerInput; 23 : import com.google.gerrit.extensions.restapi.ResourceNotFoundException; 24 : import com.google.gerrit.server.change.ChangeResource; 25 : import com.google.gerrit.server.change.ReviewerResource; 26 : import com.google.gerrit.server.permissions.PermissionBackendException; 27 : import com.google.gerrit.server.project.ProjectState; 28 : import com.google.gerrit.server.restapi.change.DeleteReviewer; 29 : import com.google.gerrit.server.restapi.change.PostReviewers; 30 : import com.google.gerrit.sshd.ChangeArgumentParser; 31 : import com.google.gerrit.sshd.CommandMetaData; 32 : import com.google.gerrit.sshd.SshCommand; 33 : import com.google.inject.Inject; 34 : import java.util.ArrayList; 35 : import java.util.HashSet; 36 : import java.util.LinkedHashMap; 37 : import java.util.List; 38 : import java.util.Map; 39 : import java.util.Set; 40 : import org.kohsuke.args4j.Argument; 41 : import org.kohsuke.args4j.Option; 42 : 43 : @CommandMetaData(name = "set-reviewers", description = "Add or remove reviewers on a change") 44 1 : public class SetReviewersCommand extends SshCommand { 45 1 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 46 : 47 : @Option(name = "--project", aliases = "-p", usage = "project containing the change") 48 : private ProjectState projectState; 49 : 50 1 : @Option( 51 : name = "--add", 52 : aliases = {"-a"}, 53 : metaVar = "REVIEWER", 54 : usage = "user or group that should be added as reviewer") 55 : private List<String> toAdd = new ArrayList<>(); 56 : 57 : @Option( 58 : name = "--remove", 59 : aliases = {"-r"}, 60 : metaVar = "REVIEWER", 61 : usage = "user that should be removed from the reviewer list") 62 : void optionRemove(Account.Id who) { 63 1 : toRemove.add(who); 64 1 : } 65 : 66 : @Argument( 67 : index = 0, 68 : required = true, 69 : multiValued = true, 70 : metaVar = "CHANGE", 71 : usage = "changes to modify") 72 : void addChange(String token) { 73 : try { 74 1 : changeArgumentParser.addChange(token, changes, projectState); 75 0 : } catch (UnloggedFailure e) { 76 0 : throw new IllegalArgumentException(e.getMessage(), e); 77 0 : } catch (StorageException e) { 78 0 : throw new IllegalArgumentException("database is down", e); 79 0 : } catch (PermissionBackendException e) { 80 0 : throw new IllegalArgumentException("can't check permissions", e); 81 1 : } 82 1 : } 83 : 84 : @Inject private ReviewerResource.Factory reviewerFactory; 85 : 86 : @Inject private PostReviewers postReviewers; 87 : 88 : @Inject private DeleteReviewer deleteReviewer; 89 : 90 : @Inject private ChangeArgumentParser changeArgumentParser; 91 : 92 1 : private Set<Account.Id> toRemove = new HashSet<>(); 93 : 94 1 : private Map<Change.Id, ChangeResource> changes = new LinkedHashMap<>(); 95 : 96 : @Override 97 : protected void run() throws UnloggedFailure { 98 1 : enableGracefulStop(); 99 1 : boolean ok = true; 100 1 : for (ChangeResource rsrc : changes.values()) { 101 : try { 102 1 : ok &= modifyOne(rsrc); 103 0 : } catch (Exception err) { 104 0 : ok = false; 105 0 : logger.atSevere().withCause(err).log("Error updating reviewers on change %s", rsrc.getId()); 106 0 : writeError("fatal", "internal error while updating " + rsrc.getId()); 107 1 : } 108 1 : } 109 : 110 1 : if (!ok) { 111 0 : throw die("one or more updates failed; review output above"); 112 : } 113 1 : } 114 : 115 : private boolean modifyOne(ChangeResource changeRsrc) throws Exception { 116 1 : boolean ok = true; 117 : 118 : // Remove reviewers 119 : // 120 1 : for (Account.Id reviewer : toRemove) { 121 1 : ReviewerResource rsrc = reviewerFactory.create(changeRsrc, reviewer); 122 1 : String error = null; 123 : try { 124 1 : deleteReviewer.apply(rsrc, new DeleteReviewerInput()); 125 0 : } catch (ResourceNotFoundException e) { 126 0 : error = String.format("could not remove %s: not found", reviewer); 127 0 : } catch (Exception e) { 128 0 : error = String.format("could not remove %s: %s", reviewer, e.getMessage()); 129 1 : } 130 1 : if (error != null) { 131 0 : ok = false; 132 0 : writeError("error", error); 133 : } 134 1 : } 135 : 136 : // Add reviewers 137 : // 138 1 : for (String reviewer : toAdd) { 139 1 : ReviewerInput input = new ReviewerInput(); 140 1 : input.reviewer = reviewer; 141 1 : input.confirmed = true; 142 : String error; 143 : try { 144 1 : error = postReviewers.apply(changeRsrc, input).value().error; 145 0 : } catch (Exception e) { 146 0 : error = String.format("could not add %s: %s", reviewer, e.getMessage()); 147 1 : } 148 1 : if (error != null) { 149 0 : ok = false; 150 0 : writeError("error", error); 151 : } 152 1 : } 153 : 154 1 : return ok; 155 : } 156 : }