Skip to content

Fix the toolbar's git branch scan, a plugin download escape, and reversed commit diffs - #254

Merged
JE-Chen merged 7 commits into
mainfrom
dev
Jul 29, 2026
Merged

Fix the toolbar's git branch scan, a plugin download escape, and reversed commit diffs#254
JE-Chen merged 7 commits into
mainfrom
dev

Conversation

@JE-Chen

@JE-Chen JE-Chen commented Jul 29, 2026

Copy link
Copy Markdown
Member

A scan of the project turned up five defects; each commit is one of them.

The toolbar's git branch box never filled. _run_in_background kept a
reference to the background thread but not to the worker moved onto it, and
thread.started — the only other thing referring to that worker — is held by
weak reference for a bound method. The worker was collected as soon as the
caller returned, so run() never executed, finished never fired, and the
thread sat in its event loop until Qt aborted the process over it. It is now a
QThread subclass, matching how every other background job here is written.

Reading the branches also moved off GitPython onto git directly. A Repo
brings up long-lived cat-file children and thread-local state; opening and
closing that from a background thread aborted the suite on Windows in three
runs out of three, while the subprocess form passes three out of three.

A downloaded plugin could land anywhere. The file name came from the
GitHub API response and went straight into Path(dest_dir) / file_name, so
../evil.py walked out of the plugins directory and C:/Windows/x.py
replaced it — and plugins are imported and run.

Commit diffs were printed backwards. commit.diff(parent) is the inverse
of what a commit did; additions showed as removals. Now verified against what
git show prints.

The editor logged file contents and reconfigured logging for its host.
write_file built a copy of every saved file for a log line, and two modules
set root logging at import — which decides the level for whatever application
embeds JEditor.

Deprecated Qt calls and silent exception handlers are cleared out.

Verified with ruff check clean and the full suite run three times: 1859
passed each time, against a 1819-test baseline confirmed stable over three
runs beforehand.

JE-Chen added 5 commits July 29, 2026 21:30
The scan behind it had never run. `_run_in_background` kept a reference to
the thread but not to the worker moved onto it, and the only other thing
referring to that worker was `thread.started`, which Qt holds by weak
reference for a bound method. So it was collected as soon as the caller
returned: `run()` never executed, `finished` never fired, the thread sat in
its event loop, and Qt aborted the process over it when the window went.

The scan is now a QThread subclass, the way every other background job in
the editor is written, so the object doing the work is the thread itself and
there is nothing to collect out from under it.

Reading the branches goes through `git` directly instead of GitPython. A
`Repo` brings up long-lived `cat-file` children and thread-local state, and
opening and closing that repeatedly from a background thread aborted the
test suite on Windows within three runs; a subprocess that answers once and
exits does not, and is what `git_cli.py` already does.

A refresh while one is already running is skipped -- the toolbar is rebuilt
on every language change, and the scans were stacking up.

Tests cover the reading itself, which nothing had checked before, and the
scan surviving its caller. Toolbar threads are now drained after every test.
The file name came from the GitHub API response, not from the user, and went
straight into `Path(dest_dir) / file_name`. A name like `../evil.py` walks
out of the plugins directory and an absolute one such as `C:/Windows/x.py`
replaces it outright -- and plugins are imported and run, so that let the
remote side choose where to write executable code.

Names carrying a path are refused now. Both separators are rejected so the
decision does not vary by platform: `sub\evil.py` is a path on Windows but a
legal file name on Linux.

Also makes `fetch_repo_tree` honour the branch it accepts. It was taking the
argument and ignoring it. It now defaults to no ref, which reads the
repository's default branch, so one whose default is called master still
works.
`show_diff_of_commit` called `commit.diff(parent)`. That reads as going from
the commit to its parent, which is the inverse of what the commit did: every
added line was printed as a removal and every removal as an addition. The
first commit, compared against the empty tree the same way, came out as the
deletion of everything it introduced.

Both now diff from the earlier side to the commit, and match what `git show`
prints for the same revision.
`write_file` logged the whole file it was about to save. Nothing reached the
log at the level shipped, but the f-string built a copy of every file on
every save regardless, and one lowered level away it was writing the user's
files into JEditor.log. It records the path and a length now, as
`write_file_with_encoding` already did.

Two modules also reconfigured logging for the entire process at import: the
logging module put the root logger at DEBUG, and the git tree widget called
`basicConfig`. JEditor is embedded in other applications, so both decided the
log level for a host that never asked. Neither did anything for the editor --
its own logger sets its own level, and records travelling up are filtered by
each handler's level, not by root's -- and the logger `basicConfig` was set
up for had no callers.

Also chains the two exceptions in the file helpers that were re-raised bare,
losing the original cause.
Every QFontDatabase member is static in Qt6 and constructing one is
deprecated; the two font menus built an instance to call `families()` on, and
the main window carried one as an attribute for them. They call it on the
class now and the attribute is gone, which also drops the deprecation warning
from the test run. One `exec_()` left over from Qt5 becomes `exec()`.

Three handlers swallowed their exception without a word: the toolbar's branch
scan, jedi completion, and the binary-file sniff. Carrying on is right in all
three -- an empty branch box, no completions, a file treated as text -- but
with nothing recorded there was no way to find out why. They log now, at a
level matching how routine each one is, and the sniff catches OSError rather
than everything.
@codacy-production

codacy-production Bot commented Jul 29, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 51 complexity · 1 duplication

Metric Results
Complexity 51
Duplication 1

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

JE-Chen added 2 commits July 29, 2026 21:39
Calling git by name so PATH finds it, and running this interpreter on a
literal defined in the same file, are both intentional. Each call site says
why and carries the suppression.

Also renames a test constant that read as a credential to the analysers. It
holds a line of text a save must not copy into the log, and now says so.
A comma separated list after nosec is not the documented syntax and only the
first id took effect, so the second check kept reporting.
@sonarqubecloud

Copy link
Copy Markdown

@JE-Chen
JE-Chen merged commit f58b4be into main Jul 29, 2026
9 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.

1 participant