Remove remote editing from the Git sidebar

This commit is contained in:
2026-07-23 09:38:40 +02:00
parent 08a5bd192c
commit 632822fe1b
13 changed files with 46 additions and 83 deletions

View File

@@ -27,7 +27,7 @@ The project is under active development. Core blogging workflows are broadly ava
- Optional one-shot AI translation, description, analysis, taxonomy, and language-detection operations run in background tasks with editor-level waiting indicators, using provider-portable JSON-only requests through independent online and local OpenAI-compatible profiles. Each profile has secure credentials, persistently discovered chat/title/image model selections, explicit tool/vision overrides, chat testing, and restart-persistent status-bar airplane-mode routing. - Optional one-shot AI translation, description, analysis, taxonomy, and language-detection operations run in background tasks with editor-level waiting indicators, using provider-portable JSON-only requests through independent online and local OpenAI-compatible profiles. Each profile has secure credentials, persistently discovered chat/title/image model selections, explicit tool/vision overrides, chat testing, and restart-persistent status-bar airplane-mode routing.
- Persistent conversational AI with safe Markdown, streamed and cancellable responses, model/session/token tracking, bounded project-aware blog tools, and localized conversation management in the Chat workspace. Allowlisted render tools add persistent native cards, charts, forms, lists, metrics, mind maps, tables, and tabs without executing assistant-provided HTML or JavaScript. - Persistent conversational AI with safe Markdown, streamed and cancellable responses, model/session/token tracking, bounded project-aware blog tools, and localized conversation management in the Chat workspace. Allowlisted render tools add persistent native cards, charts, forms, lists, metrics, mind maps, tables, and tabs without executing assistant-provided HTML or JavaScript.
- SSH-agent-based SCP or rsync publishing. - SSH-agent-based SCP or rsync publishing.
- Integrated Git workflow with repository initialization, Git LFS image tracking, status and diffs, branch/file history, commits, remotes, cancellable fetch/pull/push, and post-pull filesystem reconciliation; network actions respect airplane mode. - Integrated Git workflow for each blog project's current repository, with repository initialization, read-only origin discovery, Git LFS image tracking, status and diffs, branch/file history, commits, cancellable fetch/pull/push, and post-pull filesystem reconciliation; network actions respect airplane mode.
- Site, media, and translation validation plus `ruds://new-post` Blogmark capture and Lua transforms; captures open directly in the post editor and defer automatic translation until an explicit manual save, which queues one task per still-missing language. Publishing never starts automatic translation. bDS2 keeps its separate `bds2://` bookmarklet protocol. - Site, media, and translation validation plus `ruds://new-post` Blogmark capture and Lua transforms; captures open directly in the post editor and defer automatic translation until an explicit manual save, which queues one task per still-missing language. Publishing never starts automatic translation. bDS2 keeps its separate `bds2://` bookmarklet protocol.
RuDS uses no JavaScript application runtime and loads no CSS or JavaScript from CDNs. The preview is served by the Rust application and displayed by the operating-system webview. RuDS uses no JavaScript application runtime and loads no CSS or JavaScript from CDNs. The preview is served by the Rust application and displayed by the operating-system webview.

View File

