Line data Source code
1 : // Copyright (C) 2012 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.server.restapi.change; 16 : 17 : import com.google.common.base.Joiner; 18 : import com.google.common.collect.ImmutableList; 19 : import com.google.common.collect.Lists; 20 : import com.google.gerrit.common.Nullable; 21 : import com.google.gerrit.entities.PatchSet; 22 : import com.google.gerrit.extensions.registration.DynamicMap; 23 : import com.google.gerrit.extensions.restapi.AuthException; 24 : import com.google.gerrit.extensions.restapi.ChildCollection; 25 : import com.google.gerrit.extensions.restapi.IdString; 26 : import com.google.gerrit.extensions.restapi.ResourceNotFoundException; 27 : import com.google.gerrit.extensions.restapi.RestView; 28 : import com.google.gerrit.git.ObjectIds; 29 : import com.google.gerrit.server.PatchSetUtil; 30 : import com.google.gerrit.server.change.ChangeResource; 31 : import com.google.gerrit.server.change.RevisionResource; 32 : import com.google.gerrit.server.edit.ChangeEdit; 33 : import com.google.gerrit.server.edit.ChangeEditUtil; 34 : import com.google.gerrit.server.permissions.ChangePermission; 35 : import com.google.gerrit.server.permissions.PermissionBackend; 36 : import com.google.gerrit.server.permissions.PermissionBackendException; 37 : import com.google.gerrit.server.project.ProjectCache; 38 : import com.google.gerrit.server.project.ProjectState; 39 : import com.google.inject.Inject; 40 : import com.google.inject.Singleton; 41 : import java.io.IOException; 42 : import java.util.ArrayList; 43 : import java.util.List; 44 : import java.util.Optional; 45 : import org.eclipse.jgit.lib.ObjectId; 46 : import org.eclipse.jgit.revwalk.RevCommit; 47 : 48 : @Singleton 49 : public class Revisions implements ChildCollection<ChangeResource, RevisionResource> { 50 : private final DynamicMap<RestView<RevisionResource>> views; 51 : private final ChangeEditUtil editUtil; 52 : private final PatchSetUtil psUtil; 53 : private final PermissionBackend permissionBackend; 54 : private final ProjectCache projectCache; 55 : 56 : @Inject 57 : Revisions( 58 : DynamicMap<RestView<RevisionResource>> views, 59 : ChangeEditUtil editUtil, 60 : PatchSetUtil psUtil, 61 : PermissionBackend permissionBackend, 62 145 : ProjectCache projectCache) { 63 145 : this.views = views; 64 145 : this.editUtil = editUtil; 65 145 : this.psUtil = psUtil; 66 145 : this.permissionBackend = permissionBackend; 67 145 : this.projectCache = projectCache; 68 145 : } 69 : 70 : @Override 71 : public DynamicMap<RestView<RevisionResource>> views() { 72 11 : return views; 73 : } 74 : 75 : @Override 76 : public RestView<ChangeResource> list() throws ResourceNotFoundException { 77 1 : throw new ResourceNotFoundException(); 78 : } 79 : 80 : @Override 81 : public RevisionResource parse(ChangeResource change, IdString id) 82 : throws ResourceNotFoundException, AuthException, IOException, PermissionBackendException { 83 80 : if (id.get().equals("current")) { 84 76 : PatchSet ps = psUtil.current(change.getNotes()); 85 76 : if (ps != null && visible(change)) { 86 76 : return RevisionResource.createNonCacheable(change, ps); 87 : } 88 0 : throw new ResourceNotFoundException(id); 89 : } 90 : 91 42 : List<RevisionResource> match = Lists.newArrayListWithExpectedSize(2); 92 42 : for (RevisionResource rsrc : find(change, id.get())) { 93 42 : if (visible(change)) { 94 42 : match.add(rsrc); 95 : } 96 42 : } 97 42 : switch (match.size()) { 98 : case 0: 99 1 : throw new ResourceNotFoundException(id); 100 : case 1: 101 42 : return match.get(0); 102 : default: 103 0 : throw new ResourceNotFoundException( 104 0 : "Multiple patch sets for \"" + id.get() + "\": " + Joiner.on("; ").join(match)); 105 : } 106 : } 107 : 108 : private boolean visible(ChangeResource change) throws PermissionBackendException { 109 80 : return permissionBackend 110 80 : .user(change.getUser()) 111 80 : .change(change.getNotes()) 112 80 : .test(ChangePermission.READ) 113 80 : && projectCache.get(change.getProject()).map(ProjectState::statePermitsRead).orElse(false); 114 : } 115 : 116 : private ImmutableList<RevisionResource> find(ChangeResource change, String id) 117 : throws IOException, AuthException { 118 42 : if (id.equals("0") || id.equals("edit")) { 119 4 : return loadEdit(change, null); 120 42 : } else if (id.length() < 6 && id.matches("^[1-9][0-9]{0,4}$")) { 121 : // Legacy patch set number syntax. 122 33 : return byLegacyPatchSetId(change, id); 123 22 : } else if (id.length() < 4 || id.length() > ObjectIds.STR_LEN) { 124 : // Require a minimum of 4 digits. 125 : // Impossibly long identifier will never match. 126 0 : return ImmutableList.of(); 127 : } else { 128 22 : List<RevisionResource> out = new ArrayList<>(); 129 22 : for (PatchSet ps : psUtil.byChange(change.getNotes())) { 130 22 : if (ObjectIds.matchesAbbreviation(ps.commitId(), id)) { 131 22 : out.add(new RevisionResource(change, ps)); 132 : } 133 22 : } 134 : // Not an existing patch set on a change, but might be an edit. 135 22 : if (out.isEmpty() && ObjectId.isId(id)) { 136 2 : return loadEdit(change, ObjectId.fromString(id)); 137 : } 138 22 : return ImmutableList.copyOf(out); 139 : } 140 : } 141 : 142 : private ImmutableList<RevisionResource> byLegacyPatchSetId(ChangeResource change, String id) { 143 33 : PatchSet ps = psUtil.get(change.getNotes(), PatchSet.id(change.getId(), Integer.parseInt(id))); 144 33 : if (ps != null) { 145 33 : return ImmutableList.of(new RevisionResource(change, ps)); 146 : } 147 0 : return ImmutableList.of(); 148 : } 149 : 150 : private ImmutableList<RevisionResource> loadEdit( 151 : ChangeResource change, @Nullable ObjectId commitId) throws AuthException, IOException { 152 5 : Optional<ChangeEdit> edit = editUtil.byChange(change.getNotes(), change.getUser()); 153 5 : if (edit.isPresent()) { 154 4 : RevCommit editCommit = edit.get().getEditCommit(); 155 : PatchSet ps = 156 4 : PatchSet.builder() 157 4 : .id(PatchSet.id(change.getId(), 0)) 158 4 : .commitId(editCommit) 159 4 : .uploader(change.getUser().getAccountId()) 160 4 : .createdOn(editCommit.getCommitterIdent().getWhenAsInstant()) 161 4 : .build(); 162 4 : if (commitId == null || editCommit.equals(commitId)) { 163 4 : return ImmutableList.of(new RevisionResource(change, ps, edit)); 164 : } 165 : } 166 1 : return ImmutableList.of(); 167 : } 168 : }