Line data Source code
1 : // Copyright (C) 2014 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.gerrit.common.Nullable; 18 : import com.google.gerrit.entities.Change; 19 : import com.google.gerrit.entities.PatchSet; 20 : import com.google.gerrit.entities.Project; 21 : import com.google.gerrit.entities.RefNames; 22 : import com.google.gerrit.git.ObjectIds; 23 : import com.google.gerrit.server.PatchSetUtil; 24 : import com.google.gerrit.server.change.ChangeFinder; 25 : import com.google.gerrit.server.notedb.ChangeNotes; 26 : import com.google.gerrit.server.project.ProjectState; 27 : import com.google.gerrit.server.query.change.ChangeData; 28 : import com.google.gerrit.server.query.change.InternalChangeQuery; 29 : import com.google.gerrit.sshd.BaseCommand.UnloggedFailure; 30 : import com.google.inject.Inject; 31 : import com.google.inject.Provider; 32 : import com.google.inject.Singleton; 33 : import java.util.ArrayList; 34 : import java.util.List; 35 : import java.util.Optional; 36 : 37 : @Singleton 38 : public class PatchSetParser { 39 : private final Provider<InternalChangeQuery> queryProvider; 40 : private final ChangeNotes.Factory notesFactory; 41 : private final PatchSetUtil psUtil; 42 : private final ChangeFinder changeFinder; 43 : 44 : @Inject 45 : PatchSetParser( 46 : Provider<InternalChangeQuery> queryProvider, 47 : ChangeNotes.Factory notesFactory, 48 : PatchSetUtil psUtil, 49 2 : ChangeFinder changeFinder) { 50 2 : this.queryProvider = queryProvider; 51 2 : this.notesFactory = notesFactory; 52 2 : this.psUtil = psUtil; 53 2 : this.changeFinder = changeFinder; 54 2 : } 55 : 56 : public PatchSet parsePatchSet(String token, ProjectState projectState, String branch) 57 : throws UnloggedFailure { 58 : // By commit? 59 : // 60 2 : if (token.matches("^([0-9a-fA-F]{4," + ObjectIds.STR_LEN + "})$")) { 61 2 : InternalChangeQuery query = queryProvider.get(); 62 : List<ChangeData> cds; 63 2 : branch = branch != null ? RefNames.fullName(branch) : null; 64 2 : if (projectState != null) { 65 1 : Project.NameKey p = projectState.getNameKey(); 66 1 : if (branch != null) { 67 1 : cds = query.byBranchCommit(p.get(), branch, token); 68 : } else { 69 0 : cds = query.byProjectCommit(p, token); 70 : } 71 1 : } else { 72 2 : cds = query.byCommit(token); 73 : } 74 2 : List<PatchSet> matches = new ArrayList<>(cds.size()); 75 2 : for (ChangeData cd : cds) { 76 2 : Change c = cd.change(); 77 2 : if (!(inProject(c, projectState) && inBranch(c, branch))) { 78 0 : continue; 79 : } 80 2 : for (PatchSet ps : cd.patchSets()) { 81 2 : if (ObjectIds.matchesAbbreviation(ps.commitId(), token)) { 82 2 : matches.add(ps); 83 : } 84 2 : } 85 2 : } 86 : 87 2 : switch (matches.size()) { 88 : case 1: 89 2 : return matches.iterator().next(); 90 : case 0: 91 0 : throw error("\"" + token + "\" no such patch set"); 92 : default: 93 0 : throw error("\"" + token + "\" matches multiple patch sets"); 94 : } 95 : } 96 : 97 : // By older style change,patchset? 98 : // 99 0 : if (token.matches("^[1-9][0-9]*,[1-9][0-9]*$")) { 100 : PatchSet.Id patchSetId; 101 : try { 102 0 : patchSetId = PatchSet.Id.parse(token); 103 0 : } catch (IllegalArgumentException e) { 104 0 : throw error("\"" + token + "\" is not a valid patch set", e); 105 0 : } 106 0 : ChangeNotes notes = getNotes(projectState, patchSetId.changeId()); 107 0 : PatchSet patchSet = psUtil.get(notes, patchSetId); 108 0 : if (patchSet == null) { 109 0 : throw error("\"" + token + "\" no such patch set"); 110 : } 111 0 : if (projectState != null || branch != null) { 112 0 : Change change = notes.getChange(); 113 0 : if (!inProject(change, projectState)) { 114 0 : throw error("change " + change.getId() + " not in project " + projectState.getName()); 115 : } 116 0 : if (!inBranch(change, branch)) { 117 0 : throw error("change " + change.getId() + " not in branch " + branch); 118 : } 119 : } 120 0 : return patchSet; 121 : } 122 : 123 0 : throw error("\"" + token + "\" is not a valid patch set"); 124 : } 125 : 126 : private ChangeNotes getNotes(@Nullable ProjectState projectState, Change.Id changeId) 127 : throws UnloggedFailure { 128 0 : if (projectState != null) { 129 0 : return notesFactory.create(projectState.getNameKey(), changeId); 130 : } 131 0 : Optional<ChangeNotes> notes = changeFinder.findOne(changeId); 132 0 : if (!notes.isPresent()) { 133 0 : throw error("\"" + changeId + "\" no such change"); 134 : } 135 0 : return notesFactory.create(notes.get().getProjectName(), changeId); 136 : } 137 : 138 : private static boolean inProject(Change change, ProjectState projectState) { 139 2 : if (projectState == null) { 140 : // No --project option, so they want every project. 141 2 : return true; 142 : } 143 1 : return projectState.getNameKey().equals(change.getProject()); 144 : } 145 : 146 : private static boolean inBranch(Change change, String branch) { 147 2 : if (branch == null) { 148 : // No --branch option, so they want every branch. 149 1 : return true; 150 : } 151 1 : return change.getDest().branch().equals(branch); 152 : } 153 : 154 : public static UnloggedFailure error(String msg) { 155 0 : return new UnloggedFailure(1, msg); 156 : } 157 : 158 : public static UnloggedFailure error(String msg, Throwable why) { 159 0 : return new UnloggedFailure(1, msg, why); 160 : } 161 : }