Sebastian Sdorra avatar Sebastian Sdorra committed 1fafc13

use template engine and repository service for git repository page

Comments (0)

Files changed (5)

scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryViewer.java

 
 //~--- non-JDK imports --------------------------------------------------------
 
-import org.apache.commons.lang.StringEscapeUtils;
-
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.api.errors.NoHeadException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Closeables;
+import com.google.inject.Inject;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import sonia.scm.io.RegexResourceProcessor;
-import sonia.scm.io.ResourceProcessor;
-import sonia.scm.repository.GitUtil;
-import sonia.scm.util.IOUtil;
+import sonia.scm.repository.Branch;
+import sonia.scm.repository.Branches;
+import sonia.scm.repository.Changeset;
+import sonia.scm.repository.ChangesetPagingResult;
+import sonia.scm.repository.Person;
+import sonia.scm.repository.Repository;
+import sonia.scm.repository.RepositoryException;
+import sonia.scm.repository.api.RepositoryService;
+import sonia.scm.repository.api.RepositoryServiceFactory;
+import sonia.scm.template.Template;
+import sonia.scm.template.TemplateEngine;
+import sonia.scm.template.TemplateEngineFactory;
 import sonia.scm.util.Util;
 
 //~--- JDK imports ------------------------------------------------------------
 
-import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
+import java.io.Writer;
 
 import java.util.Date;
+import java.util.Iterator;
 
-import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletResponse;
 
 /**
   public static final String MIMETYPE_HTML = "text/html";
 
   /** Field description */
-  public static final String RESOURCE_GITINDEX = "/sonia/scm/git.index.html";
+  public static final String RESOURCE_GITINDEX =
+    "/sonia/scm/git.index.mustache";
+
+  /** Field description */
+  private static final int CHANGESET_PER_BRANCH = 10;
 
   /**
    * the logger for GitRepositoryViewer
   private static final Logger logger =
     LoggerFactory.getLogger(GitRepositoryViewer.class);
 
-  //~--- methods --------------------------------------------------------------
+  //~--- constructors ---------------------------------------------------------
 
   /**
-   * Method description
+   * Constructs ...
    *
    *
-   * @param response
-   * @param repository
-   * @param repositoryName
-   *
-   * @throws IOException
-   * @throws NoHeadException
-   * @throws ServletException
+   * @param templateEngineFactory
+   * @param repositoryServiceFactory
    */
-  public void handleRequest(HttpServletResponse response,
-                            Repository repository, String repositoryName)
-          throws IOException, ServletException, NoHeadException
+  @Inject
+  public GitRepositoryViewer(TemplateEngineFactory templateEngineFactory,
+    RepositoryServiceFactory repositoryServiceFactory)
   {
-    response.setContentType(MIMETYPE_HTML);
-
-    ResourceProcessor processor = new RegexResourceProcessor();
-
-    processor.addVariable("name", repositoryName);
-
-    StringBuilder sb = new StringBuilder();
-
-    if (!repository.getAllRefs().isEmpty())
-    {
-      Git git = new Git(repository);
-      int c = 0;
-      ObjectId head = GitUtil.getRepositoryHead(repository);
-
-      try
-      {
-        for (RevCommit commit : git.log().add(head).call())
-        {
-          appendCommit(sb, commit);
-          c++;
-
-          if (c > logSize)
-          {
-            break;
-          }
-        }
-      }
-      catch (GitAPIException ex)
-      {
-        logger.error("could not read changesets", ex);
-      }
-    }
-
-    processor.addVariable("commits", sb.toString());
-
-    BufferedReader reader = null;
-    PrintWriter writer = null;
-
-    try
-    {
-      reader = new BufferedReader(
-          new InputStreamReader(
-              GitRepositoryViewer.class.getResourceAsStream(
-                RESOURCE_GITINDEX)));
-      writer = response.getWriter();
-      processor.process(reader, writer);
-    }
-    finally
-    {
-      IOUtil.close(reader);
-      IOUtil.close(writer);
-    }
-  }
-
-  //~--- get methods ----------------------------------------------------------
-
-  /**
-   * Method description
-   *
-   *
-   * @return
-   */
-  public int getLogSize()
-  {
-    return logSize;
-  }
-
-  //~--- set methods ----------------------------------------------------------
-
-  /**
-   * Method description
-   *
-   *
-   * @param logSize
-   */
-  public void setLogSize(int logSize)
-  {
-    this.logSize = logSize;
+    this.templateEngineFactory = templateEngineFactory;
+    this.repositoryServiceFactory = repositoryServiceFactory;
   }
 
   //~--- methods --------------------------------------------------------------
    * Method description
    *
    *
