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