/* * Copyright 2015-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.facebook.buck.util.versioncontrol; import com.facebook.buck.event.BuckEventBus; import com.facebook.buck.event.SimplePerfEvent; import com.facebook.buck.log.Logger; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.util.Optional; import java.util.concurrent.ExecutorService; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; public class VersionControlStatsGenerator { private static final Logger LOG = Logger.get(VersionControlStatsGenerator.class); /** * Modes the generator can get stats in, in order from least comprehensive to most comprehensive. * Each mode should include all the information present in the previous one. */ public enum Mode { /** Do not generate new information, but return whatever is already generated */ PREGENERATED, /** Generate a set of stats that is fast to generate but incomplete */ FAST, /** Generate the full set of stats */ FULL, ; } private static final String REMOTE_MASTER = "remote/master"; private static final ImmutableSet<String> TRACKED_BOOKMARKS = ImmutableSet.of( REMOTE_MASTER); private final VersionControlCmdLineInterface versionControlCmdLineInterface; private final Optional<FastVersionControlStats> pregeneratedVersionControlStats; @GuardedBy("this") @Nullable private FastVersionControlStats fastStats; @GuardedBy("this") @Nullable private ImmutableSet<String> changedFiles; @GuardedBy("this") @Nullable private Optional<String> diff; public VersionControlStatsGenerator( VersionControlCmdLineInterface versionControlCmdLineInterface, Optional<FastVersionControlStats> pregeneratedVersionControlStats) { this.versionControlCmdLineInterface = versionControlCmdLineInterface; this.pregeneratedVersionControlStats = pregeneratedVersionControlStats; pregeneratedVersionControlStats.ifPresent( x -> this.fastStats = FastVersionControlStats.of( x.getCurrentRevisionId(), x.getBaseBookmarks(), x.getBranchedFromMasterRevisionId(), x.getBranchedFromMasterTS())); } public void generateStatsAsync( Mode mode, ExecutorService executorService, BuckEventBus buckEventBus) { executorService.submit( () -> { try { Optional<FullVersionControlStats> versionControlStats; try (SimplePerfEvent.Scope ignored = SimplePerfEvent.scope(buckEventBus, "gen_source_control_info")) { versionControlStats = generateStats(mode); } versionControlStats.ifPresent(x -> buckEventBus.post(new VersionControlStatsEvent(x))); } catch (InterruptedException e) { LOG.warn(e, "Failed to generate VC stats due to being interrupted. Skipping.."); Thread.currentThread().interrupt(); // Re-set interrupt flag } }); } public synchronized Optional<FullVersionControlStats> generateStats(Mode mode) throws InterruptedException { if (mode == Mode.PREGENERATED) { return pregeneratedVersionControlStats.map( x -> FullVersionControlStats.builder().from(x).build()); } FullVersionControlStats versionControlStats = null; LOG.info("Starting generation of version control stats."); if (!versionControlCmdLineInterface.isSupportedVersionControlSystem()) { LOG.warn("Skipping generation of version control stats as unsupported repository type."); } else { FullVersionControlStats.Builder versionControlStatsBuilder = FullVersionControlStats.builder(); try { if (fastStats == null) { fastStats = versionControlCmdLineInterface.fastVersionControlStats(); } versionControlStatsBuilder.setCurrentRevisionId(fastStats.getCurrentRevisionId()); versionControlStatsBuilder.setBaseBookmarks( Sets.intersection(fastStats.getBaseBookmarks(), TRACKED_BOOKMARKS)); versionControlStatsBuilder.setBranchedFromMasterRevisionId( fastStats.getBranchedFromMasterRevisionId()); versionControlStatsBuilder.setBranchedFromMasterTS(fastStats.getBranchedFromMasterTS()); if (mode == Mode.FULL) { // Prepopulate as much as possible before trying to query the VCS: this way if it fails // we still have this information. if (changedFiles != null) { versionControlStatsBuilder.setPathsChangedInWorkingDirectory(changedFiles); } if (diff != null) { versionControlStatsBuilder.setDiff(diff); } if (changedFiles == null) { changedFiles = versionControlCmdLineInterface.changedFiles( fastStats.getBranchedFromMasterRevisionId()); versionControlStatsBuilder.setPathsChangedInWorkingDirectory(changedFiles); } if (diff == null) { diff = versionControlCmdLineInterface.diffBetweenRevisionsOrAbsent( fastStats.getBranchedFromMasterRevisionId(), fastStats.getCurrentRevisionId()); versionControlStatsBuilder.setDiff(diff); } } } catch (VersionControlCommandFailedException e) { LOG.warn("Failed to gather some source control stats."); } versionControlStats = versionControlStatsBuilder.build(); LOG.info("Stats generated successfully. \n%s", versionControlStats); } return Optional.ofNullable(versionControlStats); } }