-   * @param sb
-   * @param commit
+   * @param response
+   * @param repository
+   *
+   * @throws IOException
+   * @throws RepositoryException
    */
-  private void appendCommit(StringBuilder sb, RevCommit commit)
+  public void handleRequest(HttpServletResponse response, Repository repository)
+    throws RepositoryException, IOException
   {
-    sb.append("<tr><td class=\"small\">");
+    response.setContentType(MIMETYPE_HTML);
 
-    long time = commit.getCommitTime();
+    TemplateEngine engine = templateEngineFactory.getDefaultEngine();
+    Template template = engine.getTemplate(RESOURCE_GITINDEX);
+    //J-
+    ImmutableMap<String,Object> env = ImmutableMap.of(
+      "repository", repository, 
+      "branches", createBranchesModel(repository)
+    );
+    //J+
 
-    sb.append(Util.formatDate(new Date(time * 1000)));
-    sb.append("</td><td class=\"small\">");
+    Writer writer = null;
 
-    PersonIdent person = commit.getCommitterIdent();
+    try
+    {
+      writer = response.getWriter();
+      template.execute(writer, env);
+    }
+    finally
+    {
+      Closeables.closeQuietly(writer);
+    }
+  }
 
-    if (person != null)
+  /**
+   * Method description
+   *
+   *
+   * @param repository
+   *
+   * @return
+   *
+   * @throws IOException
+   * @throws RepositoryException
+   */
+  private BranchesModel createBranchesModel(Repository repository)
+    throws RepositoryException, IOException
+  {
+    BranchesModel model = null;
+    RepositoryService service = null;
+
+    try
     {
-      String name = person.getName();
+      service = repositoryServiceFactory.create(repository);
 
-      if (Util.isNotEmpty(name))
-      {
-        sb.append(StringEscapeUtils.escapeHtml(name));
-      }
+      Branches branches = service.getBranchesCommand().getBranches();
+      Iterable<BranchModel> branchModels =
+        Iterables.transform(branches, new BranchModelTransformer(service));
+
+      model = new BranchesModel(branchModels);
+    }
+    finally
+    {
+      Closeables.closeQuietly(service);
     }
 
-    sb.append("</td><td>");
-    sb.append(StringEscapeUtils.escapeHtml(commit.getFullMessage()));
-    sb.append("</td></tr>");
+    return model;
   }
 
