diff --git a/RMuseum/RMuseum.xml b/RMuseum/RMuseum.xml
index d53fc75e..086f9b0a 100644
--- a/RMuseum/RMuseum.xml
+++ b/RMuseum/RMuseum.xml
@@ -24151,6 +24151,15 @@
user-secrets / environment variables, never committed to appsettings.json.
+
+
+ Full path to git.exe, e.g. "C:\Program Files\Git\cmd\git.exe". Optional — leave empty
+ to resolve "git" via PATH (with a fallback to common Git-for-Windows install
+ locations if that fails, see GitRepoPublisher.ResolveGitExecutable). Set this
+ explicitly if the process running this job has a PATH that doesn't include git for
+ any reason (e.g. a Windows service running under a service account with its own PATH).
+
+
Thin wrapper around the native git CLI (not LibGit2Sharp) for the export job: sync a
@@ -24185,6 +24194,24 @@
since last run — a normal, expected outcome on most nightly runs).
+
+
+ Resolves which git executable to launch, cached for the lifetime of this instance.
+ Prefers an explicitly configured path; otherwise tries "git" (PATH resolution via
+ Process/CreateProcess) and, only if that file genuinely doesn't exist anywhere on
+ PATH, falls back to checking the usual Git-for-Windows install locations directly —
+ covers the common case where the process that's running this job has a stale PATH
+ snapshot from before git was installed (e.g. Visual Studio/IIS Express started before
+ the PATH environment variable was updated).
+
+
+
+
+ checks every directory on this process's own PATH for the given executable, without
+ actually starting it — used only to decide whether to bother trying the Git-for-Windows
+ fallback locations
+
+
keeps the auth token out of exception messages that might end up logged somewhere
diff --git a/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-PublicDataExport.cs b/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-PublicDataExport.cs
index abc254b0..2a0520f0 100644
--- a/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-PublicDataExport.cs
+++ b/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-PublicDataExport.cs
@@ -155,6 +155,7 @@ namespace RMuseum.Services.Implementation
PushEnabled = bool.TryParse(section["PushEnabled"], out var push) && push,
GitUserName = section["GitUserName"],
GitToken = section["GitToken"],
+ GitExecutablePath = section["GitExecutablePath"],
};
}
diff --git a/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-TajikPublicDataExport.cs b/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-TajikPublicDataExport.cs
index a99af26e..32939c93 100644
--- a/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-TajikPublicDataExport.cs
+++ b/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-TajikPublicDataExport.cs
@@ -139,6 +139,7 @@ namespace RMuseum.Services.Implementation
PushEnabled = bool.TryParse(section["PushEnabled"], out var push) && push,
GitUserName = section["GitUserName"],
GitToken = section["GitToken"],
+ GitExecutablePath = section["GitExecutablePath"],
};
}
diff --git a/RMuseum/Utils/PublicDataExport/GitRepoPublisher.cs b/RMuseum/Utils/PublicDataExport/GitRepoPublisher.cs
index f1e2325a..e898e9aa 100644
--- a/RMuseum/Utils/PublicDataExport/GitRepoPublisher.cs
+++ b/RMuseum/Utils/PublicDataExport/GitRepoPublisher.cs
@@ -45,6 +45,15 @@ namespace RMuseum.Utils.PublicDataExport
/// user-secrets / environment variables, never committed to appsettings.json.
///
public string GitToken { get; set; }
+
+ ///
+ /// Full path to git.exe, e.g. "C:\Program Files\Git\cmd\git.exe". Optional — leave empty
+ /// to resolve "git" via PATH (with a fallback to common Git-for-Windows install
+ /// locations if that fails, see GitRepoPublisher.ResolveGitExecutable). Set this
+ /// explicitly if the process running this job has a PATH that doesn't include git for
+ /// any reason (e.g. a Windows service running under a service account with its own PATH).
+ ///
+ public string GitExecutablePath { get; set; }
}
///
@@ -67,6 +76,7 @@ namespace RMuseum.Utils.PublicDataExport
public class GitRepoPublisher
{
private readonly GitRepoPublisherOptions _options;
+ private string _resolvedGitExecutable;
public GitRepoPublisher(GitRepoPublisherOptions options)
{
@@ -171,7 +181,7 @@ namespace RMuseum.Utils.PublicDataExport
{
var psi = new ProcessStartInfo
{
- FileName = "git",
+ FileName = ResolveGitExecutable(),
Arguments = arguments,
WorkingDirectory = workingDirectory,
RedirectStandardOutput = true,
@@ -189,7 +199,12 @@ namespace RMuseum.Utils.PublicDataExport
catch (Win32Exception exp)
{
throw new InvalidOperationException(
- "Could not start 'git'. Make sure Git for Windows is installed and 'git' is on PATH.", exp);
+ "Could not start 'git'. This process's PATH may be stale (common right " +
+ "after installing Git while Visual Studio/IIS Express was already " +
+ "running — fully restart them so they pick up the new PATH), or git " +
+ "genuinely isn't installed. As a workaround, set " +
+ "PublicDataExport:GitExecutablePath (or TajikPublicDataExport:...) to " +
+ "the full path of git.exe.", exp);
}
string stdout = process.StandardOutput.ReadToEnd();
@@ -200,6 +215,83 @@ namespace RMuseum.Utils.PublicDataExport
}
}
+ ///
+ /// Resolves which git executable to launch, cached for the lifetime of this instance.
+ /// Prefers an explicitly configured path; otherwise tries "git" (PATH resolution via
+ /// Process/CreateProcess) and, only if that file genuinely doesn't exist anywhere on
+ /// PATH, falls back to checking the usual Git-for-Windows install locations directly —
+ /// covers the common case where the process that's running this job has a stale PATH
+ /// snapshot from before git was installed (e.g. Visual Studio/IIS Express started before
+ /// the PATH environment variable was updated).
+ ///
+ private string ResolveGitExecutable()
+ {
+ if (_resolvedGitExecutable != null)
+ return _resolvedGitExecutable;
+
+ if (!string.IsNullOrEmpty(_options.GitExecutablePath))
+ {
+ _resolvedGitExecutable = _options.GitExecutablePath;
+ return _resolvedGitExecutable;
+ }
+
+ if (IsOnPath("git"))
+ {
+ _resolvedGitExecutable = "git";
+ return _resolvedGitExecutable;
+ }
+
+ string[] fallbackCandidates =
+ {
+ Environment.ExpandEnvironmentVariables(@"%ProgramFiles%\Git\cmd\git.exe"),
+ Environment.ExpandEnvironmentVariables(@"%ProgramFiles%\Git\bin\git.exe"),
+ Environment.ExpandEnvironmentVariables(@"%ProgramFiles(x86)%\Git\cmd\git.exe"),
+ Environment.ExpandEnvironmentVariables(@"%ProgramFiles(x86)%\Git\bin\git.exe"),
+ Environment.ExpandEnvironmentVariables(@"%LocalAppData%\Programs\Git\cmd\git.exe"),
+ };
+
+ foreach (var candidate in fallbackCandidates)
+ {
+ if (File.Exists(candidate))
+ {
+ _resolvedGitExecutable = candidate;
+ return _resolvedGitExecutable;
+ }
+ }
+
+ // nothing found - fall through with plain "git" so the resulting Win32Exception
+ // message (with its own guidance) is what the caller sees
+ _resolvedGitExecutable = "git";
+ return _resolvedGitExecutable;
+ }
+
+ ///
+ /// checks every directory on this process's own PATH for the given executable, without
+ /// actually starting it — used only to decide whether to bother trying the Git-for-Windows
+ /// fallback locations
+ ///
+ private static bool IsOnPath(string executableName)
+ {
+ string pathEnv = Environment.GetEnvironmentVariable("PATH") ?? "";
+ foreach (var dir in pathEnv.Split(Path.PathSeparator))
+ {
+ if (string.IsNullOrWhiteSpace(dir))
+ continue;
+
+ try
+ {
+ string candidate = Path.Combine(dir, executableName + ".exe");
+ if (File.Exists(candidate))
+ return true;
+ }
+ catch (ArgumentException)
+ {
+ // malformed PATH entry - ignore and keep checking the rest
+ }
+ }
+ return false;
+ }
+
///
/// keeps the auth token out of exception messages that might end up logged somewhere
///
diff --git a/RMuseum/appsettings.json b/RMuseum/appsettings.json
index 926c580e..43f5bfb9 100644
--- a/RMuseum/appsettings.json
+++ b/RMuseum/appsettings.json
@@ -75,7 +75,8 @@
"CommitAuthorEmail": "bot@ganjoor.net",
"PushEnabled": "False",
"GitUserName": "",
- "GitToken": ""
+ "GitToken": "",
+ "GitExecutablePath": ""
},
"TajikPublicDataExport": {
"LocalWorkingCopyPath": "C:\\ganjoor-tajik-public-data",
@@ -85,7 +86,8 @@
"CommitAuthorEmail": "bot@ganjoor.net",
"PushEnabled": "False",
"GitUserName": "",
- "GitToken": ""
+ "GitToken": "",
+ "GitExecutablePath": ""
},
"ExternalFTPServer": {
"Host": "localhost",