@@ -37,6 +37,7 @@ | |||
<classpathentry kind="lib" path="ext/jcalendar-1.3.2.jar" /> | |||
<classpathentry kind="lib" path="ext/commons-compress-1.4.1.jar" sourcepath="ext/src/commons-compress-1.4.1.jar" /> | |||
<classpathentry kind="lib" path="ext/xz-1.0.jar" sourcepath="ext/src/xz-1.0.jar" /> | |||
<classpathentry kind="lib" path="ext/commons-io-2.2.jar" sourcepath="ext/src/commons-io-2.2.jar" /> | |||
<classpathentry kind="lib" path="ext/force-partner-api-24.0.0.jar" sourcepath="ext/src/force-partner-api-24.0.0.jar" /> | |||
<classpathentry kind="lib" path="ext/force-wsc-24.0.0.jar" sourcepath="ext/src/force-wsc-24.0.0.jar" /> | |||
<classpathentry kind="lib" path="ext/js-1.7R2.jar" sourcepath="ext/src/js-1.7R2.jar" /> | |||
@@ -60,7 +61,6 @@ | |||
<classpathentry kind="lib" path="ext/httpcore-4.2.1.jar" sourcepath="ext/src/httpcore-4.2.1.jar" /> | |||
<classpathentry kind="lib" path="ext/commons-logging-1.1.1.jar" sourcepath="ext/src/commons-logging-1.1.1.jar" /> | |||
<classpathentry kind="lib" path="ext/commons-exec-1.1.jar" sourcepath="ext/src/commons-exec-1.1.jar" /> | |||
<classpathentry kind="lib" path="ext/commons-io-2.2.jar" sourcepath="ext/src/commons-io-2.2.jar" /> | |||
<classpathentry kind="output" path="bin/classes" /> | |||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" /> | |||
</classpath> |
@@ -148,6 +148,7 @@ dependencies: | |||
- compile 'org.apache.ivy:ivy:2.2.0' :war | |||
- compile 'com.toedter:jcalendar:1.3.2' :authority | |||
- compile 'org.apache.commons:commons-compress:1.4.1' :war | |||
- compile 'commons-io:commons-io:2.2' :war | |||
- compile 'com.force.api:force-partner-api:24.0.0' :war | |||
- compile 'org.freemarker:freemarker:2.3.19' :war | |||
- compile 'com.github.dblock.waffle:waffle-jna:1.5' :war |
@@ -380,6 +380,17 @@ | |||
</SOURCES> | |||
</library> | |||
</orderEntry> | |||
<orderEntry type="module-library"> | |||
<library name="commons-io-2.2.jar"> | |||
<CLASSES> | |||
<root url="jar://$MODULE_DIR$/ext/commons-io-2.2.jar!/" /> | |||
</CLASSES> | |||
<JAVADOC /> | |||
<SOURCES> | |||
<root url="jar://$MODULE_DIR$/ext/src/commons-io-2.2.jar!/" /> | |||
</SOURCES> | |||
</library> | |||
</orderEntry> | |||
<orderEntry type="module-library"> | |||
<library name="force-partner-api-24.0.0.jar"> | |||
<CLASSES> | |||
@@ -633,17 +644,6 @@ | |||
</SOURCES> | |||
</library> | |||
</orderEntry> | |||
<orderEntry type="module-library" scope="TEST"> | |||
<library name="commons-io-2.2.jar"> | |||
<CLASSES> | |||
<root url="jar://$MODULE_DIR$/ext/commons-io-2.2.jar!/" /> | |||
</CLASSES> | |||
<JAVADOC /> | |||
<SOURCES> | |||
<root url="jar://$MODULE_DIR$/ext/src/commons-io-2.2.jar!/" /> | |||
</SOURCES> | |||
</library> | |||
</orderEntry> | |||
<orderEntry type="inheritedJdk" /> | |||
</component> | |||
</module> |
@@ -191,6 +191,15 @@ git.userRepositoryPrefix = ~ | |||
# SINCE 1.3.0 | |||
git.defaultIncrementalPushTagPrefix = r | |||
# In an Unix environment where mixed access methods exist for shared repositories, | |||
# the repository should be created with 'git init --shared' to make sure that | |||
# it can be accessed e.g. via ssh (user git) and http (user www-data). | |||
# Valid values are the values available for the '--shared' option. The the manual | |||
# page for 'git init' for more information on shared repositories. | |||
# | |||
# SINCE 1.3.2 | |||
git.createRepositoriesShared = false | |||
# Enable JGit-based garbage collection. (!!EXPERIMENTAL!!) | |||
# | |||
# USE AT YOUR OWN RISK! |
@@ -2427,7 +2427,8 @@ public class GitBlit implements ServletContextListener { | |||
} | |||
// create repository | |||
logger.info("create repository " + repository.name); | |||
r = JGitUtils.createRepository(repositoriesFolder, repository.name); | |||
String shared = getString(Keys.git.createRepositoriesShared, "FALSE"); | |||
r = JGitUtils.createRepository(repositoriesFolder, repository.name, shared); | |||
} else { | |||
// rename repository | |||
if (!repositoryName.equalsIgnoreCase(repository.name)) { | |||
@@ -2529,7 +2530,13 @@ public class GitBlit implements ServletContextListener { | |||
// close the repository object | |||
r.close(); | |||
} | |||
// Adjust permissions in case we updated the config files | |||
JGitUtils.adjustSharedPerm(new File(r.getDirectory().getAbsolutePath(), "config"), | |||
getString(Keys.git.createRepositoriesShared, "FALSE")); | |||
JGitUtils.adjustSharedPerm(new File(r.getDirectory().getAbsolutePath(), "HEAD"), | |||
getString(Keys.git.createRepositoriesShared, "FALSE")); | |||
// update repository cache | |||
removeFromCachedRepositoryList(repositoryName); | |||
// model will actually be replaced on next load because config is stale |
@@ -32,6 +32,7 @@ import java.util.Map; | |||
import java.util.Map.Entry; | |||
import java.util.regex.Pattern; | |||
import org.apache.commons.io.filefilter.TrueFileFilter; | |||
import org.eclipse.jgit.api.CloneCommand; | |||
import org.eclipse.jgit.api.FetchCommand; | |||
import org.eclipse.jgit.api.Git; | |||
@@ -58,6 +59,7 @@ import org.eclipse.jgit.lib.RefUpdate; | |||
import org.eclipse.jgit.lib.RefUpdate.Result; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.lib.RepositoryCache.FileKey; | |||
import org.eclipse.jgit.lib.StoredConfig; | |||
import org.eclipse.jgit.lib.TreeFormatter; | |||
import org.eclipse.jgit.revwalk.RevBlob; | |||
import org.eclipse.jgit.revwalk.RevCommit; | |||
@@ -259,14 +261,188 @@ public class JGitUtils { | |||
* @return Repository | |||
*/ | |||
public static Repository createRepository(File repositoriesFolder, String name) { | |||
return createRepository(repositoriesFolder, name, "FALSE"); | |||
} | |||
/** | |||
* Creates a bare, shared repository. | |||
* | |||
* @param repositoriesFolder | |||
* @param name | |||
* @param shared | |||
* the setting for the --shared option of "git init". | |||
* @return Repository | |||
*/ | |||
public static Repository createRepository(File repositoriesFolder, String name, String shared) { | |||
try { | |||
Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call(); | |||
return git.getRepository(); | |||
} catch (GitAPIException e) { | |||
Repository repo = null; | |||
try { | |||
Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call(); | |||
repo = git.getRepository(); | |||
} catch (GitAPIException e) { | |||
throw new RuntimeException(e); | |||
} | |||
GitConfigSharedRepository sharedRepository = new GitConfigSharedRepository(shared); | |||
if (sharedRepository.isShared()) { | |||
StoredConfig config = repo.getConfig(); | |||
config.setString("core", null, "sharedRepository", sharedRepository.getValue()); | |||
config.setBoolean("receive", null, "denyNonFastforwards", true); | |||
config.save(); | |||
if (! JnaUtils.isWindows()) { | |||
Iterator<File> iter = org.apache.commons.io.FileUtils.iterateFilesAndDirs(repo.getDirectory(), | |||
TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE); | |||
// Adjust permissions on file/directory | |||
while (iter.hasNext()) { | |||
adjustSharedPerm(iter.next(), sharedRepository); | |||
} | |||
} | |||
} | |||
return repo; | |||
} catch (IOException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
private enum GitConfigSharedRepositoryValue | |||
{ | |||
UMASK("0", 0), FALSE("0", 0), OFF("0", 0), NO("0", 0), | |||
GROUP("1", 0660), TRUE("1", 0660), ON("1", 0660), YES("1", 0660), | |||
ALL("2", 0664), WORLD("2", 0664), EVERYBODY("2", 0664), | |||
Oxxx(null, -1); | |||
private String configValue; | |||
private int permValue; | |||
private GitConfigSharedRepositoryValue(String config, int perm) { configValue = config; permValue = perm; }; | |||
public String getConfigValue() { return configValue; }; | |||
public int getPerm() { return permValue; }; | |||
} | |||
private static class GitConfigSharedRepository | |||
{ | |||
private int intValue; | |||
private GitConfigSharedRepositoryValue enumValue; | |||
GitConfigSharedRepository(String s) { | |||
if ( s == null || s.trim().isEmpty() ) { | |||
enumValue = GitConfigSharedRepositoryValue.GROUP; | |||
} | |||
else { | |||
try { | |||
// Try one of the string values | |||
enumValue = GitConfigSharedRepositoryValue.valueOf(s.trim().toUpperCase()); | |||
} catch (IllegalArgumentException iae) { | |||
try { | |||
// Try if this is an octal number | |||
int i = Integer.parseInt(s, 8); | |||
if ( (i & 0600) != 0600 ) { | |||
String msg = String.format("Problem with core.sharedRepository filemode value (0%03o).\nThe owner of files must always have read and write permissions.", i); | |||
throw new IllegalArgumentException(msg); | |||
} | |||
intValue = i & 0666; | |||
enumValue = GitConfigSharedRepositoryValue.Oxxx; | |||
} catch (NumberFormatException nfe) { | |||
throw new IllegalArgumentException("Bad configuration value for 'shared': '" + s + "'"); | |||
} | |||
} | |||
} | |||
} | |||
String getValue() { | |||
if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) { | |||
if (intValue == 0) return "0"; | |||
return String.format("0%o", intValue); | |||
} | |||
return enumValue.getConfigValue(); | |||
} | |||
int getPerm() { | |||
if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return intValue; | |||
return enumValue.getPerm(); | |||
} | |||
boolean isCustom() { | |||
return enumValue == GitConfigSharedRepositoryValue.Oxxx; | |||
} | |||
boolean isShared() { | |||
return (enumValue.getPerm() > 0) || enumValue == GitConfigSharedRepositoryValue.Oxxx; | |||
} | |||
} | |||
/** | |||
* Adjust file permissions of a file/directory for shared repositories | |||
* | |||
* @param path | |||
* File that should get its permissions changed. | |||
* @param configShared | |||
* Configuration string value for the shared mode. | |||
* @return Upon successful completion, a value of 0 is returned. Otherwise, a value of -1 is returned. | |||
*/ | |||
public static int adjustSharedPerm(File path, String configShared) { | |||
return adjustSharedPerm(path, new GitConfigSharedRepository(configShared)); | |||
} | |||
/** | |||
* Adjust file permissions of a file/directory for shared repositories | |||
* | |||
* @param path | |||
* File that should get its permissions changed. | |||
* @param configShared | |||
* Configuration setting for the shared mode. | |||
* @return Upon successful completion, a value of 0 is returned. Otherwise, a value of -1 is returned. | |||
*/ | |||
public static int adjustSharedPerm(File path, GitConfigSharedRepository configShared) { | |||
if (! configShared.isShared()) return 0; | |||
if (! path.exists()) return -1; | |||
int perm = configShared.getPerm(); | |||
JnaUtils.Filestat stat = JnaUtils.getFilestat(path); | |||
if (stat == null) return -1; | |||
int mode = stat.mode; | |||
if (mode < 0) return -1; | |||
// Now, here is the kicker: Under Linux, chmod'ing a sgid file whose guid is different from the process' | |||
// effective guid will reset the sgid flag of the file. Since there is no way to get the sgid flag back in | |||
// that case, we decide to rather not touch is and getting the right permissions will have to be achieved | |||
// in a different way, e.g. by using an appropriate umask for the Gitblit process. | |||
if (System.getProperty("os.name").toLowerCase().startsWith("linux")) { | |||
if ( ((mode & (JnaUtils.S_ISGID | JnaUtils.S_ISUID)) != 0) | |||
&& stat.gid != JnaUtils.getegid() ) { | |||
LOGGER.debug("Not adjusting permissions to prevent clearing suid/sgid bits for '" + path + "'" ); | |||
return 0; | |||
} | |||
} | |||
// If the owner has no write access, delete it from group and other, too. | |||
if ((mode & JnaUtils.S_IWUSR) == 0) perm &= ~0222; | |||
// If the owner has execute access, set it for all blocks that have read access. | |||
if ((mode & JnaUtils.S_IXUSR) == JnaUtils.S_IXUSR) perm |= (perm & 0444) >> 2; | |||
if (configShared.isCustom()) { | |||
// Use the custom value for access permissions. | |||
mode = (mode & ~0777) | perm; | |||
} | |||
else { | |||
// Just add necessary bits to existing permissions. | |||
mode |= perm; | |||
} | |||
if (path.isDirectory()) { | |||
mode |= (mode & 0444) >> 2; | |||
mode |= JnaUtils.S_ISGID; | |||
} | |||
return JnaUtils.setFilemode(path, mode); | |||
} | |||
/** | |||
* Returns a list of repository names in the specified folder. | |||
* |
@@ -0,0 +1,364 @@ | |||
/* | |||
* 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.utils; | |||
import com.sun.jna.Library; | |||
import com.sun.jna.Native; | |||
import java.io.BufferedReader; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStreamReader; | |||
import java.util.regex.Matcher; | |||
import java.util.regex.Pattern; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
/** | |||
* Collection of static methods to access native OS library functionality. | |||
* | |||
* @author Florian Zschocke | |||
*/ | |||
public class JnaUtils { | |||
public static final int S_ISUID = 0004000; // set user id on execution | |||
public static final int S_ISGID = 0002000; // set group id on execution | |||
public static final int S_ISVTX = 0001000; // sticky bit, save swapped text even after use | |||
public static final int S_IRWXU = 0000700; // RWX mask for owner | |||
public static final int S_IRUSR = 0000400; // read permission for owner | |||
public static final int S_IWUSR = 0000200; // write permission for owner | |||
public static final int S_IXUSR = 0000100; // execute/search permission for owner | |||
public static final int S_IRWXG = 0000070; // RWX mask for group | |||
public static final int S_IRGRP = 0000040; // read permission for group | |||
public static final int S_IWGRP = 0000020; // write permission for group | |||
public static final int S_IXGRP = 0000010; // execute/search permission for group | |||
public static final int S_IRWXO = 0000007; // RWX mask for other | |||
public static final int S_IROTH = 0000004; // read permission for other | |||
public static final int S_IWOTH = 0000002; // write permission for other | |||
public static final int S_IXOTH = 0000001; // execute/search permission for other | |||
public static final int S_IFMT = 0170000; // type of file mask | |||
public static final int S_IFIFO = 0010000; // named pipe (fifo) | |||
public static final int S_IFCHR = 0020000; // character special device | |||
public static final int S_IFDIR = 0040000; // directory | |||
public static final int S_IFBLK = 0060000; // block special device | |||
public static final int S_IFREG = 0100000; // regular file | |||
public static final int S_IFLNK = 0120000; // symbolic link | |||
public static final int S_IFSOCK = 0140000; // socket | |||
private static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class); | |||
private static UnixCLibrary unixlibc = null; | |||
/** | |||
* Utility method to check if the JVM is running on a Windows OS. | |||
* | |||
* @return true, if the system property 'os.name' starts with 'Windows'. | |||
*/ | |||
public static boolean isWindows() | |||
{ | |||
return System.getProperty("os.name").toLowerCase().startsWith("windows"); | |||
} | |||
private interface UnixCLibrary extends Library { | |||
public int chmod(String path, int mode); | |||
public int getgid(); | |||
public int getegid(); | |||
} | |||
public static int getgid() | |||
{ | |||
if (isWindows()) { | |||
throw new UnsupportedOperationException("The method JnaUtils.getgid is not supported under Windows."); | |||
} | |||
return getUnixCLibrary().getgid(); | |||
} | |||
public static int getegid() | |||
{ | |||
if (isWindows()) { | |||
throw new UnsupportedOperationException("The method JnaUtils.getegid is not supported under Windows."); | |||
} | |||
return getUnixCLibrary().getegid(); | |||
} | |||
/** | |||
* Set the permission bits of a file. | |||
* | |||
* The permission bits are set to the provided mode. This method is only | |||
* implemented for OSes of the Unix family and makes use of the 'chmod' | |||
* function of the native C library. See 'man 2 chmod' for more information. | |||
* | |||
* @param path | |||
* File/directory to set the permission bits for. | |||
* @param mode | |||
* A mode created from or'd permission bit masks S_I* | |||
* @return Upon successful completion, a value of 0 returned. Otherwise, a value of -1 is returned. | |||
*/ | |||
public static int setFilemode(File file, int mode) | |||
{ | |||
return setFilemode(file.getAbsolutePath(), mode); | |||
} | |||
/** | |||
* Set the permission bits of a file. | |||
* | |||
* The permission bits are set to the provided mode. This method is only | |||
* implemented for OSes of the Unix family and makes use of the 'chmod' | |||
* function of the native C library. See 'man 2 chmod' for more information. | |||
* | |||
* @param path | |||
* Path to a file/directory to set the permission bits for. | |||
* @param mode | |||
* A mode created from or'd permission bit masks S_I* | |||
* @return Upon successful completion, a value of 0 returned. Otherwise, a value of -1 is returned. | |||
*/ | |||
public static int setFilemode(String path, int mode) | |||
{ | |||
if (isWindows()) { | |||
throw new UnsupportedOperationException("The method JnaUtils.getFilemode is not supported under Windows."); | |||
} | |||
return getUnixCLibrary().chmod(path, mode); | |||
} | |||
/** | |||
* Get the file mode bits of a file. | |||
* | |||
* This method is only implemented for OSes of the Unix family. It returns the file mode | |||
* information as available in the st_mode member of the resulting struct stat when calling | |||
* 'lstat' on a file. | |||
* | |||
* @param path | |||
* File/directory to get the file mode from. | |||
* @return Upon successful completion, the file mode bits are returned. Otherwise, a value of -1 is returned. | |||
*/ | |||
public static int getFilemode(File path) | |||
{ | |||
return getFilemode(path.getAbsolutePath()); | |||
} | |||
/** | |||
* Get the file mode bits of a file. | |||
* | |||
* This method is only implemented for OSes of the Unix family. It returns the file mode | |||
* information as available in the st_mode member of the resulting struct stat when calling | |||
* 'lstat' on a file. | |||
* | |||
* @param path | |||
* Path to a file/directory to get the file mode from. | |||
* @return Upon successful completion, the file mode bits are returned. Otherwise, a value of -1 is returned. | |||
*/ | |||
public static int getFilemode(String path) | |||
{ | |||
if (isWindows()) { | |||
throw new UnsupportedOperationException("The method JnaUtils.getFilemode is not supported under Windows."); | |||
} | |||
Filestat stat = getFilestat(path); | |||
if ( stat == null ) return -1; | |||
return stat.mode; | |||
} | |||
/** | |||
* Status information of a file. | |||
*/ | |||
public static class Filestat | |||
{ | |||
public int mode; // file mode, permissions, type | |||
public int uid; // user Id of owner | |||
public int gid; // group Id of owner | |||
Filestat(int mode, int uid, int gid) { | |||
this.mode = mode; this.uid = uid; this.gid = gid; | |||
} | |||
} | |||
/** | |||
* Get Unix file status information for a file. | |||
* | |||
* This method is only implemented for OSes of the Unix family. It returns file status | |||
* information for a file. Currently this is the file mode, the user id and group id of the owner. | |||
* | |||
* @param path | |||
* File/directory to get the file status from. | |||
* @return Upon successful completion, a Filestat object containing the file information is returned. | |||
* Otherwise, null is returned. | |||
*/ | |||
public static Filestat getFilestat(File path) | |||
{ | |||
return getFilestat(path.getAbsolutePath()); | |||
} | |||
/** | |||
* Get Unix file status information for a file. | |||
* | |||
* This method is only implemented for OSes of the Unix family. It returns file status | |||
* information for a file. Currently this is the file mode, the user id and group id of the owner. | |||
* | |||
* @param path | |||
* Path to a file/directory to get the file status from. | |||
* @return Upon successful completion, a Filestat object containing the file information is returned. | |||
* Otherwise, null is returned. | |||
*/ | |||
public static Filestat getFilestat(String path) | |||
{ | |||
if (isWindows()) { | |||
throw new UnsupportedOperationException("The method JnaUtils.getFilestat is not supported under Windows."); | |||
} | |||
int mode = 0; | |||
// Use a Runtime, because implementing stat() via JNA is just too much trouble. | |||
// This could be done with the 'stat' command, too. But that may have a shell specific implementation, so we use 'ls' instead. | |||
String lsLine = runProcessLs(path); | |||
if (lsLine == null) { | |||
LOGGER.debug("Could not get file information for path " + path); | |||
return null; | |||
} | |||
Pattern p = Pattern.compile("^(([-bcdlspCDMnP?])([-r][-w][-xSs])([-r][-w][-xSs])([-r][-w][-xTt]))[@+.]? +[0-9]+ +([0-9]+) +([0-9]+) "); | |||
Matcher m = p.matcher(lsLine); | |||
if ( !m.lookingAt() ) { | |||
LOGGER.debug("Could not parse valid file mode information for path " + path); | |||
return null; | |||
} | |||
// Parse mode string to mode bits | |||
String group = m.group(2); | |||
switch (group.charAt(0)) { | |||
case 'p' : | |||
mode |= 0010000; break; | |||
case 'c': | |||
mode |= 0020000; break; | |||
case 'd': | |||
mode |= 0040000; break; | |||
case 'b': | |||
mode |= 0060000; break; | |||
case '-': | |||
mode |= 0100000; break; | |||
case 'l': | |||
mode |= 0120000; break; | |||
case 's': | |||
mode |= 0140000; break; | |||
} | |||
for ( int i = 0; i < 3; i++) { | |||
group = m.group(3 + i); | |||
switch (group.charAt(0)) { | |||
case 'r': | |||
mode |= (0400 >> i*3); break; | |||
case '-': | |||
break; | |||
} | |||
switch (group.charAt(1)) { | |||
case 'w': | |||
mode |= (0200 >> i*3); break; | |||
case '-': | |||
break; | |||
} | |||
switch (group.charAt(2)) { | |||
case 'x': | |||
mode |= (0100 >> i*3); break; | |||
case 'S': | |||
mode |= (04000 >> i); break; | |||
case 's': | |||
mode |= (0100 >> i*3); | |||
mode |= (04000 >> i); break; | |||
case 'T': | |||
mode |= 01000; break; | |||
case 't': | |||
mode |= (0100 >> i*3); | |||
mode |= 01000; break; | |||
case '-': | |||
break; | |||
} | |||
} | |||
return new Filestat(mode, Integer.parseInt(m.group(6)), Integer.parseInt(m.group(7))); | |||
} | |||
/** | |||
* Run the unix command 'ls -ldn' on a single file and return the resulting output line. | |||
* | |||
* @param path | |||
* Path to a single file or directory. | |||
* @return The first line of output from the 'ls' command. Null, if an error occurred and no line could be read. | |||
*/ | |||
private static String runProcessLs(String path) | |||
{ | |||
String cmd = "ls -ldn " + path; | |||
Runtime rt = Runtime.getRuntime(); | |||
Process pr = null; | |||
InputStreamReader ir = null; | |||
BufferedReader br = null; | |||
String output = null; | |||
try { | |||
pr = rt.exec(cmd); | |||
ir = new InputStreamReader(pr.getInputStream()); | |||
br = new BufferedReader(ir); | |||
output = br.readLine(); | |||
while (br.readLine() != null) ; // Swallow remaining output | |||
} | |||
catch (IOException e) { | |||
LOGGER.debug("Exception while running unix command '" + cmd + "': " + e); | |||
} | |||
finally { | |||
if (pr != null) try { pr.waitFor(); } catch (Exception ignored) {} | |||
if (br != null) try { br.close(); } catch (Exception ignored) {} | |||
if (ir != null) try { ir.close(); } catch (Exception ignored) {} | |||
if (pr != null) try { pr.getOutputStream().close(); } catch (Exception ignored) {} | |||
if (pr != null) try { pr.getInputStream().close(); } catch (Exception ignored) {} | |||
if (pr != null) try { pr.getErrorStream().close(); } catch (Exception ignored) {} | |||
} | |||
return output; | |||
} | |||
private static UnixCLibrary getUnixCLibrary() | |||
{ | |||
if (unixlibc == null) { | |||
unixlibc = (UnixCLibrary) Native.loadLibrary("c", UnixCLibrary.class); | |||
if (unixlibc == null) throw new RuntimeException("Could not initialize native C library."); | |||
} | |||
return unixlibc; | |||
} | |||
} |
@@ -52,6 +52,7 @@ import com.gitblit.models.PathModel.PathChangeModel; | |||
import com.gitblit.models.RefModel; | |||
import com.gitblit.utils.CompressionUtils; | |||
import com.gitblit.utils.JGitUtils; | |||
import com.gitblit.utils.JnaUtils; | |||
import com.gitblit.utils.StringUtils; | |||
public class JGitUtilsTest { | |||
@@ -148,6 +149,139 @@ public class JGitUtilsTest { | |||
} | |||
} | |||
@Test | |||
public void testCreateRepositoryShared() throws Exception { | |||
String[] repositories = { "NewSharedTestRepository.git" }; | |||
for (String repositoryName : repositories) { | |||
Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, | |||
repositoryName, "group"); | |||
File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName), | |||
FS.DETECTED); | |||
assertNotNull(repository); | |||
assertFalse(JGitUtils.hasCommits(repository)); | |||
assertNull(JGitUtils.getFirstCommit(repository, null)); | |||
assertEquals("1", repository.getConfig().getString("core", null, "sharedRepository")); | |||
assertTrue(folder.exists()); | |||
if (! JnaUtils.isWindows()) { | |||
int mode = JnaUtils.getFilemode(folder); | |||
assertEquals(JnaUtils.S_ISGID, mode & JnaUtils.S_ISGID); | |||
assertEquals(JnaUtils.S_IRWXG, mode & JnaUtils.S_IRWXG); | |||
mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/HEAD"); | |||
assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG); | |||
mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/config"); | |||
assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG); | |||
} | |||
repository.close(); | |||
RepositoryCache.close(repository); | |||
FileUtils.delete(repository.getDirectory(), FileUtils.RECURSIVE); | |||
} | |||
} | |||
@Test | |||
public void testCreateRepositorySharedCustom() throws Exception { | |||
String[] repositories = { "NewSharedTestRepository.git" }; | |||
for (String repositoryName : repositories) { | |||
Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, | |||
repositoryName, "660"); | |||
File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName), | |||
FS.DETECTED); | |||
assertNotNull(repository); | |||
assertFalse(JGitUtils.hasCommits(repository)); | |||
assertNull(JGitUtils.getFirstCommit(repository, null)); | |||
assertEquals("0660", repository.getConfig().getString("core", null, "sharedRepository")); | |||
assertTrue(folder.exists()); | |||
if (! JnaUtils.isWindows()) { | |||
int mode = JnaUtils.getFilemode(folder); | |||
assertEquals(JnaUtils.S_ISGID, mode & JnaUtils.S_ISGID); | |||
assertEquals(JnaUtils.S_IRWXG, mode & JnaUtils.S_IRWXG); | |||
assertEquals(0, mode & JnaUtils.S_IRWXO); | |||
mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/HEAD"); | |||
assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG); | |||
assertEquals(0, mode & JnaUtils.S_IRWXO); | |||
mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/config"); | |||
assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG); | |||
assertEquals(0, mode & JnaUtils.S_IRWXO); | |||
} | |||
repository.close(); | |||
RepositoryCache.close(repository); | |||
FileUtils.delete(repository.getDirectory(), FileUtils.RECURSIVE); | |||
} | |||
} | |||
@Test | |||
public void testCreateRepositorySharedSgidParent() throws Exception { | |||
if (! JnaUtils.isWindows()) { | |||
String repositoryAll = "NewTestRepositoryAll.git"; | |||
String repositoryUmask = "NewTestRepositoryUmask.git"; | |||
String sgidParent = "sgid"; | |||
File parent = new File(GitBlitSuite.REPOSITORIES, sgidParent); | |||
File folder = null; | |||
boolean parentExisted = parent.exists(); | |||
try { | |||
if (!parentExisted) { | |||
assertTrue("Could not create SGID parent folder.", parent.mkdir()); | |||
} | |||
int mode = JnaUtils.getFilemode(parent); | |||
assertTrue(mode > 0); | |||
assertEquals(0, JnaUtils.setFilemode(parent, mode | JnaUtils.S_ISGID | JnaUtils.S_IWGRP)); | |||
Repository repository = JGitUtils.createRepository(parent, repositoryAll, "all"); | |||
folder = FileKey.resolve(new File(parent, repositoryAll), FS.DETECTED); | |||
assertNotNull(repository); | |||
assertEquals("2", repository.getConfig().getString("core", null, "sharedRepository")); | |||
assertTrue(folder.exists()); | |||
mode = JnaUtils.getFilemode(folder); | |||
assertEquals(JnaUtils.S_ISGID, mode & JnaUtils.S_ISGID); | |||
mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/HEAD"); | |||
assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG); | |||
assertEquals(JnaUtils.S_IROTH, mode & JnaUtils.S_IRWXO); | |||
mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/config"); | |||
assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG); | |||
assertEquals(JnaUtils.S_IROTH, mode & JnaUtils.S_IRWXO); | |||
repository.close(); | |||
RepositoryCache.close(repository); | |||
repository = JGitUtils.createRepository(parent, repositoryUmask, "umask"); | |||
folder = FileKey.resolve(new File(parent, repositoryUmask), FS.DETECTED); | |||
assertNotNull(repository); | |||
assertEquals(null, repository.getConfig().getString("core", null, "sharedRepository")); | |||
assertTrue(folder.exists()); | |||
mode = JnaUtils.getFilemode(folder); | |||
assertEquals(JnaUtils.S_ISGID, mode & JnaUtils.S_ISGID); | |||
repository.close(); | |||
RepositoryCache.close(repository); | |||
} | |||
finally { | |||
FileUtils.delete(new File(parent, repositoryAll), FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS); | |||
FileUtils.delete(new File(parent, repositoryUmask), FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS); | |||
if (!parentExisted) { | |||
FileUtils.delete(parent, FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS); | |||
} | |||
} | |||
} | |||
} | |||
@Test | |||
public void testRefs() throws Exception { | |||
Repository repository = GitBlitSuite.getJGitRepository(); |
@@ -0,0 +1,152 @@ | |||
/* | |||
* Copyright 2011 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.tests; | |||
import com.gitblit.utils.JGitUtils; | |||
import com.gitblit.utils.JnaUtils; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertTrue; | |||
import org.apache.commons.io.FileUtils; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.lib.RepositoryCache; | |||
import org.eclipse.jgit.lib.RepositoryCache.FileKey; | |||
import org.eclipse.jgit.util.FS; | |||
import org.junit.Test; | |||
/** | |||
* | |||
* @author Florian Zschocke | |||
*/ | |||
public class JnaUtilsTest { | |||
@Test | |||
public void testGetgid() { | |||
if (JnaUtils.isWindows()) { | |||
try { | |||
JnaUtils.getFilemode(GitBlitSuite.REPOSITORIES); | |||
} catch(UnsupportedOperationException e) {} | |||
} | |||
else { | |||
int gid = JnaUtils.getgid(); | |||
assertTrue(gid >= 0); | |||
int egid = JnaUtils.getegid(); | |||
assertTrue(egid >= 0); | |||
assertTrue("Really? You're running unit tests as root?!", gid > 0); | |||
System.out.println("gid: " + gid + " egid: " + egid); | |||
} | |||
} | |||
@Test | |||
public void testGetFilemode() throws IOException { | |||
if (JnaUtils.isWindows()) { | |||
try { | |||
JnaUtils.getFilemode(GitBlitSuite.REPOSITORIES); | |||
} catch(UnsupportedOperationException e) {} | |||
} | |||
else { | |||
String repositoryName = "NewJnaTestRepository.git"; | |||
Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, repositoryName); | |||
File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName), FS.DETECTED); | |||
assertTrue(folder.exists()); | |||
int mode = JnaUtils.getFilemode(folder); | |||
assertTrue(mode > 0); | |||
assertEquals(JnaUtils.S_IFDIR, (mode & JnaUtils.S_IFMT)); // directory | |||
assertEquals(JnaUtils.S_IRUSR | JnaUtils.S_IWUSR | JnaUtils.S_IXUSR, (mode & JnaUtils.S_IRWXU)); // owner full access | |||
mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/config"); | |||
assertTrue(mode > 0); | |||
assertEquals(JnaUtils.S_IFREG, (mode & JnaUtils.S_IFMT)); // directory | |||
assertEquals(JnaUtils.S_IRUSR | JnaUtils.S_IWUSR, (mode & JnaUtils.S_IRWXU)); // owner full access | |||
repository.close(); | |||
RepositoryCache.close(repository); | |||
FileUtils.deleteDirectory(repository.getDirectory()); | |||
} | |||
} | |||
@Test | |||
public void testSetFilemode() throws IOException { | |||
if (JnaUtils.isWindows()) { | |||
try { | |||
JnaUtils.getFilemode(GitBlitSuite.REPOSITORIES); | |||
} catch(UnsupportedOperationException e) {} | |||
} | |||
else { | |||
String repositoryName = "NewJnaTestRepository.git"; | |||
Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, repositoryName); | |||
File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName), FS.DETECTED); | |||
assertTrue(folder.exists()); | |||
File path = new File(folder, "refs"); | |||
int mode = JnaUtils.getFilemode(path); | |||
assertTrue(mode > 0); | |||
assertEquals(JnaUtils.S_IFDIR, (mode & JnaUtils.S_IFMT)); // directory | |||
assertEquals(JnaUtils.S_IRUSR | JnaUtils.S_IWUSR | JnaUtils.S_IXUSR, (mode & JnaUtils.S_IRWXU)); // owner full access | |||
mode |= JnaUtils.S_ISGID; | |||
mode |= JnaUtils.S_IRWXG; | |||
int ret = JnaUtils.setFilemode(path, mode); | |||
assertEquals(0, ret); | |||
mode = JnaUtils.getFilemode(path); | |||
assertTrue(mode > 0); | |||
assertEquals(JnaUtils.S_ISGID, (mode & JnaUtils.S_ISGID)); // set-gid-bit set | |||
assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP | JnaUtils.S_IXGRP, (mode & JnaUtils.S_IRWXG)); // group full access | |||
path = new File(folder, "config"); | |||
mode = JnaUtils.getFilemode(path.getAbsolutePath()); | |||
assertTrue(mode > 0); | |||
assertEquals(JnaUtils.S_IFREG, (mode & JnaUtils.S_IFMT)); // directory | |||
assertEquals(JnaUtils.S_IRUSR | JnaUtils.S_IWUSR, (mode & JnaUtils.S_IRWXU)); // owner full access | |||
mode |= (JnaUtils.S_IRGRP | JnaUtils.S_IWGRP); | |||
ret = JnaUtils.setFilemode(path.getAbsolutePath(), mode); | |||
assertEquals(0, ret); | |||
mode = JnaUtils.getFilemode(path.getAbsolutePath()); | |||
assertTrue(mode > 0); | |||
assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, (mode & JnaUtils.S_IRWXG)); // group full access | |||
repository.close(); | |||
RepositoryCache.close(repository); | |||
FileUtils.deleteDirectory(repository.getDirectory()); | |||
} | |||
} | |||
@Test | |||
public void testGetFilestat() { | |||
if (JnaUtils.isWindows()) { | |||
try { | |||
JnaUtils.getFilemode(GitBlitSuite.REPOSITORIES); | |||
} catch(UnsupportedOperationException e) {} | |||
} | |||
else { | |||
JnaUtils.Filestat stat = JnaUtils.getFilestat(GitBlitSuite.REPOSITORIES); | |||
assertNotNull(stat); | |||
assertTrue(stat.mode > 0); | |||
assertTrue(stat.uid > 0); | |||
assertTrue(stat.gid > 0); | |||
} | |||
} | |||
} |