Skip to content

[patch] Wire up Pull and stop advertising unimplemented Commit and Push - #393

Merged
matt-edmondson merged 3 commits into
mainfrom
claude/record-issues-defects-n6p37v
Aug 27, 2026
Merged

[patch] Wire up Pull and stop advertising unimplemented Commit and Push#393
matt-edmondson merged 3 commits into
mainfrom
claude/record-issues-defects-n6p37v

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #391. Splits the remaining functionality out to #392.

The problem

The repository detail panel drew three enabled buttons that did nothing when clicked. Nothing indicated they were unfinished, so clicking one looked like a silent failure rather than absent functionality.

Pull is now implemented

The TODO asked for a check for uncommitted changes before pulling — and GitCli.HasUncommittedChanges already existed to answer it, so this was mostly a wiring job. PullRepoConfirmingUncommittedChanges pulls straight away on a clean tree, and otherwise asks first with Pull Anyway / Cancel.

This is a warning, not a safety mechanism, and deliberately so. PullRepo already passes --ff-only, so a divergent branch is refused rather than merged — but that protects the history and says nothing about the working tree. A pull across uncommitted changes can still fail partway, or succeed and leave the user unsure which changes were theirs. Proceeding stays available because there are legitimate reasons to pull with a dirty tree.

Commit and Push are disabled, not implemented

Both stay visible so the layout is unchanged, but are disabled with a shared "Not implemented yet." tooltip.

I stopped short of implementing them on purpose. Neither is blocked on plumbing — GitCli.RunIn, ImGuiPopups.InputString and QueueGitLog are all there — they are blocked on policy decisions that are yours, not mine:

  • What does Commit stage? git add -A sweeps up untracked files, which is a surprising thing to do to someone's working tree from a background click.
  • Should the user see the file list before confirming?
  • Which remote and branch does Push target when the branch has no upstream?
  • How is a rejected push surfaced?

PullRepo's own comment records that unattended git operations swallowing their failures already caused a bad bug in this file — a merge committed unattended, the conflict exception swallowed, the working tree left mid-conflict with nothing said. Guessing at the answers above would be repeating that mistake. #392 carries the full detail and the suggested shape.

If you would rather I just implement them with a particular set of answers, say which and I will.

