/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.git; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; import org.eclipse.che.api.project.shared.dto.event.GitCheckoutEventDto; import org.eclipse.che.api.project.shared.dto.event.GitCheckoutEventDto.Type; import org.eclipse.che.api.vfs.Path; import org.eclipse.che.api.vfs.VirtualFileSystemProvider; import org.eclipse.che.api.vfs.watcher.FileWatcherManager; import org.slf4j.Logger; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import java.nio.file.PathMatcher; import java.util.Set; import java.util.function.Consumer; import java.util.regex.Pattern; import static com.google.common.collect.Sets.newConcurrentHashSet; import static java.nio.file.Files.isDirectory; import static java.util.regex.Pattern.compile; import static org.eclipse.che.api.project.shared.dto.event.GitCheckoutEventDto.Type.BRANCH; import static org.eclipse.che.api.project.shared.dto.event.GitCheckoutEventDto.Type.REVISION; import static org.eclipse.che.api.vfs.watcher.FileWatcherManager.EMPTY_CONSUMER; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.slf4j.LoggerFactory.getLogger; public class GitCheckoutDetector { private static final Logger LOG = getLogger(GitCheckoutDetector.class); private static final String GIT_DIR = ".git"; private static final String HEAD_FILE = "HEAD"; private static final Pattern PATTERN = compile("ref: refs/heads/"); private static final String INCOMING_METHOD = "track:git-checkout"; private static final String OUTGOING_METHOD = "event:git-checkout"; private final VirtualFileSystemProvider vfsProvider; private final RequestTransmitter transmitter; private final FileWatcherManager manager; private final Set<String> endpointIds = newConcurrentHashSet(); private int id; @Inject public GitCheckoutDetector(VirtualFileSystemProvider vfsProvider, RequestTransmitter transmitter, FileWatcherManager manager) { this.vfsProvider = vfsProvider; this.transmitter = transmitter; this.manager = manager; } @Inject public void configureHandler(RequestHandlerConfigurator configurator) { configurator.newConfiguration() .methodName(INCOMING_METHOD) .noParams() .noResult() .withConsumer(endpointIds::add); } @PostConstruct public void startWatcher() { id = manager.registerByMatcher(matcher(), createConsumer(), modifyConsumer(), deleteConsumer()); } @PreDestroy public void stopWatcher() { manager.unRegisterByMatcher(id); } private PathMatcher matcher() { return it -> !isDirectory(it) && HEAD_FILE.equals(it.getFileName().toString()) && GIT_DIR.equals(it.getParent().getFileName().toString()); } private Consumer<String> createConsumer() { return fsEventConsumer(); } private Consumer<String> modifyConsumer() { return fsEventConsumer(); } private Consumer<String> deleteConsumer() { return EMPTY_CONSUMER; } private Consumer<String> fsEventConsumer() { return it -> { try { String content = vfsProvider.getVirtualFileSystem() .getRoot() .getChild(Path.of(it)) .getContentAsString(); Type type = content.contains("ref:") ? BRANCH : REVISION; String name = type == REVISION ? content : PATTERN.split(content)[1]; endpointIds.forEach(transmitConsumer(type, name)); } catch (ServerException | ForbiddenException e) { LOG.error("Error trying to read {} file and broadcast it", it, e); } }; } private Consumer<String> transmitConsumer(Type type, String name) { return id -> transmitter.newRequest() .endpointId(id) .methodName(OUTGOING_METHOD) .paramsAsDto(newDto(GitCheckoutEventDto.class).withName(name).withType(type)) .sendAndSkipResult(); } }