Skip to content
Merged
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
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ jobs:
with:
path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }}
key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }}
- name: Desktop build
run: just desktop-build
- name: Desktop E2E build
run: pnpm -C desktop build:e2e
- name: Desktop smoke e2e
run: cd desktop && pnpm exec playwright test --project=smoke --shard=${{ matrix.shard }}/4
- name: Summarize flaky tests
Expand Down Expand Up @@ -393,8 +393,8 @@ jobs:
with:
path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }}
key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }}
- name: Desktop build
run: just desktop-build
- name: Desktop E2E build
run: pnpm -C desktop build:e2e
- name: Wait for integration services
run: |
wait_healthy() {
Expand Down
4 changes: 2 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ desktop-e2e-integration: _ensure-migrations
# Run only the e2e specs changed vs origin/main (both projects) before pushing
desktop-e2e-pre-push: _ensure-migrations
git fetch origin main
cd {{desktop_dir}} && pnpm build && pnpm exec playwright test --only-changed=origin/main
cd {{desktop_dir}} && pnpm build:e2e && pnpm exec playwright test --only-changed=origin/main

# Run all checks suitable for CI / pre-push (no infra needed)
ci: check test-unit desktop-test desktop-build desktop-tauri-check desktop-tauri-test web-build mobile-test
Expand Down Expand Up @@ -340,7 +340,7 @@ mesh-e2e-confidence:
desktop-screenshot *ARGS:
#!/usr/bin/env bash
set -euo pipefail
just desktop-build
pnpm -C {{desktop_dir}} build:e2e
cd {{desktop_dir}}
if ! curl -sf http://127.0.0.1:4173/ >/dev/null 2>&1; then
python3 -m http.server 4173 -d dist >/dev/null 2>&1 &
Expand Down
7 changes: 4 additions & 3 deletions desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build:e2e": "tsc && vite build --mode e2e",
"typecheck": "tsc --noEmit",
"check:file-sizes": "node ./scripts/check-file-sizes.mjs",
"check:px-text": "node ./scripts/check-px-text.mjs",
Expand All @@ -16,9 +17,9 @@
"test": "node --import ./test-loader.mjs --experimental-strip-types --test 'src/**/*.test.mjs'",
"preview": "vite preview",
"tauri": "tauri",
"test:e2e": "pnpm build && playwright test",
"test:e2e:smoke": "pnpm build && playwright test --project=smoke",
"test:e2e:integration": "pnpm build && playwright test --project=integration",
"test:e2e": "pnpm build:e2e && playwright test",
"test:e2e:smoke": "pnpm build:e2e && playwright test --project=smoke",
"test:e2e:integration": "pnpm build:e2e && playwright test --project=integration",
"test:e2e:report": "playwright show-report",
"tauri:build": "tauri build"
},
Expand Down
4 changes: 4 additions & 0 deletions desktop/src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ mod profile;
mod project_git;
mod project_git_diff;
mod project_git_exec;
mod project_git_workflow;
mod project_repo_paths;
mod project_terminal;
mod relay_members;
Expand All @@ -45,6 +46,7 @@ mod social;
mod team_snapshot;
mod teams;
mod updater;
mod window_chrome;
mod window_vibrancy;
mod workflows;
mod workspace;
Expand Down Expand Up @@ -84,13 +86,15 @@ pub use prevent_sleep::*;
pub use profile::*;
pub use project_git::*;
pub use project_git_diff::*;
pub use project_git_workflow::*;
pub use project_terminal::*;
pub use relay_members::*;
pub use relay_reconnect::*;
pub use social::*;
pub use team_snapshot::*;
pub use teams::*;
pub use updater::*;
pub use window_chrome::*;
pub use window_vibrancy::*;
pub use workflows::*;
pub use workspace::*;
95 changes: 68 additions & 27 deletions desktop/src-tauri/src/commands/project_git.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::project_git_exec::{
build_git_auth_config, clean_branch, run_git, validate_clone_url, GitAuthConfig,
build_git_auth_config, clean_branch, run_git, validate_workspace_clone_url, GitAuthConfig,
};
use super::project_repo_paths::{canonical_repos_roots, find_local_repo_dir};
use crate::app_state::AppState;
Expand Down Expand Up @@ -57,6 +57,7 @@ pub struct ProjectRepoSyncStatusInfo {
pub remote_branch: Option<String>,
pub remote_head: Option<String>,
pub remote_short_head: Option<String>,
pub merge_base: Option<String>,
pub ahead_count: usize,
pub behind_count: usize,
pub has_uncommitted_changes: bool,
Expand All @@ -70,6 +71,9 @@ pub struct ProjectRepoSyncStatusInfo {
pub struct ProjectRepoPushResult {
pub pushed: bool,
pub message: String,
pub branch: String,
pub commit: String,
pub merge_base: Option<String>,
}
#[derive(Serialize)]
pub struct ProjectRepoPullResult {
Expand Down Expand Up @@ -104,7 +108,7 @@ fn short_hash(hash: &str) -> String {
hash.chars().take(7).collect()
}

fn first_output_line(output: &str) -> Option<String> {
pub(crate) fn first_output_line(output: &str) -> Option<String> {
output
.lines()
.next()
Expand Down Expand Up @@ -492,6 +496,7 @@ fn compare_local_remote_status(
repo_dir: &std::path::Path,
clone_url: &str,
branch_name: Option<&str>,
base_branch: Option<&str>,
auth: &GitAuthConfig,
) -> ProjectRepoSyncStatusInfo {
let local_branch = run_git(&["branch", "--show-current"], Some(repo_dir), auth)
Expand All @@ -518,18 +523,20 @@ fn compare_local_remote_status(
auth,
);
}
let _ = run_git(
&[
"fetch",
"--quiet",
"--depth=100",
"--end-of-options",
"origin",
branch.as_str(),
],
Some(repo_dir),
auth,
);
let base_branch =
normalize_branch_option(base_branch).filter(|base_branch| *base_branch != branch);
let mut fetch_args = vec![
"fetch",
"--quiet",
"--depth=100",
"--end-of-options",
"origin",
branch.as_str(),
];
if let Some(base_branch) = base_branch.as_deref() {
fetch_args.push(base_branch);
}
let _ = run_git(&fetch_args, Some(repo_dir), auth);

let local_head = run_git(&["rev-parse", "HEAD"], Some(repo_dir), auth)
.ok()
Expand All @@ -542,6 +549,19 @@ fn compare_local_remote_status(
)
.ok()
.and_then(|output| first_output_line(&output));
let merge_base = base_branch.as_deref().and_then(|base_branch| {
run_git(
&[
"merge-base",
"HEAD",
format!("origin/{base_branch}").as_str(),
],
Some(repo_dir),
auth,
)
.ok()
.and_then(|output| first_output_line(&output))
});
let status = run_git(&["status", "--porcelain"], Some(repo_dir), auth).unwrap_or_default();
let has_uncommitted_changes = has_uncommitted_changes(&status);
let has_untracked_files = has_untracked_files(&status);
Expand Down Expand Up @@ -576,6 +596,10 @@ fn compare_local_remote_status(

let push_block_reason = if local_head.is_none() {
Some("No local commits to push.".to_string())
} else if local_branch.as_deref() != Some(branch.as_str()) {
Some(format!(
"Local checkout is on a different branch than {branch}."
))
} else if has_uncommitted_changes || has_untracked_files {
Some("Commit or discard local changes before pushing.".to_string())
} else if behind_count > 0 {
Expand Down Expand Up @@ -615,6 +639,7 @@ fn compare_local_remote_status(
remote_branch: Some(branch),
remote_head: remote_head.clone(),
remote_short_head: remote_head.as_deref().map(short_hash),
merge_base,
ahead_count,
behind_count,
has_uncommitted_changes,
Expand Down Expand Up @@ -657,7 +682,7 @@ pub async fn get_project_repo_snapshot(
target_commit: Option<String>,
state: State<'_, AppState>,
) -> Result<ProjectRepoSnapshotInfo, String> {
validate_clone_url(&clone_url)?;
validate_workspace_clone_url(&clone_url, &state)?;
let auth = build_git_auth_config(&state)?;
let branch = clean_branch(default_branch);
let base_branch = clean_branch(base_branch);
Expand Down Expand Up @@ -793,10 +818,11 @@ pub async fn get_project_repo_sync_status(
repos_dir: Option<String>,
project_dtag: String,
clone_url: String,
default_branch: Option<String>,
branch_name: Option<String>,
base_branch: Option<String>,
state: State<'_, AppState>,
) -> Result<ProjectRepoSyncStatusInfo, String> {
validate_clone_url(&clone_url)?;
validate_workspace_clone_url(&clone_url, &state)?;
let auth = build_git_auth_config(&state)?;

tauri::async_runtime::spawn_blocking(move || {
Expand All @@ -808,11 +834,12 @@ pub async fn get_project_repo_sync_status(
local_branch: None,
local_head: None,
local_short_head: None,
remote_branch: default_branch
remote_branch: branch_name
.as_deref()
.and_then(|branch| normalize_branch_option(Some(branch))),
remote_head: None,
remote_short_head: None,
merge_base: None,
ahead_count: 0,
behind_count: 0,
has_uncommitted_changes: false,
Expand All @@ -827,7 +854,8 @@ pub async fn get_project_repo_sync_status(
Ok(compare_local_remote_status(
&repo_dir,
&clone_url,
default_branch.as_deref(),
branch_name.as_deref(),
base_branch.as_deref(),
&auth,
))
})
Expand All @@ -840,10 +868,11 @@ pub async fn push_project_local_repository(
repos_dir: Option<String>,
project_dtag: String,
clone_url: String,
default_branch: Option<String>,
branch_name: Option<String>,
base_branch: Option<String>,
state: State<'_, AppState>,
) -> Result<ProjectRepoPushResult, String> {
validate_clone_url(&clone_url)?;
validate_workspace_clone_url(&clone_url, &state)?;
let auth = build_git_auth_config(&state)?;

tauri::async_runtime::spawn_blocking(move || {
Expand All @@ -852,17 +881,26 @@ pub async fn push_project_local_repository(
else {
return Err("No local checkout found.".to_string());
};
let status =
compare_local_remote_status(&repo_dir, &clone_url, default_branch.as_deref(), &auth);
let status = compare_local_remote_status(
&repo_dir,
&clone_url,
branch_name.as_deref(),
base_branch.as_deref(),
&auth,
);
if !status.can_push {
return Err(status
.push_block_reason
.unwrap_or_else(|| "Local checkout cannot be pushed.".to_string()));
}
let branch = status
.remote_branch
.as_deref()
.clone()
.ok_or_else(|| "No branch selected for push.".to_string())?;
let commit = status
.local_head
.clone()
.ok_or_else(|| "No local commit selected for push.".to_string())?;
run_git(
&[
"push",
Expand All @@ -877,6 +915,9 @@ pub async fn push_project_local_repository(
Ok(ProjectRepoPushResult {
pushed: true,
message: format!("Pushed {branch} to remote."),
branch,
commit,
merge_base: status.merge_base,
})
})
.await
Expand All @@ -890,10 +931,10 @@ pub async fn pull_project_local_repository(
repos_dir: Option<String>,
project_dtag: String,
clone_url: String,
default_branch: Option<String>,
branch_name: Option<String>,
state: State<'_, AppState>,
) -> Result<ProjectRepoPullResult, String> {
validate_clone_url(&clone_url)?;
validate_workspace_clone_url(&clone_url, &state)?;
let auth = build_git_auth_config(&state)?;

tauri::async_runtime::spawn_blocking(move || {
Expand All @@ -903,7 +944,7 @@ pub async fn pull_project_local_repository(
return Err("No local checkout found.".to_string());
};
let status =
compare_local_remote_status(&repo_dir, &clone_url, default_branch.as_deref(), &auth);
compare_local_remote_status(&repo_dir, &clone_url, branch_name.as_deref(), None, &auth);
if !status.can_pull {
return Err(status
.pull_block_reason
Expand Down
6 changes: 3 additions & 3 deletions desktop/src-tauri/src/commands/project_git_diff.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::project_git_exec::{
build_git_auth_config, clean_branch, run_git, validate_clone_url, GitAuthConfig,
build_git_auth_config, clean_branch, run_git, validate_workspace_clone_url, GitAuthConfig,
};
use super::project_repo_paths::find_local_repo_dir;
use crate::app_state::AppState;
Expand Down Expand Up @@ -37,7 +37,7 @@ fn clean_target_ref(value: Option<String>) -> Option<String> {
})
}

fn clean_commit(value: Option<String>) -> Option<String> {
pub(crate) fn clean_commit(value: Option<String>) -> Option<String> {
value
.filter(|value| matches!(value.len(), 40 | 64))
.filter(|value| value.chars().all(|c| c.is_ascii_hexdigit()))
Expand Down Expand Up @@ -388,7 +388,7 @@ pub async fn get_project_repo_diff(
target_commit: Option<String>,
state: State<'_, AppState>,
) -> Result<ProjectRepoDiffInfo, String> {
validate_clone_url(&clone_url)?;
validate_workspace_clone_url(&clone_url, &state)?;
let auth = build_git_auth_config(&state)?;
let branch = clean_branch(default_branch);
let base_branch = clean_branch(base_branch);
Expand Down
Loading
Loading