Verification

  • Builds clean.
  • dotnet test passes 7/7 (5 succeeded, 2 self-skipped — git-lfs is not installed in this sandbox, which is the suite's existing Assert.Inconclusive behaviour, not a regression).
  • GitCli.HasUncommittedChanges, which the new path depends on, is already covered by UncommittedChangesAreDetected for both the clean and dirty cases.

Implementation notes

  • Uses ImGui.BeginDisabled/EndDisabled rather than ImGuiWidgets.ScopedDisable — the latter is not nested under ImGuiWidgets in the referenced package version and fails to compile as ImGuiWidgets.ScopedDisable. The ImGui pair has no package-version risk.
  • Hover state is captured per button, before EndDisabled, because IsItemHovered only ever refers to the item immediately submitted. Reading it once after both buttons would have reported only Push.

Caveat

This is a GUI change and I have no display here, so the popup and the disabled/tooltip behaviour are unverified at runtime — they are verified only to compile and to follow the existing PopupSetDevDirectory/PopupAddNewGitHubOwner pattern (field, ShowIfOpen() in the render loop, Open(...) at the call site). Worth a quick manual click-through before merging. I could not run the repo's own analyzers either: this sandbox's SDK is 10.0.111 (Roslyn 5.0.0.0) and ktsu.Sdk.Analyzers 2.28.0 requires 5.9.0.0, so CSC refuses it with CS9057.


Generated by Claude Code

claude added 2 commits August 27, 2026 05:09
The repository detail panel drew three enabled buttons that did nothing when
clicked. Because they gave no indication of being unfinished, clicking one
looked like a silent failure rather than absent functionality.

Pull is now implemented. The TODO asked for a check for uncommitted changes
before pulling, and GitCli.HasUncommittedChanges already existed to answer it,
so PullRepoConfirmingUncommittedChanges pulls straight away on a clean tree and
otherwise asks first, offering "Pull Anyway" and "Cancel".

This is a warning rather than a safety mechanism, and deliberately so. PullRepo
already passes --ff-only, which refuses to advance a divergent branch, but that
protects the history and says nothing about the working tree: a pull across
uncommitted changes can still fail partway, or succeed and leave the user
unsure which changes were theirs. Proceeding stays available because there are
legitimate reasons to pull with a dirty tree.

Commit and Push are disabled rather than implemented, with a shared "Not
implemented yet." tooltip. They stay visible so the layout is unchanged. Both
need policy decisions rather than plumbing -- what Commit stages, whether the
user sees the file list first, which remote and branch Push targets, and how a
rejected push is surfaced. PullRepo's own comment records that unattended git
operations swallowing their failures already caused a bad bug in this file, so
guessing at those answers is the wrong move. Raised as #392 with the details.

Uses ImGui.BeginDisabled/EndDisabled rather than ImGuiWidgets.ScopedDisable,
which is not nested under ImGuiWidgets in the referenced package version. The
hover states are captured per-button before EndDisabled, since IsItemHovered
only ever refers to the item immediately submitted.

Fixes #391

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTHNXgSNEHUSQ5KMLgivno
SonarCloud failed the quality gate on the previous commit with 0% coverage on
new code. That commit was almost entirely ImGui drawing, which needs a live
context and a display, but it did contain one rule: whether clicking Pull runs
immediately or asks first.

Extracts that rule into DecidePull, which returns a PullDecision rather than
branching inline, and covers it with PullDecisionTests against real throwaway
repositories the way GitCliTests already does. Getting it wrong is user-visible
in both directions: nagging on a clean tree makes the button annoying, and
staying silent on a dirty one is the case the confirmation exists for.

Covered: a clean tree pulls without asking; a modified tracked file, an
untracked file and a staged-but-uncommitted change each ask first; and
committing the change stops the confirmation, so the decision tracks the tree's
current state rather than merely that it was ever dirty.

Verified by mutation: inverting the decision fails 5 of the 12 tests.

This does not make the whole diff reach the 80% new-code coverage threshold --
the remainder is ImGui drawing with no headless harness in this repository to
exercise it. Noted on the pull request rather than left silent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTHNXgSNEHUSQ5KMLgivno
Comment thread ProjectDirector.Test/PullDecisionTests.cs Fixed
Comment thread ProjectDirector.Test/PullDecisionTests.cs Fixed
Comment thread ProjectDirector.Test/PullDecisionTests.cs Fixed
Comment thread ProjectDirector.Test/PullDecisionTests.cs Fixed
Comment thread ProjectDirector.Test/PullDecisionTests.cs Fixed
Comment thread ProjectDirector.Test/PullDecisionTests.cs Fixed

Copy link
Copy Markdown
Contributor Author

SonarCloud is red and I've stopped pushing at it. Here's exactly what's blocking.

Everything else is green — build, tests on Windows and Ubuntu, CodeQL, CodeQL Actions analysis, GitHub Advanced Security.

The one failing condition

5.6% Coverage on New Code (required ≥ 80%)

It was 0.0%. I extracted DecidePull out of PullRepoConfirmingUncommittedChanges and added PullDecisionTests (5 tests against real throwaway git repositories, mutation-checked — inverting the decision fails 5 of 12). That moved it to 5.6%.

Why it will not reach 80%

The rest of the diff is ImGui drawing:

  • the ImGui.Button / BeginDisabled / EndDisabled / SetTooltip block,
  • the PopupConfirmPull.Open(...) call and its button dictionary,
  • the ShowIfOpen() pump line.

All of it needs a live ImGui context, a window and a display. This repository has no headless UI test harness, so none of those lines is reachable from a test. DecidePull was the only part of the change with a rule in it, and it is now covered.

The 80% new-code threshold is therefore unreachable for any predominantly-UI change here, not just this one. I'd rather say that than keep pushing speculative commits at the number.

What I'd need to actually clear it

One of these, and each is a decision rather than a detail:

  1. Add a headless harness. ktsu.ImGui.App.Testing exists — ImGuiApp uses it in tests/ImGui.Widgets.UITests/ to render widgets through a CPU rasterizer with no window or GPU. Bringing it into this repository would make the UI genuinely testable, but it is a substantial piece of work and well outside what Pull, Commit and Push buttons are non-functional stubs #391 asked for.
  2. Exclude the ImGui-drawing file from coverage in the Sonar configuration, so the metric measures what is actually testable.
  3. Accept the gate failure on UI-only changes and merge on the other checks.

Happy to do (1) as its own issue if you want it — say the word. I have not opened one, since it is a big enough commitment that it should be your call.


Generated by Claude Code

github-code-quality flagged all six Path.Combine calls in the new test file:
"Call to 'System.IO.Path.Combine' may silently drop its earlier arguments."

Path.Combine returns its later argument verbatim when that argument is rooted,
discarding everything before it. Every second argument here is a literal file
name or a generated directory name, so none of them can be rooted and the
current behaviour is correct -- but Path.Join concatenates unconditionally and
so cannot exhibit the pattern at all, which is the safer construct to reach for
by default.

The results are identical for these inputs; Path.GetTempPath's trailing
separator is handled by both.

Left the pre-existing Path.Combine calls in GitCliTests alone: they are not part
of this change, and widening the diff to satisfy a rule the bot did not raise
against them belongs in its own commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTHNXgSNEHUSQ5KMLgivno
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
5.6% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@matt-edmondson
matt-edmondson merged commit 514e5b2 into main Aug 27, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pull, Commit and Push buttons are non-functional stubs

2 participants