+  //~--- inner classes --------------------------------------------------------
+
+  /**
+   * Class description
+   *
+   *
+   * @version        Enter version here..., 13/02/27
+   * @author         Enter your name here...
+   */
+  private static class BranchModel
+  {
+
+    /**
+     * Constructs ...
+     *
+     *
+     * @param name
+     * @param changesets
+     */
+    public BranchModel(String name, Iterable<ChangesetModel> changesets)
+    {
+      this.name = name;
+      this.changesets = changesets;
+    }
+
+    //~--- get methods --------------------------------------------------------
+
+    /**
+     * Method description
+     *
+     *
+     * @return
+     */
+    public Iterable<ChangesetModel> getChangesets()
+    {
+      return changesets;
+    }
+
+    /**
+     * Method description
+     *
+     *
+     * @return
+     */
+    public String getName()
+    {
+      return name;
+    }
+
+    //~--- fields -------------------------------------------------------------
+
+    /** Field description */
+    private Iterable<ChangesetModel> changesets;
+
+    /** Field description */
+    private String name;
+  }
+
+
+  /**
+   * Class description
+   *
+   *
+   * @version        Enter version here..., 13/02/27
+   * @author         Enter your name here...
+   */
+  private static class BranchModelTransformer
+    implements Function<Branch, BranchModel>
+  {
+
+    /**
+     * Constructs ...
+     *
+     *
+     * @param service
+     */
+    public BranchModelTransformer(RepositoryService service)
+    {
+      this.service = service;
+    }
+
+    //~--- methods ------------------------------------------------------------
+
+    /**
+     * Method description
+     *
+     *
+     * @param branch
+     *
+     * @return
+     */
+    @Override
+    public BranchModel apply(Branch branch)
+    {
+      BranchModel model = null;
+      String name = branch.getName();
+
+      try
+      {
+        ChangesetPagingResult cpr = service.getLogCommand().setBranch(
+                                      name).setPagingLimit(
+                                      CHANGESET_PER_BRANCH).getChangesets();
+        Iterable<ChangesetModel> changesets = Iterables.transform(cpr,
+                                                new Function<Changeset,
+                                                  ChangesetModel>()
+        {
+
+          @Override
+          public ChangesetModel apply(Changeset changeset)
+          {
+            return new ChangesetModel(changeset);
+          }
+        });
+
+        model = new BranchModel(name, changesets);
+      }
+      catch (Exception ex)
+      {
+        logger.error("could not create model for branch: ".concat(name), ex);
+      }
+
+      return model;
+    }
+
+    //~--- fields -------------------------------------------------------------
+
+    /** Field description */
+    private RepositoryService service;
+  }
+
+
+  /**
+   * Class description
+   *
+   *
+   * @version        Enter version here..., 13/02/27
+   * @author         Enter your name here...
+   */
+  private static class BranchesModel implements Iterable<BranchModel>
+  {
+
+    /**
+     * Constructs ...
+     *
+     *
+     * @param branches
+     */
+    public BranchesModel(Iterable<BranchModel> branches)
+    {
+      this.branches = branches;
+    }
+
+    //~--- methods ------------------------------------------------------------
+
+    /**
+     * Method description
+     *
+     *
+     * @return
+     */
+    @Override
+    public Iterator<BranchModel> iterator()
+    {
+      return branches.iterator();
+    }
+
+    //~--- fields -------------------------------------------------------------
+
+    /** Field description */
+    private Iterable<BranchModel> branches;
+  }
+
+
+  /**
+   * Class description
+   *
+   *
+   * @version        Enter version here..., 13/02/27
+   * @author         Enter your name here...
+   */
+  private static class ChangesetModel
+  {
+
+    /**
+     * Constructs ...
+     *
+     *
+     * @param changeset
+     */
+    public ChangesetModel(Changeset changeset)
+    {
+      this.changeset = changeset;
+    }
+
+    //~--- get methods --------------------------------------------------------
+
+    /**
+     * Method description
+     *
+     *
+     * @return
+     */
+    public Person getAuthor()
+    {
+      return changeset.getAuthor();
+    }
+
+    /**
+     * Method description
+     *
+     *
+     * @return
+     */
+    public String getDate()
+    {
+      String date = Util.EMPTY_STRING;
+      Long time = changeset.getDate();
+
+      if (time != null)
+      {
+        date = Util.formatDate(new Date(time));
+      }
+
+      return date;
+    }
+
+    /**
+     * Method description
+     *
+     *
+     * @return
+     */
+    public String getDescription()
+    {
+      return changeset.getDescription();
+    }
+
+    //~--- fields -------------------------------------------------------------
+
+    /** Field description */
+    private Changeset changeset;
+  }
+
+
   //~--- fields ---------------------------------------------------------------
 
   /** Field description */
-  private int logSize = 25;
+  private RepositoryServiceFactory repositoryServiceFactory;
+
+  /** Field description */
+  private TemplateEngineFactory templateEngineFactory;
 }

scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java

   @Override
   protected void configureServlets()
   {
+    bind(GitRepositoryViewer.class);
     bind(GitRepositoryResolver.class);
     bind(GitReceivePackFactory.class);
 

scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java

 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.http.server.GitServlet;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.transport.resolver.RepositoryResolver;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import sonia.scm.repository.GitUtil;
 import sonia.scm.repository.RepositoryProvider;
 import sonia.scm.repository.RepositoryRequestListenerUtil;
 import sonia.scm.util.HttpUtil;
    *
    * @param repositoryResolver
    * @param receivePackFactory
+   * @param repositoryViewer
    * @param repositoryProvider
    * @param repositoryRequestListenerUtil
    */
   @Inject
-  public ScmGitServlet(
-          GitRepositoryResolver repositoryResolver,
-          GitReceivePackFactory receivePackFactory,
-          RepositoryProvider repositoryProvider,
-          RepositoryRequestListenerUtil repositoryRequestListenerUtil)
+  public ScmGitServlet(GitRepositoryResolver repositoryResolver,
+    GitReceivePackFactory receivePackFactory,
+    GitRepositoryViewer repositoryViewer,
+    RepositoryProvider repositoryProvider,
+    RepositoryRequestListenerUtil repositoryRequestListenerUtil)
   {
-    this.resolver = repositoryResolver;
     this.repositoryProvider = repositoryProvider;
+    this.repositoryViewer = repositoryViewer;
     this.repositoryRequestListenerUtil = repositoryRequestListenerUtil;
     setRepositoryResolver(repositoryResolver);
     setReceivePackFactory(receivePackFactory);
    */
   @Override
   protected void service(HttpServletRequest request,
-                         HttpServletResponse response)
-          throws ServletException, IOException
+    HttpServletResponse response)
+    throws ServletException, IOException
   {
     String uri = HttpUtil.getStrippedURI(request);
 
       if (repository != null)
       {
         if (repositoryRequestListenerUtil.callListeners(request, response,
-                repository))
+          repository))
         {
           super.service(request, response);
         }
     }
     else
     {
-      printGitInformation(request, response);
+      printGitInformation(response);
     }
   }
 
    * @throws IOException
    * @throws ServletException
    */
-  private void printGitInformation(HttpServletRequest request,
-                                   HttpServletResponse response)
-          throws ServletException, IOException
+  private void printGitInformation(HttpServletResponse response)
+    throws ServletException, IOException
   {
     sonia.scm.repository.Repository scmRepository = repositoryProvider.get();
 
     if (scmRepository != null)
     {
-      Repository repository = null;
-
       try
       {
-        repository = resolver.open(request, scmRepository.getName());
-        new GitRepositoryViewer().handleRequest(response, repository,
-                scmRepository.getName());
+        repositoryViewer.handleRequest(response, scmRepository);
       }
       catch (Exception ex)
       {
-        throw new ServletException(ex);
-      }
-      finally
-      {
-        GitUtil.close(repository);
+        throw new ServletException("could not create repository view", ex);
       }
     }
     else
   private RepositoryRequestListenerUtil repositoryRequestListenerUtil;
 
   /** Field description */
-  private RepositoryResolver<HttpServletRequest> resolver;
+  private GitRepositoryViewer repositoryViewer;
 }

scm-plugins/scm-git-plugin/src/main/resources/sonia/scm/git.index.html

-<!--
-
-    Copyright (c) 2010, Sebastian Sdorra
-    All rights reserved.
-
-    Redistribution and use in source and binary forms, with or without
-    modification, are permitted provided that the following conditions are met:
-
-    1. Redistributions of source code must retain the above copyright notice,
-       this list of conditions and the following disclaimer.
-    2. Redistributions in binary form must reproduce the above copyright notice,
-       this list of conditions and the following disclaimer in the documentation
-       and/or other materials provided with the distribution.
-    3. Neither the name of SCM-Manager; nor the names of its
-       contributors may be used to endorse or promote products derived from this
-       software without specific prior written permission.
-
-    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-    DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
-    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-    http://bitbucket.org/sdorra/scm-manager
-
-
--->
-
-<html>
-  <head>
-    <title>SCM :: Manager - Git Repository - ${name}</title>
-    <style type="text/css">
-      body {
-        background-color: #ffffff;
-        margin: 10px;
-        color: #202020;
-        font-family: Verdana,Helvetica,Arial,sans-serif;
-        font-size: 75%;
-      }
-      h1, h2, h3, h4, h5 {
-        font-family: Arial, "Arial CE", "Lucida Grande CE", lucida, "Helvetica CE", sans-serif;
-        font-weight: bold;
-        margin: 0px;
-        padding: 0px;
-        color: #D20005;
-      }
-      h1 {
-        font-size: 18px;
-        border-bottom: 1px solid #AFAFAF;
-      }
-      h2 {
-        font-size:  14px;
-        border-bottom: 1px solid #AFAFAF;
-      }
-      a:link, a:visited {
-        color: #045491;
-        font-weight: bold;
-        text-decoration: none;
-      }      
-      a:link:hover, a:visited:hover  {
-        color: #045491;
-        font-weight: bold;
-        text-decoration: underline;
-      }
-      table {
-        border: 0 none;
-        border-collapse: collapse;
-        font-size: 100%;
-        margin: 20px 0;
-        padding: 20px;
-        width: 100%;
-      }
-      td, th {
-        padding: 3px;
-        vertical-align: top;
-        border: 1px solid #CCCCCC;
-        text-align: left;
-      }
-      .small {
-        width: 20%;
-      }
-    </style>
-  </head>
-  <body>
-
-    <h1>SCM :: Manager - Git Repository - ${name}</h1>
-
-    <table>
-      <thead>
-        <tr>
-          <th class="small">Time</th>
-          <th class="small">Author</th>
-          <th>Message</th>
-        </tr>
-      </thead>
-      <tbody>
-        ${commits}
-      </tbody>
-    </table>
-
-    <h2>Git Informations</h2>
-    <ul>
-      <li><a href="http://git-scm.com/" target="_blank">Git Homepage</a></li>
-      <li><a href="http://schacon.github.com/git/gittutorial.html" target="_blank">Git Tutorial</a></li>
-      <li><a href="http://schacon.github.com/git/user-manual.html" target="_blank">Git User's Manual</a></li>
-    </ul>
-
-  </body>
-</html>

