001    package org.apache.maven.scm.provider.git.gitexe.command.blame;
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.command.blame.BlameLine;
023    import org.apache.maven.scm.log.ScmLogger;
024    import org.apache.maven.scm.util.AbstractConsumer;
025    
026    import java.text.DateFormat;
027    import java.text.SimpleDateFormat;
028    import java.util.*;
029    
030    /**
031     * Parses the --porcelain format of git-blame
032     *
033     * For more information about the porcelain format, please read the official
034     * <a href="http://www.kernel.org/pub/software/scm/git/docs/git-blame.html#_the_porcelain_format">
035     * GIT blame porcelain format</a> description.
036     *
037     * @author Evgeny Mandrikov
038     * @author Olivier Lamy
039     * @author Mark Struberg
040     * @since 1.4
041     */
042    public class GitBlameConsumer
043        extends AbstractConsumer
044    {
045        private final static String GIT_COMMITTER_PREFIX = "committer";
046        private final static String GIT_COMMITTER      = GIT_COMMITTER_PREFIX + " ";
047        private final static String GIT_COMMITTER_TIME = GIT_COMMITTER_PREFIX + "-time ";
048        private final static String GIT_AUTHOR         = "author ";
049    
050    
051        private List<BlameLine> lines = new ArrayList<BlameLine>();
052    
053        /**
054         * Since the porcelain format only contains the commit information
055         * the first time a specific sha-1 commit appears, we need to store
056         * this information somwehere.
057         *
058         * key: the sha-1 of the commit
059         * value: the {@link BlameLine} containing the full committer/author info
060         */
061        private Map<String, BlameLine> commitInfo = new HashMap<String, BlameLine>();
062    
063        private boolean expectRevisionLine = true;
064    
065        private String revision  = null;
066        private String author    = null;
067        private String committer = null;
068        private Date   time      = null;
069    
070        public GitBlameConsumer( ScmLogger logger )
071        {
072            super( logger );
073        }
074    
075        public void consumeLine( String line )
076        {
077            if ( line == null )
078            {
079                return;
080            }
081    
082            if (expectRevisionLine)
083            {
084                // this is the revision line
085                String parts[] = line.split( "\\s", 4 );
086    
087                if ( parts.length >= 1)
088                {
089                    revision = parts[0];
090    
091                    BlameLine oldLine = commitInfo.get( revision );
092    
093                    if ( oldLine != null )
094                    {
095                        // restore the commit info
096                        author    = oldLine.getAuthor();
097                        committer = oldLine.getCommitter();
098                        time      = oldLine.getDate();
099                    }
100    
101                    expectRevisionLine = false;
102                }
103            }
104            else
105            {
106                if ( line.startsWith( GIT_AUTHOR ) )
107                {
108                    author = line.substring( GIT_AUTHOR.length() );
109                    return;
110                }
111    
112                if ( line.startsWith( GIT_COMMITTER ) )
113                {
114                    committer = line.substring( GIT_COMMITTER.length() );
115                    return;
116                }
117    
118                if ( line.startsWith( GIT_COMMITTER_TIME ) )
119                {
120                    String timeStr = line.substring( GIT_COMMITTER_TIME.length() );
121                    time = new Date( Long.parseLong( timeStr ) * 1000L );
122                    return;
123                }
124    
125    
126                if ( line.startsWith( "\t" ) )
127                {
128                    // this is the content line.
129                    // we actually don't need the content, but this is the right time to add the blame line
130                    BlameLine blameLine = new BlameLine( time, revision, author, committer );
131                    getLines().add( blameLine );
132    
133                    // keep commitinfo for this sha-1
134                    commitInfo.put( revision, blameLine );
135    
136                    if ( getLogger().isDebugEnabled() )
137                    {
138                        DateFormat df = SimpleDateFormat.getDateTimeInstance();
139                        getLogger().debug( author + " " + df.format( time ) );
140                    }
141    
142                    expectRevisionLine = true;
143                }
144    
145            }
146        }
147    
148        public List<BlameLine> getLines()
149        {
150            return lines;
151        }
152    }