Modernize URLs to be shorter and consistent Instead of using http://site/#change,1234 we now use a slightly more common looking http://site/#/c/1234 URL to link to a change. It is also quite a bit shorter than the old format, using up less space in the address bar. Files within a patch set are now denoted below the change, as in http://site/#/c/1234/1/src/module/foo.c making it easier to jump directly to a specific file, or to just see the structure of the current view. Whenever possible the old URLs continue to work by redirecting to the equivalent new URL. We plan to keep the old URLs alive, as many issue tracking systems have direct links based on the old URL format and these links should not be invalidated. This change also fixes the dynamic redirects of http://site/1234 and http://site/r/deadbeef to jump directly to the corresponding change if there is exactly one possible URL. This avoids the ugly interim redirect to http://site/#/q/1234,n,z when the server can compute what the correct location should be on its own. Entities that have multiple views suffix the URL with ",view-name" to indicate which view the user wants to see. For files the default view is the side-by-side view and does not have a suffix, while the unified patch view has a suffix of ",unified", for example: http://site/#/c/1234/1/src/module/foo.c,unified. Project admin panels use a similar view suffix to denote which of the tabs on the left menu is currently open, with the main info tab being the default with no suffix. Change-Id: I2bac7ef1b2638fb08df2659b8373960eadec205a
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java index db4f348..23b0d27 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -24,38 +24,38 @@ import com.google.gwtorm.client.KeyUtil; public class PageLinks { - public static final String SETTINGS = "settings"; - public static final String SETTINGS_PREFERENCES = "settings,preferences"; - public static final String SETTINGS_SSHKEYS = "settings,ssh-keys"; - public static final String SETTINGS_HTTP_PASSWORD = "settings,http-password"; - public static final String SETTINGS_WEBIDENT = "settings,web-identities"; - public static final String SETTINGS_MYGROUPS = "settings,group-memberships"; - public static final String SETTINGS_AGREEMENTS = "settings,agreements"; - public static final String SETTINGS_CONTACT = "settings,contact"; - public static final String SETTINGS_PROJECTS = "settings,projects"; - public static final String SETTINGS_NEW_AGREEMENT = "settings,new-agreement"; - public static final String REGISTER = "register"; + public static final String SETTINGS = "/settings/"; + public static final String SETTINGS_PREFERENCES = "/settings/preferences"; + public static final String SETTINGS_SSHKEYS = "/settings/ssh-keys"; + public static final String SETTINGS_HTTP_PASSWORD = "/settings/http-password"; + public static final String SETTINGS_WEBIDENT = "/settings/web-identities"; + public static final String SETTINGS_MYGROUPS = "/settings/group-memberships"; + public static final String SETTINGS_AGREEMENTS = "/settings/agreements"; + public static final String SETTINGS_CONTACT = "/settings/contact"; + public static final String SETTINGS_PROJECTS = "/settings/projects"; + public static final String SETTINGS_NEW_AGREEMENT = "/settings/new-agreement"; + public static final String REGISTER = "/register"; public static final String TOP = "n,z"; - public static final String MINE = "mine"; - public static final String ADMIN_GROUPS = "admin,groups"; - public static final String ADMIN_PROJECTS = "admin,projects"; + public static final String MINE = "/"; + public static final String ADMIN_GROUPS = "/admin/groups/"; + public static final String ADMIN_PROJECTS = "/admin/projects/"; public static String toChange(final ChangeInfo c) { return toChange(c.getId()); } public static String toChange(final Change.Id c) { - return "change," + c.toString(); + return "/c/" + c + "/"; } public static String toChange(final PatchSet.Id ps) { - return "change," + ps.getParentKey().toString() + ",patchset=" + ps.get(); + return "/c/" + ps.getParentKey() + "/" + ps.get(); } public static String toProjectAcceess(final Project.NameKey p) { - return "admin,project," + p.get() + ",access"; + return "/admin/projects/" + p.get() + ",access"; } public static String toAccountDashboard(final AccountInfo acct) { @@ -63,11 +63,16 @@ } public static String toAccountDashboard(final Account.Id acct) { - return "dashboard," + acct.toString(); + return "/dashboard/" + acct.toString(); } public static String toChangeQuery(final String query) { - return "q," + KeyUtil.encode(query) + "," + TOP; + return toChangeQuery(query, TOP); + } + + public static String toChangeQuery(String query, String page) { + query = KeyUtil.encode(query).replaceAll("%3[Aa]", ":"); + return "/q/" + query + "," + page; } public static String projectQuery(Project.NameKey proj, Status status) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java index 7e9ffc9..e3b23d7 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -58,6 +58,7 @@ import com.google.gerrit.client.changes.QueryScreen; import com.google.gerrit.client.patches.PatchScreen; import com.google.gerrit.client.ui.Screen; +import com.google.gerrit.common.PageLinks; import com.google.gerrit.common.auth.SignInMode; import com.google.gerrit.common.data.PatchSetDetail; import com.google.gerrit.reviewdb.Account; @@ -68,19 +69,25 @@ import com.google.gerrit.reviewdb.Project; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.RunAsyncCallback; +import com.google.gwt.user.client.Window; import com.google.gwtorm.client.KeyUtil; public class Dispatcher { public static String toPatchSideBySide(final Patch.Key id) { - return toPatch("sidebyside", id); + return toPatch("", id); } public static String toPatchUnified(final Patch.Key id) { return toPatch("unified", id); } - public static String toPatch(final String type, final Patch.Key id) { - return "patch," + type + "," + id.toString(); + private static String toPatch(String type, final Patch.Key id) { + PatchSet.Id ps = id.getParentKey(); + Change.Id c = ps.getParentKey(); + if (type != null && !type.isEmpty()) { + type = "," + type; + } + return "/c/" + c + "/" + ps.get() + "/" + KeyUtil.encode(id.get()) + type; } public static String toPatch(final PatchScreen.Type type, final Patch.Key id) { @@ -91,28 +98,36 @@ } } + public static String toPublish(PatchSet.Id ps) { + Change.Id c = ps.getParentKey(); + return "/c/" + c + "/" + ps.get() + ",publish"; + } + public static String toAccountGroup(final AccountGroup.Id id) { - return "admin,group," + id.toString(); + return "/admin/groups/" + id.toString(); } public static String toGroup(final AccountGroup.UUID uuid) { - return "admin,group,uuid-" + uuid.toString(); + return "/admin/groups/uuid-" + uuid.toString(); } - public static String toProjectAdmin(final Project.NameKey n, final String tab) { - return "admin,project," + n.toString() + "," + tab; + public static String toProjectAdmin(Project.NameKey n, String panel) { + if (ProjectScreen.INFO.equals(panel)) { + return "/admin/projects/" + n.toString(); + } + return "/admin/projects/" + n.toString() + "," + panel; } - static final String RELOAD_UI = "reload-ui,"; + static final String RELOAD_UI = "/reload-ui/"; private static boolean wasStartedByReloadUI; void display(String token) { assert token != null; try { try { - if (token.startsWith(RELOAD_UI)) { + if (matchPrefix(RELOAD_UI, token)) { wasStartedByReloadUI = true; - token = skip(RELOAD_UI, token); + token = skip(token); } select(token); } finally { @@ -125,160 +140,262 @@ } private static void select(final String token) { - if (token.startsWith("patch,")) { - patch(token, null, 0, null, null); + if (matchPrefix("/q/", token)) { + query(token); - } else if (token.startsWith("change,publish,")) { - publish(token); + } else if (matchPrefix("/c/", token)) { + change(token); - } else if (MINE.equals(token) || token.startsWith("mine,")) { + } else if (matchExact(MINE, token)) { Gerrit.display(token, mine(token)); - } else if (token.startsWith("all,")) { - Gerrit.display(token, all(token)); + } else if (matchPrefix("/dashboard/", token)) { + dashboard(token); - } else if (token.startsWith("project,")) { - Gerrit.display(token, project(token)); - - } else if (SETTINGS.equals(token) // - || REGISTER.equals(token) // - || token.startsWith("settings,") // - || token.startsWith("register,") // - || token.startsWith("VE,") // - || token.startsWith("SignInFailure,")) { + } else if (matchExact(SETTINGS, token) // + || matchPrefix("/settings/", token) // + || matchExact("register", token) // + || matchExact(REGISTER, token) // + || matchPrefix("/register/", token) // + || matchPrefix("/VE/", token) || matchPrefix("VE,", token) // + || matchPrefix("/SignInFailure,", token)) { settings(token); - } else if (token.startsWith("admin,")) { + } else if (matchPrefix("/admin/", token)) { admin(token); - } else { - Gerrit.display(token, core(token)); - } - } - - private static Screen mine(final String token) { - if (MINE.equals(token)) { - if (Gerrit.isSignedIn()) { - return new AccountDashboardScreen(Gerrit.getUserAccount().getId()); - - } else { - final Screen r = new AccountDashboardScreen(null); - r.setRequiresSignIn(true); - return r; - } - - } else if ("mine,starred".equals(token)) { - return QueryScreen.forQuery("is:starred"); - - } else if ("mine,drafts".equals(token)) { - return QueryScreen.forQuery("has:draft"); + } else if (/* LEGACY URL */matchPrefix("all,", token)) { + redirectFromLegacyToken(token, legacyAll(token)); + } else if (/* LEGACY URL */matchPrefix("mine,", token) + || matchExact("mine", token)) { + redirectFromLegacyToken(token, legacyMine(token)); + } else if (/* LEGACY URL */matchPrefix("project,", token)) { + redirectFromLegacyToken(token, legacyProject(token)); + } else if (/* LEGACY URL */matchPrefix("change,", token)) { + redirectFromLegacyToken(token, legacyChange(token)); + } else if (/* LEGACY URL */matchPrefix("patch,", token)) { + redirectFromLegacyToken(token, legacyPatch(token)); + } else if (/* LEGACY URL */matchPrefix("admin,", token)) { + redirectFromLegacyToken(token, legacyAdmin(token)); + } else if (/* LEGACY URL */matchPrefix("settings,", token) + || matchPrefix("register,", token)) { + redirectFromLegacyToken(token, legacySettings(token)); } else { - String p = "mine,watched,"; - if (token.startsWith(p)) { - return QueryScreen.forQuery("is:watched status:open", skip(p, token)); - } - - return new NotFoundScreen(); + Gerrit.display(token, new NotFoundScreen()); } } - private static Screen all(final String token) { - String p; - - p = "all,abandoned,"; - if (token.startsWith(p)) { - return QueryScreen.forQuery("status:abandoned", skip(p, token)); + private static void redirectFromLegacyToken(String oldToken, String newToken) { + if (newToken != null) { + Window.Location.replace(Window.Location.getPath() + "#" + newToken); + } else { + Gerrit.display(oldToken, new NotFoundScreen()); } - - p = "all,merged,"; - if (token.startsWith(p)) { - return QueryScreen.forQuery("status:merged", skip(p, token)); - } - - p = "all,open,"; - if (token.startsWith(p)) { - return QueryScreen.forQuery("status:open", skip(p, token)); - } - - return new NotFoundScreen(); } - private static Screen project(final String token) { - String p; + private static String legacyMine(final String token) { + if (matchExact("mine", token)) { + return MINE; + } - p = "project,open,"; - if (token.startsWith(p)) { - final String s = skip(p, token); + if (matchExact("mine,starred", token)) { + return PageLinks.toChangeQuery("is:starred"); + } + + if (matchExact("mine,drafts", token)) { + return PageLinks.toChangeQuery("has:draft"); + } + + if (matchPrefix("mine,watched,", token)) { + return PageLinks.toChangeQuery("is:watched status:open", skip(token)); + } + + return null; + } + + private static String legacyAll(final String token) { + if (matchPrefix("all,abandoned,", token)) { + return PageLinks.toChangeQuery("status:abandoned", skip(token)); + } + + if (matchPrefix("all,merged,", token)) { + return PageLinks.toChangeQuery("status:merged", skip(token)); + } + + if (matchPrefix("all,open,", token)) { + return PageLinks.toChangeQuery("status:open", skip(token)); + } + + return null; + } + + private static String legacyProject(final String token) { + if (matchPrefix("project,open,", token)) { + final String s = skip(token); final int c = s.indexOf(','); Project.NameKey proj = Project.NameKey.parse(s.substring(0, c)); - return QueryScreen.forQuery( // + return PageLinks.toChangeQuery( // "status:open " + op("project", proj.get()), // s.substring(c + 1)); } - p = "project,merged,"; - if (token.startsWith(p)) { - final String s = skip(p, token); + if (matchPrefix("project,merged,", token)) { + final String s = skip(token); final int c = s.indexOf(','); Project.NameKey proj = Project.NameKey.parse(s.substring(0, c)); - return QueryScreen.forQuery( // + return PageLinks.toChangeQuery( // "status:merged " + op("project", proj.get()), // s.substring(c + 1)); } - p = "project,abandoned,"; - if (token.startsWith(p)) { - final String s = skip(p, token); + if (matchPrefix("project,abandoned,", token)) { + final String s = skip(token); final int c = s.indexOf(','); Project.NameKey proj = Project.NameKey.parse(s.substring(0, c)); - return QueryScreen.forQuery( // + return PageLinks.toChangeQuery( // "status:abandoned " + op("project", proj.get()), // s.substring(c + 1)); } - return new NotFoundScreen(); + return null; } - private static Screen core(final String token) { - String p; + private static String legacyChange(final String token) { + final String s = skip(token); + final String q = "patchset="; + final String t[] = s.split(",", 2); + if (t.length > 1 && matchPrefix("patchset=", t[1])) { + return PageLinks.toChange(PatchSet.Id.parse(t[0] + "," + skip(t[1]))); + } + return PageLinks.toChange(Change.Id.parse(t[0])); + } - p = "change,"; - if (token.startsWith(p)) { - final String s = skip(p, token); - final String q = "patchset="; - final String t[] = s.split(",", 2); - if (t.length > 1 && t[1].startsWith(q)) { - return new ChangeScreen(PatchSet.Id.parse(t[0] + "," + skip(q, t[1]))); + private static String legacyPatch(String token) { + if (/* LEGACY URL */matchPrefix("patch,sidebyside,", token)) { + return toPatchSideBySide(Patch.Key.parse(skip(token))); + } + + if (/* LEGACY URL */matchPrefix("patch,unified,", token)) { + return toPatchUnified(Patch.Key.parse(skip(token))); + } + + return null; + } + + private static String legacyAdmin(String token) { + if (matchPrefix("admin,group,", token)) { + return "/admin/groups/" + skip(token); + } + + if (matchPrefix("admin,project,", token)) { + String rest = skip(token); + int c = rest.indexOf(','); + String panel; + Project.NameKey k; + if (0 < c) { + panel = rest.substring(c + 1); + k = Project.NameKey.parse(rest.substring(0, c)); + } else { + panel = ProjectScreen.INFO; + k = Project.NameKey.parse(rest); } - return new ChangeScreen(Change.Id.parse(t[0])); + return toProjectAdmin(k, panel); } - p = "dashboard,"; - if (token.startsWith(p)) - return new AccountDashboardScreen(Account.Id.parse(skip(p, token))); - - p = "q,"; - if (token.startsWith(p)) { - final String s = skip(p, token); - final int c = s.indexOf(','); - return new QueryScreen(s.substring(0, c), s.substring(c + 1)); - } - - return new NotFoundScreen(); + return null; } - private static void publish(String token) { + private static String legacySettings(String token) { + int c = token.indexOf(','); + if (0 < c) { + return "/" + token.substring(0, c) + "/" + token.substring(c + 1); + } + return null; + } + + private static void query(final String token) { + final String s = skip(token); + final int c = s.indexOf(','); + Gerrit.display(token, new QueryScreen(s.substring(0, c), s.substring(c + 1))); + } + + private static Screen mine(final String token) { + if (Gerrit.isSignedIn()) { + return new AccountDashboardScreen(Gerrit.getUserAccount().getId()); + + } else { + Screen r = new AccountDashboardScreen(null); + r.setRequiresSignIn(true); + return r; + } + } + + private static void dashboard(final String token) { + Gerrit.display(token, // + new AccountDashboardScreen(Account.Id.parse(skip(token)))); + } + + private static void change(final String token) { + String rest = skip(token); + int c = rest.lastIndexOf(','); + String panel = null; + if (0 <= c) { + panel = rest.substring(c + 1); + rest = rest.substring(0, c); + } + + Change.Id id; + int s = rest.indexOf('/'); + if (0 <= s) { + id = Change.Id.parse(rest.substring(0, s)); + rest = rest.substring(s + 1); + } else { + id = Change.Id.parse(rest); + rest = ""; + } + + if (rest.isEmpty()) { + Gerrit.display(token, panel== null // + ? new ChangeScreen(id) // + : new NotFoundScreen()); + return; + } + + String psIdStr; + s = rest.indexOf('/'); + if (0 <= s) { + psIdStr = rest.substring(0, s); + rest = rest.substring(s + 1); + } else { + psIdStr = rest; + rest = ""; + } + + PatchSet.Id ps = new PatchSet.Id(id, Integer.parseInt(psIdStr)); + if (!rest.isEmpty()) { + Patch.Key p = new Patch.Key(ps, rest); + patch(token, p, 0, null, null, panel); + } else { + if (panel == null) { + Gerrit.display(token, new ChangeScreen(ps)); + } else if ("publish".equals(panel)) { + publish(ps); + } else { + Gerrit.display(token, new NotFoundScreen()); + } + } + } + + private static void publish(final PatchSet.Id ps) { + String token = toPublish(ps); new AsyncSplit(token) { public void onSuccess() { Gerrit.display(token, select()); } private Screen select() { - String p = "change,publish,"; - if (token.startsWith(p)) - return new PublishCommentScreen(PatchSet.Id.parse(skip(p, token))); - return new NotFoundScreen(); + return new PublishCommentScreen(ps); } }.onSuccess(); } @@ -286,32 +403,41 @@ public static void patch(String token, final Patch.Key id, final int patchIndex, final PatchSetDetail patchSetDetail, final PatchTable patchTable) { + patch(token, id, patchIndex, patchSetDetail, patchTable, null); + } + + public static void patch(String token, final Patch.Key id, + final int patchIndex, final PatchSetDetail patchSetDetail, + final PatchTable patchTable, + final String panelType) { GWT.runAsync(new AsyncSplit(token) { public void onSuccess() { Gerrit.display(token, select()); } private Screen select() { - String p; + if (id != null) { + String panel = panelType; + if (panel == null) { + int c = token.lastIndexOf(','); + panel = 0 <= c ? token.substring(c + 1) : ""; + } - p = "patch,sidebyside,"; - if (token.startsWith(p)) { - return new PatchScreen.SideBySide( // - id != null ? id : Patch.Key.parse(skip(p, token)), // - patchIndex, // - patchSetDetail, // - patchTable // - ); - } - - p = "patch,unified,"; - if (token.startsWith(p)) { - return new PatchScreen.Unified( // - id != null ? id : Patch.Key.parse(skip(p, token)), // - patchIndex, // - patchSetDetail, // - patchTable // - ); + if ("".equals(panel)) { + return new PatchScreen.SideBySide( // + id, // + patchIndex, // + patchSetDetail, // + patchTable // + ); + } else if ("unified".equals(panel)) { + return new PatchScreen.Unified( // + id, // + patchIndex, // + patchSetDetail, // + patchTable // + ); + } } return new NotFoundScreen(); @@ -326,59 +452,56 @@ } private Screen select() { - String p; - - if (token.equals(SETTINGS)) { + if (matchExact(SETTINGS, token)) { return new MyProfileScreen(); } - if (token.equals(SETTINGS_PREFERENCES)) { + if (matchExact(SETTINGS_PREFERENCES, token)) { return new MyPreferencesScreen(); } - if (token.equals(SETTINGS_PROJECTS)) { + if (matchExact(SETTINGS_PROJECTS, token)) { return new MyWatchedProjectsScreen(); } - if (token.equals(SETTINGS_CONTACT)) { + if (matchExact(SETTINGS_CONTACT, token)) { return new MyContactInformationScreen(); } - if (token.equals(SETTINGS_SSHKEYS)) { + if (matchExact(SETTINGS_SSHKEYS, token)) { return new MySshKeysScreen(); } - if (token.equals(SETTINGS_WEBIDENT)) { + if (matchExact(SETTINGS_WEBIDENT, token)) { return new MyIdentitiesScreen(); } - if (token.equals(SETTINGS_HTTP_PASSWORD)) { + if (matchExact(SETTINGS_HTTP_PASSWORD, token)) { return new MyPasswordScreen(); } - if (token.equals(SETTINGS_MYGROUPS)) { + if (matchExact(SETTINGS_MYGROUPS, token)) { return new MyGroupsScreen(); } - if (token.equals(SETTINGS_AGREEMENTS) + if (matchExact(SETTINGS_AGREEMENTS, token) && Gerrit.getConfig().isUseContributorAgreements()) { return new MyAgreementsScreen(); } - p = "register,"; - if (token.startsWith(p)) { - return new RegisterScreen(skip(p, token)); - } else if (REGISTER.equals(token)) { + if (matchExact(REGISTER, token) + || matchExact("/register/", token) + || matchExact("register", token)) { return new RegisterScreen(MINE); + } else if (matchPrefix("/register/", token)) { + return new RegisterScreen("/" + skip(token)); } - p = "VE,"; - if (token.startsWith(p)) - return new ValidateEmailScreen(skip(p, token)); + if (matchPrefix("/VE/", token) || matchPrefix("VE,", token)) + return new ValidateEmailScreen(skip(token)); - p = "SignInFailure,"; - if (token.startsWith(p)) { - final String[] args = skip(p, token).split(","); + if (matchPrefix("/SignInFailure,", token)) { + final String[] args = skip(token).split(","); final SignInMode mode = SignInMode.valueOf(args[0]); final String msg = KeyUtil.decode(args[1]); final String to = MINE; @@ -401,12 +524,11 @@ } } - if (SETTINGS_NEW_AGREEMENT.equals(token)) + if (matchExact(SETTINGS_NEW_AGREEMENT, token)) return new NewAgreementScreen(); - p = SETTINGS_NEW_AGREEMENT + ","; - if (token.startsWith(p)) { - return new NewAgreementScreen(skip(p, token)); + if (matchPrefix(SETTINGS_NEW_AGREEMENT + "/", token)) { + return new NewAgreementScreen(skip(token)); } return new NotFoundScreen(); @@ -421,54 +543,72 @@ } private Screen select() { - String p; + if (matchExact(ADMIN_GROUPS, token) + || matchExact("/admin/groups", token)) { + return new GroupListScreen(); + } - p = "admin,group,uuid-"; - if (token.startsWith(p)) - return new AccountGroupScreen(AccountGroup.UUID.parse(skip(p, token))); + if (matchExact(ADMIN_PROJECTS, token) + || matchExact("/admin/projects", token)) { + return new ProjectListScreen(); + } - p = "admin,group,"; - if (token.startsWith(p)) - return new AccountGroupScreen(AccountGroup.Id.parse(skip(p, token))); + if (matchPrefix("/admin/groups/uuid-", token)) + return new AccountGroupScreen(AccountGroup.UUID.parse(skip(token))); - p = "admin,project,"; - if (token.startsWith(p)) { - p = skip(p, token); - final int c = p.indexOf(','); - final Project.NameKey k = Project.NameKey.parse(p.substring(0, c)); - final boolean isWild = k.equals(Gerrit.getConfig().getWildProject()); - p = p.substring(c + 1); + if (matchPrefix("/admin/groups/", token)) + return new AccountGroupScreen(AccountGroup.Id.parse(skip(token))); - if (ProjectScreen.INFO.equals(p)) { + if (matchPrefix("/admin/projects/", token)) { + String rest = skip(token); + int c = rest.lastIndexOf(','); + if (c < 0) { + return new ProjectInfoScreen(Project.NameKey.parse(rest)); + } else if (c == 0) { + return new NotFoundScreen(); + } + + Project.NameKey k = Project.NameKey.parse(rest.substring(0, c)); + String panel = rest.substring(c + 1); + + if (ProjectScreen.INFO.equals(panel)) { return new ProjectInfoScreen(k); } - if (!isWild && ProjectScreen.BRANCH.equals(p)) { + if (ProjectScreen.BRANCH.equals(panel) + && !k.equals(Gerrit.getConfig().getWildProject())) { return new ProjectBranchesScreen(k); } - if (ProjectScreen.ACCESS.equals(p)) { + if (ProjectScreen.ACCESS.equals(panel)) { return new ProjectAccessScreen(k); } return new NotFoundScreen(); } - if (ADMIN_GROUPS.equals(token)) { - return new GroupListScreen(); - } - - if (ADMIN_PROJECTS.equals(token)) { - return new ProjectListScreen(); - } - return new NotFoundScreen(); } }); } - private static String skip(final String prefix, final String in) { - return in.substring(prefix.length()); + private static boolean matchExact(String want, String token) { + return token.equals(want); + } + + private static int prefixlen; + + private static boolean matchPrefix(String want, String token) { + if (token.startsWith(want)) { + prefixlen = want.length(); + return true; + } else { + return false; + } + } + + private static String skip(String token) { + return token.substring(prefixlen); } private static abstract class AsyncSplit implements RunAsyncCallback {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/userpass/UserPassSignInDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/userpass/UserPassSignInDialog.java index dfcddf5..09d41e5 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/userpass/UserPassSignInDialog.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/userpass/UserPassSignInDialog.java
@@ -201,16 +201,13 @@ public void onSuccess(final LoginResult result) { if (result.success) { String to = token; - if (result.isNew && !to.startsWith(PageLinks.REGISTER + ",")) { - to = PageLinks.REGISTER + "," + to; + if (!to.startsWith("/")) { + to = "/" + to; } - - // Unfortunately we no longer support updating the web UI when the - // user signs in. Instead we must force a reload of the page, but - // that isn't easy because we might need to change the anchor. So - // we bounce through a little redirection servlet on the server. - // - Location.replace(Location.getPath() + "login/" + to); + if (result.isNew && !token.startsWith(PageLinks.REGISTER + "/")) { + to = PageLinks.REGISTER + to; + } + Location.replace(Location.getPath() + "login" + to); } else { showError(Util.C.invalidLogin()); enable(true);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java index 6da0ceab..ed0f2f2 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
@@ -14,6 +14,7 @@ package com.google.gerrit.client.changes; +import com.google.gerrit.client.Dispatcher; import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.ScreenLoadCallback; @@ -475,8 +476,7 @@ @Override public void onKeyPress(final KeyPressEvent event) { PatchSet.Id currentPatchSetId = patchSetsBlock.getCurrentPatchSet().getId(); - Gerrit.display("change,publish," + currentPatchSetId.toString(), - new PublishCommentScreen(currentPatchSetId)); + Gerrit.display(Dispatcher.toPublish(currentPatchSetId)); } } }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java index f4862ee..5ce5710 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
@@ -518,8 +518,7 @@ b.addClickHandler(new ClickHandler() { @Override public void onClick(final ClickEvent event) { - Gerrit.display("change,publish," + patchSet.getId().toString(), - new PublishCommentScreen(patchSet.getId())); + Gerrit.display(Dispatcher.toPublish(patchSet.getId())); } }); actionsPanel.add(b);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java index 1192dd0..5540a6d 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java
@@ -37,7 +37,7 @@ private final String query; public QueryScreen(final String encQuery, final String positionToken) { - super("q," + encQuery, positionToken); + super("/q/" + encQuery, positionToken); query = KeyUtil.decode(encQuery); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java index 20589de..96befb0 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
@@ -14,9 +14,9 @@ package com.google.gerrit.client.patches; +import com.google.gerrit.client.Dispatcher; import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.changes.PatchTable; -import com.google.gerrit.client.changes.PublishCommentScreen; import com.google.gerrit.client.changes.Util; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.ui.CommentPanel; @@ -682,8 +682,7 @@ @Override public void onKeyPress(final KeyPressEvent event) { final PatchSet.Id id = patchKey.getParentKey(); - Gerrit.display("change,publish," + id.toString(), - new PublishCommentScreen(id)); + Gerrit.display(Dispatcher.toPublish(id)); } }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/DirectChangeByCommit.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/DirectChangeByCommit.java new file mode 100644 index 0000000..b7709be --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/DirectChangeByCommit.java
@@ -0,0 +1,89 @@ +// Copyright 2011 Google Inc. All Rights Reserved. + +package com.google.gerrit.httpd; + +import com.google.gerrit.common.PageLinks; +import com.google.gerrit.reviewdb.Change; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.query.Predicate; +import com.google.gerrit.server.query.QueryParseException; +import com.google.gerrit.server.query.change.ChangeData; +import com.google.gerrit.server.query.change.ChangeDataSource; +import com.google.gerrit.server.query.change.ChangeQueryBuilder; +import com.google.gerrit.server.query.change.ChangeQueryRewriter; +import com.google.gwtorm.client.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashSet; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Singleton +class DirectChangeByCommit extends HttpServlet { + private static final long serialVersionUID = 1L; + private static final Logger log = + LoggerFactory.getLogger(DirectChangeByCommit.class); + + private final ChangeQueryBuilder.Factory queryBuilder; + private final Provider<ChangeQueryRewriter> queryRewriter; + private final Provider<CurrentUser> currentUser; + + @Inject + DirectChangeByCommit(ChangeQueryBuilder.Factory queryBuilder, + Provider<ChangeQueryRewriter> queryRewriter, + Provider<CurrentUser> currentUser) { + this.queryBuilder = queryBuilder; + this.queryRewriter = queryRewriter; + this.currentUser = currentUser; + } + + @SuppressWarnings("unchecked") + @Override + protected void doGet(final HttpServletRequest req, + final HttpServletResponse rsp) throws IOException { + String query = req.getPathInfo(); + HashSet<Change.Id> ids = new HashSet<Change.Id>(); + try { + ChangeQueryBuilder builder = queryBuilder.create(currentUser.get()); + Predicate<ChangeData> visibleToMe = builder.is_visible(); + Predicate<ChangeData> q = builder.parse(query); + q = Predicate.and(q, builder.sortkey_before("z"), builder.limit(2), visibleToMe); + + ChangeQueryRewriter rewriter = queryRewriter.get(); + Predicate<ChangeData> s = rewriter.rewrite(q); + if (!(s instanceof ChangeDataSource)) { + s = rewriter.rewrite(Predicate.and(builder.status_open(), q)); + } + + if (s instanceof ChangeDataSource) { + for (ChangeData d : ((ChangeDataSource) s).read()) { + ids.add(d.getId()); + } + } + } catch (QueryParseException e) { + log.warn("Received invalid query by URL: /r/" + query, e); + } catch (OrmException e) { + log.warn("Cannot process query by URL: /r/" + query, e); + } + + String token; + if (ids.size() == 1) { + // If exactly one change matches, link to that change. + // TODO Link to a specific patch set, if one matched. + token = PageLinks.toChange(ids.iterator().next()); + + } else { + // Otherwise, link to the query page. + token = PageLinks.toChangeQuery(query); + } + UrlModule.toGerrit(token, req, rsp); + } +}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java index a55c5a7..d859024 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -23,6 +23,7 @@ import com.google.gerrit.httpd.raw.SshInfoServlet; import com.google.gerrit.httpd.raw.StaticServlet; import com.google.gerrit.httpd.raw.ToolServlet; +import com.google.gerrit.reviewdb.Change; import com.google.gwtexpui.server.CacheControlFilter; import com.google.inject.Key; import com.google.inject.Provider; @@ -63,14 +64,13 @@ serve("/all").with(query("status:merged")); serve("/mine").with(screen(PageLinks.MINE)); serve("/open").with(query("status:open")); - serve("/settings").with(screen(PageLinks.SETTINGS)); serve("/watched").with(query("is:watched status:open")); serve("/starred").with(query("is:starred")); - serveRegex( // - "^/([1-9][0-9]*)/?$", // - "^/r/(.+)/?$" // - ).with(changeQuery()); + serveRegex("^/settings/?$").with(screen(PageLinks.SETTINGS)); + serveRegex("^/register/?$").with(screen(PageLinks.REGISTER + "/")); + serveRegex("^/([1-9][0-9]*)/?$").with(directChangeById()); + serveRegex("^/r/(.+)/?$").with(DirectChangeByCommit.class); } private Key<HttpServlet> notFound() { @@ -110,14 +110,19 @@ }); } - private Key<HttpServlet> changeQuery() { + private Key<HttpServlet> directChangeById() { return key(new HttpServlet() { private static final long serialVersionUID = 1L; @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse rsp) throws IOException { - toGerrit(PageLinks.toChangeQuery(req.getPathInfo()), req, rsp); + try { + Change.Id id = Change.Id.parse(req.getPathInfo()); + toGerrit(PageLinks.toChange(id), req, rsp); + } catch (IllegalArgumentException err) { + rsp.sendError(HttpServletResponse.SC_NOT_FOUND); + } } }); } @@ -146,7 +151,7 @@ return srv; } - private void toGerrit(final String target, final HttpServletRequest req, + static void toGerrit(final String target, final HttpServletRequest req, final HttpServletResponse rsp) throws IOException { final StringBuilder url = new StringBuilder(); url.append(req.getContextPath());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java index c3f7de1..b09a50b 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -139,7 +139,6 @@ rdr.append('#'); if (res.isNew()) { rdr.append(PageLinks.REGISTER); - rdr.append(','); } rdr.append(PageLinks.MINE); rsp.sendRedirect(rdr.toString());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java index 58f589a..8e80a0c 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
@@ -130,9 +130,8 @@ final StringBuilder rdr = new StringBuilder(); rdr.append(urlProvider.get()); rdr.append('#'); - if (arsp.isNew() && !token.startsWith(PageLinks.REGISTER + ",")) { + if (arsp.isNew() && !token.startsWith(PageLinks.REGISTER + "/")) { rdr.append(PageLinks.REGISTER); - rdr.append(','); } rdr.append(token); @@ -165,9 +164,6 @@ private String getToken(final HttpServletRequest req) { String token = req.getPathInfo(); - if (token != null && token.startsWith("/")) { - token = token.substring(1); - } if (token == null || token.isEmpty()) { token = PageLinks.MINE; }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertLoginServlet.java index 20b6352..c5fa1ba 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertLoginServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertLoginServlet.java
@@ -63,9 +63,6 @@ private String getToken(final HttpServletRequest req) { String token = req.getPathInfo(); - if (token != null && token.startsWith("/")) { - token = token.substring(1); - } if (token == null || token.isEmpty()) { token = PageLinks.MINE; }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LoginRedirectServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LoginRedirectServlet.java index 7e04358..2726a78 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LoginRedirectServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LoginRedirectServlet.java
@@ -59,7 +59,7 @@ token = getToken(req); } else { final String msg = "Session cookie not available."; - token = "SignInFailure," + SignInMode.SIGN_IN + "," + msg; + token = "/SignInFailure," + SignInMode.SIGN_IN + "," + msg; } final StringBuilder rdr = new StringBuilder(); @@ -75,9 +75,6 @@ private String getToken(final HttpServletRequest req) { String token = req.getPathInfo(); - if (token != null && token.startsWith("/")) { - token = token.substring(1); - } if (token == null || token.isEmpty()) { token = PageLinks.MINE; }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java index 3dce967..02ef765 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -462,16 +462,15 @@ private void callback(final boolean isNew, final HttpServletRequest req, final HttpServletResponse rsp) throws IOException { String token = req.getParameter(P_TOKEN); - if (token == null || token.isEmpty() || token.startsWith("SignInFailure,")) { + if (token == null || token.isEmpty() || token.startsWith("/SignInFailure,")) { token = PageLinks.MINE; } final StringBuilder rdr = new StringBuilder(); rdr.append(urlProvider.get()); rdr.append('#'); - if (isNew && !token.startsWith(PageLinks.REGISTER + ",")) { + if (isNew && !token.startsWith(PageLinks.REGISTER + "/")) { rdr.append(PageLinks.REGISTER); - rdr.append(','); } rdr.append(token); rsp.sendRedirect(rdr.toString());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java index c893f7e..a72fd08 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
@@ -265,9 +265,9 @@ p.print("+branch:$1,n,z};\n"); // wrapped p.print(" } elsif ($h =~ /^refs\\/changes\\/\\d{2}\\/(\\d+)\\/\\d+$/) "); p.print("{\n"); // wrapped - p.print(" $q = qq{#change,$1};\n"); + p.print(" $q = qq{#/c/$1};\n"); p.print(" } else {\n"); - p.print(" $q = qq{#q,$h,n,z};\n"); + p.print(" $q = qq{#/q/$h,n,z};\n"); p.print(" }\n"); p.print(" my $r = qq{$ENV{'GERRIT_CONTEXT_PATH'}$q};\n"); p.print(" push @{$feature{'actions'}{'default'}},\n");
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm index c1de87e..34682f2 100644 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm
@@ -36,7 +36,7 @@ To add a verified email address to your user account, please click on the following link: -$email.gerritUrl#VE,$email.emailRegistrationToken +$email.gerritUrl#/VE/$email.emailRegistrationToken If you have received this mail in error, you do not need to take any action to cancel the account. The account will not be activated, and