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
52 changes: 47 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,42 @@ last tagged version.
The `k16.kmono.version/resolve-package-changes-since` can be used to find commits that have changed the package since a
given git revision.

#### Ignoring Changes by File Pattern

When using change detection (`--changed` or `--changed-since`), you can filter out changes to specific files using
`--ignore-changes`. This accepts a comma-separated list of regexp patterns matched against file paths relative to the
package root. If **all** changed files in a package match the ignore patterns, that package is treated as unchanged.

```bash
# Ignore markdown-only changes
kmono run --changed --ignore-changes ".*\.md$" -M :test

# Ignore multiple patterns (comma-separated)
kmono run --changed --ignore-changes ".*\.md$,LICENSE,^docs/" -M :build

# Works with --changed-since too
kmono run --changed-since main --ignore-changes ".*\.md$" -M :test
```

This is useful in CI/CD workflows where documentation-only changes shouldn't trigger builds or tests.

Patterns can also be configured at the workspace level in `deps.edn` (see [Workspace Configuration](#workspace-configuration)):

```clojure
{:kmono/workspace {:group com.example
:ignore-changes [".*\\.md$" "^docs/" "LICENSE"]}}
```

Individual packages can override the workspace-level patterns by setting `:ignore-changes` in their `:kmono/package`
config (see [Package Configuration](#package-configuration)):

```clojure
;; packages/my-lib/deps.edn
{:kmono/package {:ignore-changes [".*\\.md$" "^test-fixtures/"]}}
```

The precedence order is: CLI `--ignore-changes` > per-package `:ignore-changes` > workspace `:ignore-changes`.

These tools and others can be used to build sophisticated build and release pipelines for kmono workspaces.

## API Documentation
Expand Down Expand Up @@ -263,6 +299,7 @@ configuration accepts the following properties:
| `:repl-aliases` | `[keyword?]` | `nil` | A set of `deps` aliases to include when running `kmono repl` |
| `:aliases` | `[keyword?]` | `nil` | A set of `deps` aliases to include for all kmono workspace commands by default |
| `:package-aliases` | `[keyword?]` | `nil` | A set of namespaced [alias globs](#alias-globs) that describe the aliases of packages within the workspace to include in the classpath |
| `:ignore-changes` | `[string?]` | `nil` | A list of regexp patterns for file paths to ignore when determining package changes (used with `--changed` / `--changed-since`) |

Example:

Expand All @@ -271,7 +308,9 @@ Example:
{:kmono/workspace {:group com.example
:packages #{"./(packages|modules)/**"}
;; Include any `:test` aliases from all (`*`) packages in the workspace
:package-aliases [:*/test]}
:package-aliases [:*/test]
;; Ignore doc-only changes when using --changed
:ignore-changes [".*\\.md$" "LICENSE"]}

:paths ["src" "resources"]

Expand All @@ -285,9 +324,10 @@ Packages in the workspace can optionally provide their own configuration metadat

| Field | Type | Default | Description |
| ----------- | -------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `:group` | `symbol?` | `nil` | The mvn group to use for this package. If not specified, the `:group` specified in the root `:kmono/workspace` configuration will be used. |
| `:name` | `string? \| symbol?` | `$dir` | The name of the package. If not set the name of the parent directory containing the packages' `deps.edn` file will be used as the package name |
| `:excluded` | `boolean?` | `false` | Whether or not this package is excluded from the project workspace |
| `:group` | `symbol?` | `nil` | The mvn group to use for this package. If not specified, the `:group` specified in the root `:kmono/workspace` configuration will be used. |
| `:name` | `string? \| symbol?` | `$dir` | The name of the package. If not set the name of the parent directory containing the packages' `deps.edn` file will be used as the package name |
| `:excluded` | `boolean?` | `false` | Whether or not this package is excluded from the project workspace |
| `:ignore-changes` | `[string?]` | `nil` | Override the workspace-level `:ignore-changes` patterns for this package |

Example

Expand All @@ -296,7 +336,9 @@ Example
{:kmono/package {;; Maven artifacts group
:group com.example
;; Override the default package name
:name example-lib}
:name example-lib
;; Override workspace-level ignore-changes for this package
:ignore-changes [".*\\.md$" "^test-fixtures/"]}

:paths ["src"]
:deps {...}
Expand Down
10 changes: 7 additions & 3 deletions packages/kmono-cli/src/k16/kmono/cli/commands/exec.clj
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,21 @@
include-dependents run-in-order concurrency]
:as opts}
args]
(let [{:keys [root packages]} (common.context/load-context opts)
(let [{:keys [root packages config]} (common.context/load-context opts)
ignore-changes (:ignore-changes config)
change-opts (when ignore-changes
{:ignore-changes ignore-changes})

packages
(cond-> packages
(or changed skip-unchanged)
(->> (kmono.version/resolve-package-versions root)
(kmono.version/resolve-package-changes root)
(kmono.version/resolve-package-changes root change-opts)
(core.graph/filter-by kmono.version/package-changed?
{:include-dependents include-dependents}))

changed-since
(->> (kmono.version/resolve-package-changes-since root changed-since)
(->> (kmono.version/resolve-package-changes-since root changed-since change-opts)
(core.graph/filter-by kmono.version/package-changed?
{:include-dependents include-dependents}))

Expand Down Expand Up @@ -65,6 +68,7 @@
:skip-unchanged opts/skip-unchanged-opt
:changed opts/changed-opt
:changed-since opts/changed-since-opt
:ignore-changes opts/ignore-changes-opt
:filter opts/package-filter-opt
:include-dependents include-dependents
:concurrency opts/concurrency-opt}
Expand Down
10 changes: 7 additions & 3 deletions packages/kmono-cli/src/k16/kmono/cli/commands/query.clj
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,19 @@
(log/error "Either --with-changes or --with-changes-since is required when setting --filter-unchanged")
(System/exit 1))

{:keys [root packages]} (common.context/load-context opts)
{:keys [root packages config]} (common.context/load-context opts)
ignore-changes (:ignore-changes config)
change-opts (when ignore-changes
{:ignore-changes ignore-changes})
packages (cond->> packages
with-versions
(kmono.version/resolve-package-versions root)

with-changes
(kmono.version/resolve-package-changes root)
(kmono.version/resolve-package-changes root change-opts)

with-changes-since
(kmono.version/resolve-package-changes-since root with-changes-since)
(kmono.version/resolve-package-changes-since root with-changes-since change-opts)

filter-unchanged
(core.graph/filter-by kmono.version/package-changed?
Expand Down Expand Up @@ -107,6 +110,7 @@
:options {:with-versions with-versions
:with-changes-since with-changes-since
:with-changes with-changes
:ignore-changes opts/ignore-changes-opt
:filter-unchanged filter-unchanged
:include-dependents include-dependents
:filter opts/package-filter-opt
Expand Down
10 changes: 7 additions & 3 deletions packages/kmono-cli/src/k16/kmono/cli/commands/run.clj
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,23 @@
(set! *warn-on-reflection* true)

(defn- run-command [{:keys [M T X skip-unchanged changed changed-since] :as opts} args]
(let [{:keys [root packages]} (common.context/load-context opts)
(let [{:keys [root packages config]} (common.context/load-context opts)
filter' (:filter opts)
ignore-changes (:ignore-changes config)
change-opts (when ignore-changes
{:ignore-changes ignore-changes})
packages (cond-> packages
filter'
(->> (core.graph/filter-by (core.packages/name-matches? filter')))

(or changed skip-unchanged)
(->> (kmono.version/resolve-package-versions root)
(kmono.version/resolve-package-changes root)
(kmono.version/resolve-package-changes root change-opts)
(core.graph/filter-by kmono.version/package-changed?
{:include-dependents true}))

changed-since
(->> (kmono.version/resolve-package-changes-since root changed-since)
(->> (kmono.version/resolve-package-changes-since root changed-since change-opts)
(core.graph/filter-by kmono.version/package-changed?
{:include-dependents true})))

Expand Down Expand Up @@ -68,6 +71,7 @@
:skip-unchanged opts/skip-unchanged-opt
:changed opts/changed-opt
:changed-since opts/changed-since-opt
:ignore-changes opts/ignore-changes-opt
:concurrency opts/concurrency-opt
:filter opts/package-filter-opt

Expand Down
5 changes: 3 additions & 2 deletions packages/kmono-cli/src/k16/kmono/cli/common/config.clj
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
(ns k16.kmono.cli.common.config)

(defn merge-workspace-config
[workspace-config {:keys [package-aliases aliases]}]
[workspace-config {:keys [package-aliases aliases ignore-changes]}]
(cond-> workspace-config
package-aliases (assoc :package-aliases package-aliases)
aliases (update :aliases into aliases)))
aliases (update :aliases into aliases)
ignore-changes (assoc :ignore-changes ignore-changes)))
5 changes: 5 additions & 0 deletions packages/kmono-cli/src/k16/kmono/cli/common/opts.clj
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,8 @@
{:desc "Filter packages by those that have changed since the specified git <rev>"
:ref "<rev>"
:coerce :string})

(def ignore-changes-opt
{:desc "Comma-separated regexp patterns of file paths to ignore when determining changes"
:coerce :string
:parse-fn (fn [value] (str/split value #","))})
4 changes: 2 additions & 2 deletions packages/kmono-core/src/k16/kmono/core/packages.clj
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
(not (fs/same-file? project-root package-path)))
(let [relative-path (str (fs/relativize project-root package-path))
package (merge {:name (symbol (fs/file-name package-path))}
(select-keys workspace-config [:group])
(select-keys config [:group :name :deps-edn])
(select-keys workspace-config [:group :ignore-changes])
(select-keys config [:group :name :deps-edn :ignore-changes])
{:absolute-path (str package-path)
:relative-path relative-path
:depends-on #{}})
Expand Down
13 changes: 10 additions & 3 deletions packages/kmono-core/src/k16/kmono/core/schema.clj
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,19 @@
[:aliases {:optional true}
[:vector :keyword]]
[:package-aliases {:optional true}
[:vector :keyword]]])
[:vector :keyword]]

[:ignore-changes {:optional true}
[:sequential :string]]])

(def ?PackageConfig
[:map
[:group {:optional true} :symbol]
[:name {:optional true}
[:maybe [:or :string :symbol]]]
[:excluded {:optional true} :boolean]])
[:excluded {:optional true} :boolean]
[:ignore-changes {:optional true}
[:sequential :string]]])

(def ?Coordinate
[:map
Expand Down Expand Up @@ -56,7 +61,9 @@
[:dependents [:set :symbol]]

[:absolute-path :string]
[:relative-path :string]])
[:relative-path :string]

[:ignore-changes {:optional true} [:sequential :string]]])

(def ?PackageMap
[:map-of :symbol ?Package])
30 changes: 29 additions & 1 deletion packages/kmono-git/src/k16/kmono/git/files.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
(:import
[java.io ByteArrayOutputStream]
[org.eclipse.jgit.diff DiffEntry DiffFormatter]
[org.eclipse.jgit.lib Constants Repository]))
[org.eclipse.jgit.lib Constants Repository]
[org.eclipse.jgit.revwalk RevCommit RevWalk]))

(set! *warn-on-reflection* true)

Expand Down Expand Up @@ -41,3 +42,30 @@
(or (nil? subdir)
(str/starts-with? entry subdir)))))
results))))))

(defn find-commit-changed-files
"Return file paths changed by a specific commit. If `subdir` is provided, only
include files within that subdir."
[^String repo-path {:keys [sha subdir]}]
(git/with-repo [repo repo-path]
(let [commit-id (Repository/.resolve repo sha)
subdir-prefix (when subdir (str subdir "/"))]
(with-open [walk (RevWalk. repo)]
(let [commit (RevWalk/.parseCommit walk commit-id)
parent-tree (when (pos? (RevCommit/.getParentCount commit))
(let [parent (RevWalk/.parseCommit walk
(aget (RevCommit/.getParents commit) 0))]
(RevCommit/.getTree parent)))
commit-tree (RevCommit/.getTree commit)]
(with-open [out (ByteArrayOutputStream.)
df (DiffFormatter. out)]
(DiffFormatter/.setRepository df repo)
(let [diffs (DiffFormatter/.scan df parent-tree commit-tree)]
(into []
(comp
(map #(DiffEntry/.getNewPath %))
(remove #(= % DiffEntry/DEV_NULL))
(filter (fn [path]
(or (nil? subdir-prefix)
(str/starts-with? path subdir-prefix)))))
diffs))))))))
68 changes: 53 additions & 15 deletions packages/kmono-version/src/k16/kmono/version.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
[k16.kmono.core.thread :as core.thread]
[k16.kmono.git :as git]
[k16.kmono.git.commit :as git.commit]
[k16.kmono.git.files :as git.files]
[k16.kmono.git.tags :as git.tags]
[k16.kmono.version.semver :as semver]))

Expand Down Expand Up @@ -68,15 +69,38 @@
(transient {}))
persistent!)))