scm-plugins/scm-git-plugin/src/main/resources/sonia/scm/git.index.mustache

+<!--
+
+    Copyright (c) 2010, Sebastian Sdorra
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of SCM-Manager; nor the names of its
+       contributors may be used to endorse or promote products derived from this
+       software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+    DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+    http://bitbucket.org/sdorra/scm-manager
+
+
+-->
+
+<html>
+  <head>
+    <title>SCM :: Manager - Git Repository - {{repository.name}}</title>
+    <style type="text/css">
+      body {
+        background-color: #ffffff;
+        margin: 10px;
+        color: #202020;
+        font-family: Verdana,Helvetica,Arial,sans-serif;
+        font-size: 75%;
+      }
+      h1, h2, h3, h4, h5 {
+        font-family: Arial, "Arial CE", "Lucida Grande CE", lucida, "Helvetica CE", sans-serif;
+        font-weight: bold;
+        margin: 0px;
+        padding: 0px;
+        color: #D20005;
+        margin-bottom: 1em;
+      }
+      h1, h2 {
+        border-bottom: 1px solid #AFAFAF;
+      }
+      h1 {
+        font-size: 18px;
+      }
+      h2 {
+        font-size:  14px;
+      }
+      h3 {
+        font-size:  12px;
+      }
+      a:link, a:visited {
+        color: #045491;
+        font-weight: bold;
+        text-decoration: none;
+      }      
+      a:link:hover, a:visited:hover  {
+        color: #045491;
+        font-weight: bold;
+        text-decoration: underline;
+      }
+      table {
+        border: 0 none;
+        border-collapse: collapse;
+        font-size: 100%;
+        margin: 20px 0;
+        padding: 20px;
+        width: 100%;
+      }
+      td, th {
+        padding: 3px;
+        vertical-align: top;
+        border: 1px solid #CCCCCC;
+        text-align: left;
+      }
+      .small {
+        width: 20%;
+      }
+    </style>
+  </head>
+  <body>
+
+    <h1>SCM :: Manager - Git Repository - {{repository.name}}</h1>
+
+    <h2>Branch Informations</h2>
+    {{#branches}}
+      <h3>{{name}}</h3>
+
+      <table>
+        <thead>
+          <tr>
+            <th class="small">Date</th>
+            <th class="small">Author</th>
+            <th>Message</th>
+          </tr>
+        </thead>
+        <tbody>
+          {{#changesets}}
+          <tr>
+            <td class="small">{{date}}</td>
+            <td class="small">{{author}}</td>
+            <td>{{description}}</td>
+          </tr>
+          {{/changesets}}
+        </tbody>
+      </table>
+    {{/branches}}
+
+    <h2>Git Informations</h2>
+    <ul>
+      <li><a href="http://git-scm.com/" target="_blank">Git Homepage</a></li>
+      <li><a href="http://schacon.github.com/git/gittutorial.html" target="_blank">Git Tutorial</a></li>
+      <li><a href="http://schacon.github.com/git/user-manual.html" target="_blank">Git User's Manual</a></li>
+    </ul>
+
+  </body>
+</html>
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.