Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
123 changes: 121 additions & 2 deletions macos/Sources/Lithe/Core/Rust/RustCoreBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,59 @@ struct RustCoreBridge: Sendable {
}
}

struct GitReferencesPayload: Decodable, Sendable {
let references: [GitHistoryPayload.Reference]
let recentReferences: [GitHistoryPayload.Reference]
let userName: String?
let userEmail: String?

func makeSnapshot() -> GitReferenceSnapshot {
GitReferenceSnapshot(
references: references.compactMap(makeReference),
recentReferences: recentReferences.compactMap(makeReference),
identity: (userName == nil && userEmail == nil)
? nil
: GitIdentity(name: userName, email: userEmail)
)
}

private func makeReference(_ reference: GitHistoryPayload.Reference) -> GitReference? {
guard let kind = GitReferenceKind(rawValue: reference.kind) else { return nil }
return GitReference(
fullName: reference.fullName,
shortName: reference.shortName,
kind: kind,
isCurrent: reference.isCurrent,
upstreamShortName: reference.upstreamShortName
)
}
}

struct GitHistoryPagePayload: Decodable, Sendable {
let commits: [GitHistoryPayload.Commit]
let nextCursor: String?
let hasMore: Bool

func makePage() -> GitHistoryPage {
GitHistoryPage(
commits: commits.map { commit in
GitCommit(
hash: commit.hash,
shortHash: commit.shortHash,
parentHashes: commit.parentHashes,
authorName: commit.authorName,
authorEmail: commit.authorEmail,
date: commit.date,
subject: commit.subject,
decorations: commit.decorations
)
},
nextCursor: nextCursor,
hasMore: hasMore
)
}
}