(defn- file-ignored?
"Returns true if the file path matches any of the ignore patterns."
[patterns file-path]
(boolean (some #(re-find (re-pattern %) file-path) patterns)))

(defn- commit-has-meaningful-changes?
"Returns true if the commit has changed files that don't all match ignore
patterns. Files are matched relative to the package root."
[project-root pkg ignore-changes commit]
(let [files (git.files/find-commit-changed-files
project-root {:sha (:sha commit)
:subdir (:relative-path pkg)})
pkg-prefix (str (:relative-path pkg) "/")
relative-files (map #(str/replace-first % pkg-prefix "") files)]
(boolean (seq (remove #(file-ignored? ignore-changes %) relative-files)))))

(defn- -resolve-package-changes-since
[project-root packages rev-fn]
[project-root packages rev-fn {:keys [ignore-changes]}]
(git/with-repo [_ project-root]
(into {}
(core.thread/batch
(fn find-commits [[pkg-name pkg]]
(let [commits (git.commit/find-commits-since
project-root {:ref (rev-fn pkg)
(let [ref (rev-fn pkg)
commits (git.commit/find-commits-since
project-root {:ref ref
:subdir (:relative-path pkg)})
pkg-ignore-changes (or (:ignore-changes pkg) ignore-changes)
commits (if (and (seq pkg-ignore-changes) (seq commits) ref)
(filterv #(commit-has-meaningful-changes?
project-root pkg pkg-ignore-changes %)
commits)
commits)
pkg (assoc pkg :commits (vec commits))]
[pkg-name pkg]))
32)
Expand All @@ -90,25 +114,39 @@
and version. See `k16.kmono.version/resolve-package-versions` for a
description on how this tag is expected to be formatted.

Any commits found will be appended to the packages `:commits` key."
Any commits found will be appended to the packages `:commits` key.

An optional `opts` map may be provided with `:ignore-changes` - a sequence of
regexp patterns. Commits where all changed files match these patterns are
filtered out."
{:malli/schema [:-> :string core.schema/?PackageMap core.schema/?PackageMap]}
[project-root packages]
(-resolve-package-changes-since project-root
packages
(fn [pkg]
(when (:version pkg)
(create-package-version-tag pkg)))))
([project-root packages]
(resolve-package-changes project-root nil packages))
([project-root opts packages]
(-resolve-package-changes-since project-root
packages
(fn [pkg]
(when (:version pkg)
(create-package-version-tag pkg)))
opts)))

(defn resolve-package-changes-since
"For each package try find all commits that modified files in the package
subdirectory since the given rev.

Any commits found will be appended to the packages `:commits` key."
Any commits found will be appended to the packages `:commits` key.

An optional `opts` map may be provided with `:ignore-changes` - a sequence of
regexp patterns. Commits where all changed files match these patterns are
filtered out."
{:malli/schema [:-> :string :string core.schema/?PackageMap core.schema/?PackageMap]}
[project-root rev packages]
(-resolve-package-changes-since project-root
packages
(constantly rev)))
([project-root rev packages]
(resolve-package-changes-since project-root rev nil packages))
([project-root rev opts packages]
(-resolve-package-changes-since project-root
packages
(constantly rev)
opts)))

(defn package-changed?
"A filter function designed to be used with `k16.kmono.core.graph/filter-by`.
Expand Down
Loading
Loading