Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
874f7bc
feat: continue and skip rebase
craigharman Jun 19, 2026
f55ca09
fix: git tests for rebasing
craigharman Jun 19, 2026
7e07df3
feat: liquid glass interface
craigharman Jun 19, 2026
8f7c2c9
feat: add copy to clipboard button to error banner
craigharman Jun 19, 2026
3852ef3
Merge branch 'main' into develop
craigharman Jun 19, 2026
eac879e
feat:automatic software updates using Sparkle
craigharman Jun 19, 2026
9074f29
Gate the step on secrets.SPARKLE_PRIVATE_KEY
craigharman Jun 19, 2026
13f17fe
Ensure we are committing on the latest main branch
craigharman Jun 19, 2026
7487310
Disable update option if sparkle unavailable
craigharman Jun 19, 2026
354016d
Fail loudly on build failure
craigharman Jun 19, 2026
cd5dde1
Potential fix for pull request finding
craigharman Jun 19, 2026
d49ba06
fix: prevent appcast.xml commit from retriggering Release workflow
Copilot Jun 19, 2026
0c9ce1c
Strip any new lines from secret
craigharman Jun 19, 2026
3293625
Warn if sparkle not set
craigharman Jun 19, 2026
a6a5c24
Add guard when version already exists
craigharman Jun 19, 2026
ff59aa8
Fix versioning issues with sparkle
craigharman Jun 19, 2026
ee5d2b7
Potential fix for pull request finding
craigharman Jun 19, 2026
730c686
Potential fix for pull request finding
craigharman Jun 19, 2026
7b23eeb
Potential fix for pull request finding
craigharman Jun 19, 2026
7e96235
Skip appcast.xml when no changes
craigharman Jun 19, 2026
b904ab8
Update agents and claude files
craigharman Jun 19, 2026
4f1a868
fix: sign_update matched the wrong binary
craigharman Jun 19, 2026
895e093
Merge develop changes
craigharman Jun 19, 2026
a9338d0
Merge branch 'main' into develop
craigharman Jun 19, 2026
626e3f8
feat: allow push/pull indicators in sidebar to actually do the push/p…
craigharman Jun 19, 2026
7a1a0a6
fix: when pushing a remote branch include the remote name as well as …
craigharman Jun 19, 2026
70df5fd
Do not fallback to origin show error instead
craigharman Jun 19, 2026
ff5ed2c
fix: make the sparkle search less specific
craigharman Jun 19, 2026
0f530b7
Merge branch 'main' into develop
craigharman Jun 20, 2026
b3e181d
Fix release.yml
craigharman Jun 20, 2026
528f9e7
Merge branch 'main' into develop
craigharman Jun 20, 2026
ff6c4ac
fix: download Sparkle into project from CI
craigharman Jun 20, 2026
6e8b217
fix: access to sparkle key file
craigharman Jun 20, 2026
505d31a
Merge main into develop
craigharman Jun 20, 2026
93cafbd
Merge branch 'main' into develop
craigharman Jun 20, 2026
4028d96
fix: add debugging to deployment
craigharman Jun 20, 2026
fef2820
Merge branch 'main' into develop
craigharman Jun 20, 2026
25103c1
fix: add automerge to release
craigharman Jun 20, 2026
dfb183d
test:Add checks for pull/push numbers
craigharman Jun 21, 2026
6b7a74d
feat:automate deployment workflow
craigharman Jun 22, 2026
1707933
fix: merge conflicts not visible when files are staged
craigharman Jun 23, 2026
563c1d9
Complete merge
craigharman Jun 23, 2026
7a88a41
fix: use token for deployment
craigharman Jun 23, 2026
a0addf8
feat: add remote SSH git repository management
craigharman Jun 24, 2026
03f2837
fix: do not allow banner colour to bleed into sidebar
craigharman Jun 24, 2026
133bb9c
chore: updated clipping on main panel
craigharman Jun 24, 2026
0b5e9f3
ci:remove duplicate tests and use the release token to trigger the bu…
craigharman Jun 24, 2026
c3c82c2
Merge from main
craigharman Jun 24, 2026
57de7ef
Merge branch 'main' into develop
craigharman Jun 24, 2026
6359e71
feat: add scan folder feature to allow bulk import of repositories
craigharman Jun 27, 2026
1300bb7
feat: image diff viewer
craigharman Jun 30, 2026
cc46caf
chore: add before/after image comparison options
craigharman Jun 30, 2026
f0e800d
Merge branch 'main' into develop
craigharman Jun 30, 2026
c5b6d01
Merge branch 'main' into develop
craigharman Jun 30, 2026
c69f4c5
fix: check for updates now available in menu
craigharman Jun 30, 2026
dea8b6b
chore: turn of update check in dev builds
craigharman Jun 30, 2026
f38dedb
feat: add what's new popup to show user change log
craigharman Jun 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ let package = Package(
"GitKit",
.product(name: "Sparkle", package: "Sparkle"),
],
resources: [.copy("Resources/AppIcon.icns")]
resources: [
.copy("Resources/AppIcon.icns"),
.copy("Resources/CHANGELOG.md"),
]
),
// Full Xcode (XCTest / swift-testing) isn't installed, so the engine's
// verification suite runs as a standalone executable harness instead.
Expand Down
7 changes: 7 additions & 0 deletions Sources/Gitify/Model/AppDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ enum AppDefaults {

private static let terminalKey = "defaultTerminalBundleID"
private static let editorKey = "defaultEditorBundleID"
private static let lastSeenVersionKey = "whatsNew.lastSeenVersion"

// MARK: - Accessors

Expand All @@ -22,6 +23,12 @@ enum AppDefaults {
set { UserDefaults.standard.set(newValue, forKey: editorKey) }
}

/// The app version the user last saw the \u{201c}What\u{2019}s New\u{201d} sheet for.
static var lastSeenVersion: String? {
get { UserDefaults.standard.string(forKey: lastSeenVersionKey) }
set { UserDefaults.standard.set(newValue, forKey: lastSeenVersionKey) }
}

/// Resolves a bundle identifier to an application URL, or `nil` if not installed.
static func appURL(for bundleID: String) -> URL? {
NSWorkspace.shared.urlsForApplications(withBundleIdentifier: bundleID).first
Expand Down
107 changes: 107 additions & 0 deletions Sources/Gitify/Model/ChangelogParser.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import Foundation

/// A single release entry parsed from CHANGELOG.md.
struct ReleaseEntry: Identifiable, Sendable {
let version: String
let date: String
let sections: [Section]

var id: String { version }

struct Section: Sendable {
let heading: String
let items: [String]
}
}

/// Parses a keep-a-changelog style CHANGELOG.md into structured release entries.
enum ChangelogParser {

/// Parse the full changelog text and return releases in document order (newest first).
static func parse(_ text: String) -> [ReleaseEntry] {
let lines = text.components(separatedBy: "\n")
var entries: [ReleaseEntry] = []

var currentVersion: String?
var currentDate: String?
var currentSections: [ReleaseEntry.Section] = []
var currentHeading: String?
var currentItems: [String] = []

for line in lines {
// Release header: ## [1.12.0](url) (2026-06-30)
if line.hasPrefix("## [") {
// Flush previous entry
if let version = currentVersion, let date = currentDate {
if let heading = currentHeading, !currentItems.isEmpty {
currentSections.append(.init(heading: heading, items: currentItems))
}
entries.append(.init(version: version, date: date, sections: currentSections))
}

// Parse new header
let stripped = line.dropFirst(4) // remove "## ["
if let closeBracket = stripped.firstIndex(of: "]") {
currentVersion = String(stripped[stripped.startIndex..<closeBracket])
} else {
currentVersion = nil
}
// Date is in the last parentheses
if let lastOpen = line.lastIndex(of: "("),
let lastClose = line.lastIndex(of: ")"),
lastOpen < lastClose {
let dateStart = line.index(after: lastOpen)
currentDate = String(line[dateStart..<lastClose])
} else {
currentDate = nil
}
currentSections = []
currentHeading = nil
currentItems = []
continue
}

// Section header: ### Features, ### Bug Fixes
if line.hasPrefix("### ") {
if let heading = currentHeading, !currentItems.isEmpty {
currentSections.append(.init(heading: heading, items: currentItems))
}
currentHeading = String(line.dropFirst(4))
currentItems = []
continue
}

// Bullet item: * description ([#30](url)) ([hash](url))
if line.hasPrefix("* ") {
let cleaned = cleanItem(String(line.dropFirst(2)))
if !cleaned.isEmpty {
currentItems.append(cleaned)
}
}
}

// Flush final entry
if let version = currentVersion, let date = currentDate {
if let heading = currentHeading, !currentItems.isEmpty {
currentSections.append(.init(heading: heading, items: currentItems))
}
entries.append(.init(version: version, date: date, sections: currentSections))
}

return entries
}

/// Strip markdown link references like ([#30](url)) and ([hash](url)) from an item.
private static func cleanItem(_ text: String) -> String {
// Remove patterns like ([#30](url)) and ([hash](url))
var result = text
// Pattern: ([ ... ]( ... )) — parenthesised markdown links
while let openParen = result.range(of: " \\(\\[[^\\]]*\\]\\([^)]*\\)\\)", options: .regularExpression) {
result.removeSubrange(openParen)
}
result = result.trimmingCharacters(in: .whitespaces)
// Capitalize first letter for display.
guard let first = result.first else { return result }
return first.uppercased() + result.dropFirst()
}
}
29 changes: 23 additions & 6 deletions Sources/Gitify/Model/SparkleController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,39 @@ import Sparkle
///
/// `SPUStandardUpdaterController` must be created and used on the main thread,
/// so the entire class is confined to `@MainActor`.
@MainActor
@MainActor @Observable
final class SparkleController {
private let updaterController: SPUStandardUpdaterController
private var observation: NSKeyValueObservation?

/// Whether the \u{201c}Check for Updates\u{2026}\u{201d} menu item should be enabled.
var canCheckForUpdates: Bool {
updaterController.updater.canCheckForUpdates
}
private(set) var canCheckForUpdates = false

init() {
let startUpdater: Bool
#if DEBUG
startUpdater = false
#else
startUpdater = true
#endif

updaterController = SPUStandardUpdaterController(
startingUpdater: true,
startingUpdater: startUpdater,
updaterDelegate: nil,
userDriverDelegate: nil
)


guard startUpdater else { return }

observation = updaterController.updater.observe(
\.canCheckForUpdates,
options: [.initial, .new]
) { [weak self] updater, _ in
MainActor.assumeIsolated {
self?.canCheckForUpdates = updater.canCheckForUpdates
}
}

// Quiet check on launch; surfaces a prompt only if a newer version exists.
updaterController.updater.checkForUpdatesInBackground()
}
Expand Down
150 changes: 150 additions & 0 deletions Sources/Gitify/Resources/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Changelog

## [1.12.0](https://github.com/craigharman/gitify/compare/v1.11.0...v1.12.0) (2026-06-30)


### Features

* add image diff viewer ([#30](https://github.com/craigharman/gitify/issues/30)) ([a3129f4](https://github.com/craigharman/gitify/commit/a3129f4378918bc1dcd06f9aaa7b094518db1bde))

## [1.11.0](https://github.com/craigharman/gitify/compare/v1.10.0...v1.11.0) (2026-06-27)


### Features

* add "scan folders" to allow bulk import of repositories ([#28](https://github.com/craigharman/gitify/issues/28)) ([94f2352](https://github.com/craigharman/gitify/commit/94f2352dd6b5402f8f9ffe5fe5d2f8c5b7737a48))

## [1.10.0](https://github.com/craigharman/gitify/compare/v1.9.1...v1.10.0) (2026-06-24)


### Features

* add remote SSH git repository management ([#26](https://github.com/craigharman/gitify/issues/26)) ([4fb4bf1](https://github.com/craigharman/gitify/commit/4fb4bf13db9ec857ed3c10922edbcea5201b5cbf))

## [1.9.1](https://github.com/craigharman/gitify/compare/v1.9.0...v1.9.1) (2026-06-23)


### Bug Fixes

* merge conflicts not visible when changes are staged ([#24](https://github.com/craigharman/gitify/issues/24)) ([fa20ee4](https://github.com/craigharman/gitify/commit/fa20ee49ca4cad67e2f875539b1e3df3891deddc))

## [1.9.0](https://github.com/craigharman/gitify/compare/v1.8.3...v1.9.0) (2026-06-20)


### Features

* add automerge ([#22](https://github.com/craigharman/gitify/issues/22)) ([6b290b1](https://github.com/craigharman/gitify/commit/6b290b14fee0e41208471fe5068692479abb725d))

## [1.8.3](https://github.com/craigharman/gitify/compare/v1.8.2...v1.8.3) (2026-06-20)


### Bug Fixes

* add debugging to deployment script ([#20](https://github.com/craigharman/gitify/issues/20)) ([556fed0](https://github.com/craigharman/gitify/commit/556fed0268261e0115411126588383e4f76ca671))

## [1.8.2](https://github.com/craigharman/gitify/compare/v1.8.1...v1.8.2) (2026-06-20)


### Bug Fixes

* access to sparkle key file ([#18](https://github.com/craigharman/gitify/issues/18)) ([723f659](https://github.com/craigharman/gitify/commit/723f6591ea18b14d3e24b7da08efed150c0b0f77))

## [1.8.1](https://github.com/craigharman/gitify/compare/v1.8.0...v1.8.1) (2026-06-20)


### Bug Fixes

* download sparkle release as part of CI ([#16](https://github.com/craigharman/gitify/issues/16)) ([251c71a](https://github.com/craigharman/gitify/commit/251c71a3c5ab4b8fc3e3134e505d82f197affe97))

## [1.8.0](https://github.com/craigharman/gitify/compare/v1.7.0...v1.8.0) (2026-06-20)


### Features

* add sidebar push/pull buttons ([#13](https://github.com/craigharman/gitify/issues/13)) ([308ef97](https://github.com/craigharman/gitify/commit/308ef976c959ad7c7e734cc82d66904f1fb44287))
* fix deploy issue ([#12](https://github.com/craigharman/gitify/issues/12)) ([8ffc097](https://github.com/craigharman/gitify/commit/8ffc097a181415b5c9b3274325f5b64215e4d7d6))

## [1.7.0](https://github.com/craigharman/gitify/compare/v1.6.0...v1.7.0) (2026-06-19)


### Features

* automatic software updates using sparkle ([#10](https://github.com/craigharman/gitify/issues/10)) ([2276fe0](https://github.com/craigharman/gitify/commit/2276fe007d87d82f7f23ad7b20c5156929081ea2))

## [1.6.0](https://github.com/craigharman/gitify/compare/v1.5.0...v1.6.0) (2026-06-19)


### Features

* liquid glass interface added ([#8](https://github.com/craigharman/gitify/issues/8)) ([dc7ccff](https://github.com/craigharman/gitify/commit/dc7ccff1fab3a1dd56574e19249fa822f10fa1a4))

## [1.5.0](https://github.com/craigharman/gitify/compare/v1.4.2...v1.5.0) (2026-06-19)


### Features

* continue and skip rebase ([06a5099](https://github.com/craigharman/gitify/commit/06a5099a3e7b6a7e2f87d1ce515bc5ce5ba4ab2f))
* continue and skip rebase ([#6](https://github.com/craigharman/gitify/issues/6)) ([06a5099](https://github.com/craigharman/gitify/commit/06a5099a3e7b6a7e2f87d1ce515bc5ce5ba4ab2f))

## [1.4.2](https://github.com/craigharman/gitify/compare/v1.4.1...v1.4.2) (2026-06-18)


### Bug Fixes

* copy SwiftPM resource bundle into .app to prevent launch crash ([67e9d5b](https://github.com/craigharman/gitify/commit/67e9d5bab42cb16b7815e8f0428a87d26f79be41))

## [1.4.1](https://github.com/craigharman/gitify/compare/v1.4.0...v1.4.1) (2026-06-18)


### Bug Fixes

* versioning system ([f8899ee](https://github.com/craigharman/gitify/commit/f8899eedd958da6b8bd5fee1c44f7f6fb8469305))

## [1.4.0](https://github.com/craigharman/gitify/compare/v1.3.0...v1.4.0) (2026-06-18)


### Features

* ability to remove repos from gitify ([ee1e39d](https://github.com/craigharman/gitify/commit/ee1e39d2d1e96806fec9e1f3445eacc3ac09e35d))
* add a push after merge option ([92295ee](https://github.com/craigharman/gitify/commit/92295ee22d135dd8bd7266ec5e7afb893820db4e))
* add merge rebase option if that merge error occurs ([09ebf84](https://github.com/craigharman/gitify/commit/09ebf84698d513580ed7faad7e0ba2770cfc4cf3))
* add next/previous arrows to scroll through conflicts in conflict editor ([f9f11f8](https://github.com/craigharman/gitify/commit/f9f11f840b8388eb85ffa584ceb65eb417350ded))
* add push/pull counter indicators to sidebar. ([b99c241](https://github.com/craigharman/gitify/commit/b99c241718205da8543a303209724f668f808f9a))
* Add tree view for remote branches. ([58a6b58](https://github.com/craigharman/gitify/commit/58a6b5822ccd8aefc1b645386140e94099156de4))
* commit inspect, commit context menu, overview stats, remote push, stash diff and conflict resolution ([735f4fb](https://github.com/craigharman/gitify/commit/735f4fb22486f66176624eff2c71ed4c72e9efa0))
* Markdown rendering of readme.md on overview page ([02c42c9](https://github.com/craigharman/gitify/commit/02c42c94d55abb850aeb29b84e3b1c40babbe0f6))
* Merge current branch to another branch ([11911fb](https://github.com/craigharman/gitify/commit/11911fb25da85bb82529eab6b89a6b185d2470dd))
* search filter and submodules ([e67ae2d](https://github.com/craigharman/gitify/commit/e67ae2de92bd7ab5aea666d179096bb2ad449dd1))
* sidebar status saved on exit and restored on start. ([6daa1e7](https://github.com/craigharman/gitify/commit/6daa1e7f931a54c7b9e905b3a8cd9935a79a71ed))
* syntax highlighting added in conflict editor ([92151d3](https://github.com/craigharman/gitify/commit/92151d343942b0671c52c5aaf4e9374803e89760))


### Bug Fixes

* commit diff layout not showing when files staged. ([a884ff8](https://github.com/craigharman/gitify/commit/a884ff86c97d6b616cdecfddbc0c1d4fe7c6513e))
* delete branch after merge conflict ([d9ed59b](https://github.com/craigharman/gitify/commit/d9ed59bd85889737808bb33ef1d6ecc95a5d8a8a))
* The merge with push was reloading the merge window. This is now fixed. ([75b7cf9](https://github.com/craigharman/gitify/commit/75b7cf965081b2033c0cde859d8b9adc03eb3a4b))
* warning banner header colour no longer spills into the sidebar. ([381abe0](https://github.com/craigharman/gitify/commit/381abe0fdb978d8f0af8bbfd0a8fa3cb75aef012))

## [1.3.0](https://github.com/craigharman/gitify/compare/v1.2.0...v1.3.0) (2026-06-18)


### Features

* add a push after merge option ([92295ee](https://github.com/craigharman/gitify/commit/92295ee22d135dd8bd7266ec5e7afb893820db4e))
* add merge rebase option if that merge error occurs ([09ebf84](https://github.com/craigharman/gitify/commit/09ebf84698d513580ed7faad7e0ba2770cfc4cf3))


### Bug Fixes

* The merge with push was reloading the merge window. This is now fixed. ([75b7cf9](https://github.com/craigharman/gitify/commit/75b7cf965081b2033c0cde859d8b9adc03eb3a4b))

## [1.2.0](https://github.com/craigharman/gitify/compare/v1.1.0...v1.2.0) (2026-06-18)


### Features

* ability to remove repos from gitify ([ee1e39d](https://github.com/craigharman/gitify/commit/ee1e39d2d1e96806fec9e1f3445eacc3ac09e35d))
* add next/previous arrows to scroll through conflicts in conflict editor ([f9f11f8](https://github.com/craigharman/gitify/commit/f9f11f840b8388eb85ffa584ceb65eb417350ded))
* add push/pull counter indicators to sidebar. ([b99c241](https://github.com/craigharman/gitify/commit/b99c241718205da8543a303209724f668f808f9a))
* syntax highlighting added in conflict editor ([92151d3](https://github.com/craigharman/gitify/commit/92151d343942b0671c52c5aaf4e9374803e89760))
22 changes: 22 additions & 0 deletions Sources/Gitify/Views/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import SwiftUI
struct RootView: View {
@Environment(AppModel.self) private var model
@State private var showSSHServers = false
@State private var whatsNewEntries: WhatsNewContent?

var body: some View {
Group {
Expand Down Expand Up @@ -44,5 +45,26 @@ struct RootView: View {
}
}
.sheet(isPresented: $showSSHServers) { SSHServersView(model: model) }
.sheet(item: $whatsNewEntries) { entries in
WhatsNewView(entries: entries.items)
}
.onChange(of: whatsNewEntries?.id) { old, new in
if old != nil, new == nil {
AppDefaults.lastSeenVersion = currentAppVersion
}
}
.task {
let version = currentAppVersion
guard version != AppDefaults.lastSeenVersion else { return }
guard let url = Bundle.module.url(forResource: "CHANGELOG", withExtension: "md"),
let text = try? String(contentsOf: url, encoding: .utf8) else { return }
let entries = ChangelogParser.parse(text)
guard !entries.isEmpty else { return }
whatsNewEntries = WhatsNewContent(items: entries)
}
}

private var currentAppVersion: String {
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0"
}
}
Loading
Loading