22#![ deny( rust_2018_idioms, missing_docs) ]
33#![ forbid( unsafe_code) ]
44
5+ use bstr:: BString ;
56use std:: ffi:: OsString ;
7+ use std:: path:: PathBuf ;
68
79/// A structure to keep settings to use when invoking a command via [`spawn()`][Prepare::spawn()], after creating it with [`prepare()`].
810pub struct Prepare {
911 /// The command to invoke (either with or without shell depending on `use_shell`.
1012 pub command : OsString ,
13+ /// Additional information to be passed to the spawned command.
14+ pub context : Option < Context > ,
1115 /// The way standard input is configured.
1216 pub stdin : std:: process:: Stdio ,
1317 /// The way standard output is configured.
@@ -35,6 +39,37 @@ pub struct Prepare {
3539 pub allow_manual_arg_splitting : bool ,
3640}
3741
42+ /// Additional information that is relevant to spawned processes, which typically receive
43+ /// a wealth of contextual information when spawned from `git`.
44+ ///
45+ /// See [the git source code](https://github.com/git/git/blob/cfb8a6e9a93adbe81efca66e6110c9b4d2e57169/git.c#L191)
46+ /// for details.
47+ #[ derive( Debug , Default , Clone ) ]
48+ pub struct Context {
49+ /// The `.git` directory that contains the repository.
50+ ///
51+ /// If set, it will be used to set the the `GIT_DIR` environment variable.
52+ pub git_dir : Option < PathBuf > ,
53+ /// Set the `GIT_WORK_TREE` environment variable with the given path.
54+ pub worktree_dir : Option < PathBuf > ,
55+ /// If `true`, set `GIT_NO_REPLACE_OBJECTS` to `1`, which turns off object replacements, or `0` otherwise.
56+ /// If `None`, the variable won't be set.
57+ pub no_replace_objects : Option < bool > ,
58+ /// Set the `GIT_NAMESPACE` variable with the given value, effectively namespacing all
59+ /// operations on references.
60+ pub ref_namespace : Option < BString > ,
61+ /// If `true`, set `GIT_LITERAL_PATHSPECS` to `1`, which makes globs literal and prefixes as well, or `0` otherwise.
62+ /// If `None`, the variable won't be set.
63+ pub literal_pathspecs : Option < bool > ,
64+ /// If `true`, set `GIT_GLOB_PATHSPECS` to `1`, which lets wildcards not match the `/` character, and equals the `:(glob)` prefix.
65+ /// If `false`, set `GIT_NOGLOB_PATHSPECS` to `1` which lets globs match only themselves.
66+ /// If `None`, the variable won't be set.
67+ pub glob_pathspecs : Option < bool > ,
68+ /// If `true`, set `GIT_ICASE_PATHSPECS` to `1`, to let patterns match case-insensitively, or `0` otherwise.
69+ /// If `None`, the variable won't be set.
70+ pub icase_pathspecs : Option < bool > ,
71+ }
72+
3873mod prepare {
3974 use std:: {
4075 ffi:: OsString ,
@@ -43,7 +78,7 @@ mod prepare {
4378
4479 use bstr:: ByteSlice ;
4580
46- use crate :: Prepare ;
81+ use crate :: { Context , Prepare } ;
4782
4883 /// Builder
4984 impl Prepare {
@@ -67,6 +102,15 @@ mod prepare {
67102 self
68103 }
69104
105+ /// Set additional `ctx` to be used when spawning the process.
106+ ///
107+ /// Note that this is a must for most kind of commands that `git` usually spawns,
108+ /// as at least they need to know the correct `git` repository to function.
109+ pub fn with_context ( mut self , ctx : Context ) -> Self {
110+ self . context = Some ( ctx) ;
111+ self
112+ }
113+
70114 /// Use a shell, but try to split arguments by hand if this be safely done without a shell.
71115 ///
72116 /// If that's not the case, use a shell instead.
@@ -164,6 +208,36 @@ mod prepare {
164208 . stderr ( prep. stderr )
165209 . envs ( prep. env )
166210 . args ( prep. args ) ;
211+ if let Some ( ctx) = prep. context {
212+ if let Some ( git_dir) = ctx. git_dir {
213+ cmd. env ( "GIT_DIR" , & git_dir) ;
214+ }
215+ if let Some ( worktree_dir) = ctx. worktree_dir {
216+ cmd. env ( "GIT_WORK_TREE" , worktree_dir) ;
217+ }
218+ if let Some ( value) = ctx. no_replace_objects {
219+ cmd. env ( "GIT_NO_REPLACE_OBJECTS" , usize:: from ( value) . to_string ( ) ) ;
220+ }
221+ if let Some ( namespace) = ctx. ref_namespace {
222+ cmd. env ( "GIT_NAMESPACE" , gix_path:: from_bstring ( namespace) ) ;
223+ }
224+ if let Some ( value) = ctx. literal_pathspecs {
225+ cmd. env ( "GIT_LITERAL_PATHSPECS" , usize:: from ( value) . to_string ( ) ) ;
226+ }
227+ if let Some ( value) = ctx. glob_pathspecs {
228+ cmd. env (
229+ if value {
230+ "GIT_GLOB_PATHSPECS"
231+ } else {
232+ "GIT_NOGLOB_PATHSPECS"
233+ } ,
234+ "1" ,
235+ ) ;
236+ }
237+ if let Some ( value) = ctx. icase_pathspecs {
238+ cmd. env ( "GIT_ICASE_PATHSPECS" , usize:: from ( value) . to_string ( ) ) ;
239+ }
240+ }
167241 cmd
168242 }
169243 }
@@ -176,9 +250,16 @@ mod prepare {
176250/// - `stdin` is null to prevent blocking unexpectedly on consumption of stdin
177251/// - `stdout` is captured for consumption by the caller
178252/// - `stderr` is inherited to allow the command to provide context to the user
253+ ///
254+ /// ### Warning
255+ ///
256+ /// When using this method, be sure that the invoked program doesn't rely on the current working dir and/or
257+ /// environment variables to know its context. If so, call instead [`Prepare::with_context()`] to provide
258+ /// additional information.
179259pub fn prepare ( cmd : impl Into < OsString > ) -> Prepare {
180260 Prepare {
181261 command : cmd. into ( ) ,
262+ context : None ,
182263 stdin : std:: process:: Stdio :: null ( ) ,
183264 stdout : std:: process:: Stdio :: piped ( ) ,
184265 stderr : std:: process:: Stdio :: inherit ( ) ,
0 commit comments