docs/00_index.mkd | ●●●●● patch | view | raw | blame | history | |
docs/00_setup.mkd | ●●●●● patch | view | raw | blame | history | |
docs/01_faq.mkd | ●●●●● patch | view | raw | blame | history | |
docs/architecture.odg | patch | view | raw | blame | history | |
docs/architecture.png | patch | view | raw | blame | history | |
src/com/gitblit/GitBlit.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/utils/JGitUtils.java | ●●●●● patch | view | raw | blame | history | |
tests/com/gitblit/tests/GitBlitSuite.java | ●●●●● patch | view | raw | blame | history | |
tests/com/gitblit/tests/JGitUtilsTest.java | ●●●●● patch | view | raw | blame | history |
docs/00_index.mkd
@@ -31,6 +31,7 @@ - Repository Owners may edit repositories through the web UI - Automatically generates a self-signed certificate for https communications - Git-notes support - Branch-selectable metrics - Dates can optionally be displayed using the browser's reported timezone - Author and Committer email address display can be controlled - Dynamic zip downloads feature @@ -57,6 +58,7 @@ - Unit testing - Branch selector on Metrics - Blame - Clone remote repository ### Idea List - Ticgit activity/timeline docs/00_setup.mkd
@@ -9,7 +9,7 @@ - *server.httpBindInterface* and *server.httpsBindInterface*<br/> **NOTE:** Consider using **https** exclusively because passwords for authentication are transmitted as clear text! - *server.storePassword*<br/> **NOTE:** The certificate password AND the keystore password must match! **NOTE:** If you manually generate an ssl certificate, the certificate password AND the keystore password must match! 3. Execute `gitblit.cmd` or `java -jar gitblit.jar` from a command-line 4. Wait a minute or two while all dependencies are downloaded and your self-signed certificate is generated. 5. Open your browser to <http://localhost> or <https://localhost> depending on your chosen configuration. @@ -35,9 +35,9 @@ Repository names must be unique and are CASE-SENSITIVE ON CASE-SENSITIVE FILESYSTEMS. The name must be composed of letters, digits, or `/ _ - .`<br/> Whitespace is illegal. Repositories can be grouped by folders. e.g. *libraries/mycoollib.git* and *libraries/myotherlib.git* Repositories can be grouped within subfolders. e.g. *libraries/mycoollib.git* and *libraries/myotherlib.git* Repository names will automatically have *.git* appended to the name at creation time, if not already specified. All created repositories are *bare* and will automatically have *.git* appended to the name at creation time, if not already specified. #### Repository Owner The *Repository Owner* has the special permission of being able to edit a repository through the web UI. The Repository Owner is not permitted to rename the repository, delete the repository, or reassign ownership to another user. @@ -61,7 +61,7 @@ ### Creating your own Self-Signed Certificate Review the contents of the `makekeystore.cmd` or `makekeystore_jdk.cmd` script and execute it.<br/> **NOTE:** The certificate password AND the keystore password must match! **NOTE:** If you manually generate an ssl certificate, the certificate password AND the keystore password must match! ### Running as a Service Review the contents of the `installService.cmd` or `installService64.cmd`, as appropriate for your installed Java Virtual Machine.<br/> docs/01_faq.mkd
@@ -27,9 +27,29 @@ ### Why use Gitblit? It's a small tool that allows you to easily manage shared repositories and doesn't require alot of setup or git kung-foo. ### Who is the target user for Gitblit? Small workgroups that require centralized repositories. Gitblit is not meant to be a social coding resource like [Github](http://github.com) or [Bitbucket](http://bitbucket.com) with 100s or 1000s of users. Gitblit is designed to fulfill the same function as your centralized Subversion or CVS server. ### Why does Gitblit exist? As a Java developer I prefer that as much of my tooling as possible is Java.<br/> Originally, I was going to use [Mercurial](http://mercurial.selenic.com) but... - MercurialEclipse [shells to Python and captures System.in](http://mercurial.808500.n3.nabble.com/Hg4J-Mercurial-pure-Java-library-tp2693090p2694555.html)<br/> Parsing command-line output is fragile and suboptimal.<br/>Unfortunately this is necessary because Mercurial is an application, not a library. - Mercurial seems to [frown](http://mercurial.808500.n3.nabble.com/Hg4J-Mercurial-pure-Java-library-tp2693090p2695051.html) on the fledgling [Hg4j][hg4j] (pure Java Mercurial) project. - Mercurial HTTP/HTTPS needs to run as CGI through Apache/IIS/etc, as mod_python through Apache, or served with a built-in http server.<br/> This requires setup and maintenance of multiple, mixed 3rd party components. Gitblit eliminates all that complication with its 100% Java stack and simple single configuration file. ### Do I need real Git? No. Gitblit is based on [JGit][jgit] which is a pure Java implementation of the [Git version control system][git].<br/> Everything you need for Gitblit is either in the zip distribution file or automatically downloaded on execution. ### Can I run Gitblit in conjunction with my existing Git tooling? Yes. You can configure Gitblit to only be a repository viewer. ### Do I need a JDK or can I use a JRE? Gitblit will run just fine with a JRE. Gitblit can optionally use `keytool` from the JDK to generate self-signed certificates, but normally Gitblit uses [BouncyCastle][bouncycastle] for that need. @@ -73,4 +93,5 @@ [jgit]: http://eclipse.org/jgit "Eclipse JGit Site" [git]: http://git-scm.com "Official Git Site" [mina]: http://mina.apache.org "Apache Mina" [bouncycastle]: http://bouncycastle.org "The Legion of the Bouncy Castle" [bouncycastle]: http://bouncycastle.org "The Legion of the Bouncy Castle" [hg4j]: http://code.google.com/p/hg4j/ "hg4j" docs/architecture.odgBinary files differ
docs/architecture.pngsrc/com/gitblit/GitBlit.java
@@ -226,7 +226,7 @@ Repository r = null; if (isCreate) { // ensure created repository name ends with .git if (!repository.name.endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) { if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) { repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT; } if (new File(repositoriesFolder, repository.name).exists()) { @@ -236,7 +236,7 @@ } // create repository logger.info("create repository " + repository.name); r = JGitUtils.createRepository(repositoriesFolder, repository.name, true); r = JGitUtils.createRepository(repositoriesFolder, repository.name); } else { // rename repository if (!repositoryName.equalsIgnoreCase(repository.name)) { src/com/gitblit/utils/JGitUtils.java
@@ -22,6 +22,7 @@ import java.io.OutputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -32,6 +33,8 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.eclipse.jgit.api.CloneCommand; import org.eclipse.jgit.api.FetchCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffEntry.ChangeType; @@ -57,6 +60,9 @@ import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.OrTreeFilter; @@ -90,8 +96,54 @@ return r.toString().trim(); } public static Repository createRepository(File repositoriesFolder, String name, boolean bare) { Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(bare).call(); public static FetchResult cloneRepository(File repositoriesFolder, String name, String fromUrl) throws Exception { FetchResult result = null; if (!name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) { name += Constants.DOT_GIT_EXT; } File folder = new File(repositoriesFolder, name); if (folder.exists()) { File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED); FileRepository repository = new FileRepository(gitDir); result = fetchRepository(repository); repository.close(); } else { CloneCommand clone = new CloneCommand(); clone.setBare(true); clone.setCloneAllBranches(true); clone.setURI(fromUrl); clone.setDirectory(folder); clone.call(); // Now we have to fetch because CloneCommand doesn't fetch // refs/notes nor does it allow manual RefSpec. File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED); FileRepository repository = new FileRepository(gitDir); result = fetchRepository(repository); repository.close(); } return result; } public static FetchResult fetchRepository(Repository repository, RefSpec... refSpecs) throws Exception { Git git = new Git(repository); FetchCommand fetch = git.fetch(); List<RefSpec> specs = new ArrayList<RefSpec>(); if (refSpecs == null || refSpecs.length == 0) { specs.add(new RefSpec("+refs/heads/*:refs/remotes/origin/*")); specs.add(new RefSpec("+refs/tags/*:refs/tags/*")); specs.add(new RefSpec("+refs/notes/*:refs/notes/*")); } else { specs.addAll(Arrays.asList(refSpecs)); } fetch.setRefSpecs(specs); FetchResult result = fetch.call(); repository.close(); return result; } public static Repository createRepository(File repositoriesFolder, String name) { Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call(); return git.getRepository(); } @@ -219,7 +271,7 @@ refs.put(objectid, new ArrayList<RefModel>()); } refs.get(objectid).add(ref); } } return refs; } tests/com/gitblit/tests/GitBlitSuite.java
@@ -16,25 +16,20 @@ package com.gitblit.tests; import java.io.File; import java.util.ArrayList; import java.util.List; import junit.extensions.TestSetup; import junit.framework.Test; import junit.framework.TestSuite; import org.eclipse.jgit.api.CloneCommand; import org.eclipse.jgit.api.FetchCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.RefSpec; import com.gitblit.FileSettings; import com.gitblit.GitBlit; import com.gitblit.GitBlitException; import com.gitblit.JettyLoginService; import com.gitblit.models.RepositoryModel; import com.gitblit.utils.JGitUtils; public class GitBlitSuite extends TestSetup { public static final File REPOSITORIES = new File("git"); @@ -66,11 +61,11 @@ } public static Repository getJGitRepository() throws Exception { return new FileRepository(new File(REPOSITORIES, "nested/jgit.git")); return new FileRepository(new File(REPOSITORIES, "test/jgit.git")); } public static Repository getBluezGnomeRepository() throws Exception { return new FileRepository(new File(REPOSITORIES, "nested/bluez-gnome.git")); return new FileRepository(new File(REPOSITORIES, "test/bluez-gnome.git")); } @Override @@ -82,51 +77,24 @@ GitBlit.self().setLoginService(loginService); if (REPOSITORIES.exists() || REPOSITORIES.mkdirs()) { cloneOrFetch("helloworld.git", "https://github.com/git/hello-world.git", true); cloneOrFetch("ticgit.git", "https://github.com/jeffWelling/ticgit.git", true); cloneOrFetch("nested/bluez-gnome.git", "https://git.kernel.org/pub/scm/bluetooth/bluez-gnome.git", true); cloneOrFetch("nested/jgit.git", "https://github.com/eclipse/jgit.git", true); cloneOrFetch("nested/helloworld.git", "https://github.com/git/hello-world.git", true); cloneOrFetch("helloworld.git", "https://github.com/git/hello-world.git"); cloneOrFetch("ticgit.git", "https://github.com/jeffWelling/ticgit.git"); cloneOrFetch("test/bluez-gnome.git", "https://git.kernel.org/pub/scm/bluetooth/bluez-gnome.git"); cloneOrFetch("test/jgit.git", "https://github.com/eclipse/jgit.git"); cloneOrFetch("test/helloworld.git", "https://github.com/git/hello-world.git"); enableTickets("ticgit.git"); enableDocs("ticgit.git"); showRemoteBranches("ticgit.git"); showRemoteBranches("nested/jgit.git"); showRemoteBranches("test/jgit.git"); } } private void cloneOrFetch(String toFolder, String fromUrl, boolean bare) throws Exception { File folder = new File(REPOSITORIES, toFolder + (bare ? "" : "/.git")); if (folder.exists()) { System.out.print("Updating " + (bare ? "bare " : " ") + toFolder + "... "); fetch(toFolder); System.out.println("done."); } else { System.out.println("Cloning " + (bare ? "bare " : " ") + toFolder + "... "); CloneCommand clone = new CloneCommand(); clone.setBare(bare); clone.setCloneAllBranches(true); clone.setURI(fromUrl); clone.setDirectory(folder); clone.call(); // Now we have to fetch because CloneCommand doesn't fetch // Notes nor does it allow manual RefSpec. fetch(toFolder); System.out.println("done."); } } private void fetch(String toFolder) throws Exception { FileRepository repository = new FileRepository(new File(REPOSITORIES, toFolder)); Git git = new Git(repository); FetchCommand fetch = git.fetch(); List<RefSpec> specs = new ArrayList<RefSpec>(); specs.add(new RefSpec("+refs/heads/*:refs/remotes/origin/*")); specs.add(new RefSpec("+refs/tags/*:refs/tags/*")); specs.add(new RefSpec("+refs/notes/*:refs/notes/*")); fetch.setRefSpecs(specs); fetch.call(); repository.close(); private void cloneOrFetch(String name, String fromUrl) throws Exception { System.out.print("Fetching " + name + "... "); JGitUtils.cloneRepository(REPOSITORIES, name, fromUrl); System.out.println("done."); } private void enableTickets(String repositoryName) { @@ -138,7 +106,7 @@ g.printStackTrace(); } } private void enableDocs(String repositoryName) { try { RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName); @@ -148,7 +116,7 @@ g.printStackTrace(); } } private void showRemoteBranches(String repositoryName) { try { RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName); tests/com/gitblit/tests/JGitUtilsTest.java
@@ -30,7 +30,9 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache.FileKey; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.util.FS; import com.gitblit.GitBlit; import com.gitblit.Keys; @@ -94,16 +96,10 @@ public void testCreateRepository() throws Exception { String[] repositories = { "NewTestRepository.git", "NewTestRepository" }; for (String repositoryName : repositories) { boolean isBare = repositoryName.endsWith(".git"); for (String repositoryName : repositories) { Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, repositoryName, isBare); File folder; if (isBare) { folder = new File(GitBlitSuite.REPOSITORIES, repositoryName); } else { folder = new File(GitBlitSuite.REPOSITORIES, repositoryName + "/.git"); } repositoryName); File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName), FS.DETECTED); assertTrue(repository != null); assertFalse(JGitUtils.hasCommits(repository)); assertTrue(JGitUtils.getFirstCommit(repository, null) == null); @@ -138,7 +134,7 @@ } public void testBranches() throws Exception { Repository repository = GitBlitSuite.getTicgitRepository(); Repository repository = GitBlitSuite.getJGitRepository(); for (RefModel model : JGitUtils.getLocalBranches(repository, true, -1)) { assertTrue(model.getName().startsWith(Constants.R_HEADS)); assertTrue(model.equals(model)); @@ -155,14 +151,14 @@ + model.getName().hashCode()); assertTrue(model.getShortMessage().equals(model.getShortMessage())); } assertTrue(JGitUtils.getRemoteBranches(repository, true, 10).size() == 10); assertTrue(JGitUtils.getRemoteBranches(repository, true, 8).size() == 8); repository.close(); } public void testTags() throws Exception { Repository repository = GitBlitSuite.getTicgitRepository(); Repository repository = GitBlitSuite.getJGitRepository(); for (RefModel model : JGitUtils.getTags(repository, true, -1)) { if (model.getObjectId().getName().equals("283035e4848054ff1803cb0e690270787dc92399")) { if (model.getObjectId().getName().equals("d28091fb2977077471138fe97da1440e0e8ae0da")) { assertTrue("Not an annotated tag!", model.isAnnotatedTag()); } assertTrue(model.getName().startsWith(Constants.R_TAGS));