<classpathentry kind="lib" path="ext/jcalendar-1.3.2.jar" /> | <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/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/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-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/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" /> | <classpathentry kind="lib" path="ext/js-1.7R2.jar" sourcepath="ext/src/js-1.7R2.jar" /> | ||||
<classpathentry kind="lib" path="ext/httpcore-4.2.1.jar" sourcepath="ext/src/httpcore-4.2.1.jar" /> | <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-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-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="output" path="bin/classes" /> | ||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" /> | <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" /> | ||||
</classpath> | </classpath> |
- compile 'org.apache.ivy:ivy:2.2.0' :war | - compile 'org.apache.ivy:ivy:2.2.0' :war | ||||
- compile 'com.toedter:jcalendar:1.3.2' :authority | - compile 'com.toedter:jcalendar:1.3.2' :authority | ||||
- compile 'org.apache.commons:commons-compress:1.4.1' :war | - 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 'com.force.api:force-partner-api:24.0.0' :war | ||||
- compile 'org.freemarker:freemarker:2.3.19' :war | - compile 'org.freemarker:freemarker:2.3.19' :war | ||||
- compile 'com.github.dblock.waffle:waffle-jna:1.5' :war | - compile 'com.github.dblock.waffle:waffle-jna:1.5' :war |
</SOURCES> | </SOURCES> | ||||
</library> | </library> | ||||
</orderEntry> | </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"> | <orderEntry type="module-library"> | ||||
<library name="force-partner-api-24.0.0.jar"> | <library name="force-partner-api-24.0.0.jar"> | ||||
<CLASSES> | <CLASSES> | ||||
</SOURCES> | </SOURCES> | ||||
</library> | </library> | ||||
</orderEntry> | </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" /> | <orderEntry type="inheritedJdk" /> | ||||
</component> | </component> | ||||
</module> | </module> |
# SINCE 1.3.0 | # SINCE 1.3.0 | ||||
git.defaultIncrementalPushTagPrefix = r | 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!!) | # Enable JGit-based garbage collection. (!!EXPERIMENTAL!!) | ||||
# | # | ||||
# USE AT YOUR OWN RISK! | # USE AT YOUR OWN RISK! |
} | } | ||||
// create repository | // create repository | ||||
logger.info("create repository " + repository.name); | 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 { | } else { | ||||
// rename repository | // rename repository | ||||
if (!repositoryName.equalsIgnoreCase(repository.name)) { | if (!repositoryName.equalsIgnoreCase(repository.name)) { | ||||
// close the repository object | // close the repository object | ||||
r.close(); | 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 | // update repository cache | ||||
removeFromCachedRepositoryList(repositoryName); | removeFromCachedRepositoryList(repositoryName); | ||||
// model will actually be replaced on next load because config is stale | // model will actually be replaced on next load because config is stale |
import java.util.Map.Entry; | import java.util.Map.Entry; | ||||
import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||
import org.apache.commons.io.filefilter.TrueFileFilter; | |||||
import org.eclipse.jgit.api.CloneCommand; | import org.eclipse.jgit.api.CloneCommand; | ||||
import org.eclipse.jgit.api.FetchCommand; | import org.eclipse.jgit.api.FetchCommand; | ||||
import org.eclipse.jgit.api.Git; | import org.eclipse.jgit.api.Git; | ||||
import org.eclipse.jgit.lib.RefUpdate.Result; | import org.eclipse.jgit.lib.RefUpdate.Result; | ||||
import org.eclipse.jgit.lib.Repository; | import org.eclipse.jgit.lib.Repository; | ||||
import org.eclipse.jgit.lib.RepositoryCache.FileKey; | import org.eclipse.jgit.lib.RepositoryCache.FileKey; | ||||
import org.eclipse.jgit.lib.StoredConfig; | |||||
import org.eclipse.jgit.lib.TreeFormatter; | import org.eclipse.jgit.lib.TreeFormatter; | ||||
import org.eclipse.jgit.revwalk.RevBlob; | import org.eclipse.jgit.revwalk.RevBlob; | ||||
import org.eclipse.jgit.revwalk.RevCommit; | import org.eclipse.jgit.revwalk.RevCommit; | ||||
* @return Repository | * @return Repository | ||||
*/ | */ | ||||
public static Repository createRepository(File repositoriesFolder, String name) { | 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 { | 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); | 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. | * Returns a list of repository names in the specified folder. | ||||
* | * |
/* | |||||
* 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; | |||||
} | |||||
} |
import com.gitblit.models.RefModel; | import com.gitblit.models.RefModel; | ||||
import com.gitblit.utils.CompressionUtils; | import com.gitblit.utils.CompressionUtils; | ||||
import com.gitblit.utils.JGitUtils; | import com.gitblit.utils.JGitUtils; | ||||
import com.gitblit.utils.JnaUtils; | |||||
import com.gitblit.utils.StringUtils; | import com.gitblit.utils.StringUtils; | ||||
public class JGitUtilsTest { | 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 | @Test | ||||
public void testRefs() throws Exception { | public void testRefs() throws Exception { | ||||
Repository repository = GitBlitSuite.getJGitRepository(); | Repository repository = GitBlitSuite.getJGitRepository(); |
/* | |||||
* 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); | |||||
} | |||||
} | |||||
} |