View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.scm.provider.git.gitexe.command.blame;
20  
21  import java.text.DateFormat;
22  import java.text.SimpleDateFormat;
23  import java.util.ArrayList;
24  import java.util.Date;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.apache.maven.scm.command.blame.BlameLine;
30  import org.apache.maven.scm.util.AbstractConsumer;
31  
32  /**
33   * Parses the --porcelain format of git-blame
34   *
35   * For more information about the porcelain format, please read the official
36   * <a href="http://www.kernel.org/pub/software/scm/git/docs/git-blame.html#_the_porcelain_format">
37   * GIT blame porcelain format</a> description.
38   *
39   * @author Evgeny Mandrikov
40   * @author Olivier Lamy
41   * @author Mark Struberg
42   * @since 1.4
43   */
44  public class GitBlameConsumer extends AbstractConsumer {
45      private static final String GIT_COMMITTER_PREFIX = "committer";
46      private static final String GIT_COMMITTER = GIT_COMMITTER_PREFIX + " ";
47      private static final String GIT_COMMITTER_TIME = GIT_COMMITTER_PREFIX + "-time ";
48      private static final String GIT_AUTHOR = "author ";
49  
50      private final List<BlameLine> lines = new ArrayList<>();
51  
52      /**
53       * Since the porcelain format only contains the commit information
54       * the first time a specific sha-1 commit appears, we need to store
55       * this information somwehere.
56       *
57       * key: the sha-1 of the commit
58       * value: the {@link BlameLine} containing the full committer/author info
59       */
60      private final Map<String, BlameLine> commitInfo = new HashMap<>();
61  
62      private boolean expectRevisionLine = true;
63  
64      private String revision = null;
65      private String author = null;
66      private String committer = null;
67      private Date time = null;
68  
69      public void consumeLine(String line) {
70          if (line == null) {
71              return;
72          }
73  
74          if (expectRevisionLine) {
75              // this is the revision line
76              String parts[] = line.split("\\s", 4);
77  
78              if (parts.length >= 1) {
79                  revision = parts[0];
80  
81                  BlameLine oldLine = commitInfo.get(revision);
82  
83                  if (oldLine != null) {
84                      // restore the commit info
85                      author = oldLine.getAuthor();
86                      committer = oldLine.getCommitter();
87                      time = oldLine.getDate();
88                  }
89  
90                  expectRevisionLine = false;
91              }
92          } else {
93              if (line.startsWith(GIT_AUTHOR)) {
94                  author = line.substring(GIT_AUTHOR.length());
95                  return;
96              }
97  
98              if (line.startsWith(GIT_COMMITTER)) {
99                  committer = line.substring(GIT_COMMITTER.length());
100                 return;
101             }
102 
103             if (line.startsWith(GIT_COMMITTER_TIME)) {
104                 String timeStr = line.substring(GIT_COMMITTER_TIME.length());
105                 time = new Date(Long.parseLong(timeStr) * 1000L);
106                 return;
107             }
108 
109             if (line.startsWith("\t")) {
110                 // this is the content line.
111                 // we actually don't need the content, but this is the right time to add the blame line
112                 BlameLine blameLine = new BlameLine(time, revision, author, committer);
113                 getLines().add(blameLine);
114 
115                 // keep commitinfo for this sha-1
116                 commitInfo.put(revision, blameLine);
117 
118                 if (logger.isDebugEnabled()) {
119                     DateFormat df = SimpleDateFormat.getDateTimeInstance();
120                     logger.debug(author + " " + df.format(time));
121                 }
122 
123                 expectRevisionLine = true;
124             }
125         }
126     }
127 
128     public List<BlameLine> getLines() {
129         return lines;
130     }
131 }