Skip to content

Commit 41382a0

Browse files
committed
fix(windows): normalise git executable path display, prefer system git, and use platform-specific clone destination placeholder
1 parent f224c0b commit 41382a0

6 files changed

Lines changed: 78 additions & 42 deletions

File tree

‎src-tauri/src/commands/repo.rs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,12 @@ pub fn cancel_clone(flag: tauri::State<'_, CloneCancelFlag>) {
236236

237237
#[tauri::command]
238238
pub fn get_default_clone_dir() -> String {
239+
#[cfg(windows)]
240+
let home = std::env::var("USERPROFILE")
241+
.or_else(|_| std::env::var("HOME"))
242+
.unwrap_or_else(|_| ".".to_string());
243+
#[cfg(not(windows))]
239244
let home = std::env::var("HOME")
240-
.or_else(|_| std::env::var("USERPROFILE"))
241245
.unwrap_or_else(|_| ".".to_string());
242246
std::path::PathBuf::from(home)
243247
.join("GitmunProjects")

‎src-tauri/src/commands/settings.rs‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -733,17 +733,18 @@ fn validate_git_executable_path(path: &str) -> Result<String, String> {
733733
return Ok(String::new());
734734
}
735735

736-
let path = Path::new(trimmed);
736+
let normalised = crate::normalise_display_path(trimmed);
737+
let path = Path::new(&normalised);
737738
if !path.exists() {
738-
return Err(format!("Git executable was not found: {trimmed}"));
739+
return Err(format!("Git executable was not found: {normalised}"));
739740
}
740741
if path.is_dir() {
741742
return Err(format!(
742-
"Git executable path points to a directory: {trimmed}"
743+
"Git executable path points to a directory: {normalised}"
743744
));
744745
}
745746

746-
Ok(trimmed.to_string())
747+
Ok(normalised)
747748
}
748749

749750
#[tauri::command]

‎src-tauri/src/lib.rs‎

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,26 @@ pub(crate) fn git_command() -> std::process::Command {
129129
}
130130
}
131131

