/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.plugins.scm.git;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.batch.scm.BlameLine;
import org.sonar.api.utils.command.StreamConsumer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GitBlameConsumer implements StreamConsumer {
private static final String GIT_COMMITTER_PREFIX = "committer";
private static final String GIT_COMMITTER_TIME = GIT_COMMITTER_PREFIX + "-time ";
private static final String GIT_AUTHOR_EMAIL = "author-mail ";
private static final String OPENING_EMAIL_FIELD = "<";
private static final String CLOSING_EMAIL_FIELD = ">";
private List<BlameLine> lines = new ArrayList<BlameLine>();
/**
* Since the porcelain format only contains the commit information
* the first time a specific sha-1 commit appears, we need to store
* this information somwehere.
* <p/>
* key: the sha-1 of the commit
* value: the {@link BlameLine} containing the full committer/author info
*/
private Map<String, BlameLine> commitInfo = new HashMap<String, BlameLine>();
private boolean expectRevisionLine = true;
private String revision = null;
private String author = null;
private Date time = null;
private final String filename;
public GitBlameConsumer(String filename) {
this.filename = filename;
}
@Override
public void consumeLine(String line) {
if (line == null) {
return;
}
if (expectRevisionLine) {
// this is the revision line
consumeRevisionLine(line);
} else {
if (extractCommitInfoFromLine(line)) {
return;
}
if (line.startsWith("\t")) {
// this is the content line.
// we actually don't need the content, but this is the right time to add the blame line
consumeContentLine();
}
}
}
@VisibleForTesting
protected boolean extractCommitInfoFromLine(String line) {
if (line.startsWith(GIT_AUTHOR_EMAIL)) {
author = extractEmail(line);
return true;
}
if (line.startsWith(GIT_COMMITTER_TIME)) {
String timeStr = line.substring(GIT_COMMITTER_TIME.length());
time = new Date(Long.parseLong(timeStr) * 1000L);
return true;
}
return false;
}
private String extractEmail(String line) {
int emailStartIndex = line.indexOf(OPENING_EMAIL_FIELD);
int emailEndIndex = line.indexOf(CLOSING_EMAIL_FIELD);
if (emailStartIndex == -1 || emailEndIndex == -1 || emailEndIndex <= emailStartIndex) {
return null;
}
return line.substring(emailStartIndex + 1, emailEndIndex);
}
private void consumeContentLine() {
BlameLine blameLine = new BlameLine().date(time).revision(revision).author(author);
getLines().add(blameLine);
// keep commitinfo for this sha-1
commitInfo.put(revision, blameLine);
expectRevisionLine = true;
}
private void consumeRevisionLine(String line) {
String[] parts = line.split("\\s", 4);
if (parts.length >= 1) {
revision = parts[0];
if (StringUtils.containsOnly(revision, "0")) {
throw new IllegalStateException("Unable to blame file " + filename + ". No blame info at line " + (getLines().size() + 1) + ". Is file commited?");
}
BlameLine oldLine = commitInfo.get(revision);
if (oldLine != null) {
// restore the commit info
author = oldLine.author();
time = oldLine.date();
}
expectRevisionLine = false;
}
}
public List<BlameLine> getLines() {
return lines;
}
}