Merge "Don't display tooltips on touch devices"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index f41f643..e5073b2 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -3472,7 +3472,7 @@
 for later processing by command line tools.
 
 [[submit-preview]]
-===Submit Preview
+=== Submit Preview
 --
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/preview_submit'
 --
@@ -4195,7 +4195,7 @@
 
 .Request
 ----
-  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/website%2Freleases%2Flogo.png/safe_content HTTP/1.0
+  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/website%2Freleases%2Flogo.png/download HTTP/1.0
 ----
 
 .Response
@@ -4209,7 +4209,7 @@
 
 .Request
 ----
-  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/gerrit-server%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgoogle%2Fgerrit%2Fserver%2Fproject%2FRefControl.java/safe_content?suffix=new HTTP/1.0
+  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/gerrit-server%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgoogle%2Fgerrit%2Fserver%2Fproject%2FRefControl.java/download?suffix=new HTTP/1.0
 ----
 
 .Response
diff --git a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
index 02827ab..35f79c9 100644
--- a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
+++ b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
@@ -62,6 +62,7 @@
   private final OAuthTokenCache tokenCache;
   private OAuthServiceProvider serviceProvider;
   private OAuthUserInfo user;
+  private Account.Id accountId;
   private String redirectToken;
   private boolean linkMode;
 
@@ -80,7 +81,7 @@
   }
 
   boolean isLoggedIn() {
-    return tokenCache.has(user);
+    return user != null;
   }
 
   boolean isOAuthFinal(HttpServletRequest request) {
@@ -101,13 +102,10 @@
       OAuthToken token = oauth.getAccessToken(
           new OAuthVerifier(request.getParameter("code")));
       user = oauth.getUserInfo(token);
-      if (user != null && token != null) {
-        tokenCache.put(user, token);
-      }
 
       if (isLoggedIn()) {
         log.debug("Login-SUCCESS " + this);
-        authenticateAndRedirect(request, response);
+        authenticateAndRedirect(request, response, token);
         return true;
       }
       response.sendError(SC_UNAUTHORIZED);
@@ -128,7 +126,7 @@
   }
 
   private void authenticateAndRedirect(HttpServletRequest req,
-      HttpServletResponse rsp) throws IOException {
+      HttpServletResponse rsp, OAuthToken token) throws IOException {
     AuthRequest areq = new AuthRequest(user.getExternalId());
     AuthResult arsp;
     try {
@@ -147,6 +145,9 @@
       areq.setEmailAddress(user.getEmailAddress());
       areq.setDisplayName(user.getDisplayName());
       arsp = accountManager.authenticate(areq);
+
+      accountId = arsp.getAccountId();
+      tokenCache.put(accountId, token);
     } catch (AccountException e) {
       log.error("Unable to authenticate user \"" + user + "\"", e);
       rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
@@ -215,7 +216,10 @@
   }
 
   void logout() {
-    tokenCache.remove(user);
+    if (accountId != null) {
+      tokenCache.remove(accountId);
+      accountId = null;
+    }
     user = null;
     redirectToken = null;
     serviceProvider = null;
@@ -247,8 +251,8 @@
 
   @Override
   public String toString() {
-    return "OAuthSession [token=" + tokenCache.get(user) + ", user=" + user
-        + "]";
+    return "OAuthSession [token=" + tokenCache.get(accountId) + ", user="
+        + user + "]";
   }
 
   public void setServiceProvider(OAuthServiceProvider provider) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index 7c69224..8baef83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -309,6 +309,7 @@
     }
 
     byEmailCache.evict(account.getPreferredEmail());
+    byIdCache.evict(account.getId());
     realm.onCreateAccount(who, account);
     return new AuthResult(newId, extId.getKey(), true);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetOAuthToken.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetOAuthToken.java
index b6ba3bc..5d343c4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetOAuthToken.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetOAuthToken.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.auth.oauth.OAuthTokenCache;
 import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -52,16 +53,13 @@
     if (self.get() != rsrc.getUser()) {
       throw new AuthException("not allowed to get access token");
     }
-    String username = rsrc.getUser().getAccount().getUserName();
-    if (username == null) {
-      throw new ResourceNotFoundException();
-    }
-    OAuthToken accessToken = tokenCache.get(username);
+    Account a = rsrc.getUser().getAccount();
+    OAuthToken accessToken = tokenCache.get(a.getId());
     if (accessToken == null) {
       throw new ResourceNotFoundException();
     }
     OAuthTokenInfo accessTokenInfo = new OAuthTokenInfo();
-    accessTokenInfo.username = username;
+    accessTokenInfo.username = a.getUserName();
     accessTokenInfo.resourceHost = hostName;
     accessTokenInfo.accessToken = accessToken.getToken();
     accessTokenInfo.providerId = accessToken.getProviderId();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
index bcc5eab..94bdb06 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
@@ -19,8 +19,8 @@
 import com.google.common.cache.Cache;
 import com.google.gerrit.extensions.auth.oauth.OAuthToken;
 import com.google.gerrit.extensions.auth.oauth.OAuthTokenEncrypter;
-import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.inject.Inject;
 import com.google.inject.Module;
@@ -37,54 +37,39 @@
     return new CacheModule() {
       @Override
       protected void configure() {
-        persist(OAUTH_TOKENS, String.class, OAuthToken.class);
+        persist(OAUTH_TOKENS, Account.Id.class, OAuthToken.class);
       }
     };
   }
 