132+
pub(crate) fn normalise_display_path(path: &str) -> String {
133+
#[cfg(windows)]
134+
{
135+
if let Some(stripped) = path.strip_prefix(r"\\?\") {
136+
if let Some(unc) = stripped.strip_prefix(r"UNC\") {
137+
return format!("\\\\{}", unc);
138+
}
139+
return stripped.to_string();
140+
}
141+
}
142+
path.to_string()
143+
}
144+
132145
pub(crate) fn set_configured_git_executable_path(path: String) {
133146
let trimmed = path.trim();
134147
let next = if trimmed.is_empty() {
135148
None
136149
} else {
137-
Some(PathBuf::from(trimmed.trim_matches('"')))
150+
let normalised = normalise_display_path(trimmed.trim_matches('"'));
151+
Some(PathBuf::from(normalised))
138152
};
139153

140154
if let Ok(mut configured) = CONFIGURED_GIT_EXE.get_or_init(|| RwLock::new(None)).write() {
@@ -165,33 +179,35 @@ fn resolve_on_path(names: &[&str]) -> Option<PathBuf> {
165179
}
166180

167181
pub(crate) fn resolve_active_git_executable_path() -> String {
168-
if let Some(path) = configured_git_executable_path() {
169-
return path.to_string_lossy().into_owned();
170-
}
171-
172-
match git_backend() {
173-
GitBackend::FlatpakHost => "flatpak-spawn --host git".to_string(),
174-
GitBackend::FlatpakBundled => "/app/bin/git".to_string(),
175-
GitBackend::System => {
176-
#[cfg(windows)]
177-
{
178-
let git_exe = PathBuf::from(resolve_git_exe());
179-
if git_exe.is_absolute() {
180-
return git_exe.to_string_lossy().into_owned();
182+
let raw = if let Some(path) = configured_git_executable_path() {
183+
path.to_string_lossy().into_owned()
184+
} else {
185+
match git_backend() {
186+
GitBackend::FlatpakHost => return "flatpak-spawn --host git".to_string(),
187+
GitBackend::FlatpakBundled => return "/app/bin/git".to_string(),
188+
GitBackend::System => {
189+
#[cfg(windows)]
190+
{
191+
let git_exe = PathBuf::from(resolve_git_exe());
192+
if git_exe.is_absolute() {
193+
git_exe.to_string_lossy().into_owned()
194+
} else {
195+
resolve_on_path(&["git.exe", "git"])
196+
.map(|path| path.to_string_lossy().into_owned())
197+
.unwrap_or_else(|| "git".to_string())
198+
}
181199
}
182-
resolve_on_path(&["git.exe", "git"])
183-
.map(|path| path.to_string_lossy().into_owned())
184-
.unwrap_or_else(|| "git".to_string())
185-
}
186200

187-
#[cfg(not(windows))]
188-
{
189-
resolve_on_path(&["git"])
190-
.map(|path| path.to_string_lossy().into_owned())
191-
.unwrap_or_else(|| "git".to_string())
201+
#[cfg(not(windows))]
202+
{
203+
resolve_on_path(&["git"])
204+
.map(|path| path.to_string_lossy().into_owned())
205+
.unwrap_or_else(|| "git".to_string())
206+
}
192207
}
193208
}
194-
}
209+
};
210+
normalise_display_path(&raw)
195211
}
196212

197213
pub(crate) fn git_version_string() -> Result<String, String> {
@@ -203,7 +219,12 @@ pub(crate) fn git_version_string() -> Result<String, String> {
203219
.map_err(|error| error.to_string())?;
204220

205221
if output.status.success() {
206-
let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
222+
let version = String::from_utf8_lossy(&output.stdout)
223+
.trim()
224+
.strip_prefix("git version ")
225+
.unwrap_or("")
226+
.trim()
227+
.to_string();
207228
if !version.is_empty() {
208229
return Ok(version);
209230
}
@@ -521,19 +542,20 @@ pub(crate) fn configured_git_command() -> std::process::Command {
521542
fn resolve_git_exe() -> std::ffi::OsString {
522543
#[cfg(windows)]
523544
{
545+
for candidate in &[
546+
r"C:\Program Files\Git\cmd\git.exe",
547+
r"C:\Program Files\Git\bin\git.exe",
548+
r"C:\Program Files (x86)\Git\cmd\git.exe",
549+
] {
550+
if std::path::Path::new(candidate).exists() {
551+
return (*candidate).into();
552+
}
553+
}
524554
let git_in_path = std::env::var_os("PATH")
525555
.map(|p| std::env::split_paths(&p).any(|dir| dir.join("git.exe").exists()))
526556
.unwrap_or(false);
527-
if !git_in_path {
528-
for candidate in &[
529-
r"C:\Program Files\Git\cmd\git.exe",
530-
r"C:\Program Files\Git\bin\git.exe",
531-
r"C:\Program Files (x86)\Git\cmd\git.exe",
532-
] {
533-
if std::path::Path::new(candidate).exists() {
534-
return (*candidate).into();
535-
}
536-
}
557+
if git_in_path {
558+
return "git".into();
537559
}
538560
if let Some(candidate) = bundled_git_exe() {
539561
return candidate.into_os_string();

‎src/components/settings/SettingsWindow.tsx‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ function safePlatform(): string {
7979
export function SettingsWindow() {
8080
const {t} = useTranslation("settings");
8181
const useNativeWindowBar = true;
82+
const os = safePlatform();
83+
const cloneDestinationPlaceholder = os === "windows"
84+
? t("placeholders.defaultCloneDestinationWindows")
85+
: os === "macos"
86+
? t("placeholders.defaultCloneDestinationMac")
87+
: t("placeholders.defaultCloneDestinationLinux");
8288
const [backendMode, setBackendMode] = useState<BackendMode>("Default");
8389
const [themeMode, setThemeMode] = useState<ThemeMode>("System");
8490
const [wrapDiffLines, setWrapDiffLines] = useState(false);
@@ -755,7 +761,7 @@ export function SettingsWindow() {
755761
type="text"
756762
value={defaultCloneDir}
757763
onChange={e => setDefaultCloneDir(e.target.value)}
758-
placeholder={t("placeholders.defaultCloneDestination")}
764+
placeholder={cloneDestinationPlaceholder}
759765
/>
760766
<button
761767
className="settings-window__btn settings-window__btn--secondary settings-window__icon-btn"

‎src/i18n/locales/en/settings.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@
7979
"placeholders": {
8080
"defaultBranch": "Leave blank to use Git's default",
8181
"defaultCloneDestination": "Leave blank to use ~/GitmunProjects",
82+
"defaultCloneDestinationLinux": "Leave blank to use ~/GitmunProjects",
83+
"defaultCloneDestinationMac": "Leave blank to use ~/GitmunProjects",
84+
"defaultCloneDestinationWindows": "Leave blank to use %USERPROFILE%\\GitmunProjects",
8285
"diffToolPath": "Auto-detect {{tool}} or choose its .exe",
8386
"gitExecutable": "Auto-detect Git or choose an executable",
8487
"gpgProgram": "Leave blank for Git's default, or choose gpg"

‎vite.config.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default defineConfig(() => ({
1010
port: 1420,
1111
strictPort: true,
1212
watch: {
13-
ignored: ["**/.flatpak-*/**", "**/packaging/flatpak/rootfs/**"]
13+
ignored: ["**/.flatpak-*/**", "**/packaging/flatpak/**"]
1414
}
1515
},
1616
envPrefix: ["VITE_", "TAURI_"],

0 commit comments

Comments
 (0)