h.setStdoutSuppressed(true);
h.addParameters("-M", "--name-status", parser.getPretty(), "--encoding=UTF-8", commit);
h.endOptions();
parser.parseStatusBeforeName(true);
final String output = h.run();
final List records = parser.parse(output);
// we have information about all changed files of the commit. Extracting information about the file we need.
GitLogRecord fileRecord = null;
for (GitLogRecord record : records) {
final List paths = record.getPaths();
if (!paths.isEmpty()) {
String path = paths.get(paths.size()-1); // if the file is renamed, it has 2 paths - we are looking for the new name.
if (path.equals(GitUtil.relativePath(root, filePath))) {
fileRecord = record;
break;
}
}
}
if (fileRecord != null) {
final List changes = fileRecord.coolChangesParser(project, root);
final Change change = changes.get(0);
if (change.isMoved() || change.isRenamed()) {
final List paths = fileRecord.getFilePaths(root);
final String message = "Rename commit should have 2 paths. Commit: " + commit;
if (!LOG.assertTrue(paths.size() == 2, message + " Output: [" + output + "]")) {
throw new VcsException(message);
}
return paths.get(0);
}
}
return null;
}
private static class MyTokenAccumulator {
private final StringBuilder myBuffer = new StringBuilder();
private boolean myNotStarted = true;
private GitLogParser myParser;
public MyTokenAccumulator(GitLogParser parser) {
myParser = parser;
}
@Nullable
public GitLogRecord acceptLine(String s) {
final boolean lineEnd = s.startsWith(GitLogParser.RECORD_START);
if (lineEnd && (!myNotStarted)) {
final String line = myBuffer.toString();
myBuffer.setLength(0);
myBuffer.append(s.substring(GitLogParser.RECORD_START.length()));
return processResult(line);
}
else {
myBuffer.append(lineEnd ? s.substring(GitLogParser.RECORD_START.length()) : s);
myBuffer.append("\n");
}
myNotStarted = false;
return null;
}
public GitLogRecord processLast() {
return processResult(myBuffer.toString());
}
private GitLogRecord processResult(final String line) {
return myParser.parseOneRecord(line);
}
}
/**
* Get history for the file
*
* @param project the context project
* @param path the file path
* @return the list of the revisions
* @throws VcsException if there is problem with running git
*/
public static List history(final Project project, final FilePath path) throws VcsException {
final VirtualFile root = GitUtil.getGitRoot(path);
return history(project, path, root);
}
/**
* Get history for the file
*
* @param project the context project
* @param path the file path
* @return the list of the revisions
* @throws VcsException if there is problem with running git
*/
public static List history(final Project project, FilePath path, final VirtualFile root, final String... parameters) throws VcsException {
final List rc = new ArrayList();
final List exceptions = new ArrayList();
history(project, path, root, new Consumer() {
@Override public void consume(GitFileRevision gitFileRevision) {
rc.add(gitFileRevision);
}
}, new Consumer() {
@Override public void consume(VcsException e) {
exceptions.add(e);
}
}, parameters);
if (!exceptions.isEmpty()) {
throw exceptions.get(0);
}
return rc;
}
public static List> onlyHashesHistory(Project project, FilePath path, final String... parameters)
throws VcsException {
final VirtualFile root = GitUtil.getGitRoot(path);
return onlyHashesHistory(project, path, root, parameters);
}
public static List> onlyHashesHistory(Project project, FilePath path, final VirtualFile root, final String... parameters)
throws VcsException {
// adjust path using change manager
path = getLastCommitName(project, path);
GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG);
GitLogParser parser = new GitLogParser(HASH, COMMIT_TIME);
h.setNoSSH(true);
h.setStdoutSuppressed(true);
h.addParameters(parameters);
h.addParameters(parser.getPretty(), "--encoding=UTF-8");
h.endOptions();
h.addRelativePaths(path);
String output = h.run();
final List> rc = new ArrayList>();
for (GitLogRecord record : parser.parse(output)) {
rc.add(new Pair(new SHAHash(record.getHash()), record.getDate()));
}
return rc;
}
public static void historyWithLinks(final Project project,
FilePath path,
final SymbolicRefs refs,
final AsynchConsumer gitCommitConsumer,
final Getter isCanceled,
final String... parameters) throws VcsException {
// adjust path using change manager
path = getLastCommitName(project, path);
final VirtualFile root = GitUtil.getGitRoot(path);
final GitLineHandler h = new GitLineHandler(project, root, GitCommand.LOG);
final GitLogParser parser = new GitLogParser(SHORT_HASH, HASH, COMMIT_TIME, AUTHOR_NAME, AUTHOR_TIME, AUTHOR_EMAIL, COMMITTER_NAME, COMMITTER_EMAIL, SHORT_PARENTS, REF_NAMES, SUBJECT, BODY);
h.setNoSSH(true);
h.setStdoutSuppressed(true);
h.addParameters(parameters);
parser.parseStatusBeforeName(true);
h.addParameters("--name-status", parser.getPretty(), "--encoding=UTF-8"); // todo ?
h.endOptions();
h.addRelativePaths(path);
final VcsException[] exc = new VcsException[1];
final Semaphore semaphore = new Semaphore();
final StringBuilder sb = new StringBuilder();
h.addLineListener(new GitLineHandlerAdapter() {
@Override
public void onLineAvailable(final String line, final Key outputType) {
try {
if (ProcessOutputTypes.STDOUT.equals(outputType)) {
if (isCanceled != null && isCanceled.get()) {
h.cancel();
return;
}
//if (line.charAt(line.length() - 1) != '\u0003') {
if (! line.startsWith("\u0001")) {
sb.append("\n").append(line);
return;
}
takeLine(project, line, sb, parser, refs, root, exc, h, gitCommitConsumer);
}
} catch (ProcessCanceledException e) {
h.cancel();
semaphore.up();
}
}
@Override
public void processTerminated(int exitCode) {
semaphore.up();
}
@Override
public void startFailed(Throwable exception) {
}
});
semaphore.down();
h.start();
semaphore.waitFor();
takeLine(project, "", sb, parser, refs, root, exc, h, gitCommitConsumer);
gitCommitConsumer.finished();
if (exc[0] != null) {
throw exc[0];
}
}
private static void takeLine(final Project project, String line,
StringBuilder sb,
GitLogParser parser,
SymbolicRefs refs,
VirtualFile root,
VcsException[] exc, GitLineHandler h, AsynchConsumer gitCommitConsumer) {
final String text = sb.toString();
sb.setLength(0);
sb.append(line);
if (text.length() == 0) return;
GitLogRecord record = parser.parseOneRecord(text);
final GitCommit gitCommit;
try {
gitCommit = createCommit(project, refs, root, record);
}
catch (VcsException e) {
exc[0] = e;
h.cancel();
return;
}
gitCommitConsumer.consume(gitCommit);
}
private static GitCommit createCommit(Project project, SymbolicRefs refs, VirtualFile root, GitLogRecord record) throws VcsException {
GitCommit gitCommit;
final Collection currentRefs = record.getRefs();
List locals = new ArrayList();
List remotes = new ArrayList();
List tags = new ArrayList();
final String s = parseRefs(refs, currentRefs, locals, remotes, tags);
gitCommit = new GitCommit(AbstractHash.create(record.getShortHash()), new SHAHash(record.getHash()), record.getAuthorName(),
record.getCommitterName(),
record.getDate(), record.getFullMessage(),
new HashSet(Arrays.asList(record.getParentsShortHashes())), record.getFilePaths(root),
record.getAuthorEmail(),
record.getCommitterEmail(), tags, locals, remotes,
record.coolChangesParser(project, root), record.getAuthorTimeStamp() * 1000);
gitCommit.setCurrentBranch(s);
return gitCommit;
}
private static String parseRefs(SymbolicRefs refs,
Collection currentRefs,
List locals,
List remotes,
List tags) {
for (String ref : currentRefs) {
final SymbolicRefs.Kind kind = refs.getKind(ref);
if (SymbolicRefs.Kind.LOCAL.equals(kind)) {
locals.add(ref);
} else if (SymbolicRefs.Kind.REMOTE.equals(kind)) {
remotes.add(ref);
} else {
tags.add(ref);
}
}
if (refs.getCurrent() != null && currentRefs.contains(refs.getCurrent().getName())) return refs.getCurrent().getName();
return null;
}
public static List commitsDetails(Project project,
FilePath path, SymbolicRefs refs,
final Collection commitsIds) throws VcsException {
// adjust path using change manager
path = getLastCommitName(project, path);
final VirtualFile root = GitUtil.getGitRoot(path);
GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.SHOW);
GitLogParser parser = new GitLogParser(SHORT_HASH, HASH, COMMIT_TIME, AUTHOR_NAME, AUTHOR_TIME, AUTHOR_EMAIL, COMMITTER_NAME, COMMITTER_EMAIL, SHORT_PARENTS, REF_NAMES, SUBJECT, BODY);
h.setNoSSH(true);
h.setStdoutSuppressed(true);
h.addParameters("--name-only", parser.getPretty(), "--encoding=UTF-8");
parser.parseStatusBeforeName(false);
h.addParameters(new ArrayList(commitsIds));
h.endOptions();
h.addRelativePaths(path);
String output = h.run();
final List rc = new ArrayList();
for (GitLogRecord record : parser.parse(output)) {
final GitCommit gitCommit = createCommit(project, refs, root, record);
rc.add(gitCommit);
}
return rc;
}
public static void hashesWithParents(Project project, FilePath path, final AsynchConsumer consumer,
final Getter isCanceled,
final String... parameters) throws VcsException {
// adjust path using change manager
path = getLastCommitName(project, path);
final VirtualFile root = GitUtil.getGitRoot(path);
final GitLineHandler h = new GitLineHandler(project, root, GitCommand.LOG);
final GitLogParser parser = new GitLogParser(SHORT_HASH, COMMIT_TIME, SHORT_PARENTS, AUTHOR_NAME);
parser.parseStatusBeforeName(false);
h.setNoSSH(true);
h.setStdoutSuppressed(true);
h.addParameters(parameters);
h.addParameters(parser.getPretty(), "--encoding=UTF-8");
h.endOptions();
h.addRelativePaths(path);
final Semaphore semaphore = new Semaphore();
h.addLineListener(new GitLineHandlerListener() {
@Override
public void onLineAvailable(final String line, final Key outputType) {
try {
if (ProcessOutputTypes.STDOUT.equals(outputType)) {
if (isCanceled != null && isCanceled.get()) {
h.cancel();
return;
}
GitLogRecord record = parser.parseOneRecord(line);
consumer.consume(new CommitHashPlusParents(record.getShortHash(),
record.getParentsShortHashes(), record.getLongTimeStamp() * 1000,
record.getAuthorName()));
}
} catch (ProcessCanceledException e) {
h.cancel();
semaphore.up();
}
}
@Override
public void processTerminated(int exitCode) {
semaphore.up();
}
@Override
public void startFailed(Throwable exception) {
// todo
}
});
semaphore.down();
h.start();
semaphore.waitFor();
consumer.finished();
}
/**
* Get name of the file in the last commit. If file was renamed, returns the previous name.
*
* @param project the context project
* @param path the path to check
* @return the name of file in the last commit or argument
*/
public static FilePath getLastCommitName(final Project project, FilePath path) {
final ChangeListManager changeManager = ChangeListManager.getInstance(project);
final Change change = changeManager.getChange(path);
if (change != null && change.getType() == Change.Type.MOVED) {
GitContentRevision r = (GitContentRevision)change.getBeforeRevision();
assert r != null : "Move change always have beforeRevision";
path = r.getFile();
}
return path;
}
} |