Releases: olegklimakov/fretwire
Release list
Fretwire 0.7.0
The seventh beta is about reading a diff, and about the rail knowing what it
draws. A changed line says which characters changed rather than tinting the
whole row, and the diff is finally text you can select, copy and scroll sideways
through. On the rail, the tooltip that was written for a badge and never once
appeared is replaced by a card that says which repository, which checkout and
which branch — and a repository whose own icon is no use at 30pt can be told to
show letters instead.
New
- Only what changed is marked — a changed line used to tint end to end, which
says a line is different without saying how. Each deletion is now compared
against the addition it faces, at word granularity, and only those characters
carry the mark. A pair that shares less than 30% of its characters keeps the
plain whole-row tint: below that the two lines are not a rewrite of each other,
and marking them word by word produces confetti rather than a reading. - Diff lines can be selected and copied — press, Shift, ⌘, drag, ⌘A, ⌘C and a
context menu, in the diff as in a list. The unit is the line, so the two halves
of a split pair are picked apart rather than taken together, and a range runs
along the column it is drawn in. What lands on the clipboard is plain: no+
and-markers, no line numbers — the text as it will be in the file. - Long lines can be reached — the diff body scrolls both ways, and in the
split view each pane scrolls horizontally inside its own half instead of
pushing the AFTER side off the window. The content width is measured rather
than guessed, because a lazy stack only measures the rows currently on screen
and a long line further down would otherwise not widen anything. - A card for a rail badge — the rail draws two or three letters per tab, and
the tooltip written to explain them never appeared. It is a card now, shown
while the pointer rests on a badge: the repository, the checkout, the branch
and the path on disk, one fact per line. It takes no mouse events at all —
switching repositories stays one click, where a popover's click-outside-to-
close ate the first one — and it follows the pointer from badge to badge as a
swap rather than a blink. VoiceOver reads the same facts, so two worktrees of
one project stop being two elements with the same name. - A repository's icon can be turned down — the rail draws whatever a
repository ships, and some of those are no use at 30pt: a white mark on white,
or the one icon every service in a monorepo carries. The customize sheet now
asks the question outright — repository icon, or letters — and appears only
when there is an icon to turn down, because otherwise letters are not a choice
but the only answer. The preview shows whichever badge is chosen. - Column widths belong to the tab — dragging AUTHOR wide enough for a
monorepo full offeature/PROJ-1234_…branch names used to widen every other
repository too. Widths are per repository now: they are a fit to what the rows
in front of you hold, not a preference that travels. Which columns to show
stays app-wide — that one is a reading preference, and it follows the reader. - A branch checked out elsewhere goes to its worktree — selecting such a
branch was answered with git's refusal and an Open Worktree button to press
afterwards. You had said where you wanted to go; the app made you say it twice.
git allows a branch in exactly one working tree, so "check it out here" has no
meaning for one — the app goes to the directory instead, from the toolbar menu,
the sidebar, the context menus and ⏎ alike. The refusal still appears when the
app's refs were stale, and then it offers what that path can still take: open
it if the worktree is there, prune it if git's record has outlived it, and
nothing at all if it is there but unreadable, where pruning would throw a live
association away to fix a permissions problem.
Fixed
- A tag whose name a branch also carries was unreachable — Checkout in the
tag's own menu asked git for the bare name, and git calls a name held by both a
tag and a branch ambiguous and picks the branch. The app then went further and
sent it to whichever worktree held that branch. A tag checkout detaches HEAD,
so it now asks forrefs/tags/<name>, which resolves to one thing. - The traffic lights rode above the row they label — macOS centres the window
buttons in the 32pt titlebar it draws itself, and the row beside them is 44pt,
which left the three buttons 6pt above the repository name and path. They sit
on the row's centre line now, and are put back there after every resize and
full-screen transition, where AppKit re-runs its own layout. - Switching between open tabs was slow — the rail wrapped the switch in an
animation so its accent marker could travel between icons, and that transaction
reached the pane as well: the window's whole contents crossfaded over a 0.28 s
spring on every click. ⌘⇧] and ⌘⇧[, which animate nothing, were already
instant — the same switch, one of them slow. The pane opts out now and the
marker keeps its travel. What is left is 84 ms for 240 commits, and 122 ms for
the same history carrying 300 branches; nothing in a switch runs git.
Requirements
- macOS 14 or later.
- A
git— whichever comes first on your login shell's PATH, else Apple's. With
none installed at all, the Welcome screen says so once, with the command that
fixes it.
Not here yet
- A command palette.
- Dragging a commit onto a branch. The rebase editor covers reordering a range;
history by drag-and-drop is a separate decision, and it hasn't been made.
During the beta
Free, for any use. Licensing starts at 1.0: free for personal projects, paid per
seat for commercial use — see the EULA.
The app updates itself — each update is checked against a signature before it is
applied. Settings ▸ Updates decides how much of that happens on its own. Bug
reports and requests go to the issue tracker.
Fretwire 0.6.0
The sixth beta is about the history row as a place to act from. Two things that
lived somewhere else moved onto the commit itself: the force push that every
rewrite in its menu warns will be needed, and the worktree already checked out
at that commit — a checkout that costs nothing, because the directory is
standing there on it. And a linked worktree stops posing as a project of its
own: its tab nests under the project it belongs to, smaller, with a branch
glyph, and it is the repository that goes into recents.
New
- Force push from the history — a commit's menu offers four ways to rewrite
what is under it, and each one says the same thing afterwards: this will need
a force push. The push itself was a trip away, to the toolbar or the remote
hub. Force Push<branch>… now sits on the tip of any branch whose
same-named upstream is still there, one entry per branch — several
upstream-tracking branches on one tip is rare enough that a submenu would be
noise. It pushes--force-with-leasewith a fully qualified refspec, so it
works from any checkout rather than only when that branch is the one you are
standing on: rewriting a branch you are not on and pushing it is one menu now,
not a checkout in between. - The same warning everywhere — force push confirms through one sheet, and
the toolbar's private confirmation dialog is gone. The button, the remote hub
and the commit row ask the question in the same words, name the same branch,
and warn in the same breath that the push aborts if the remote has commits you
haven't fetched. - Open the worktree sitting on a commit — when another linked worktree is
checked out at the commit under the cursor, Open Worktree<name>jumps
to its tab. It is the only checkout in the menu that touches no git at all —
the working tree already exists, on that commit — so it stays available while
an operation is running, alongside the copy items. Offered for a worktree
holding a branch and for one on a detached HEAD, and only while its directory
is still on disk. - Worktrees live inside their project — a linked worktree used to open as
though it were an unrelated repository: its own entry in recents, its own icon
in the rail, no sign of what it belonged to. Now the recents entry records the
repository, a worktree tab inserts next to its project's tab — a project
opened later adopts the worktree tabs already there — and the rail draws it
smaller and indented, with a branch glyph in the corner and a tooltip that
says which project it is a worktree of. The relationship is read at open time
from--git-common-dirand never stored, so a worktree removed behind the
app's back leaves nothing stale behind, and a submodule or a bare repository
is not mistaken for one.
Requirements
- macOS 14 or later.
- A
git— whichever comes first on your login shell's PATH, else Apple's. With
none installed at all, the Welcome screen says so once, with the command that
fixes it.
Not here yet
- A command palette.
- Dragging a commit onto a branch. The rebase editor covers reordering a range;
history by drag-and-drop is a separate decision, and it hasn't been made.
During the beta
Free, for any use. Licensing starts at 1.0: free for personal projects, paid per
seat for commercial use — see the EULA.
The app updates itself — each update is checked against a signature before it is
applied. Settings ▸ Updates decides how much of that happens on its own. Bug
reports and requests go to the issue tracker.
Fretwire 0.5.0
The fifth beta is about reading and about reach. The settings window stopped
being four tabs and became a list of small panes, which is what let three
preferences land in it that had nowhere to go before: how tall a history row is,
how large its text is beside everything else, and how loud the commit messages
are. Beside that, two commands grew past the one place they used to work — a
worktree can start at any commit now, and a squash can take a run from anywhere
inside the branch rather than only one ending at HEAD. And a branch pushed to
two remotes finally draws one pill instead of three that read the same.
New
- Settings as a list of panes — six panes down the left instead of four
tabs. A tab strip charges for each new preference twice, once in the strip and
once in the pane it makes taller; Appearance alone had grown to carry five.
Appearance keeps the theme and the text size, History takes the lane
weight and the message emphasis with the sample that draws them both, and a
new Diff pane takes the in-diff sidebar toggle together with the external
diff and merge tools — which answer "what opens this file", not "which git
runs", and were only ever under Git for want of a better home. The stored key
is the old one, so an upgrade reopens on the pane it left off. - A history you can set the density of — ⌘, ▸ History: Row height
(Compact / Regular / Comfortable) and a History text size. The history is
the one list in the app with a height of its own — the refs sidebar and the
file lists are as tall as what is in them — which is what makes "how many
commits fit on screen" a question worth answering; Compact fits about a
quarter more. The text size multiplies the app-wide one rather than replacing
it, so ⌘+ and ⌘− still move both, and nobody has to choose between comfortable
panels and a dense history. Everything inside a row follows it — message,
author, date, SHA, ref pills, avatar — while the column header stays on the
app-wide scale, being the table's chrome rather than its content. - Commit messages you can turn down — the subject is the brightest ink on
the screen, and on a wide history that column of white pulls the eye off the
lanes and the pills it is supposed to sit beside. Three steps, each an existing
text rank rather than a new colour, so the message keeps its place in the
hierarchy everything else is built from. - One pill per branch — a pushed branch used to decorate two pills printing
the same word: past the column's label budget both truncate to the last
segment, and the only difference left was an icon the eye has to hunt for. The
refs sharing a branch name fold into one pill now — the local name, an icon per
ref inside it — and the tooltip and the VoiceOver label spell out every full
name, including which remotes carry it. Tags and the detached-HEAD marker keep
their own pill: a tag named like a branch is a different thing pointing at the
same commit. - A worktree that starts at a commit — Add Worktree Here… on any history
row, beside Create Branch Here and Create Tag Here, which are the same "work
from this point" aimed elsewhere. On a branch made for the occasion, because
that is the only thing that can be checked out at an arbitrary commit — a
directory sitting on a bare commit is a detached HEAD nobody asked for — with
the folder seeded from the short hash until a branch name is typed. - Squash a run of commits — a selection of five could previously be folded
only by opening the rebase editor and marking four rows by hand, and only if it
ended at HEAD. Squash N Commits… now takes any contiguous run inside the
branch: the base is the oldest selected commit's parent, everything above the
run is replayed on top, and the messages are joined exactly as a squash into a
parent already did — so the command is a confirmation, not a second editor. The
menu entry reads the run off the selection without touching git, so it is a
command or an explanation ("Squashing needs commits in a row", "Merge commits
can't be squashed"), never a dead button. - Escape leaves the diff — reading a diff, the only way back to the history
was the chevron in its header, a mouse trip for something every other Mac app
answers with the key already under the reader's finger. It stands down wherever
there is a caret: the search field and the commit message answer Escape
themselves.
Fixed
- Light back to System left the windows half-themed — the palette resolved
to dark while every window went on drawing light chrome, which reads as broken
colours and washed-out type. Light and Dark never showed it, because there the
next forced value overwrites the last. The forcing went through SwiftUI's
preferredColorScheme, which sets the window's appearance and does not unset
it when the value goes back to nil. The appearance is the application's now, so
unsetting it is an operation that exists — and nothing forced is what keeps the
Mac's own setting readable, which is the signal the System choice follows. - A local branch under a prefix drew a remote's pill —
feature/xis
spelled exactly like a branch on a remote named "feature", and the graph read
it as one, cloud icon and all. Decorations are read as full refnames now.
Refs outsiderefs/heads,refs/remotesandrefs/tagsdecorate nothing:
a stash's internals and a fetchedrefs/pull/*/headused to draw a pill named
after a path.
Requirements
- macOS 14 or later.
- A
git— whichever comes first on your login shell's PATH, else Apple's. With
none installed at all, the Welcome screen says so once, with the command that
fixes it.
Not here yet
- A command palette.
- Dragging a commit onto a branch. The rebase editor covers reordering a range;
history by drag-and-drop is a separate decision, and it hasn't been made.
During the beta
Free, for any use. Licensing starts at 1.0: free for personal projects, paid per
seat for commercial use — see the EULA.
The app updates itself — each update is checked against a signature before it is
applied. Settings ▸ Updates decides how much of that happens on its own. Bug
reports and requests go to the issue tracker.
Fretwire 0.4.0
The fourth beta closes the item the third one listed as not here yet: an
interactive rebase editor that takes a whole branch at once, rather than one
commit at a time. Beside it the app learned a light theme — the same design
solved for a light screen, not inverted — and the history pane got the work it
was owed: columns you can drag, lanes thin enough to stay out of the way of the
messages beside them, and a scroller that stays with the pane however far
sideways you have gone.
New
- An interactive rebase editor — one sheet holding the range from a base to HEAD, in the order git will replay it, with pick / reword / squash / drop and reordering on every row. Three ways in: Rebase from Here… on a commit, Rebase N Unpushed Commits… on the current branch (its menu, and the toolbar's branch pill), and a selection that forms a run ending at HEAD. No editor window ever opens:
rewordis written aspickandsquashasfixup, and every message this app decides on is applied by anexec git commit --amendline at the end of its group, so each resulting commit gets its own text. A plan that stops on a conflict stays put — Continue still works, because the files git's todo names are still where it left them. - A light theme — ⌘, ▸ Appearance ▸ Theme: System, Light, Dark. Every token keeps the hue and chroma of the dark token it replaces and moves only in lightness, until its contrast against the page matches what the dark theme already measures. Two places a mirror image would have been wrong: the lanes keep their lightness spread, so eight branches stay eight colours you can tell apart, and
conflictbecomes a terracotta — the system orange manages about 2.2:1 on a light ground, and an amber solved the usual way lands on top of the modified colour. - History columns you can drag — each fixed column now has a handle on the edge facing MESSAGE, a floor, a ceiling, and a width that persists across launches. MESSAGE stays elastic; it is whatever the others leave. And when the columns want more room than the pane has, the history scrolls sideways — header and rows together — instead of squeezing the message down to nothing and pushing the trailing columns off the edge.
- Lanes that don't shout — Graph lines (Thin / Regular / Bold, thin now the default) in the history's gear popover and in ⌘, Appearance, which draws a live sample of each. The glow's width and opacity move with the core, so a thin lane isn't a thin line under a fat bloom, and the node rings keep their own width — a node is the commit itself. Hovering a row steps every lane that isn't its branch back to 30%. Lane turns are rounded elbows now, which look the same crossing one column as they do crossing ten.
- A scroller that stays with the pane — the history's vertical indicator used to ride off the edge as soon as the columns were wider than the pane, so a vertical scroll gave no feedback at all. It is drawn outside the horizontal scroll now: at the pane's edge whatever the columns are doing, absent until something moves, lit under the pointer, draggable — and staying put under "Show scroll bars: Always", which it used to ignore.
- A sidebar that steps out of a diff — a new preference in ⌘, Appearance, off by default: while a diff has the middle pane, the branch list folds away and hands it the room. The toolbar button still brings it back, and there it means "for this diff", not "from now on".
Requirements
- macOS 14 or later.
- A
git— whichever comes first on your login shell's PATH, else Apple's. With none installed at all, the Welcome screen says so once, with the command that fixes it.
Not here yet
- A command palette.
- Dragging a commit onto a branch. The editor above covers reordering a range; history by drag-and-drop is a separate decision, and it hasn't been made.
During the beta
Free, for any use. Licensing starts at 1.0: free for personal projects, paid per seat for commercial use — see the EULA.
The app updates itself — each update is checked against a signature before it is applied. Settings ▸ Updates decides how much of that happens on its own. Bug reports and requests go to the issue tracker.
Fretwire 0.3.0
The third beta, and both of its features are about work that used to happen in a
terminal beside the app. A git worktree is now a section of the sidebar — one
you can add to, open as a tab, and remove from — and any file can be handed to
the diff or merge tool you already have set up, because Fretwire runs your
git difftool rather than reimplementing one. The DMG got a window drawn for it
along the way.
New
- Worktrees — a WORKTREES section appears in the sidebar as soon as a repository has one. Each row carries the folder, the branch it holds (or
detached at <sha>), and a lock or prunable marker; its menu is Open in New Tab · Reveal in Finder · Copy Path · Remove…, and the section header holds Add Worktree… and Prune Missing Worktrees. A worktree opens as a tab of its own, and removing one closes the tab that was showing it. The Add sheet takes an existing branch or creates a new one, and fills the folder name in as<repo>-<branch>. - A branch checked out somewhere else says where — a branch another worktree holds shows a folder marker, and its menu reads
Checked out in ~/code/app-reviewwith Open Worktree above it, rather than a Checkout that git was always going to refuse. Where a refusal does arrive — "is already used by worktree at …" — it comes with the same button instead of a dead end. So does the refusal to remove a worktree with uncommitted work, and a locked one's confirmation says out loud that the lock is what's being overridden. - Your own diff and merge tool — ⌘D on the diff pane, or Open in External Diff Tool in the staging and commit-detail menus, hands the file to
git difftool; a conflicted file gets Resolve in External Merge Tool andgit mergetool, which stages what it resolves. Both readdiff.tool/merge.toolout of your own git config, so a tool already working in your terminal needs no setting up here. Settings ▸ Git names the config line being followed, lists the tools this Mac actually has, and can override the choice for the app alone. One caveat, written there too: the tool has to wait for the editor —codewithout--waitreturns before git's temporary files can be read. - An installer window worth opening — the DMG opens on a fretboard, with the icon's own teal curve running out of the app and up into
/Applications, and the mounted volume carries the app's icon rather than a generic disk.
Fixed
- A folder whose name ends in a space —
git rev-parse --show-toplevelanswers with a path plus git's own newline, and trimming whitespace took the last character of such a name with it. Only git's terminator comes off now.
Requirements
- macOS 14 or later.
- A
git— whichever comes first on your login shell's PATH, else Apple's. With none installed at all, the Welcome screen says so once, with the command that fixes it.
Not here yet
- An interactive rebase editor. One commit at a time can be reworded, squashed or dropped; reordering a range still belongs in the terminal.
- A command palette.
During the beta
Free, for any use. Licensing starts at 1.0: free for personal projects, paid per seat for commercial use — see the EULA.
The app updates itself — each update is checked against a signature before it is applied. Settings ▸ Updates decides how much of that happens on its own. Bug reports and requests go to the issue tracker.
Fretwire 0.2.0
The second beta. Most of it is about reaching a remote from a Mac that is not
the one the app was built on: running the git you installed rather than the
one Apple ships, ssh remotes and the host keys they need, and failures that say
which failure they are. Plus a crash on a wedged pipe, the tabs you had open at
quit, and repository badges that use more than one letter.
New
- Your git, and your PATH — a Mac app inherits launchd's environment, not the login shell's, so every command ran as
/usr/bin/gitwith almost no PATH under it. The app now reads the login shell's PATH once per launch and runs the firstgiton it — or one named in Settings ▸ Git, else Apple's shim — which brings gh's credential helper, gpg for signed commits and acore.sshCommandalong with it. Settings ▸ Git shows which git was chosen, its version, where it came from, and the whole search path. - SSH remotes — clone and publish over ssh rather than https. Both sheets show which transport they will write, preselected from whether this Mac has a key to offer and changeable in place, and
Change URL…moves a remote that already exists. An ssh remote pushes with no token in its environment at all. - Host keys — a host this Mac had never spoken to failed with "Host key verification failed" and nothing to do about it, since ssh can only ask that question on a terminal. The failure card now offers to look:
ssh-keyscan's fingerprints beside a link to the list the host publishes, andknown_hostsappended to only once you say they match. A key that changed gets no such button, only thessh-keygen -Rcommand to copy — that is either a rekeyed server or someone in the middle, and the app cannot tell which. - A silent ssh agent is named as one — a push that went nowhere looked exactly like a slow one, and the timeout blamed the network. An operation that involves ssh and is still running after eight seconds asks the agent whether it answers at all; if it does not, the app says so, with a Stop and the command to see it for yourself.
- Pushing a branch that has no upstream — creates it on the remote and records the upstream with it, so the toolbar's ahead/behind works and the next plain push lands. A one-off push to another remote publishes the branch there without quietly redirecting where it pulls from.
- The tabs from last time — the repositories open at quit, and which one was in front, come back at launch. A restored tab is not a visit: it does not reshuffle the recents order, and a path that has since moved or been deleted is dropped instead of opening as an error tab.
- Badges that tell repositories apart (#2) — the icon rail took the first letter of a name, so project-a, project-b and project-c all read "P". A badge now abbreviates the whole name, a letter from each of the first two words (project-a → PA, MyApp → MA). Where no rule can separate two names, it can be set by hand — letters and a colour, kept per repository, ahead of any icon found on disk.
- Install with Homebrew (#1) —
brew install --cask olegklimakov/fretwire/fretwire. Updates still arrive through the app.
Fixed
- A wedged pipe no longer takes the app with it — when something held a child's output pipe open past the child itself — a lingering
git-remote-https, an ssh ControlPersist master — the waiting thread was reclaimed by closing the descriptor under a read still in flight, andFileHandleanswers that with an Objective-C exception no Swiftcatchcan intercept. Output is read through DispatchIO now, where giving up on the rest is an ordinary cancel. - Remote failures say which failure they are — having no credentials at all was reported as "the remote rejected these credentials", which was both untrue and a dead end. Missing credentials, rejected credentials, a refused key, an unknown or changed host key and an unreachable host are now told apart, each naming the host git named, and a missing-credentials card offers to connect an account for it.
- The detail column stays where it was put — opening a file from the commit-detail list re-divided the window, and the column collected a little more width with every file until it stood at its maximum.
- "Use Default Icon" is offered to VoiceOver — the badge's context menu reaches VoiceOver as named actions, and the reset was missing from that list, so a custom badge could only be undone by reopening the sheet.
Requirements
- macOS 14 or later.
- A
git— whichever comes first on your login shell's PATH, else Apple's. With none installed at all, the Welcome screen says so once, with the command that fixes it.
Not here yet
- An interactive rebase editor. One commit at a time can be reworded, squashed or dropped; reordering a range still belongs in the terminal.
- A command palette.
During the beta
Free, for any use. Licensing starts at 1.0: free for personal projects, paid per seat for commercial use — see the EULA.
The app updates itself — each update is checked against a signature before it is applied. Settings ▸ Updates decides how much of that happens on its own. Bug reports and requests go to the issue tracker.
Fretwire 0.1.0
The first beta of Fretwire — a native macOS Git client built around the commit graph, with staging, diffs, and the everyday branch and remote operations attached to it. Every read and write goes through Apple's git, so what the app does is what the terminal would have done.
In this build
- Commit graph — swim-lane layout with ref badges, per-identity author colours, stashes shown where they were taken, and a marked boundary where the checked-out branch diverges from the default one.
- Staging — stage and unstage by file or by hunk, amend, commit; the working tree sits as a row above HEAD whenever it is dirty.
- Diffs — side-by-side or unified, syntax-highlighted, with image previews, binary placeholders, and per-file history.
- Branches, remotes, tags, stashes — checkout, create, rename, delete, merge, rebase, cherry-pick (one commit or a selection), revert, reset, fetch/pull/push per remote, and publishing to GitHub or GitLab. Anything that moves a branch confirms first; force push is only ever
--force-with-lease, scoped to one branch. - Rewriting history — reword a message, squash a commit into its parent, or drop one, from the commit's own menu. Each says plainly that ids change, and refuses rather than half-applying when the tree is dirty.
- Conflicts — a stopped merge, rebase, cherry-pick, revert or
git amis detected and shown with Abort and Continue, and conflicted files keep their diff. - Several repositories at once — an icon rail on the left; each tab keeps its own history, selection and load state.
- Live refresh — commits made in the terminal appear in the graph without a manual reload and without blanking the view.
- Adjustable text size — the whole type ramp scales from ⌘, ▸ Appearance, or with ⌘+ / ⌘− / ⌘0.
Requirements
- macOS 14 or later.
- Apple's command line tools, for
/usr/bin/git. If they are missing, the app says so and names the fix.
Not here yet
- An interactive rebase editor. One commit at a time can be reworded, squashed or dropped; reordering a range still belongs in the terminal.
- A command palette.
During the beta
Free, for any use. Licensing starts at 1.0: free for personal projects, paid per seat for commercial use — see the EULA.
The app updates itself — each update is checked against a signature before it is applied. Settings ▸ Updates decides how much of that happens on its own. Bug reports and requests go to the issue tracker.