diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -748,9 +748,75 @@ $slug->delete(); } + // Destroy my milestones because they cannot live without me. + // Do not use PhabricatorProjectQuery to avoid a circular dependency, + // and, to do not have a load silent fail, since these milestones do not + // have their parent anymore. + $milestones = id(new self()) + ->loadAllWhere('parentProjectPHID = %s AND milestoneNumber IS NOT NULL', + $this->getPHID()); + foreach ($milestones as $milestone) { + $milestone->attachParentProject($this); + $engine->destroyObject($milestone); + } + + // Update my children to eventually fix holes in the tree. + $this->onDestroyTouchChildren(true); + + // After the tree is fixed, update my parent hasSubProjects field. + if ($this->getParentProject()) { + id(new PhabricatorProjectsMembershipIndexEngineExtension()) + ->rematerialize($this->getParentProject()); + } + $this->saveTransaction(); } + /** + * On destroy, eventually bubble up my direct children, to take my place. + * Refresh all remaining children, to consolidate depth, path key, etc. + */ + private function onDestroyTouchChildren($close_my_hole) { + // Micro-optimization. + if (!$this->supportsSubprojects() && !$this->supportsMilestones()) { + return; + } + + // Get direct sub-projects and milestones and their new desired parent. + // We must skip my direct milestones since they are under removal. + // Do not use PhabricatorProjectQuery to avoid a circular dependency. + $query_children = new self(); + if ($close_my_hole) { + $desired_parent = $this->getParentProject(); + $children = $query_children->loadAllWhere( + 'parentProjectPHID = %s AND milestoneNumber IS NULL', + $this->getPHID()); + } else { + $desired_parent = $this; + $children = $query_children->loadAllWhere( + 'parentProjectPHID = %s', + $this->getPHID()); + } + + // The desired parent PHID for my children may become NULL, + // when we are closing my hole but my parent it's a root-project. + $desired_parent_phid = null; + if ($desired_parent) { + $desired_parent_phid = $desired_parent->getPHID(); + } + + // Eventually bubble up my direct children. Update the others. + foreach ($children as $child) { + $child->attachParentProject($desired_parent); + $child->setParentProjectPHID($desired_parent_phid); + $child->setProjectPathKey(null); // Force a new path key and depth. + $child->save(); + + // Descend the tree. + $child->onDestroyTouchChildren(false); + } + } + /* -( PhabricatorFulltextInterface )--------------------------------------- */ diff --git a/src/docs/user/userguide/projects.diviner b/src/docs/user/userguide/projects.diviner --- a/src/docs/user/userguide/projects.diviner +++ b/src/docs/user/userguide/projects.diviner @@ -337,3 +337,122 @@ Form customization also provides a powerful tool for making many policy management tasks easier (see @{article:User Guide: Customizing Forms}). + +Archiving +========= + +Phorge supports the destroy (unsafe) and the archive (safe) of projects. + +Archiving a project (or archiving a milestone - since milestones are projects) +is very recommended: you are encouraged in archiving a project when it stopped +being useful, when the project reached its deadline, or when its investor was +a scam. You might be surprised how useful it is to know which colleagues have +worked on a certain very old archived project, which fortunately somebody +decided to archive rather than violently destroy. +Archiving still has sense when your boss created a nonsense project about +how-to integrate Phorge with Doom, but now would like to hide all traces... +still, please desist from looking for destruction, and evaluate archiving it, +and not only for the glory of the internal documentation in your organization. + +The {nav icon=ban,name=Archive} action is visible to all people who +can {nav icon=pencil,name=Edit} a project. As usual in Phorge, +there is a confirmation dialog. + +After you confirm to archive a project, these things will happen: + +- in general, the archived project will avoid to distract people, + still preserving its past glory. +- whatever mentions the tag or its hashtag, the related badge is generously + de-colorized or struck-through. +- the archived project is unlisted from the active list at + [ /project/query/active/ ]( /project/query/active/ ) +- the archived project is de-prioritized from most search results and selectors, + including the top search bar, the tag pickers, etc. +- the archived project is muted, and do not cause "watch" notifications. +- who triggered this action is logged in the recent actions. + +All these consequences are reversible. You can bring a project back +to life anytime using the {nav icon=check,name=Activate project} action. + +After archiving a project, all tagged objects, tagged tasks, etc. +will be intentionally kept as-is. In particular, tagged objects are not +forced in read-only, etc. This has sense since an object can have so many tags, +but also because if a specific team group closed its operations +does not mean that others should stop working on all their tagged stuff, etc. + +After discussing how amazing is archiving to your coworkers or your boss, +they may still try to negatively convince you to "delete a project" or +"make it go away" or use extra force and violence. In case, keep calm +and additionally evaluate to "just archive", plus, you can additionally +reduce the visibility settings of that project: a very limited +visibility is "show only to me", making effectively invisible to others. +Mastering the visibility is generally all you may additionally need to keep +your Phorge clean, in the Phorge-way, that is, a safe way. + +If at this point we still haven't convinced you to archive your project, +enjoy the next scary and unsafe section about permanently destroying. + +Permanently Destroying +====================== + +Phorge is designed as a safe collaborative platform that rarely +requires @{article:Permanently Destroying Data}. + +If you have read that article, and if you have done a backup, and if +you have access the command line, and if you still want to permanently +destroy a project (or a milestone), these will be the consequences: + +- the project is destroyed permanently from the database, forever + (unless you have a good backup and sufficient recover skills) +- all objects, including tasks, repositories, wiki documents, calendars, + secrets, other projects, etc. to which you set visibility restrictions + involving that project (example: "visible to project members"), + these objects will be broken, and everyone will be locked out of them. + It means these objects will become completely invisible from the web + interface and API results. + You still have margin to recover from these particular policy problems, + reading the @{article:User Guide: Unlocking Objects}. +- tagged items are generally preserved, including tasks, commits, + wiki documents, calendar events, repositories, etc. and these objects + will simply not be associated anymore with that project tag + (but will remain associated with other tags, of course). +- users that are members or watchers of the destroyed project will be + kept in your Phorge but unassigned from that project. + Watchers might go crazy until they find something else to watch. +- comments and texts wrote by users will be preserved even if they were + mentioning your `#project` but that hashtag will not render a link. + You will still be able to add that hashtag in another project, + to revive these links. +- if the project has a workboard, that workboard is destroyed as well + (tasks in that workboard will always be kept and will remain associated + with other workboards, in case). +- if the project has direct milestones, these milestones are destroyed as well + (note that milestones are technically projects, so, read this list + again aloud to understand what will happen to these milestones, and to items + associated to these milestones, etc.) +- if the project has a parent project, and if that parent has no other child + projects anymore, that parent can be promoted to root-project again. + This means the members of the parent project will be editable again. +- if the project has sub-projects, all sub-projects and all their descendant + sub-projects will climb the tree depth by one level, to fill the hole + you caused. Grandchildren become children, sons become parents, + etc. - a real mess for family photos. +- you increase the risk of something completely unexpected happening, + such as the destruction of your entire datacenter by our + Slugma Pokemons out of our recursion control. + +If you are sure about permanently destroying a project from command line, +here an example: + +``` +./bin/remove/destroy PHID-PROJ-abcdef123456 +``` + +As you see you need the "PHID" code of the project you want to destroy. +Every project has a PHID and it can be easily retrieved in multiple ways, +including the {nav icon=cog,name=Manage} menu of that project, hovering +the cursor on the {nav icon=flag,name=Flag For Later} feature. + +This command line process requires a manual confirmation. Before proceeding, +take your time to read again the previous section about archiving projects +(safe), instead of permanently destroying them (unsafe).