Skip to content

Commit 0dc3ff0

Browse files
committed
Compute artifact differences
1 parent 439bde5 commit 0dc3ff0

File tree

7 files changed

+243
-57
lines changed

7 files changed

+243
-57
lines changed

src/main/resources/messages.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,5 @@ lock.status.full.dependencies.changed.multiple=\ \ {0} dependencies changed:\n{1
5151

5252
# lockfile full status - artifacts ============================================
5353
lock.status.full.artifacts.changed.none=
54-
lock.status.full.artifacts.changed.singular=\ \ 1 dependency artifacts changed
55-
lock.status.full.artifacts.changed.multiple=\ \ {0} dependency artifacts changed
54+
lock.status.full.artifacts.changed.singular=\ \ 1 dependency artifacts changed:\n{1}
55+
lock.status.full.artifacts.changed.multiple=\ \ {0} dependency artifacts changed:\n{1}

src/main/scala/software/purpledragon/sbt/lock/model/ChangedDependency.scala

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,70 @@
1717
package software.purpledragon.sbt.lock.model
1818

1919
import scala.collection.SortedSet
20+
import scala.math.Ordered.orderingToOrdered
2021

2122
final case class ChangedDependency(
2223
org: String,
2324
name: String,
2425
oldVersion: String,
2526
newVersion: String,
26-
oldArtifacts: SortedSet[ResolvedArtifact],
27-
newArtifacts: SortedSet[ResolvedArtifact],
2827
oldConfigurations: SortedSet[String],
29-
newConfigurations: SortedSet[String]) {
28+
newConfigurations: SortedSet[String],
29+
addedArtifacts: SortedSet[ResolvedArtifact],
30+
removedArtifacts: SortedSet[ResolvedArtifact],
31+
changedArtifacts: SortedSet[ChangedArtifact]) {
3032

3133
def versionChanged: Boolean = oldVersion != newVersion
3234
def configurationsChanged: Boolean = oldConfigurations != newConfigurations
35+
def artifactsChanged: Boolean = addedArtifacts.nonEmpty || removedArtifacts.nonEmpty || changedArtifacts.nonEmpty
36+
}
37+
38+
object ChangedDependency {
39+
def apply(
40+
org: String,
41+
name: String,
42+
oldVersion: String,
43+
newVersion: String,
44+
oldConfigurations: SortedSet[String],
45+
newConfigurations: SortedSet[String],
46+
oldArtifacts: SortedSet[ResolvedArtifact],
47+
newArtifacts: SortedSet[ResolvedArtifact]): ChangedDependency = {
48+
49+
val oldArtifactsByName = oldArtifacts.map(a => a.name -> a).toMap
50+
val newArtifactsByName = newArtifacts.map(a => a.name -> a).toMap
51+
52+
val addedArtifacts = (newArtifactsByName.keySet -- oldArtifactsByName.keySet).map(newArtifactsByName.apply)
53+
val removedArtifacts = (oldArtifactsByName.keySet -- newArtifactsByName.keySet).map(oldArtifactsByName.apply)
54+
55+
val changedArtifacts =
56+
oldArtifactsByName.keySet.intersect(newArtifactsByName.keySet).foldLeft(Seq.empty[ChangedArtifact]) {
57+
(changes, name) =>
58+
val oldHash = oldArtifactsByName(name).hash
59+
val newHash = newArtifactsByName(name).hash
60+
61+
if (oldHash != newHash) {
62+
changes :+ ChangedArtifact(name, oldHash, newHash)
63+
} else {
64+
changes
65+
}
66+
}
67+
68+
ChangedDependency(
69+
org,
70+
name,
71+
oldVersion,
72+
newVersion,
73+
oldConfigurations,
74+
newConfigurations,
75+
addedArtifacts.to[SortedSet],
76+
removedArtifacts.to[SortedSet],
77+
changedArtifacts.to[SortedSet]
78+
)
79+
}
80+
}
81+
82+
final case class ChangedArtifact(name: String, oldHash: String, newHash: String) extends Ordered[ChangedArtifact] {
83+
override def compare(that: ChangedArtifact): Int = {
84+
(name, oldHash, newHash) compare (that.name, that.oldHash, that.newHash)
85+
}
3386
}

src/main/scala/software/purpledragon/sbt/lock/model/DependencyLockFile.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ final case class DependencyLockFile(
8080
depref.name,
8181
ourDep.version,
8282
otherDep.version,
83-
ourDep.artifacts,
84-
otherDep.artifacts,
8583
ourDep.configurations,
86-
otherDep.configurations)
84+
otherDep.configurations,
85+
ourDep.artifacts,
86+
otherDep.artifacts)
8787
} else {
8888
changes
8989
}

