diff --git a/resources/sql/patches/032.viewtime.sql b/resources/sql/patches/032.viewtime.sql
new file mode 100644
index 0000000000..455e3d7809
--- /dev/null
+++ b/resources/sql/patches/032.viewtime.sql
@@ -0,0 +1,6 @@
+CREATE TABLE phabricator_differential.differential_viewtime (
+ viewerPHID varchar(64) not null,
+ objectPHID varchar(64) not null,
+ viewTime int unsigned not null,
+ PRIMARY KEY (viewerPHID, objectPHID)
+);
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 6527f5a0fd..1aded6c0a3 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -158,6 +158,7 @@ phutil_register_library_map(array(
'DifferentialSubscribeController' => 'applications/differential/controller/subscribe',
'DifferentialTasksAttacher' => 'applications/differential/tasks',
'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus',
+ 'DifferentialViewTime' => 'applications/differential/storage/viewtime',
'DiffusionBranchInformation' => 'applications/diffusion/data/branch',
'DiffusionBranchQuery' => 'applications/diffusion/query/branch/base',
'DiffusionBranchTableView' => 'applications/diffusion/view/branchtable',
@@ -585,6 +586,7 @@ phutil_register_library_map(array(
'DifferentialRevisionUpdateHistoryView' => 'AphrontView',
'DifferentialRevisionViewController' => 'DifferentialController',
'DifferentialSubscribeController' => 'DifferentialController',
+ 'DifferentialViewTime' => 'DifferentialDAO',
'DiffusionBranchTableView' => 'DiffusionView',
'DiffusionBrowseController' => 'DiffusionController',
'DiffusionBrowseFileController' => 'DiffusionController',
diff --git a/src/applications/differential/controller/revisionlist/DifferentialRevisionListController.php b/src/applications/differential/controller/revisionlist/DifferentialRevisionListController.php
index af365b56ad..177a7e0c4f 100644
--- a/src/applications/differential/controller/revisionlist/DifferentialRevisionListController.php
+++ b/src/applications/differential/controller/revisionlist/DifferentialRevisionListController.php
@@ -90,6 +90,16 @@ class DifferentialRevisionListController extends DifferentialController {
),
),
),
+ 'updates' => array(
+ 'name' => 'Updates',
+ 'queries' => array(
+ array(
+ 'query' => DifferentialRevisionListData::QUERY_UPDATED_SINCE,
+ 'header' =>
+ 'Diffs that have been updated since you\'ve last viewed them',
+ ),
+ ),
+ ),
'
',
'All Revisions',
'allopen' => array(
diff --git a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php
index 5a03483b2e..835250f9f8 100644
--- a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php
+++ b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php
@@ -205,6 +205,8 @@ class DifferentialRevisionViewController extends DifferentialController {
$comment_form->setUser($user);
$comment_form->setDraft($draft);
+ $this->updateViewTime($user->getPHID(), $revision->getPHID());
+
return $this->buildStandardPageResponse(
''.
$revision_detail->render().
@@ -593,6 +595,14 @@ class DifferentialRevisionViewController extends DifferentialController {
return array($changesets, $vs_map);
}
+ private function updateViewTime($user_phid, $revision_phid) {
+ $view_time =
+ id(new DifferentialViewTime())
+ ->setViewerPHID($user_phid)
+ ->setObjectPHID($revision_phid)
+ ->setViewTime(time())
+ ->replace();
+ }
}
/*
diff --git a/src/applications/differential/controller/revisionview/__init__.php b/src/applications/differential/controller/revisionview/__init__.php
index 41a5ec6d22..3308871f23 100644
--- a/src/applications/differential/controller/revisionview/__init__.php
+++ b/src/applications/differential/controller/revisionview/__init__.php
@@ -15,6 +15,7 @@ phutil_require_module('phabricator', 'applications/differential/storage/comment'
phutil_require_module('phabricator', 'applications/differential/storage/diffproperty');
phutil_require_module('phabricator', 'applications/differential/storage/inlinecomment');
phutil_require_module('phabricator', 'applications/differential/storage/revision');
+phutil_require_module('phabricator', 'applications/differential/storage/viewtime');
phutil_require_module('phabricator', 'applications/differential/view/addcomment');
phutil_require_module('phabricator', 'applications/differential/view/changesetlistview');
phutil_require_module('phabricator', 'applications/differential/view/difftableofcontents');
diff --git a/src/applications/differential/data/revisionlist/DifferentialRevisionListData.php b/src/applications/differential/data/revisionlist/DifferentialRevisionListData.php
index c6c8f80540..afa81d7c2f 100644
--- a/src/applications/differential/data/revisionlist/DifferentialRevisionListData.php
+++ b/src/applications/differential/data/revisionlist/DifferentialRevisionListData.php
@@ -29,6 +29,7 @@ class DifferentialRevisionListData {
const QUERY_PHIDS = 'phids';
const QUERY_CC = 'cc';
const QUERY_ALL_OPEN = 'all-open';
+ const QUERY_UPDATED_SINCE = 'updated-since';
private $ids;
private $filter;
@@ -170,6 +171,9 @@ class DifferentialRevisionListData {
'revision.phid in (%Ls)',
$this->ids);
break;
+ case self::QUERY_UPDATED_SINCE:
+ $this->revisions = $this->loadAllUpdated();
+ break;
}
return $this->revisions;
@@ -183,6 +187,31 @@ class DifferentialRevisionListData {
);
}
+ private function loadAllUpdated() {
+ $revision = new DifferentialRevision();
+ $min_view_time = (int)PhabricatorEnv::getEnvConfig('updates.min-view-time');
+
+ $data = queryfx_all(
+ $revision->establishConnection('r'),
+ 'SELECT DISTINCT revision.* FROM %T revision
+ JOIN %T rel ON rel.revisionID = revision.id
+ AND (rel.objectPHID in (%Ls) OR revision.authorPHID in (%Ls))
+ LEFT JOIN %T viewtime ON viewtime.viewerPHID in (%Ls)
+ AND viewtime.objectPHID = revision.phid
+ WHERE GREATEST(%d, IFNULL(viewtime.viewTime, 0)) < revision.dateModified
+ %Q',
+ $revision->getTableName(),
+ DifferentialRevision::RELATIONSHIP_TABLE,
+ $this->ids,
+ $this->ids,
+ DifferentialRevision::TABLE_VIEW_TIME,
+ $this->ids,
+ $min_view_time,
+ $this->getOrderClause());
+ return $revision->loadAllFromArray($data);
+ }
+
+
private function loadAllOpen() {
return $this->loadAllWhere('status in (%Ld)', $this->getOpenStatuses());
}
diff --git a/src/applications/differential/data/revisionlist/__init__.php b/src/applications/differential/data/revisionlist/__init__.php
index 166a7864cf..846363efa2 100644
--- a/src/applications/differential/data/revisionlist/__init__.php
+++ b/src/applications/differential/data/revisionlist/__init__.php
@@ -8,6 +8,7 @@
phutil_require_module('phabricator', 'applications/differential/constants/revisionstatus');
phutil_require_module('phabricator', 'applications/differential/storage/revision');
+phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phutil', 'utils');
diff --git a/src/applications/differential/storage/revision/DifferentialRevision.php b/src/applications/differential/storage/revision/DifferentialRevision.php
index ed72902df1..910e4bd290 100644
--- a/src/applications/differential/storage/revision/DifferentialRevision.php
+++ b/src/applications/differential/storage/revision/DifferentialRevision.php
@@ -39,6 +39,7 @@ class DifferentialRevision extends DifferentialDAO {
private $commits;
const RELATIONSHIP_TABLE = 'differential_relationship';
+ const TABLE_VIEW_TIME = 'differential_viewtime';
const TABLE_COMMIT = 'differential_commit';
const RELATION_REVIEWER = 'revw';
diff --git a/src/applications/differential/storage/viewtime/DifferentialViewTime.php b/src/applications/differential/storage/viewtime/DifferentialViewTime.php
new file mode 100644
index 0000000000..2df8075781
--- /dev/null
+++ b/src/applications/differential/storage/viewtime/DifferentialViewTime.php
@@ -0,0 +1,43 @@
+ false,
+ self::CONFIG_IDS => self::IDS_MANUAL,
+ self::CONFIG_TIMESTAMPS => false,
+ );
+ }
+
+ // Primary key is (viewerPHID, objectPHID)
+ public function getIDKey() {
+ return null;
+ }
+
+ protected function shouldInsertWhenSaved() {
+ return true;
+ }
+}
+
diff --git a/src/applications/differential/storage/viewtime/__init__.php b/src/applications/differential/storage/viewtime/__init__.php
new file mode 100644
index 0000000000..905e57f5c9
--- /dev/null
+++ b/src/applications/differential/storage/viewtime/__init__.php
@@ -0,0 +1,13 @@
+