@@ -502,22 +502,6 @@ impl GitEngine {
}) })
} }
pub fn set_remote(&self, remote_url: &str) -> Result<(), GitError> {
let remote_url = remote_url.trim();
if remote_url.is_empty() {
return Err(GitError::Validation("remote URL is required".into()));
}
let args = if self
.optional_output(&["remote", "get-url", "origin"], GitOperation::Remote)?
.is_some()
{
["remote", "set-url", "origin", remote_url]
} else {
["remote", "add", "origin", remote_url]
};
self.run_local(&args, GitOperation::Remote).map(|_| ())
}
pub fn commit_all(&self, message: &str) -> Result<NetworkResult, GitError> { pub fn commit_all(&self, message: &str) -> Result<NetworkResult, GitError> {
let message = message.trim(); let message = message.trim();
if message.is_empty() { if message.is_empty() {

View File

@@ -134,7 +134,10 @@ fn remote_state_history_fetch_and_fast_forward_pull_use_real_refs() {
git(&local, &["add", "-A"]); git(&local, &["add", "-A"]);
git(&local, &["commit", "-m", "local one"]); git(&local, &["commit", "-m", "local one"]);
let engine = GitEngine::new(&local); let engine = GitEngine::new(&local);
engine.set_remote(remote.to_str().unwrap()).unwrap(); git(
&local,
&["remote", "add", "origin", remote.to_str().unwrap()],
);
git(&local, &["push", "-u", "origin", "master"]); git(&local, &["push", "-u", "origin", "master"]);
git( git(

View File

@@ -311,10 +311,8 @@ pub enum Message {
repository_dir: PathBuf, repository_dir: PathBuf,
result: Result<GitSnapshot, String>, result: Result<GitSnapshot, String>,
}, },
GitRemoteInputChanged(String),
GitCommitMessageChanged(String), GitCommitMessageChanged(String),
GitInitialize, GitInitialize,
GitSetRemote,
GitCommit, GitCommit,
GitFetch, GitFetch,
GitPull, GitPull,
@@ -2855,10 +2853,8 @@ impl BdsApp {
// ── Git ── // ── Git ──
message @ (Message::GitRefresh message @ (Message::GitRefresh
| Message::GitLoaded { .. } | Message::GitLoaded { .. }
| Message::GitRemoteInputChanged(_)
| Message::GitCommitMessageChanged(_) | Message::GitCommitMessageChanged(_)
| Message::GitInitialize | Message::GitInitialize
| Message::GitSetRemote
| Message::GitCommit | Message::GitCommit
| Message::GitFetch | Message::GitFetch
| Message::GitPull | Message::GitPull

View File

@@ -25,30 +25,14 @@ impl BdsApp {
} }
Task::none() Task::none()
} }
Message::GitRemoteInputChanged(value) => {
self.git_state.remote_input = value;
Task::none()
}
Message::GitCommitMessageChanged(value) => { Message::GitCommitMessageChanged(value) => {
self.git_state.commit_message = value; self.git_state.commit_message = value;
Task::none() Task::none()
} }
Message::GitInitialize => { Message::GitInitialize => self
let remote = self.git_state.remote_input.trim().to_string(); .run_local_git_action(GitOperation::Initialize, |engine| {
self.run_local_git_action(GitOperation::Initialize, move |engine| { engine.initialize().map(|_| ())
engine.initialize()?; }),
if !remote.is_empty() {
engine.set_remote(&remote)?;
}
Ok(())
})
}
Message::GitSetRemote => {
let remote = self.git_state.remote_input.trim().to_string();
self.run_local_git_action(GitOperation::Remote, move |engine| {
engine.set_remote(&remote)
})
}
Message::GitCommit => { Message::GitCommit => {
let message = self.git_state.commit_message.clone(); let message = self.git_state.commit_message.clone();
self.run_local_git_action(GitOperation::Commit, move |engine| { self.run_local_git_action(GitOperation::Commit, move |engine| {

View File

@@ -35,7 +35,6 @@ pub struct GitUiState {
pub files: Vec<GitFileStatus>, pub files: Vec<GitFileStatus>,
pub history: Vec<GitCommit>, pub history: Vec<GitCommit>,
pub remote: GitRemoteState, pub remote: GitRemoteState,
pub remote_input: String,
pub commit_message: String, pub commit_message: String,
pub loading: bool, pub loading: bool,
pub error: Option<String>, pub error: Option<String>,
@@ -61,7 +60,6 @@ impl Default for GitUiState {
ahead: 0, ahead: 0,
behind: 0, behind: 0,
}, },
remote_input: String::new(),
commit_message: String::new(), commit_message: String::new(),
loading: false, loading: false,
error: None, error: None,
@@ -72,7 +70,6 @@ impl Default for GitUiState {
impl GitUiState { impl GitUiState {
pub fn apply_snapshot(&mut self, snapshot: GitSnapshot) { pub fn apply_snapshot(&mut self, snapshot: GitSnapshot) {
self.remote_input = snapshot.repository.remote_url.clone().unwrap_or_default();
self.repository = snapshot.repository; self.repository = snapshot.repository;
self.files = snapshot.files; self.files = snapshot.files;
self.history = snapshot.history; self.history = snapshot.history;
@@ -151,11 +148,6 @@ pub fn sidebar_view(
text(t(locale, "git.notRepository")) text(t(locale, "git.notRepository"))
.size(12) .size(12)
.color(Color::from_rgb(0.6, 0.6, 0.65)), .color(Color::from_rgb(0.6, 0.6, 0.65)),
text_input(&t(locale, "git.remoteOptional"), &state.remote_input)
.on_input(Message::GitRemoteInputChanged)
.size(12)
.padding([6, 8])
.style(inputs::field_style),
button(text(t(locale, "git.initialize")).size(12)) button(text(t(locale, "git.initialize")).size(12))
.on_press(Message::GitInitialize) .on_press(Message::GitInitialize)
.padding([5, 8]) .padding([5, 8])
@@ -209,21 +201,6 @@ pub fn sidebar_view(
.into(), .into(),
); );
} }
content.push(
row![
text_input(&t(locale, "git.remoteUrl"), &state.remote_input)
.on_input(Message::GitRemoteInputChanged)
.size(11)
.padding([5, 7])
.style(inputs::field_style),
button(text(t(locale, "git.saveRemote")).size(11))
.on_press(Message::GitSetRemote)
.padding([5, 7])
.style(inputs::secondary_button),
]
.spacing(4)
.into(),
);
content.push(section_title( content.push(section_title(
tw( tw(
locale, locale,
@@ -529,3 +506,32 @@ impl fmt::Display for PathTitle<'_> {
} }
use std::fmt; use std::fmt;
#[cfg(test)]
mod tests {
use super::*;
use iced::advanced::widget::{Tree, tree::Tag};
use iced::widget::text_input;
type TextInputState =
text_input::State<<iced::Renderer as iced::advanced::text::Renderer>::Paragraph>;
fn count_text_inputs(tree: &Tree) -> usize {
usize::from(tree.tag == Tag::of::<TextInputState>())
+ tree.children.iter().map(count_text_inputs).sum::<usize>()
}
#[test]
fn repository_location_is_never_editable_in_the_git_sidebar() {
let not_initialized = GitUiState::default();
let inactive_tree =
Tree::new(sidebar_view(&not_initialized, false, UiLocale::En).as_widget());
assert_eq!(count_text_inputs(&inactive_tree), 0);
let mut initialized = GitUiState::default();
initialized.repository.is_initialized = true;
initialized.repository.remote_url = Some("ssh://example.test/blog.git".into());
let active_tree = Tree::new(sidebar_view(&initialized, false, UiLocale::En).as_widget());
assert_eq!(count_text_inputs(&active_tree), 1);
}
}

View File

@@ -700,14 +700,12 @@ find-noMatches = Keine Treffer gefunden
find-replacedCount = { $count } Treffer ersetzt find-replacedCount = { $count } Treffer ersetzt
git-loading = Repository wird geladen… git-loading = Repository wird geladen…
git-notRepository = Dieses Projekt ist noch kein Git-Repository. git-notRepository = Dieses Projekt ist noch kein Git-Repository.
git-remoteOptional = Remote-URL (optional)
git-initialize = Git initialisieren git-initialize = Git initialisieren
git-fetch = Abrufen git-fetch = Abrufen
git-pull = Herunterladen git-pull = Herunterladen
git-push = Hochladen git-push = Hochladen
git-airplaneBlocked = Netzwerk-Git-Aktionen sind im Flugmodus nicht verfügbar. git-airplaneBlocked = Netzwerk-Git-Aktionen sind im Flugmodus nicht verfügbar.
git-remoteUrl = Origin-URL git-remoteUrl = Origin-URL
git-saveRemote = Speichern
git-changesCount = Änderungen ({ $count }) git-changesCount = Änderungen ({ $count })
git-commitMessage = Commit-Nachricht git-commitMessage = Commit-Nachricht
git-commit = Commit git-commit = Commit

View File

@@ -700,14 +700,12 @@ find-noMatches = No matches found
find-replacedCount = Replaced { $count } matches find-replacedCount = Replaced { $count } matches
git-loading = Loading repository… git-loading = Loading repository…
git-notRepository = This project is not a Git repository yet. git-notRepository = This project is not a Git repository yet.
git-remoteOptional = Remote URL (optional)
git-initialize = Initialize Git git-initialize = Initialize Git
git-fetch = Fetch git-fetch = Fetch
git-pull = Pull git-pull = Pull
git-push = Push git-push = Push
git-airplaneBlocked = Network Git actions are unavailable in airplane mode. git-airplaneBlocked = Network Git actions are unavailable in airplane mode.
git-remoteUrl = Origin URL git-remoteUrl = Origin URL
git-saveRemote = Save
git-changesCount = Changes ({ $count }) git-changesCount = Changes ({ $count })
git-commitMessage = Commit message git-commitMessage = Commit message
git-commit = Commit git-commit = Commit

View File

@@ -700,14 +700,12 @@ find-noMatches = No se encontraron coincidencias
find-replacedCount = Se reemplazaron { $count } coincidencias find-replacedCount = Se reemplazaron { $count } coincidencias
git-loading = Cargando repositorio… git-loading = Cargando repositorio…
git-notRepository = Este proyecto aún no es un repositorio Git. git-notRepository = Este proyecto aún no es un repositorio Git.
git-remoteOptional = URL remota (opcional)
git-initialize = Inicializar Git git-initialize = Inicializar Git
git-fetch = Obtener git-fetch = Obtener
git-pull = Descargar git-pull = Descargar
git-push = Subir git-push = Subir
git-airplaneBlocked = Las acciones Git de red no están disponibles en modo avión. git-airplaneBlocked = Las acciones Git de red no están disponibles en modo avión.
git-remoteUrl = URL de origin git-remoteUrl = URL de origin
git-saveRemote = Guardar
git-changesCount = Cambios ({ $count }) git-changesCount = Cambios ({ $count })
git-commitMessage = Mensaje de commit git-commitMessage = Mensaje de commit
git-commit = Commit git-commit = Commit

View File

@@ -700,14 +700,12 @@ find-noMatches = Aucun résultat
find-replacedCount = { $count } résultats remplacés find-replacedCount = { $count } résultats remplacés
git-loading = Chargement du dépôt… git-loading = Chargement du dépôt…
git-notRepository = Ce projet nest pas encore un dépôt Git. git-notRepository = Ce projet nest pas encore un dépôt Git.
git-remoteOptional = URL distante (facultative)
git-initialize = Initialiser Git git-initialize = Initialiser Git
git-fetch = Récupérer git-fetch = Récupérer
git-pull = Tirer git-pull = Tirer
git-push = Pousser git-push = Pousser
git-airplaneBlocked = Les actions Git réseau sont indisponibles en mode avion. git-airplaneBlocked = Les actions Git réseau sont indisponibles en mode avion.
git-remoteUrl = URL dorigine git-remoteUrl = URL dorigine
git-saveRemote = Enregistrer
git-changesCount = Modifications ({ $count }) git-changesCount = Modifications ({ $count })
git-commitMessage = Message de commit git-commitMessage = Message de commit
git-commit = Commit git-commit = Commit

View File

@@ -700,14 +700,12 @@ find-noMatches = Nessuna corrispondenza
find-replacedCount = Sostituite { $count } corrispondenze find-replacedCount = Sostituite { $count } corrispondenze
git-loading = Caricamento del repository… git-loading = Caricamento del repository…
git-notRepository = Questo progetto non è ancora un repository Git. git-notRepository = Questo progetto non è ancora un repository Git.
git-remoteOptional = URL remoto (facoltativo)
git-initialize = Inizializza Git git-initialize = Inizializza Git
git-fetch = Recupera git-fetch = Recupera
git-pull = Scarica git-pull = Scarica
git-push = Carica git-push = Carica
git-airplaneBlocked = Le azioni Git di rete non sono disponibili in modalità aereo. git-airplaneBlocked = Le azioni Git di rete non sono disponibili in modalità aereo.
git-remoteUrl = URL origin git-remoteUrl = URL origin
git-saveRemote = Salva
git-changesCount = Modifiche ({ $count }) git-changesCount = Modifiche ({ $count })
git-commitMessage = Messaggio di commit git-commitMessage = Messaggio di commit
git-commit = Commit git-commit = Commit

View File

@@ -87,7 +87,6 @@ surface GitControlSurface {
GitHistoryRequested(project, branch) GitHistoryRequested(project, branch)
GitFileHistoryRequested(project, file_path) GitFileHistoryRequested(project, file_path)
GitRemoteStateRequested(project) GitRemoteStateRequested(project)
GitRemoteSetRequested(project, remote_url)
GitFetchRequested(project) GitFetchRequested(project)
GitPullRequested(project) GitPullRequested(project)
GitPushRequested(project) GitPushRequested(project)
@@ -153,12 +152,6 @@ rule GetRemoteState {
ensures: GitRemoteStateReport(project, local_branch, upstream_branch, ahead, behind) ensures: GitRemoteStateReport(project, local_branch, upstream_branch, ahead, behind)
} }
rule SetRemote {
when: GitRemoteSetRequested(project, remote_url)
-- Creates origin when absent and replaces its URL when already configured.
ensures: GitRemoteUpdated(project, remote_url)
}
rule Fetch { rule Fetch {
when: GitFetchRequested(project) when: GitFetchRequested(project)
ensures: RemoteRefsUpdated(project) ensures: RemoteRefsUpdated(project)

View File

@@ -674,8 +674,10 @@ rule ImportListClick {
-- The sidebar must surface these capabilities directly. -- The sidebar must surface these capabilities directly.
-- State: not_a_repo -- State: not_a_repo
-- Remote URL text input + "Initialize Git" button. -- "Initialize Git" button for the current blog project directory.
-- Init progress with phase/percentage/detail, collapsible transcript. -- Init progress with phase/percentage/detail, collapsible transcript.
-- The remote URL is discovered from the repository and is never editable
-- from the sidebar.
-- State: active_repo -- State: active_repo
value GitActiveView { value GitActiveView {
@@ -702,6 +704,11 @@ surface GitActiveViewSurface {
provides: provides:
GitCommitRequested(message) GitCommitRequested(message)
@guarantee CurrentProjectRepository
-- Git operations always target the current blog project's directory.
-- Any origin URL is discovered from that repository and cannot be
-- entered or changed in this surface.
} }
value GitStatusFile { value GitStatusFile {