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