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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/desktop/src/components/ChromeHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
const useCustomTitleBar = $derived(!($settingsStore?.ui.useNativeTitleBar ?? false));
const backend = inject(BACKEND);

const mode = $derived(modeService.mode({ projectId }));
const mode = $derived(modeService.mode(projectId));
const currentMode = $derived(mode.response);
const currentBranchName = $derived.by(() => {
if (currentMode?.type === 'OpenWorkspace') {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/components/NotOnGitButlerBranch.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
const [setBaseBranchTarget, targetBranchSwitch] = baseBranchService.setTarget;

const modeService = inject(MODE_SERVICE);
const mode = $derived(modeService.mode({ projectId }));
const mode = $derived(modeService.mode(projectId));

const worktreeService = inject(WORKTREE_SERVICE);
const changes = $derived(worktreeService.treeChanges(projectId));
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/components/SnapshotCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@
const operation = mapOperation(entry.details);

const modeService = inject(MODE_SERVICE);
const mode = $derived(modeService.mode({ projectId }));
const mode = $derived(modeService.mode(projectId));

const historyService = inject(HISTORY_SERVICE);
const snapshotDiff = $derived(historyService.snapshotDiff({ projectId, snapshotId: entry.id }));
Expand Down
41 changes: 37 additions & 4 deletions apps/desktop/src/lib/mode/modeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ interface HeadAndMode {
operatingMode?: Mode;
}

interface HeadSha {
headSha?: string;
}

export const MODE_SERVICE = new InjectionToken<ModeService>('ModeService');

export class ModeService {
Expand Down Expand Up @@ -69,8 +73,18 @@ export class ModeService {
return this.api.endpoints.changesSinceInitialEditState.useQuery;
}

get mode() {
return this.api.endpoints.mode.useQuery;
mode(projectId: string) {
return this.api.endpoints.headAndMode.useQuery(
{ projectId },
{ transform: (response) => response.operatingMode }
);
}

head(projectId: string) {
return this.api.endpoints.headSha.useQuery(
{ projectId },
{ transform: (response) => response.headSha }
);
}
}

Expand Down Expand Up @@ -136,7 +150,7 @@ function injectEndpoints(api: ClientState['backendApi']) {
unsubscribe();
}
}),
mode: build.query<Mode, { projectId: string }>({
headAndMode: build.query<HeadAndMode, { projectId: string }>({
extraOptions: { command: 'operating_mode' },
query: (args) => args,
providesTags: [providesList(ReduxTag.HeadMetadata)],
Expand All @@ -148,7 +162,26 @@ function injectEndpoints(api: ClientState['backendApi']) {
const unsubscribe = lifecycleApi.extra.backend.listen<HeadAndMode>(
`project://${arg.projectId}/git/head`,
(event) => {
lifecycleApi.updateCachedData(() => event.payload.operatingMode);
lifecycleApi.updateCachedData(() => event.payload);
}
);
await lifecycleApi.cacheEntryRemoved;
unsubscribe();
}
}),
headSha: build.query<HeadSha, { projectId: string }>({
extraOptions: { command: 'head_sha' },
query: (args) => args,
providesTags: [providesList(ReduxTag.HeadMetadata)],
async onCacheEntryAdded(arg, lifecycleApi) {
if (!hasBackendExtra(lifecycleApi.extra)) {
throw new Error('Redux dependency Backend not found!');
}
await lifecycleApi.cacheDataLoaded;
const unsubscribe = lifecycleApi.extra.backend.listen<HeadSha>(
`project://${arg.projectId}/git/activity`,
(event) => {
lifecycleApi.updateCachedData(() => event.payload);
}
);
await lifecycleApi.cacheEntryRemoved;
Expand Down
10 changes: 10 additions & 0 deletions apps/desktop/src/lib/stacks/stackService.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -929,10 +929,20 @@ export class StackService {
])
);
}

invalidateStacks() {
this.dispatch(this.api.util.invalidateTags([invalidatesList(ReduxTag.Stacks)]));
}

invalidateStacksAndDetails() {
this.dispatch(
this.api.util.invalidateTags([
invalidatesList(ReduxTag.Stacks),
invalidatesList(ReduxTag.StackDetails)
])
);
}

templates(projectId: string, forgeName: string) {
return this.api.endpoints.templates.useQuery({ projectId, forge: forgeName });
}
Expand Down
12 changes: 11 additions & 1 deletion apps/desktop/src/routes/[projectId]/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
const stackService = inject(STACK_SERVICE);
const worktreeService = inject(WORKTREE_SERVICE);
const modeQuery = $derived(modeService.mode({ projectId }));
const modeQuery = $derived(modeService.mode(projectId));
const mode = $derived(modeQuery.response);
// Invalidate stacks when switching branches outside workspace
Expand Down Expand Up @@ -214,6 +214,16 @@
stackService.stackDetailsUpdateListener(projectId);
});
const headResponse = $derived(modeService.head(projectId));
const head = $derived(headResponse.response);
// If the head changes, invalidate stacks and details
$effect(() => {
if (head) {
stackService.invalidateStacksAndDetails();
}
});
Comment on lines +217 to +226
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mtsgrd Let's maybe talk about this?
This is the approach to updating the frontend based on the workspace SHA and not the DB updates

// =============================================================================
// AUTO-REFRESH & SYNCHRONIZATION
// =============================================================================
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/routes/[projectId]/edit/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// TODO: Refactor so we don't need non-null assertion.
const projectId = $derived(page.params.projectId!);
const modeService = inject(MODE_SERVICE);
const mode = $derived(modeService.mode({ projectId }));
const mode = $derived(modeService.mode(projectId));

let editModeMetadata = $state<EditModeMetadata>();

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/routes/[projectId]/workspace/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
const modeService = inject(MODE_SERVICE);

const projectId = $derived(page.params.projectId!);
const mode = $derived(modeService.mode({ projectId }));
const mode = $derived(modeService.mode(projectId));
const uiState = inject(UI_STATE);
const stackService = inject(STACK_SERVICE);
const projectState = $derived(uiState.project(projectId));
Expand Down
41 changes: 39 additions & 2 deletions crates/but-api/src/commands/modes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,50 @@ use tracing::instrument;

use crate::error::Error;

#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HeadAndMode {
pub head: Option<String>,
pub operating_mode: Option<OperatingMode>,
}

#[api_cmd]
#[cfg_attr(feature = "tauri", tauri::command(async))]
#[instrument(err(Debug))]
pub fn operating_mode(project_id: ProjectId) -> Result<OperatingMode, Error> {
pub fn operating_mode(project_id: ProjectId) -> Result<HeadAndMode, Error> {
let project = gitbutler_project::get(project_id)?;
let ctx = CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
Ok(gitbutler_operating_modes::operating_mode(&ctx))
let head = match ctx.repo().head() {
Ok(head_ref) => head_ref.shorthand().map(|s| s.to_string()),
Err(_) => None,
};

Ok(HeadAndMode {
head,
operating_mode: Some(gitbutler_operating_modes::operating_mode(&ctx)),
})
}

#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HeadSha {
head_sha: String,
}

#[api_cmd]
#[cfg_attr(feature = "tauri", tauri::command(async))]
#[instrument(err(Debug))]
pub fn head_sha(project_id: ProjectId) -> Result<HeadSha, Error> {
let project = gitbutler_project::get(project_id)?;
let ctx = CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
let head_ref = ctx.repo().head().context("failed to get head")?;
let head_sha = head_ref
.peel_to_commit()
.context("failed to get head commit")?
.id()
.to_string();

Ok(HeadSha { head_sha })
}

#[api_cmd]
Expand Down
1 change: 1 addition & 0 deletions crates/but-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ async fn handle_command(
}
// Operating modes commands
"operating_mode" => modes::operating_mode_cmd(request.params),
"head_sha" => modes::head_sha_cmd(request.params),
"enter_edit_mode" => modes::enter_edit_mode_cmd(request.params),
"abort_edit_and_return_to_workspace" => {
modes::abort_edit_and_return_to_workspace_cmd(request.params)
Expand Down
9 changes: 7 additions & 2 deletions crates/but-server/src/projects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,14 @@ impl ActiveProjects {
name: format!("project://{project_id}/git/head"),
payload: serde_json::json!({ "head": head, "operatingMode": operating_mode }),
},
Change::GitActivity(project_id) => FrontendEvent {
Change::GitActivity {
project_id,
head_sha,
} => FrontendEvent {
name: format!("project://{project_id}/git/activity"),
payload: serde_json::json!({}),
payload: serde_json::json!({
"headSha": head_sha,
}),
},
Change::WorktreeChanges {
project_id,
Expand Down
1 change: 1 addition & 0 deletions crates/gitbutler-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ fn main() -> anyhow::Result<()> {
remotes::list_remotes,
remotes::add_remote,
modes::operating_mode,
modes::head_sha,
modes::enter_edit_mode,
modes::save_edit_and_return_to_workspace,
modes::abort_edit_and_return_to_workspace,
Expand Down
9 changes: 7 additions & 2 deletions crates/gitbutler-tauri/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,14 @@ pub(crate) mod state {
payload: serde_json::json!({ "head": head, "operatingMode": operating_mode }),
project_id,
},
Change::GitActivity(project_id) => ChangeForFrontend {
Change::GitActivity {
project_id,
head_sha,
} => ChangeForFrontend {
name: format!("project://{project_id}/git/activity"),
payload: serde_json::json!({}),
payload: serde_json::json!({
"headSha": head_sha,
}),
project_id,
},
Change::WorktreeChanges {
Expand Down
2 changes: 2 additions & 0 deletions crates/gitbutler-watcher/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ but-settings.workspace = true
but-hunk-assignment.workspace = true
but-hunk-dependency.workspace = true
serde-error = "0.1.3"
gix.workspace = true
gitbutler-oxidize.workspace = true

[lints.clippy]
all = "deny"
Expand Down
5 changes: 4 additions & 1 deletion crates/gitbutler-watcher/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ pub enum Change {
head: String,
operating_mode: OperatingMode,
},
GitActivity(ProjectId),
GitActivity {
project_id: ProjectId,
head_sha: String,
},
WorktreeChanges {
project_id: ProjectId,
changes: but_hunk_assignment::WorktreeChanges,
Expand Down
12 changes: 11 additions & 1 deletion crates/gitbutler-watcher/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,17 @@ impl Handler {
self.emit_app_event(Change::GitFetch(ctx.project().id))?;
}
"logs/HEAD" => {
self.emit_app_event(Change::GitActivity(ctx.project().id))?;
let repo = ctx.repo();
let head_ref = repo.head().context("failed to get head")?;
let head_sha = head_ref
.peel_to_commit()
.context("failed to get head commit")?
.id()
.to_string();
self.emit_app_event(Change::GitActivity {
project_id: ctx.project().id,
head_sha,
})?;
}
"index" => {
let _ = self.emit_worktree_changes(ctx);
Expand Down
Loading