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