Skip to main content

KiCad-semantics aware git diff and git status CLI tool

Project description

kigit

A KiCad-aware git diff for your schematics and boards.

Acknowledgements

kigit is developed as a Software Engineering course project for and with help from Auto-Intern GmbH, whose hardware team uses KiCad and Git for industrial monitoring systems. The goal is to help engineers make better commit decisions and to keep project history readable, with the intention of maintaining it as an open-source tool for the wider KiCad community. This tool is being developed by group 1 of this course.


PyPI version Python versions License: MIT

kigit is a command-line tool that makes Git output for KiCad projects readable. Raw git diff on a .kicad_sch file is dominated by formatting, re-ordering, and tool-generated metadata, so it tells you that a file changed but not what meaningfully changed. kigit cuts through that noise and reports the change in electrical terms instead: which components changed, and which nets were added, removed, renamed, or rewired.

Status: early preview (0.0.7). This is an early public release. It focuses on schematic components, the netlist, board placement, and a board-level statistics summary, and intentionally does a small set of things well. Interfaces and output may change in future versions. Bug reports and feedback are very welcome.


What it does

kigit answers questions a plain text diff struggles with, such as "Did the wiring actually change, or is this just KiCad reshuffling the file?"

For the schematic in your repository it:

  • locates the schematic via your project (.kicad_pro) file,
  • exports the netlist for two project states using KiCad's own kicad-cli,
  • compares the two states semantically - components and netlists - ignoring text-level noise, and
  • prints a summary that opens with change counts and one impact line, followed (with --verbose) by the details.

For components it reports parts that were added, removed, changed (value, footprint, sourcing fields, documentation, ...), and renamed. A pure re-annotation - R42 becoming R14 with everything else identical - is reported as one rename, and is also fed into the net comparison, so a renamed part does not show up as fake rewiring on every net it touches.

For nets it reports nets that were added, removed, changed (pins added, removed, or modified; net-class changes), and renamed (a net whose connections are unchanged but whose name differs). When several pins move between nets together, kigit additionally recognizes the pattern - a net split, merge, or move - and reports it as one event; see Reading the output. Each entry is annotated with semantic tags such as power-net, ground-net, critical-net, high-speed-net, connectivity level, and impact tags like connectivity-increased, node-added, or pin-function-changed. Purely cosmetic differences and unconnected (unconnected-*) nets are filtered out.

When the project also has a board file (.kicad_pcb), kigit diff additionally reports placement changes by exporting each version's position (pick-and-place) file and comparing components by reference designator. It flags parts that were moved past a small threshold (component-moved), rotated (component-rotated), or flipped to the other side of the board (side-changed) — manual edits that are easy to miss in a text diff but matter for assembly and clearance. Sub-threshold jitter from rounding is ignored, and --verbose shows the exact position, rotation, and side deltas. Projects without a board file are unaffected.

When the project also has a PCB (.kicad_pcb), kigit diff additionally exports a board-level statistics summary for both states via kicad-cli and reports the numeric deltas: component count, board area, front/back copper area, and drill hole count. Only fields that actually changed are shown (zero-difference fields are suppressed), and a field that's missing from one of the two exports is treated as unchanged rather than reported as a spurious change.

Not in scope yet

To set expectations for 0.0.7, the following are not analyzed yet and are planned for later releases:

  • full PCB routing (.kicad_pcb) — copper, traces, and zones (component placement — position, rotation, and side — is covered, and a board-level statistics summary is available; see above),
  • changes to project settings files (.kicad_pro, .kicad_prl),
  • hierarchical-sheet-aware grouping of changes.

Requirements

kigit orchestrates the tools you already use; it does not reimplement them.

  • Python 3.10 or newer.
  • Git, available on your PATH.
  • KiCad 10.x, with the bundled kicad-cli available on your PATH. (kicad-cli ships with the standard KiCad installation on Windows, Linux, and macOS.)

kigit is cross-platform and runs on Windows and Linux (and other platforms where Python, Git, and KiCad are available). Run kigit check-health at any time to confirm your environment is ready.


