Skip to content

config: Use XDG-standard config location on macOS #6300

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 2, 2025

Conversation

strega-nil
Copy link
Contributor

@strega-nil strega-nil commented Apr 9, 2025

Changing where the default config location is on macOS, switching to etcetera from dirs.

Some points for discussion:

  • I am treating the classic behaviour as a bug, not a feature; I do not believe that it is correct in any way to place configuration in ~/Library/Application Support, ever, and certainly not for CLI applications
  • Currently, if you run jj config edit --user, and you already have a config in ~/Library/Application Support, you'll get a picker for both; this seems less than ideal? Perhaps advice would be good? Or we could ignore the normal file if the legacy file exists?
    • This has been changed as of latest patch set – it may be desireable to pull that patch out into its own PR. Let me know.
    • This bugfix has been pulled into its own PR: config: only add new when no other config exists #6315
    • This was not a bug. I screwed up my own computer's configuration.

This PR takes a lot of inspiration from @ckoehler's #3466, but has been rewritten since the current codebase is a hell of a lot more amenable to these changes.

Fixes #3434

Checklist

If applicable:

  • I have updated CHANGELOG.md
  • I have updated the documentation (README.md, docs/, demos/)
  • I have updated the config schema (cli/src/config-schema.json)
  • I have added tests to cover my changes
  • I have played around with my changes

@strega-nil strega-nil requested a review from a team as a code owner April 9, 2025 20:23
@martinvonz
Copy link
Member

config: Correct macOS config location

nit: Is the old behavior actually incorrect? Say "Follow XDG spec for macOS config location" or something like that if that's the spec we're talking about.

@strega-nil strega-nil force-pushed the strega-nil/etcetera branch from e0d7710 to 7050863 Compare April 9, 2025 20:32
@strega-nil
Copy link
Contributor Author

strega-nil commented Apr 9, 2025

@martinvonz I personally believe strongly that it is incorrect. User-editable configuration should not be placed in ~/Library/Application Support on macOS. I don't even really believe non-user-editable configuration should be in ~/Library/Application Support, but I don't really have strong opinions on what GUI programs do.

It is not incorrect in that it will work, but it breaks the assumptions that normal people have for CLI programs on macOS. macOS is a Unix, and it is expected that configuration goes in $XDG_CONFIG_HOME.

Copy link
Contributor

@PhilipMetzger PhilipMetzger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just some minor deprecation stuff but the patch SGTM

@strega-nil strega-nil changed the title config: Correct macOS config location config: Use XDG-standard config location on macOS Apr 10, 2025
@strega-nil strega-nil force-pushed the strega-nil/etcetera branch from 7050863 to 8546437 Compare April 10, 2025 21:41
@strega-nil strega-nil force-pushed the strega-nil/etcetera branch from 8546437 to ddf454c Compare April 11, 2025 06:55
@wjv
Copy link

wjv commented Apr 11, 2025

Late to the party here, but the NeXTStep-derived ~/Library/Application Support is indeed standard on macOS; the Mac App Programming Guide explicitly states that it is also the location for configuration files. (It's arguable whether that Guide covers 3rd party command-line applications, which aren't things Apple institutionally cares about.)

Conversely, the XDG Base Directory Specification carries no official weight on macOS. (Nor has it been universally adopted beyond Linux.)

Now of course, I personally prefer using XDG (almost as much as I like a plain ~/jjconfig.toml). I personally dislike that an increasing number of command-line tools use ~/Library/Application Support. But the standards are what they are, and probably shouldn't be treated as a bug.

As long as all three options are possible, everything is probably fine!

@strega-nil
Copy link
Contributor Author

strega-nil commented Apr 11, 2025

@wjv I think the arguments against using ~/Library/Application Support are made fairly clear in the rest of the thread; jj is not an application, and therefore the standard does not apply, and is incorrectly (imo) used here (and in other Rust tools that depend on dirs).

XDG, while not an official standard on macOS, has become a de-facto standard among the vast majority of CLI application developers, and as a person who has used macOS for [redacted] decades at this point, it is significantly more correct to use XDG rather than ~/Library/Application Support – if someone can find an officially released apple CLI tool that uses ~/Library/Application Support, I might change my tune, but Apple absolutely ships tools that respect XDG (including, just as an example, vim), and as far as I know ships zero tools that use ~/Library/Application Support

