Java tutorial
/* App.java: Main file for Git-VCR Copyright 2015 Bob W. Hogg. All Rights Reserved. This file is part of Git-VCR. Git-VCR is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Git-VCR is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Git-VCR. If not, see <http://www.gnu.org/licenses/>. */ package com.github.rwhogg.git_vcr; import com.github.rwhogg.git_vcr.review.*; import com.squareup.okhttp.*; import gnu.getopt.*; import java.awt.*; import java.io.*; import java.net.*; import java.nio.charset.*; import org.apache.commons.configuration.*; import org.eclipse.jgit.api.*; import org.eclipse.jgit.api.ResetCommand.*; import org.eclipse.jgit.api.errors.*; import org.eclipse.jgit.errors.*; import org.eclipse.jgit.lib.*; import org.eclipse.jgit.patch.*; import org.eclipse.jgit.storage.file.*; import org.eclipse.jgit.submodule.*; /** * App is the main class for Git-VCR */ public class App { /** * Usage displays a usage message and then terminates. */ private static void usage() { System.out.printf("Usage: git vcr [-h | -v] <patch file> [branch]%n"); System.out.printf( "Perform an automatic code review on the given patch file, optionally starting from the given branch.%n"); System.out.println(); System.out.printf("Options:%n"); System.out.printf(" -h\tShow this help message%n"); System.out.printf(" -v\tDisplay version and license information%n"); System.out.println(); System.out.printf("Exit Status:%n"); System.out.printf(" 0 if ok%n"); System.out.printf(" 1 if there was a problem%n"); System.out.println(); System.out.printf("Report bugs to: https://github.com/rwhogg/git-vcr/issues%n"); System.out.printf("Git-VCR Home Page: <https://github.com/rwhogg/git-vcr>%n"); System.exit(1); } /** * version displays version information and then terminates. */ private static void version() { System.out.printf("%s %s%n", Constants.PROGRAM_NAME, Constants.VERSION); System.out.printf("Copyright 2015 - 2016, Bob W. Hogg. All Rights Reserved.%n"); System.out.printf( "%s is free software; see the source for copying conditions. There is NO%nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.%n", Constants.PROGRAM_NAME); System.exit(1); } /** * parseCommandLine returns the options associated with the given arguments * @param args Command-line arguments */ private static Options parseCommandLine(String[] args) { if (args.length == 0) { usage(); } Getopt getopt = new Getopt("git-vcr", args, "hv"); int c; while ((c = getopt.getopt()) != -1) { switch (c) { case 'h': usage(); break; case 'v': version(); break; case '?': System.exit(1); default: System.err.println("Error: getopt failed with status " + c); } } // get the patch int nonOptionIndex = getopt.getOptind(); if (nonOptionIndex == args.length) { usage(); } Options options = new Options(); String patchString = args[nonOptionIndex]; try { options.guessPatchUrl(patchString); } catch (MalformedURLException e) { Util.error("given URL " + patchString + " is incorrectly formatted!"); } catch (FileNotFoundException e) { Util.error("given file " + patchString + " could not be read!"); } catch (IOException e) { Util.error("could not determine MIME type of patch file " + patchString + "!"); } // get the branch if (nonOptionIndex < args.length - 1) { options.setBranch(args[nonOptionIndex + 1]); } return options; } /** * main is the entry point for Git-VCR * @param args Command-line arguments */ public static void main(String[] args) { Options options = parseCommandLine(args); HierarchicalINIConfiguration configuration = null; try { configuration = getConfiguration(); } catch (ConfigurationException e) { Util.error("could not parse configuration file!"); } // verify we are in a git folder and then construct the repo final File currentFolder = new File("."); FileRepositoryBuilder builder = new FileRepositoryBuilder(); Repository localRepo = null; try { localRepo = builder.findGitDir().build(); } catch (IOException e) { Util.error("not in a Git folder!"); } // deal with submodules assert localRepo != null; if (localRepo.isBare()) { FileRepositoryBuilder parentBuilder = new FileRepositoryBuilder(); Repository parentRepo; try { parentRepo = parentBuilder.setGitDir(new File("..")).findGitDir().build(); localRepo = SubmoduleWalk.getSubmoduleRepository(parentRepo, currentFolder.getName()); } catch (IOException e) { Util.error("could not find parent of submodule!"); } } // if we need to retrieve the patch file, get it now URL patchUrl = options.getPatchUrl(); String patchPath = patchUrl.getFile(); File patchFile = null; HttpUrl httpUrl = HttpUrl.get(patchUrl); if (httpUrl != null) { try { patchFile = com.twitter.common.io.FileUtils.SYSTEM_TMP.createFile(".diff"); Request request = new Request.Builder().url(httpUrl).build(); OkHttpClient client = new OkHttpClient(); Call call = client.newCall(request); Response response = call.execute(); ResponseBody body = response.body(); if (!response.isSuccessful()) { Util.error("could not retrieve diff file from URL " + patchUrl); } String content = body.string(); org.apache.commons.io.FileUtils.write(patchFile, content, (Charset) null); } catch (IOException ie) { Util.error("could not retrieve diff file from URL " + patchUrl); } } else { patchFile = new File(patchPath); } // find the patch //noinspection ConstantConditions if (!patchFile.canRead()) { Util.error("patch file " + patchFile.getAbsolutePath() + " is not readable!"); } final Git git = new Git(localRepo); // handle the branch String branchName = options.getBranchName(); String theOldCommit = null; try { theOldCommit = localRepo.getBranch(); } catch (IOException e2) { Util.error("could not get reference to current branch!"); } final String oldCommit = theOldCommit; // needed to reference from shutdown hook if (branchName != null) { // switch to the branch try { git.checkout().setName(branchName).call(); } catch (RefAlreadyExistsException e) { // FIXME Auto-generated catch block e.printStackTrace(); } catch (RefNotFoundException e) { Util.error("the branch " + branchName + " was not found!"); } catch (InvalidRefNameException e) { Util.error("the branch name " + branchName + " is invalid!"); } catch (org.eclipse.jgit.api.errors.CheckoutConflictException e) { Util.error("there was a checkout conflict!"); } catch (GitAPIException e) { Util.error("there was an unspecified Git API failure!"); } } // ensure there are no changes before we apply the patch try { if (!git.status().call().isClean()) { Util.error("cannot run git-vcr while there are uncommitted changes!"); } } catch (NoWorkTreeException e1) { // won't happen assert false; } catch (GitAPIException e1) { Util.error("call to git status failed!"); } // list all the files changed String patchName = patchFile.getName(); Patch patch = new Patch(); try { patch.parse(new FileInputStream(patchFile)); } catch (FileNotFoundException e) { assert false; } catch (IOException e) { Util.error("could not parse the patch file!"); } ReviewResults oldResults = new ReviewResults(patchName, patch, configuration, false); try { oldResults.review(); } catch (InstantiationException e1) { Util.error("could not instantiate a review tool class!"); } catch (IllegalAccessException e1) { Util.error("illegal access to a class"); } catch (ClassNotFoundException e1) { Util.error("could not find a review tool class"); } catch (ReviewFailedException e1) { e1.printStackTrace(); Util.error("Review failed!"); } // we're about to change the repo, so register a shutdown hook to clean it up Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { cleanupGit(git, oldCommit); } }); // apply the patch try { git.apply().setPatch(new FileInputStream(patchFile)).call(); } catch (PatchFormatException e) { Util.error("patch file " + patchFile.getAbsolutePath() + " is malformatted!"); } catch (PatchApplyException e) { Util.error("patch file " + patchFile.getAbsolutePath() + " did not apply correctly!"); } catch (FileNotFoundException e) { assert false; } catch (GitAPIException e) { Util.error(e.getLocalizedMessage()); } ReviewResults newResults = new ReviewResults(patchName, patch, configuration, true); try { newResults.review(); } catch (InstantiationException e1) { Util.error("could not instantiate a review tool class!"); } catch (IllegalAccessException e1) { Util.error("illegal access to a class"); } catch (ClassNotFoundException e1) { Util.error("could not find a review tool class"); } catch (ReviewFailedException e1) { e1.printStackTrace(); Util.error("Review failed!"); } // generate and show the report VelocityReport report = new VelocityReport(patch, oldResults, newResults); File reportFile = null; try { reportFile = com.twitter.common.io.FileUtils.SYSTEM_TMP.createFile(".html"); org.apache.commons.io.FileUtils.write(reportFile, report.toString(), (String) null); } catch (IOException e) { Util.error("could not generate the results page!"); } try { assert reportFile != null; Desktop.getDesktop().open(reportFile); } catch (IOException e) { Util.error("could not open the results page!"); } } /** * Clean up the Git repo * @param git Git to use * @param oldCommit Hash of the commit to return to */ private static void cleanupGit(Git git, String oldCommit) { try { git.reset().setMode(ResetType.HARD).call(); git.checkout().setName(oldCommit).call(); } catch (org.eclipse.jgit.api.errors.CheckoutConflictException e) { Util.error("there was an checkout conflict!"); } catch (GitAPIException e) { Util.error("there was an unspecified Git API error!"); } git.close(); } /** * Returns configuration details from the default configuration file * @return the configuration details * @throws ConfigurationException if the configuration file is malformed or inadequate */ public static HierarchicalINIConfiguration getConfiguration() throws ConfigurationException { return new HierarchicalINIConfiguration(Constants.CONFIG_FILENAME); } }