Unified · Split
| @@ -103,6 +103,7 @@ git-testtools = { path = "../tests/tools" } · 102 unmodified lines | |||
| 103 | is_ci = "1.1.1" | 103 | is_ci = "1.1.1" |
| 104 | anyhow = "1" | 104 | anyhow = "1" |
| 105 | tempfile = "3.2.0" | 105 | tempfile = "3.2.0" |
| 106 | serial_test = "0.8.0" | ||
| 106 | 107 | ||
| 107 | [package.metadata.docs.rs] | 108 | [package.metadata.docs.rs] |
| 108 | features = ["document-features", "max-performance", "one-stop-shop", "unstable", "blocking-network-client"] | 109 | features = ["document-features", "max-performance", "one-stop-shop", "unstable", "blocking-network-client"] |
| @@ -1,9 +1,10 @@ | |||
| 1 | use std::{convert::TryFrom, path::PathBuf}; | 1 | use std::{convert::TryFrom, path::PathBuf}; |
| 2 | 2 | ||
| 3 | use crate::repository; | ||
| 3 | use git_config::{Boolean, Integer}; | 4 | use git_config::{Boolean, Integer}; |
| 4 | 5 | ||
| 5 | use super::{Cache, Error}; | 6 | use super::{Cache, Error}; |
| 6 | use crate::{bstr::ByteSlice, permission}; | 7 | use crate::bstr::ByteSlice; |
| 7 | 8 | ||
| 8 | /// A utility to deal with the cyclic dependency between the ref store and the configuration. The ref-store needs the | 9 | /// A utility to deal with the cyclic dependency between the ref store and the configuration. The ref-store needs the |
| 9 | /// object hash kind, and the configuration needs the current branch name to resolve conditional includes with `onbranch`. | 10 | /// object hash kind, and the configuration needs the current branch name to resolve conditional includes with `onbranch`. |
| @@ -70,7 +71,7 @@ impl StageOne { · 60 unmodified lines | |||
| 70 | impl Cache { | 71 | impl Cache { |
| 71 | pub fn from_stage_one( | 72 | pub fn from_stage_one( |
| 72 | StageOne { | 73 | StageOne { |
| 73 | git_dir_config: mut config, | 74 | mut git_dir_config, |
| 74 | buf: _, | 75 | buf: _, |
| 75 | is_bare, | 76 | is_bare, |
| 76 | object_hash, | 77 | object_hash, |
| @@ -79,15 +80,17 @@ impl Cache { · 2 unmodified lines | |||
| 79 | git_dir: &std::path::Path, | 80 | git_dir: &std::path::Path, |
| 80 | branch_name: Option<&git_ref::FullNameRef>, | 81 | branch_name: Option<&git_ref::FullNameRef>, |
| 81 | mut filter_config_section: fn(&git_config::file::Metadata) -> bool, | 82 | mut filter_config_section: fn(&git_config::file::Metadata) -> bool, |
| 82 | xdg_config_home_env: permission::env_var::Resource, | ||
| 83 | home_env: permission::env_var::Resource, | ||
| 84 | git_install_dir: Option<&std::path::Path>, | 83 | git_install_dir: Option<&std::path::Path>, |
| 84 | repository::permissions::Environment { | ||
| 85 | git_prefix, | ||
| 86 | home: home_env, | ||
| 87 | xdg_config_home: xdg_config_home_env, | ||
| 88 | }: repository::permissions::Environment, | ||
| 85 | ) -> Result<Self, Error> { | 89 | ) -> Result<Self, Error> { |
| 86 | let home = std::env::var_os("HOME") | 90 | let home = std::env::var_os("HOME") |
| 87 | .map(PathBuf::from) | 91 | .map(PathBuf::from) |
| 88 | .and_then(|home| home_env.check(home).ok().flatten()); | 92 | .and_then(|home| home_env.check(home).ok().flatten()); |
| 89 | // TODO: don't forget to use the canonicalized home for initializing the stacked config. | 93 | |
| 90 | // like git here: https://github.com/git/git/blob/master/config.c#L208:L208 | ||
| 91 | let options = git_config::file::init::Options { | 94 | let options = git_config::file::init::Options { |
| 92 | lossy: !cfg!(debug_assertions), | 95 | lossy: !cfg!(debug_assertions), |
| 93 | includes: git_config::file::includes::Options::follow( | 96 | includes: git_config::file::includes::Options::follow( |
| @@ -98,6 +101,55 @@ impl Cache { · 4 unmodified lines | |||
| 98 | }, | 101 | }, |
| 99 | ), | 102 | ), |
| 100 | }; | 103 | }; |
| 104 | |||
| 105 | let mut config = { | ||
| 106 | let home_env = &home_env; | ||
| 107 | let xdg_config_home_env = &xdg_config_home_env; | ||
| 108 | let git_prefix = &git_prefix; | ||
| 109 | let metas = [git_config::source::Kind::System, git_config::source::Kind::Global] | ||
| 110 | .iter() | ||
| 111 | .flat_map(|kind| kind.sources()) | ||
| 112 | .filter_map(|source| { | ||
| 113 | let path = source | ||
| 114 | .storage_location(&mut |name| { | ||
| 115 | match name { | ||
| 116 | git_ if git_.starts_with("GIT_") => Some(git_prefix), | ||
| 117 | "XDG_CONFIG_HOME" => Some(xdg_config_home_env), | ||
| 118 | "HOME" => Some(home_env), | ||
| 119 | _ => None, | ||
| 120 | } | ||
| 121 | .and_then(|perm| std::env::var_os(name).and_then(|val| perm.check(val).ok().flatten())) | ||
| 122 | }) | ||
| 123 | .and_then(|p| p.is_file().then(|| p)) // todo: allow it to skip non-existing ones in the options to save check | ||
| 124 | .map(|p| p.into_owned()); | ||
| 125 | |||
| 126 | git_config::file::Metadata { | ||
| 127 | path, | ||
| 128 | source: *source, | ||
| 129 | level: 0, | ||
| 130 | trust: git_sec::Trust::Full, | ||
| 131 | } | ||
| 132 | .into() | ||
| 133 | }); | ||
| 134 | |||
| 135 | let no_include_options = git_config::file::init::Options { | ||
| 136 | includes: git_config::file::includes::Options::no_includes(), | ||
| 137 | ..options | ||
| 138 | }; | ||
| 139 | git_dir_config.resolve_includes(options)?; | ||
| 140 | match git_config::File::from_paths_metadata(metas, no_include_options).map_err(|err| match err { | ||
| 141 | git_config::file::init::from_paths::Error::Init(err) => Error::from(err), | ||
| 142 | git_config::file::init::from_paths::Error::Io(err) => err.into(), | ||
| 143 | })? { | ||
| 144 | Some(mut globals) => { | ||
| 145 | globals.resolve_includes(options)?; | ||
| 146 | globals.append(git_dir_config); | ||
| 147 | globals | ||
| 148 | } | ||
| 149 | None => git_dir_config, | ||
| 150 | } | ||
| 151 | }; | ||
| 152 | |||
| 101 | config.resolve_includes(options)?; | 153 | config.resolve_includes(options)?; |
| 102 | 154 | ||
| 103 | let excludes_file = config | 155 | let excludes_file = config |
| @@ -285,9 +285,8 @@ impl ThreadSafeRepository { · 284 unmodified lines | |||
| 285 | common_dir_ref, | 285 | common_dir_ref, |
| 286 | head.as_ref().and_then(|head| head.target.try_name()), | 286 | head.as_ref().and_then(|head| head.target.try_name()), |
| 287 | filter_config_section.unwrap_or(crate::config::section::is_trusted), | 287 | filter_config_section.unwrap_or(crate::config::section::is_trusted), |
| 288 | env.xdg_config_home.clone(), | ||
| 289 | env.home.clone(), | ||
| 290 | crate::path::install_dir().ok().as_deref(), | 288 | crate::path::install_dir().ok().as_deref(), |
| 289 | env.clone(), | ||
| 291 | )?; | 290 | )?; |
| 292 | 291 | ||
| 293 | if **git_dir_perm != git_sec::ReadWrite::all() { | 292 | if **git_dir_perm != git_sec::ReadWrite::all() { |
| @@ -26,17 +26,24 @@ pub struct Environment { · 25 unmodified lines | |||
| 26 | pub git_prefix: permission::env_var::Resource, | 26 | pub git_prefix: permission::env_var::Resource, |
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | impl Environment { | ||
| 30 | /// Allow access to the entire environment. | ||
| 31 | pub fn allow_all() -> Self { | ||
| 32 | Environment { | ||
| 33 | xdg_config_home: Access::resource(git_sec::Permission::Allow), | ||
| 34 | home: Access::resource(git_sec::Permission::Allow), | ||
| 35 | git_prefix: Access::resource(git_sec::Permission::Allow), | ||
| 36 | } | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 29 | impl Permissions { | 40 | impl Permissions { |
| 30 | /// Return permissions similar to what git does when the repository isn't owned by the current user, | 41 | /// Return permissions similar to what git does when the repository isn't owned by the current user, |
| 31 | /// thus refusing all operations in it. | 42 | /// thus refusing all operations in it. |
| 32 | pub fn strict() -> Self { | 43 | pub fn strict() -> Self { |
| 33 | Permissions { | 44 | Permissions { |
| 34 | git_dir: Access::resource(git_sec::ReadWrite::READ), | 45 | git_dir: Access::resource(git_sec::ReadWrite::READ), |
| 35 | env: Environment { | 46 | env: Environment::allow_all(), |
| 36 | xdg_config_home: Access::resource(git_sec::Permission::Allow), | ||
| 37 | home: Access::resource(git_sec::Permission::Allow), | ||
| 38 | git_prefix: Access::resource(git_sec::Permission::Allow), | ||
| 39 | }, | ||
| 40 | } | 47 | } |
| 41 | } | 48 | } |
| 42 | 49 | ||
| @@ -48,11 +55,7 @@ impl Permissions { · 5 unmodified lines | |||
| 48 | pub fn secure() -> Self { | 55 | pub fn secure() -> Self { |
| 49 | Permissions { | 56 | Permissions { |
| 50 | git_dir: Access::resource(git_sec::ReadWrite::all()), | 57 | git_dir: Access::resource(git_sec::ReadWrite::all()), |
| 51 | env: Environment { | 58 | env: Environment::allow_all(), |
| 52 | xdg_config_home: Access::resource(git_sec::Permission::Allow), | ||
| 53 | home: Access::resource(git_sec::Permission::Allow), | ||
| 54 | git_prefix: Access::resource(git_sec::Permission::Allow), | ||
| 55 | }, | ||
| 56 | } | 59 | } |
| 57 | } | 60 | } |
| 58 | 61 | ||
| @@ -61,11 +64,7 @@ impl Permissions { · 2 unmodified lines | |||
| 61 | pub fn all() -> Self { | 64 | pub fn all() -> Self { |
| 62 | Permissions { | 65 | Permissions { |
| 63 | git_dir: Access::resource(git_sec::ReadWrite::all()), | 66 | git_dir: Access::resource(git_sec::ReadWrite::all()), |
| 64 | env: Environment { | 67 | env: Environment::allow_all(), |
| 65 | xdg_config_home: Access::resource(git_sec::Permission::Allow), | ||
| 66 | home: Access::resource(git_sec::Permission::Allow), | ||
| 67 | git_prefix: Access::resource(git_sec::Permission::Allow), | ||
| 68 | }, | ||
| 69 | } | 68 | } |
| 70 | } | 69 | } |
| 71 | } | 70 | } |
| @@ -1,3 +1,3 @@ | |||
| 1 | version https://git-lfs.github.com/spec/v1 | 1 | version https://git-lfs.github.com/spec/v1 |
| 2 | oid sha256:20b64234f3e8047502160c3c56841b8ddf659798a8b02fc72b5a21bc85dee920 | 2 | oid sha256:5c16dcf86219e5dd5755e21472c40d289b246d62fb92594378f166a4920bad58 |
| 3 | size 9212 | 3 | size 9276 |
| @@ -3,7 +3,7 @@ set -eu -o pipefail · 2 unmodified lines | |||
| 3 | 3 | ||
| 4 | git init -q | 4 | git init -q |
| 5 | 5 | ||
| 6 | cat <<EOF>>.git/config | 6 | cat <<EOF >>.git/config |
| 7 | [a] | 7 | [a] |
| 8 | bool = on | 8 | bool = on |
| 9 | bad-bool = zero | 9 | bad-bool = zero |
| @@ -20,7 +20,19 @@ cat <<EOF>>.git/config · 10 unmodified lines | |||
| 20 | EOF | 20 | EOF |
| 21 | 21 | ||
| 22 | 22 | ||
| 23 | cat <<EOF>>a.config | 23 | cat <<EOF >>a.config |
| 24 | [a] | 24 | [a] |
| 25 | override = from-a.config | 25 | override = from-a.config |
| 26 | EOF | 26 | EOF |
| 27 | cat <<EOF >>b.config | ||
| 28 | [a] | ||
| 29 | system-override = from-b.config | ||
| 30 | EOF | ||
| 31 | |||
| 32 | cat <<EOF >>system.config | ||
| 33 | [a] | ||
| 34 | system = from-system.config | ||
| 35 | system-override = base | ||
| 36 | [include] | ||
| 37 | path = ./b.config | ||
| 38 | EOF | ||
| @@ -1,12 +1,37 @@ | |||
| 1 | use crate::named_repo; | 1 | use crate::named_repo; |
| 2 | use git_repository as git; | 2 | use git_repository as git; |
| 3 | use git_sec::{Access, Permission}; | ||
| 4 | use git_testtools::Env; | ||
| 5 | use serial_test::serial; | ||
| 3 | use std::path::Path; | 6 | use std::path::Path; |
| 4 | 7 | ||
| 5 | #[test] | 8 | #[test] |
| 9 | #[serial] | ||
| 6 | fn access_values() { | 10 | fn access_values() { |
| 7 | for trust in [git_sec::Trust::Full, git_sec::Trust::Reduced] { | 11 | for trust in [git_sec::Trust::Full, git_sec::Trust::Reduced] { |
| 8 | let repo = named_repo("make_config_repo.sh").unwrap(); | 12 | let repo = named_repo("make_config_repo.sh").unwrap(); |
| 9 | let repo = git::open_opts(repo.git_dir(), repo.open_options().clone().with(trust)).unwrap(); | 13 | let _env = Env::new().set( |
| 14 | "GIT_CONFIG_SYSTEM", | ||
| 15 | repo.work_dir() | ||
| 16 | .expect("present") | ||
| 17 | .join("system.config") | ||
| 18 | .canonicalize() | ||
| 19 | .unwrap() | ||
| 20 | .display() | ||
| 21 | .to_string(), | ||
| 22 | ); | ||
| 23 | let repo = git::open_opts( | ||
| 24 | repo.git_dir(), | ||
| 25 | repo.open_options().clone().with(trust).permissions(git::Permissions { | ||
| 26 | env: git::permissions::Environment { | ||
| 27 | xdg_config_home: Access::resource(Permission::Deny), | ||
| 28 | home: Access::resource(Permission::Deny), | ||
| 29 | ..git::permissions::Environment::allow_all() | ||
| 30 | }, | ||
| 31 | ..Default::default() | ||
| 32 | }), | ||
| 33 | ) | ||
| 34 | .unwrap(); | ||
| 10 | 35 | ||
| 11 | let config = repo.config_snapshot(); | 36 | let config = repo.config_snapshot(); |
| 12 | 37 | ||
| @@ -26,6 +51,14 @@ fn access_values() { · 13 unmodified lines | |||
| 26 | ); | 51 | ); |
| 27 | 52 | ||
| 28 | assert_eq!(config.string("a.override").expect("present").as_ref(), "from-a.config"); | 53 | assert_eq!(config.string("a.override").expect("present").as_ref(), "from-a.config"); |
| 54 | assert_eq!( | ||
| 55 | config.string("a.system").expect("present").as_ref(), | ||
| 56 | "from-system.config" | ||
| 57 | ); | ||
| 58 | assert_eq!( | ||
| 59 | config.string("a.system-override").expect("present").as_ref(), | ||
| 60 | "from-b.config" | ||
| 61 | ); | ||
| 29 | 62 | ||
| 30 | assert_eq!(config.boolean("core.missing"), None); | 63 | assert_eq!(config.boolean("core.missing"), None); |
| 31 | assert_eq!(config.try_boolean("core.missing"), None); | 64 | assert_eq!(config.try_boolean("core.missing"), None); |