diff --git a/resources/sql/autopatches/20160503.repo.01.lpath.sql b/resources/sql/autopatches/20160503.repo.01.lpath.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20160503.repo.01.lpath.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_repository.repository
+  ADD localPath VARCHAR(128) COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20160503.repo.02.lpathkey.sql b/resources/sql/autopatches/20160503.repo.02.lpathkey.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20160503.repo.02.lpathkey.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_repository.repository
+  ADD UNIQUE KEY `key_local` (localPath);
diff --git a/resources/sql/autopatches/20160503.repo.03.lpathmigrate.php b/resources/sql/autopatches/20160503.repo.03.lpathmigrate.php
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20160503.repo.03.lpathmigrate.php
@@ -0,0 +1,57 @@
+<?php
+
+$table = new PhabricatorRepository();
+$conn_w = $table->establishConnection('w');
+
+$default_path = PhabricatorEnv::getEnvConfig('repository.default-local-path');
+$default_path = rtrim($default_path, '/');
+
+foreach (new LiskMigrationIterator($table) as $repository) {
+  $local_path = $repository->getLocalPath();
+  if (strlen($local_path)) {
+    // Repository already has a modern, unique local path.
+    continue;
+  }
+
+  $local_path = $repository->getDetail('local-path');
+  if (!strlen($local_path)) {
+    // Repository does not have a local path using the older format.
+    continue;
+  }
+
+  $random = Filesystem::readRandomCharacters(8);
+
+  // Try the configured path first, then a default path, then a path with some
+  // random noise.
+  $paths = array(
+    $local_path,
+    $default_path.'/'.$repository->getID().'/',
+    $default_path.'/'.$repository->getID().'-'.$random.'/',
+  );
+
+  foreach ($paths as $path) {
+    // Set, then get the path to normalize it.
+    $repository->setLocalPath($path);
+    $path = $repository->getLocalPath();
+
+    try {
+      queryfx(
+        $conn_w,
+        'UPDATE %T SET localPath = %s WHERE id = %d',
+        $table->getTableName(),
+        $path,
+        $repository->getID());
+
+      echo tsprintf(
+        "%s\n",
+        pht(
+          'Assigned repository "%s" to local path "%s".',
+          $repository->getDisplayName(),
+          $path));
+
+      break;
+    } catch (AphrontDuplicateKeyQueryException $ex) {
+      // Ignore, try the next one.
+    }
+  }
+}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
@@ -631,9 +631,7 @@
       pht('Storage Service'),
       $v_service);
 
-    $view->addProperty(
-      pht('Storage Path'),
-      $repository->getHumanReadableDetail('local-path'));
+    $view->addProperty(pht('Storage Path'), $repository->getLocalPath());
 
     return $view;
   }
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php
@@ -15,7 +15,7 @@
 
     $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
 
-    $v_local = $repository->getHumanReadableDetail('local-path');
+    $v_local = $repository->getLocalPath();
     $errors = array();
 
     $crumbs = $this->buildApplicationCrumbs();
@@ -51,11 +51,7 @@
       ->appendRemarkupInstructions(
         pht(
           "You can not adjust the local path for this repository from the ".
-          "web interface. To edit it, run this command:\n\n  %s",
-          sprintf(
-            'phabricator/ $ ./bin/repository edit %s --as %s --local-path ...',
-            $repository->getMonogram(),
-            $viewer->getUsername())))
+          'web interface.'))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($edit_uri, pht('Done')));
diff --git a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php
--- a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php
@@ -40,7 +40,7 @@
       ->setViewer($viewer);
 
     if ($repository->usesLocalWorkingCopy()) {
-      $storage_path = $repository->getHumanReadableDetail('local-path');
+      $storage_path = $repository->getLocalPath();
     } else {
       $storage_path = phutil_tag('em', array(), pht('No Local Working Copy'));
     }
diff --git a/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php b/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php
--- a/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php
+++ b/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php
@@ -110,7 +110,6 @@
       'description'       => $request->getValue('description'),
       'tracking-enabled'  => (bool)$request->getValue('tracking', true),
       'remote-uri'        => $remote_uri,
-      'local-path'        => $local_path,
       'branch-filter'     => array_fill_keys(
         $request->getValue('branchFilter', array()),
         true),
@@ -128,6 +127,8 @@
       $repository->setDetail($key, $value);
     }
 
