| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| import java.io.*; |
| import java.util.*; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| |
| /* dkulp - Stupid little program I use to help merge changes from |
| trunk to the fixes branches. It requires the command line version of |
| svn to be available on the path. If using a git checkout, it also requires |
| the command line version of git on the path. |
| |
| Basically, git does all the work, but this little wrapper |
| thing will display the commit logs, prompt if you want to merge/block/ignore |
| each commit, prompt for commit (so you can resolve any conflicts first), |
| etc.... |
| |
| Yes - doing this in python itself (or perl or even bash itself or ruby or ...) |
| would probably be better. However, I'd then need to spend time |
| learning python/ruby/etc... that I just don't have time to do right now. |
| What is more productive: Taking 30 minutes to bang this out in Java or |
| spending a couple days learning another language that would allow me to |
| bang it out in 15 minutes? |
| |
| Explanation of commands: |
| |
| [B]lock will permanently block the particular commit from being merged. |
| It won't ask again on subsequent runs of DoMerge. |
| |
| [I]gnore ignores the commit for the current DoMerges run, but will ask |
| again the next time you DoMerges. If you're not certain for a particular |
| commit use this option for someone else to determine on a later run. |
| |
| [R]ecord formally records that a merge occurred, but it does *not* |
| actually merge the commit. This is useful if you another tool to do |
| the merging but still wish to record a merge did occur. |
| |
| [F]lush will permanently save all the [B]'s and [R]'s you've earlier made, |
| useful when you need to stop DoMerges (due to a missed commit or other |
| problem) before it's complete. That way subsequent runs of DoMerges |
| won't go through the blocked/recorded items again. (Flushes occur |
| automatically when DoMerges is finished running.) |
| |
| [C]hanges will display the changes in the commit to help you decide the |
| appropriate action to take. |
| |
| */ |
| |
| public class DoMerges { |
| public static final String MERGEINFOFILE = ".gitmergeinfo"; |
| |
| public static boolean auto = false; |
| public static Pattern jiraPattern = Pattern.compile("([A-Z]{2,10}+-\\d+)"); |
| public static String username; |
| public static String fromBranch; |
| |
| public static Set<String> records = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); |
| public static Set<String> patchIds = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); |
| |
| static class ToFrom { |
| final String from; |
| final String to; |
| |
| public ToFrom(String t, String f) { |
| to = t; |
| from = f; |
| } |
| } |
| public static List<ToFrom> pathMaps = new LinkedList<ToFrom>(); |
| |
| static int waitFor(Process p) throws Exception { |
| return waitFor(p, true); |
| } |
| static int waitFor(Process p, boolean exit) throws Exception { |
| int i = p.waitFor(); |
| if (i != 0) { |
| System.out.println("ERROR!"); |
| BufferedReader reader = new BufferedReader(new InputStreamReader(p.getErrorStream())); |
| String line = reader.readLine(); |
| while (line != null) { |
| System.out.println(line); |
| line = reader.readLine(); |
| } |
| if (exit) { |
| System.exit(1); |
| } |
| } |
| return i; |
| } |
| static int runProcess(Process p) throws Exception { |
| return runProcess(p, true); |
| } |
| static int runProcess(Process p, boolean exit) throws Exception { |
| BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); |
| String line = reader.readLine(); |
| while (line != null) { |
| System.out.println(line); |
| line = reader.readLine(); |
| } |
| return waitFor(p, exit); |
| } |
| |
| |
| static boolean doCommit() throws Exception { |
| while (System.in.available() > 0) { |
| System.in.read(); |
| } |
| char c = auto ? 'Y' : 0; |
| while (c != 'Y' |
| && c != 'N') { |
| System.out.print("Commit: [Y]es, or [N]o? "); |
| int i = System.in.read(); |
| c = Character.toUpperCase((char)i); |
| } |
| if (c == 'N') { |
| Process p = Runtime.getRuntime().exec(new String[] {"git", "reset", "--hard"}); |
| runProcess(p); |
| return false; |
| } |
| |
| Process p = Runtime.getRuntime().exec(new String[] {"git", "commit", "--no-edit", "-a"}); |
| runProcess(p); |
| return true; |
| } |
| |
| public static void changes(String ver) throws Exception { |
| Process p = Runtime.getRuntime().exec(getCommandLine(new String[] {"git", "show", ver})); |
| runProcess(p); |
| } |
| |
| public static void flush() throws Exception { |
| BufferedWriter writer = new BufferedWriter(new FileWriter(MERGEINFOFILE)); |
| writer.write(fromBranch); |
| writer.newLine(); |
| for (ToFrom ent : pathMaps) { |
| writer.write("A "); |
| writer.write(ent.from); |
| writer.write(" "); |
| writer.write(ent.to); |
| writer.newLine(); |
| } |
| for (String s : records) { |
| writer.write(s); |
| writer.newLine(); |
| } |
| writer.flush(); |
| writer.close(); |
| |
| Process p = Runtime.getRuntime().exec(getCommandLine(new String[] {"git", "commit", "-m", |
| "Recording .gitmergeinfo Changes", |
| MERGEINFOFILE})); |
| runProcess(p); |
| } |
| public static void doUpdate() throws Exception { |
| Process p = Runtime.getRuntime().exec(new String[] {"git", "pull", "--rebase"}); |
| runProcess(p); |
| |
| File file = new File(MERGEINFOFILE); |
| records.clear(); |
| if (file.exists()) { |
| BufferedReader reader = new BufferedReader(new FileReader(MERGEINFOFILE)); |
| String line = reader.readLine(); |
| fromBranch = line; |
| line = reader.readLine(); |
| while (line != null) { |
| if (line.startsWith("A ")) { |
| line = line.substring(2).trim(); |
| String from = line.substring(0, line.indexOf(' ')); |
| String to = line.substring(line.indexOf(' ') + 1); |
| pathMaps.add(new ToFrom(to, from)); |
| } else { |
| records.add(line.trim()); |
| } |
| line = reader.readLine(); |
| } |
| reader.close(); |
| } |
| file = new File("patch-info"); |
| if (file.exists()) { |
| BufferedReader reader = new BufferedReader(new FileReader("patch-info")); |
| String line = reader.readLine(); |
| while (line != null) { |
| patchIds.add(line.trim()); |
| line = reader.readLine(); |
| } |
| reader.close(); |
| } |
| } |
| |
| public static List<String> getAvailableUpdates() throws Exception { |
| List<String> verList = new LinkedList<String>(); |
| Process p; |
| BufferedReader reader; |
| String line; |
| |
| p = Runtime.getRuntime().exec(getCommandLine(new String[] {"git", "cherry", |
| "HEAD", fromBranch})); |
| |
| reader = new BufferedReader(new InputStreamReader(p.getInputStream())); |
| line = reader.readLine(); |
| while (line != null) { |
| if (line.charAt(0) == '+') { |
| String ver = line.substring(2).trim(); |
| if (!records.contains("B " + ver) && !records.contains("M " + ver)) { |
| verList.add(ver); |
| } |
| } |
| line = reader.readLine(); |
| } |
| p.waitFor(); |
| return verList; |
| } |
| |
| public static List<String[]> getGitLogs() throws Exception { |
| BufferedReader reader; |
| String line; |
| Process p = Runtime.getRuntime().exec(new String[] {"git", "log", fromBranch + "..HEAD"}); |
| reader = new BufferedReader(new InputStreamReader(p.getInputStream())); |
| line = reader.readLine(); |
| |
| List<String[]> map = new LinkedList<String[]>(); |
| List<String> list = new ArrayList<String>(10); |
| while (line != null) { |
| if (line.length() > 0 && line.startsWith("commit ")) { |
| if (!list.isEmpty()) { |
| map.add(list.toArray(new String[list.size()])); |
| list.clear(); |
| } |
| while (line != null && line.length() > 0 && line.charAt(0) != ' ') { |
| list.add(line); |
| line = reader.readLine(); |
| } |
| } |
| list.add(line); |
| line = reader.readLine(); |
| } |
| if (!list.isEmpty()) { |
| map.add(list.toArray(new String[list.size()])); |
| list.clear(); |
| } |
| return map; |
| } |
| |
| public static String[] getLog(String ver, Set<String> jiras) throws Exception { |
| Process p; |
| BufferedReader reader; |
| String line; |
| p = Runtime.getRuntime().exec(new String[] {"git", "log", "--pretty=medium", "-n", "1" , ver}); |
| |
| reader = new BufferedReader(new InputStreamReader(p.getInputStream())); |
| line = reader.readLine(); |
| List<String> lines = new ArrayList<String>(10); |
| while (line != null) { |
| if (!line.startsWith("commit ")) { |
| lines.add(line); |
| Matcher m = jiraPattern.matcher(line); |
| while (m.find()) { |
| jiras.add(m.group()); |
| } |
| } |
| line = reader.readLine(); |
| } |
| p.waitFor(); |
| return lines.toArray(new String[lines.size()]); |
| } |
| |
| private static void doMerge(String ver) throws Exception { |
| Process p = Runtime.getRuntime().exec(getCommandLine(new String[] {"git", "cherry-pick", ver})); |
| if (runProcess(p, false) != 0) { |
| p = Runtime.getRuntime().exec(getCommandLine(new String[] {"git", "status"})); |
| runProcess(p); |
| |
| if (doCommit()) { |
| records.add("M " + ver); |
| } |
| } else { |
| String oldPatchId = getPatchId(ver); |
| String newPatchId = getPatchId("HEAD"); |
| if (!oldPatchId.equals(newPatchId)) { |
| records.add("M " + ver); |
| } |
| } |
| } |
| private static void doMappedMerge(String ver) throws Exception { |
| Process p = Runtime.getRuntime().exec(getCommandLine(new String[] {"git", "format-patch", "--stdout", "-1", "-k", ver})); |
| BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); |
| |
| File outputFile = File.createTempFile("merge", ".patch"); |
| outputFile.deleteOnExit(); |
| BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile)); |
| |
| String line = reader.readLine(); |
| while (line != null) { |
| if ((line.startsWith("--- ") |
| || line.startsWith("+++ ")) && line.length() > 7) { |
| String file = line.substring(6).trim(); |
| for (ToFrom ent : pathMaps) { |
| if (file.contains(ent.from)) { |
| String newf = file.replace(ent.from, ent.to); |
| File fo = new File(newf); |
| if (fo.exists() && fo.isFile()) { |
| line = line.substring(0, 6) + newf; |
| break; |
| } |
| } |
| } |
| //System.out.println("newl: " + line); |
| } |
| writer.append(line); |
| writer.newLine(); |
| line = reader.readLine(); |
| } |
| waitFor(p, false); |
| writer.flush(); |
| writer.close(); |
| |
| p = Runtime.getRuntime().exec(getCommandLine(new String[] {"git", "am", "-k", outputFile.getCanonicalPath()})); |
| |
| if (waitFor(p, false) != 0) { |
| p = Runtime.getRuntime().exec(getCommandLine(new String[] {"git", "status"})); |
| runProcess(p); |
| |
| if (doCommit()) { |
| records.add("M " + ver); |
| } |
| } else { |
| records.add("M " + ver); |
| } |
| outputFile.delete(); |
| } |
| private static String getPatchId(String id) throws Exception { |
| |
| String commands[] = new String[] { "git", "show", id}; |
| Process p = Runtime.getRuntime().exec(commands); |
| InputStream in = p.getInputStream(); |
| |
| commands = new String[] { "git", "patch-id"}; |
| Process p2 = Runtime.getRuntime().exec(commands); |
| OutputStream out = p2.getOutputStream(); |
| byte bytes[] = new byte[1024]; |
| int len = in.read(bytes); |
| BufferedReader r2 = new BufferedReader(new InputStreamReader(p2.getInputStream())); |
| while (len > 0) { |
| out.write(bytes, 0, len); |
| len = in.read(bytes); |
| } |
| p.waitFor(); |
| out.close(); |
| |
| id = r2.readLine(); |
| p2.waitFor(); |
| |
| id = id.substring(0, id.indexOf(" ")); |
| return id; |
| } |
| |
| private static String getUserName() throws Exception { |
| BufferedReader reader; |
| String line; |
| Process p = Runtime.getRuntime().exec(new String[] {"git", "config", "user.name"}); |
| |
| reader = new BufferedReader(new InputStreamReader(p.getInputStream())); |
| line = reader.readLine(); |
| p.waitFor(); |
| return line; |
| } |
| |
| public static void main(String a[]) throws Exception { |
| File file = new File(".git-commit-message.txt"); |
| if (file.exists()) { |
| //make sure we delete this to not cause confusion |
| file.delete(); |
| } |
| |
| System.out.println("Updating directory"); |
| |
| doUpdate(); |
| |
| List<String> args = new LinkedList<String>(Arrays.asList(a)); |
| List<String> check = new LinkedList<String>(); |
| while (!args.isEmpty()) { |
| String get = args.remove(0); |
| |
| if ("-auto".equals(get)) { |
| auto = true; |
| } else if ("-me".equals(get)) { |
| username = getUserName(); |
| } else if ("-user".equals(get)) { |
| username = args.get(0); |
| } else { |
| check.add(get); |
| } |
| } |
| |
| |
| List<String> verList = getAvailableUpdates(); |
| if (!check.isEmpty()) { |
| List<String> newList = new LinkedList<String>(); |
| for (String s : check) { |
| if (verList.contains(s)) { |
| newList.add(s); |
| } |
| } |
| verList = newList; |
| } |
| if (verList.isEmpty()) { |
| System.out.println("Nothing needs to be merged"); |
| System.exit(0); |
| } |
| |
| System.out.println("Merging versions (" + verList.size() + "): " + verList); |
| |
| List<String[]> gitLogs = null; |
| //with GIT, we can relatively quickly check the logs on the current branch |
| //and compare with what should be merged and check if things are already merged |
| gitLogs = getGitLogs(); |
| |
| Set<String> jiras = new TreeSet<String>(); |
| |
| for (int cur = 0; cur < verList.size(); cur++) { |
| jiras.clear(); |
| String ver = verList.get(cur); |
| String[] logLines = getLog(ver, jiras); |
| if (logLines.length > 1 |
| && username != null |
| && !logLines[0].contains(username)) { |
| continue; |
| } |
| System.out.println(); |
| System.out.println("Merging: " + ver + " (" + (cur + 1) + "/" + verList.size() + ")"); |
| //System.out.println("http://svn.apache.org/viewvc?view=revision&revision=" + ver); |
| |
| for (String s : jiras) { |
| System.out.println("https://issues.apache.org/jira/browse/" + s); |
| } |
| StringBuilder log = new StringBuilder(); |
| if (isBlocked(logLines)) { |
| records.add("B " + ver); |
| continue; |
| } |
| for (String s : logLines) { |
| System.out.println(s); |
| log.append(s).append("\n"); |
| } |
| |
| char c = auto ? 'M' : 0; |
| if (checkPatchId(ver)) { |
| continue; |
| } |
| if (checkAlreadyMerged(ver, gitLogs, logLines)) { |
| c = 'R'; |
| } |
| |
| while (System.in.available() > 0) { |
| System.in.read(); |
| } |
| while (c != 'M' |
| && c != 'A' |
| && c != 'B' |
| && c != 'I' |
| && c != 'R' |
| && c != 'F' |
| && c != 'C' |
| && c != 'P') { |
| System.out.print("[M]erge, [A]dvancedMerge, [B]lock, or [I]gnore, [R]ecord only, [F]lush, [C]hanges? "); |
| int i = System.in.read(); |
| c = Character.toUpperCase((char)i); |
| } |
| |
| switch (c) { |
| case 'M': |
| doMerge(ver); |
| break; |
| case 'A': |
| doMappedMerge(ver); |
| break; |
| case 'P': |
| System.out.println("Patch Id: " + getPatchId(ver)); |
| cur--; |
| break; |
| case 'B': |
| records.add("B " + ver); |
| break; |
| case 'R': |
| records.add("M " + ver); |
| break; |
| case 'F': |
| flush(); |
| cur--; |
| break; |
| case 'C': |
| changes(ver); |
| cur--; |
| break; |
| case 'I': |
| System.out.println("Ignoring"); |
| break; |
| } |
| } |
| flush(); |
| } |
| private static boolean isBlocked(String[] logLines) { |
| for (String s: logLines) { |
| if (s.trim().contains("Recording .gitmergeinfo Changes")) { |
| return true; |
| } |
| if (s.contains("[maven-release-plugin] prepare")) { |
| return true; |
| } |
| } |
| return false; |
| } |
| private static boolean checkPatchId(String ver) throws Exception { |
| if (!patchIds.isEmpty()) { |
| String pid = getPatchId(ver); |
| if (patchIds.contains("B " + pid)) { |
| records.add("B " + ver); |
| System.out.println("Already blocked: " + ver); |
| return true; |
| } else if (patchIds.contains("M " + pid)) { |
| records.add("M " + ver); |
| System.out.println("Already merged: " + ver); |
| return true; |
| } |
| } |
| return false; |
| } |
| private static boolean checkAlreadyMerged(String ver, List<String[]> gitLogs, String[] logLines) throws Exception { |
| if (gitLogs == null) { |
| return false; |
| } |
| List<List<String>> matchingLogs = new LinkedList<List<String>>(); |
| for (String[] f : gitLogs) { |
| List<String> ll = compareLogs(f, logLines); |
| if (!ll.isEmpty()) { |
| matchingLogs.add(ll); |
| } |
| } |
| |
| if (!matchingLogs.isEmpty()) { |
| //everything in the source log is in a log on this branch, let's prompt to record the merge |
| System.out.println("Found possible commit(s) already on branch:"); |
| for (List<String> f : matchingLogs) { |
| for (String s : f) { |
| System.out.println(" " + s); |
| } |
| System.out.println("------------------------"); |
| } |
| |
| while (System.in.available() > 0) { |
| System.in.read(); |
| } |
| char c = 0; |
| while (c != 'Y' |
| && c != 'N') { |
| System.out.print("Record as merged [Y/N]? "); |
| int i = System.in.read(); |
| c = Character.toUpperCase((char)i); |
| } |
| if (c == 'Y') { |
| return true; |
| } |
| } |
| return false; |
| } |
| private static List<String> compareLogs(String[] f, String[] logLines) throws IOException { |
| ArrayList<String> onBranch = new ArrayList<String>(f.length); |
| for (String s : f) { |
| if (s.trim().startsWith("Conflicts:")) { |
| break; |
| } |
| if (s.trim().length() > 0 |
| && s.charAt(0) == ' ' |
| && !s.contains("git-svn-id")) { |
| onBranch.add(s.trim()); |
| } |
| } |
| |
| List<String> ll = new ArrayList<String>(); |
| for (String s : logLines) { |
| if (s.trim().length() > 0 |
| && onBranch.remove(s.trim()) |
| && !s.startsWith("Author: ") |
| && !s.startsWith("Date: ") |
| && !s.contains("git-svn-id")) { |
| ll.add(s); |
| } |
| } |
| return ll; |
| } |
| |
| |
| private static String[] getCommandLine(String[] args) { |
| List<String> argLine = new ArrayList<String>(); |
| if (isWindows()) { |
| argLine.add("cmd.exe"); |
| argLine.add("/c"); |
| } |
| |
| argLine.addAll(Arrays.asList(args)); |
| StringBuilder b = new StringBuilder(256); |
| for (String s : argLine) { |
| if (b.length() == 0) { |
| b.append("Running \""); |
| } else { |
| b.append(" "); |
| } |
| b.append(s); |
| } |
| b.append("\"..."); |
| System.out.println(b.toString()); |
| return argLine.toArray(new String[argLine.size()]); |
| } |
| |
| private static boolean isWindows() { |
| return System.getProperty("os.name").toLowerCase().indexOf("windows") != -1; |
| } |
| |
| |
| } |
| |
| |