All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opoo.press.maven.wagon.github.GitHub Maven / Gradle / Ivy

There is a newer version: 1.1.3
Show newest version
/*
 * Copyright 2013 Alex Lin.
 *
 * 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.opoo.press.maven.wagon.github;

import static org.eclipse.egit.github.core.Blob.ENCODING_BASE64;
import static org.eclipse.egit.github.core.TreeEntry.MODE_BLOB;
import static org.eclipse.egit.github.core.TreeEntry.TYPE_BLOB;
import static org.eclipse.egit.github.core.TypedResource.TYPE_COMMIT;

import java.io.Console;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.codehaus.plexus.util.DirectoryScanner;
import org.eclipse.egit.github.core.Blob;
import org.eclipse.egit.github.core.Commit;
import org.eclipse.egit.github.core.Reference;
import org.eclipse.egit.github.core.RepositoryId;
import org.eclipse.egit.github.core.Tree;
import org.eclipse.egit.github.core.TreeEntry;
import org.eclipse.egit.github.core.TypedResource;
import org.eclipse.egit.github.core.client.GitHubClient;
import org.eclipse.egit.github.core.client.RequestException;
import org.eclipse.egit.github.core.service.DataService;
import org.eclipse.egit.github.core.util.EncodingUtils;

/**
 * Pure java GitHub client wagon.
 * @author Alex Lin
 * @author Kevin Sawicki ([email protected])
 */
public class GitHub{
	private static final Logger log = LoggerFactory.getLogger(GitHub.class);

	/**
	 * BRANCH_DEFAULT
	 */
	public static final String BRANCH_DEFAULT = "refs/heads/gh-pages";

	/**
	 * NO_JEKYLL_FILE
	 */
	public static final String NO_JEKYLL_FILE = ".nojekyll";

	private String branch = BRANCH_DEFAULT;

	private String message;

	private String repositoryName;

	private String repositoryOwner;

	private String userName;

	private String password;

	private String oauth2Token;

	private String host;

	private String[] includes;

	private String[] excludes;

	private boolean force;

	private boolean noJekyll = true;

	private boolean merge;

	private boolean dryRun;
	
	private int numThreads = 1;

	/**
	 * @param branch the branch to set
	 */
	public void setBranch(String branch) {
		if(!branch.startsWith("refs/heads/")){
			branch = "refs/heads/" + branch;
		}
		this.branch = branch;
	}

	/**
	 * @param message the message to set
	 */
	public void setMessage(String message) {
		this.message = message;
	}

	/**
	 * @param repositoryName the repositoryName to set
	 */
	public void setRepositoryName(String repositoryName) {
		this.repositoryName = repositoryName;
	}

	/**
	 * @param repositoryOwner the repositoryOwner to set
	 */
	public void setRepositoryOwner(String repositoryOwner) {
		this.repositoryOwner = repositoryOwner;
	}

	/**
	 * @param userName the userName to set
	 */
	public void setUserName(String userName) {
		this.userName = userName;
	}

	/**
	 * @param password the password to set
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * @param oauth2Token the oauth2Token to set
	 */
	public void setOauth2Token(String oauth2Token) {
		this.oauth2Token = oauth2Token;
	}

	/**
	 * @param host the host to set
	 */
	public void setHost(String host) {
		this.host = host;
	}

	/**
	 * @param includes the includes to set
	 */
	public void setIncludes(String[] includes) {
		this.includes = includes;
	}

	/**
	 * @param excludes the excludes to set
	 */
	public void setExcludes(String[] excludes) {
		this.excludes = excludes;
	}

	/**
	 * @param force the force to set
	 */
	public void setForce(boolean force) {
		this.force = force;
	}

	/**
	 * @param noJekyll the noJekyll to set
	 */
	public void setNoJekyll(boolean noJekyll) {
		this.noJekyll = noJekyll;
	}

	/**
	 * @param merge the merge to set
	 */
	public void setMerge(boolean merge) {
		this.merge = merge;
	}

	/**
	 * @param dryRun the dryRun to set
	 */
	public void setDryRun(boolean dryRun) {
		this.dryRun = dryRun;
	}
	
	/**
	 * @param numThreads the numThreads to set
	 */
	public void setNumThreads(int numThreads) {
		this.numThreads = numThreads;
	}

	/**
	 * 
	 */
	public GitHub() {
		super();
	}

	public void deploy(File outputDirectory, String destinationDirectory) throws GitHubException {
		RepositoryId repository = getRepository(repositoryOwner, repositoryName);
		if (dryRun){
			log.info("Dry run mode, repository will not be modified");
		}
		
		String[] paths = getPaths(outputDirectory);
		String prefix = getPrefix(destinationDirectory);
		
		GitHubClient client = createClient(host, userName, password, oauth2Token);
		DataService service = new DataService(client);
		
		boolean createNoJekyll = noJekyll;
		
		if(createNoJekyll){
			for(String path: paths){
				if (NO_JEKYLL_FILE.equals(path)){
					createNoJekyll = false;
					break;
				}
			}
		}

		// Write blobs and build tree entries
		List entries = new ArrayList(paths.length);
		if(numThreads <= 1){
			createEntries(entries, prefix, paths, service, repository, outputDirectory);
		}else{
			createEntriesInThreads(entries, prefix, paths, service, repository, outputDirectory, numThreads);
		}
		
		if (createNoJekyll) {
			if (log.isDebugEnabled()){
				log.debug("Creating empty '.nojekyll' blob at root of tree");
			}
			TreeEntry entry = createEntry("", NO_JEKYLL_FILE, service, repository, outputDirectory);
			entries.add(entry);
		}

		Reference ref = getReference(service, repository);
		
		if(dryRun){
			log.debug("Dry run mode, skip deploy.");
			return;
		}

		// Write tree
		Tree tree = createTree(service, repository, ref, entries);

		// Build commit
		Commit commit = new Commit();
		commit.setMessage(message != null ? message : "GitHubWagon: Deploying OpooPress to GitHub Pages.");
		commit.setTree(tree);

		// Set parent commit SHA-1 if reference exists
		if (ref != null){
			commit.setParents(Collections.singletonList(new Commit().setSha(ref.getObject().getSha())));
		}

		Commit created;
		try {
			created = service.createCommit(repository, commit);
			log.info(MessageFormat.format("Creating commit with SHA-1: {0}", created.getSha()));
		} catch (IOException e) {
			throw new GitHubException("Error creating commit: " + e.getMessage(), e);
		}

		TypedResource object = new TypedResource();
		object.setType(TYPE_COMMIT).setSha(created.getSha());
		if (ref != null) {
			// Update existing reference
			ref.setObject(object);
			try {
				log.info(String.format("Updating reference %s from %s to %s", branch, commit.getParents().get(0).getSha(), created.getSha()));
				service.editReference(repository, ref, force);
			} catch (IOException e) {
				throw new GitHubException("Error editing reference: " + e.getMessage(), e);
			}
		} else {
			// Create new reference
			ref = new Reference().setObject(object).setRef(branch);
			try {
				log.info(MessageFormat.format("Creating reference {0} starting at commit {1}", branch, created.getSha()));
				service.createReference(repository, ref);
			} catch (IOException e) {
				throw new GitHubException("Error creating reference: " + e.getMessage(), e);
			}
		}
	}
	
	private List createEntries(List entries, final String prefix, final String[] paths, 
			final DataService service, final RepositoryId repository, final File outputDirectory) throws GitHubException{
		for (String path : paths) {
			TreeEntry entry = createEntry(prefix, path, service, repository, outputDirectory);
			entries.add(entry);
		}
		return entries;
	}

	private List createEntriesInThreads(List entries, final String prefix, final String[] paths, 
			final DataService service, final RepositoryId repository, final File outputDirectory, int numThreads) throws GitHubException{
		ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);//.newCachedThreadPool();  
        CompletionService cs = new ExecutorCompletionService(threadPool);

        for (final String path : paths) {
			cs.submit(new Callable() {
				@Override
				public TreeEntry call() throws Exception {
					return createEntry(prefix, path, service, repository, outputDirectory);
				}
			});
		}
		
