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,90 @@ Form customization also provides a powerful tool for making many policy management tasks easier (see @{article:User Guide: Customizing Forms}). + +Archiving +========= + +Like most things in real life, sometimes project tags or milestones +stop being useful, perhaps because they reached their deadline, +perhaps because their investor was a scam, perhaps because your +boss created a nonsense project by mistake with a faulty mouse, etc. + +In general, we recommend archiving projects instead of destroying them. +Firstly, Phorge discourages killing. Secondly, keeping track of which +colleagues worked on legacy projects is generally useful. Thirdly, +the alternative is destroying projects (from the command line), but +destroying things is generally very scary, very unsafe, and not +recommended. In the other hand, archiving is generally 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=pencil,name=Edit} a project can also +use the {nav icon=ban,name=Archive} action. + +Archived projects will generally avoid to distract you, but they will preserve +their past glory. For example: + +- archived projects are unlisted from the active list at /project/query/active/ +- archived projects are de-prioritized from most search selections, + including the top search bar, including tag pickers, etc. +- archived projects are kept where they were placed, but are + generously de-colorized from their tag badges, and struck-through +- archived projects are muted, and do not cause "watch" notifications + +All these consequences are reversible. You can bring a project back +to life anytime using the {nav icon=check,name=Activate project} action. + +If somebody ask you to delete a project and make it go away, you can still +just archive the project, and also change its project visibility. +Valid examples are "show only to me", or "show only to these coworkers", +etc. This is also a safe and easily reversible operation. + +If at this point we still haven't convinced you to archive your projects, +enjoy the next scary section. + +Permanently Destroying +======================== + +Phorge is designed as a safe collaborative platform that rarely +requires permanently destroying things from the web interface. + +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 (or 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" will immediately become + completely invisible from the web interface and API results +- tagged items are generally preserved, including tasks, commits, + wiki documents, 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 of the destroyed project are preserved but + they are unassigned from that project +- watchers of the destroyed project are preserved but they may need to find + something else to look at +- if the project has a workboard, that workboard is destroyed as well + (tasks in that workboard will be kept and will remain associated + with other workboards, of course) +- 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 can be edited directly. +- if the project has sub-projects, all sub-projects and all their descendant + sub-projects will climb the tree depth by one level, to still have sense + and remain preserved. 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 a rebel fleet + of Slugma Pokemons out of recursion control. + +So, please consider just simply archiving projects instead of destroying them.