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,86 @@ Form customization also provides a powerful tool for making many policy management tasks easier (see @{article:User Guide: Customizing Forms}). + +Archiviation +============ + +Like most things in real life, sometime also a project tag or a milestones +can become not useful anymore, maybe because they reached their deadline, +maybe because their investor was actually a scam, maybe because your +boss created a nonsense project by mistake with their faulty mouse, etc. + +In general, in most cases, we strongly recommend to archive projects instead +of destroying them. First, because Phorge does not encourage killing life forms. +Second, because keeping track of which colleagues worked on legacy projects +is generally useful. Third, because the alternative is destroying projects +from the command line, but destroying things is generally very scary, +very unsafe, not recommended. In the other hand, archiviation is in +general very stable, well-tested, safe, encouraged, reversible, and full of +satisfaction. Some Phorge instances also start smelling of peach fragrances, +after you archive your first project. + +People who can {nav icon=fa-pencil,name=Edit} a project can also +use the {nav icon=fa-ban,name=Archive} action. + +Archived projects will generally avoid to distract you, but they will preserve +their past glory. For example: + +- archived projects are de-prioritized from most search selections, + including the top search bar, including tag pickers, etc. +- archived projects are unlisted from the active list at /project/query/active/ +- archived projects are generally kept where they had been placed, but are + generously de-colorized from their tag badges, or stroked +- archived projects are muted, do not cause extra "watch" notifications + +All the following consequences are reversible. You can turn a project back +to life anytime using the {nav icon=fa-check Activate project} action. + +As usual, to hide a project from the eyes of your delicate coworkers, you can +also evaluate changes in its visibility options, to show it only to you, +or only to a coworker that still have love for archived materials. + +If at this point we still haven't convinced you to archive your projects, +enjoy the next scary section. + +Permanently Destroy +=================== + +Phorge is designed as a safe collaborative platform that rarely +allows to permanently destroy things from the web interface. But... +there are still uncommon circumstances were you may want to afford +all the risks in the wild and not recommended world of +@{article:Permanently Destroying Data}. + +What happens when you permanently destroy a project (including a milestone) +using the command line: + +- the project is destroyed permanently, forever (unless you have a good backup) +- all objects (such as tasks, repositories, etc.) to which you set visibility + restrictions like "visible to: members of THAT TAG" may immediately become + completely invisible from the web interface and API results +- tagged stuff is generally preserved, including tasks, commits, + wiki documents, events, repositories, etc. and these objects will simply + be not associated anymore with that project tag +- users that are members of the destroyed project are preserved but + they are unassigned from that project +- whatchers of the destroyed project will have to find something else to look at +- if the project has a workboard, that workboard is destroyed as well + (their tasks will be kept in general - just without that association) +- if the project has direct milestones, these milestones are destroyed as well + (note that milestones are technically projects, so, read aloud this list + again to understand what will happen to these milestones, and to stuff + associated to these milestones, etc.) +- if the project had a parent project, and if that parent will not have other + children projects, that parent project may be promoted to root-project again. + This means you will probably be able to directly edit the members of the + parent project again. +- if the project has sub-projects, all sub-projects and all their descendent + sub-projects will climb the tree depth by one level, to still have sense + and still be preserved. Grandchildren become children. Sons become parents, + etc. - a real mess for family photos. +- you increase the risk of something totally unexpected happening, like, your + entire datacenter may be destroyed by a rebel fleet of Slugma Pokemons + out of recursion control. + +So, please consider just simply archiving projects instead of destroying them.