src/main/scala/software/purpledragon/sbt/lock/model/LockFileStatus.scala

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,45 @@ final case class LockFileDiffers(
167167
dumpChanges(otherChanged))
168168
}
169169

170+
def dumpArtifactChanges(changes: Seq[ChangedDependency]): String = {
171+
val changesBuilder = new mutable.StringBuilder()
172+
173+
changes foreach { change =>
174+
changesBuilder ++= " "
175+
changesBuilder ++= change.org
176+
changesBuilder ++= ":"
177+
changesBuilder ++= change.name
178+
changesBuilder ++= " ("
179+
change.oldConfigurations.addString(changesBuilder, ",")
180+
changesBuilder ++= ") "
181+
changesBuilder ++= change.oldVersion
182+
changesBuilder ++= ":\n"
183+
184+
val table = new TableFormatter(None, prefix = " ", stripTrailingNewline = false)
185+
186+
change.addedArtifacts foreach { added =>
187+
table.addRow("(added)", added.name, added.hash)
188+
}
189+
190+
change.removedArtifacts foreach { removed =>
191+
table.addRow("(removed)", removed.name, removed.hash)
192+
}
193+
194+
change.changedArtifacts foreach { changed =>
195+
table.addRow("(changed)", changed.name, changed.oldHash, s"-> ${changed.newHash}")
196+
}
197+
198+
changesBuilder ++= table.toString()
199+
}
200+
201+
changesBuilder.toString()
202+
}
203+
170204
if (artifactChanged.nonEmpty) {
171-
errors += MessageUtil.formatPlural("lock.status.full.artifacts.changed", artifactChanged.size)
205+
errors += MessageUtil.formatPlural(
206+
"lock.status.full.artifacts.changed",
207+
artifactChanged.size,
208+
dumpArtifactChanges(artifactChanged))
172209
}
173210

174211
MessageUtil.formatMessage("lock.status.failed.long", errors.mkString("\n"))

src/main/scala/software/purpledragon/sbt/lock/model/ResolvedArtifact.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ package software.purpledragon.sbt.lock.model
1818

1919
import java.io.File
2020

21-
import sbt.{File, Hash}
21+
import sbt.Hash
2222
import sbt.librarymanagement.Artifact
23-
import software.purpledragon.sbt.lock.DependencyUtils.hashFile
2423

2524
import scala.collection.mutable
2625
import scala.math.Ordered.orderingToOrdered

src/test/scala/software/purpledragon/sbt/lock/model/DependencyLockFileSpec.scala

Lines changed: 112 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,18 @@ import org.scalatest.matchers.should.Matchers
2424
import scala.collection.SortedSet
2525