Installation

From PyPI with pip:

pip install kigit

Or as an isolated tool with uv:

uv tool install kigit

Quick start

Prerequisite: your schematic must be committed to Git at least once. kigit retrieves historical versions with git show, so an untracked schematic cannot be diffed.

# Compare your working changes against the last commit (the "status" use case):
kigit diff

# Compare two commits, branches, or tags:
kigit diff -c1 main -c2 my-feature-branch

# See full detail instead of a summary:
kigit diff --verbose

# Confirm Git and KiCad are installed and reachable:
kigit check-health

Commands

Run kigit or kigit --help for general help, and kigit COMMAND --help for details on a specific command.

kigit diff

Summarizes the differences between two KiCad project states, filtering out noise from formatting, ordering, and tool-generated metadata.

Option Alias Default Description
--c1-ref -c1 HEAD Git reference (commit, branch, or tag) used as the first state for comparison. Validated up front: must resolve in the repository.
--c2-ref -c2 none Git reference used as the second state. When omitted, the first ref is compared against the working-directory schematic. When given, it must resolve in the repository.
--search-depth -p 2 How many directory levels to descend when locating the project, schematic, and board files. Must be a non-negative integer.
--search-directory -d . Root directory for the project, schematic, and board file lookup. Must be a path tracked in the repository.
--verbose -v false Show the detail sections below the summary block, instead of the summary alone.

Behavior of the default invocation: with no -c2, kigit diff compares the first reference (default HEAD) against the current schematic on disk — i.e. "what have I changed since my last commit?". There is deliberately no separate kigit status command: plain kigit diff is that view.

Input validation. kigit diff checks its arguments before any schematic is exported or compared, so mistakes fail fast with a clear message instead of a confusing downstream error:

  • a -c1 / -c2 reference that does not resolve in the repository is rejected with "Commit doesn't exist in this repository.";
  • a -d search directory that is not tracked in the repository is rejected with "Path doesn't exist in this repository.";
  • a negative -p search depth is rejected as an invalid depth.

kigit check-health

Verifies that all required dependencies (Git and kicad-cli) are present on the system and reports any that are missing.

kigit version

Prints the installed kigit version.


Reading the output

Output is color-coded in your terminal; the examples in this section show the plain text.

Here is kigit diff --verbose for a small, realistic edit: the feedback divider of a 3.3V regulator was rewired onto its own net, R42 was renamed to R14, R4 was retuned, and a filter capacitor C7 was added.

Comparing 'HEAD~3' (9f31c2a, 2026-07-08) → 'working tree'

+ 1 component added   ~ 1 component changed   ~ 1 component renamed
⇄ 1 net split   + 2 nets added   - 1 net removed
! impact: critical-net-involved, ground-net-involved, value-change

Added Components:
	+ C7 (100n) [Device:C] Capacitor_SMD:C_0402
		* properties: passive

Changed Components:
	R4 (4.7k):
		* impact: value-change
		* value: 10k → 4.7k

Renamed Components:
	R42 -> R14 (32k)

Net Structure Changes:
	Net split: '+3.3V' (removed) → 'GND' (3 pins, new), 'Net-(U1-ADJ)' (3 pins, new)
		* impact: critical-net-involved, ground-net-involved
		'+3.3V' → 'GND':
			• U1 pin 4 (GND, power_in)
			• U1 pin 5 (~{SHDN}, input)
			• U2 pin 1 (GND, power_in)
		'+3.3V' → 'Net-(U1-ADJ)':
			• R14 pin 2 (passive)
			• R4 pin 2 (passive)
			• U1 pin 2 (ADJ, input)

Added Nets:
	Net 'Net-(U1-ADJ)':
		+ C7 pin 1 (passive)
		(3 nodes arrived from other nets — see 'Net Structure Changes')

The first line: what is compared with what