@strega-nil strega-nil force-pushed the strega-nil/etcetera branch from ddf454c to d661d7b Compare April 11, 2025 11:49
@wjv
Copy link

wjv commented Apr 11, 2025

@strega-nil To be fair, Apple ships 3rd party tools that originate outside the macOS ecosystem that happen to support XDG. :)

I see your arguments, here and elsewhere. I even agree. But as someone who has also used macOS for [redacted] decades, I think it would be a mistake to completely remove support for the directory that is, as much as anything, the standard. And that certainly some Mac users would expect.

Look, as long as a simple config file in ${HOME} works, I'm personally happy!

@strega-nil
Copy link
Contributor Author

I'll work on this more this week maybe, but easter has been a bit hectic, so no work on this for now :)

@strega-nil strega-nil force-pushed the strega-nil/etcetera branch 3 times, most recently from 48540c5 to 6b5cb55 Compare April 19, 2025 10:23
@strega-nil
Copy link
Contributor Author

Alright, it has been worked on!

@strega-nil
Copy link
Contributor Author

strega-nil commented Apr 20, 2025

And another update; now, it only warns on all config commands, so as to avoid annoying people too much:

Screenshot of jj printing a warning on `jj config edit` and `jj config path`, but not `jj st`

I'm happy to also warn on every command, but I don't feel it's necessary.

Interdiff
diff --git a/cli/src/commands/config/mod.rs b/cli/src/commands/config/mod.rs
index 5d8fefc3af..306c314fd7 100644
--- a/cli/src/commands/config/mod.rs
+++ b/cli/src/commands/config/mod.rs
@@ -41,7 +41,6 @@
 use crate::command_error::CommandError;
 use crate::config::ConfigEnv;
 use crate::config::ConfigPath;
-use crate::config::DeprecatedLocation;
 use crate::ui::Ui;
 
 #[derive(clap::Args, Clone, Debug)]
