You can't merge the bytes of two edited VIs, so a LabVIEW merge conflict has two ways out. Pick one side with git checkout --ours or --theirs, then redo the other side's change by hand in LabVIEW. Or run LVMerge, if you have a Professional seat and both branches load on your machine. Either way you'll reopen the VI, because there's no text to resolve.
What the Conflict Looks Like
Git tells you up front that it won't try. The warning line is the one to read.
$ git merge widen-3v3-limitwarning: Cannot merge binary files: FCT/Power Rails.vi (HEAD vs. widen-3v3-limit)Auto-merging FCT/Power Rails.viCONFLICT (content): Merge conflict in FCT/Power Rails.viAutomatic merge failed; fix conflicts and then commit the result.$ git statusOn branch mainYou have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge)Unmerged paths: (use "git add <file>..." to mark resolution) both modified: FCT/Power Rails.viThe file on disk right now is one of the two versions, not a mix, and Git won't say which without asking. Don't open it in LabVIEW yet. Decide first.
The Decision
| Situation | Do this |
|---|---|
| One side's change is small (a constant, a limit, a wire) | Keep the other side, redo the small change by hand |
| Both sides made real diagram changes, you have Professional, both branches load | LVMerge |
| The VI is a class member or a typedef | Manual, always. LVMerge and the rename it needs don't hold up on classes |
| No machine on the team has Professional | Manual, always |
| You're in a rebase, not a merge | Same steps, but --ours and --theirs swap meaning (see below) |
| You don't know what the other side changed | Ask, or run git log -1 --format=%B <branch> -- "FCT/Power Rails.vi" and read the commit message. That message is all the history you have |
That last row is the real cost. On a text file you'd read the diff. On a VI the commit message is the diff, which is why "fixed limits" as a message costs you an afternoon later. Why LabVIEW version control is so painful has the full mechanism.
The Manual Path
This works on any seat and any machine that can open the VI.
# Keep one side of the VI, then redo the other side's change in LabVIEW by hand.git checkout --ours -- "FCT/Power Rails.vi"# or, to keep the incoming branch's version instead:# git checkout --theirs -- "FCT/Power Rails.vi"# Open the VI in LabVIEW, redo the other side's change, save. Then:git add "FCT/Power Rails.vi"git commit -m "Merge widen-3v3-limit: redo the 3.15 V floor in Power Rails.vi by hand"Step by step:
- Pick the side with the larger change and check it out. In a merge,
--oursis the branch you're on and--theirsis the branch you're merging in. In a rebase they swap:--oursis the upstream you're rebasing onto and--theirsis your own commit being replayed. - Open the VI in LabVIEW and redo the smaller change. Use the other branch's commit message, or ask the author.
- Save. If the two branches were built on the same LabVIEW version and bitness, you're done with LabVIEW. If the merge crossed a version or bitness change, mass compile the project now, and only now. A mass compile rewrites every VI it touches, so doing it for a single-VI conflict adds noise to the commit for no reason.
git addthe VI and commit. Put what you redid in the message, with the value. That's the only place it will ever be written down.
Run the VI once before you push. There's no diff to review, so the test is the review.
The LVMerge Path
LVMerge does a real three-way merge: Base (the common ancestor), Theirs, Yours, into a merged VI you accept difference by difference. It needs the mergetool config from LVCompare and LVMerge: what they cannot do, a Professional seat, and every subVI and class in both branches resolvable on this machine.
# Three-way merge with LVMerge. Needs Professional and the lvmerge mergetool config.git config mergetool.keepBackup falsegit mergetool --tool=lvmerge -- "FCT/Power Rails.vi"# LVMerge opens Base, Theirs and Yours. Accept or reject each difference, then save.git add "FCT/Power Rails.vi"git commitStep by step:
- Run
git mergetoolon the one file. Git writes the three inputs to temporary absolute paths and calls LVMerge in Base, Theirs, Yours, Merged order. - Work through the difference list. Each entry is a front panel, block diagram or attribute change from one side.
- Before saving, read the merged block diagram once. LVMerge can duplicate a node that one branch deleted and re-added. Remove the duplicate.
- Save,
git add, commit.keepBackup falsestops Git leaving a.origcopy of the VI next to the real one, which LabVIEW would otherwise find on the next load.
If LVMerge stalls on a dependency dialog, abort, and take the manual path on the development machine that has the full project.
How to Stop It Recurring
The conflict is a symptom of two people needing the same binary file at the same time. Four habits reduce how often that happens, in order of cost.
One person per VI at a time. Say which VIs you're in before you start, in the channel or in a LOCKS.md at the repository root, and nobody else opens them until you push. It's a convention, not a mechanism, and it holds on a small team.
Smaller VIs. A 40-node VI that does the whole test is one file two people will both need. Split by phase and the odds of two engineers in the same file drop.
Limits in a config file, not in the diagram. Most conflicts on test VIs are two people changing two different constants in one file. Move the constants out, and the VI stops changing when a limit does.
; Read by the test VI with the Config File VIs. This file diffs and merges in plain Git.[rail_3v3]min = 3.15max = 3.4[rail_5v]min = 4.85max = 5.15Separate compiled code from the source file. It doesn't prevent a real two-edit conflict, but it removes the fake ones, where a VI shows as modified because a callee changed. Separate compiled code in LabVIEW, explained covers the setting. Together with the hand-over notes in document a test station for hand-over, it's what makes the manual path survivable when the author of the other branch has left.
The Contrast
A TofuPilot Framework station is text: procedure.yaml, phases/*.py, plugs/*.py. The same conflict, two branches both changing the 3.3 V floor, looks like this.
- name: rail_3v3 unit: V validators: - operator: ">="<<<<<<< HEAD expected_value: 3.15======= expected_value: 3.1>>>>>>> widen-3v3-limit - operator: "<=" expected_value: 3.4You read both values, keep one, delete the markers, git add, commit. Every other change in the two branches merged automatically. Afterwards, git blame answers the question the LabVIEW commit message couldn't.
$ git blame -L 25,26 procedure.yamla41f9c2e (Priya Raman 2026-09-14 10:22:41 +0200 25) - operator: ">="7d03be51 (Marc Dubois 2026-09-18 16:05:09 +0200 26) expected_value: 3.15Who set the floor to 3.15, on what day, in which commit. And once that commit is pushed, the station pulls the deployment and every run it uploads to TofuPilot carries the deployment id, so the units tested under 3.15 are a filter on the run list.
Start With One Station
Pick the station whose VIs conflict most often, or the one only one person can open. Rebuild it as a procedure.yaml with phases and plugs in Python with the TofuPilot Framework, keep the same limits, and run it side by side with the LabVIEW version on the same units for two weeks.
# Install the CLI, run the station locally (no account needed), or run with upload.curl -fsSL https://www.tofupilot.app/install | shtofupilot run ./procedure.yamltofupilot run ./procedure.yaml --uploadThe rebuild is in how to migrate from LabVIEW to Python for manufacturing tests with TofuPilot, and the framework is at tofupilot.com/products/framework. The Lab tier is free, and tofupilot run works without an account.
