001    package 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    
022    import org.apache.maven.scm.ScmFile;
023    import org.apache.maven.scm.ScmFileStatus;
024    import org.apache.maven.scm.log.ScmLogger;
025    import org.apache.regexp.RE;
026    import org.apache.regexp.RESyntaxException;
027    import org.codehaus.plexus.util.cli.StreamConsumer;
028    
029    import java.io.File;
030    import java.util.ArrayList;
031    import java.util.HashMap;
032    import java.util.List;
033    import java.util.Map;
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     */
041    public 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 String DIFF_FILES_PATTERN = "^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         * @see #DIFF_FILES_PATTERN
095         */
096        private RE filesRegexp;
097    
098        // ----------------------------------------------------------------------
099        //
100        // ----------------------------------------------------------------------
101    
102        public GitDiffConsumer( ScmLogger logger, File workingDirectory )
103        {
104            this.logger = logger;
105            try
106            {
107                filesRegexp = new RE( DIFF_FILES_PATTERN );
108            }
109            catch ( RESyntaxException ex )
110            {
111                throw new RuntimeException(
112                                            "INTERNAL ERROR: Could not create regexp to parse git log file. Something is probably wrong with the oro installation.",
113                                            ex );
114            }
115    
116        }
117    
118        // ----------------------------------------------------------------------
119        // StreamConsumer Implementation
120        // ----------------------------------------------------------------------
121    
122        /** {@inheritDoc} */
123        public void consumeLine( String line )
124        {
125            if ( filesRegexp.match( line ) )
126            {
127                // start a new file
128                currentFile = filesRegexp.getParen( 1 );
129    
130                changedFiles.add( new ScmFile( currentFile, ScmFileStatus.MODIFIED ) );
131    
132                currentDifference = new StringBuilder();
133    
134                differences.put( currentFile, currentDifference );
135    
136                patch.append( line ).append( "\n" );
137    
138                return;
139            }
140    
141            if ( currentFile == null )
142            {
143                if ( logger.isWarnEnabled() )
144                {
145                    logger.warn( "Unparseable line: '" + line + "'" );
146                }
147                patch.append( line ).append( "\n" );
148                return;
149            }
150            else if ( line.startsWith( INDEX_LINE_TOKEN ) )
151            {
152                // skip, though could parse to verify start revision and end revision
153                patch.append( line ).append( "\n" );
154            }
155            else if ( line.startsWith( NEW_FILE_MODE_TOKEN ) || line.startsWith( DELETED_FILE_MODE_TOKEN ) )
156            {
157                // skip, though could parse to verify file mode
158                patch.append( line ).append( "\n" );
159            }
160            else if ( line.startsWith( START_REVISION_TOKEN ) )
161            {
162                // skip, though could parse to verify filename, start revision
163                patch.append( line ).append( "\n" );
164            }
165            else if ( line.startsWith( END_REVISION_TOKEN ) )
166            {
167                // skip, though could parse to verify filename, end revision
168                patch.append( line ).append( "\n" );
169            }
170            else if ( line.startsWith( ADDED_LINE_TOKEN ) || line.startsWith( REMOVED_LINE_TOKEN )
171                || line.startsWith( UNCHANGED_LINE_TOKEN ) || line.startsWith( CHANGE_SEPARATOR_TOKEN )
172                || line.equals( NO_NEWLINE_TOKEN ) )
173            {
174                // add to buffer
175                currentDifference.append( line ).append( "\n" );
176                patch.append( line ).append( "\n" );
177            }
178            else
179            {
180                // TODO: handle property differences
181    
182                if ( logger.isWarnEnabled() )
183                {
184                    logger.warn( "Unparseable line: '" + line + "'" );
185                }
186                patch.append( line ).append( "\n" );
187                // skip to next file
188                currentFile = null;
189                currentDifference = null;
190            }
191        }
192    
193        public List<ScmFile> getChangedFiles()
194        {
195            return changedFiles;
196        }
197    
198        public Map<String,CharSequence> getDifferences()
199        {
200            return differences;
201        }
202    
203        public String getPatch()
204        {
205            return patch.toString();
206        }
207    
208    }