Line data Source code
1 : // Copyright (C) 2015 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.change; 16 : 17 : import com.google.common.collect.ImmutableListMultimap; 18 : import com.google.common.collect.ListMultimap; 19 : import com.google.common.flogger.FluentLogger; 20 : import com.google.gerrit.entities.Project; 21 : import com.google.gerrit.exceptions.StorageException; 22 : import com.google.gerrit.index.query.QueryParseException; 23 : import com.google.gerrit.server.InternalUser; 24 : import com.google.gerrit.server.config.ChangeCleanupConfig; 25 : import com.google.gerrit.server.query.change.ChangeData; 26 : import com.google.gerrit.server.query.change.ChangeQueryBuilder; 27 : import com.google.gerrit.server.query.change.ChangeQueryProcessor; 28 : import com.google.gerrit.server.update.BatchUpdate; 29 : import com.google.inject.Inject; 30 : import com.google.inject.Provider; 31 : import com.google.inject.Singleton; 32 : import java.util.ArrayList; 33 : import java.util.Collection; 34 : import java.util.List; 35 : import java.util.concurrent.TimeUnit; 36 : 37 : @Singleton 38 : public class AbandonUtil { 39 138 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 40 : 41 : private final ChangeCleanupConfig cfg; 42 : private final Provider<ChangeQueryProcessor> queryProvider; 43 : private final ChangeQueryBuilder queryBuilder; 44 : private final BatchAbandon batchAbandon; 45 : private final InternalUser internalUser; 46 : 47 : @Inject 48 : AbandonUtil( 49 : ChangeCleanupConfig cfg, 50 : InternalUser.Factory internalUserFactory, 51 : Provider<ChangeQueryProcessor> queryProvider, 52 : ChangeQueryBuilder queryBuilder, 53 138 : BatchAbandon batchAbandon) { 54 138 : this.cfg = cfg; 55 138 : this.queryProvider = queryProvider; 56 138 : this.queryBuilder = queryBuilder; 57 138 : this.batchAbandon = batchAbandon; 58 138 : internalUser = internalUserFactory.create(); 59 138 : } 60 : 61 : public void abandonInactiveOpenChanges(BatchUpdate.Factory updateFactory) { 62 1 : if (cfg.getAbandonAfter() <= 0) { 63 0 : return; 64 : } 65 : 66 : try { 67 1 : String query = 68 1 : "status:new age:" + TimeUnit.MILLISECONDS.toMinutes(cfg.getAbandonAfter()) + "m"; 69 1 : if (!cfg.getAbandonIfMergeable()) { 70 1 : query += " -is:mergeable"; 71 : } 72 : 73 1 : List<ChangeData> changesToAbandon = 74 1 : queryProvider.get().enforceVisibility(false).query(queryBuilder.parse(query)).entities(); 75 : ImmutableListMultimap.Builder<Project.NameKey, ChangeData> builder = 76 1 : ImmutableListMultimap.builder(); 77 1 : for (ChangeData cd : changesToAbandon) { 78 1 : builder.put(cd.project(), cd); 79 1 : } 80 : 81 1 : int count = 0; 82 1 : ListMultimap<Project.NameKey, ChangeData> abandons = builder.build(); 83 1 : String message = cfg.getAbandonMessage(); 84 1 : for (Project.NameKey project : abandons.keySet()) { 85 1 : Collection<ChangeData> changes = getValidChanges(abandons.get(project), query); 86 : try { 87 1 : batchAbandon.batchAbandon(updateFactory, project, internalUser, changes, message); 88 1 : count += changes.size(); 89 0 : } catch (Exception e) { 90 0 : StringBuilder msg = new StringBuilder("Failed to auto-abandon inactive change(s):"); 91 0 : for (ChangeData change : changes) { 92 0 : msg.append(" ").append(change.getId().get()); 93 0 : } 94 0 : msg.append("."); 95 0 : logger.atSevere().withCause(e).log("%s", msg); 96 1 : } 97 1 : } 98 1 : logger.atInfo().log("Auto-Abandoned %d of %d changes.", count, changesToAbandon.size()); 99 0 : } catch (QueryParseException | StorageException e) { 100 0 : logger.atSevere().withCause(e).log( 101 : "Failed to query inactive open changes for auto-abandoning."); 102 1 : } 103 1 : } 104 : 105 : private Collection<ChangeData> getValidChanges(Collection<ChangeData> changes, String query) 106 : throws QueryParseException { 107 1 : Collection<ChangeData> validChanges = new ArrayList<>(); 108 1 : for (ChangeData cd : changes) { 109 1 : String newQuery = query + " change:" + cd.getId(); 110 1 : List<ChangeData> changesToAbandon = 111 : queryProvider 112 1 : .get() 113 1 : .enforceVisibility(false) 114 1 : .query(queryBuilder.parse(newQuery)) 115 1 : .entities(); 116 1 : if (!changesToAbandon.isEmpty()) { 117 1 : validChanges.add(cd); 118 : } else { 119 0 : logger.atFine().log( 120 : "Change data with id \"%s\" does not satisfy the query \"%s\"" 121 : + " any more, hence skipping it in clean up", 122 0 : cd.getId(), query); 123 : } 124 1 : } 125 1 : return validChanges; 126 : } 127 : }