@@ -73,32 +72,10 @@
         config_env: &'a ConfigEnv,
     ) -> Result<Vec<&'a ConfigPath>, CommandError> {
         if self.user {
+            config_env.warn_for_deprecated_paths(ui);
             let paths = config_env.user_config_paths().collect_vec();
             if paths.is_empty() {
                 return Err(user_error("No user config path found"));
-            } else {
-                for p in &paths {
-                    if let Some(DeprecatedLocation {
-                        old_location,
-                        new_location,
-                    }) = p.deprecated_location()
-                    {
-                        writeln!(
-                            ui.warning_default(),
-                            "Deprecated configuration file `{}`:",
-                            p.path().display()
-                        )?;
-                        writeln!(
-                            ui.warning_default(),
-                            "configuration files in `{old_location}` are deprecated, and support \
-                             will be removed in a future release.",
-                        )?;
-                        writeln!(
-                            ui.warning_default(),
-                            "Instead, move your configuration files to `{new_location}`.",
-                        )?;
-                    }
-                }
             }
             Ok(paths)
         } else if self.repo {
@@ -134,6 +111,7 @@
             files.pop().ok_or_else(|| user_error(not_found_error))
         };
         if self.user {
+            config_env.warn_for_deprecated_paths(ui);
             pick_one(
                 config_env.user_config_files(config)?,
                 "No user config path found to edit",
diff --git a/cli/src/config.rs b/cli/src/config.rs
index 66afd4456d..80b959fbf8 100644
--- a/cli/src/config.rs
+++ b/cli/src/config.rs
@@ -40,6 +40,7 @@
 use crate::command_error::config_error;
 use crate::command_error::config_error_with_message;
 use crate::command_error::CommandError;
+use crate::ui::Ui;
 
 // TODO(#879): Consider generating entire schema dynamically vs. static file.
 pub const CONFIG_SCHEMA: &str = include_str!("config-schema.json");
@@ -161,9 +162,9 @@
 }
 
 #[derive(Copy, Clone, Debug)]
-pub(crate) struct DeprecatedLocation {
-    pub(crate) old_location: &'static str,
-    pub(crate) new_location: &'static str,
+struct DeprecatedLocation {
+    old: &'static str,
+    new: &'static str,
 }
 #[derive(Clone, Debug)]
 enum ConfigPathState {
@@ -186,17 +187,10 @@
             path,
         }
     }
-    fn new_deprecated(
-        path: PathBuf,
-        old_location: &'static str,
-        new_location: &'static str,
-    ) -> Self {
+    fn new_deprecated(path: PathBuf, old: &'static str, new: &'static str) -> Self {
         use ConfigPathState::*;
         let state = if path.exists() {
-            Deprecated(DeprecatedLocation {
-                old_location,
-                new_location,
-            })
+            Deprecated(DeprecatedLocation { old, new })
         } else {
             New
         };
@@ -213,7 +207,7 @@
             ConfigPathState::New => false,
         }
     }
-    pub(crate) fn deprecated_location(&self) -> Option<DeprecatedLocation> {
+    fn deprecated_location(&self) -> Option<DeprecatedLocation> {
         match self.state {
             ConfigPathState::Deprecated(d) => Some(d),
             ConfigPathState::Exists | ConfigPathState::New => None,
@@ -371,6 +365,30 @@
         self.command = Some(command);
     }
 
+    pub fn warn_for_deprecated_paths(&self, ui: &Ui) {
+        if cfg!(target_os = "macos") {
+            for p in &self.user_config_paths {
+                let Some(DeprecatedLocation { old, new }) = p.deprecated_location() else {
+                    continue;
+                };
+                let _ = writeln!(
+                    ui.warning_default(),
+                    "Deprecated configuration file `{}`:",
+                    p.path().display()
+                );
+                let _ = writeln!(
+                    ui.warning_default(),
+                    "configuration files in `{old}` are deprecated, and support will be removed \
+                     in a future release.",
+                );
+                let _ = writeln!(
+                    ui.warning_default(),
+                    "Instead, move your configuration files to `{new}`.",
+                );
+            }
+        }
+    }
+
     /// Returns the paths to the user-specific config files or directories.
     pub fn user_config_paths(&self) -> impl Iterator<Item = &ConfigPath> {
         self.user_config_paths.iter()
@@ -379,7 +397,7 @@
     /// Returns the paths to the existing user-specific config files or
     /// directories.
     pub fn existing_user_config_paths(&self) -> impl Iterator<Item = &ConfigPath> {
-        self.user_config_paths.iter().filter(|p| p.exists())
+        self.user_config_paths().filter(|p| p.exists())
     }
 
     /// Returns user configuration files for modification. Instantiates one if

@strega-nil strega-nil force-pushed the strega-nil/etcetera branch 3 times, most recently from 5685af2 to b1f1dfe Compare April 21, 2025 09:04
@strega-nil strega-nil force-pushed the strega-nil/etcetera branch 2 times, most recently from 868c10f to 96fe150 Compare April 24, 2025 22:04
@strega-nil
Copy link
Contributor Author

@martinvonz I believe I have resolved all of your concerns :)

I split the new ConfigPath representation into its own commit, going before everything else.

@strega-nil strega-nil force-pushed the strega-nil/etcetera branch from 96fe150 to 8caf647 Compare April 24, 2025 22:13
@strega-nil strega-nil force-pushed the strega-nil/etcetera branch from 4b6a27c to d1110e1 Compare April 29, 2025 18:31
@strega-nil
Copy link
Contributor Author

Added some more info, and removed get_existing

range-diff
  • 1: bcd366b ! 1: f4a73fa config: change how config paths are represented
@@ Metadata
  ## Commit message ##
     config: change how config paths are represented
 
+    This change, from an enum to a struct, is a more accurate representation
+    of the actual way that a ConfigPath works; additionally, it lets us add
+    different information without modifying every single enumeration field.
+
+    The change to `config_paths()`, to return a `ConfigPath` instead of a
+    `Path`, is less interesting, but in my opinion results in slightly more
+    obvious code. It also makes the test code significantly easier.
+
  ## cli/src/commands/config/mod.rs ##
 @@ cli/src/commands/config/mod.rs: mod path;
  mod set;
@@ cli/Cargo.toml: clap_complete_nushell = { workspace = true }
  gix = { workspace = true, optional = true }
 
  ## cli/src/config.rs ##
+@@ cli/src/config.rs: impl UnresolvedConfigEnv {
+ 
+         // theoretically this should be an `if let Some(...) = ... && ..., but that
+         // isn't stable
+-        let get_existing = |opt: Option<ConfigPath>| match opt {
+-            Some(path) if path.exists() => Some(path),
+-            _ => None,
+-        };
+-        if let Some(path) = get_existing(platform_config_dir) {
+-            paths.push(path);
++        if let Some(path) = platform_config_dir {
++            if path.exists() {
++                paths.push(path);
++            }
+         }
+         paths
+     }
 @@ cli/src/config.rs: pub struct ConfigEnv {
  impl ConfigEnv {
      /// Initializes configuration loader based on environment variables.
  • 3: a4a790e ! 3: 57b4cd7 config: default to XDG config files on macOS
@@ cli/src/config.rs: impl UnresolvedConfigEnv {
              }
          }
 @@ cli/src/config.rs: impl UnresolvedConfigEnv {
-         if let Some(path) = get_existing(platform_config_dir) {
              paths.push(path);
          }
+ 
+-        // theoretically this should be an `if let Some(...) = ... && ..., but that
++        // theoretically these should be an `if let Some(...) = ... && ..., but that
+         // isn't stable
+         if let Some(path) = platform_config_dir {
+             if path.exists() {
+                 paths.push(path);
+             }
+         }
 +
-+        if let Some(path) = get_existing(legacy_platform_config_path) {
-+            paths.push(path);
++        if let Some(path) = legacy_platform_config_path {
++            if path.exists() {
++                paths.push(path);
++            }
 +        }
-+        if let Some(path) = get_existing(legacy_platform_config_dir) {
-+            paths.push(path);
++        if let Some(path) = legacy_platform_config_dir {
++            if path.exists() {
++                paths.push(path);
++            }
 +        }
 +
          paths
  • 4: b6c8143 = 4: d1110e1 config: deprecate macOS legacy platform configs

@strega-nil strega-nil force-pushed the strega-nil/etcetera branch from d1110e1 to fd20633 Compare April 30, 2025 16:42
@strega-nil strega-nil force-pushed the strega-nil/etcetera branch from fd20633 to 41a110a Compare April 30, 2025 16:53
@strega-nil
Copy link
Contributor Author

strega-nil commented Apr 30, 2025

I really hope that I've resolved all the nits now 😅

range-diff
  • 1: f4a73fa ! 1: 8d66617 config: change how config paths are represented
@@ Commit message
     of the actual way that a ConfigPath works; additionally, it lets us add
     different information without modifying every single enumeration field.
 
-    The change to `config_paths()`, to return a `ConfigPath` instead of a
-    `Path`, is less interesting, but in my opinion results in slightly more
-    obvious code. It also makes the test code significantly easier.
-
- ## cli/src/commands/config/mod.rs ##
-@@ cli/src/commands/config/mod.rs: mod path;
- mod set;
- mod unset;
- 
--use std::path::Path;
--
- use itertools::Itertools as _;
- use jj_lib::config::ConfigFile;
- use jj_lib::config::ConfigSource;
-@@ cli/src/commands/config/mod.rs: use crate::cli_util::CommandHelper;
- use crate::command_error::user_error;
- use crate::command_error::CommandError;
- use crate::config::ConfigEnv;
-+use crate::config::ConfigPath;
- use crate::ui::Ui;
- 
- #[derive(clap::Args, Clone, Debug)]
-@@ cli/src/commands/config/mod.rs: impl ConfigLevelArgs {
-         }
-     }
- 
--    fn config_paths<'a>(&self, config_env: &'a ConfigEnv) -> Result<Vec<&'a Path>, CommandError> {
-+    fn config_paths<'a>(
-+        &self,
-+        config_env: &'a ConfigEnv,
-+    ) -> Result<Vec<&'a ConfigPath>, CommandError> {
-         if self.user {
-             let paths = config_env.user_config_paths().collect_vec();
-             if paths.is_empty() {
-
- ## cli/src/commands/config/path.rs ##
-@@ cli/src/commands/config/path.rs: pub fn cmd_config_path(
-             ui.stdout(),
-             "{}",
-             config_path
-+                .path()
-                 .to_str()
-                 .ok_or_else(|| user_error("The config path is not valid UTF-8"))?
-         )?;
-
  ## cli/src/config.rs ##
 @@ cli/src/config.rs: impl AsMut<StackedConfig> for RawConfig {
  }
@@ cli/src/config.rs: impl AsMut<StackedConfig> for RawConfig {
 +/// - !exists(): a config file doesn't exist here, but a new file _can_ be
 +///   created at this path
 +#[derive(Clone, Debug)]
-+pub struct ConfigPath {
++struct ConfigPath {
 +    path: PathBuf,
 +    state: ConfigPathState,
  }
@@ cli/src/config.rs: impl AsMut<StackedConfig> for RawConfig {
          }
      }
  
--    fn as_path(&self) -> &Path {
+     fn as_path(&self) -> &Path {
 -        match self {
 -            ConfigPath::Existing(path) | ConfigPath::New(path) => path,
-+    pub fn path(&self) -> &Path {
 +        &self.path
 +    }
 +
-+    pub(crate) fn exists(&self) -> bool {
++    fn exists(&self) -> bool {
 +        match self.state {
 +            ConfigPathState::Exists => true,
 +            ConfigPathState::New => false,
@@ cli/src/config.rs: impl UnresolvedConfigEnv {
          }
          paths
 @@ cli/src/config.rs: impl ConfigEnv {
-     }
- 
-     /// Returns the paths to the user-specific config files or directories.
--    pub fn user_config_paths(&self) -> impl Iterator<Item = &Path> {
--        self.user_config_paths.iter().map(|p| p.as_path())
-+    pub fn user_config_paths(&self) -> impl Iterator<Item = &ConfigPath> {
-+        self.user_config_paths.iter()
-     }
- 
      /// Returns the paths to the existing user-specific config files or
      /// directories.
--    pub fn existing_user_config_paths(&self) -> impl Iterator<Item = &Path> {
+     pub fn existing_user_config_paths(&self) -> impl Iterator<Item = &Path> {
 -        self.user_config_paths.iter().filter_map(|p| match p {
 -            ConfigPath::Existing(path) => Some(path.as_path()),
 -            _ => None,
 -        })
-+    pub fn existing_user_config_paths(&self) -> impl Iterator<Item = &ConfigPath> {
-+        self.user_config_paths().filter(|p| p.exists())
++        self.user_config_paths
++            .iter()
++            .filter_map(|p| if p.exists() { Some(p.as_path()) } else { None })
      }
  
      /// Returns user configuration files for modification. Instantiates one if
 @@ cli/src/config.rs: impl ConfigEnv {
-             .map(|path| {
-                 // No need to propagate io::Error here. If the directory
-                 // couldn't be created, file.save() would fail later.
--                if let Some(dir) = path.parent() {
-+                if let Some(dir) = path.path().parent() {
-                     create_dir_all(dir).ok();
-                 }
-                 // The path doesn't usually exist, but we shouldn't overwrite it
-                 // with an empty config if it did exist.
--                ConfigFile::load_or_empty(ConfigSource::User, path)
-+                ConfigFile::load_or_empty(ConfigSource::User, path.path())
-             })
-             .transpose()
-     }
-@@ cli/src/config.rs: impl ConfigEnv {
-     pub fn reload_user_config(&self, config: &mut RawConfig) -> Result<(), ConfigLoadError> {
-         config.as_mut().remove_layers(ConfigSource::User);
-         for path in self.existing_user_config_paths() {
--            if path.is_dir() {
--                config.as_mut().load_dir(ConfigSource::User, path)?;
-+            if path.path().is_dir() {
-+                config.as_mut().load_dir(ConfigSource::User, path.path())?;
-             } else {
--                config.as_mut().load_file(ConfigSource::User, path)?;
-+                config.as_mut().load_file(ConfigSource::User, path.path())?;
-             }
-         }
-         Ok(())
-@@ cli/src/config.rs: impl ConfigEnv {
-     }
  
      /// Returns a path to the repo-specific config file.
--    pub fn repo_config_path(&self) -> Option<&Path> {
+     pub fn repo_config_path(&self) -> Option<&Path> {
 -        self.repo_config_path.as_ref().map(ConfigPath::as_path)
-+    pub fn repo_config_path(&self) -> Option<&ConfigPath> {
-+        self.repo_config_path.as_ref()
++        self.repo_config_path.as_ref().map(|p| p.as_path())
      }
  
      /// Returns a path to the existing repo-specific config file.
@@ cli/src/config.rs: impl ConfigEnv {
 -        match &self.repo_config_path {
 -            Some(ConfigPath::Existing(path)) => Some(path),
 +        match self.repo_config_path {
-+            Some(ref path) if path.exists() => Some(path.path()),
++            Some(ref path) if path.exists() => Some(path.as_path()),
              _ => None,
          }
      }
-@@ cli/src/config.rs: impl ConfigEnv {
-         self.repo_config_path()
-             // The path doesn't usually exist, but we shouldn't overwrite it
-             // with an empty config if it did exist.
--            .map(|path| ConfigFile::load_or_empty(ConfigSource::Repo, path))
-+            .map(|path| ConfigFile::load_or_empty(ConfigSource::Repo, path.path()))
-             .transpose()
-     }
- 
 @@ cli/src/config.rs: mod tests {
      struct TestCase {
          files: &'static [&'static str],
@@ cli/src/config.rs: mod tests {
 +        }
 +
 +        fn matches(&self, root: &Path, path: &ConfigPath) -> bool {
-+            if root.join(self.path) != path.path() {
++            if root.join(self.path) != path.as_path() {
 +                return false;
 +            }
 +
@@ cli/src/config.rs: mod tests {
 -            env.existing_user_config_paths().collect_vec(),
 -            expected_existing
 -        );
-+        let config_paths = env.user_config_paths().cloned().collect_vec();
++        let config_paths = env.user_config_paths.clone();
 +        let correct = case.wants.len() == config_paths.len()
 +            && case
 +                .wants
@@ cli/Cargo.toml: clap_complete_nushell = { workspace = true }
  gix = { workspace = true, optional = true }
 
  ## cli/src/config.rs ##
+@@ cli/src/config.rs: use std::path::Path;
+ use std::path::PathBuf;
+ use std::process::Command;
+ 
++use etcetera::BaseStrategy as _;
+ use itertools::Itertools as _;
+ use jj_lib::config::ConfigFile;
+ use jj_lib::config::ConfigGetError;
 @@ cli/src/config.rs: impl UnresolvedConfigEnv {
  
          // theoretically this should be an `if let Some(...) = ... && ..., but that
@@ cli/src/config.rs: pub struct ConfigEnv {
 +            etcetera::base_strategy::choose_native_strategy()
 +                .ok()
 +                .map(|s| {
-+                    use etcetera::BaseStrategy as _;
 +                    // note that etcetera calls Library/Application Support the "data dir",
 +                    // Library/Preferences is supposed to be exclusively plists
 +                    s.data_dir()
 +                })
 +        } else {
-+            etcetera::choose_base_strategy().ok().map(|s| {
-+                use etcetera::BaseStrategy as _;
-+                s.config_dir()
-+            })
++            etcetera::choose_base_strategy()
++                .ok()
++                .map(|s| s.config_dir())
 +        };
 +
          // Canonicalize home as we do canonicalize cwd in CliRunner. $HOME might
  • 3: 57b4cd7 ! 3: ac3f5e8 config: default to XDG config files on macOS
@@ cli/src/config.rs: pub struct ConfigEnv {
      /// Initializes configuration loader based on environment variables.
      pub fn from_environment() -> Self {
 -        let config_dir = if cfg!(target_os = "macos") {
-+        let config_dir = etcetera::choose_base_strategy().ok().map(|s| {
-+            use etcetera::BaseStrategy as _;
-+            s.config_dir()
-+        });
++        let config_dir = etcetera::choose_base_strategy()
++            .ok()
++            .map(|s| s.config_dir());
 +
 +        // older versions of jj used a more "GUI" config option,
 +        // which is not designed for user-editable configuration of CLI utilities.
@@ cli/src/config.rs: impl ConfigEnv {
                      s.data_dir()
                  })
          } else {
--            etcetera::choose_base_strategy().ok().map(|s| {
--                use etcetera::BaseStrategy as _;
--                s.config_dir()
--            })
+-            etcetera::choose_base_strategy()
+-                .ok()
+-                .map(|s| s.config_dir())
 +            None
          };
  
  • 4: d1110e1 < -: --------- config: deprecate macOS legacy platform configs
  • -: --------- > 4: 41a110a config: deprecate macOS legacy platform configs

Copy link
Contributor

@yuja yuja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks!

strega-nil added 2 commits May 1, 2025 14:10
This change, from an enum to a struct, is a more accurate representation
of the actual way that a ConfigPath works; additionally, it lets us add
different information without modifying every single enumeration field.
@strega-nil strega-nil force-pushed the strega-nil/etcetera branch from 41a110a to 0847672 Compare May 1, 2025 12:10
@strega-nil
Copy link
Contributor Author

range-diff
  • 1: 8d66617 ! 1: ae2b0de24 config: change how config paths are represented
@@ cli/src/config.rs: impl UnresolvedConfigEnv {
              paths.push(path);
          }
 -        if let Some(path @ Existing(_)) = platform_config_dir {
+-            paths.push(path);
 +
 +        // theoretically this should be an `if let Some(...) = ... && ..., but that
 +        // isn't stable
-+        let get_existing = |opt: Option<ConfigPath>| match opt {
-+            Some(path) if path.exists() => Some(path),
-+            _ => None,
-+        };
-+        if let Some(path) = get_existing(platform_config_dir) {
-             paths.push(path);
++        if let Some(path) = platform_config_dir {
++            if path.exists() {
++                paths.push(path);
++            }
          }
          paths
+     }
 @@ cli/src/config.rs: impl ConfigEnv {
+ 
+     /// Returns the paths to the user-specific config files or directories.
+     pub fn user_config_paths(&self) -> impl Iterator<Item = &Path> {
+-        self.user_config_paths.iter().map(|p| p.as_path())
++        self.user_config_paths.iter().map(ConfigPath::as_path)
+     }
+ 
      /// Returns the paths to the existing user-specific config files or
      /// directories.
      pub fn existing_user_config_paths(&self) -> impl Iterator<Item = &Path> {
@@ cli/src/config.rs: impl ConfigEnv {
 -        })
 +        self.user_config_paths
 +            .iter()
-+            .filter_map(|p| if p.exists() { Some(p.as_path()) } else { None })
++            .filter(|p| p.exists())
++            .map(ConfigPath::as_path)
      }
  
      /// Returns user configuration files for modification. Instantiates one if
@@ cli/src/config.rs: mod tests {
 +            }
 +        }
 +
-+        fn matches(&self, root: &Path, path: &ConfigPath) -> bool {
-+            if root.join(self.path) != path.as_path() {
-+                return false;
-+            }
++        fn rooted_path(&self, root: &Path) -> PathBuf {
++            root.join(self.path)
++        }
 +
-+            match self.state {
-+                WantState::New => !path.exists(),
-+                WantState::Existing => path.exists(),
-+            }
++        fn exists(&self) -> bool {
++            matches!(self.state, WantState::Existing)
 +        }
      }
  
@@ cli/src/config.rs: mod tests {
          let env = resolve_config_env(&case.env, tmp.path());
  
 -        let expected_existing: Vec<PathBuf> = case
--            .wants
--            .iter()
++        let all_expected_paths = case
+             .wants
+             .iter()
 -            .filter_map(|want| match want {
 -                Want::Existing(path) => Some(tmp.path().join(path)),
 -                _ => None,
@@ cli/src/config.rs: mod tests {
 -            env.existing_user_config_paths().collect_vec(),
 -            expected_existing
 -        );
-+        let config_paths = env.user_config_paths.clone();
-+        let correct = case.wants.len() == config_paths.len()
-+            && case
-+                .wants
-+                .iter()
-+                .zip(&config_paths)
-+                .all(|(w, p)| w.matches(tmp.path(), p));
++            .map(|w| w.rooted_path(tmp.path()))
++            .collect_vec();
++        let exists_expected_paths = case
++            .wants
++            .iter()
++            .filter(|w| w.exists())
++            .map(|w| w.rooted_path(tmp.path()))
++            .collect_vec();
  
 -        let expected_paths: Vec<PathBuf> = case
 -            .wants
@@ cli/src/config.rs: mod tests {
 -            })
 -            .collect();
 -        assert_eq!(env.user_config_paths().collect_vec(), expected_paths);
-+        assert!(correct, "{:#?} != {:#?}", case.wants, config_paths);
++        let all_paths = env.user_config_paths().collect_vec();
++        let exists_paths = env.existing_user_config_paths().collect_vec();
++
++        assert_eq!(all_paths, all_expected_paths);
++        assert_eq!(exists_paths, exists_expected_paths);
      }
  
      fn setup_config_fs(files: &[&str]) -> tempfile::TempDir {
  • 2: c16bafb ! 2: 14091e809 config: switch to etcetera from dirs
    • this is just a move from this commit to the last commit
  • 3: ac3f5e8 = 3: ecd6c3c88 config: default to XDG config files on macOS
  • 4: 41a110a ! 4: 03c3941ba config: deprecate macOS legacy platform configs
@@ cli/src/config.rs: impl UnresolvedConfigEnv {
      }
 +
 +    fn warn_for_deprecated_path(ui: &Ui, path: &Path, old: &str, new: &str) {
-+        let _ = writeln!(
++        let _ = indoc::writedoc!(
 +            ui.warning_default(),
-+            "Deprecated configuration file `{}`:",
-+            path.display()
-+        );
-+        let _ = writeln!(
-+            ui.warning_default(),
-+            "Configuration files in `{old}` are deprecated, and support will be removed in a \
-+             future release.",
-+        );
-+        let _ = writeln!(
-+            ui.warning_default(),
-+            "Instead, move your configuration files to `{new}`.",
++            r"
++            Deprecated configuration file `{}`.
++            Configuration files in `{old}` are deprecated, and support will be removed in a future release.
++            Instead, move your configuration files to `{new}`.
++            ",
++            path.display(),
 +        );
 +    }
  }

strega-nil added 2 commits May 1, 2025 17:17
Support existing users with the "legacy" config directory, as well.
This will be deprecated in a latter commit.
@strega-nil strega-nil force-pushed the strega-nil/etcetera branch from 0847672 to 0f89f2e Compare May 1, 2025 15:17
@strega-nil strega-nil added this pull request to the merge queue May 2, 2025
Merged via the queue into jj-vcs:main with commit b568bb6 May 2, 2025
28 checks passed
@strega-nil strega-nil deleted the strega-nil/etcetera branch May 2, 2025 20:16
bnjmnt4n added a commit to bnjmnt4n/home-manager that referenced this pull request May 7, 2025
Following jj-vcs/jj#6300, Jujutsu has deprecated
support for configuration files in `~/Library/Application Support` for
darwin. The XDG-standard configuration location can be used instead, for
all platforms.
bnjmnt4n added a commit to bnjmnt4n/home-manager that referenced this pull request May 8, 2025
Following jj-vcs/jj#6300, Jujutsu has deprecated
support for configuration files in `~/Library/Application Support` for
darwin. The XDG-standard configuration location can be used instead, for
all platforms.
bnjmnt4n added a commit to bnjmnt4n/home-manager that referenced this pull request May 8, 2025
Following jj-vcs/jj#6300, Jujutsu has deprecated
support for configuration files in `~/Library/Application Support` for
darwin. The XDG-standard configuration location can be used instead, for
all platforms.
bnjmnt4n added a commit to bnjmnt4n/home-manager that referenced this pull request May 8, 2025
Following jj-vcs/jj#6300, Jujutsu has deprecated
support for configuration files in `~/Library/Application Support` for
darwin. The XDG-standard configuration location can be used instead, for
all platforms.
bnjmnt4n added a commit to bnjmnt4n/home-manager that referenced this pull request May 9, 2025
Following jj-vcs/jj#6300, Jujutsu has deprecated
support for configuration files in `~/Library/Application Support` for
darwin. The XDG-standard configuration location can be used instead, for
all platforms.
khaneliman pushed a commit to nix-community/home-manager that referenced this pull request May 9, 2025
…6994)

Following jj-vcs/jj#6300, Jujutsu has deprecated
support for configuration files in `~/Library/Application Support` for
darwin. The XDG-standard configuration location can be used instead, for
all platforms.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

FR: look in $XDG_CONFIG_HOME for jj config on OSX
8 participants