The path to git repositories is well-known as they either end in `.git` or `.../.git`. If this is not the case, by default we append `/.git` to the path. With this new option enabled, no path transformations apply to open the given path as is, which is preferable if you know it's a non-standard git repository folder name.
Unified · Split
| @@ -23,6 +23,7 @@ pub struct Options { · 22 unmodified lines | |||
| 23 | pub(crate) bail_if_untrusted: bool, | 23 | pub(crate) bail_if_untrusted: bool, |
| 24 | pub(crate) api_config_overrides: Vec<BString>, | 24 | pub(crate) api_config_overrides: Vec<BString>, |
| 25 | pub(crate) cli_config_overrides: Vec<BString>, | 25 | pub(crate) cli_config_overrides: Vec<BString>, |
| 26 | pub(crate) open_path_as_is: bool, | ||
| 26 | /// Internal to pass an already obtained CWD on to where it may also be used. This avoids the CWD being queried more than once per repo. | 27 | /// Internal to pass an already obtained CWD on to where it may also be used. This avoids the CWD being queried more than once per repo. |
| 27 | pub(crate) current_dir: Option<PathBuf>, | 28 | pub(crate) current_dir: Option<PathBuf>, |
| 28 | } | 29 | } |
| @@ -13,6 +13,7 @@ impl Default for Options { · 12 unmodified lines | |||
| 13 | lossy_config: None, | 13 | lossy_config: None, |
| 14 | lenient_config: true, | 14 | lenient_config: true, |
| 15 | bail_if_untrusted: false, | 15 | bail_if_untrusted: false, |
| 16 | open_path_as_is: false, | ||
| 16 | api_config_overrides: Vec::new(), | 17 | api_config_overrides: Vec::new(), |
| 17 | cli_config_overrides: Vec::new(), | 18 | cli_config_overrides: Vec::new(), |
| 18 | current_dir: None, | 19 | current_dir: None, |
| @@ -69,6 +70,15 @@ impl Options { · 50 unmodified lines | |||
| 69 | self | 70 | self |
| 70 | } | 71 | } |
| 71 | 72 | ||
| 73 | /// If `true`, default `false`, we will not modify the incoming path to open to assure it is a `.git` directory. | ||
| 74 | /// | ||
| 75 | /// If `false`, we will try to open the input directory as is, even though it doesn't appear to be a `git` repository | ||
| 76 | /// due to the lack of `.git` suffix or because its basename is not `.git` as in `worktree/.git`. | ||
| 77 | pub fn open_path_as_is(mut self, enable: bool) -> Self { | ||
| 78 | self.open_path_as_is = enable; | ||
| 79 | self | ||
| 80 | } | ||
| 81 | |||
| 72 | /// Set the trust level of the `.git` directory we are about to open. | 82 | /// Set the trust level of the `.git` directory we are about to open. |
| 73 | /// | 83 | /// |
| 74 | /// This can be set manually to force trust even though otherwise it might | 84 | /// This can be set manually to force trust even though otherwise it might |
| @@ -146,6 +156,7 @@ impl git_sec::trust::DefaultForLevel for Options { · 71 unmodified lines | |||
| 146 | lossy_config: None, | 156 | lossy_config: None, |
| 147 | bail_if_untrusted: false, | 157 | bail_if_untrusted: false, |
| 148 | lenient_config: true, | 158 | lenient_config: true, |
| 159 | open_path_as_is: false, | ||
| 149 | api_config_overrides: Vec::new(), | 160 | api_config_overrides: Vec::new(), |
| 150 | cli_config_overrides: Vec::new(), | 161 | cli_config_overrides: Vec::new(), |
| 151 | current_dir: None, | 162 | current_dir: None, |
| @@ -157,6 +168,7 @@ impl git_sec::trust::DefaultForLevel for Options { · 5 unmodified lines | |||
| 157 | filter_config_section: Some(config::section::is_trusted), | 168 | filter_config_section: Some(config::section::is_trusted), |
| 158 | bail_if_untrusted: false, | 169 | bail_if_untrusted: false, |
| 159 | lenient_config: true, | 170 | lenient_config: true, |
| 171 | open_path_as_is: false, | ||
| 160 | lossy_config: None, | 172 | lossy_config: None, |
| 161 | api_config_overrides: Vec::new(), | 173 | api_config_overrides: Vec::new(), |
| 162 | cli_config_overrides: Vec::new(), | 174 | cli_config_overrides: Vec::new(), |
| @@ -48,13 +48,26 @@ impl ThreadSafeRepository { · 47 unmodified lines | |||
| 48 | pub fn open_opts(path: impl Into<PathBuf>, mut options: Options) -> Result<Self, Error> { | 48 | pub fn open_opts(path: impl Into<PathBuf>, mut options: Options) -> Result<Self, Error> { |
| 49 | let (path, kind) = { | 49 | let (path, kind) = { |
| 50 | let path = path.into(); | 50 | let path = path.into(); |
| 51 | match git_discover::is_git(&path) { | 51 | let looks_like_git_dir = |
| 52 | Ok(kind) => (path, kind), | 52 | path.ends_with(git_discover::DOT_GIT_DIR) || path.extension() == Some(std::ffi::OsStr::new("git")); |
| 53 | Err(_err) => { | 53 | let candidate = if !options.open_path_as_is && !looks_like_git_dir { |
| 54 | let git_dir = path.join(git_discover::DOT_GIT_DIR); | 54 | Cow::Owned(path.join(git_discover::DOT_GIT_DIR)) |
| 55 | git_discover::is_git(&git_dir) | 55 | } else { |
| 56 | .map(|kind| (git_dir, kind)) | 56 | Cow::Borrowed(&path) |
| 57 | .map_err(|err| Error::NotARepository { source: err, path })? | 57 | }; |
| 58 | match git_discover::is_git(candidate.as_ref()) { | ||
| 59 | Ok(kind) => (candidate.into_owned(), kind), | ||
| 60 | Err(err) => { | ||
| 61 | if options.open_path_as_is || matches!(candidate, Cow::Borrowed(_)) { | ||
| 62 | return Err(Error::NotARepository { | ||
| 63 | source: err, | ||
| 64 | path: candidate.into_owned(), | ||
| 65 | }); | ||
| 66 | } | ||
| 67 | match git_discover::is_git(&path) { | ||
| 68 | Ok(kind) => (path, kind), | ||
| 69 | Err(err) => return Err(Error::NotARepository { source: err, path }), | ||
| 70 | } | ||
| 58 | } | 71 | } |
| 59 | } | 72 | } |
| 60 | }; | 73 | }; |
| @@ -128,6 +141,7 @@ impl ThreadSafeRepository { · 67 unmodified lines | |||
| 128 | lossy_config, | 141 | lossy_config, |
| 129 | lenient_config, | 142 | lenient_config, |
| 130 | bail_if_untrusted, | 143 | bail_if_untrusted, |
| 144 | open_path_as_is: _, | ||
| 131 | permissions: Permissions { ref env, config }, | 145 | permissions: Permissions { ref env, config }, |
| 132 | ref api_config_overrides, | 146 | ref api_config_overrides, |
| 133 | ref cli_config_overrides, | 147 | ref cli_config_overrides, |
| @@ -46,6 +46,34 @@ mod not_a_repository { · 45 unmodified lines | |||
| 46 | } | 46 | } |
| 47 | } | 47 | } |
| 48 | 48 | ||
| 49 | mod open_path_as_is { | ||
| 50 | use crate::util::{named_subrepo_opts, repo_opts}; | ||
| 51 | use git_repository as git; | ||
| 52 | |||
| 53 | fn open_path_as_is() -> git::open::Options { | ||
| 54 | git::open::Options::isolated().open_path_as_is(true) | ||
| 55 | } | ||
| 56 | |||
| 57 | #[test] | ||
| 58 | fn bare_repos_open_normally() -> crate::Result { | ||
| 59 | assert!(named_subrepo_opts("make_basic_repo.sh", "bare.git", open_path_as_is())?.is_bare()); | ||
| 60 | Ok(()) | ||
| 61 | } | ||
| 62 | |||
| 63 | #[test] | ||
| 64 | fn worktrees_cannot_be_opened() -> crate::Result { | ||
| 65 | let err = repo_opts("make_basic_repo.sh", open_path_as_is()).unwrap_err(); | ||
| 66 | assert!(matches!(err, git::open::Error::NotARepository { .. })); | ||
| 67 | Ok(()) | ||
| 68 | } | ||
| 69 | |||
| 70 | #[test] | ||
| 71 | fn git_dir_within_worktrees_open_normally() -> crate::Result { | ||
| 72 | assert!(!named_subrepo_opts("make_basic_repo.sh", ".git", open_path_as_is())?.is_bare()); | ||
| 73 | Ok(()) | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 49 | mod submodules { | 77 | mod submodules { |
| 50 | use std::path::Path; | 78 | use std::path::Path; |
| 51 | 79 | ||
| @@ -18,6 +18,11 @@ pub fn repo(name: &str) -> Result<ThreadSafeRepository> { · 17 unmodified lines | |||
| 18 | Ok(ThreadSafeRepository::open_opts(repo_path, restricted())?) | 18 | Ok(ThreadSafeRepository::open_opts(repo_path, restricted())?) |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | pub fn repo_opts(name: &str, opts: open::Options) -> std::result::Result<ThreadSafeRepository, open::Error> { | ||
| 22 | let repo_path = git_testtools::scripted_fixture_read_only(name).unwrap(); | ||
| 23 | ThreadSafeRepository::open_opts(repo_path, opts) | ||
| 24 | } | ||
| 25 | |||
| 21 | pub fn named_repo(name: &str) -> Result<Repository> { | 26 | pub fn named_repo(name: &str) -> Result<Repository> { |
| 22 | let repo_path = git_testtools::scripted_fixture_read_only(name)?; | 27 | let repo_path = git_testtools::scripted_fixture_read_only(name)?; |
| 23 | Ok(ThreadSafeRepository::open_opts(repo_path, restricted())?.to_thread_local()) | 28 | Ok(ThreadSafeRepository::open_opts(repo_path, restricted())?.to_thread_local()) |