James Moger
2013-05-23 722e2325300c7e5d73a93416e28c20354556fec4
Implemented compare page for branch/tag/manual diffs (issue-75, issue-133)
2 files added
5 files modified
374 ■■■■■ changed files
src/main/java/com/gitblit/wicket/GitBlitWebApp.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/WicketUtils.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ComparePage.html 79 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ComparePage.java 277 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java 1 ●●●● patch | view | raw | blame | history
src/main/resources/gitblit.css 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebApp.java
@@ -36,6 +36,7 @@
import com.gitblit.wicket.pages.BranchesPage;
import com.gitblit.wicket.pages.CommitDiffPage;
import com.gitblit.wicket.pages.CommitPage;
import com.gitblit.wicket.pages.ComparePage;
import com.gitblit.wicket.pages.DocsPage;
import com.gitblit.wicket.pages.FederationRegistrationPage;
import com.gitblit.wicket.pages.ForkPage;
@@ -106,6 +107,7 @@
        mount("/raw", RawPage.class, "r", "h", "f");
        mount("/blobdiff", BlobDiffPage.class, "r", "h", "f");
        mount("/commitdiff", CommitDiffPage.class, "r", "h");
        mount("/compare", ComparePage.class, "r", "h");
        mount("/patch", PatchPage.class, "r", "h", "f");
        mount("/history", HistoryPage.class, "r", "h", "f");
        mount("/search", GitSearchPage.class);
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -454,4 +454,8 @@
gb.overview = overview
gb.home = home
gb.monthlyActivity = monthly activity
gb.myProfile = my profile
gb.myProfile = my profile
gb.compare = compare
gb.manual = manual
gb.from = from
gb.to = to
src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -319,6 +319,10 @@
        return new PageParameters("r=" + repositoryName + ",h=" + objectId);
    }
    public static PageParameters newRangeParameter(String repositoryName, String startRange, String endRange) {
        return new PageParameters("r=" + repositoryName + ",h=" + startRange + ".." + endRange);
    }
    public static PageParameters newPathParameter(String repositoryName, String objectId,
            String path) {
        if (StringUtils.isEmpty(path)) {
src/main/java/com/gitblit/wicket/pages/ComparePage.html
New file
@@ -0,0 +1,79 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
      xml:lang="en"
      lang="en">
<body>
<wicket:extend>
    <div class="tabbable">
        <ul class="nav nav-pills">
            <li class="active"><a href="#refs" data-toggle="tab"><wicket:message key="gb.refs"></wicket:message></a></li>
            <li><a href="#ids" data-toggle="tab"><wicket:message key="gb.manual"></wicket:message></a></li>
        </ul>
        <div class="tab-content">
            <div class="tab-pane active" id="refs">
                <form wicket:id="compareRefsForm" class="form-inline">
                    <select wicket:id="fromRef" class="span3" />
                    <select wicket:id="toRef" class="span3" />
                    <button class="btn" type="submit"><wicket:message key="gb.compare"></wicket:message></button>
                </form>
            </div>
            <div class="tab-pane" id="ids">
                <form wicket:id="compareIdsForm" class="form-inline">
                    <input wicket:id="fromId" type="text" class="span3" />
                    <input wicket:id="toId" type="text" class="span3" />
                    <button class="btn" type="submit"><wicket:message key="gb.compare"></wicket:message></button>
                </form>
            </div>
        </div>
    </div>
    <div wicket:id="comparison"></div>
    <wicket:fragment wicket:id="comparisonFragment">
        <div class="tabbable">
            <ul class="nav nav-tabs">
                <li class="active"><a href="#commits" data-toggle="tab"><wicket:message key="gb.commits"></wicket:message></a></li>
                <li><a href="#diff" data-toggle="tab"><wicket:message key="gb.diff"></wicket:message></a></li>
            </ul>
            <div class="tab-content">
                <div class="tab-pane active" id="commits">
                    <!-- commit list -->
                    <div wicket:id="commitList">[commit list]</div>
                </div>
                <div class="tab-pane" id="diff">
<!--                     <div class="page_nav2"> -->
<!--                         <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> -->
<!--                     </div>     -->
                    <!-- changed paths -->
                    <div>
                        <!-- commit legend -->
                        <div class="hidden-phone" style="text-align:right;" wicket:id="commitLegend"></div>
                        <div class="header"><i class="icon-file"></i> <wicket:message key="gb.changedFiles">[changed files]</wicket:message></div>
                    </div>
                    <table class="pretty">
                        <tr wicket:id="changedPath">
                            <td class="changeType"><span wicket:id="changeType">[change type]</span></td>
                            <td class="path"><span wicket:id="pathName">[commit path]</span></td>
                            <td class="hidden-phone rightAlign">
                                <span class="link">
                                    <a wicket:id="patch"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a>
                                </span>
                            </td>
                        </tr>
                    </table>
                    <!--  diff content -->
                    <pre style="padding-top:10px;" wicket:id="diffText">[diff text]</pre>
                </div>
            </div>
        </div>
    </wicket:fragment>
</wicket:extend>
</body>
</html>
src/main/java/com/gitblit/wicket/pages/ComparePage.java
New file
@@ -0,0 +1,277 @@
/*
 * Copyright 2013 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.wicket.pages;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.protocol.http.RequestUtils;
import org.apache.wicket.request.target.basic.RedirectRequestTarget;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.RefModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.SubmoduleModel;
import com.gitblit.utils.DiffUtils;
import com.gitblit.utils.DiffUtils.DiffOutputType;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.SessionlessForm;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitLegendPanel;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.LogPanel;
/**
 * The compare page allows you to compare two branches, tags, or hash ids.
 *
 * @author James Moger
 *
 */
public class ComparePage extends RepositoryPage {
    IModel<String> fromCommitId = new Model<String>("");
    IModel<String> toCommitId = new Model<String>("");
    IModel<String> fromRefId = new Model<String>("");
    IModel<String> toRefId = new Model<String>("");
    public ComparePage(PageParameters params) {
        super(params);
        Repository r = getRepository();
        RepositoryModel repository = getRepositoryModel();
        if (StringUtils.isEmpty(objectId)) {
            // seleciton form
            add(new Label("comparison").setVisible(false));
        } else {
            // active comparison
            Fragment comparison = new Fragment("comparison", "comparisonFragment", this);
            add(comparison);
            DiffOutputType diffType = DiffOutputType.forName(GitBlit.getString(Keys.web.diffStyle,
                    DiffOutputType.GITBLIT.name()));
            RevCommit fromCommit;
            RevCommit toCommit;
            String[] parts = objectId.split("\\.\\.");
            if (parts[0].startsWith("refs/") && parts[1].startsWith("refs/")) {
                // set the ref models
                fromRefId.setObject(parts[0]);
                toRefId.setObject(parts[1]);
                fromCommit = getCommit(r, fromRefId.getObject());
                toCommit = getCommit(r, toRefId.getObject());
            } else {
                // set the id models
                fromCommitId.setObject(parts[0]);
                toCommitId.setObject(parts[1]);
                fromCommit = getCommit(r, fromCommitId.getObject());
                toCommit = getCommit(r, toCommitId.getObject());
            }
            final String startId = fromCommit.getId().getName();
            final String endId = toCommit.getId().getName();
            // commit ids
            fromCommitId.setObject(startId);
            toCommitId.setObject(endId);
            String diff = DiffUtils.getDiff(r, fromCommit, toCommit, diffType);
            // compare page links
//            comparison.add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
//                    WicketUtils.newRangeParameter(repositoryName, fromCommitId.toString(), toCommitId.getObject())));
            // display list of commits
            comparison.add(new LogPanel("commitList", repositoryName, objectId, r, 0, 0, repository.showRemoteBranches));
            // changed paths list
            List<PathChangeModel> paths = JGitUtils.getFilesInRange(r, fromCommit, toCommit);
            comparison.add(new CommitLegendPanel("commitLegend", paths));
            ListDataProvider<PathChangeModel> pathsDp = new ListDataProvider<PathChangeModel>(paths);
            DataView<PathChangeModel> pathsView = new DataView<PathChangeModel>("changedPath", pathsDp) {
                private static final long serialVersionUID = 1L;
                int counter;
                public void populateItem(final Item<PathChangeModel> item) {
                    final PathChangeModel entry = item.getModelObject();
                    Label changeType = new Label("changeType", "");
                    WicketUtils.setChangeTypeCssClass(changeType, entry.changeType);
                    setChangeTypeTooltip(changeType, entry.changeType);
                    item.add(changeType);
                    boolean hasSubmodule = false;
                    String submodulePath = null;
                    if (entry.isTree()) {
                        // tree
                        item.add(new LinkPanel("pathName", null, entry.path, TreePage.class,
                                WicketUtils
                                .newPathParameter(repositoryName, endId, entry.path)));
                    } else if (entry.isSubmodule()) {
                        // submodule
                        String submoduleId = entry.objectId;
                        SubmoduleModel submodule = getSubmodule(entry.path);
                        submodulePath = submodule.gitblitPath;
                        hasSubmodule = submodule.hasSubmodule;
                        // add relative link
                        item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#" + entry.path));
                    } else {
                        // add relative link
                        item.add(new LinkPanel("pathName", "list", entry.path, "#" + entry.path));
                    }
                    // quick links
                    if (entry.isSubmodule()) {
                        // submodule
                        item.add(new ExternalLink("patch", "").setEnabled(false));
                        item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils
                                .newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule));
                        item.add(new ExternalLink("blame", "").setEnabled(false));
                        item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
                                .newPathParameter(repositoryName, endId, entry.path))
                                .setEnabled(!entry.changeType.equals(ChangeType.ADD)));
                    } else {
                        // tree or blob
                        item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils
                                .newBlobDiffParameter(repositoryName, startId, endId, entry.path))
                                .setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
                        item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
                                .newPathParameter(repositoryName, endId, entry.path))
                                .setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
                        item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
                                .newPathParameter(repositoryName, endId, entry.path))
                                .setEnabled(!entry.changeType.equals(ChangeType.ADD)
                                        && !entry.changeType.equals(ChangeType.DELETE)));
                        item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
                                .newPathParameter(repositoryName, endId, entry.path))
                                .setEnabled(!entry.changeType.equals(ChangeType.ADD)));
                    }
                    WicketUtils.setAlternatingBackground(item, counter);
                    counter++;
                }
            };
            comparison.add(pathsView);
            comparison.add(new Label("diffText", diff).setEscapeModelStrings(false));
        }
        //
        // ref selection form
        //
        SessionlessForm<Void> refsForm = new SessionlessForm<Void>("compareRefsForm", getClass(), getPageParameters()) {
            private static final long serialVersionUID = 1L;
            @Override
            public void onSubmit() {
                String from = ComparePage.this.fromRefId.getObject();
                String to = ComparePage.this.toRefId.getObject();
                PageParameters params = WicketUtils.newRangeParameter(repositoryName, from, to);
                String relativeUrl = urlFor(ComparePage.class, params).toString();
                String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
                getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
            }
        };
        List<String> refs = new ArrayList<String>();
        for (RefModel ref : JGitUtils.getLocalBranches(r, true, -1)) {
            refs.add(ref.getName());
        }
        if (repository.showRemoteBranches) {
            for (RefModel ref : JGitUtils.getRemoteBranches(r, true, -1)) {
                refs.add(ref.getName());
            }
        }
        for (RefModel ref : JGitUtils.getTags(r, true, -1)) {
            refs.add(ref.getName());
        }
        refsForm.add(new DropDownChoice<String>("fromRef", fromRefId, refs).setEnabled(refs.size() > 0));
        refsForm.add(new DropDownChoice<String>("toRef", toRefId, refs).setEnabled(refs.size() > 0));
        add(refsForm);
        //
        // manual ids form
        //
        SessionlessForm<Void> idsForm = new SessionlessForm<Void>("compareIdsForm", getClass(), getPageParameters()) {
            private static final long serialVersionUID = 1L;
            @Override
            public void onSubmit() {
                String from = ComparePage.this.fromCommitId.getObject();
                String to = ComparePage.this.toCommitId.getObject();
                PageParameters params = WicketUtils.newRangeParameter(repositoryName, from, to);
                String relativeUrl = urlFor(ComparePage.class, params).toString();
                String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
                getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
            }
        };
        TextField<String> fromIdField = new TextField<String>("fromId", fromCommitId);
        WicketUtils.setInputPlaceholder(fromIdField, getString("gb.from") + "...");
        idsForm.add(fromIdField);
        TextField<String> toIdField = new TextField<String>("toId", toCommitId);
        WicketUtils.setInputPlaceholder(toIdField, getString("gb.to") + "...");
        idsForm.add(toIdField);
        add(idsForm);
        r.close();
    }
    @Override
    protected String getPageName() {
        return getString("gb.compare");
    }
    @Override
    protected Class<? extends BasePage> getRepoNavPageClass() {
        return ComparePage.class;
    }
    private RevCommit getCommit(Repository r, String rev)
    {
        RevCommit otherCommit = JGitUtils.getCommit(r, rev);
        if (otherCommit == null) {
            error(MessageFormat.format(getString("gb.failedToFindCommit"), rev, repositoryName, getPageName()), true);
        }
        return otherCommit;
    }
}
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -169,6 +169,7 @@
        }
        pages.put("commits", new PageRegistration("gb.commits", LogPage.class, params));
        pages.put("tree", new PageRegistration("gb.tree", TreePage.class, params));
        pages.put("compare", new PageRegistration("gb.compare", ComparePage.class, params));
        if (GitBlit.getBoolean(Keys.web.allowForking, true)) {
            pages.put("forks", new PageRegistration("gb.forks", ForksPage.class, params));
        }
src/main/resources/gitblit.css
@@ -119,6 +119,11 @@
    color: #ffffff !important;
}
.nav-pills > .active > a, .nav-pills > .active > a:hover {
    color: #fff;
    background-color: #002060;
}
.repositorynavbar {
    background-color: #fbfbfb;
    border-bottom: 1px solid #ccc;