Skip to content

fix(home): don't shorten sibling dirs that share the home dir prefix - #532

Merged
joshmedeski merged 3 commits into
joshmedeski:mainfrom
ChandraChaan:fix/shorten-home-sibling-prefix
Nov 15, 2026
Merged

joshmedeski merged 3 commits into
joshmedeski:mainfrom
ChandraChaan:fix/shorten-home-sibling-prefix

Conversation

@ChandraChaan

Copy link
Copy Markdown
Contributor

ShortenHome replaces the home directory with ~ using only strings.HasPrefix(path, home), so it also rewrites sibling directories whose name merely starts with the home path. With home /home/user, ShortenHome("/home/username/projects") returns ~name/projects instead of leaving the path alone. The result is used for the session names of zoxide directories (lister/zoxide.go) and as the input to name_substitution rules (namer/substitute.go), so a directory like /home/username/proj shows up as ~name/proj.

This only shortens when the home directory ends at a path boundary: the rest of the path is empty or starts with / (or the OS path separator). Paths under the home directory and the home directory itself behave as before.

I added two cases to TestShortenHome: the home directory itself becomes ~, and /home/username/projects stays unchanged.

I did not run Go locally, so I checked it in CI on my fork:

@purplezimmermann

Copy link
Copy Markdown
Contributor

I had wondered before if there is no simpler, more idiomatic way for home path substitution than using string operations... Now that I saw your PR, @ChandraChaan, I revisited this topic and came up with:

func (p *RealHome) ShortenHome(path string) (string, error) {
	home, err := p.os.UserHomeDir()
	if err != nil {
		return "", err
	}
	if rel, err := filepath.Rel(home, path); err == nil {
		// IsLocal() only accepts non-absolute paths not starting with ../
		if filepath.IsLocal(rel) {
			return filepath.Join("~", rel), nil
		}
	}
	return path, nil
}

It implicitly solves your issue as well... If you want to use it, there is one little test adaptation necessary:

@@ -97,7 +98,7 @@ func TestShortenHome(t *testing.T) {

                got, err := NewHome(mockOs).ShortenHome("/home/user/projects")
                assert.NoError(t, err)
-           assert.Equal(t, "~/projects", got)
+         assert.Equal(t, filepath.FromSlash("~/projects"), got)
        })

        t.Run("shortens the home dir itself to a tilde", func(t *testing.T) {

@ChandraChaan

Copy link
Copy Markdown
Contributor Author

Thanks @purplezimmermann, that's a neat approach, and filepath.Rel also handles the Windows case where the separators or drive-letter casing differ from UserHomeDir(), which the plain prefix check doesn't.

I kept the PR as the smallest change that fixes the sibling-prefix bug, and haven't run your version in CI. Two behavior differences I noticed with Rel + Join:

  • Rel cleans the path, so a trailing slash (/home/user/projects/) or . segments are dropped, where the current code keeps them as given.
  • The result is built with filepath.Join, so on Windows it uses \ even if the input used /. That is why the test needs filepath.FromSlash.

Neither looks harmful to me, but it's @joshmedeski's call. If you prefer the Rel version I'm happy to switch the PR to it (with your test adaptation) and run it on my fork's CI before pushing; otherwise I'll leave it as is.

@joshmedeski joshmedeski left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks @ChandraChaan, the fix is solid, and thanks for running the before/after CI on your fork. @purplezimmermann, thanks for the filepath.Rel suggestion.

I'd like to go with the Rel version. On Windows, Rel compares paths case-insensitively and accepts both / and \. That means a path like c:/users/me/proj gets shortened, which the HasPrefix check misses. It also fixes the sibling-prefix bug without any boundary logic of our own:

func (p *RealHome) ShortenHome(path string) (string, error) {
	home, err := p.os.UserHomeDir()
	if err != nil {
		return "", err
	}
	if rel, err := filepath.Rel(home, path); err == nil && filepath.IsLocal(rel) {
		return filepath.Join("~", rel), nil
	}
	return path, nil
}

You already covered the behaviour differences, and I'm fine with both:

  • Cleaned paths: trailing slashes and . segments get dropped. That's fine for session names.
  • \ on Windows: name_substitution runs ToSlash before applying rules, so user rules written with / still match.

To finish it:

  1. Update the existing test to expect filepath.FromSlash("~/projects"), as @purplezimmermann showed. Keep both of your new tests: the home dir becomes ~ and the sibling dir stays unchanged. They should pass as written.
  2. Leave out code comments, both the one above the current boundary check and the // IsLocal() ... one in the suggestion. I keep the reasoning for a change in the commit message and PR description rather than in the file, and your PR body already covers it well.

Happy to merge when CI passes.

Switch ShortenHome from a prefix check with a boundary test to
filepath.Rel plus IsLocal. Paths outside the home dir, including
siblings that share its prefix such as /home/username, stay unchanged.
On Windows Rel compares case-insensitively and accepts both separators.
The result is built with filepath.Join, so it uses the OS separator;
the existing test now expects filepath.FromSlash("~/projects").
@ChandraChaan

Copy link
Copy Markdown
Contributor Author

Thanks @joshmedeski. Done in bcb72bf: ShortenHome now uses filepath.Rel with filepath.IsLocal, the existing test expects filepath.FromSlash("~/projects"), the two new tests are unchanged, and the code comments are gone.

I ran it on my fork before pushing, and Lint, Build, Benchmarks and Tests on ubuntu, macOS and Windows all passed: https://github.com/ChandraChaan/sesh/actions/runs/42426858629

@ChandraChaan

Copy link
Copy Markdown
Contributor Author

Hi @joshmedeski, a gentle ping: the filepath.Rel version you asked for is in bcb72bf and all checks pass. Could you take another look when you have time? GitHub won't let me re-request your review from my side. Thanks!

@joshmedeski
joshmedeski merged commit f41887c into joshmedeski:main Nov 15, 2026
7 checks passed
@joshmedeski

Copy link
Copy Markdown
Owner

Thanks for the ping, nice job on this!

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.

3 participants