diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php
index 93a5f5562c..e90f0e806c 100644
--- a/src/applications/maniphest/storage/ManiphestTask.php
+++ b/src/applications/maniphest/storage/ManiphestTask.php
@@ -1,473 +1,506 @@
 <?php
 
 final class ManiphestTask extends ManiphestDAO
   implements
     PhabricatorSubscribableInterface,
     PhabricatorMarkupInterface,
     PhabricatorPolicyInterface,
     PhabricatorTokenReceiverInterface,
     PhabricatorFlaggableInterface,
     PhabricatorMentionableInterface,
     PhrequentTrackableInterface,
     PhabricatorCustomFieldInterface,
     PhabricatorDestructibleInterface,
     PhabricatorApplicationTransactionInterface,
     PhabricatorProjectInterface,
     PhabricatorSpacesInterface,
     PhabricatorConduitResultInterface,
     PhabricatorFulltextInterface {
 
   const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
 
   protected $authorPHID;
   protected $ownerPHID;
 
   protected $status;
   protected $priority;
   protected $subpriority = 0;
 
   protected $title = '';
   protected $originalTitle = '';
   protected $description = '';
   protected $originalEmailSource;
   protected $mailKey;
   protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
   protected $editPolicy = PhabricatorPolicies::POLICY_USER;
 
   protected $ownerOrdering;
   protected $spacePHID;
   protected $properties = array();
   protected $points;
 
   private $subscriberPHIDs = self::ATTACHABLE;
   private $groupByProjectPHID = self::ATTACHABLE;
   private $customFields = self::ATTACHABLE;
   private $edgeProjectPHIDs = self::ATTACHABLE;
 
   public static function initializeNewTask(PhabricatorUser $actor) {
     $app = id(new PhabricatorApplicationQuery())
       ->setViewer($actor)
       ->withClasses(array('PhabricatorManiphestApplication'))
       ->executeOne();
 
     $view_policy = $app->getPolicy(ManiphestDefaultViewCapability::CAPABILITY);
     $edit_policy = $app->getPolicy(ManiphestDefaultEditCapability::CAPABILITY);
 
     return id(new ManiphestTask())
       ->setStatus(ManiphestTaskStatus::getDefaultStatus())
       ->setPriority(ManiphestTaskPriority::getDefaultPriority())
       ->setAuthorPHID($actor->getPHID())
       ->setViewPolicy($view_policy)
       ->setEditPolicy($edit_policy)
       ->setSpacePHID($actor->getDefaultSpacePHID())
       ->attachProjectPHIDs(array())
       ->attachSubscriberPHIDs(array());
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_SERIALIZATION => array(
         'properties' => self::SERIALIZATION_JSON,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'ownerPHID' => 'phid?',
         'status' => 'text12',
         'priority' => 'uint32',
         'title' => 'sort',
         'originalTitle' => 'text',
         'description' => 'text',
         'mailKey' => 'bytes20',
         'ownerOrdering' => 'text64?',
         'originalEmailSource' => 'text255?',
         'subpriority' => 'double',
         'points' => 'double?',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'key_phid' => null,
         'phid' => array(
           'columns' => array('phid'),
           'unique' => true,
         ),
         'priority' => array(
           'columns' => array('priority', 'status'),
         ),
         'status' => array(
           'columns' => array('status'),
         ),
         'ownerPHID' => array(
           'columns' => array('ownerPHID', 'status'),
         ),
         'authorPHID' => array(
           'columns' => array('authorPHID', 'status'),
         ),
         'ownerOrdering' => array(
           'columns' => array('ownerOrdering'),
         ),
         'priority_2' => array(
           'columns' => array('priority', 'subpriority'),
         ),
         'key_dateCreated' => array(
           'columns' => array('dateCreated'),
         ),
         'key_dateModified' => array(
           'columns' => array('dateModified'),
         ),
         'key_title' => array(
           'columns' => array('title(64)'),
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function loadDependsOnTaskPHIDs() {
     return PhabricatorEdgeQuery::loadDestinationPHIDs(
       $this->getPHID(),
       ManiphestTaskDependsOnTaskEdgeType::EDGECONST);
   }
 
   public function loadDependedOnByTaskPHIDs() {
     return PhabricatorEdgeQuery::loadDestinationPHIDs(
       $this->getPHID(),
       ManiphestTaskDependedOnByTaskEdgeType::EDGECONST);
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(ManiphestTaskPHIDType::TYPECONST);
   }
 
   public function getSubscriberPHIDs() {
     return $this->assertAttached($this->subscriberPHIDs);
   }
 
   public function getProjectPHIDs() {
     return $this->assertAttached($this->edgeProjectPHIDs);
   }
 
   public function attachProjectPHIDs(array $phids) {
     $this->edgeProjectPHIDs = $phids;
     return $this;
   }
 
   public function attachSubscriberPHIDs(array $phids) {
     $this->subscriberPHIDs = $phids;
     return $this;
   }
 
   public function setOwnerPHID($phid) {
     $this->ownerPHID = nonempty($phid, null);
     return $this;
   }
 
   public function setTitle($title) {
     $this->title = $title;
     if (!$this->getID()) {
       $this->originalTitle = $title;
     }
     return $this;
   }
 
   public function getMonogram() {
     return 'T'.$this->getID();
   }
 
   public function attachGroupByProjectPHID($phid) {
     $this->groupByProjectPHID = $phid;
     return $this;
   }
 
   public function getGroupByProjectPHID() {
     return $this->assertAttached($this->groupByProjectPHID);
   }
 
   public function save() {
     if (!$this->mailKey) {
       $this->mailKey = Filesystem::readRandomCharacters(20);
     }
 
     $result = parent::save();
 
     return $result;
   }
 
   public function isClosed() {
     return ManiphestTaskStatus::isClosedStatus($this->getStatus());
   }
 
   public function setProperty($key, $value) {
     $this->properties[$key] = $value;
     return $this;
   }
 
   public function getProperty($key, $default = null) {
     return idx($this->properties, $key, $default);
   }
 
   public function getCoverImageFilePHID() {
     return idx($this->properties, 'cover.filePHID');
   }
 
   public function getCoverImageThumbnailPHID() {
     return idx($this->properties, 'cover.thumbnailPHID');
   }
 
   public function getWorkboardOrderVectors() {
     return array(
       PhabricatorProjectColumn::ORDER_PRIORITY => array(
         (int)-$this->getPriority(),
         (double)-$this->getSubpriority(),
         (int)-$this->getID(),
       ),
     );
   }
 
+  private function comparePriorityTo(ManiphestTask $other) {
+    $upri = $this->getPriority();
+    $vpri = $other->getPriority();
+
+    if ($upri != $vpri) {
+      return ($upri - $vpri);
+    }
+
+    $usub = $this->getSubpriority();
+    $vsub = $other->getSubpriority();
+
+    if ($usub != $vsub) {
+      return ($usub - $vsub);
+    }
+
+    $uid = $this->getID();
+    $vid = $other->getID();
+
+    if ($uid != $vid) {
+      return ($uid - $vid);
+    }
+
+    return 0;
+  }
+
+  public function isLowerPriorityThan(ManiphestTask $other) {
+    return ($this->comparePriorityTo($other) < 0);
+  }
+
+  public function isHigherPriorityThan(ManiphestTask $other) {
+    return ($this->comparePriorityTo($other) > 0);
+  }
+
   public function getWorkboardProperties() {
     return array(
       'status' => $this->getStatus(),
       'points' => (double)$this->getPoints(),
     );
   }
 
 
 /* -(  PhabricatorSubscribableInterface  )----------------------------------- */
 
 
   public function isAutomaticallySubscribed($phid) {
     return ($phid == $this->getOwnerPHID());
   }
 
   public function shouldShowSubscribersProperty() {
     return true;
   }
 
 
 /* -(  Markup Interface  )--------------------------------------------------- */
 
 
   /**
    * @task markup
    */
   public function getMarkupFieldKey($field) {
     $hash = PhabricatorHash::digest($this->getMarkupText($field));
     $id = $this->getID();
     return "maniphest:T{$id}:{$field}:{$hash}";
   }
 
 
   /**
    * @task markup
    */
   public function getMarkupText($field) {
     return $this->getDescription();
   }
 
 
   /**
    * @task markup
    */
   public function newMarkupEngine($field) {
     return PhabricatorMarkupEngine::newManiphestMarkupEngine();
   }
 
 
   /**
    * @task markup
    */
   public function didMarkupText(
     $field,
     $output,
     PhutilMarkupEngine $engine) {
     return $output;
   }
 
 
   /**
    * @task markup
    */
   public function shouldUseMarkupCache($field) {
     return (bool)$this->getID();
   }
 
 
 /* -(  Policy Interface  )--------------------------------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return $this->getViewPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return $this->getEditPolicy();
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $user) {
     // The owner of a task can always view and edit it.
     $owner_phid = $this->getOwnerPHID();
     if ($owner_phid) {
       $user_phid = $user->getPHID();
       if ($user_phid == $owner_phid) {
         return true;
       }
     }
 
     return false;
   }
 
   public function describeAutomaticCapability($capability) {
     return pht('The owner of a task can always view and edit it.');
   }
 
 
 /* -(  PhabricatorTokenReceiverInterface  )---------------------------------- */
 
 
   public function getUsersToNotifyOfTokenGiven() {
     // Sort of ambiguous who this was intended for; just let them both know.
     return array_filter(
       array_unique(
         array(
           $this->getAuthorPHID(),
           $this->getOwnerPHID(),
         )));
   }
 
 
 /* -(  PhabricatorCustomFieldInterface  )------------------------------------ */
 
 
   public function getCustomFieldSpecificationForRole($role) {
     return PhabricatorEnv::getEnvConfig('maniphest.fields');
   }
 
   public function getCustomFieldBaseClass() {
     return 'ManiphestCustomField';
   }
 
   public function getCustomFields() {
     return $this->assertAttached($this->customFields);
   }
 
   public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
     $this->customFields = $fields;
     return $this;
   }
 
 
 /* -(  PhabricatorDestructibleInterface  )----------------------------------- */
 
 
   public function destroyObjectPermanently(
     PhabricatorDestructionEngine $engine) {
 
     $this->openTransaction();
     $this->delete();
     $this->saveTransaction();
   }
 
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
 
   public function getApplicationTransactionEditor() {
     return new ManiphestTransactionEditor();
   }
 
   public function getApplicationTransactionObject() {
     return $this;
   }
 
   public function getApplicationTransactionTemplate() {
     return new ManiphestTransaction();
   }
 
   public function willRenderTimeline(
     PhabricatorApplicationTransactionView $timeline,
     AphrontRequest $request) {
 
     return $timeline;
   }
 
 
 /* -(  PhabricatorSpacesInterface  )----------------------------------------- */
 
 
   public function getSpacePHID() {
     return $this->spacePHID;
   }
 
 
 /* -(  PhabricatorConduitResultInterface  )---------------------------------- */
 
 
   public function getFieldSpecificationsForConduit() {
     return array(
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('title')
         ->setType('string')
         ->setDescription(pht('The title of the task.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('authorPHID')
         ->setType('phid')
         ->setDescription(pht('Original task author.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('ownerPHID')
         ->setType('phid?')
         ->setDescription(pht('Current task owner, if task is assigned.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('status')
         ->setType('map<string, wild>')
         ->setDescription(pht('Information about task status.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('priority')
         ->setType('map<string, wild>')
         ->setDescription(pht('Information about task priority.')),
     );
   }
 
   public function getFieldValuesForConduit() {
 
     $status_value = $this->getStatus();
     $status_info = array(
       'value' => $status_value,
       'name' => ManiphestTaskStatus::getTaskStatusName($status_value),
       'color' => ManiphestTaskStatus::getStatusColor($status_value),
     );
 
     $priority_value = (int)$this->getPriority();
     $priority_info = array(
       'value' => $priority_value,
       'subpriority' => (double)$this->getSubpriority(),
       'name' => ManiphestTaskPriority::getTaskPriorityName($priority_value),
       'color' => ManiphestTaskPriority::getTaskPriorityColor($priority_value),
     );
 
     return array(
       'name' => $this->getTitle(),
       'authorPHID' => $this->getAuthorPHID(),
       'ownerPHID' => $this->getOwnerPHID(),
       'status' => $status_info,
       'priority' => $priority_info,
     );
   }
 
   public function getConduitSearchAttachments() {
     return array();
   }
 
 
 /* -(  PhabricatorFulltextInterface  )--------------------------------------- */
 
 
   public function newFulltextEngine() {
     return new ManiphestTaskFulltextEngine();
   }
 
 }
diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php
index 4aa9e0eec2..d3540a1781 100644
--- a/src/applications/project/controller/PhabricatorProjectMoveController.php
+++ b/src/applications/project/controller/PhabricatorProjectMoveController.php
@@ -1,182 +1,233 @@
 <?php
 
 final class PhabricatorProjectMoveController
   extends PhabricatorProjectController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $request->validateCSRF();
 
     $column_phid = $request->getStr('columnPHID');
     $object_phid = $request->getStr('objectPHID');
     $after_phid = $request->getStr('afterPHID');
     $before_phid = $request->getStr('beforePHID');
     $order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER);
 
     $project = id(new PhabricatorProjectQuery())
       ->setViewer($viewer)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
         ))
       ->withIDs(array($id))
       ->executeOne();
     if (!$project) {
       return new Aphront404Response();
     }
 
     $board_phid = $project->getPHID();
 
     $object = id(new ManiphestTaskQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($object_phid))
       ->needProjectPHIDs(true)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
 
     if (!$object) {
       return new Aphront404Response();
     }
 
     $columns = id(new PhabricatorProjectColumnQuery())
       ->setViewer($viewer)
       ->withProjectPHIDs(array($project->getPHID()))
       ->execute();
 
     $columns = mpull($columns, null, 'getPHID');
     $column = idx($columns, $column_phid);
     if (!$column) {
       // User is trying to drop this object into a nonexistent column, just kick
       // them out.
       return new Aphront404Response();
     }
 
     $engine = id(new PhabricatorBoardLayoutEngine())
       ->setViewer($viewer)
       ->setBoardPHIDs(array($board_phid))
       ->setObjectPHIDs(array($object_phid))
       ->executeLayout();
 
     $columns = $engine->getObjectColumns($board_phid, $object_phid);
     $old_column_phids = mpull($columns, 'getPHID');
 
     $xactions = array();
 
     if ($order == PhabricatorProjectColumn::ORDER_NATURAL) {
       $order_params = array(
         'afterPHID' => $after_phid,
         'beforePHID' => $before_phid,
       );
     } else {
       $order_params = array();
     }
 
     $xactions[] = id(new ManiphestTransaction())
       ->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN)
       ->setNewValue(
         array(
           'columnPHIDs' => array($column->getPHID()),
           'projectPHID' => $column->getProjectPHID(),
         ) + $order_params)
       ->setOldValue(
         array(
           'columnPHIDs' => $old_column_phids,
           'projectPHID' => $column->getProjectPHID(),
         ));
 
-    $task_phids = array();
-    if ($after_phid) {
-      $task_phids[] = $after_phid;
-    }
-    if ($before_phid) {
-      $task_phids[] = $before_phid;
-    }
-
-    if ($task_phids && ($order == PhabricatorProjectColumn::ORDER_PRIORITY)) {
-      $tasks = id(new ManiphestTaskQuery())
-        ->setViewer($viewer)
-        ->withPHIDs($task_phids)
-        ->needProjectPHIDs(true)
-        ->requireCapabilities(
-          array(
-            PhabricatorPolicyCapability::CAN_VIEW,
-            PhabricatorPolicyCapability::CAN_EDIT,
-          ))
-        ->execute();
-      if (count($tasks) != count($task_phids)) {
-        return new Aphront404Response();
-      }
-      $tasks = mpull($tasks, null, 'getPHID');
-
-      $try = array(
-        array($after_phid, true),
-        array($before_phid, false),
-      );
-
-      $pri = null;
-      $sub = null;
-      foreach ($try as $spec) {
-        list($task_phid, $is_after) = $spec;
-        $task = idx($tasks, $task_phid);
-        if ($task) {
-          list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority(
-            $task,
-            $is_after);
-          break;
-        }
-      }
-
-      if ($pri !== null) {
-        $xactions[] = id(new ManiphestTransaction())
-          ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
-          ->setNewValue($pri);
-        $xactions[] = id(new ManiphestTransaction())
-          ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
-          ->setNewValue($sub);
+    if ($order == PhabricatorProjectColumn::ORDER_PRIORITY) {
+      $priority_xactions = $this->getPriorityTransactions(
+        $object,
+        $after_phid,
+        $before_phid);
+      foreach ($priority_xactions as $xaction) {
+        $xactions[] = $xaction;
       }
     }
 
     $proxy = $column->getProxy();
     if ($proxy) {
       // We're moving the task into a subproject or milestone column, so add
       // the subproject or milestone.
       $add_projects = array($proxy->getPHID());
     } else if ($project->getHasSubprojects() || $project->getHasMilestones()) {
       // We're moving the task into the "Backlog" column on the parent project,
       // so add the parent explicitly. This gets rid of any subproject or
       // milestone tags.
       $add_projects = array($project->getPHID());
     } else {
       $add_projects = array();
     }
 
     if ($add_projects) {
       $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
 
       $xactions[] = id(new ManiphestTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
         ->setMetadataValue('edge:type', $project_type)
         ->setNewValue(
           array(
             '+' => array_fuse($add_projects),
           ));
     }
 
     $editor = id(new ManiphestTransactionEditor())
       ->setActor($viewer)
       ->setContinueOnMissingFields(true)
       ->setContinueOnNoEffect(true)
       ->setContentSourceFromRequest($request);
 
     $editor->applyTransactions($object, $xactions);
 
     return $this->newCardResponse($board_phid, $object_phid);
   }
 
+  private function getPriorityTransactions(
+    ManiphestTask $task,
+    $after_phid,
+    $before_phid) {
+
+    list($after_task, $before_task) = $this->loadPriorityTasks(
+      $after_phid,
+      $before_phid);
+
+    $must_move = false;
+    if ($after_task && !$task->isLowerPriorityThan($after_task)) {
+      $must_move = true;
+    }
+
+    if ($before_task && !$task->isHigherPriorityThan($before_task)) {
+      $must_move = true;
+    }
+
+    // The move doesn't require a priority change to be valid, so don't
+    // change the priority since we are not being forced to.
+    if (!$must_move) {
+      return array();
+    }
+
+    $try = array(
+      array($after_task, true),
+      array($before_task, false),
+    );
+
+    $pri = null;
+    $sub = null;
+    foreach ($try as $spec) {
+      list($task, $is_after) = $spec;
+
+      if (!$task) {
+        continue;
+      }
+
+      list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority(
+        $task,
+        $is_after);
+    }
+
+    $xactions = array();
+    if ($pri !== null) {
+      $xactions[] = id(new ManiphestTransaction())
+        ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
+        ->setNewValue($pri);
+      $xactions[] = id(new ManiphestTransaction())
+        ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
+        ->setNewValue($sub);
+    }
+
+    return $xactions;
+  }
+
+  private function loadPriorityTasks($after_phid, $before_phid) {
+    $viewer = $this->getViewer();
+
+    $task_phids = array();
+
+    if ($after_phid) {
+      $task_phids[] = $after_phid;
+    }
+    if ($before_phid) {
+      $task_phids[] = $before_phid;
+    }
+
+    if (!$task_phids) {
+      return array(null, null);
+    }
+
+    $tasks = id(new ManiphestTaskQuery())
+      ->setViewer($viewer)
+      ->withPHIDs($task_phids)
+      ->execute();
+    $tasks = mpull($tasks, null, 'getPHID');
+
+    if ($after_phid) {
+      $after_task = idx($tasks, $after_phid);
+    } else {
+      $after_task = null;
+    }
+
+    if ($before_phid) {
+      $before_task = idx($tasks, $before_phid);
+    } else {
+      $before_task = null;
+    }
+
+    return array($after_task, $before_task);
+  }
+
 }