The header names both states of the comparison. The left side is the base (here HEAD~3, with its commit id and date); the arrow points at what it is compared to (here the uncommitted working tree). If you ask for a comparison that runs backwards in history — for example -c1 HEAD -c2 HEAD~5kigit adds a note that the arrows read newer → older, so you don't misread it: + added in such an output means the older state had it and the newer one does not.

The summary block

The first block answers, at a glance, how much changed and how much it matters:

  • one counter line per area — components, nets, and (when the project has a board file) placement and board-level stats;
  • one ! impact: line collecting every impact tag found anywhere in the diff, ordered most-serious-first: changes involving ground, supply, or otherwise critical nets lead; paperwork like documentation edits comes last.

Without --verbose, the header plus this block is the entire output — that is the intended "can I commit this?" view. If the impact line worries you, rerun with --verbose.

Net structure changes: splits, merges, and moves

When several pins leave one net for another together, that is one editing action — splitting a net, merging nets, or moving a group of pins — not a coincidence of unrelated additions and removals. kigit correlates the movements and reports the event as one story under Net Structure Changes: which net fed which, and exactly which pins went where. In the example, six pins leaving '+3.3V' for two destinations is one split, not a dozen separate line items scattered across the Added and Removed Nets listings.

Reshuffles spanning more than six nets are deliberately not narrated — at that size a summary would obscure more than it explains — and the plain added / removed / changed listings then report everything unabridged.

Every change is reported exactly once

The listing sections after the narrative do not repeat what it already told:

  • A pin movement told under Net Structure Changes is not listed again under Added / Removed / Changed Nets.
  • A net whose entire content the narrative explains is not listed again at all. Its name in the narrative carries the fact instead: 'GND' (3 pins, new) says everything an Added Nets entry would have said, and '+3.3V' (removed) marks the emptied source. That is why the example has no Removed Nets section at all, and why 'GND' does not appear under Added Nets.
  • A net with anything the narrative does not explain stays listed — showing only that part. 'Net-(U1-ADJ)' keeps its entry because C7 pin 1 is genuinely new (the capacitor was just added, it came from no other net); the dimmed pointer line below it — "3 nodes arrived from other nets" — accounts for the three pins already told above. (A node is netlist vocabulary for one component pin on one net.)
  • The counters in the summary block always count every net, listed or folded: + 2 nets added includes 'GND'.

So if a section seems to be "missing", nothing was dropped — the whole story of those nets is told in the narrative, and repeating it would only make the output longer, not clearer.

Properties and impact tags

Two kinds of annotations appear on entries, answering different questions:

  • * properties: describes what a net or component is — for example power-net, ground-net, critical-net, high-speed-net, or its connectivity level. Context, not change.
  • * impact: describes what the change did or touched. The most common tags:
Tag Meaning
critical-net-involved, ground-net-involved, power-net-involved the change touches a ground or supply net (GND, VCC, 3V3, 5V, ...)
connectivity-increased / connectivity-decreased the net gained or lost pins
node-added / node-removed specific pins appeared on / disappeared from the net
pin-function-changed / pin-type-changed a pin is now reported with a different function or electrical type
value-change a component's value changed (e.g. 10k → 4.7k)
footprint-change a component's footprint changed
component-moved, component-rotated, side-changed board placement changed
documentation-change, metadata-change datasheet, description, or similar paperwork

The summary block's ! impact: line is the union of these across the whole diff, so scanning one line tells you the most serious thing the change does.

Board-level summary

Shown when the project has a .kicad_pcb, listing only the fields that changed:

Board:
 ~ components: 42 → 45 (+3)
 ~ board area: 1200.0 mm² → 1180.0 mm² (-20 mm²)

The board summary

When both states have a board file, the detail view ends with a compact board-level section — the numbers a fab house asks about:

Board:
	~ components: 42 → 43 (+1)
	~ front copper area: 812.5 mm² → 818.75 mm² (+6.25 mm²)
	~ drill holes: 96 → 102 (+6)

Only fields that actually changed are shown, each with its signed delta — board area and back copper area were identical here, so they are suppressed. A field missing from one of the two exports is not comparable and is left out rather than reported as a change. In the summary block all of this appears as one counter (~ 3 board stats changed), and these numbers are context, not verdicts: they raise no impact tags.

