Last active
August 29, 2015 14:26
-
-
Save bmvakili/1d8298387e12a2354a7e to your computer and use it in GitHub Desktop.
A utility for some code reviews
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.BufferedReader; | |
import java.io.File; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.InputStreamReader; | |
import java.security.AllPermission; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Set; | |
/** | |
* Utility to help some code reviews | |
* What? | |
* Utility to help some code reviews; specifically, to review code in a | |
* topic branch that may have been merged with other branches; though | |
* needs a code review in it's entirety. | |
* | |
* Why? | |
* There is no straightforward command for code reviewing work in a | |
* topic branch that may have been merged with other branches | |
* several times and try to only review the code that was committed | |
* as part of that branch. | |
* A simple {@code git diff} would also include changes merged in | |
* from other branches whereas this utility allows code reviewer | |
* to ignore all of those files and only focus on files that | |
* the committer has touched as part of the commits in the | |
* topic branch. | |
* | |
* How? | |
* The method that summarizes the entire logic is as follows | |
* {@link #createNewReviewBranchBasedOnBranchAndCommitAndFiles(String, String, String, String)} | |
* This utility uses the local git repository's references log to | |
* locate local commits to a topic branch; then it creates a new | |
* branch and checks out only files changed by the user to the | |
* new branch for review. | |
* | |
* Note others cannot run this utility for the developer; because | |
* the reflogs for that branch will only exists on the developer's | |
* local repository. As such this tool needs to be run by local | |
* developer to aid in the code review process. | |
* | |
* Note this utility depends on the configuration property {@code gc.reflogExpire} | |
* By default it is set to 90 days. | |
* http://git-scm.com/docs/git-gc | |
* | |
* Note this utility has been tested on projects using {@code git merge} | |
* to merge; and has not been tested on projects using {@code git rebase} | |
* The effect of the latter is that the utility will ignore commits brought | |
* in to the topic branch using git rebase since the reflog information | |
* is not preserved during rebase operation; instead shows as {@code %H@HEAD..} | |
* | |
* TODO: create script to fetch this code using bat and sh files and | |
* run it with one simple command assuming javac is on classpath. | |
* @author bmvakili | |
* | |
*/ | |
public class GitCodeReviewUtility { | |
private static String gitPathValue = "/opt/git/incomplete/newmerge"; | |
/** | |
* | |
* @param args this is not used though note the following variables | |
* startCommit - how far back do you want to go back for the | |
* code review? Put the value of the commit here. | |
* branchName - which topic branch do you want to use for the | |
* code review? Put name of the branch here. | |
* pathToGitWorkingDirectory - where is your git directory? Put | |
* the value in the gitPathValue | |
* attribute. | |
*/ | |
public static void main(String[] args) { | |
String startCommit = "91a1e01"; | |
String branchName = "SIH_1"; | |
String pathToGitWorkingDirectory = getGitPathValue(); | |
String diffFiles = getStringOfFilesModifiedInBranchSinceCommit( | |
startCommit, branchName); | |
createNewReviewBranchBasedOnBranchAndCommitAndFiles(startCommit, branchName, diffFiles, pathToGitWorkingDirectory); | |
} | |
private static void createNewReviewBranchBasedOnBranchAndCommitAndFiles( | |
String startCommit, String branchName, String diffFiles, String path) { | |
String reviewBranchName = branchName + REVIEW_BRANCH_SUFFIX; | |
checkoutBranchAndResetHard(startCommit, reviewBranchName); | |
checkoutFilesFromBranch(branchName, diffFiles); | |
commitFilesToCurrentBranchForReview(startCommit, branchName, diffFiles); | |
resetHard(); | |
} | |
private static void resetHard() { | |
String gitResetHardCommand = getGitResetHardCommand(); | |
runCommand(gitResetHardCommand); | |
} | |
private static String getGitResetHardCommand() { | |
String gitResetHardCommand = getBaseGitCommand(); | |
gitResetHardCommand += addCommandArgumentAndSpace(RESET); | |
gitResetHardCommand += addCommandArgumentAndSpace(GIT_HARD); | |
return gitResetHardCommand; | |
} | |
private static void commitFilesToCurrentBranchForReview(String commit, String branch, String diffFiles) { | |
String gitAddAllCurrentFilesCommit = getAddAllCurrentFilesCommandForPath(diffFiles); | |
runCommand(gitAddAllCurrentFilesCommit); | |
String commitMessage = getCodeReviewCommitMessage(commit, branch); | |
String gitCommitCommand = getCommitCommand(commitMessage); | |
runCommand(gitCommitCommand); | |
} | |
private static String getCodeReviewCommitMessage(String commit, | |
String branch) { | |
String commitMessage = REVIEW_COMMIT_MESSAGE + SPACE + commit + GIT_DOUBLEDOT + branch; | |
return commitMessage; | |
} | |
private static String getCommitCommand(String commitMessage) { | |
String gitCommitCommand = getBaseGitCommand(); | |
gitCommitCommand += addCommandArgumentAndSpace(COMMIT); | |
gitCommitCommand += addCommandArgumentAndSpace(GIT_MESSAGE_FLAG); | |
gitCommitCommand += QUOTE; | |
gitCommitCommand += commitMessage; | |
gitCommitCommand += QUOTE; | |
return gitCommitCommand; | |
} | |
private static String getAddAllCurrentFilesCommandForPath(String diffFiles) { | |
String gitAddAllCurrentFiles = getBaseGitCommand(); | |
gitAddAllCurrentFiles += addCommandArgumentAndSpace(ADD); | |
gitAddAllCurrentFiles += addCommandArgumentAndSpace(GIT_ALL_FLAG); | |
gitAddAllCurrentFiles += addCommandArgumentAndSpace(DASHDASH); | |
gitAddAllCurrentFiles += addCommandArgumentAndSpace(diffFiles); | |
return gitAddAllCurrentFiles; | |
} | |
private static void checkoutFilesFromBranch(String branchName, | |
String diffFiles) { | |
String gitCheckoutFilesCommand = getCheckoutFilesCommand(branchName, diffFiles); | |
runCommand(gitCheckoutFilesCommand); | |
} | |
private static String getCheckoutFilesCommand(String branchName, | |
String diffFiles) { | |
String gitCheckoutCommand = getCheckoutCommand(branchName); | |
gitCheckoutCommand += addCommandArgumentAndSpace(DASHDASH); | |
gitCheckoutCommand += addCommandArgumentAndSpace(diffFiles); | |
return gitCheckoutCommand; | |
} | |
private static void checkoutBranchAndResetHard(String startCommit, | |
String branchName) { | |
String gitCheckoutCommand = getCheckoutCommand(branchName); | |
runCommand(gitCheckoutCommand); | |
String gitResetHardCommand = getResetHardCommand(startCommit); | |
runCommand(gitResetHardCommand); | |
} | |
private static String getResetHardCommand(String startCommit) { | |
String gitCommand = getBaseGitCommand(); | |
gitCommand += addCommandArgumentAndSpace(RESET); | |
gitCommand += addCommandArgumentAndSpace(startCommit); | |
return gitCommand; | |
} | |
private static String getCheckoutCommand(String branchName) { | |
String gitCommand = getBaseGitCommand(); | |
gitCommand += addCommandArgumentAndSpace(CHECKOUT); | |
gitCommand += addCommandArgumentAndSpace(GIT_NEW_BRANCH_FLAG); | |
gitCommand += addCommandArgumentAndSpace(branchName); | |
return gitCommand; | |
} | |
private static String getStringOfFilesModifiedInBranchSinceCommit( | |
String startCommit, String branchName) { | |
String[] files = getFilesModifiedInBranchSinceCommit(startCommit, branchName); | |
String diffFiles = String.join(SPACE, files); | |
return diffFiles; | |
} | |
private static String getDiffCommandForFiles(String startCommit, | |
String branchName, String diffFiles) { | |
String gitCommand = getDiffCommand(startCommit, branchName); | |
gitCommand += addCommandArgumentAndSpace(DASHDASH); | |
gitCommand += addCommandArgumentAndSpace(diffFiles); | |
return gitCommand; | |
} | |
private static String getDiffCommand(String startCommit, String branchName) { | |
String gitCommand = getBaseGitCommand(); | |
gitCommand += addCommandArgumentAndSpace(DIFF); | |
gitCommand += addCommandArgumentAndSpace(startCommit); | |
gitCommand += addCommandArgumentAndSpace(branchName); | |
return gitCommand; | |
} | |
private static String[] getFilesModifiedInBranchSinceCommit( | |
String startCommit, String branchName) { | |
String[] revs = getRevList(startCommit, branchName); | |
String[] branchCommits = getCommitsForBranchFromReflog(revs, branchName); | |
return getFilesForCommits(branchCommits); | |
} | |
private static String[] getFilesForCommits(String[] branchCommits) { | |
Set<String> setOfFiles = getSetOfFilesForCommits(branchCommits); | |
String[] filesForCommits = new String[setOfFiles.size()]; | |
setOfFiles.toArray(filesForCommits); | |
return filesForCommits; | |
} | |
private static Set<String> getSetOfFilesForCommits(String[] branchCommits) { | |
String gitDiffTreeBaseCommand = getDiffTreeCommand(); | |
Set<String> setOfFiles = new HashSet<String>(); | |
setOfFiles = getAllFilesForCommitsAndBranch(branchCommits, gitDiffTreeBaseCommand); | |
return setOfFiles; | |
} | |
private static Set<String> getAllFilesForCommitsAndBranch(String[] branchCommits, | |
String gitDiffTreeBaseCommand) { | |
Set<String> setOfFiles = new HashSet<String>(); | |
for (String branchCommit : branchCommits) { | |
String[] arrayOfFiles = getFilesForCommitsAndBranch( | |
gitDiffTreeBaseCommand, branchCommit); | |
setOfFiles.addAll(Arrays.asList(arrayOfFiles)); | |
} | |
return setOfFiles; | |
} | |
private static String[] getFilesForCommitsAndBranch( | |
String gitDiffTreeBaseCommand, String branchCommit) { | |
String gitDiffTreeCommand = gitDiffTreeBaseCommand + branchCommit; | |
String[] commandOutputs = runCommand(gitDiffTreeCommand); | |
String files = commandOutputs[OUTPUT_INDEX]; | |
String[] arrayOfFiles = splitStringByLines(files); | |
arrayOfFiles = wrapWithQuotes(arrayOfFiles); | |
return arrayOfFiles; | |
} | |
private static String[] wrapWithQuotes(String[] arrayOfFiles) { | |
for (int index = 0; index < arrayOfFiles.length; index++) { | |
arrayOfFiles[index] = QUOTE + arrayOfFiles[index] + QUOTE; | |
} | |
return arrayOfFiles; | |
} | |
private static String getDiffTreeCommand() { | |
String gitCommand = getBaseDiffTreeCommand(); | |
gitCommand += addCommandArgumentAndSpace(GIT_NO_COMMIT_ID); | |
gitCommand += addCommandArgumentAndSpace(GIT_NAME_ONLY); | |
gitCommand += addCommandArgumentAndSpace(GIT_RECURSE_INTO_SUBTREES); | |
return gitCommand; | |
} | |
private static String addCommandArgumentAndSpace(String gitCommandArgument) { | |
return gitCommandArgument + SPACE; | |
} | |
private static String getBaseDiffTreeCommand() { | |
String gitCommand = getBaseGitCommand(); | |
gitCommand += addCommandArgumentAndSpace(DIFF_TREE); | |
return gitCommand; | |
} | |
private static String[] getCommitsForBranchFromReflog(String[] revs, | |
String branchName) { | |
String gitCommand = getRefLogCommandForAllRefs(); | |
String[] commandOutput = runCommand(gitCommand); | |
String[] referenceLogs = splitStringByLines(commandOutput[OUTPUT_INDEX]); | |
String[] branchSpecificCommits = getReferencesSpecificToBranch( | |
branchName, referenceLogs); | |
return branchSpecificCommits; | |
} | |
private static String[] splitStringByLines(String string) { | |
return string.split(NEWLINE_REGULAR_EXPRESSION); | |
} | |
private static String[] getReferencesSpecificToBranch(String branchName, | |
String[] referenceLogs) { | |
List<String> commits = addBranchSpecificCommits(branchName, | |
referenceLogs); | |
String[] branchSpecificCommits = new String[commits.size()]; | |
branchSpecificCommits = commits.toArray(branchSpecificCommits); | |
return branchSpecificCommits; | |
} | |
private static List<String> addBranchSpecificCommits(String branchName, | |
String[] referenceLogs) { | |
List<String> commits = new ArrayList<String>(); | |
for (String referenceLog : referenceLogs) { | |
String[] referenceLogLineArray = referenceLog.split(SPACE); | |
String reference = referenceLogLineArray[REFLINEARRAY_REF_INDEX]; | |
if (reference.startsWith(branchName + AT_SYMBOL)) { | |
String commit = referenceLogLineArray[REFLINEARRAY_HASH_INDEX]; | |
if(isNotMergeCommit(commit)) { | |
commits.add(commit); | |
} | |
} | |
} | |
return commits; | |
} | |
private static boolean isNotMergeCommit(String commit) { | |
String gitCommand = getBaseGitCommand(); | |
gitCommand += addCommandArgumentAndSpace(SHOW); | |
gitCommand += addCommandArgumentAndSpace(GIT_SUMMARY); | |
gitCommand += GIT_FORMAT; | |
gitCommand += EQUALS; | |
gitCommand += addCommandArgumentAndSpace(GIT_FORMAT_PARENT_HASHES); | |
gitCommand += commit; | |
String[] commandOutputs = runCommand(gitCommand); | |
String parentsLine = commandOutputs[OUTPUT_INDEX]; | |
boolean hasMultipleParents = false; | |
if (parentsLine.split(SPACE).length > 1) { | |
hasMultipleParents = true; | |
} | |
boolean isNotMerge = !hasMultipleParents; | |
return isNotMerge; | |
} | |
private static String getRefLogCommandForAllRefs() { | |
String gitCommand = getRefLogCommandShowAll(); | |
gitCommand += getReflogFormatOptions(); | |
return gitCommand; | |
} | |
private static String getReflogFormatOptions() { | |
String gitCommand = GIT_FORMAT; | |
gitCommand += EQUALS; | |
gitCommand += QUOTE; | |
gitCommand += GIT_REFLOG_FORMAT; | |
gitCommand += QUOTE; | |
return gitCommand; | |
} | |
private static String getRefLogCommandShowAll() { | |
String gitCommand = getRefLogCommandShow(); | |
gitCommand += addCommandArgumentAndSpace(GIT_ALL); | |
return gitCommand; | |
} | |
private static String getRefLogCommandShow() { | |
String gitCommand = getRefLogCommand(); | |
gitCommand += addCommandArgumentAndSpace(SHOW); | |
return gitCommand; | |
} | |
private static String getRefLogCommand() { | |
String gitCommand = getBaseGitCommand(); | |
gitCommand += addCommandArgumentAndSpace(REFLOG); | |
return gitCommand; | |
} | |
private static String[] getRevList(String commitFrom, String commitTo) { | |
String gitCommand = getRevListCommand(commitFrom, commitTo); | |
String[] revsArray = runCommand(gitCommand); | |
return revsArray; | |
} | |
private static String getRevListCommand(String commitFrom, String commitTo) { | |
String gitCommand = getRevListCommand(); | |
gitCommand += addCommandArgumentAndSpace(commitFrom); | |
gitCommand += commitTo; | |
return gitCommand; | |
} | |
private static String getRevListCommand() { | |
String gitCommand = getBaseGitCommand(); | |
gitCommand += addCommandArgumentAndSpace(REV_LIST); | |
return gitCommand; | |
} | |
public static String getMergedCommitHashes() { | |
String gitCommand = getBranchMergesCommand(); | |
String[] mergeCommits = runCommand(gitCommand); | |
return mergeCommits[OUTPUT_INDEX]; | |
} | |
public static String getLogCommand() { | |
String gitCommand = getBaseGitCommand(); | |
gitCommand += addCommandArgumentAndSpace(LOG); | |
return gitCommand; | |
} | |
public static String getBranchMergesCommand() { | |
String gitCommand = getLogCommand(); | |
gitCommand += addCommandArgumentAndSpace(GIT_SHOW_ONLY_MERGES); | |
gitCommand += GIT_FORMAT; | |
gitCommand += EQUALS; | |
gitCommand += GIT_FORMAT_PARENT_HASHES; | |
return gitCommand; | |
} | |
public static String getBaseGitCommand() { | |
String gitCommand = addCommandArgumentAndSpace(GIT_COMMAND); | |
gitCommand += addCommandArgumentAndSpace(GIT_PATH); | |
gitCommand += addCommandArgumentAndSpace(GIT_PATH_VALUE); | |
return gitCommand; | |
} | |
public static String getShowBranchesCommand() { | |
String gitCommand = getBaseGitCommand(); | |
gitCommand += BRANCH; | |
return gitCommand; | |
} | |
public static void printCommandResults(String gitCommand) { | |
String[] output = runCommand(gitCommand); | |
System.out.println(output[OUTPUT_INDEX]); | |
System.out.println(output[ERROR_INDEX]); | |
} | |
private static String[] runCommand(String gitCommand) { | |
String shellCommand = getShellCommand(); | |
String shellCommandFlagCommand = getShellCommandFlagCommand(); | |
String[] commands = { | |
shellCommand, shellCommandFlagCommand, gitCommand | |
}; | |
return runCommands(commands); | |
} | |
private static String getShellCommandFlagCommand() { | |
if (File.separatorChar == UNIX_SEPARATOR_CHARACTER) { | |
return UNIX_COMMAND_FLAG; | |
} | |
return WINDOWS_COMMAND_FLAG; | |
} | |
private static String getShellCommand() { | |
if (File.separatorChar == '/') { | |
return UNIX_COMMAND; | |
} | |
return WINDOWS_COMMAND; | |
} | |
private static String[] runCommands(String[] commands) { | |
Runtime rt = Runtime.getRuntime(); | |
Process proc = runCommand(commands, rt); | |
String[] retVal = readOutputErrorStreams(proc); | |
return retVal; | |
} | |
private static String[] readOutputErrorStreams(Process proc) { | |
String[] retVal = new String[2]; | |
retVal[OUTPUT_INDEX] = readInputStream(proc.getInputStream()); | |
retVal[ERROR_INDEX] = readInputStream(proc.getErrorStream()); | |
return retVal; | |
} | |
private static String readInputStream(InputStream input) { | |
BufferedReader stdInput = new BufferedReader(new | |
InputStreamReader(input)); | |
String content = readLines(stdInput); | |
return content; | |
} | |
private static String readLines(BufferedReader stdInput) { | |
String content = BLANK; | |
String s = null; | |
try { | |
while ((s = stdInput.readLine()) != null) { | |
content += s + UNIX_NEWLINE; | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
return content; | |
} | |
private static String getGitPathValue() { | |
if (gitPathValue == null || gitPathValue.trim().isEmpty()) { | |
return GIT_PATH_VALUE; | |
} else { | |
return gitPathValue; | |
} | |
} | |
private static Process runCommand(String[] commands, Runtime rt) { | |
Process proc = null; | |
try { | |
proc = rt.exec(commands); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
return proc; | |
} | |
private static final String GIT_COMMAND = "git"; | |
private static final String DASHDASH = "--"; | |
private static final int OUTPUT_INDEX = 0; | |
private static final int ERROR_INDEX = 1; | |
private static final int REFLINEARRAY_HASH_INDEX = 0; | |
private static final int REFLINEARRAY_REF_INDEX = 1; | |
private static final String GIT_PATH = "-C"; | |
private static final String GIT_PATH_VALUE = "/opt/git/incomplete/newmerge"; | |
private static final String EQUALS = "="; | |
private static final String SPACE = " "; | |
private static final String BRANCH = "branch"; | |
private static final String GIT_SHOW_ONLY_MERGES = "--merges"; | |
private static final String SHOW = "show"; | |
private static final String LOG = "log"; | |
private static final String GIT_FORMAT = "--format"; | |
private static final String GIT_FORMAT_PARENT_HASHES = "%P"; | |
private static final String NEWLINE_REGULAR_EXPRESSION = "\\r?\\n"; | |
private static final String REV_LIST = "rev-list"; | |
private static final String REFLOG = "reflog"; | |
private static final String GIT_ALL = "--all"; | |
private static final String GIT_REFLOG_FORMAT = "%H %gd"; | |
private static final String QUOTE = "\""; | |
private static final String AT_SYMBOL = "@"; | |
private static final String DIFF_TREE = "diff-tree"; | |
private static final String GIT_NO_COMMIT_ID = "--no-commit-id"; | |
private static final String GIT_NAME_ONLY = "--name-only"; | |
private static final String GIT_RECURSE_INTO_SUBTREES = "-r"; | |
private static final String GIT_DOUBLEDOT = ".."; | |
private static final String GIT_SUMMARY = "--summary"; | |
private static final String GIT_HARD = "--hard"; | |
private static final String DIFF = "diff"; | |
private static final String CHECKOUT = "checkout"; | |
private static final String GIT_NEW_BRANCH_FLAG = "-b"; | |
private static final String RESET = "reset"; | |
private static final String ADD = "add"; | |
private static final String COMMIT = "commit"; | |
private static final String REVIEW_BRANCH_SUFFIX = "_REVIEW_TMP"; | |
private static final String REVIEW_COMMIT_MESSAGE = "Temporary commit for code review"; | |
private static final String GIT_MESSAGE_FLAG = "-m"; | |
private static final String GIT_ALL_FLAG = "-A"; | |
private static final String UNIX_NEWLINE = "\n"; | |
private static final String BLANK = ""; | |
private static final String UNIX_COMMAND = "/bin/bash"; | |
private static final String UNIX_COMMAND_FLAG = "-c"; | |
private static final String WINDOWS_COMMAND = "bash.exe"; | |
private static final String WINDOWS_COMMAND_FLAG = "-c"; | |
private static final char UNIX_SEPARATOR_CHARACTER = '/'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment