James Moger
2012-09-10 fabe060d3a435f116128851f828e35c2af5fde67
commit | author | age
f13c4c 1 /*
JM 2  * Copyright 2011 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
1f9dae 16 package com.gitblit.wicket.pages;
5fe7df 17
85c2e6 18 import java.io.Serializable;
bc9d4a 19 import java.text.MessageFormat;
3e087a 20 import java.util.ArrayList;
JM 21 import java.util.Arrays;
eb870f 22 import java.util.HashMap;
9e5bee 23 import java.util.LinkedHashMap;
eb870f 24 import java.util.LinkedHashSet;
5fe7df 25 import java.util.List;
JM 26 import java.util.Map;
eb870f 27 import java.util.Set;
5fe7df 28
98ce17 29 import org.apache.wicket.Component;
5fe7df 30 import org.apache.wicket.PageParameters;
JM 31 import org.apache.wicket.markup.html.basic.Label;
3e087a 32 import org.apache.wicket.markup.html.form.DropDownChoice;
JM 33 import org.apache.wicket.markup.html.form.TextField;
c22722 34 import org.apache.wicket.markup.html.link.ExternalLink;
98ce17 35 import org.apache.wicket.markup.html.panel.Fragment;
3e087a 36 import org.apache.wicket.model.IModel;
JM 37 import org.apache.wicket.model.Model;
77e1d2 38 import org.apache.wicket.protocol.http.RequestUtils;
JM 39 import org.apache.wicket.request.target.basic.RedirectRequestTarget;
9bc17d 40 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
98ce17 41 import org.eclipse.jgit.lib.PersonIdent;
5fe7df 42 import org.eclipse.jgit.lib.Repository;
JM 43 import org.eclipse.jgit.revwalk.RevCommit;
44
33d8d8 45 import com.gitblit.Constants;
fc948c 46 import com.gitblit.GitBlit;
87cc1e 47 import com.gitblit.Keys;
11924d 48 import com.gitblit.PagesServlet;
c22722 49 import com.gitblit.SyndicationServlet;
1f9dae 50 import com.gitblit.models.RepositoryModel;
eb870f 51 import com.gitblit.models.SubmoduleModel;
4e1930 52 import com.gitblit.utils.ArrayUtils;
5fe7df 53 import com.gitblit.utils.JGitUtils;
f5d0ad 54 import com.gitblit.utils.StringUtils;
793f76 55 import com.gitblit.utils.TicgitUtils;
1f9dae 56 import com.gitblit.wicket.GitBlitWebSession;
9e5bee 57 import com.gitblit.wicket.PageRegistration;
11924d 58 import com.gitblit.wicket.PageRegistration.OtherPageLink;
ed295f 59 import com.gitblit.wicket.SessionlessForm;
1f9dae 60 import com.gitblit.wicket.WicketUtils;
JM 61 import com.gitblit.wicket.panels.LinkPanel;
9e5bee 62 import com.gitblit.wicket.panels.NavigationPanel;
5fe7df 63 import com.gitblit.wicket.panels.RefsPanel;
JM 64
65 public abstract class RepositoryPage extends BasePage {
66
13a3f5 67     protected final String projectName;
5fe7df 68     protected final String repositoryName;
bc9d4a 69     protected final String objectId;
eb870f 70     
2a7306 71     private transient Repository r;
bc9d4a 72
2a7306 73     private RepositoryModel m;
5fe7df 74
eb870f 75     private Map<String, SubmoduleModel> submodules;
JM 76     
9e5bee 77     private final Map<String, PageRegistration> registeredPages;
3e087a 78
cebf45 79     public RepositoryPage(PageParameters params) {
5fe7df 80         super(params);
7d35e2 81         repositoryName = WicketUtils.getRepositoryName(params);
13a3f5 82         if (repositoryName.indexOf('/') > -1) {
JM 83             projectName = repositoryName.substring(0, repositoryName.indexOf('/'));
84         } else {
85             projectName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main");
86         }
7d35e2 87         objectId = WicketUtils.getObject(params);
eb870f 88         
bc9d4a 89         if (StringUtils.isEmpty(repositoryName)) {
6caa93 90             error(MessageFormat.format(getString("gb.repositoryNotSpecifiedFor"), getPageName()), true);
bc9d4a 91         }
JM 92
82df52 93         if (!getRepositoryModel().hasCommits) {
JM 94             setResponsePage(EmptyRepositoryPage.class, params);
95         }
96
9e5bee 97         // register the available page links for this page and user
JM 98         registeredPages = registerPages();
2a7306 99
3e087a 100         // standard page links
9e5bee 101         List<PageRegistration> pages = new ArrayList<PageRegistration>(registeredPages.values());
JM 102         NavigationPanel navigationPanel = new NavigationPanel("navPanel", getClass(), pages);
103         add(navigationPanel);
8c9a20 104
c22722 105         add(new ExternalLink("syndication", SyndicationServlet.asLink(getRequest()
JM 106                 .getRelativePathPrefixToContextRoot(), repositoryName, null, 0)));
3e087a 107
JM 108         // add floating search form
109         SearchForm searchForm = new SearchForm("searchForm", repositoryName);
110         add(searchForm);
111         searchForm.setTranslatedAttributes();
bc9d4a 112
3e087a 113         // set stateless page preference
5fe7df 114         setStatelessHint(true);
3e087a 115     }
9e5bee 116
JM 117     private Map<String, PageRegistration> registerPages() {
118         PageParameters params = null;
119         if (!StringUtils.isEmpty(repositoryName)) {
120             params = WicketUtils.newRepositoryParameter(repositoryName);
121         }
122         Map<String, PageRegistration> pages = new LinkedHashMap<String, PageRegistration>();
123
124         // standard links
125         pages.put("repositories", new PageRegistration("gb.repositories", RepositoriesPage.class));
13a3f5 126         pages.put("project", new PageRegistration("gb.project", ProjectPage.class, WicketUtils.newProjectParameter(projectName)));
9e5bee 127         pages.put("summary", new PageRegistration("gb.summary", SummaryPage.class, params));
JM 128         pages.put("log", new PageRegistration("gb.log", LogPage.class, params));
129         pages.put("branches", new PageRegistration("gb.branches", BranchesPage.class, params));
130         pages.put("tags", new PageRegistration("gb.tags", TagsPage.class, params));
131         pages.put("tree", new PageRegistration("gb.tree", TreePage.class, params));
132
133         // conditional links
134         Repository r = getRepository();
135         RepositoryModel model = getRepositoryModel();
136
137         // per-repository extra page links
138         if (model.useTickets && TicgitUtils.getTicketsBranch(r) != null) {
139             pages.put("tickets", new PageRegistration("gb.tickets", TicketsPage.class, params));
140         }
141         if (model.useDocs) {
142             pages.put("docs", new PageRegistration("gb.docs", DocsPage.class, params));
143         }
11924d 144         if (JGitUtils.getPagesBranch(r) != null) {
JM 145             OtherPageLink pagesLink = new OtherPageLink("gb.pages", PagesServlet.asLink(
146                     getRequest().getRelativePathPrefixToContextRoot(), repositoryName, null));
147             pages.put("pages", pagesLink);
148         }
149
9e5bee 150         // Conditionally add edit link
JM 151         final boolean showAdmin;
152         if (GitBlit.getBoolean(Keys.web.authenticateAdminPages, true)) {
153             boolean allowAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, false);
154             showAdmin = allowAdmin && GitBlitWebSession.get().canAdmin();
155         } else {
156             showAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, false);
157         }
158         if (showAdmin
159                 || GitBlitWebSession.get().isLoggedIn()
160                 && (model.owner != null && model.owner.equalsIgnoreCase(GitBlitWebSession.get()
d97e52 161                         .getUsername()))) {
9e5bee 162             pages.put("edit", new PageRegistration("gb.edit", EditRepositoryPage.class, params));
JM 163         }
164         return pages;
165     }
166
1f5915 167     @Override
11924d 168     protected void setupPage(String repositoryName, String pageName) {
JM 169         add(new LinkPanel("repositoryName", null, StringUtils.stripDotGit(repositoryName),
170                 SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryName)));
62cec2 171         add(new Label("pageName", pageName).setRenderBodyOnly(true));
ccab3a 172         if (getRepositoryModel().isBare) {
JM 173             add(new Label("workingCopy").setVisible(false));
174         } else {
3cc6e2 175             Fragment fragment = new Fragment("workingCopy", "workingCopyFragment", this);
ccab3a 176             Label lbl = new Label("workingCopy", getString("gb.workingCopy"));
JM 177             WicketUtils.setHtmlTooltip(lbl,  getString("gb.workingCopyWarning"));
3cc6e2 178             fragment.add(lbl);
JM 179             add(fragment);
ccab3a 180         }
1f5915 181
JM 182         super.setupPage(repositoryName, pageName);
8c9a20 183     }
JM 184
185     protected void addSyndicationDiscoveryLink() {
186         add(WicketUtils.syndicationDiscoveryLink(SyndicationServlet.getTitle(repositoryName,
187                 objectId), SyndicationServlet.asLink(getRequest()
188                 .getRelativePathPrefixToContextRoot(), repositoryName, objectId, 0)));
5fe7df 189     }
JM 190
191     protected Repository getRepository() {
192         if (r == null) {
f5d0ad 193             Repository r = GitBlit.self().getRepository(repositoryName);
7ba0ec 194             if (r == null) {
6caa93 195                 error(getString("gb.canNotLoadRepository") + " " + repositoryName, true);
7ba0ec 196                 return null;
JM 197             }
198             this.r = r;
5fe7df 199         }
JM 200         return r;
201     }
bc9d4a 202
f97bf0 203     protected RepositoryModel getRepositoryModel() {
JM 204         if (m == null) {
2a7306 205             RepositoryModel model = GitBlit.self().getRepositoryModel(
JM 206                     GitBlitWebSession.get().getUser(), repositoryName);
dfb889 207             if (model == null) {
d97e52 208                 if (GitBlit.self().hasRepository(repositoryName)) {
JM 209                     // has repository, but unauthorized
210                     authenticationError(getString("gb.unauthorizedAccessForRepository") + " " + repositoryName);
211                 } else {
212                     // does not have repository
213                     error(getString("gb.canNotLoadRepository") + " " + repositoryName, true);
214                 }
00afd7 215                 return null;
dfb889 216             }
JM 217             m = model;
f97bf0 218         }
JM 219         return m;
bc9d4a 220     }
dfb889 221
bc9d4a 222     protected RevCommit getCommit() {
JM 223         RevCommit commit = JGitUtils.getCommit(r, objectId);
224         if (commit == null) {
6caa93 225             error(MessageFormat.format(getString("gb.failedToFindCommit"),
2a7306 226                     objectId, repositoryName, getPageName()), true);
bc9d4a 227         }
eb870f 228         getSubmodules(commit);
bc9d4a 229         return commit;
f97bf0 230     }
eb870f 231     
JM 232     private Map<String, SubmoduleModel> getSubmodules(RevCommit commit) {    
233         if (submodules == null) {
234             submodules = new HashMap<String, SubmoduleModel>();
235             for (SubmoduleModel model : JGitUtils.getSubmodules(r, commit.getTree())) {
236                 submodules.put(model.path, model);
237             }
238         }
239         return submodules;
240     }
241     
242     protected Map<String, SubmoduleModel> getSubmodules() {
243         return submodules;
244     }
245     
246     protected SubmoduleModel getSubmodule(String path) {
247         SubmoduleModel model = submodules.get(path);
248         if (model == null) {
249             // undefined submodule?!
250             model = new SubmoduleModel(path.substring(path.lastIndexOf('/') + 1), path, path);
251             model.hasSubmodule = false;
252             model.gitblitPath = model.name;
253             return model;
254         } else {
255             // extract the repository name from the clone url
256             List<String> patterns = GitBlit.getStrings(Keys.git.submoduleUrlPatterns);
257             String submoduleName = StringUtils.extractRepositoryPath(model.url, patterns.toArray(new String[0]));
258             
259             // determine the current path for constructing paths relative
260             // to the current repository
261             String currentPath = "";
262             if (repositoryName.indexOf('/') > -1) {
263                 currentPath = repositoryName.substring(0, repositoryName.lastIndexOf('/') + 1);
264             }
265
266             // try to locate the submodule repository
267             // prefer bare to non-bare names
268             List<String> candidates = new ArrayList<String>();
269
270             // relative
271             candidates.add(currentPath + StringUtils.stripDotGit(submoduleName));
272             candidates.add(candidates.get(candidates.size() - 1) + ".git");
273
274             // relative, no subfolder
275             if (submoduleName.lastIndexOf('/') > -1) {
276                 String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
277                 candidates.add(currentPath + StringUtils.stripDotGit(name));
278                 candidates.add(currentPath + candidates.get(candidates.size() - 1) + ".git");
279             }
280
281             // absolute
282             candidates.add(StringUtils.stripDotGit(submoduleName));
283             candidates.add(candidates.get(candidates.size() - 1) + ".git");
284
285             // absolute, no subfolder
286             if (submoduleName.lastIndexOf('/') > -1) {
287                 String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
288                 candidates.add(StringUtils.stripDotGit(name));
289                 candidates.add(candidates.get(candidates.size() - 1) + ".git");
290             }
291
292             // create a unique, ordered set of candidate paths
293             Set<String> paths = new LinkedHashSet<String>(candidates);
294             for (String candidate : paths) {
295                 if (GitBlit.self().hasRepository(candidate)) {
296                     model.hasSubmodule = true;
297                     model.gitblitPath = candidate;
298                     return model;
299                 }
300             }
301             
302             // we do not have a copy of the submodule, but we need a path
303             model.gitblitPath = candidates.get(0);
304             return model;
305         }        
306     }
5fe7df 307
008322 308     protected String getShortObjectId(String objectId) {
JM 309         return objectId.substring(0, 8);
310     }
311
5fe7df 312     protected void addRefs(Repository r, RevCommit c) {
155bf7 313         add(new RefsPanel("refsPanel", repositoryName, c, JGitUtils.getAllRefs(r)));
5fe7df 314     }
JM 315
316     protected void addFullText(String wicketId, String text, boolean substituteRegex) {
227736 317         String html = StringUtils.escapeForHtml(text, true);
5fe7df 318         if (substituteRegex) {
8c9a20 319             html = GitBlit.self().processCommitMessage(repositoryName, text);
f339f5 320         } else {
227736 321             html = StringUtils.breakLinesForHtml(html);
f339f5 322         }
JM 323         add(new Label(wicketId, html).setEscapeModelStrings(false));
5fe7df 324     }
1e47ab 325
cebf45 326     protected abstract String getPageName();
5fe7df 327
2a7306 328     protected Component createPersonPanel(String wicketId, PersonIdent identity,
33d8d8 329             Constants.SearchType searchType) {
e11f48 330         String name = identity == null ? "" : identity.getName();
JM 331         String address = identity == null ? "" : identity.getEmailAddress();
2a7306 332         boolean showEmail = GitBlit.getBoolean(Keys.web.showEmailAddresses, false);
e11f48 333         if (!showEmail || StringUtils.isEmpty(name) || StringUtils.isEmpty(address)) {
JM 334             String value = name;
98ce17 335             if (StringUtils.isEmpty(value)) {
f5d0ad 336                 if (showEmail) {
e11f48 337                     value = address;
f5d0ad 338                 } else {
JM 339                     value = getString("gb.missingUsername");
340                 }
98ce17 341             }
JM 342             Fragment partial = new Fragment(wicketId, "partialPersonIdent", this);
4e1930 343             LinkPanel link = new LinkPanel("personName", "list", value, GitSearchPage.class,
2a7306 344                     WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType));
98ce17 345             setPersonSearchTooltip(link, value, searchType);
JM 346             partial.add(link);
347             return partial;
348         } else {
349             Fragment fullPerson = new Fragment(wicketId, "fullPersonIdent", this);
4e1930 350             LinkPanel nameLink = new LinkPanel("personName", "list", name, GitSearchPage.class,
e11f48 351                     WicketUtils.newSearchParameter(repositoryName, objectId, name, searchType));
JM 352             setPersonSearchTooltip(nameLink, name, searchType);
98ce17 353             fullPerson.add(nameLink);
3e087a 354
227736 355             LinkPanel addressLink = new LinkPanel("personAddress", "hidden-phone list", "<" + address + ">",
4e1930 356                     GitSearchPage.class, WicketUtils.newSearchParameter(repositoryName, objectId,
e11f48 357                             address, searchType));
JM 358             setPersonSearchTooltip(addressLink, address, searchType);
98ce17 359             fullPerson.add(addressLink);
JM 360             return fullPerson;
361         }
362     }
3e087a 363
11924d 364     protected void setPersonSearchTooltip(Component component, String value,
JM 365             Constants.SearchType searchType) {
33d8d8 366         if (searchType.equals(Constants.SearchType.AUTHOR)) {
1e8390 367             WicketUtils.setHtmlTooltip(component, getString("gb.searchForAuthor") + " " + value);
33d8d8 368         } else if (searchType.equals(Constants.SearchType.COMMITTER)) {
1e8390 369             WicketUtils.setHtmlTooltip(component, getString("gb.searchForCommitter") + " " + value);
98ce17 370         }
JM 371     }
3e087a 372
9bc17d 373     protected void setChangeTypeTooltip(Component container, ChangeType type) {
JM 374         switch (type) {
375         case ADD:
1e8390 376             WicketUtils.setHtmlTooltip(container, getString("gb.addition"));
9bc17d 377             break;
JM 378         case COPY:
379         case RENAME:
1e8390 380             WicketUtils.setHtmlTooltip(container, getString("gb.rename"));
9bc17d 381             break;
JM 382         case DELETE:
1e8390 383             WicketUtils.setHtmlTooltip(container, getString("gb.deletion"));
9bc17d 384             break;
JM 385         case MODIFY:
1e8390 386             WicketUtils.setHtmlTooltip(container, getString("gb.modification"));
9bc17d 387             break;
JM 388         }
389     }
3e087a 390
1e47ab 391     @Override
JM 392     protected void onBeforeRender() {
393         // dispose of repository object
394         if (r != null) {
395             r.close();
396             r = null;
397         }
398         // setup page header and footer
cebf45 399         setupPage(repositoryName, "/ " + getPageName());
1e47ab 400         super.onBeforeRender();
5fe7df 401     }
JM 402
403     protected PageParameters newRepositoryParameter() {
698678 404         return WicketUtils.newRepositoryParameter(repositoryName);
5fe7df 405     }
7ba0ec 406
5fe7df 407     protected PageParameters newCommitParameter() {
ef5c58 408         return WicketUtils.newObjectParameter(repositoryName, objectId);
5fe7df 409     }
7ba0ec 410
5fe7df 411     protected PageParameters newCommitParameter(String commitId) {
ef5c58 412         return WicketUtils.newObjectParameter(repositoryName, commitId);
5fe7df 413     }
JM 414
ed295f 415     private class SearchForm extends SessionlessForm<Void> implements Serializable {
3e087a 416         private static final long serialVersionUID = 1L;
JM 417
418         private final String repositoryName;
419
420         private final IModel<String> searchBoxModel = new Model<String>("");
421
11924d 422         private final IModel<Constants.SearchType> searchTypeModel = new Model<Constants.SearchType>(
JM 423                 Constants.SearchType.COMMIT);
3e087a 424
JM 425         public SearchForm(String id, String repositoryName) {
ed295f 426             super(id, RepositoryPage.this.getClass(), RepositoryPage.this.getPageParameters());
3e087a 427             this.repositoryName = repositoryName;
11924d 428             DropDownChoice<Constants.SearchType> searchType = new DropDownChoice<Constants.SearchType>(
JM 429                     "searchType", Arrays.asList(Constants.SearchType.values()));
3e087a 430             searchType.setModel(searchTypeModel);
2a7306 431             add(searchType.setVisible(GitBlit.getBoolean(Keys.web.showSearchTypeSelection, false)));
3e087a 432             TextField<String> searchBox = new TextField<String>("searchBox", searchBoxModel);
JM 433             add(searchBox);
434         }
bc9d4a 435
JM 436         void setTranslatedAttributes() {
3e087a 437             WicketUtils.setHtmlTooltip(get("searchType"), getString("gb.searchTypeTooltip"));
9e5bee 438             WicketUtils.setHtmlTooltip(get("searchBox"),
JM 439                     MessageFormat.format(getString("gb.searchTooltip"), repositoryName));
bc9d4a 440             WicketUtils.setInputPlaceholder(get("searchBox"), getString("gb.search"));
3e087a 441         }
JM 442
443         @Override
444         public void onSubmit() {
33d8d8 445             Constants.SearchType searchType = searchTypeModel.getObject();
3e087a 446             String searchString = searchBoxModel.getObject();
8c5d72 447             if (searchString == null) {
JM 448                 return;
449             }
33d8d8 450             for (Constants.SearchType type : Constants.SearchType.values()) {
3e087a 451                 if (searchString.toLowerCase().startsWith(type.name().toLowerCase() + ":")) {
JM 452                     searchType = type;
2a7306 453                     searchString = searchString.substring(type.name().toLowerCase().length() + 1)
JM 454                             .trim();
3e087a 455                     break;
JM 456                 }
457             }
4e1930 458             Class<? extends BasePage> searchPageClass = GitSearchPage.class;
JM 459             RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
7db092 460             if (GitBlit.getBoolean(Keys.web.allowLuceneIndexing, true)
JM 461                     && !ArrayUtils.isEmpty(model.indexedBranches)) {
4e1930 462                 // this repository is Lucene-indexed
JM 463                 searchPageClass = LuceneSearchPage.class;
464             }
77e1d2 465             // use an absolute url to workaround Wicket-Tomcat problems with
JM 466             // mounted url parameters (issue-111)
467             PageParameters params = WicketUtils.newSearchParameter(repositoryName, null, searchString, searchType);
468             String relativeUrl = urlFor(searchPageClass, params).toString();
469             String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
470             getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
3e087a 471         }
JM 472     }
5fe7df 473 }