/*
* 2016 Red Hat, Inc. and/or its affiliates.
*
* 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 org.guvnor.structure.backend.repositories;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.inject.Inject;
import javax.inject.Named;
import org.guvnor.structure.repositories.GitMetadata;
import org.guvnor.structure.repositories.GitMetadataStore;
import org.guvnor.structure.repositories.PullRequest;
import org.guvnor.structure.repositories.PullRequestAlreadyExistsException;
import org.guvnor.structure.repositories.PullRequestService;
import org.guvnor.structure.repositories.PullRequestStatus;
import org.guvnor.structure.repositories.Repository;
import org.guvnor.structure.repositories.RepositoryNotFoundException;
import org.guvnor.structure.repositories.impl.GitMetadataImpl;
import org.guvnor.structure.repositories.impl.PullRequestImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.uberfire.io.IOService;
import org.uberfire.java.nio.base.FileDiff;
import org.uberfire.java.nio.base.options.MergeCopyOption;
import org.uberfire.java.nio.file.Path;
import static java.lang.Integer.*;
import static java.util.stream.Collectors.*;
import static org.uberfire.backend.server.util.Paths.*;
import static org.uberfire.commons.validation.PortablePreconditions.*;
public class PullRequestServiceImpl implements PullRequestService {
private final GitMetadataStore metadataStore;
private final IOService ioService;
private final ConfiguredRepositories configuredRepositories;
private Logger logger = LoggerFactory.getLogger( PullRequestServiceImpl.class );
@Inject
public PullRequestServiceImpl( GitMetadataStore metadataStore,
@Named("ioStrategy") IOService ioService,
ConfiguredRepositories configuredRepositories ) {
this.metadataStore = metadataStore;
this.ioService = ioService;
this.configuredRepositories = configuredRepositories;
}
@Override
public PullRequest createPullRequest( String sourceRepository,
String sourceBranch,
String targetRepository,
String targetBranch ) {
checkNotEmpty( "sourceRepository", sourceRepository );
checkNotEmpty( "sourceBranch", sourceBranch );
checkNotEmpty( "targetRepository", targetRepository );
checkNotEmpty( "targetBranch", targetBranch );
final Optional<GitMetadata> targetRepositoryMetadata = this.metadataStore.read( targetRepository );
PullRequestImpl storablePullRequest;
if ( targetRepositoryMetadata.isPresent() ) {
GitMetadata metadata = targetRepositoryMetadata.get();
long generatedId = this.generatePullRequestId( metadata );
List<PullRequest> pullRequests = metadata.getPullRequests();
if ( pullRequests == null ) {
pullRequests = new ArrayList<>();
}
storablePullRequest = new PullRequestImpl( generatedId,
sourceRepository,
sourceBranch,
targetRepository,
targetBranch, PullRequestStatus.OPEN );
if ( metadata.exists( storablePullRequest ) ) {
throw new PullRequestAlreadyExistsException( storablePullRequest );
}
pullRequests.add( storablePullRequest );
GitMetadata newMetadata = new GitMetadataImpl( metadata.getName(), metadata.getOrigin(), metadata.getForks(), pullRequests );
this.metadataStore.write( metadata.getName(), newMetadata );
if ( logger.isDebugEnabled() ) {
logger.debug( "Pull request PR-{} created. Target repository: {} / {}", storablePullRequest.getId(), storablePullRequest.getTargetRepository(), storablePullRequest.getTargetBranch() );
}
} else {
throw new RepositoryNotFoundException(
String.format( "The repository or branch does not exists %s/%s",
targetRepository,
targetBranch ) );
}
return storablePullRequest;
}
@Override
public PullRequest acceptPullRequest( final PullRequest pullRequest ) {
checkNotNull( "pullRequest", pullRequest );
checkNotNull( "id", pullRequest.getId() );
checkNotEmpty( "targetRepository", pullRequest.getTargetRepository() );
String repository = pullRequest.getTargetRepository();
long id = pullRequest.getId();
final GitMetadata metadata = this.getRepositoryMetadata( repository );
final PullRequest acceptPullRequest = metadata.getPullRequest( id );
this.createHiddenBranch( acceptPullRequest );
this.mergePullRequest( acceptPullRequest );
this.changePullRequestStatus( repository, id, PullRequestStatus.MERGED );
return this.getRepositoryMetadata( repository ).getPullRequest( id );
}
@Override
public PullRequest rejectPullRequest( final PullRequest pullRequest ) {
checkNotNull( "pullRequest", pullRequest );
checkNotNull( "id", pullRequest.getId() );
checkNotEmpty( "targetRepository", pullRequest.getTargetRepository() );
String repository = pullRequest.getTargetRepository();
long id = pullRequest.getId();
this.changePullRequestStatus( repository, id, PullRequestStatus.REJECTED );
return this.getRepositoryMetadata( repository ).getPullRequest( id );
}
@Override
public PullRequest closePullRequest( final PullRequest pullRequest ) {
checkNotNull( "pullRequest", pullRequest );
checkNotNull( "id", pullRequest.getId() );
checkNotEmpty( "targetRepository", pullRequest.getTargetRepository() );
String repository = pullRequest.getTargetRepository();
long id = pullRequest.getId();
this.changePullRequestStatus( repository, id, PullRequestStatus.CLOSED );
return this.getRepositoryMetadata( repository ).getPullRequest( id );
}
public List<PullRequest> getPullRequestsByBranch( Integer page,
Integer pageSize,
final String repository,
final String branch ) {
GitMetadata metadata = getRepositoryMetadata( repository );
List<PullRequest> pullRequests = metadata.getPullRequests( elem -> elem.getTargetBranch().equals( branch ) );
return this.paginate( page, pageSize, pullRequests );
}
@Override
public List<PullRequest> getPullRequestsByRepository( Integer page,
Integer pageSize,
final String repository ) {
GitMetadata metadata = getRepositoryMetadata( repository );
List<PullRequest> pullRequests = metadata.getPullRequests();
return this.paginate( page, pageSize, pullRequests );
}
public List<PullRequest> getPullRequestsByStatus( Integer page,
Integer pageSize,
final String repository,
final PullRequestStatus status ) {
final List<PullRequest> pullRequests = this.getPullRequestsByRepository( page, pageSize, repository );
final List<PullRequest> finalPullRequests = pullRequests.stream().filter( elem -> elem.getStatus().equals( status ) ).collect( Collectors.toList() );
return this.paginate( page, pageSize, finalPullRequests );
}
@Override
public void deletePullRequest( final PullRequest pullRequest ) {
checkNotNull( "pullRequest", pullRequest );
checkNotNull( "id", pullRequest.getId() );
checkNotEmpty( "targetRepository", pullRequest.getTargetRepository() );
String repository = pullRequest.getTargetRepository();
long id = pullRequest.getId();
GitMetadata metadata = getRepositoryMetadata( repository );
PullRequest removablePullRequest = metadata.getPullRequest( id );
List<PullRequest> finalPullRequests = metadata.getPullRequests();
finalPullRequests.remove( removablePullRequest );
GitMetadata storableMetadata = new GitMetadataImpl( metadata.getName(), metadata.getOrigin(), metadata.getForks(), finalPullRequests );
this.metadataStore.write( storableMetadata.getName(), storableMetadata );
this.deleteHiddenBranch( removablePullRequest );
}
protected List<PullRequest> paginate( Integer page,
Integer pageSize,
List<PullRequest> pullRequests ) {
if ( page == 0 && pageSize == 0 ) {
return pullRequests;
}
Integer finalPageSize = pageSize == 0 ? 10 : pageSize;
final Map<Integer, List<PullRequest>> map = IntStream.iterate( 0, i -> i + finalPageSize )
.limit( ( pullRequests.size() + finalPageSize - 1 ) / finalPageSize )
.boxed()
.collect( toMap( i -> i / finalPageSize,
i -> pullRequests.subList( i, min( i + finalPageSize, pullRequests.size() ) ) ) );
return map.getOrDefault( page, new ArrayList<>() );
}
@Override
public List<FileDiff> diff( final PullRequest pullRequest ) {
final Repository repository = configuredRepositories.getRepositoryByRepositoryAlias( pullRequest.getTargetRepository() );
this.createHiddenBranch( pullRequest );
String diff = String.format( "diff:%s,%s", pullRequest.getTargetBranch(), this.buildHiddenBranchName( pullRequest ) );
final List<FileDiff> diffs = (List<FileDiff>) this.ioService.readAttributes( convert( repository.getRoot() ), diff );
this.deleteHiddenBranch( pullRequest );
return diffs;
}
protected void changePullRequestStatus( final String repository,
final long id,
final PullRequestStatus status ) {
checkNotEmpty( "repository", repository );
checkNotNull( "status", status );
GitMetadata metadata = getRepositoryMetadata( repository );
PullRequest pullRequest = metadata.getPullRequest( id );
PullRequestImpl finalPullRequest = new PullRequestImpl( pullRequest.getId(),
pullRequest.getSourceRepository(),
pullRequest.getSourceBranch(),
pullRequest.getTargetRepository(),
pullRequest.getTargetBranch(),
status );
List<PullRequest> finalPullRequests = metadata.getPullRequests( elem -> elem.getId() != id );
finalPullRequests.add( finalPullRequest );
GitMetadata storableMetadata = new GitMetadataImpl( metadata.getName(), metadata.getOrigin(), metadata.getForks(), finalPullRequests );
this.metadataStore.write( storableMetadata.getName(), storableMetadata );
}
private GitMetadata getRepositoryMetadata( final String repository ) {
final Optional<GitMetadata> optional = this.metadataStore.read( repository );
if ( !optional.isPresent() ) {
throw new RepositoryNotFoundException(
String.format( "The repository does not exists <<%s>>", repository ) );
}
return optional.get();
}
private synchronized long generatePullRequestId( final GitMetadata metadata ) {
List<PullRequest> pullRequests = metadata.getPullRequests();
final Optional<PullRequest> last = pullRequests.stream().max( ( first, second ) -> Long.compare( first.getId(), second.getId() ) );
return last.map( pr -> pr.getId() + 1 ).orElse( 1l );
}
protected void createHiddenBranch( final PullRequest pullRequest ) {
final Path source = this.buildPath( pullRequest.getSourceRepository(), pullRequest.getSourceBranch() );
final Path target = this.buildHiddenPath( pullRequest );
ioService.copy( source, target );
if ( logger.isDebugEnabled() ) {
logger.debug( "Hidden branch {} created.", target.toString() );
}
}
private void mergePullRequest( final PullRequest pullRequest ) {
final Path source = this.buildHiddenPath( pullRequest );
final Path target = this.buildPath( pullRequest.getTargetRepository(), pullRequest.getTargetBranch() );
ioService.copy( source, target, new MergeCopyOption() );
if ( logger.isDebugEnabled() ) {
logger.debug( "Merged from <{}> to <{}>", source.toString(), target.toString() );
}
}
private void deleteHiddenBranch( final PullRequest pullRequest ) {
final Path path = this.buildHiddenPath( pullRequest );
ioService.delete( path );
if ( logger.isDebugEnabled() ) {
logger.debug( "Hidden branch {} deleted", pullRequest.toString() );
}
}
protected Path buildHiddenPath( PullRequest pullRequest ) {
String branchName = buildHiddenBranchName( pullRequest );
return this.buildPath( pullRequest.getTargetRepository(), branchName );
}
private String buildHiddenBranchName( PullRequest pullRequest ) {
return String.format( "PR-%s-%s/%s-%s", pullRequest.getId(), pullRequest.getSourceRepository(), pullRequest.getSourceBranch(), pullRequest.getTargetBranch() );
}
protected Path buildPath( final String repository,
final String branch ) {
return ioService.get( URI.create( String.format( "git://%s@%s", branch, repository ) ) );
}
}