Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/main/kotlin/git/semver/plugin/scm/GitProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -161,19 +161,35 @@ internal class GitProvider(internal val settings: SemverSettings) {
private fun computeRelevantShas(repo: Repository, head: ObjectId, tags: Map<String, List<Tag>>): Set<String>? {
if (settings.pathFilter.isEmpty()) return null

val versionTagIds = getVersionTagObjectIds(tags)
val versionTagIds = getVersionTagObjectIds(repo, head, tags)
val releaseMsgIds = findReleaseMessageCommits(repo, head, versionTagIds)
val boundaryIds = versionTagIds + releaseMsgIds

return filterCommitsByPath(repo, head, boundaryIds)
}

private fun getVersionTagObjectIds(tags: Map<String, List<Tag>>): List<ObjectId> {
return tags.entries
private fun getVersionTagObjectIds(repo: Repository, head: ObjectId, tags: Map<String, List<Tag>>): List<ObjectId> {
val candidateIds = tags.entries
.filter { (_, tagList) -> tagList.any { MutableSemVersion.tryParse(it) != null } }
.mapNotNull { (sha, _) ->
try { ObjectId.fromString(sha) } catch (_: Exception) { null }
}
// Only tags reachable from `head` may bound the walk below.
return filterAncestorsOf(repo, head, candidateIds)
}

private fun filterAncestorsOf(repo: Repository, head: ObjectId, candidateIds: List<ObjectId>): List<ObjectId> {
if (candidateIds.isEmpty()) return candidateIds
return RevWalk(repo).use { walk ->
val headCommit = walk.parseCommit(head)
candidateIds.filter { oid ->
try {
walk.isMergedInto(walk.parseCommit(oid), headCommit)
} catch (_: Exception) {
false
}
}
}
}

private fun findReleaseMessageCommits(repo: Repository, head: ObjectId, boundaryIds: List<ObjectId>): List<ObjectId> {
Expand Down
57 changes: 55 additions & 2 deletions src/test/kotlin/git/semver/plugin/scm/GitProviderPathFilterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package git.semver.plugin.scm
import git.semver.plugin.semver.SemverSettings
import org.assertj.core.api.Assertions.assertThat
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevCommit
import org.junit.jupiter.api.io.TempDir
import java.io.File
import java.nio.file.Files
Expand All @@ -21,12 +22,12 @@ class GitProviderPathFilterTest {
return path.toFile()
}

private fun commitFile(git: Git, relPath: String, message: String) {
private fun commitFile(git: Git, relPath: String, message: String): RevCommit {
val file = File(git.repository.workTree, relPath)
file.parentFile?.mkdirs()
file.writeText(message)
git.add().addFilepattern(relPath).call()
git.commit().setMessage(message).call()
return git.commit().setMessage(message).call()
}

@Test
Expand Down Expand Up @@ -168,4 +169,56 @@ class GitProviderPathFilterTest {
assertThat(messages).doesNotContain("feat: add service outside lib")
}
}

@Test
fun `a release tag on a descendant commit does not hide pending changes on an earlier commit`() {
val gitDir = getGitDir("pathFilterDescendantTag")
val provider = GitProvider(SemverSettings().apply {
pathFilter = "lib"
releaseTagNameFormat = "v%s"
})

Git.init().setDirectory(gitDir).call().use { git ->
commitFile(git, "README.md", "Initial commit")
commitFile(git, "lib/A.kt", "release: 1.0.0")
git.tag().setName("v1.0.0").call()

// Pending change, not yet released.
val pendingCommit = commitFile(git, "lib/B.kt", "fix: patch pending in lib")

// A later commit lands and gets released first (descendant of pendingCommit).
commitFile(git, "lib/C.kt", "feat: a later feature also in lib")
git.tag().setName("v1.1.0").call()

// Compute version as of the earlier, still-pending commit.
git.checkout().setName(pendingCommit.name).call()

assertThat(provider.semVersion(git).toVersionString()).isEqualTo("1.0.1-SNAPSHOT")
}
}

@Test
fun `changelog for an earlier commit is not hidden by a later release tag`() {
val gitDir = getGitDir("pathFilterDescendantTagChangelog")
val provider = GitProvider(SemverSettings().apply {
pathFilter = "lib"
releaseTagNameFormat = "v%s"
})

Git.init().setDirectory(gitDir).call().use { git ->
commitFile(git, "README.md", "Initial commit")
commitFile(git, "lib/A.kt", "release: 1.0.0")
git.tag().setName("v1.0.0").call()

val pendingCommit = commitFile(git, "lib/B.kt", "fix: patch pending in lib")

commitFile(git, "lib/C.kt", "feat: a later feature also in lib")
git.tag().setName("v1.1.0").call()

git.checkout().setName(pendingCommit.name).call()

val messages = provider.changeLog(git).map { it.text }
assertThat(messages).contains("fix: patch pending in lib")
}
}
}
Loading