Remove remote editing from the Git sidebar
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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| {
|
||||||
|
|||||||
@@ -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(¬_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 n’est pas encore un dépôt Git.
|
git-notRepository = Ce projet n’est 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 d’origine
|
git-remoteUrl = URL d’origine
|
||||||
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user