1 /*
  2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 package org.openjdk.skara.bots.bridgekeeper;
 24 
 25 import org.openjdk.skara.bot.*;
 26 import org.openjdk.skara.forge.*;
 27 
 28 import java.nio.file.Path;
 29 import java.time.*;
 30 import java.util.*;
 31 import java.util.logging.Logger;
 32 
 33 class PullRequestPrunerBotWorkItem implements WorkItem {
 34     private final Logger log = Logger.getLogger("org.openjdk.skara.bots");;
 35     private final HostedRepository repository;
 36     private final PullRequest pr;
 37     private final Duration maxAge;
 38 
 39     PullRequestPrunerBotWorkItem(HostedRepository repository, PullRequest pr, Duration maxAge) {
 40         this.pr = pr;
 41         this.repository = repository;
 42         this.maxAge = maxAge;
 43     }
 44 
 45     @Override
 46     public boolean concurrentWith(WorkItem other) {
 47         if (!(other instanceof PullRequestPrunerBotWorkItem)) {
 48             return true;
 49         }
 50         PullRequestPrunerBotWorkItem otherItem = (PullRequestPrunerBotWorkItem) other;
 51         if (!pr.id().equals(otherItem.pr.id())) {
 52             return true;
 53         }
 54         if (!repository.name().equals(otherItem.repository.name())) {
 55             return true;
 56         }
 57         return false;
 58     }
 59 
 60     // Prune durations are on the order of days and weeks
 61     private String formatDuration(Duration duration) {
 62         var count = duration.toDays();
 63         var unit = "day";
 64 
 65         if (count > 14) {
 66             count /= 7;
 67             unit = "week";
 68         }
 69         if (count != 1) {
 70             unit += "s";
 71         }
 72         return count + " " + unit;
 73     }
 74 
 75     @Override
 76     public void run(Path scratchPath) {
 77         var message = "@" + pr.author().userName() + " This pull request has been inactive for more than " +
 78                 formatDuration(maxAge) + " and will be automatically closed. If you think this is incorrect, " +
 79                 "feel free to reopen it!";
 80 
 81         log.fine("Posting prune message");
 82         pr.addComment(message);
 83 
 84         pr.setState(PullRequest.State.CLOSED);
 85     }
 86 }
 87 
 88 public class PullRequestPrunerBot implements Bot {
 89     private final HostedRepository repository;
 90     private final Duration maxAge;
 91 
 92     PullRequestPrunerBot(HostedRepository repository, Duration maxAge) {
 93         this.repository = repository;
 94         this.maxAge = maxAge;
 95     }
 96 
 97     @Override
 98     public List<WorkItem> getPeriodicItems() {
 99         List<WorkItem> ret = new LinkedList<>();
100         var oldestAllowed = ZonedDateTime.now().minus(maxAge);
101 
102         for (var pr : repository.pullRequests()) {
103             if (pr.updatedAt().isBefore(oldestAllowed)) {
104                 var item = new PullRequestPrunerBotWorkItem(repository, pr, maxAge);
105                 ret.add(item);
106             }
107         }
108 
109         return ret;
110     }
111 }