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.*;
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 */
042public 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}