+    $repository->setLocalPath($local_path);
+
     try {
       $repository->save();
     } catch (AphrontDuplicateKeyQueryException $ex) {
diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php
--- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php
+++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php
@@ -86,7 +86,7 @@
       case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI:
         return $object->getDetail('remote-uri');
       case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH:
-        return $object->getDetail('local-path');
+        return $object->getLocalPath();
       case PhabricatorRepositoryTransaction::TYPE_HOSTING:
         return $object->isHosted();
       case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP:
@@ -209,7 +209,7 @@
         $object->setDetail('remote-uri', $xaction->getNewValue());
         break;
       case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH:
-        $object->setDetail('local-path', $xaction->getNewValue());
+        $object->setLocalPath($xaction->getNewValue());
         break;
       case PhabricatorRepositoryTransaction::TYPE_HOSTING:
         return $object->setHosted($xaction->getNewValue());
@@ -706,7 +706,7 @@
 
     // If the repository does not have a local path yet, assign it one based
     // on its ID. We can't do this earlier because we won't have an ID yet.
-    $local_path = $object->getDetail('local-path');
+    $local_path = $object->getLocalPath();
     if (!strlen($local_path)) {
       $local_key = 'repository.default-local-path';
 
@@ -716,7 +716,7 @@
       $id = $object->getID();
       $local_path = "{$local_root}/{$id}/";
 
-      $object->setDetail('local-path', $local_path);
+      $object->setLocalPath($local_path);
       $object->save();
     }
 
diff --git a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php
--- a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php
+++ b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php
@@ -65,7 +65,7 @@
       ->setCallsign($callsign)
       ->setName(pht('Test Repo "%s"', $callsign))
       ->setVersionControlSystem($vcs_type)
-      ->setDetail('local-path', dirname($local).'/'.$callsign)
+      ->setLocalPath(dirname($local).'/'.$callsign)
       ->setDetail('remote-uri', 'file://'.$dir->getPath().'/');
 
     $this->didConstructRepository($repo);
diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php
--- a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php
@@ -22,11 +22,6 @@
             'help' => pht('Edit as user.'),
           ),
           array(
-            'name' => 'local-path',
-            'param' => 'path',
-            'help' => pht('Edit the local path.'),
-          ),
-          array(
             'name' => 'serve-http',
             'param' => 'string',
             'help' => pht('Edit the http serving policy.'),
@@ -83,7 +78,6 @@
 
       $xactions = array();
 
-      $type_local_path = PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH;
       $type_protocol_http =
         PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP;
       $type_protocol_ssh = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH;
@@ -93,11 +87,6 @@
         PhabricatorRepository::SERVE_READWRITE,
       );
 
-      if ($args->getArg('local-path')) {
-        $xactions[] = id(new PhabricatorRepositoryTransaction())
-          ->setTransactionType($type_local_path)
-          ->setNewValue($args->getArg('local-path'));
-      }
       $serve_http = $args->getArg('serve-http');
       if ($serve_http && in_array($serve_http, $allowed_serve_modes)) {
         $xactions[] = id(new PhabricatorRepositoryTransaction())
diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php
--- a/src/applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php
@@ -128,14 +128,12 @@
       }
 
       $repo = $row['repository'];
-      $details = $repo->getDetails();
-      $details['local-path'] = $row['dst'];
 
       queryfx(
         $repo->establishConnection('w'),
-        'UPDATE %T SET details = %s WHERE id = %d',
+        'UPDATE %T SET localPath = %s WHERE id = %d',
         $repo->getTableName(),
-        phutil_json_encode($details),
+        $row['dst'],
         $repo->getID());
     }
 
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -62,6 +62,7 @@
   protected $credentialPHID;
   protected $almanacServicePHID;
   protected $spacePHID;
+  protected $localPath;
 
   private $commitCount = self::ATTACHABLE;
   private $mostRecentCommit = self::ATTACHABLE;
@@ -107,6 +108,7 @@
         'pushPolicy' => 'policy',
         'credentialPHID' => 'phid?',
         'almanacServicePHID' => 'phid?',
+        'localPath' => 'text128?',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'callsign' => array(
@@ -123,6 +125,10 @@
           'columns' => array('repositorySlug'),
           'unique' => true,
         ),
+        'key_local' => array(
+          'columns' => array('localPath'),
+          'unique' => true,
+        ),
       ),
     ) + parent::getConfiguration();
   }
@@ -216,6 +222,13 @@
     return $monograms;
   }
 
+  public function setLocalPath($path) {
+    // Convert any extra slashes ("//") in the path to a single slash ("/").
+    $path = preg_replace('(//+)', '/', $path);
+
+    return parent::setLocalPath($path);
+  }
+
   public function getDetail($key, $default = null) {
     return idx($this->details, $key, $default);
   }
@@ -279,10 +292,6 @@
       ));
   }
 
-  public function getLocalPath() {
-    return $this->getDetail('local-path');
-  }
-
   public function getSubversionBaseURI($commit = null) {
     $subpath = $this->getDetail('svn-subpath');
     if (!strlen($subpath)) {
diff --git a/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php b/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php
--- a/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php
+++ b/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php
@@ -70,12 +70,12 @@
 
     $repo->setDetail('hosting-enabled', true);
 
-    $repo->setDetail('local-path', '/var/repo/SVN');
+    $repo->setLocalPath('/var/repo/SVN');
     $this->assertEqual(
       'file:///var/repo/SVN',
       $repo->getSubversionPathURI());
 
-    $repo->setDetail('local-path', '/var/repo/SVN/');
+    $repo->setLocalPath('/var/repo/SVN/');
     $this->assertEqual(
       'file:///var/repo/SVN',
       $repo->getSubversionPathURI());