When nothing meaningful changed

No semantically meaningful changes to show.

 (Hint: only added, removed, changed and renamed items are shown (i.e. code changes are discarded.))

This is the tool's whole point: KiCad rewriting timestamps, reordering entries, or reshuffling a file is not a design change, and kigit says so explicitly.


How it works

Before the steps below, kigit diff validates its arguments (commit refs, search path, search depth) and stops early with a clear message if any are invalid.

  1. Locate — find the project's .kicad_pro and the matching .kicad_sch (and .kicad_pcb, if present) within the configured search directory and depth.
  2. Retrieve — for each requested Git reference, extract that version of the project with git show <ref>:<path> (schematic) or a detached worktree (PCB) into a temporary working area.
  3. Export — run kicad-cli sch export netlist --format kicadxml on each schematic version to produce a netlist in KiCad XML. When the project has a board file, also run kicad-cli pcb export pos --format csv --units mm to produce the position (pick-and-place) CSV, and kicad-cli pcb export stats --format json to produce the board-level statistics summary.
  4. Parse — read the <components> and <nets> sections into an immutable model of components, nets, and their nodes (component reference, pin, pin function, pin type); parse the position CSV into placements (reference, value, package, X, Y, rotation, side); and parse the board stats JSON into a matching model of board/pad/via/component counts and measurements.
  5. Diff — compare components first (added / removed / changed, plus renames: the same symbol with only its reference changed, or a deleted-and-recreated but otherwise identical part). The rename map feeds the net comparison, so a renamed part is not read as rewiring. Then compute added / removed / changed / renamed nets, ignoring net codes and unconnected-* nets, and correlate pins that moved between nets into splits, merges, and moves. When a board file is present, also diff placements by reference designator, flagging moves past the position threshold, rotations, and side flips, and compute numeric deltas for the board-level summary fields.
  6. Classify & report — tag nets, components, and changes with semantic categories, roll every impact tag up into the summary line, and print the report described in Reading the output, including the board-level summary when applicable.

Logs and temporary files

  • Diagnostic logs are written to a kigit-log/ directory in the current working directory. If a run fails unexpectedly, this is the first place to look.
  • Temporary working data (.kigit_temp/, .kigit_netlist_temp/, .kigit_stats_temp/) is created during a run and cleaned up automatically afterwards.

You may wish to add kigit-log/, .kigit_temp/, .kigit_netlist_temp/, and .kigit_stats_temp/ to your .gitignore.


Development

The project uses uv for packaging, pytest and Hypothesis for testing, and Typer for the command-line interface.


License

kigit is released under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kigit-0.0.7.tar.gz (38.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kigit-0.0.7-py3-none-any.whl (46.1 kB view details)

Uploaded Python 3

File details

Details for the file kigit-0.0.7.tar.gz.

File metadata

  • Download URL: kigit-0.0.7.tar.gz
  • Upload date:
  • Size: 38.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for kigit-0.0.7.tar.gz
Algorithm Hash digest
SHA256 5a91f5f539518b2b276404b797628c0f5e3873316968aa69c91d9e79de322647
MD5 84d4819ee42909a9f1d94e67f8c2243c
BLAKE2b-256 c7d25fff663305a6607a6b55c606ac2350e83c2f28bf1e056f96e0b1ce545f2e

See more details on using hashes here.

File details

Details for the file kigit-0.0.7-py3-none-any.whl.

File metadata

  • Download URL: kigit-0.0.7-py3-none-any.whl
  • Upload date:
  • Size: 46.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for kigit-0.0.7-py3-none-any.whl
Algorithm Hash digest
SHA256 33d5ec24f77db4d31714aba4a028f26c95535b81d4c041982e72f88a118299f9
MD5 071a78d060e7a8f3bb3a1b2a018eface
BLAKE2b-256 353df166a29967544b3099d57e1fbae4fa889c29f650035d43771968ac0f0009

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page