Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php
index 543c574d58..940cd35b92 100644
--- a/src/applications/differential/controller/DifferentialRevisionViewController.php
+++ b/src/applications/differential/controller/DifferentialRevisionViewController.php
@@ -1,911 +1,937 @@
<?php
final class DifferentialRevisionViewController extends DifferentialController {
private $revisionID;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->revisionID = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$viewer_is_anonymous = !$user->isLoggedIn();
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($this->revisionID))
->setViewer($request->getUser())
->needRelationships(true)
->needReviewerStatus(true)
->needReviewerAuthority(true)
->executeOne();
if (!$revision) {
return new Aphront404Response();
}
$diffs = id(new DifferentialDiffQuery())
->setViewer($request->getUser())
->withRevisionIDs(array($this->revisionID))
->needArcanistProjects(true)
->execute();
$diffs = array_reverse($diffs, $preserve_keys = true);
if (!$diffs) {
throw new Exception(
'This revision has no diffs. Something has gone quite wrong.');
}
$revision->attachActiveDiff(last($diffs));
$diff_vs = $request->getInt('vs');
$target_id = $request->getInt('id');
$target = idx($diffs, $target_id, end($diffs));
$target_manual = $target;
if (!$target_id) {
foreach ($diffs as $diff) {
if ($diff->getCreationMethod() != 'commit') {
$target_manual = $diff;
}
}
}
if (empty($diffs[$diff_vs])) {
$diff_vs = null;
}
$repository = null;
$repository_phid = $target->getRepositoryPHID();
if ($repository_phid) {
if ($repository_phid == $revision->getRepositoryPHID()) {
$repository = $revision->getRepository();
} else {
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($user)
->withPHIDs(array($repository_phid))
->executeOne();
}
}
list($changesets, $vs_map, $vs_changesets, $rendering_references) =
$this->loadChangesetsAndVsMap(
$target,
idx($diffs, $diff_vs),
$repository);
if ($request->getExists('download')) {
return $this->buildRawDiffResponse(
$revision,
$changesets,
$vs_changesets,
$vs_map,
$repository);
}
$props = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID = %d',
$target_manual->getID());
$props = mpull($props, 'getData', 'getName');
$all_changesets = $changesets;
$inlines = $revision->loadInlineComments($all_changesets);
$object_phids = array_merge(
$revision->getReviewers(),
$revision->getCCPHIDs(),
$revision->loadCommitPHIDs(),
array(
$revision->getAuthorPHID(),
$user->getPHID(),
));
foreach ($revision->getAttached() as $type => $phids) {
foreach ($phids as $phid => $info) {
$object_phids[] = $phid;
}
}
$field_list = PhabricatorCustomField::getObjectFields(
$revision,
PhabricatorCustomField::ROLE_VIEW);
$field_list->setViewer($user);
$field_list->readFieldsFromStorage($revision);
$warning_handle_map = array();
foreach ($field_list->getFields() as $key => $field) {
$req = $field->getRequiredHandlePHIDsForRevisionHeaderWarnings();
foreach ($req as $phid) {
$warning_handle_map[$key][] = $phid;
$object_phids[] = $phid;
}
}
$handles = $this->loadViewerHandles($object_phids);
$request_uri = $request->getRequestURI();
$limit = 100;
$large = $request->getStr('large');
if (count($changesets) > $limit && !$large) {
$count = count($changesets);
$warning = new PHUIInfoView();
$warning->setTitle('Very Large Diff');
$warning->setSeverity(PHUIInfoView::SEVERITY_WARNING);
$warning->appendChild(hsprintf(
'%s <strong>%s</strong>',
pht(
'This diff is very large and affects %s files. '.
'You may load each file individually or ',
new PhutilNumber($count)),
phutil_tag(
'a',
array(
'class' => 'button grey',
'href' => $request_uri
->alter('large', 'true')
->setFragment('toc'),
),
pht('Show All Files Inline'))));
$warning = $warning->render();
$my_inlines = id(new DifferentialInlineCommentQuery())
->withDraftComments($user->getPHID(), $this->revisionID)
->execute();
$visible_changesets = array();
foreach ($inlines + $my_inlines as $inline) {
$changeset_id = $inline->getChangesetID();
if (isset($changesets[$changeset_id])) {
$visible_changesets[$changeset_id] = $changesets[$changeset_id];
}
}
if (!empty($props['arc:lint'])) {
$changeset_paths = mpull($changesets, null, 'getFilename');
foreach ($props['arc:lint'] as $lint) {
$changeset = idx($changeset_paths, $lint['path']);
if ($changeset) {
$visible_changesets[$changeset->getID()] = $changeset;
}
}
}
} else {
$warning = null;
$visible_changesets = $changesets;
}
// TODO: This should be in a DiffQuery or similar.
$need_props = array();
foreach ($field_list->getFields() as $field) {
foreach ($field->getRequiredDiffPropertiesForRevisionView() as $prop) {
$need_props[$prop] = $prop;
}
}
if ($need_props) {
$prop_diff = $revision->getActiveDiff();
$load_props = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID = %d AND name IN (%Ls)',
$prop_diff->getID(),
$need_props);
$load_props = mpull($load_props, 'getData', 'getName');
foreach ($need_props as $need) {
$prop_diff->attachProperty($need, idx($load_props, $need));
}
}
$commit_hashes = mpull($diffs, 'getSourceControlBaseRevision');
$local_commits = idx($props, 'local:commits', array());
foreach ($local_commits as $local_commit) {
$commit_hashes[] = idx($local_commit, 'tree');
$commit_hashes[] = idx($local_commit, 'local');
}
$commit_hashes = array_unique(array_filter($commit_hashes));
if ($commit_hashes) {
$commits_for_links = id(new DiffusionCommitQuery())
->setViewer($user)
->withIdentifiers($commit_hashes)
->execute();
$commits_for_links = mpull(
$commits_for_links,
null,
'getCommitIdentifier');
} else {
$commits_for_links = array();
}
$revision_detail = id(new DifferentialRevisionDetailView())
->setUser($user)
->setRevision($revision)
->setDiff(end($diffs))
->setCustomFields($field_list)
->setURI($request->getRequestURI());
$actions = $this->getRevisionActions($revision);
$whitespace = $request->getStr(
'whitespace',
DifferentialChangesetParser::WHITESPACE_IGNORE_MOST);
$arc_project = $target->getArcanistProject();
if ($arc_project) {
list($symbol_indexes, $project_phids) = $this->buildSymbolIndexes(
$arc_project,
$visible_changesets);
} else {
$symbol_indexes = array();
$project_phids = null;
}
$revision_detail->setActions($actions);
$revision_detail->setUser($user);
$revision_detail_box = $revision_detail->render();
$revision_warnings = $this->buildRevisionWarnings(
$revision,
$field_list,
$warning_handle_map,
$handles);
if ($revision_warnings) {
$revision_warnings = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors($revision_warnings);
$revision_detail_box->setInfoView($revision_warnings);
}
$comment_view = $this->buildTransactions(
$revision,
$diff_vs ? $diffs[$diff_vs] : $target,
$target,
$all_changesets);
if (!$viewer_is_anonymous) {
$comment_view->setQuoteRef('D'.$revision->getID());
$comment_view->setQuoteTargetID('comment-content');
}
$wrap_id = celerity_generate_unique_node_id();
$comment_view = phutil_tag(
'div',
array(
'id' => $wrap_id,
),
$comment_view);
if ($arc_project) {
Javelin::initBehavior(
'repository-crossreference',
array(
'section' => $wrap_id,
'projects' => $project_phids,
));
}
$changeset_view = new DifferentialChangesetListView();
$changeset_view->setChangesets($changesets);
$changeset_view->setVisibleChangesets($visible_changesets);
if (!$viewer_is_anonymous) {
$changeset_view->setInlineCommentControllerURI(
'/differential/comment/inline/edit/'.$revision->getID().'/');
}
$changeset_view->setStandaloneURI('/differential/changeset/');
$changeset_view->setRawFileURIs(
'/differential/changeset/?view=old',
'/differential/changeset/?view=new');
$changeset_view->setUser($user);
$changeset_view->setDiff($target);
$changeset_view->setRenderingReferences($rendering_references);
$changeset_view->setVsMap($vs_map);
$changeset_view->setWhitespace($whitespace);
if ($repository) {
$changeset_view->setRepository($repository);
}
$changeset_view->setSymbolIndexes($symbol_indexes);
$changeset_view->setTitle('Diff '.$target->getID());
$diff_history = id(new DifferentialRevisionUpdateHistoryView())
->setUser($user)
->setDiffs($diffs)
->setSelectedVersusDiffID($diff_vs)
->setSelectedDiffID($target->getID())
->setSelectedWhitespace($whitespace)
->setCommitsForLinks($commits_for_links);
$local_view = id(new DifferentialLocalCommitsView())
->setUser($user)
->setLocalCommits(idx($props, 'local:commits'))
->setCommitsForLinks($commits_for_links);
if ($repository) {
$other_revisions = $this->loadOtherRevisions(
$changesets,
$target,
$repository);
} else {
$other_revisions = array();
}
$other_view = null;
if ($other_revisions) {
$other_view = $this->renderOtherRevisions($other_revisions);
}
$toc_view = new DifferentialDiffTableOfContentsView();
$toc_view->setChangesets($changesets);
$toc_view->setVisibleChangesets($visible_changesets);
$toc_view->setRenderingReferences($rendering_references);
$toc_view->setUnitTestData(idx($props, 'arc:unit', array()));
if ($repository) {
$toc_view->setRepository($repository);
}
$toc_view->setDiff($target);
$toc_view->setUser($user);
$toc_view->setRevisionID($revision->getID());
$toc_view->setWhitespace($whitespace);
$comment_form = null;
if (!$viewer_is_anonymous) {
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
'differential-comment-'.$revision->getID());
$reviewers = array();
$ccs = array();
if ($draft) {
$reviewers = idx($draft->getMetadata(), 'reviewers', array());
$ccs = idx($draft->getMetadata(), 'ccs', array());
if ($reviewers || $ccs) {
$handles = $this->loadViewerHandles(array_merge($reviewers, $ccs));
$reviewers = array_select_keys($handles, $reviewers);
$ccs = array_select_keys($handles, $ccs);
}
}
$comment_form = new DifferentialAddCommentView();
$comment_form->setRevision($revision);
$review_warnings = array();
foreach ($field_list->getFields() as $field) {
$review_warnings[] = $field->getWarningsForDetailView();
}
$review_warnings = array_mergev($review_warnings);
if ($review_warnings) {
$review_warnings_panel = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors($review_warnings);
$comment_form->setInfoView($review_warnings_panel);
}
$comment_form->setActions($this->getRevisionCommentActions($revision));
$action_uri = $this->getApplicationURI(
'comment/save/'.$revision->getID().'/');
$comment_form->setActionURI($action_uri);
$comment_form->setUser($user);
$comment_form->setDraft($draft);
$comment_form->setReviewers(mpull($reviewers, 'getFullName', 'getPHID'));
$comment_form->setCCs(mpull($ccs, 'getFullName', 'getPHID'));
// TODO: This just makes the "Z" key work. Generalize this and remove
// it at some point.
$comment_form = phutil_tag(
'div',
array(
'class' => 'differential-add-comment-panel',
),
$comment_form);
}
$pane_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'differential-keyboard-navigation',
array(
'haunt' => $pane_id,
));
Javelin::initBehavior('differential-user-select');
$page_pane = id(new DifferentialPrimaryPaneView())
->setID($pane_id)
- ->appendChild(array(
- $comment_view,
- $diff_history,
- $warning,
- $local_view,
- $toc_view,
- $other_view,
- $changeset_view,
- ));
- if ($comment_form) {
+ ->appendChild($comment_view);
+
+ $signatures = DifferentialRequiredSignaturesField::loadForRevision(
+ $revision);
+ $missing_signatures = false;
+ foreach ($signatures as $phid => $signed) {
+ if (!$signed) {
+ $missing_signatures = true;
+ }
+ }
+
+ if ($missing_signatures) {
+ $signature_message = id(new PHUIInfoView())
+ ->setErrors(
+ array(
+ array(
+ phutil_tag('strong', array(), pht('Content Hidden:')),
+ ' ',
+ pht(
+ 'The content of this revision is hidden until the author has '.
+ 'signed all of the required legal agreements.'),
+ ),
+ ));
+ $page_pane->appendChild($signature_message);
+ } else {
+ $page_pane->appendChild(
+ array(
+ $diff_history,
+ $warning,
+ $local_view,
+ $toc_view,
+ $other_view,
+ $changeset_view,
+ ));
+ }
+ if ($comment_form) {
$page_pane->appendChild($comment_form);
} else {
// TODO: For now, just use this to get "Login to Comment".
$page_pane->appendChild(
id(new PhabricatorApplicationTransactionCommentView())
->setUser($user)
->setRequestURI($request->getRequestURI()));
}
$object_id = 'D'.$revision->getID();
$content = array(
$revision_detail_box,
$page_pane,
);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($object_id, '/'.$object_id);
$prefs = $user->loadPreferences();
$pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
if ($prefs->getPreference($pref_filetree)) {
$collapsed = $prefs->getPreference(
PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED,
false);
$nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
->setTitle('D'.$revision->getID())
->setBaseURI(new PhutilURI('/D'.$revision->getID()))
->setCollapsed((bool)$collapsed)
->build($changesets);
$nav->appendChild($content);
$nav->setCrumbs($crumbs);
$content = $nav;
} else {
array_unshift($content, $crumbs);
}
return $this->buildApplicationPage(
$content,
array(
'title' => $object_id.' '.$revision->getTitle(),
'pageObjects' => array($revision->getPHID()),
));
}
private function getRevisionActions(DifferentialRevision $revision) {
$viewer = $this->getRequest()->getUser();
$revision_id = $revision->getID();
$revision_phid = $revision->getPHID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$revision,
PhabricatorPolicyCapability::CAN_EDIT);
$actions = array();
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setHref("/differential/revision/edit/{$revision_id}/")
->setName(pht('Edit Revision'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit);
$this->requireResource('phabricator-object-selector-css');
$this->requireResource('javelin-behavior-phabricator-object-selector');
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-link')
->setName(pht('Edit Dependencies'))
->setHref("/search/attach/{$revision_phid}/DREV/dependencies/")
->setWorkflow(true)
->setDisabled(!$can_edit);
$maniphest = 'PhabricatorManiphestApplication';
if (PhabricatorApplication::isClassInstalled($maniphest)) {
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-anchor')
->setName(pht('Edit Maniphest Tasks'))
->setHref("/search/attach/{$revision_phid}/TASK/")
->setWorkflow(true)
->setDisabled(!$can_edit);
}
$request_uri = $this->getRequest()->getRequestURI();
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-download')
->setName(pht('Download Raw Diff'))
->setHref($request_uri->alter('download', 'true'));
return $actions;
}
private function getRevisionCommentActions(DifferentialRevision $revision) {
$actions = array(
DifferentialAction::ACTION_COMMENT => true,
);
$viewer = $this->getRequest()->getUser();
$viewer_phid = $viewer->getPHID();
$viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID());
$viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
$status = $revision->getStatus();
$viewer_has_accepted = false;
$viewer_has_rejected = false;
$status_accepted = DifferentialReviewerStatus::STATUS_ACCEPTED;
$status_rejected = DifferentialReviewerStatus::STATUS_REJECTED;
foreach ($revision->getReviewerStatus() as $reviewer) {
if ($reviewer->getReviewerPHID() == $viewer_phid) {
if ($reviewer->getStatus() == $status_accepted) {
$viewer_has_accepted = true;
}
if ($reviewer->getStatus() == $status_rejected) {
$viewer_has_rejected = true;
}
break;
}
}
$allow_self_accept = PhabricatorEnv::getEnvConfig(
'differential.allow-self-accept');
$always_allow_abandon = PhabricatorEnv::getEnvConfig(
'differential.always-allow-abandon');
$always_allow_close = PhabricatorEnv::getEnvConfig(
'differential.always-allow-close');
$allow_reopen = PhabricatorEnv::getEnvConfig(
'differential.allow-reopen');
if ($viewer_is_owner) {
switch ($status) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept;
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED:
$actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept;
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_REQUEST] = true;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_REQUEST] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
$actions[DifferentialAction::ACTION_CLOSE] = true;
break;
case ArcanistDifferentialRevisionStatus::CLOSED:
break;
case ArcanistDifferentialRevisionStatus::ABANDONED:
$actions[DifferentialAction::ACTION_RECLAIM] = true;
break;
}
} else {
switch ($status) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon;
$actions[DifferentialAction::ACTION_ACCEPT] = true;
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED:
$actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon;
$actions[DifferentialAction::ACTION_ACCEPT] = true;
$actions[DifferentialAction::ACTION_REJECT] = !$viewer_has_rejected;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon;
$actions[DifferentialAction::ACTION_ACCEPT] = !$viewer_has_accepted;
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::CLOSED:
case ArcanistDifferentialRevisionStatus::ABANDONED:
break;
}
if ($status != ArcanistDifferentialRevisionStatus::CLOSED) {
$actions[DifferentialAction::ACTION_CLAIM] = true;
$actions[DifferentialAction::ACTION_CLOSE] = $always_allow_close;
}
}
$actions[DifferentialAction::ACTION_ADDREVIEWERS] = true;
$actions[DifferentialAction::ACTION_ADDCCS] = true;
$actions[DifferentialAction::ACTION_REOPEN] = $allow_reopen &&
($status == ArcanistDifferentialRevisionStatus::CLOSED);
$actions = array_keys(array_filter($actions));
$actions_dict = array();
foreach ($actions as $action) {
$actions_dict[$action] = DifferentialAction::getActionVerb($action);
}
return $actions_dict;
}
private function loadChangesetsAndVsMap(
DifferentialDiff $target,
DifferentialDiff $diff_vs = null,
PhabricatorRepository $repository = null) {
$load_diffs = array($target);
if ($diff_vs) {
$load_diffs[] = $diff_vs;
}
$raw_changesets = id(new DifferentialChangesetQuery())
->setViewer($this->getRequest()->getUser())
->withDiffs($load_diffs)
->execute();
$changeset_groups = mgroup($raw_changesets, 'getDiffID');
$changesets = idx($changeset_groups, $target->getID(), array());
$changesets = mpull($changesets, null, 'getID');
$refs = array();
$vs_map = array();
$vs_changesets = array();
if ($diff_vs) {
$vs_id = $diff_vs->getID();
$vs_changesets_path_map = array();
foreach (idx($changeset_groups, $vs_id, array()) as $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $diff_vs);
$vs_changesets_path_map[$path] = $changeset;
$vs_changesets[$changeset->getID()] = $changeset;
}
foreach ($changesets as $key => $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $target);
if (isset($vs_changesets_path_map[$path])) {
$vs_map[$changeset->getID()] =
$vs_changesets_path_map[$path]->getID();
$refs[$changeset->getID()] =
$changeset->getID().'/'.$vs_changesets_path_map[$path]->getID();
unset($vs_changesets_path_map[$path]);
} else {
$refs[$changeset->getID()] = $changeset->getID();
}
}
foreach ($vs_changesets_path_map as $path => $changeset) {
$changesets[$changeset->getID()] = $changeset;
$vs_map[$changeset->getID()] = -1;
$refs[$changeset->getID()] = $changeset->getID().'/-1';
}
} else {
foreach ($changesets as $changeset) {
$refs[$changeset->getID()] = $changeset->getID();
}
}
$changesets = msort($changesets, 'getSortKey');
return array($changesets, $vs_map, $vs_changesets, $refs);
}
private function buildSymbolIndexes(
PhabricatorRepositoryArcanistProject $arc_project,
array $visible_changesets) {
assert_instances_of($visible_changesets, 'DifferentialChangeset');
$engine = PhabricatorSyntaxHighlighter::newEngine();
$langs = $arc_project->getSymbolIndexLanguages();
if (!$langs) {
return array(array(), array());
}
$symbol_indexes = array();
$project_phids = array_merge(
array($arc_project->getPHID()),
nonempty($arc_project->getSymbolIndexProjects(), array()));
$indexed_langs = array_fill_keys($langs, true);
foreach ($visible_changesets as $key => $changeset) {
$lang = $engine->getLanguageFromFilename($changeset->getFilename());
if (isset($indexed_langs[$lang])) {
$symbol_indexes[$key] = array(
'lang' => $lang,
'projects' => $project_phids,
);
}
}
return array($symbol_indexes, $project_phids);
}
private function loadOtherRevisions(
array $changesets,
DifferentialDiff $target,
PhabricatorRepository $repository) {
assert_instances_of($changesets, 'DifferentialChangeset');
$paths = array();
foreach ($changesets as $changeset) {
$paths[] = $changeset->getAbsoluteRepositoryPath(
$repository,
$target);
}
if (!$paths) {
return array();
}
$path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs();
if (!$path_map) {
return array();
}
$query = id(new DifferentialRevisionQuery())
->setViewer($this->getRequest()->getUser())
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
->setOrder(DifferentialRevisionQuery::ORDER_PATH_MODIFIED)
->setLimit(10)
->needFlags(true)
->needDrafts(true)
->needRelationships(true);
foreach ($path_map as $path => $path_id) {
$query->withPath($repository->getID(), $path_id);
}
$results = $query->execute();
// Strip out *this* revision.
foreach ($results as $key => $result) {
if ($result->getID() == $this->revisionID) {
unset($results[$key]);
}
}
return $results;
}
private function renderOtherRevisions(array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$user = $this->getRequest()->getUser();
$view = id(new DifferentialRevisionListView())
->setHeader(pht('Open Revisions Affecting These Files'))
->setRevisions($revisions)
->setUser($user);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
return $view;
}
/**
* Note this code is somewhat similar to the buildPatch method in
* @{class:DifferentialReviewRequestMail}.
*
* @return @{class:AphrontRedirectResponse}
*/
private function buildRawDiffResponse(
DifferentialRevision $revision,
array $changesets,
array $vs_changesets,
array $vs_map,
PhabricatorRepository $repository = null) {
assert_instances_of($changesets, 'DifferentialChangeset');
assert_instances_of($vs_changesets, 'DifferentialChangeset');
$viewer = $this->getRequest()->getUser();
id(new DifferentialHunkQuery())
->setViewer($viewer)
->withChangesets($changesets)
->needAttachToChangesets(true)
->execute();
$diff = new DifferentialDiff();
$diff->attachChangesets($changesets);
$raw_changes = $diff->buildChangesList();
$changes = array();
foreach ($raw_changes as $changedict) {
$changes[] = ArcanistDiffChange::newFromDictionary($changedict);
}
$loader = id(new PhabricatorFileBundleLoader())
->setViewer($viewer);
$bundle = ArcanistBundle::newFromChanges($changes);
$bundle->setLoadFileDataCallback(array($loader, 'loadFileData'));
$vcs = $repository ? $repository->getVersionControlSystem() : null;
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$raw_diff = $bundle->toGitPatch();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
default:
$raw_diff = $bundle->toUnifiedDiff();
break;
}
$request_uri = $this->getRequest()->getRequestURI();
// this ends up being something like
// D123.diff
// or the verbose
// D123.vs123.id123.whitespaceignore-all.diff
// lame but nice to include these options
$file_name = ltrim($request_uri->getPath(), '/').'.';
foreach ($request_uri->getQueryParams() as $key => $value) {
if ($key == 'download') {
continue;
}
$file_name .= $key.$value.'.';
}
$file_name .= 'diff';
$file = PhabricatorFile::buildFromFileDataOrHash(
$raw_diff,
array(
'name' => $file_name,
'ttl' => (60 * 60 * 24),
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$file->attachToObject($revision->getPHID());
unset($unguarded);
return $file->getRedirectResponse();
}
private function buildTransactions(
DifferentialRevision $revision,
DifferentialDiff $left_diff,
DifferentialDiff $right_diff,
array $changesets) {
$timeline = $this->buildTransactionTimeline(
$revision,
new DifferentialTransactionQuery(),
$engine = null,
array(
'left' => $left_diff->getID(),
'right' => $right_diff->getID(),
));
return $timeline;
}
private function buildRevisionWarnings(
DifferentialRevision $revision,
PhabricatorCustomFieldList $field_list,
array $warning_handle_map,
array $handles) {
$warnings = array();
foreach ($field_list->getFields() as $key => $field) {
$phids = idx($warning_handle_map, $key, array());
$field_handles = array_select_keys($handles, $phids);
$field_warnings = $field->getWarningsForRevisionHeader($field_handles);
foreach ($field_warnings as $warning) {
$warnings[] = $warning;
}
}
return $warnings;
}
}
diff --git a/src/docs/user/userguide/legalpad.diviner b/src/docs/user/userguide/legalpad.diviner
index 0d235d61cc..053f275d9f 100644
--- a/src/docs/user/userguide/legalpad.diviner
+++ b/src/docs/user/userguide/legalpad.diviner
@@ -1,131 +1,135 @@
@title Legalpad User Guide
@group userguide
Using Legalpad to track agreements and signatures on legal documents.
Overview
========
Legalpad is a simple application for tracking signatures on legal agreements.
You can add legal documents, users can sign them, and you can keep track of who
has signed what.
Right now, it is primarily useful for open source projects that have a
Contributor License Agreement or a similar document which needs to be signed
before changes can be accepted from contributors. In particular, it has
integrations into Differential which can block changes from being accepted until
the author has signed the required documents.
NOTE: Legalpad is a basic application, and missing many of the features of more
general document signing software. It may be useful to help you do things
beyond track CLAs, but you should evaluate its capabilities carefully.
Documents
=========
The primary object in legalpad is the //Legalpad Document//, which represents
a written agreement, contract, policy, or other similar document.
Most fields of a document are relatively straightforward, but some are unique
to the application:
**Who Should Sign?** This field controls what kind of signatures the document
accepts. You can choose either **Individuals** (users will be prompted to sign
with their name), **Corporations** (users will be prompted to enter information
identifying the corporation they are signing on behalf of) or **No One** (for
policy documents or other documents which do not require a signature).
**Require Signature** This field allows you to create a document which all of
your users must sign before they can use Phabricator, like a terms of service
document. See "Use Case: Terms of Service" below for details. These documents
must be signable by individuals.
Use Case: Requiring a CLA
===============
Open source projects often require contributors to sign a license agreement
before their contributions can be accepted to the project. To require a CLA or
similar document for an open source project:
- Create a CLA document in Legalpad.
- Create a "Global" Herald rule which triggers "Always".
- The rule should take the action "Require legal signatures", specifying
your CLA document as the required document.
After you've done this, all new reviews created in Differential by authors who
have not signed the document will trigger a signature requirement. These reviews
can not be accepted until the document has been signed.
+The content of these revisions will also be hidden until the document has been
+signed. This prevents reviewers from being tainted by examining the changes if
+the author ultimately declines to sign the CLA.
+
If the author has already signed all of the required documents, Herald will not
take any actions. This reduces the amount of noise the CLA process generates for
regular contributors.
You can require more than one document (to require that they all be signed), if
you have several agreements that contributors must sign.
Alternatively, if you have several different sets of agreements for different
projects, you can also choose a more narrow Herald condition than "Always" (for
example, require a signature only if the revision is against certain
repositories).
Use Case: Terms of Service
=================================
If you have a "Terms of Service" document that you'd like users to agree to
before they're allowed to use your install, you can add it to Legalpad and then
check the **Require Signature** box for the document.
After logging in, users will need to agree to the document before they can
do other things with their account (you'll need to agree to it, too, as soon
as you save your changes, so that will give you a sense of the workflow).
Note that although users who have not signed all of the required documents can
not use most Phabricator functions, they can browse other Legalpad documents
that they have permission to see. This allows a terms document to be
supplemented with additional policy or guideline documents that users are free
to review before agreeing to the terms.
Use Case: Document-Based Policies
=======================
If you have a document like an NDA, you can write a policy rule which prevents
users from seeing content until they sign the document:
- In any policy control ("Visible To", "Editable By"), choose "Custom Policy".
- Add a rule like "Allow signers of legalpad documents: X".
- Leave the default rule as "Deny all other users".
- Save the policy.
Users will now only be able to take the action (for example, view or edit the
object) if they have signed the specified documents.
Adding Exemptions
=================
If you have users who have signed an alternate form of a document (for example,
you have a hard copy on file), or an equivalent document, or who are otherwise
exempt from needing to sign a document in Legalpad, you can add a signature
exemption for them.
Other applications will treat users with a signature exemption as though they
had signed the document, although the UI will show the signature as an exemption
rather than a normal signature.
To add an exemption, go to **Manage Document**, then **View Signatures**, then
**Add Signature Exemption**.
You can optionally add notes about why a user is exempt from signing a document.
To review the notes later (and see who added the exemption), click the colored
asterisk in the list view.
Roadmap
========
You can find discussion about the Legalpad roadmap here:
https://secure.phabricator.com/T5505
If there are features you'd like to see, let us know.

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 28, 2:48 AM (16 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
107684
Default Alt Text
(36 KB)

Event Timeline