        try {
        	//BUG: wait for ever??
//			Future future = cs.take();
//			while(future != null){
//				entries.add(future.get());
//				future = cs.take();
//			}
			
        	for(int i = 0 ; i < paths.length ; i++){
        		entries.add(cs.take().get());
        	}
			log.info("All entries created: " + paths.length);
		} catch (InterruptedException e) {
			throw new GitHubException("", e);
		} catch (ExecutionException e) {
			throw new GitHubException("", e);
		}
		return entries;
	}

	private Tree createTree(DataService service, RepositoryId repository, Reference ref, List entries) throws GitHubException {
		try {
			int size = entries.size();
			log.info(String.format("Creating tree with %s blob entries", size));

			String baseTree = null;
			if (merge && ref != null) {
				Tree currentTree = service.getCommit(repository, ref.getObject().getSha()).getTree();
				if (currentTree != null){
					baseTree = currentTree.getSha();
				}
				log.info(MessageFormat.format("Merging with tree {0}", baseTree));
			}
			
			return service.createTree(repository, entries, baseTree);
		} catch (IOException e) {
			throw new GitHubException("Error creating tree: " + e.getMessage(), e);
		}
	}

	private Reference getReference(DataService service, RepositoryId repository) throws GitHubException {
		Reference ref = null;
		try {
			ref = service.getReference(repository, branch);
		} catch (RequestException e) {
			if (404 != e.getStatus()){			
				throw new GitHubException("Error getting reference: " + e.getMessage(), e);
			}
		} catch (IOException e) {
			throw new GitHubException("Error getting reference: " + e.getMessage(), e);
		}

		if (ref != null && !TYPE_COMMIT.equals(ref.getObject().getType())){
			throw new GitHubException(MessageFormat.format("Existing ref {0} points to a {1} ({2}) instead of a commmit",
					ref.getRef(), ref.getObject().getType(), ref.getObject().getSha()));
		}
		return ref;
	}

	private TreeEntry createEntry(String prefix, String path, DataService service, RepositoryId repository, File outputDirectory) throws GitHubException {
		TreeEntry entry = new TreeEntry();
		entry.setPath(prefix + path);
		entry.setType(TYPE_BLOB);
		entry.setMode(MODE_BLOB);
		if(!dryRun){
			entry.setSha(createBlob(service, repository, outputDirectory, path));
			log.info("" + path + " -> " + entry.getSha());
		}
		return entry;
	}

	/**
	 * @param destinationDirectory
	 * @return
	 */
	private String getPrefix(String destinationDirectory) {
		String prefix = destinationDirectory;
		//String prefix = site.getRoot();
		if (prefix == null){
			prefix = "";
		}
		if("./".equals(prefix)){
			prefix = "";
		}
		if (prefix.length() > 0 && !prefix.endsWith("/")){
			prefix += "/";
		}
		return prefix;
	}

	private String createBlob(DataService service, RepositoryId repository, File outputDirectory, String path) throws GitHubException {
		try {
			Blob blob = new Blob().setEncoding(ENCODING_BASE64);
			if(NO_JEKYLL_FILE.equals(path)){
				blob.setContent("");
				//log.debug("Creating blob from " + NO_JEKYLL_FILE);
			}else{
				File file = new File(outputDirectory, path);
				byte[] bytes = FileUtils.readFileToByteArray(file);
				String encoded = EncodingUtils.toBase64(bytes);
				blob.setContent(encoded);
				//log.debug("Creating blob from " +  file.getAbsolutePath());
			}
			if(log.isDebugEnabled()){
				log.debug("Creating blob from " +  path);
			}
			return service.createBlob(repository, blob);
		} catch (IOException e) {
			throw new GitHubException("Error creating blob from '" + path + "': " + e.getMessage(), e);
		}
	}

	private GitHubClient createClient(String host, String userName, String password, String oauth2Token) throws GitHubException {
		GitHubClient client;
		if (!StringUtils.isEmpty(host)) {
			if (log.isDebugEnabled()){
				log.debug("Using custom host: " + host);
			}
			client = createClient(host);
		} else{
			client = new GitHubClient();
		}
		
		if(!StringUtils.isEmpty(userName) && !StringUtils.isEmpty(password)){
			if (log.isDebugEnabled()){
				log.debug("Using basic authentication with username: " + userName);
			}
			client.setCredentials(userName, password);
			return client;
		}else if(!StringUtils.isEmpty(oauth2Token)){
			if (log.isDebugEnabled()){
				log.debug("Using OAuth2 access token authentication");
			}
			client.setOAuth2Token(oauth2Token);
			return client;
		}else if(StringUtils.isEmpty(userName) && !StringUtils.isEmpty(password)){
			if (log.isDebugEnabled()){
				log.debug("Using OAuth2 access token authentication");
			}
			client.setOAuth2Token(password);
			return client;
		}else if(!StringUtils.isEmpty(userName) && System.console() != null){
			Console console = System.console();
			while(StringUtils.isEmpty(password)){
				password = new String(console.readPassword("Input the password for '" + userName + "': "));
			}
			client.setCredentials(userName, password);
			return client;
		}

		throw new GitHubException("No authentication credentials configured");
	}

	/**
	 * Create client
	 * 

* Subclasses can override to do any custom client configuration * * @param hostname * @return non-null client * @throws MojoExecutionException */ private GitHubClient createClient(String hostname) throws GitHubException { if (!hostname.contains("://")) return new GitHubClient(hostname); try { URL hostUrl = new URL(hostname); return new GitHubClient(hostUrl.getHost(), hostUrl.getPort(), hostUrl.getProtocol()); } catch (MalformedURLException e) { throw new GitHubException("Could not parse host URL " + hostname, e); } } /** * Get repository and throw a {@link MojoExecutionException} on failures * * @param project * @param owner * @param name * @return non-null repository id * @throws MojoExecutionException */ private RepositoryId getRepository(final String owner, final String name) throws GitHubException { RepositoryId repository = null; if(StringUtils.isNotBlank(name) && StringUtils.isNotBlank(owner)){ repository = RepositoryId.create(owner, name); }else{ throw new GitHubException("No GitHub repository (owner and name) configured"); } if (log.isDebugEnabled()){ log.debug(MessageFormat.format("Using GitHub repository {0}", repository.generateId())); } return repository; } private String[] getPaths(File outputDirectory){ // Find files to include String baseDir = outputDirectory.getAbsolutePath(); String[] includePaths = removeEmpties(includes); String[] excludePaths = removeEmpties(excludes); if (log.isDebugEnabled()){ log.debug(MessageFormat.format("Scanning {0} and including {1} and exluding {2}", baseDir, Arrays.toString(includePaths), Arrays.toString(excludePaths))); } String[] paths = getMatchingPaths(includePaths, excludePaths, baseDir); // Convert separator to forward slash '/' if ('\\' == File.separatorChar){ for (int i = 0; i < paths.length; i++){ paths[i] = paths[i].replace('\\', '/'); //FilenameUtils.separatorsToUnix(paths[i]); } } if (paths.length != 1){ log.info(MessageFormat.format("Creating {0} blobs", paths.length)); }else{ log.info("Creating 1 blob"); } if (log.isDebugEnabled()){ log.debug(MessageFormat.format("Scanned files to include: {0}", Arrays.toString(paths))); } return paths; } /** * Create an array with only the non-null and non-empty values * * @param values * @return non-null but possibly empty array of non-null/non-empty strings */ public static String[] removeEmpties(final String... values) { if (values == null || values.length == 0){ return new String[0]; } List validValues = new ArrayList(); for (String value : values){ if (value != null && value.length() > 0){ validValues.add(value); } } return validValues.toArray(new String[validValues.size()]); } /** * Get matching paths found in given base directory * * @param includes * @param excludes * @param baseDir * @return non-null but possibly empty array of string paths relative to the * base directory */ public static String[] getMatchingPaths(final String[] includes, final String[] excludes, final String baseDir) { DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir(baseDir); if (includes != null && includes.length > 0){ scanner.setIncludes(includes); } if (excludes != null && excludes.length > 0){ scanner.setExcludes(excludes); } scanner.scan(); return scanner.getIncludedFiles(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy