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,126 @@ 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, on tagged objects is not +enforced any special read-only policy. This has sense if you have paid +attention to "Policies In Depth". In short, an object can have so many tags, +and if a specific team group closed its operations does not mean that others +should stop working on all their tagged stuff, etc. + +After you presented "how amazing is Phorge about archiving projects" and +after you distributed stickers about `#JustArchive` to all coworkers, +it's a classic that somebody still want to "just remove the project" or +"make it go away" or "run obliviate", etc. +In these cases where "more censorship" is needed, you can evaluate an +additional change in the visibility settings of that project. For example, +the very limited visibility "show only to me" makes the project effectively +invisible to others. Mastering the visibility policies helps a lot in making +sure your cleanup requests are managed professionally and in a secure way, +still allowing future auditing, when needed. + +At this point, if you still haven't convinced everyone to archive a specific +project, explore 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 recursion control. + +To permanently destroy a project, you will need to execute a command like this, +from the root directory of the Phorge repository on your server: + +``` +./bin/remove destroy PHID-PROJ-abcdef123456 +``` + +The command needs the "PHID" code of the project. +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 requires a manual confirmation. Before proceeding, +take the disclaimer seriously and read again the previous section about +archiving projects (safe), instead of permanently destroying them (unsafe), +to eventually change your mind.