SCRIPTS_FETCH_DIR := ./research/fetch_data
LIST_FULL := ./research/list_of_repositories.csv
LIST_SMALL := ./research/list_of_repositories_100.csv
LIST_TINY := ./research/list_of_repositories_tiny.csv
MODE ?= tiny
LIST = $(if $(filter tiny,$(MODE)),$(LIST_TINY),$(if $(filter small,$(MODE)),$(LIST_SMALL),$(LIST_FULL)))
REPOSITORIES_DIR = /var/tmp/research/$(MODE)/repositories/
RESEARCH_DIR = /var/tmp/research/$(MODE)/
clean: clean-db
rm -rf $(REPOSITORIES_DIR)
clean-db:
rm -rf $(RESEARCH_DIR)/stats.sqlite
test:
cargo test
view-diff:
@if [ -z "$(NAME)" ]; then \
echo "usage: make view-diff NAME=<fixture-name> (e.g. rust-add-if - see src/test/data/diffs/)" >&2; \
exit 1; \
fi
./src/test/view_test_diff.sh $(NAME)
build: test
cargo build --release --features stats
benchmark-optimal:
cargo run --release --features test-fixtures --bin benchmark_optimal_solutions
benchmark-optimal-report:
cargo run --release --features test-fixtures --bin benchmark_optimal_solutions -- --csv
(cd research && uv run ./analysis/matching_reasons_report.py)
benchmark-other-report:
(cd research && uv run ./analysis/benchmark_other_report.py)
benchmark-other:
cargo run --release --features test-fixtures --bin benchmark_other -- --csv
$(MAKE) benchmark-other-report
introductory-paper: benchmark-other-report
cp research/plots/benchmark_other_accuracy.png research/plots/benchmark_other_runtime.png \
research/plots/benchmark_other_variance.tex research/papers/introductory-paper/figures/
cd research/papers/introductory-paper && latexmk -pdf -g -interaction=nonstopmode main.tex
introductory-paper-empirical: file-stats-report
cp research/plots/variables.tex research/plots/tips.png research/papers/introductory-paper/figures/
cd research/papers/introductory-paper && latexmk -pdf -g -interaction=nonstopmode main.tex
ablation-study:
./research/measure/ablation_study.sh $(OUT_DIR)
QUALITY_BASELINE := research/quality_baseline.txt
BENCH_OUTPUT := target/benchmark_optimal_output.txt
check-quality:
cargo run --release --features test-fixtures --bin benchmark_optimal_solutions | tee $(BENCH_OUTPUT)
@total=$$(grep -m1 '^TOTAL' $(BENCH_OUTPUT) | awk '{print $$2}'); \
baseline_total=$$(grep '^TOTAL_MISMATCHES=' $(QUALITY_BASELINE) | cut -d= -f2); \
ms=$$(grep -oE '[0-9.]+ms/fixture' $(BENCH_OUTPUT) | grep -oE '[0-9.]+'); \
baseline_ms=$$(grep '^MS_PER_FIXTURE=' $(QUALITY_BASELINE) | cut -d= -f2); \
echo ""; \
echo "Quality: TOTAL mismatches = $$total (baseline: $$baseline_total)"; \
echo "Runtime: $$ms ms/fixture (baseline: $$baseline_ms)"; \
if [ "$$total" -gt "$$baseline_total" ]; then \
echo "error: quality regressed - TOTAL mismatches $$total > baseline $$baseline_total" >&2; \
exit 1; \
fi; \
over_2x=$$(awk -v ms="$$ms" -v base="$$baseline_ms" 'BEGIN { print (ms > base * 2) ? 1 : 0 }'); \
if [ "$$over_2x" = "1" ]; then \
echo "warning: runtime is more than 2x the baseline ($$ms ms/fixture vs $$baseline_ms ms/fixture) - investigate before deploying" >&2; \
fi
update-quality-baseline: check-quality
@total=$$(grep -m1 '^TOTAL' $(BENCH_OUTPUT) | awk '{print $$2}'); \
ms=$$(grep -oE '[0-9.]+ms/fixture' $(BENCH_OUTPUT) | grep -oE '[0-9.]+'); \
{ \
echo "# Baseline compared against by \`make check-quality\` / \`make deploy\` - see Makefile."; \
echo "# TOTAL_MISMATCHES: benchmark_optimal_solutions' first TOTAL row (mismatches vs. the"; \
echo "# human-authored ground truth in src/test/data/diffs/) - a hard gate, since this is"; \
echo "# algorithm-only and not machine-dependent: deploy fails if this number goes up."; \
echo "# MS_PER_FIXTURE: benchmark_optimal_solutions' own \"Runtime: ... ms/fixture\" line -"; \
echo "# informational only (a >2x jump warns but doesn't fail deploy), since wall-clock time"; \
echo "# varies by machine."; \
echo "# Update deliberately via \`make update-quality-baseline\` after a reviewed improvement,"; \
echo "# not automatically as a side effect of every deploy."; \
echo "TOTAL_MISMATCHES=$$total"; \
echo "MS_PER_FIXTURE=$$ms"; \
} > $(QUALITY_BASELINE); \
echo "Updated $(QUALITY_BASELINE): TOTAL_MISMATCHES=$$total MS_PER_FIXTURE=$$ms"
deploy-checks:
@if [ -n "$$(git status --porcelain)" ]; then \
echo "error: working tree is dirty - commit or stash before deploying" >&2; \
exit 1; \
fi
git fetch origin main
@if [ "$$(git rev-parse HEAD)" != "$$(git rev-parse origin/main)" ]; then \
echo "error: HEAD does not match origin/main - push your commits first" >&2; \
exit 1; \
fi
$(MAKE) check-quality
deploy-crates: deploy-checks
cargo publish --locked
deploy-github: deploy-checks
$(eval VERSION := $(shell grep -m1 '^version = ' Cargo.toml | sed -E 's/version = "(.*)"/\1/'))
@echo "Tagging and pushing v$(VERSION)..."
git tag v$(VERSION)
git push origin v$(VERSION)
deploy: deploy-crates deploy-github
hermetic-benchmark:
cargo bench --bench diff_code_benchmark
hermetic-benchmark-update-baseline:
cargo bench --bench diff_code_benchmark -- --save-baseline baseline
benchmark-sampled:
@echo "Running benchmarks for Rust, Python, Go, Kotlin..."
@echo "Results will be written to research/results/"
cd research && ./measure/benchmark_all_extended.sh --language "Rust Python Go Kotlin" --repos-dir /var/tmp/research/small/repositories/ --bin-dir ../target/release
@echo ""
@echo "Running analysis..."
cd research && uv run ./analysis/benchmark_report.py
benchmark-sampled-extended:
@echo "Running extended benchmarks for all supported languages..."
@echo "Results will be written to research/results/"
@echo "Using 20000 node limit, max 100 commits per repo"
cd research && ./measure/benchmark_all_extended.sh \
--language all \
--repos-dir /var/tmp/research/small/repositories/ \
--bin-dir ../target/release \
--limit 20000 \
--max-commits 100 \
--timeout-min 120 \
--continue-on-error
@echo ""
@echo "Running analysis..."
cd research && uv run ./analysis/benchmark_report.py
fetch: $(LIST) $(SCRIPTS_FETCH_DIR)/dataset.sh
$(SCRIPTS_FETCH_DIR)/dataset.sh update --root $(REPOSITORIES_DIR) --list $(LIST)
file-stats-report:
(cd research && uv run ./analysis/file_stats.py $(RESEARCH_DIR)/stats.sqlite)
file-stats: build
./target/release/file_stats --path $(REPOSITORIES_DIR) --db $(RESEARCH_DIR)/stats.sqlite
$(MAKE) file-stats-report
commit-stats: build
./target/release/commit_stats --path $(REPOSITORIES_DIR) --db $(RESEARCH_DIR)/stats.sqlite
(cd research && uv run ./analysis/commit_stats.py $(RESEARCH_DIR)/stats.sqlite)
DEBUG_MODE ?= dirs
debug-stats:
@if [ -z "$(DIR)" ]; then \
echo "usage: make debug-stats DIR=/path/to/repos [DEBUG_MODE=dirs|all|repositories]" >&2; \
exit 1; \
fi
./research/measure/debug.sh --$(DEBUG_MODE) $(DIR)
sample-pairs: build
./target/release/sample_code_pairs --path $(REPOSITORIES_DIR) --output research/sampled_code_pairs.csv
benchmark-pairs: build
./target/release/benchmark_diff_pairs --csv research/sampled_code_pairs.csv --repo-root $(REPOSITORIES_DIR) --output research/diff_pairs_benchmark.csv
sample-pairs-rust: build
./target/release/sample_code_pairs --path $(REPOSITORIES_DIR) --output research/sampled_code_pairs_rust.csv --language Rust
benchmark-pairs-rust: build
./target/release/benchmark_diff_pairs --csv research/sampled_code_pairs_rust.csv --repo-root $(REPOSITORIES_DIR) --output research/diff_pairs_benchmark_rust.csv
code-pair-diff-stats:
(cd research && uv run ./analysis/code_pair_diff_stats.py sampled_code_pairs_rust.csv --repo-root $(REPOSITORIES_DIR) --output-csv code_pair_diff_stats_rust.csv)
benchmark-pairs-diff:
@if [ -z "$(BEFORE)" ] || [ -z "$(AFTER)" ]; then \
echo "usage: make benchmark-pairs-diff BEFORE=path/to/before.csv AFTER=path/to/after.csv" >&2; \
exit 1; \
fi
(cd research && uv run ./analysis/diff_pairs_benchmark_comparison.py --before $(abspath $(BEFORE)) --after $(abspath $(AFTER)))
sample-pairs-java: build
./target/release/sample_code_pairs --path $(REPOSITORIES_DIR) --output research/sampled_code_pairs_java.csv --language Java --count 1000 --max-commits-per-repo 100
benchmark-pairs-java: build
./target/release/benchmark_diff_pairs --csv research/sampled_code_pairs_java.csv --repo-root $(REPOSITORIES_DIR) --output research/benchmark_java.csv --max-combined-nodes 20000
sample-pairs-javascript: build
./target/release/sample_code_pairs --path $(REPOSITORIES_DIR) --output research/sampled_code_pairs_javascript.csv --language JavaScript --count 1000 --max-commits-per-repo 100
benchmark-pairs-javascript: build
./target/release/benchmark_diff_pairs --csv research/sampled_code_pairs_javascript.csv --repo-root $(REPOSITORIES_DIR) --output research/benchmark_javascript.csv --max-combined-nodes 20000
sample-pairs-typescript: build
./target/release/sample_code_pairs --path $(REPOSITORIES_DIR) --output research/sampled_code_pairs_typescript.csv --language TypeScript --count 1000 --max-commits-per-repo 100
benchmark-pairs-typescript: build
./target/release/benchmark_diff_pairs --csv research/sampled_code_pairs_typescript.csv --repo-root $(REPOSITORIES_DIR) --output research/benchmark_typescript.csv --max-combined-nodes 20000
analyze: file-stats
tiny: override MODE=tiny
tiny: analyze
small: override MODE=small
small: analyze
full: override MODE=full
full: analyze