-  private final Cache<String, OAuthToken> cache;
+  private final Cache<Account.Id, OAuthToken> cache;
 
   @Inject
-  OAuthTokenCache(@Named(OAUTH_TOKENS) Cache<String, OAuthToken> cache,
+  OAuthTokenCache(@Named(OAUTH_TOKENS) Cache<Account.Id, OAuthToken> cache,
       DynamicItem<OAuthTokenEncrypter> encrypter) {
     this.cache = cache;
     this.encrypter = encrypter;
   }
 
-  public boolean has(OAuthUserInfo user) {
-    return user != null
-      ? cache.getIfPresent(user.getUserName()) != null
-      : false;
-  }
-
-  public OAuthToken get(OAuthUserInfo user) {
-    return user != null
-      ? get(user.getUserName())
-      : null;
-  }
-
-  public OAuthToken get(String userName) {
-    OAuthToken accessToken = cache.getIfPresent(userName);
+  public OAuthToken get(Account.Id id) {
+    OAuthToken accessToken = cache.getIfPresent(id);
     if (accessToken == null) {
       return null;
     }
     accessToken = decrypt(accessToken);
     if (accessToken.isExpired()) {
-      cache.invalidate(userName);
+      cache.invalidate(id);
       return null;
     }
     return accessToken;
   }
 
-  public void put(OAuthUserInfo user, OAuthToken accessToken) {
-    cache.put(checkNotNull(user.getUserName()),
-        encrypt(checkNotNull(accessToken)));
+  public void put(Account.Id id, OAuthToken accessToken) {
+    cache.put(id, encrypt(checkNotNull(accessToken)));
   }
 
-  public void remove(OAuthUserInfo user) {
-    if (user != null) {
-      cache.invalidate(user.getUserName());
-    }
+  public void remove(Account.Id id) {
+    cache.invalidate(id);
   }
 
   private OAuthToken encrypt(OAuthToken token) {
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
index 6d04ac3..17df28c 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
@@ -46,4 +46,13 @@
     Gerrit-Branch: {$branch.shortName}<br/>
     Gerrit-Owner: {$change.ownerEmail}
   </p>
+
+  {if $email.changeUrl}
+    <div itemscope itemtype="http://schema.org/EmailMessage">
+      <div itemscope itemprop="action" itemtype="http://schema.org/ViewAction">
+        <link itemprop="url" href="{$email.changeUrl}"/>
+        <meta itemprop="name" content="View Change"/>
+      </div>
+    </div>
+  {/if}
 {/template}
diff --git a/tools/js/bower2bazel.py b/tools/js/bower2bazel.py
index 8682012..33f7320 100755
--- a/tools/js/bower2bazel.py
+++ b/tools/js/bower2bazel.py
@@ -20,13 +20,11 @@
 
 from __future__ import print_function
 
-import atexit
 import collections
 import json
 import hashlib
 import optparse
 import os
-import shutil
 import subprocess
 import sys
 import tempfile
diff --git a/tools/js/bower2buck.py b/tools/js/bower2buck.py
index 57da475..c9f4789 100755
--- a/tools/js/bower2buck.py
+++ b/tools/js/bower2buck.py
@@ -26,8 +26,7 @@
 import sys
 import tempfile
 
-from tools import util
-
+from tools.js.bowerutil import hash_bower_component
 
 # This script is run with `buck run`, but needs to shell out to buck; this is
 # only possible if we avoid buckd.
@@ -78,7 +77,7 @@
     self.version = bower_json['version']
     self.deps = bower_json.get('dependencies', {})
     self.license = bower_json.get('license', 'NO LICENSE')
-    self.sha1 = bowerutil.hash_bower_component(
+    self.sha1 = hash_bower_component(
         hashlib.sha1(), os.path.dirname(bower_json_path)).hexdigest()
 
   def to_rule(self, packages):
diff --git a/tools/js/bowerutil.py b/tools/js/bowerutil.py
index eb8893b..8e8e835 100644
--- a/tools/js/bowerutil.py
+++ b/tools/js/bowerutil.py
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 import os
-from os import path
 
 
 def hash_bower_component(hash_obj, path):
diff --git a/tools/util.py b/tools/util.py
index 6dd6d59..e8182ed 100644
--- a/tools/util.py
+++ b/tools/util.py
@@ -12,7 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import os
 from os import path
 
 REPO_ROOTS = {