Skip to content

Commit e983321

Browse files
authored
Speed up 'make fmt' and 'make lint' by running it on changes only (#3021)
## Why Major speed when working on a branch (one go file changed in a small package): This branch - 'make fmt': ``` % hyperfine 'make fmt' Benchmark 1: make fmt Time (mean ± σ): 386.0 ms ± 98.0 ms [User: 178.4 ms, System: 239.8 ms] Range (min … max): 345.5 ms … 664.6 ms 10 runs Warning: The first benchmarking run for this command was significantly slower than the rest (664.6 ms). This could be caused by (filesystem) caches that were not filled until after the first run. You should consider using the '--warmup' option to fill those caches before the actual benchmark. Alternatively, use the '--prepare' option to clear the caches before each timing run. ``` main branch: ``` % git checkout main M libs/git/config.go Switched to branch 'main' Your branch is up to date with 'origin/main'. ~/work/cli-side % hyperfine 'make fmt' Benchmark 1: make fmt Time (mean ± σ): 11.304 s ± 0.568 s [User: 4.183 s, System: 3.622 s] Range (min … max): 10.502 s … 12.154 s 10 runs ```
1 parent 8cf9052 commit e983321

File tree

3 files changed

+70
-5
lines changed

3 files changed

+70
-5
lines changed

.github/workflows/push.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,10 @@ jobs:
112112
with:
113113
version: "0.9.1"
114114
args: "format --check"
115-
- name: "make fmt: Python and Go formatting"
115+
- name: "make fmtfull: Python and Go formatting"
116116
# This is already done by actions, but good to check that make command is working
117117
run: |
118-
make fmt
118+
make fmtfull
119119
git diff --exit-code
120120
- name: "make checks: custom checks outside of fmt and lint"
121121
run: |

Makefile

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,27 @@ GOTESTSUM_FORMAT ?= pkgname-and-test-fails
66
GOTESTSUM_CMD ?= go tool gotestsum --format ${GOTESTSUM_FORMAT} --no-summary=skipped --jsonfile test-output.json
77

88

9-
lint:
9+
lintfull:
1010
golangci-lint run --fix
1111

12+
lint:
13+
./tools/lintdiff.py run --fix
14+
1215
tidy:
1316
@# not part of golangci-lint, apparently
1417
go mod tidy
1518

1619
lintcheck:
1720
golangci-lint run ./...
1821

19-
fmt:
22+
fmtfull:
2023
ruff format -qn
2124
golangci-lint fmt
2225

26+
fmt:
27+
ruff format -qn
28+
./tools/lintdiff.py fmt
29+
2330
ws:
2431
./tools/validate_whitespace.py
2532

@@ -78,4 +85,4 @@ generate:
7885
[ ! -f .github/workflows/next-changelog.yml ] || rm .github/workflows/next-changelog.yml
7986
pushd experimental/python && make codegen
8087

81-
.PHONY: lint tidy lintcheck fmt test cover showcover build snapshot schema integration integration-short acc-cover acc-showcover docs ws links checks
88+
.PHONY: lint lintfull tidy lintcheck fmt fmtfull test cover showcover build snapshot schema integration integration-short acc-cover acc-showcover docs ws links checks

tools/lintdiff.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Drop in replacement for golangci-lint that runs it only on changed packages.
4+
5+
Changes are calculated as diff against main by default, use --ref or -H/--head to change this.
6+
"""
7+
8+
import os
9+
import sys
10+
import argparse
11+
import subprocess
12+
13+
14+
def parse_lines(cmd):
15+
# print("+ " + " ".join(cmd), file=sys.stderr, flush=True)
16+
result = subprocess.run(cmd, stdout=subprocess.PIPE, encoding="utf-8", check=True)
17+
return [x.strip() for x in result.stdout.split("\n") if x.strip()]
18+
19+
20+
def main():
21+
parser = argparse.ArgumentParser()
22+
parser.add_argument("--ref", default="main", help="Reference to calculate diff against.")
23+
parser.add_argument("-H", "--head", action="store_true", help="Shortcut for '--ref HEAD' - test uncommitted changes only")
24+
parser.add_argument("args", nargs=argparse.REMAINDER, help="golangci-lint command and options")
25+
args = parser.parse_args()
26+
27+
if not args.args:
28+
args.args = ["run"]
29+
30+
if args.head:
31+
args.ref = "HEAD"
32+
33+
gitroot = parse_lines(["git", "rev-parse", "--show-toplevel"])[0]
34+
os.chdir(gitroot)
35+
36+
changed = parse_lines(["git", "diff", "--name-only", args.ref, "--", "."])
37+
38+
# We need to pass packages to golangci-lint, not individual files.
39+
dirs = set()
40+
for filename in changed:
41+
if "/testdata/" in filename:
42+
continue
43+
if filename.endswith(".go"):
44+
d = os.path.dirname(filename)
45+
dirs.add(d)
46+
47+
if not dirs:
48+
sys.exit(0)
49+
50+
dirs = ["./" + d for d in sorted(dirs)]
51+
52+
cmd = ["golangci-lint"] + args.args + dirs
53+
print("+ " + " ".join(cmd), file=sys.stderr, flush=True)
54+
os.execvp(cmd[0], cmd)
55+
56+
57+
if __name__ == "__main__":
58+
main()

0 commit comments

Comments
 (0)