struct GitCommitPayload: Decodable, Sendable {
let commit: GitHistoryPayload.Commit

Expand Down Expand Up @@ -1784,6 +1837,26 @@ struct RustCoreBridge: Sendable {
let limit: Int
}

private struct GitReferencesRequest: Encodable {
let root: String
}

private struct GitHistoryPageRequest: Encodable {
let root: String
let reference: String?
let cursor: String?
let limit: Int
}

private struct GitHistoryCursorCloseRequest: Encodable {
let root: String
let cursor: String
}

private struct GitHistoryCursorClosePayload: Decodable {
let closed: Bool
}

private struct GitCommitRequest: Encodable {
let root: String
let commit: String
Expand Down Expand Up @@ -2847,6 +2920,47 @@ struct RustCoreBridge: Sendable {
)
}

func gitReferences(
at rootURL: URL,
operationID: String
) -> GitReferencesPayload? {
execute(
command: "git.references",
payload: GitReferencesRequest(root: rootURL.standardizedFileURL.path),
operationID: operationID
)
}

func gitHistoryPage(
at rootURL: URL,
reference: String?,
cursor: String?,
limit: Int,
operationID: String
) -> GitHistoryPagePayload? {
execute(
command: "git.historyPage",
payload: GitHistoryPageRequest(
root: rootURL.standardizedFileURL.path,
reference: reference,
cursor: cursor,
limit: limit
),
operationID: operationID
)
}

func closeGitHistoryCursor(at rootURL: URL, cursor: String) -> Bool {
let payload: GitHistoryCursorClosePayload? = execute(
command: "git.historyCursorClose",
payload: GitHistoryCursorCloseRequest(
root: rootURL.standardizedFileURL.path,
cursor: cursor
)
)
return payload?.closed ?? false
}

func gitCommit(at rootURL: URL, commit: String) -> GitCommitPayload? {
execute(
command: "git.commit",
Expand Down Expand Up @@ -3369,9 +3483,14 @@ struct RustCoreBridge: Sendable {

private func execute<Payload: Encodable, Data: Decodable>(
command: String,
payload: Payload
payload: Payload,
operationID: String? = nil
) -> Data? {
try? executeResult(command: command, payload: payload).get()
try? executeResult(
command: command,
payload: payload,
operationID: operationID
).get()
}

/// Runs a command whose success carries no data. The core encodes those as a
Expand Down
28 changes: 28 additions & 0 deletions macos/Sources/Lithe/Core/Rust/RustGitOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,34 @@ struct RustGitOperations: GitOperations, Sendable {
)?.makeSnapshot()
}

func references(at rootURL: URL, operationID: String) -> GitReferenceSnapshot? {
core.gitReferences(at: rootURL, operationID: operationID)?.makeSnapshot()
}

func historyPage(
at rootURL: URL,
reference: GitReference?,
cursor: String?,
limit: Int,
operationID: String
) -> GitHistoryPage? {
core.gitHistoryPage(
at: rootURL,
reference: reference?.fullName,
cursor: cursor,
limit: limit,
operationID: operationID
)?.makePage()
}

func closeHistoryCursor(at rootURL: URL, cursor: String) -> Bool {
core.closeGitHistoryCursor(at: rootURL, cursor: cursor)
}

func cancel(operationID: String) -> Bool {
core.cancel(operationID: operationID)
}

func files(in commit: GitCommit, at rootURL: URL) -> [GitCommitFile]? {
core.gitCommitFiles(at: rootURL, commit: commit.hash)?.files.map { file in
GitCommitFile(status: file.status, path: file.path)
Expand Down
3 changes: 3 additions & 0 deletions macos/Sources/Lithe/Models/AppModel/AppModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1542,11 +1542,14 @@ final class AppModel: ObservableObject, Identifiable {
}
if isGitLogVisible && gitCommits.isEmpty {
await refreshGitHistory()
} else if !isGitLogVisible {
gitFeatureIfActive?.cancelGitHistoryLoading()
}
}

func closeGitLog() {
isGitLogVisible = false
gitFeatureIfActive?.cancelGitHistoryLoading()
}

func selectGitReference(_ reference: GitReference?) async {
Expand Down
118 changes: 99 additions & 19 deletions macos/Sources/LitheGitModule/Application/GitFeatureModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ package final class GitFeatureModel: ObservableObject {
private var onGitOperationBegan: (@MainActor () -> Void)?
private var onGitOperationEnded: (@MainActor () async -> Void)?
private var acquireModuleLease: (@MainActor (String) -> ModuleLease)?
private var gitHistoryLimit = 300
private static let gitHistoryPageSize = 100
private var gitHistoryCursor: String?
private var gitHistoryGeneration = UUID()
private var activeGitHistoryOperationIDs: Set<String> = []
private var deferredSavedChanges: GitDeferredSavedChanges?
private var nextRefreshRequestID: UInt64 = 0
private var activeRefreshRequestIDs: Set<UInt64> = []
Expand Down Expand Up @@ -180,6 +183,7 @@ package final class GitFeatureModel: ObservableObject {
}

package func reset() {
cancelGitHistoryLoading()
gitChanges = []
pendingStagingStates = [:]
gitStashes = []
Expand Down Expand Up @@ -220,7 +224,7 @@ package final class GitFeatureModel: ObservableObject {
commitPathsByHash = [:]
clearGitCommitFilesCache()
gitLogFilterGeneration = UUID()
gitHistoryLimit = 300
gitHistoryCursor = nil
isLoadingGitHistory = false
isLoadingMoreGitHistory = false
canLoadMoreGitHistory = false
Expand Down Expand Up @@ -1257,29 +1261,55 @@ package final class GitFeatureModel: ObservableObject {

package func selectGitReference(_ reference: GitReference?) async {
selectedGitReference = reference
gitHistoryLimit = 300
canLoadMoreGitHistory = false
await refreshGitHistory()
}

package func refreshGitHistory() async {
guard let gitRepositoryRoot, !isLoadingGitHistory else { return }
guard let gitRepositoryRoot else { return }
cancelGitHistoryLoading()
let generation = gitHistoryGeneration
let selectedReference = selectedGitReference
let referencesOperationID = gitHistoryOperationID(kind: "references", generation: generation)
let pageOperationID = gitHistoryOperationID(kind: "page", generation: generation)
activeGitHistoryOperationIDs.formUnion([referencesOperationID, pageOperationID])
isLoadingGitHistory = true
let previousCommitHash = selectedGitCommit?.hash
let snapshot = await service.history(
async let references = service.references(
at: gitRepositoryRoot,
operationID: referencesOperationID
)
async let page = service.historyPage(
at: gitRepositoryRoot,
reference: selectedGitReference,
limit: gitHistoryLimit
reference: selectedReference,
cursor: nil,
limit: Self.gitHistoryPageSize,
operationID: pageOperationID
)
gitReferences = snapshot.references
recentGitReferences = snapshot.recentReferences
gitCommits = snapshot.commits
gitIdentity = snapshot.identity
canLoadMoreGitHistory = snapshot.hasMore

let nextCommit = snapshot.commits.first(where: { $0.hash == previousCommitHash })
?? snapshot.commits.first
let (referenceSnapshot, historyPage) = await (references, page)
activeGitHistoryOperationIDs.subtract([referencesOperationID, pageOperationID])
guard gitHistoryGeneration == generation,
self.gitRepositoryRoot == gitRepositoryRoot,
selectedGitReference == selectedReference else {
if let cursor = historyPage?.nextCursor {
service.closeHistoryCursor(at: gitRepositoryRoot, cursor: cursor)
}
return
}
isLoadingGitHistory = false
guard let historyPage else { return }

if let referenceSnapshot {
gitReferences = referenceSnapshot.references
recentGitReferences = referenceSnapshot.recentReferences
gitIdentity = referenceSnapshot.identity
}
gitCommits = historyPage.commits
gitHistoryCursor = historyPage.nextCursor
canLoadMoreGitHistory = historyPage.hasMore

let nextCommit = historyPage.commits.first(where: { $0.hash == previousCommitHash })
?? historyPage.commits.first
if let nextCommit {
if previousCommitHash == nextCommit.hash {
selectedGitCommit = nextCommit
Expand Down Expand Up @@ -1365,11 +1395,61 @@ package final class GitFeatureModel: ObservableObject {
}

package func loadMoreGitHistory() async {
guard canLoadMoreGitHistory, !isLoadingGitHistory else { return }
guard let gitRepositoryRoot,
let cursor = gitHistoryCursor,
canLoadMoreGitHistory,
!isLoadingGitHistory,
!isLoadingMoreGitHistory else { return }
let generation = gitHistoryGeneration
let selectedReference = selectedGitReference
let operationID = gitHistoryOperationID(kind: "page-more", generation: generation)
activeGitHistoryOperationIDs.insert(operationID)
isLoadingMoreGitHistory = true
defer { isLoadingMoreGitHistory = false }
gitHistoryLimit += 300
await refreshGitHistory()
gitHistoryCursor = nil
let page = await service.historyPage(
at: gitRepositoryRoot,
reference: selectedReference,
cursor: cursor,
limit: Self.gitHistoryPageSize,
operationID: operationID
)
activeGitHistoryOperationIDs.remove(operationID)
guard gitHistoryGeneration == generation,
self.gitRepositoryRoot == gitRepositoryRoot,
selectedGitReference == selectedReference else {
if let cursor = page?.nextCursor {
service.closeHistoryCursor(at: gitRepositoryRoot, cursor: cursor)
}
return
}
isLoadingMoreGitHistory = false
guard let page else {
canLoadMoreGitHistory = false
return
}

let loadedHashes = Set(gitCommits.map(\.hash))
gitCommits.append(contentsOf: page.commits.filter { !loadedHashes.contains($0.hash) })
gitHistoryCursor = page.nextCursor
canLoadMoreGitHistory = page.hasMore
}

package func cancelGitHistoryLoading() {
gitHistoryGeneration = UUID()
if let gitRepositoryRoot, let cursor = gitHistoryCursor {
service.closeHistoryCursor(at: gitRepositoryRoot, cursor: cursor)
}
gitHistoryCursor = nil
for operationID in activeGitHistoryOperationIDs {
service.cancel(operationID: operationID)
}
activeGitHistoryOperationIDs.removeAll()
isLoadingGitHistory = false
isLoadingMoreGitHistory = false
}

private func gitHistoryOperationID(kind: String, generation: UUID) -> String {
"git-history-\(kind)-\(generation.uuidString)"
}

package func selectGitCommit(_ commit: GitCommit) async {
Expand Down
Loading
Loading