From c5dfd60d174a9841e64e4097cecab5aea5c422d0 Mon Sep 17 00:00:00 2001 From: James Moger <james.moger@gitblit.com> Date: Sat, 12 Apr 2014 12:26:17 -0400 Subject: [PATCH] Documentation --- src/main/java/com/gitblit/wicket/pages/TicketPage.java | 395 +++++++++++++++++++++++++++++++------------------------- 1 files changed, 220 insertions(+), 175 deletions(-) diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.java b/src/main/java/com/gitblit/wicket/pages/TicketPage.java index dde31a2..e4bb41f 100644 --- a/src/main/java/com/gitblit/wicket/pages/TicketPage.java +++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.java @@ -54,7 +54,6 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.transport.URIish; import com.gitblit.Constants; import com.gitblit.Constants.AccessPermission; @@ -64,6 +63,7 @@ import com.gitblit.models.PathModel.PathChangeModel; import com.gitblit.models.RegistrantAccessPermission; import com.gitblit.models.RepositoryModel; +import com.gitblit.models.RepositoryUrl; import com.gitblit.models.SubmoduleModel; import com.gitblit.models.TicketModel; import com.gitblit.models.TicketModel.Change; @@ -79,6 +79,7 @@ import com.gitblit.tickets.TicketLabel; import com.gitblit.tickets.TicketMilestone; import com.gitblit.tickets.TicketResponsible; +import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.JGitUtils.MergeStatus; import com.gitblit.utils.MarkdownUtils; @@ -115,7 +116,6 @@ super(params); final UserModel user = GitBlitWebSession.get().getUser() == null ? UserModel.ANONYMOUS : GitBlitWebSession.get().getUser(); - final boolean isAuthenticated = !UserModel.ANONYMOUS.equals(user) && user.isAuthenticated; final RepositoryModel repository = getRepositoryModel(); final String id = WicketUtils.getObject(params); long ticketId = Long.parseLong(id); @@ -265,7 +265,7 @@ Label label = new Label("progress"); WicketUtils.setCssStyle(label, "width:" + progress + "%;"); milestoneProgress.add(label); - WicketUtils.setHtmlTooltip(milestoneProgress, MessageFormat.format("{0} open, {1} closed", open, closed)); + WicketUtils.setHtmlTooltip(milestoneProgress, MessageFormat.format(getString("gb.milestoneProgress"), open, closed)); add(milestoneProgress); } @@ -327,157 +327,171 @@ /* * UPDATE FORM (DISCUSSION TAB) */ - if (isAuthenticated && app().tickets().isAcceptingTicketUpdates(repository)) { - Fragment controls = new Fragment("controls", "controlsFragment", this); + if (user.canEdit(ticket, repository) && app().tickets().isAcceptingTicketUpdates(repository)) { + if (user.canAdmin(ticket, repository) && ticket.isOpen()) { + /* + * OPEN TICKET + */ + Fragment controls = new Fragment("controls", "openControlsFragment", this); - - /* - * STATUS - */ - List<Status> choices = new ArrayList<Status>(); - if (ticket.isProposal()) { - choices.addAll(Arrays.asList(TicketModel.Status.proposalWorkflow)); - } else if (ticket.isBug()) { - choices.addAll(Arrays.asList(TicketModel.Status.bugWorkflow)); - } else { - choices.addAll(Arrays.asList(TicketModel.Status.requestWorkflow)); - } - choices.remove(ticket.status); - - ListDataProvider<Status> workflowDp = new ListDataProvider<Status>(choices); - DataView<Status> statusView = new DataView<Status>("newStatus", workflowDp) { - private static final long serialVersionUID = 1L; - - @Override - public void populateItem(final Item<Status> item) { - SimpleAjaxLink<Status> link = new SimpleAjaxLink<Status>("link", item.getModel()) { - - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - Status status = getModel().getObject(); - Change change = new Change(user.username); - change.setField(Field.status, status); - if (!ticket.isWatching(user.username)) { - change.watch(user.username); - } - TicketModel update = app().tickets().updateTicket(repository, ticket.number, change); - app().tickets().createNotifier().sendMailing(update); - setResponsePage(TicketsPage.class, getPageParameters()); - } - }; - String css = getStatusClass(item.getModel().getObject()); - WicketUtils.setCssClass(link, css); - item.add(link); + /* + * STATUS + */ + List<Status> choices = new ArrayList<Status>(); + if (ticket.isProposal()) { + choices.addAll(Arrays.asList(TicketModel.Status.proposalWorkflow)); + } else if (ticket.isBug()) { + choices.addAll(Arrays.asList(TicketModel.Status.bugWorkflow)); + } else { + choices.addAll(Arrays.asList(TicketModel.Status.requestWorkflow)); } - }; - controls.add(statusView); + choices.remove(ticket.status); - /* - * RESPONSIBLE LIST - */ - Set<String> userlist = new TreeSet<String>(ticket.getParticipants()); - for (RegistrantAccessPermission rp : app().repositories().getUserAccessPermissions(getRepositoryModel())) { - if (rp.permission.atLeast(AccessPermission.PUSH) && !rp.isTeam()) { - userlist.add(rp.registrant); - } - } - List<TicketResponsible> responsibles = new ArrayList<TicketResponsible>(); - if (!StringUtils.isEmpty(ticket.responsible)) { - // exclude the current responsible - userlist.remove(ticket.responsible); - } - for (String username : userlist) { - UserModel u = app().users().getUserModel(username); - if (u != null) { - responsibles.add(new TicketResponsible(u)); - } - } - Collections.sort(responsibles); - responsibles.add(new TicketResponsible(ESC_NIL, "", "")); - ListDataProvider<TicketResponsible> responsibleDp = new ListDataProvider<TicketResponsible>(responsibles); - DataView<TicketResponsible> responsibleView = new DataView<TicketResponsible>("newResponsible", responsibleDp) { - private static final long serialVersionUID = 1L; + ListDataProvider<Status> workflowDp = new ListDataProvider<Status>(choices); + DataView<Status> statusView = new DataView<Status>("newStatus", workflowDp) { + private static final long serialVersionUID = 1L; - @Override - public void populateItem(final Item<TicketResponsible> item) { - SimpleAjaxLink<TicketResponsible> link = new SimpleAjaxLink<TicketResponsible>("link", item.getModel()) { + @Override + public void populateItem(final Item<Status> item) { + SimpleAjaxLink<Status> link = new SimpleAjaxLink<Status>("link", item.getModel()) { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - @Override - public void onClick(AjaxRequestTarget target) { - TicketResponsible responsible = getModel().getObject(); - Change change = new Change(user.username); - change.setField(Field.responsible, responsible.username); - if (!StringUtils.isEmpty(responsible.username)) { - if (!ticket.isWatching(responsible.username)) { - change.watch(responsible.username); + @Override + public void onClick(AjaxRequestTarget target) { + Status status = getModel().getObject(); + Change change = new Change(user.username); + change.setField(Field.status, status); + if (!ticket.isWatching(user.username)) { + change.watch(user.username); } + TicketModel update = app().tickets().updateTicket(repository, ticket.number, change); + app().tickets().createNotifier().sendMailing(update); + setResponsePage(TicketsPage.class, getPageParameters()); } - if (!ticket.isWatching(user.username)) { - change.watch(user.username); - } - TicketModel update = app().tickets().updateTicket(repository, ticket.number, change); - app().tickets().createNotifier().sendMailing(update); - setResponsePage(TicketsPage.class, getPageParameters()); - } - }; - item.add(link); - } - }; - controls.add(responsibleView); + }; + String css = getStatusClass(item.getModel().getObject()); + WicketUtils.setCssClass(link, css); + item.add(link); + } + }; + controls.add(statusView); - /* - * MILESTONE LIST - */ - List<TicketMilestone> milestones = app().tickets().getMilestones(repository, Status.Open); - if (!StringUtils.isEmpty(ticket.milestone)) { - for (TicketMilestone milestone : milestones) { - if (milestone.name.equals(ticket.milestone)) { - milestones.remove(milestone); - break; + /* + * RESPONSIBLE LIST + */ + Set<String> userlist = new TreeSet<String>(ticket.getParticipants()); + for (RegistrantAccessPermission rp : app().repositories().getUserAccessPermissions(getRepositoryModel())) { + if (rp.permission.atLeast(AccessPermission.PUSH) && !rp.isTeam()) { + userlist.add(rp.registrant); } } - } - milestones.add(new TicketMilestone(ESC_NIL)); - ListDataProvider<TicketMilestone> milestoneDp = new ListDataProvider<TicketMilestone>(milestones); - DataView<TicketMilestone> milestoneView = new DataView<TicketMilestone>("newMilestone", milestoneDp) { - private static final long serialVersionUID = 1L; - - @Override - public void populateItem(final Item<TicketMilestone> item) { - SimpleAjaxLink<TicketMilestone> link = new SimpleAjaxLink<TicketMilestone>("link", item.getModel()) { - - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - TicketMilestone milestone = getModel().getObject(); - Change change = new Change(user.username); - if (NIL.equals(milestone.name) || ESC_NIL.equals(milestone.name)) { - change.setField(Field.milestone, ""); - } else { - change.setField(Field.milestone, milestone.name); - } - if (!ticket.isWatching(user.username)) { - change.watch(user.username); - } - TicketModel update = app().tickets().updateTicket(repository, ticket.number, change); - app().tickets().createNotifier().sendMailing(update); - setResponsePage(TicketsPage.class, getPageParameters()); - } - }; - item.add(link); + List<TicketResponsible> responsibles = new ArrayList<TicketResponsible>(); + if (!StringUtils.isEmpty(ticket.responsible)) { + // exclude the current responsible + userlist.remove(ticket.responsible); } - }; - controls.add(milestoneView); + for (String username : userlist) { + UserModel u = app().users().getUserModel(username); + if (u != null) { + responsibles.add(new TicketResponsible(u)); + } + } + Collections.sort(responsibles); + responsibles.add(new TicketResponsible(ESC_NIL, "", "")); + ListDataProvider<TicketResponsible> responsibleDp = new ListDataProvider<TicketResponsible>(responsibles); + DataView<TicketResponsible> responsibleView = new DataView<TicketResponsible>("newResponsible", responsibleDp) { + private static final long serialVersionUID = 1L; - String editHref = urlFor(EditTicketPage.class, params).toString(); - controls.add(new ExternalLink("editLink", editHref, getString("gb.edit"))); + @Override + public void populateItem(final Item<TicketResponsible> item) { + SimpleAjaxLink<TicketResponsible> link = new SimpleAjaxLink<TicketResponsible>("link", item.getModel()) { - add(controls); + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + TicketResponsible responsible = getModel().getObject(); + Change change = new Change(user.username); + change.setField(Field.responsible, responsible.username); + if (!StringUtils.isEmpty(responsible.username)) { + if (!ticket.isWatching(responsible.username)) { + change.watch(responsible.username); + } + } + if (!ticket.isWatching(user.username)) { + change.watch(user.username); + } + TicketModel update = app().tickets().updateTicket(repository, ticket.number, change); + app().tickets().createNotifier().sendMailing(update); + setResponsePage(TicketsPage.class, getPageParameters()); + } + }; + item.add(link); + } + }; + controls.add(responsibleView); + + /* + * MILESTONE LIST + */ + List<TicketMilestone> milestones = app().tickets().getMilestones(repository, Status.Open); + if (!StringUtils.isEmpty(ticket.milestone)) { + for (TicketMilestone milestone : milestones) { + if (milestone.name.equals(ticket.milestone)) { + milestones.remove(milestone); + break; + } + } + } + milestones.add(new TicketMilestone(ESC_NIL)); + ListDataProvider<TicketMilestone> milestoneDp = new ListDataProvider<TicketMilestone>(milestones); + DataView<TicketMilestone> milestoneView = new DataView<TicketMilestone>("newMilestone", milestoneDp) { + private static final long serialVersionUID = 1L; + + @Override + public void populateItem(final Item<TicketMilestone> item) { + SimpleAjaxLink<TicketMilestone> link = new SimpleAjaxLink<TicketMilestone>("link", item.getModel()) { + + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + TicketMilestone milestone = getModel().getObject(); + Change change = new Change(user.username); + if (NIL.equals(milestone.name) || ESC_NIL.equals(milestone.name)) { + change.setField(Field.milestone, ""); + } else { + change.setField(Field.milestone, milestone.name); + } + if (!ticket.isWatching(user.username)) { + change.watch(user.username); + } + TicketModel update = app().tickets().updateTicket(repository, ticket.number, change); + app().tickets().createNotifier().sendMailing(update); + setResponsePage(TicketsPage.class, getPageParameters()); + } + }; + item.add(link); + } + }; + controls.add(milestoneView); + + String editHref = urlFor(EditTicketPage.class, params).toString(); + controls.add(new ExternalLink("editLink", editHref, getString("gb.edit"))); + + add(controls); + } else { + /* + * CLOSED TICKET + */ + Fragment controls = new Fragment("controls", "closedControlsFragment", this); + + String editHref = urlFor(EditTicketPage.class, params).toString(); + controls.add(new ExternalLink("editLink", editHref, getString("gb.edit"))); + + add(controls); + } } else { add(new Label("controls").setVisible(false)); } @@ -507,7 +521,7 @@ WicketUtils.setCssClass(votersCount, "badge badge-info"); } add(votersCount); - if (user.isAuthenticated) { + if (user.isAuthenticated && app().tickets().isAcceptingTicketUpdates(repository)) { Model<String> model; if (ticket.isVoter(user.username)) { model = Model.of(getString("gb.removeVote")); @@ -547,7 +561,7 @@ WicketUtils.setCssClass(watchersCount, "badge badge-info"); } add(watchersCount); - if (user.isAuthenticated) { + if (user.isAuthenticated && app().tickets().isAcceptingTicketUpdates(repository)) { Model<String> model; if (ticket.isWatching(user.username)) { model = Model.of(getString("gb.stopWatching")); @@ -718,15 +732,43 @@ * PATCHSET TAB */ if (currentPatchset == null) { - // no patchset yet, show propose fragment - String repoUrl = getRepositoryUrl(user, repository); - Fragment changeIdFrag = new Fragment("patchset", "proposeFragment", this); - changeIdFrag.add(new Label("proposeInstructions", MarkdownUtils.transformMarkdown(getString("gb.proposeInstructions"))).setEscapeModelStrings(false)); - changeIdFrag.add(new Label("ptWorkflow", MessageFormat.format(getString("gb.proposeWith"), "Barnum"))); - changeIdFrag.add(new Label("ptWorkflowSteps", getProposeWorkflow("propose_pt.md", repoUrl, ticket.number)).setEscapeModelStrings(false)); - changeIdFrag.add(new Label("gitWorkflow", MessageFormat.format(getString("gb.proposeWith"), "Git"))); - changeIdFrag.add(new Label("gitWorkflowSteps", getProposeWorkflow("propose_git.md", repoUrl, ticket.number)).setEscapeModelStrings(false)); - add(changeIdFrag); + // no patchset available + RepositoryUrl repoUrl = getRepositoryUrl(user, repository); + boolean canPropose = repoUrl != null && repoUrl.permission.atLeast(AccessPermission.CLONE) && !UserModel.ANONYMOUS.equals(user); + if (ticket.isOpen() && app().tickets().isAcceptingNewPatchsets(repository) && canPropose) { + // ticket & repo will accept a proposal patchset + // show the instructions for proposing a patchset + Fragment changeIdFrag = new Fragment("patchset", "proposeFragment", this); + changeIdFrag.add(new Label("proposeInstructions", MarkdownUtils.transformMarkdown(getString("gb.proposeInstructions"))).setEscapeModelStrings(false)); + changeIdFrag.add(new Label("ptWorkflow", MessageFormat.format(getString("gb.proposeWith"), "Barnum"))); + changeIdFrag.add(new Label("ptWorkflowSteps", getProposeWorkflow("propose_pt.md", repoUrl.url, ticket.number)).setEscapeModelStrings(false)); + changeIdFrag.add(new Label("gitWorkflow", MessageFormat.format(getString("gb.proposeWith"), "Git"))); + changeIdFrag.add(new Label("gitWorkflowSteps", getProposeWorkflow("propose_git.md", repoUrl.url, ticket.number)).setEscapeModelStrings(false)); + add(changeIdFrag); + } else { + // explain why you can't propose a patchset + Fragment fragment = new Fragment("patchset", "canNotProposeFragment", this); + String reason = ""; + if (ticket.isClosed()) { + reason = getString("gb.ticketIsClosed"); + } else if (repository.isMirror) { + reason = getString("gb.repositoryIsMirror"); + } else if (repository.isFrozen) { + reason = getString("gb.repositoryIsFrozen"); + } else if (!repository.acceptNewPatchsets) { + reason = getString("gb.repositoryDoesNotAcceptPatchsets"); + } else if (!canPropose) { + if (UserModel.ANONYMOUS.equals(user)) { + reason = getString("gb.anonymousCanNotPropose"); + } else { + reason = getString("gb.youDoNotHaveClonePermission"); + } + } else { + reason = getString("gb.serverDoesNotAcceptPatchsets"); + } + fragment.add(new Label("reason", reason)); + add(fragment); + } } else { // show current patchset Fragment patchsetFrag = new Fragment("patchset", "patchsetFragment", this); @@ -895,7 +937,11 @@ break; } sb.append("<tr><th style=\"width:70px;\">"); - sb.append(entry.getKey().name()); + try { + sb.append(getString("gb." + entry.getKey().name())); + } catch (Exception e) { + sb.append(entry.getKey().name()); + } sb.append("</th><td>"); sb.append(value); sb.append("</td></tr>"); @@ -950,7 +996,11 @@ md = md.replace("${ticketId}", "" + ticketId); md = md.replace("${patchset}", "" + 1); md = md.replace("${reviewBranch}", Repository.shortenRefName(PatchsetCommand.getTicketBranch(ticketId))); - md = md.replace("${integrationBranch}", Repository.shortenRefName(getRepositoryModel().HEAD)); + String integrationBranch = Repository.shortenRefName(getRepositoryModel().mergeTo); + if (!StringUtils.isEmpty(ticket.mergeTo)) { + integrationBranch = ticket.mergeTo; + } + md = md.replace("${integrationBranch}", integrationBranch); return MarkdownUtils.transformMarkdown(md); } @@ -1027,7 +1077,7 @@ panel.add(reviewsView); - if (ticket.isOpen() && user.canReviewPatchset(repository)) { + if (ticket.isOpen() && user.canReviewPatchset(repository) && app().tickets().isAcceptingTicketUpdates(repository)) { // can only review open tickets Review myReview = null; for (Change change : ticket.getReviews(currentPatchset)) { @@ -1117,7 +1167,7 @@ String displayPath = entry.path; String path = entry.path; if (entry.isSymlink()) { - RevCommit commit = JGitUtils.getCommit(getRepository(), Constants.R_TICKETS_PATCHSETS + ticket.number); + RevCommit commit = JGitUtils.getCommit(getRepository(), PatchsetCommand.getTicketBranch(ticket.number)); path = JGitUtils.getStringContent(getRepository(), commit.getTree(), path); displayPath = entry.path + " -> " + path; } @@ -1161,8 +1211,8 @@ }; panel.add(pathsView); - addPtReviewInstructions(user, repository, panel); - addGitReviewInstructions(user, repository, panel); + addPtCheckoutInstructions(user, repository, panel); + addGitCheckoutInstructions(user, repository, panel); panel.add(createMergePanel(user, repository)); return panel; @@ -1236,17 +1286,14 @@ return x; } - protected void addGitReviewInstructions(UserModel user, RepositoryModel repository, MarkupContainer panel) { - String repoUrl = getRepositoryUrl(user, repository); - + protected void addGitCheckoutInstructions(UserModel user, RepositoryModel repository, MarkupContainer panel) { panel.add(new Label("gitStep1", MessageFormat.format(getString("gb.stepN"), 1))); panel.add(new Label("gitStep2", MessageFormat.format(getString("gb.stepN"), 2))); String ticketBranch = Repository.shortenRefName(PatchsetCommand.getTicketBranch(ticket.number)); - String reviewBranch = PatchsetCommand.getReviewBranch(ticket.number); - String step1 = MessageFormat.format("git fetch {0} {1}", repoUrl, ticketBranch); - String step2 = MessageFormat.format("git checkout -B {0} FETCH_HEAD", reviewBranch); + String step1 = "git fetch origin"; + String step2 = MessageFormat.format("git checkout {0} && git pull --ff-only\nOR\ngit checkout {0} && git reset --hard origin/{0}", ticketBranch); panel.add(new Label("gitPreStep1", step1)); panel.add(new Label("gitPreStep2", step2)); @@ -1255,7 +1302,7 @@ panel.add(createCopyFragment("gitCopyStep2", step2.replace("\n", " && "))); } - protected void addPtReviewInstructions(UserModel user, RepositoryModel repository, MarkupContainer panel) { + protected void addPtCheckoutInstructions(UserModel user, RepositoryModel repository, MarkupContainer panel) { String step1 = MessageFormat.format("pt checkout {0,number,0}", ticket.number); panel.add(new Label("ptPreStep", step1)); panel.add(createCopyFragment("ptCopyStep", step1)); @@ -1400,7 +1447,6 @@ protected Component getMergeInstructions(UserModel user, RepositoryModel repository, String markupId, String infoKey) { Fragment cmd = new Fragment(markupId, "commandlineMergeFragment", this); cmd.add(new Label("instructions", MessageFormat.format(getString(infoKey), ticket.mergeTo))); - String repoUrl = getRepositoryUrl(user, repository); // git instructions cmd.add(new Label("mergeStep1", MessageFormat.format(getString("gb.stepN"), 1))); @@ -1410,9 +1456,9 @@ String ticketBranch = Repository.shortenRefName(PatchsetCommand.getTicketBranch(ticket.number)); String reviewBranch = PatchsetCommand.getReviewBranch(ticket.number); - String step1 = MessageFormat.format("git checkout -B {0} {1}", reviewBranch, ticket.mergeTo); - String step2 = MessageFormat.format("git pull {0} {1}", repoUrl, ticketBranch); - String step3 = MessageFormat.format("git checkout {0}\ngit merge {1}\ngit push origin {0}", ticket.mergeTo, reviewBranch); + String step1 = MessageFormat.format("git checkout -b {0} {1}", reviewBranch, ticket.mergeTo); + String step2 = MessageFormat.format("git pull origin {0}", ticketBranch); + String step3 = MessageFormat.format("git checkout {0}\ngit merge {1}\ngit push origin {0}\ngit branch -d {1}", ticket.mergeTo, reviewBranch); cmd.add(new Label("mergePreStep1", step1)); cmd.add(new Label("mergePreStep2", step2)); @@ -1436,15 +1482,14 @@ * @param repository * @return the primary repository url */ - protected String getRepositoryUrl(UserModel user, RepositoryModel repository) { + protected RepositoryUrl getRepositoryUrl(UserModel user, RepositoryModel repository) { HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest(); - String primaryurl = app().gitblit().getRepositoryUrls(req, user, repository).get(0).url; - String url = primaryurl; - try { - url = new URIish(primaryurl).setUser(null).toString(); - } catch (Exception e) { + List<RepositoryUrl> urls = app().gitblit().getRepositoryUrls(req, user, repository); + if (ArrayUtils.isEmpty(urls)) { + return null; } - return url; + RepositoryUrl primary = urls.get(0); + return primary; } /** -- Gitblit v1.9.1