2626
class DependencyLockFileSpec extends AnyFlatSpec with Matchers {
27+
private val Dependency1Artifact = ResolvedArtifact("package-1.jar", "hash-1")
28+
private val Dependency1 =
29+
ResolvedDependency("com.example", "package-1", "1.0.0", SortedSet(Dependency1Artifact), SortedSet("test-1"))
30+
private val Dependency2 =
31+
ResolvedDependency("com.example", "package-2", "1.2.0", SortedSet.empty, SortedSet("test-2"))
32+
2733
private val EmptyLockFile = DependencyLockFile(1, Instant.now(), Nil, Nil)
2834
private val TestLockFile = DependencyLockFile(
2935
1,
3036
Instant.now(),
3137
Seq("test-1", "test-2"),
32-
Seq(
33-
ResolvedDependency(
34-
"com.example",
35-
"package-1",
36-
"1.0.0",
37-
SortedSet(ResolvedArtifact("package-1.jar", "hash-1")),
38-
SortedSet("test-1")),
39-
ResolvedDependency("com.example", "package-2", "1.2.0", SortedSet.empty, SortedSet("test-2"))
40-
)
38+
Seq(Dependency1, Dependency2)
4139
)
4240

4341
"findChanges" should "return LockFileMatches for identical lockfiles" in {
@@ -78,12 +76,13 @@ class DependencyLockFileSpec extends AnyFlatSpec with Matchers {
7876
}
7977

8078
it should "return LockFileDiffers if dependency added" in {
81-
val newDependency = ResolvedDependency(
82-
"com.example",
83-
"package-3",
84-
"3.0",
85-
SortedSet(ResolvedArtifact("package-3.jar", "hash-3")),
86-
SortedSet("test-1"))
79+
val newDependency =
80+
ResolvedDependency(
81+
"com.example",
82+
"package-3",
83+
"3.0",
84+
SortedSet(ResolvedArtifact("package-3.jar", "hash-3")),
85+
SortedSet("test-1"))
8786

8887
val left = TestLockFile
8988
val right = left.copy(dependencies = left.dependencies :+ newDependency)
@@ -101,12 +100,8 @@ class DependencyLockFileSpec extends AnyFlatSpec with Matchers {
101100
it should "return LockFileDiffers if dependency changed" in {
102101
val left = TestLockFile
103102
val right = left.copy(
104-
dependencies = left.dependencies.tail :+ ResolvedDependency(
105-
"com.example",
106-
"package-1",
107-
"2.0.0",
108-
SortedSet(ResolvedArtifact("package-1.jar", "hash-1a")),
109-
SortedSet("test-1", "test-2"))
103+
dependencies = left.dependencies.head.copy(version = "2.0.0", configurations = SortedSet("test-1", "test-2")) +:
104+
left.dependencies.tail
110105
)
111106

112107
left.findChanges(right) shouldBe LockFileDiffers(
@@ -120,10 +115,103 @@ class DependencyLockFileSpec extends AnyFlatSpec with Matchers {
120115
"package-1",
121116
"1.0.0",
122117
"2.0.0",
123-
SortedSet(ResolvedArtifact("package-1.jar", "hash-1")),
124-
SortedSet(ResolvedArtifact("package-1.jar", "hash-1a")),
125118
SortedSet("test-1"),
126-
SortedSet("test-1", "test-2")
119+
SortedSet("test-1", "test-2"),
120+
SortedSet.empty,
121+
SortedSet.empty,
122+
SortedSet.empty
123+
))
124+
)
125+
}
126+
127+
it should "return LockFileDiffers if artifact changed" in {
128+
val left = TestLockFile
129+
val right = left.copy(
130+
dependencies = left.dependencies map { dep =>
131+
dep.copy(artifacts = dep.artifacts map { art =>
132+
art.copy(hash = art.hash + "a")
133+
})
134+
}
135+
)
136+
137+
left.findChanges(right) shouldBe LockFileDiffers(
138+
Nil,
139+
Nil,
140+
Nil,
141+
Nil,
142+
Seq(
143+
ChangedDependency(
144+
"com.example",
145+
"package-1",
146+
"1.0.0",
147+
"1.0.0",
148+
SortedSet("test-1"),
149+
SortedSet("test-1"),
150+
SortedSet.empty,
151+
SortedSet.empty,
152+
SortedSet(ChangedArtifact("package-1.jar", "hash-1", "hash-1a"))
153+
))
154+
)
155+
}
156+
157+
it should "return LockFileDiffers if artifact added" in {
158+
val left = TestLockFile
159+
160+
val right = left.copy(
161+
dependencies = Seq(
162+
Dependency1.copy(
163+
artifacts = SortedSet(
164+
Dependency1Artifact,
165+
ResolvedArtifact("package-1a.jar", "hash-1a")
166+
)),
167+
Dependency2
168+
))
169+
170+
left.findChanges(right) shouldBe LockFileDiffers(
171+
Nil,
172+
Nil,
173+
Nil,
174+
Nil,
175+
Seq(
176+
ChangedDependency(
177+
"com.example",
178+
"package-1",
179+
"1.0.0",
180+
"1.0.0",
181+
SortedSet("test-1"),
182+
SortedSet("test-1"),
183+
SortedSet(ResolvedArtifact("package-1a.jar", "hash-1a")),
184+
SortedSet.empty,
185+
SortedSet.empty
186+
))
187+
)
188+
}
189+
190+
it should "return LockFileDiffers if artifact removed" in {
191+
val left = TestLockFile
192+
193+
val right = left.copy(
194+
dependencies = Seq(
195+
Dependency1.copy(artifacts = SortedSet.empty),
196+
Dependency2
197+
))
198+
199+
left.findChanges(right) shouldBe LockFileDiffers(
200+
Nil,
201+
Nil,
202+
Nil,
203+
Nil,
204+
Seq(
205+
ChangedDependency(
206+
"com.example",
207+
"package-1",
208+
"1.0.0",
209+
"1.0.0",
210+
SortedSet("test-1"),
211+
SortedSet("test-1"),
212+
SortedSet.empty,
213+
SortedSet(ResolvedArtifact("package-1.jar", "hash-1")),
214+
SortedSet.empty
127215
))
128216
)
129217
}

0 commit comments

Comments
 (0)