Java tutorial
package org.apache.maven.scm.provider.git.gitexe.command.status; /* * 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.File; import java.io.UnsupportedEncodingException; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.apache.maven.scm.ScmFile; import org.apache.maven.scm.ScmFileStatus; import org.apache.maven.scm.log.ScmLogger; import org.codehaus.plexus.util.cli.StreamConsumer; /** * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a> */ public class GitStatusConsumer implements StreamConsumer { /** * The pattern used to match added file lines */ private static final Pattern ADDED_PATTERN = Pattern.compile("^A[ M]* (.*)$"); /** * The pattern used to match modified file lines */ private static final Pattern MODIFIED_PATTERN = Pattern.compile("^ *M[ M]* (.*)$"); /** * The pattern used to match deleted file lines */ private static final Pattern DELETED_PATTERN = Pattern.compile("^ *D * (.*)$"); /** * The pattern used to match renamed file lines */ private static final Pattern RENAMED_PATTERN = Pattern.compile("^R (.*) -> (.*)$"); private ScmLogger logger; private File workingDirectory; /** * Entries are relative to working directory, not to the repositoryroot */ private List<ScmFile> changedFiles = new ArrayList<ScmFile>(); private URI relativeRepositoryPath; // ---------------------------------------------------------------------- // // ---------------------------------------------------------------------- /** * Consumer when workingDirectory and repositoryRootDirectory are the same * * @param logger the logger * @param workingDirectory the working directory */ public GitStatusConsumer(ScmLogger logger, File workingDirectory) { this.logger = logger; this.workingDirectory = workingDirectory; } /** * Assuming that you have to discover the repositoryRoot, this is how you can get the * <code>relativeRepositoryPath</code> * <pre> * URI.create( repositoryRoot ).relativize( fileSet.getBasedir().toURI() ) * </pre> * * @param logger the logger * @param workingDirectory the working directory * @param relativeRepositoryPath the working directory relative to the repository root * @since 1.9 * @see GitStatusCommand#createRevparseShowToplevelCommand(org.apache.maven.scm.ScmFileSet) */ public GitStatusConsumer(ScmLogger logger, File workingDirectory, URI relativeRepositoryPath) { this(logger, workingDirectory); this.relativeRepositoryPath = relativeRepositoryPath; } // ---------------------------------------------------------------------- // StreamConsumer Implementation // ---------------------------------------------------------------------- /** * {@inheritDoc} */ public void consumeLine(String line) { if (logger.isDebugEnabled()) { logger.debug(line); } if (StringUtils.isEmpty(line)) { return; } ScmFileStatus status = null; List<String> files = new ArrayList<String>(); Matcher matcher; if ((matcher = ADDED_PATTERN.matcher(line)).find()) { status = ScmFileStatus.ADDED; files.add(resolvePath(matcher.group(1), relativeRepositoryPath)); } else if ((matcher = MODIFIED_PATTERN.matcher(line)).find()) { status = ScmFileStatus.MODIFIED; files.add(resolvePath(matcher.group(1), relativeRepositoryPath)); } else if ((matcher = DELETED_PATTERN.matcher(line)).find()) { status = ScmFileStatus.DELETED; files.add(resolvePath(matcher.group(1), relativeRepositoryPath)); } else if ((matcher = RENAMED_PATTERN.matcher(line)).find()) { status = ScmFileStatus.RENAMED; files.add(resolvePath(matcher.group(1), relativeRepositoryPath)); files.add(resolvePath(matcher.group(2), relativeRepositoryPath)); logger.debug("RENAMED status for line '" + line + "' files added '" + matcher.group(1) + "' '" + matcher.group(2)); } else { logger.warn("Ignoring unrecognized line: " + line); return; } // If the file isn't a file; don't add it. if (!files.isEmpty() && status != null) { if (workingDirectory != null) { if (status == ScmFileStatus.RENAMED) { String oldFilePath = files.get(0); String newFilePath = files.get(1); if (isFile(oldFilePath)) { logger.debug("file '" + oldFilePath + "' is a file"); return; } else { logger.debug("file '" + oldFilePath + "' not a file"); } if (!isFile(newFilePath)) { logger.debug("file '" + newFilePath + "' not a file"); return; } else { logger.debug("file '" + newFilePath + "' is a file"); } } else if (status == ScmFileStatus.DELETED) { if (isFile(files.get(0))) { return; } } else { if (!isFile(files.get(0))) { return; } } } for (String file : files) { changedFiles.add(new ScmFile(file, status)); } } } private boolean isFile(String file) { File targetFile; if (relativeRepositoryPath == null) { targetFile = new File(workingDirectory, file); } else { targetFile = new File(relativeRepositoryPath.getPath(), file); } return targetFile.isFile(); } protected static String resolvePath(String fileEntry, URI path) { /* Quotes may be included (from the git status line) when an fileEntry includes spaces */ String cleanedEntry = stripQuotes(fileEntry); if (path != null) { return resolveURI(cleanedEntry, path).getPath(); } else { return cleanedEntry; } } /** * * @param fileEntry the fileEntry, must not be {@code null} * @param path the path, must not be {@code null} * @return */ public static URI resolveURI(String fileEntry, URI path) { // When using URI.create, spaces need to be escaped but not the slashes, so we can't use // URLEncoder.encode( String, String ) // new File( String ).toURI() results in an absolute URI while path is relative, so that can't be used either. return path.relativize(URI.create(stripQuotes(fileEntry).replace(" ", "%20"))); } public List<ScmFile> getChangedFiles() { return changedFiles; } /** * @param str the (potentially quoted) string, must not be {@code null} * @return the string with a pair of double quotes removed (if they existed) */ private static String stripQuotes(String str) { int strLen = str.length(); return (strLen > 0 && str.startsWith("\"") && str.endsWith("\"")) ? unescape(str.substring(1, strLen - 1)) : str; } /** * Dequote a quoted string generated by git status --porcelain. * The leading and trailing quotes have already been removed. * @param fileEntry * @return */ private static String unescape(String fileEntry) { // If there are no escaped characters, just return the input argument int pos = fileEntry.indexOf('\\'); if (pos == -1) { return fileEntry; } // We have escaped characters byte[] inba = fileEntry.getBytes(); int inSub = 0; // Input subscript into fileEntry byte[] outba = new byte[fileEntry.length()]; int outSub = 0; // Output subscript into outba while (true) { System.arraycopy(inba, inSub, outba, outSub, pos - inSub); outSub += pos - inSub; inSub = pos + 1; switch ((char) inba[inSub++]) { case '"': outba[outSub++] = '"'; break; case 'a': outba[outSub++] = 7; // Bell break; case 'b': outba[outSub++] = '\b'; break; case 't': outba[outSub++] = '\t'; break; case 'n': outba[outSub++] = '\n'; break; case 'v': outba[outSub++] = 11; // Vertical tab break; case 'f': outba[outSub++] = '\f'; break; case 'r': outba[outSub++] = '\f'; break; case '\\': outba[outSub++] = '\\'; break; case '0': case '1': case '2': case '3': // This assumes that the octal escape here is valid. byte b = (byte) ((inba[inSub - 1] - '0') << 6); b |= (byte) ((inba[inSub++] - '0') << 3); b |= (byte) (inba[inSub++] - '0'); outba[outSub++] = b; break; default: //This is an invalid escape in a string. Just copy it. outba[outSub++] = '\\'; inSub--; break; } pos = fileEntry.indexOf('\\', inSub); if (pos == -1) // No more backslashes; we're done { System.arraycopy(inba, inSub, outba, outSub, inba.length - inSub); outSub += inba.length - inSub; break; } } try { // explicit say UTF-8, otherwise it'll fail at least on Windows cmdline return new String(outba, 0, outSub, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } }