-
Notifications
You must be signed in to change notification settings - Fork 92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Submodule support #79
Changes from 15 commits
47fe0b5
085fecc
9798556
8521cb7
8751a3a
7a95ab5
2a5d0c5
4e15853
34e5252
c33d1f4
c596fcd
5243233
d12b2c6
59f9c07
e8b3dc9
6dc2df0
1eb9994
319851f
3ea9223
0179e7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,26 +14,20 @@ | |
|
||
import com.google.common.base.Optional; | ||
import com.google.common.collect.Ordering; | ||
import java.io.Closeable; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.util.Collection; | ||
import java.util.Comparator; | ||
import java.util.Date; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.TreeSet; | ||
import java.io.*; | ||
import java.util.*; | ||
import org.eclipse.jgit.api.Git; | ||
import org.eclipse.jgit.lib.AnyObjectId; | ||
import org.eclipse.jgit.lib.ObjectId; | ||
import org.eclipse.jgit.lib.Ref; | ||
import org.eclipse.jgit.lib.Repository; | ||
import org.eclipse.jgit.diff.DiffEntry; | ||
import org.eclipse.jgit.diff.DiffFormatter; | ||
import org.eclipse.jgit.errors.ConfigInvalidException; | ||
import org.eclipse.jgit.lib.*; | ||
import org.eclipse.jgit.revwalk.RevCommit; | ||
import org.eclipse.jgit.revwalk.RevTag; | ||
import org.eclipse.jgit.revwalk.RevTree; | ||
import org.eclipse.jgit.revwalk.RevWalk; | ||
import org.eclipse.jgit.storage.file.FileRepositoryBuilder; | ||
import org.eclipse.jgit.submodule.SubmoduleWalk; | ||
import org.eclipse.jgit.treewalk.CanonicalTreeParser; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import se.bjurr.gitchangelog.api.GitChangelogApiConstants; | ||
|
@@ -47,10 +41,12 @@ public class GitRepo implements Closeable { | |
private Git git; | ||
private final Repository repository; | ||
private final RevWalk revWalk; | ||
private final HashMap<String, GitRepo> submodules; | ||
|
||
public GitRepo() { | ||
this.repository = null; | ||
this.revWalk = null; | ||
this.submodules = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer an empty map here, not null There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
} | ||
|
||
public GitRepo(final File repo) throws GitChangelogRepositoryException { | ||
|
@@ -71,6 +67,26 @@ public GitRepo(final File repo) throws GitChangelogRepositoryException { | |
this.repository = builder.build(); | ||
this.revWalk = new RevWalk(this.repository); | ||
this.git = new Git(this.repository); | ||
|
||
if (SubmoduleWalk.containsGitModulesFile(repository)) { | ||
this.submodules = new HashMap<>(); | ||
final SubmoduleWalk submoduleWalk = SubmoduleWalk.forIndex(repository); | ||
while (submoduleWalk.next()) { | ||
final Repository submoduleRepository = submoduleWalk.getRepository(); | ||
if (submoduleRepository != null) { | ||
try { | ||
String submodulePath = submoduleWalk.getModulesPath(); | ||
this.submodules.put(submodulePath, new GitRepo(submoduleRepository.getDirectory())); | ||
} catch (ConfigInvalidException e) { | ||
LOG.warn("invalid submodule configuration; skipping submodule\n" + e); | ||
} | ||
submoduleRepository.close(); | ||
} | ||
} | ||
} else { | ||
this.submodules = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should not be needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
} | ||
|
||
} catch (final IOException e) { | ||
throw new GitChangelogRepositoryException( | ||
"Could not use GIT repo in " + repo.getAbsolutePath(), e); | ||
|
@@ -89,6 +105,11 @@ public void close() throws IOException { | |
LOG.error(e.getMessage(), e); | ||
} | ||
} | ||
if (submodules != null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
for (Map.Entry<String, GitRepo> submodule : submodules.entrySet()) { | ||
submodule.getValue().close(); | ||
} | ||
} | ||
} | ||
|
||
public ObjectId getCommit(final String fromCommit) throws GitChangelogRepositoryException { | ||
|
@@ -142,6 +163,45 @@ public ObjectId getRef(final String fromRef) throws GitChangelogRepositoryExcept | |
throw new GitChangelogRepositoryException(fromRef + " not found in:\n" + toString()); | ||
} | ||
|
||
public boolean hasSubmodules() { | ||
return submodules != null && submodules.size() > 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This null-check is not needed, if changing to empty map as default value. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
} | ||
|
||
public GitRepo getSubmodule(String submodulePath) { | ||
return submodules.getOrDefault(submodulePath, null); | ||
} | ||
|
||
public String getDiff(String commitHash) { | ||
RevCommit commit = null; | ||
RevCommit prevCommit = null; | ||
|
||
try { | ||
commit = this.revWalk.parseCommit(getCommit(commitHash)); | ||
prevCommit = commit.getParentCount() > 0 ? commit.getParent(0) : null; | ||
} catch (GitChangelogRepositoryException | IOException e) { | ||
e.printStackTrace(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. throw GitChangelogRepositoryException There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
} | ||
|
||
OutputStream outputStream = new ByteArrayOutputStream(); | ||
DiffFormatter diffFormatter = new DiffFormatter(outputStream); | ||
diffFormatter.setRepository(this.repository); | ||
diffFormatter.setAbbreviationLength(10); | ||
|
||
try { | ||
for (DiffEntry entry : diffFormatter.scan(prevCommit, commit)) { | ||
diffFormatter.format(diffFormatter.toFileHeader(entry)); | ||
} | ||
} catch (IOException e) { | ||
e.printStackTrace(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. throw GitChangelogRepositoryException There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
} | ||
|
||
return outputStream.toString(); | ||
} | ||
|
||
public String getDirectory() { | ||
return this.repository.getDirectory().getAbsolutePath(); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
final StringBuilder sb = new StringBuilder(); | ||
|
@@ -151,6 +211,21 @@ public String toString() { | |
return "Repo: " + this.repository + "\n" + sb.toString(); | ||
} | ||
|
||
private CanonicalTreeParser getTreeParser(RevCommit commit) { | ||
RevTree revTree = commit.getTree(); | ||
if (revTree == null) { | ||
return null; | ||
} | ||
ObjectId treeId = commit.getTree().getId(); | ||
ObjectReader reader = this.repository.newObjectReader(); | ||
try { | ||
return new CanonicalTreeParser(null, reader, treeId); | ||
} catch (IOException e) { | ||
e.printStackTrace(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. throw GitChangelogRepositoryException There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
} | ||
return null; | ||
} | ||
|
||
private boolean addCommitToCurrentTag( | ||
final Map<String, Set<GitCommit>> commitsPerTagName, | ||
final String currentTagName, | ||
|
@@ -291,6 +366,10 @@ private List<GitTag> gitTags( | |
final RevCommit from = this.revWalk.lookupCommit(fromObjectId); | ||
final RevCommit to = this.revWalk.lookupCommit(toObjectId); | ||
|
||
if (from == to) { | ||
return newArrayList(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there is a tag on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generally speaking - yes, but it looks like we are only interested in the diff between the two commits in this case? |
||
} | ||
|
||
this.commitsToInclude = getDiffingCommits(from, to); | ||
|
||
final List<Ref> tagList = tagsBetweenFromAndTo(from, to); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package se.bjurr.gitchangelog.internal.git; | ||
|
||
import static org.slf4j.LoggerFactory.getLogger; | ||
|
||
import com.google.common.base.Optional; | ||
import java.util.*; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
import org.slf4j.Logger; | ||
import se.bjurr.gitchangelog.api.GitChangelogApi; | ||
import se.bjurr.gitchangelog.api.exceptions.GitChangelogRepositoryException; | ||
import se.bjurr.gitchangelog.api.model.Changelog; | ||
import se.bjurr.gitchangelog.internal.git.model.GitCommit; | ||
import se.bjurr.gitchangelog.internal.settings.Settings; | ||
|
||
public class GitSubmoduleParser { | ||
private static final Logger LOG = getLogger(GitSubmoduleParser.class); | ||
|
||
public GitSubmoduleParser() {} | ||
|
||
public List<Changelog> parseForSubmodules( | ||
final GitChangelogApi gitChangelogApi, | ||
final boolean useIntegrationIfConfigured, | ||
final GitRepo gitRepo, | ||
final List<GitCommit> commits) { | ||
|
||
List<Changelog> submodules = new ArrayList<>(); | ||
Map<String, SubmoduleEntry> submoduleEntries = new TreeMap<>(); | ||
Pattern submoduleNamePattern = | ||
Pattern.compile( | ||
"(?m)^\\+{3} b/([\\w/\\s-]+)($\\n@.+)?\\n-Subproject commit (\\w+)$\\n\\+Subproject commit (\\w+)$"); | ||
|
||
Settings settings = gitChangelogApi.getSettings(); | ||
|
||
Optional<String> cachedFromCommit = settings.getFromCommit(); | ||
Optional<String> cachedToCommit = settings.getToCommit(); | ||
Optional<String> cachedFromRef = settings.getFromRef(); | ||
Optional<String> cachedToRef = settings.getToRef(); | ||
String cachedFromRepo = settings.getFromRepo(); | ||
|
||
for (GitCommit commit : commits) { | ||
String diff = gitRepo.getDiff(commit.getHash()); | ||
Matcher submoduleMatch = submoduleNamePattern.matcher(diff); | ||
while (submoduleMatch.find()) { | ||
String submoduleName = submoduleMatch.group(1); | ||
String previousSubmoduleHash = submoduleMatch.group(3); | ||
String currentSubmoduleHash = submoduleMatch.group(4); | ||
GitRepo submodule = gitRepo.getSubmodule(submoduleName); | ||
|
||
if (submodule == null) { | ||
continue; | ||
} | ||
|
||
SubmoduleEntry submoduleEntry = | ||
new SubmoduleEntry( | ||
submoduleName, previousSubmoduleHash, currentSubmoduleHash, submodule); | ||
|
||
if (!submoduleEntries.containsKey(submoduleName)) { | ||
submoduleEntries.put(submoduleName, submoduleEntry); | ||
} | ||
|
||
SubmoduleEntry existingEntry = submoduleEntries.getOrDefault(submoduleName, submoduleEntry); | ||
existingEntry.previousSubmoduleHash = submoduleEntry.previousSubmoduleHash; | ||
} | ||
} | ||
|
||
for (Map.Entry<String, SubmoduleEntry> submoduleEntry : submoduleEntries.entrySet()) { | ||
settings.setFromCommit(submoduleEntry.getValue().previousSubmoduleHash); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I dont like changing the settings object, and later change it back. I would rather like a new instance created and used. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Switched to cloning the settings object via serialization utils. |
||
settings.setToCommit(submoduleEntry.getValue().currentSubmoduleHash); | ||
settings.setFromRef(null); | ||
settings.setToRef(null); | ||
settings.setFromRepo(submoduleEntry.getValue().gitRepo.getDirectory()); | ||
|
||
try { | ||
submodules.add( | ||
GitChangelogApi.gitChangelogApiBuilder() | ||
.withSettings(settings) | ||
.getChangelog(useIntegrationIfConfigured)); | ||
} catch (GitChangelogRepositoryException e) { | ||
e.printStackTrace(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. throw GitChangelogRepositoryException There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
} | ||
} | ||
|
||
settings.setFromCommit(cachedFromCommit.orNull()); | ||
settings.setToCommit(cachedToCommit.orNull()); | ||
settings.setFromRef(cachedFromRef.orNull()); | ||
settings.setToRef(cachedToRef.orNull()); | ||
settings.setFromRepo(cachedFromRepo); | ||
|
||
return submodules; | ||
} | ||
|
||
private class SubmoduleEntry { | ||
public final String name; | ||
public String previousSubmoduleHash; | ||
public final String currentSubmoduleHash; | ||
public final GitRepo gitRepo; | ||
|
||
public SubmoduleEntry( | ||
final String name, | ||
final String previousSubmoduleHash, | ||
final String currentSubmoduleHash, | ||
final GitRepo gitRepo) { | ||
this.name = name; | ||
this.previousSubmoduleHash = previousSubmoduleHash; | ||
this.currentSubmoduleHash = currentSubmoduleHash; | ||
this.gitRepo = gitRepo; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont like to expose Settings in the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a better way to provide an existing Settings object?