Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions doc/modules/ROOT/pages/across_repositories.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,10 @@ It works on the *normalized* remote, so the spelling doesn't matter:

=== The leading word of the directory name

The signal of last resort, and the only one available for a repository
with no remote at all. `rubocop`, `rubocop-ast` and `rubocop-rails` are
related because they start with the same word.
The signal of last resort, and the only one that works with no version
control at all - a plain directory with a `.projectile` in it still gets
siblings. `rubocop`, `rubocop-ast` and `rubocop-rails` are related
because they start with the same word.

Only the *leading* word counts. Matching on any shared word relates every
`-mode` package to every other one and every `docs.` site to the rest,
Expand Down Expand Up @@ -190,11 +191,19 @@ Worth knowing before you file a bug:
to group on.
* *Only a repository's own top level has an identity.* A project defined
by a `.projectile` file inside a larger repository is a directory in a
checkout, not a checkout of its own, so it has no worktrees. Projectile
cannot yet map such a project to the matching subdirectory of another
worktree.
checkout, not a checkout of its own, so it has no worktrees - the
enclosing repository's worktrees are not other copies of it, and aren't
offered as though they were. Projectile cannot yet map such a project to
the matching subdirectory of another worktree.
* *Remote (TRAMP) projects are skipped.* Every probe would be a network
round trip, so a remote project has no identity and no worktrees.

When kbd:[s-p W] has nothing to offer it distinguishes the two reasons:
having looked and found no other checkout, and not being able to look at
all because the project isn't the top level of a git or Mercurial
repository. Only the first means "there aren't any". kbd:[s-p n] needs
none of this, so it's the one to reach for when Projectile can't identify
the repository.
* *Sibling clones must already be known projects.* Real git worktrees are
found without that, since git registers them itself.

Expand Down
26 changes: 23 additions & 3 deletions projectile.el
Original file line number Diff line number Diff line change
Expand Up @@ -13890,6 +13890,14 @@ Git registers them itself, so this finds worktrees that have never been
visited in this Emacs session - which the known projects can't do."
(when-let* (((eq (projectile-project-vcs root) 'git))
((not (file-remote-p root)))
;; Only a checkout's own top level has worktrees. ROOT can
;; just as well be a directory *inside* one - a project marked
;; out by its own `.projectile' in a corner of a bigger
;; repository, say, which `projectile-project-vcs' still calls
;; git because it walks up to find the repository. Git would
;; happily list the enclosing repository's worktrees, but none
;; of them is another copy of *this* project.
((projectile--git-dir root))
(output (projectile--git root "worktree" "list" "--porcelain")))
(projectile--parse-git-worktree-list output)))

Expand Down Expand Up @@ -13978,8 +13986,17 @@ switch. With a prefix ARG invokes `projectile-dispatch' instead."
worktree))
worktrees)))
(unless worktrees
(user-error "No other checkout of %s found"
(projectile-project-name root)))
;; Say which of the two it is: nothing to switch to, or nothing
;; Projectile is able to look at. They call for different responses
;; and the same message for both sends people hunting for a bug.
(cond
((file-remote-p root)
(user-error "Projectile doesn't look for the checkouts of a remote project"))
((projectile-repo-identity root)
(user-error "No other checkout of %s found" (projectile-project-name root)))
(t
(user-error "Cannot tell what %s is a checkout of - only git and Mercurial say. Try `projectile-switch-sibling-project'"
(projectile-project-name root)))))
(projectile-completing-read
"Switch to worktree: " (mapcar #'car by-path)
:action (lambda (path)
Expand Down Expand Up @@ -14220,7 +14237,10 @@ switch. With a prefix ARG invokes `projectile-dispatch' instead."
(not (file-directory-p project))))
(projectile-sibling-projects root))))
(unless siblings
(user-error "No projects related to %s found"
;; Nothing inferred is a perfectly ordinary outcome - it's what the
;; share cap does when a signal relates too much - so point at the
;; setting that always works rather than just reporting the miss.
(user-error "No projects related to %s found - see `projectile-project-groups'"
(projectile-project-name root)))
(projectile-completing-read
"Switch to sibling project: " siblings
Expand Down
21 changes: 20 additions & 1 deletion test/projectile-sibling-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,26 @@
(it "errors when nothing is related to the current project"
(let ((projectile-sibling-project-functions nil))
(spy-on 'projectile-acquire-root :and-return-value "/src/lonely/")
(expect (projectile-switch-sibling-project) :to-throw 'user-error)))
(spy-on 'projectile-project-name :and-return-value "lonely")
;; Finding nothing is an ordinary outcome - it's what the share cap
;; does - so the message points at the setting that always works.
(expect (condition-case err (projectile-switch-sibling-project)
(user-error (error-message-string err)))
:to-match "projectile-project-groups")))

(it "still relates projects when there's no version control at all"
;; The name signal needs no repository, so a project Projectile can say
;; nothing else about still gets siblings.
(projectile-test-with-sandbox
(projectile-test-with-files
("aaa/" "aaa/.projectile" "aaa-two/" "aaa-two/.projectile")
(let* ((root (file-name-as-directory (expand-file-name "aaa")))
(projectile-known-projects
(list root (file-name-as-directory (expand-file-name "aaa-two")))))
(spy-on 'projectile-repo-identity)
(expect (projectile-sibling-test--names
(projectile-sibling-projects root))
:to-have-same-items-as '("aaa" "aaa-two"))))))

(it "does not offer the project we're already in"
(projectile-test-with-sandbox
Expand Down
34 changes: 33 additions & 1 deletion test/projectile-worktree-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,22 @@

(it "returns nothing for a project that isn't under git"
(spy-on 'projectile-project-vcs :and-return-value 'hg)
(expect (projectile-worktrees-from-git "/src/repo/") :to-be nil)))
(expect (projectile-worktrees-from-git "/src/repo/") :to-be nil))

(it "returns nothing for a project sitting below a repository's root"
;; `projectile-project-vcs' answers `git' for a project inside a
;; checkout, because it walks up to find the repository - but the
;; enclosing repository's worktrees are not other copies of that
;; project, and offering them would switch somewhere unrelated.
(projectile-test-with-sandbox
(let ((repo (projectile-test-init-git-repo "repo")))
(projectile-test-add-git-worktree
repo (expand-file-name "feature") "feature")
(let ((sub (file-name-as-directory (expand-file-name "sub" repo))))
(make-directory sub t)
(expect (projectile-project-vcs sub) :to-equal 'git)
(expect (projectile-worktrees-from-git sub) :to-be nil)
(expect (projectile-project-worktrees sub) :to-be nil))))))

(describe "projectile-worktrees-from-known-projects"
(it "finds another clone of the same upstream"
Expand Down Expand Up @@ -202,6 +217,23 @@
(spy-on 'projectile-acquire-root :and-return-value repo)
(expect (projectile-switch-worktree) :to-throw 'user-error))))

(it "says so when it can't tell what the project is a checkout of"
;; Distinct from having looked and found nothing: this project's version
;; control system can't answer the question at all, which calls for a
;; different response from the user.
(spy-on 'projectile-acquire-root :and-return-value "/src/plain/")
(spy-on 'projectile-project-vcs :and-return-value 'svn)
(spy-on 'projectile-project-name :and-return-value "plain")
(expect (condition-case err (projectile-switch-worktree)
(user-error (error-message-string err)))
:to-match "Cannot tell what plain is a checkout of"))

(it "says so for a remote project rather than reaching over TRAMP"
(spy-on 'projectile-acquire-root :and-return-value "/ssh:host:/src/repo/")
(expect (condition-case err (projectile-switch-worktree)
(user-error (error-message-string err)))
:to-match "remote project"))

(it "switches to the chosen worktree"
(projectile-test-with-sandbox
(let ((repo (projectile-test-init-git-repo "repo")))
Expand Down
Loading