001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.scm.provider.git.command.diff;
020
021import java.io.File;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028
029import org.apache.maven.scm.ScmFile;
030import org.apache.maven.scm.ScmFileStatus;
031import org.apache.maven.scm.util.AbstractConsumer;
032
033/**
034 * @author <a href="mailto:brett@apache.org">Brett Porter</a>
035 * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
036 * @author Olivier Lamy
037 *
038 */
039public class GitDiffConsumer extends AbstractConsumer {
040    // diff --git a/readme.txt b/readme.txt
041    // index fea1611..9e131cf 100644
042    // --- a/readme.txt
043    // +++ b/readme.txt
044    // @@ -1 +1 @@
045    // -/readme.txt
046    // \ No newline at end of file
047    // +new version of /readme.txt
048
049    /**
050     * patern matches the index line of the diff comparison
051     * paren.1 matches the first file
052     * paren.2 matches the 2nd file
053     */
054    private static final Pattern DIFF_FILES_PATTERN = Pattern.compile("^diff --git\\sa/(.*)\\sb/(.*)");
055
056    private static final String START_REVISION_TOKEN = "---";
057
058    private static final String END_REVISION_TOKEN = "+++";
059
060    private static final String ADDED_LINE_TOKEN = "+";
061
062    private static final String REMOVED_LINE_TOKEN = "-";
063
064    private static final String UNCHANGED_LINE_TOKEN = " ";
065
066    private static final String CHANGE_SEPARATOR_TOKEN = "@@";
067
068    private static final String NO_NEWLINE_TOKEN = "\\ No newline at end of file";
069
070    private static final String INDEX_LINE_TOKEN = "index ";
071
072    private static final String SIMILARITY_INDEX_LINE_TOKEN = "similarity index ";
073
074    private static final String RENAME_FROM_LINE_TOKEN = "rename from ";
075
076    private static final String RENAME_TO_LINE_TOKEN = "rename to ";
077
078    private static final String NEW_FILE_MODE_TOKEN = "new file mode ";
079
080    private static final String DELETED_FILE_MODE_TOKEN = "deleted file mode ";
081
082    private String currentFile;
083
084    private StringBuilder currentDifference;
085
086    private final List<ScmFile> changedFiles = new ArrayList<>();
087
088    private final Map<String, CharSequence> differences = new HashMap<>();
089
090    private final StringBuilder patch = new StringBuilder();
091
092    // ----------------------------------------------------------------------
093    //
094    // ----------------------------------------------------------------------
095
096    public GitDiffConsumer(File workingDirectory) {
097        // empty
098    }
099
100    // ----------------------------------------------------------------------
101    // StreamConsumer Implementation
102    // ----------------------------------------------------------------------
103
104    /** {@inheritDoc} */
105    public void consumeLine(String line) {
106        Matcher matcher = DIFF_FILES_PATTERN.matcher(line);
107        if (matcher.matches()) {
108            // start a new file
109            currentFile = matcher.group(1);
110
111            changedFiles.add(new ScmFile(currentFile, ScmFileStatus.MODIFIED));
112
113            currentDifference = new StringBuilder();
114
115            differences.put(currentFile, currentDifference);
116
117            patch.append(line).append("\n");
118
119            return;
120        }
121
122        if (currentFile == null) {
123            if (logger.isWarnEnabled()) {
124                logger.warn("Unparseable line: '" + line + "'");
125            }
126            patch.append(line).append("\n");
127            return;
128        } else if (line.startsWith(INDEX_LINE_TOKEN)) {
129            // skip, though could parse to verify start revision and end revision
130            patch.append(line).append("\n");
131        } else if (line.startsWith(NEW_FILE_MODE_TOKEN) || line.startsWith(DELETED_FILE_MODE_TOKEN)) {
132            // skip, though could parse to verify file mode
133            patch.append(line).append("\n");
134        } else if (line.startsWith(START_REVISION_TOKEN)) {
135            // skip, though could parse to verify filename, start revision
136            patch.append(line).append("\n");
137        } else if (line.startsWith(END_REVISION_TOKEN)) {
138            // skip, though could parse to verify filename, end revision
139            patch.append(line).append("\n");
140        } else if (line.startsWith(SIMILARITY_INDEX_LINE_TOKEN)) {
141            // skip
142            patch.append(line).append("\n");
143        } else if (line.startsWith(RENAME_FROM_LINE_TOKEN) || line.startsWith(RENAME_TO_LINE_TOKEN)) {
144            // skip, though could parse to verify filename
145            patch.append(line).append("\n");
146        } else if (line.startsWith(ADDED_LINE_TOKEN)
147                || line.startsWith(REMOVED_LINE_TOKEN)
148                || line.startsWith(UNCHANGED_LINE_TOKEN)
149                || line.startsWith(CHANGE_SEPARATOR_TOKEN)
150                || line.equals(NO_NEWLINE_TOKEN)) {
151            // add to buffer
152            currentDifference.append(line).append("\n");
153            patch.append(line).append("\n");
154        } else {
155            // TODO: handle property differences
156
157            if (logger.isWarnEnabled()) {
158                logger.warn("Unparseable line: '" + line + "'");
159            }
160            patch.append(line).append("\n");
161            // skip to next file
162            currentFile = null;
163            currentDifference = null;
164        }
165    }
166
167    public List<ScmFile> getChangedFiles() {
168        return changedFiles;
169    }
170
171    public Map<String, CharSequence> getDifferences() {
172        return differences;
173    }
174
175    public String getPatch() {
176        return patch.toString();
177    }
178}