Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

Commit

A work item's origin in another workspace gives only reading it

A repository transferred away keeps the work items filed in it in their own workspace, but its roles became the new workspace's: Triage there let that workspace's people close and change them. Reading still follows the repository, as before; anything more through the origin needs it in the work item's workspace.

G1T-Session:
ses_01m56vxcstesp8b57jt5edge6y
syntaqxcommitted Parent068969aBrowse files
1 file+34−30/1 viewed
+34−3
483483 /// work items, anyone who can read a repository sees those filed there
484484 /// (on a public one, everyone), and one with no origin is for members
485485 /// only. `None` means they may not see it at all.
486+///
487+/// A work item stays in its workspace when its origin moves to another:
488+/// there, reading it still follows the repository (public stays public;
489+/// a private one's readers still read what was filed in it), but nothing
490+/// more comes through the origin, whose roles are now another
491+/// workspace's.
486492 pub fn item_role(viewer: Option<&User>, workspace: &str, origin: Option<RepoRef<'_>>) -> Option<RepoRole> {
487493 let member = viewer.and_then(|user| workspace_role(user, workspace));
488− let repo = origin.and_then(|origin| permission(viewer, origin));
494+ let repo = origin.and_then(|origin| {
495+ let role = permission(viewer, origin)?;
496+ Some(if origin_is_in(origin, workspace) { role } else { role.min(RepoRole::Read) })
497+ });
489498 member.max(repo)
490499 }
491500
501+/// Whether a work item's origin is still in the work item's workspace.
502+fn origin_is_in(origin: RepoRef<'_>, workspace: &str) -> bool {
503+ origin.namespace.eq_ignore_ascii_case(workspace)
504+}
505+
492506 /// Whether the viewer may do `capability` on a work item, by the same
493507 /// table as [`can`]: through their membership of the workspace, or their
494−/// role on its origin as [`can`] reads it.
508+/// role on its origin as [`can`] reads it (only Read, once the origin is
509+/// in another workspace: see [`item_role`]).
495510 pub fn can_item(viewer: Option<&User>, workspace: &str, origin: Option<RepoRef<'_>>, capability: Capability) -> bool {
496511 let member = viewer.and_then(|user| workspace_role(user, workspace));
497512 if member.is_some_and(|role| allows(role, capability)) {
498513 return true;
499514 }
500− origin.is_some_and(|origin| can(viewer, origin, capability))
515+ origin.is_some_and(|origin| (capability == Capability::Read || origin_is_in(origin, workspace)) && can(viewer, origin, capability))
501516 }
502517
503518 /// `Ok` when the viewer may do `capability` on a work item; otherwise
882897 }
883898
884899 #[test]
900+ fn an_origin_moved_to_another_workspace_still_reads_but_gives_nothing_more() {
901+ // rep_1 moved from acme to globex; its work items stayed in acme.
902+ let moved = |private| Some(RepoRef { id: "rep_1", namespace: "globex", private });
903+ let globex_owner = person(&[("globex", Role::Owner, None)], &[]);
904+ assert_eq!(item_role(Some(&globex_owner), "acme", moved(true)), Some(RepoRole::Read));
905+ assert!(can_item(Some(&globex_owner), "acme", moved(true), Capability::Read));
906+ assert!(!can_item(Some(&globex_owner), "acme", moved(true), Capability::Triage));
907+ assert!(!can_item(Some(&globex_owner), "acme", moved(false), Capability::Participate));
908+ // Public stays public.
909+ assert_eq!(item_role(None, "acme", moved(false)), Some(RepoRole::Read));
910+ // acme's members keep what their membership gives.
911+ let acme_writer = person(&[("acme", Role::Member, Some(BasePermission::Write))], &[]);
912+ assert!(can_item(Some(&acme_writer), "acme", moved(true), Capability::Triage));
913+ }
914+
915+ #[test]
885916 fn a_narrowed_token_reaches_only_what_it_was_made_for() {
886917 let mut member = person(&[("acme", Role::Member, Some(BasePermission::Write))], &[]);
887918 member.token = Some(Box::new(TokenAccess {

Comments

Sign in to comment.