mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-02 14:37:30 +00:00
Add template dir and set gid to repo init
This extends git_repository_init_ext further with support for initializing the repository from an external template directory and with support for the "create shared" type flags that make a set GID repository directory. This also adds tests for much of the new functionality to the existing `repo/init.c` test suite. Also, this adds a bunch of new utility functions including a very general purpose `git_futils_mkdir` (with the ability to make paths and to chmod the paths post-creation) and a file tree copying function `git_futils_cp_r`. Also, this includes some new path functions that were useful to keep the code simple.
This commit is contained in:
parent
662880ca60
commit
ca1b6e5409
@ -90,6 +90,18 @@ GIT_EXTERN(int) git_config_find_system(char *system_config_path, size_t length);
|
||||
*/
|
||||
GIT_EXTERN(int) git_config_open_global(git_config **out);
|
||||
|
||||
/**
|
||||
* Open the global and system configuration files
|
||||
*
|
||||
* Utility wrapper that finds the global and system configuration files
|
||||
* and opens them into a single prioritized config object that can be
|
||||
* used when accessing config data outside a repository.
|
||||
*
|
||||
* @param out Pointer to store the config instance
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_config_open_outside_repo(git_config **out);
|
||||
|
||||
/**
|
||||
* Create a configuration file backend for ondisk files
|
||||
*
|
||||
|
@ -89,8 +89,11 @@ GIT_EXTERN(int) git_repository_discover(
|
||||
* * GIT_REPOSITORY_OPEN_NO_SEARCH - Only open the repository if it can be
|
||||
* immediately found in the start_path. Do not walk up from the
|
||||
* start_path looking at parent directories.
|
||||
* * GIT_REPOSITORY_OPEN_CROSS_FS - Do not continue search across
|
||||
* filesystem boundaries (as reported by the `stat` system call).
|
||||
* * GIT_REPOSITORY_OPEN_CROSS_FS - Unless this flag is set, open will not
|
||||
* continue searching across filesystem boundaries (i.e. when `st_dev`
|
||||
* changes from the `stat` system call). (E.g. Searching in a user's home
|
||||
* directory "/home/user/source/" will not return "/.git/" as the found
|
||||
* repo if "/" is a different filesystem than "/home".)
|
||||
*/
|
||||
enum {
|
||||
GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0),
|
||||
@ -178,11 +181,6 @@ GIT_EXTERN(int) git_repository_init(
|
||||
* looking the "template_path" from the options if set, or the
|
||||
* `init.templatedir` global config if not, or falling back on
|
||||
* "/usr/share/git-core/templates" if it exists.
|
||||
* * SHARED_UMASK - Use permissions reported by umask - this is default
|
||||
* * SHARED_GROUP - Use "--shared=group" behavior, chmod'ing the new repo
|
||||
* to be group writable and "g+sx" for sticky group assignment.
|
||||
* * SHARED_ALL - Use "--shared=all" behavior, adding world readability.
|
||||
* * SHARED_CUSTOM - Use the `mode` value from the init options struct.
|
||||
*/
|
||||
enum {
|
||||
GIT_REPOSITORY_INIT_BARE = (1u << 0),
|
||||
@ -191,10 +189,25 @@ enum {
|
||||
GIT_REPOSITORY_INIT_MKDIR = (1u << 3),
|
||||
GIT_REPOSITORY_INIT_MKPATH = (1u << 4),
|
||||
GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE = (1u << 5),
|
||||
GIT_REPOSITORY_INIT_SHARED_UMASK = (0u << 6),
|
||||
GIT_REPOSITORY_INIT_SHARED_GROUP = (1u << 6),
|
||||
GIT_REPOSITORY_INIT_SHARED_ALL = (2u << 6),
|
||||
GIT_REPOSITORY_INIT_SHARED_CUSTOM = (3u << 6),
|
||||
};
|
||||
|
||||
/**
|
||||
* Mode options for `git_repository_init_ext`.
|
||||
*
|
||||
* Set the mode field of the `git_repository_init_options` structure
|
||||
* either to the custom mode that you would like, or to one of the
|
||||
* following modes:
|
||||
*
|
||||
* * SHARED_UMASK - Use permissions configured by umask - the default.
|
||||
* * SHARED_GROUP - Use "--shared=group" behavior, chmod'ing the new repo
|
||||
* to be group writable and "g+sx" for sticky group assignment.
|
||||
* * SHARED_ALL - Use "--shared=all" behavior, adding world readability.
|
||||
* * Anything else - Set to custom value.
|
||||
*/
|
||||
enum {
|
||||
GIT_REPOSITORY_INIT_SHARED_UMASK = 0,
|
||||
GIT_REPOSITORY_INIT_SHARED_GROUP = 0002775,
|
||||
GIT_REPOSITORY_INIT_SHARED_ALL = 0002777,
|
||||
};
|
||||
|
||||
/**
|
||||
@ -204,13 +217,13 @@ enum {
|
||||
* additional initialization features. The fields are:
|
||||
*
|
||||
* * flags - Combination of GIT_REPOSITORY_INIT flags above.
|
||||
* * mode - When GIT_REPOSITORY_INIT_SHARED_CUSTOM is set, this contains
|
||||
* the mode bits that should be used for directories in the repo.
|
||||
* * mode - Set to one of the standard GIT_REPOSITORY_INIT_SHARED_...
|
||||
* constants above, or to a custom value that you would like.
|
||||
* * workdir_path - The path to the working dir or NULL for default (i.e.
|
||||
* repo_path parent on non-bare repos). If a relative path, this
|
||||
* will be evaluated relative to the repo_path. If this is not the
|
||||
* "natural" working directory, a .git gitlink file will be created
|
||||
* here linking to the repo_path.
|
||||
* repo_path parent on non-bare repos). IF THIS IS RELATIVE PATH,
|
||||
* IT WILL BE EVALUATED RELATIVE TO THE REPO_PATH. If this is not
|
||||
* the "natural" working directory, a .git gitlink file will be
|
||||
* created here linking to the repo_path.
|
||||
* * description - If set, this will be used to initialize the "description"
|
||||
* file in the repository, instead of using the template content.
|
||||
* * template_path - When GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE is set,
|
||||
|
@ -250,18 +250,15 @@ git_attr_assignment *git_attr_rule__lookup_assignment(
|
||||
int git_attr_path__init(
|
||||
git_attr_path *info, const char *path, const char *base)
|
||||
{
|
||||
ssize_t root;
|
||||
|
||||
/* build full path as best we can */
|
||||
git_buf_init(&info->full, 0);
|
||||
|
||||
if (base != NULL && git_path_root(path) < 0) {
|
||||
if (git_buf_joinpath(&info->full, base, path) < 0)
|
||||
return -1;
|
||||
info->path = info->full.ptr + strlen(base);
|
||||
} else {
|
||||
if (git_buf_sets(&info->full, path) < 0)
|
||||
return -1;
|
||||
info->path = info->full.ptr;
|
||||
}
|
||||
if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
|
||||
return -1;
|
||||
|
||||
info->path = info->full.ptr + root;
|
||||
|
||||
/* remove trailing slashes */
|
||||
while (info->full.size > 0) {
|
||||
|
25
src/config.c
25
src/config.c
@ -515,3 +515,28 @@ int git_config_open_global(git_config **out)
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_config_open_outside_repo(git_config **out)
|
||||
{
|
||||
int error;
|
||||
git_config *cfg = NULL;
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
|
||||
error = git_config_new(&cfg);
|
||||
|
||||
if (!error && !git_config_find_global_r(&buf))
|
||||
error = git_config_add_file_ondisk(cfg, buf.ptr, 2);
|
||||
|
||||
if (!error && !git_config_find_system_r(&buf))
|
||||
error = git_config_add_file_ondisk(cfg, buf.ptr, 1);
|
||||
|
||||
git_buf_free(&buf);
|
||||
|
||||
if (error && cfg) {
|
||||
git_config_free(cfg);
|
||||
cfg = NULL;
|
||||
}
|
||||
|
||||
*out = cfg;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
350
src/fileops.c
350
src/fileops.c
@ -10,19 +10,8 @@
|
||||
|
||||
int git_futils_mkpath2file(const char *file_path, const mode_t mode)
|
||||
{
|
||||
int result = 0;
|
||||
git_buf target_folder = GIT_BUF_INIT;
|
||||
|
||||
if (git_path_dirname_r(&target_folder, file_path) < 0)
|
||||
return -1;
|
||||
|
||||
/* Does the containing folder exist? */
|
||||
if (git_path_isdir(target_folder.ptr) == false)
|
||||
/* Let's create the tree structure */
|
||||
result = git_futils_mkdir_r(target_folder.ptr, NULL, mode);
|
||||
|
||||
git_buf_free(&target_folder);
|
||||
return result;
|
||||
return git_futils_mkdir(
|
||||
file_path, NULL, mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST);
|
||||
}
|
||||
|
||||
int git_futils_mktmp(git_buf *path_out, const char *filename)
|
||||
@ -239,76 +228,90 @@ void git_futils_mmap_free(git_map *out)
|
||||
p_munmap(out);
|
||||
}
|
||||
|
||||
int git_futils_mkdir_q(const char *path, const mode_t mode)
|
||||
int git_futils_mkdir(
|
||||
const char *path,
|
||||
const char *base,
|
||||
mode_t mode,
|
||||
uint32_t flags)
|
||||
{
|
||||
if (p_mkdir(path, mode) < 0 && errno != EEXIST) {
|
||||
giterr_set(GITERR_OS, "Failed to create directory at '%s'", path);
|
||||
git_buf make_path = GIT_BUF_INIT;
|
||||
ssize_t root = 0;
|
||||
char lastch, *tail;
|
||||
|
||||
/* build path and find "root" where we should start calling mkdir */
|
||||
if (git_path_join_unrooted(&make_path, path, base, &root) < 0)
|
||||
return -1;
|
||||
|
||||
if (make_path.size == 0) {
|
||||
giterr_set(GITERR_OS, "Attempt to create empty path");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* remove trailing slashes on path */
|
||||
while (make_path.ptr[make_path.size - 1] == '/') {
|
||||
make_path.size--;
|
||||
make_path.ptr[make_path.size] = '\0';
|
||||
}
|
||||
|
||||
/* if we are not supposed to made the last element, truncate it */
|
||||
if ((flags & GIT_MKDIR_SKIP_LAST) != 0)
|
||||
git_buf_rtruncate_at_char(&make_path, '/');
|
||||
|
||||
/* if we are not supposed to make the whole path, reset root */
|
||||
if ((flags & GIT_MKDIR_PATH) == 0)
|
||||
root = git_buf_rfind(&make_path, '/');
|
||||
|
||||
/* clip root to make_path length */
|
||||
if (root >= (ssize_t)make_path.size)
|
||||
root = (ssize_t)make_path.size - 1;
|
||||
|
||||
tail = & make_path.ptr[root];
|
||||
|
||||
while (*tail) {
|
||||
/* advance tail to include next path component */
|
||||
while (*tail == '/')
|
||||
tail++;
|
||||
while (*tail && *tail != '/')
|
||||
tail++;
|
||||
|
||||
/* truncate path at next component */
|
||||
lastch = *tail;
|
||||
*tail = '\0';
|
||||
|
||||
/* make directory */
|
||||
if (p_mkdir(make_path.ptr, mode) < 0 &&
|
||||
(errno != EEXIST || (flags & GIT_MKDIR_EXCL) != 0))
|
||||
{
|
||||
giterr_set(GITERR_OS, "Failed to make directory '%s'",
|
||||
make_path.ptr);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* chmod if requested */
|
||||
if ((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||
|
||||
((flags & GIT_MKDIR_CHMOD) != 0 && lastch == '\0'))
|
||||
{
|
||||
if (p_chmod(make_path.ptr, mode) < 0) {
|
||||
giterr_set(GITERR_OS, "Failed to set permissions on '%s'",
|
||||
make_path.ptr);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
*tail = lastch;
|
||||
}
|
||||
|
||||
git_buf_free(&make_path);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
git_buf_free(&make_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
|
||||
{
|
||||
git_buf make_path = GIT_BUF_INIT;
|
||||
size_t start = 0;
|
||||
char *pp, *sp;
|
||||
bool failed = false;
|
||||
|
||||
if (base != NULL) {
|
||||
/*
|
||||
* when a base is being provided, it is supposed to already exist.
|
||||
* Therefore, no attempt is being made to recursively create this leading path
|
||||
* segment. It's just skipped. */
|
||||
start = strlen(base);
|
||||
if (git_buf_joinpath(&make_path, base, path) < 0)
|
||||
return -1;
|
||||
} else {
|
||||
int root_path_offset;
|
||||
|
||||
if (git_buf_puts(&make_path, path) < 0)
|
||||
return -1;
|
||||
|
||||
root_path_offset = git_path_root(make_path.ptr);
|
||||
if (root_path_offset > 0) {
|
||||
/*
|
||||
* On Windows, will skip the drive name (eg. C: or D:)
|
||||
* or the leading part of a network path (eg. //computer_name ) */
|
||||
start = root_path_offset;
|
||||
}
|
||||
}
|
||||
|
||||
pp = make_path.ptr + start;
|
||||
|
||||
while (!failed && (sp = strchr(pp, '/')) != NULL) {
|
||||
if (sp != pp && git_path_isdir(make_path.ptr) == false) {
|
||||
*sp = 0;
|
||||
|
||||
/* Do not choke while trying to recreate an existing directory */
|
||||
if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST)
|
||||
failed = true;
|
||||
|
||||
*sp = '/';
|
||||
}
|
||||
|
||||
pp = sp + 1;
|
||||
}
|
||||
|
||||
if (*pp != '\0' && !failed) {
|
||||
if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST)
|
||||
failed = true;
|
||||
}
|
||||
|
||||
git_buf_free(&make_path);
|
||||
|
||||
if (failed) {
|
||||
giterr_set(GITERR_OS,
|
||||
"Failed to create directory structure at '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH);
|
||||
}
|
||||
|
||||
static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
|
||||
@ -505,3 +508,202 @@ int git_futils_fake_symlink(const char *old, const char *new)
|
||||
}
|
||||
return retcode;
|
||||
}
|
||||
|
||||
static int git_futils_cp_fd(int ifd, int ofd, bool close_fd)
|
||||
{
|
||||
int error = 0;
|
||||
char buffer[4096];
|
||||
ssize_t len = 0;
|
||||
|
||||
while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0)
|
||||
/* p_write() does not have the same semantics as write(). It loops
|
||||
* internally and will return 0 when it has completed writing.
|
||||
*/
|
||||
error = p_write(ofd, buffer, len);
|
||||
|
||||
if (len < 0) {
|
||||
giterr_set(GITERR_OS, "Read error while copying file");
|
||||
error = (int)len;
|
||||
}
|
||||
|
||||
if (close_fd) {
|
||||
p_close(ifd);
|
||||
p_close(ofd);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_futils_cp_withpath(
|
||||
const char *from, const char *to, mode_t filemode, mode_t dirmode)
|
||||
{
|
||||
int ifd, ofd;
|
||||
|
||||
if (git_futils_mkpath2file(to, dirmode) < 0)
|
||||
return -1;
|
||||
|
||||
if ((ifd = git_futils_open_ro(from)) < 0)
|
||||
return ifd;
|
||||
|
||||
if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) {
|
||||
if (errno == ENOENT || errno == ENOTDIR)
|
||||
ofd = GIT_ENOTFOUND;
|
||||
giterr_set(GITERR_OS, "Failed to open '%s' for writing", to);
|
||||
p_close(ifd);
|
||||
return ofd;
|
||||
}
|
||||
|
||||
return git_futils_cp_fd(ifd, ofd, true);
|
||||
}
|
||||
|
||||
static int git_futils_cplink(
|
||||
const char *from, size_t from_filesize, const char *to)
|
||||
{
|
||||
int error = 0;
|
||||
ssize_t read_len;
|
||||
char *link_data = git__malloc(from_filesize + 1);
|
||||
GITERR_CHECK_ALLOC(link_data);
|
||||
|
||||
read_len = p_readlink(from, link_data, from_filesize);
|
||||
if (read_len != (ssize_t)from_filesize) {
|
||||
giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", from);
|
||||
error = -1;
|
||||
}
|
||||
else {
|
||||
link_data[read_len] = '\0';
|
||||
|
||||
if (p_symlink(link_data, to) < 0) {
|
||||
giterr_set(GITERR_OS, "Could not symlink '%s' as '%s'",
|
||||
link_data, to);
|
||||
error = -1;
|
||||
}
|
||||
}
|
||||
|
||||
git__free(link_data);
|
||||
return error;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *to_root;
|
||||
git_buf to;
|
||||
ssize_t from_prefix;
|
||||
uint32_t flags;
|
||||
uint32_t mkdir_flags;
|
||||
mode_t dirmode;
|
||||
} cp_r_info;
|
||||
|
||||
static int _cp_r_callback(void *ref, git_buf *from)
|
||||
{
|
||||
cp_r_info *info = ref;
|
||||
struct stat from_st, to_st;
|
||||
bool exists = false;
|
||||
|
||||
if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 &&
|
||||
from->ptr[git_path_basename_offset(from)] == '.')
|
||||
return 0;
|
||||
|
||||
if (git_buf_joinpath(
|
||||
&info->to, info->to_root, from->ptr + info->from_prefix) < 0)
|
||||
return -1;
|
||||
|
||||
if (p_lstat(info->to.ptr, &to_st) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
giterr_set(GITERR_OS,
|
||||
"Could not access %s while copying files", info->to.ptr);
|
||||
return -1;
|
||||
}
|
||||
} else
|
||||
exists = true;
|
||||
|
||||
if (git_path_lstat(from->ptr, &from_st) < 0)
|
||||
return -1;
|
||||
|
||||
if (S_ISDIR(from_st.st_mode)) {
|
||||
int error = 0;
|
||||
mode_t oldmode = info->dirmode;
|
||||
|
||||
/* if we are not chmod'ing, then overwrite dirmode */
|
||||
if ((info->flags & GIT_CPDIR_CHMOD) == 0)
|
||||
info->dirmode = from_st.st_mode;
|
||||
|
||||
/* make directory now if CREATE_EMPTY_DIRS is requested and needed */
|
||||
if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0)
|
||||
error = git_futils_mkdir(
|
||||
info->to.ptr, NULL, info->dirmode, info->mkdir_flags);
|
||||
|
||||
/* recurse onto target directory */
|
||||
if (!exists || S_ISDIR(to_st.st_mode))
|
||||
error = git_path_direach(from, _cp_r_callback, info);
|
||||
|
||||
if (oldmode != 0)
|
||||
info->dirmode = oldmode;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
if ((info->flags & GIT_CPDIR_OVERWRITE) == 0)
|
||||
return 0;
|
||||
|
||||
if (p_unlink(info->to.ptr) < 0) {
|
||||
giterr_set(GITERR_OS, "Cannot overwrite existing file '%s'",
|
||||
info->to.ptr);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Done if this isn't a regular file or a symlink */
|
||||
if (!S_ISREG(from_st.st_mode) &&
|
||||
(!S_ISLNK(from_st.st_mode) ||
|
||||
(info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0))
|
||||
return 0;
|
||||
|
||||
/* Make container directory on demand if needed */
|
||||
if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 &&
|
||||
git_futils_mkdir(
|
||||
info->to.ptr, NULL, info->dirmode, info->mkdir_flags) < 0)
|
||||
return -1;
|
||||
|
||||
/* make symlink or regular file */
|
||||
if (S_ISLNK(from_st.st_mode))
|
||||
return git_futils_cplink(from->ptr, from_st.st_size, info->to.ptr);
|
||||
else
|
||||
return git_futils_cp_withpath(
|
||||
from->ptr, info->to.ptr, from_st.st_mode, info->dirmode);
|
||||
}
|
||||
|
||||
int git_futils_cp_r(
|
||||
const char *from,
|
||||
const char *to,
|
||||
uint32_t flags,
|
||||
mode_t dirmode)
|
||||
{
|
||||
int error;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
cp_r_info info;
|
||||
|
||||
if (git_buf_sets(&path, from) < 0)
|
||||
return -1;
|
||||
|
||||
info.to_root = to;
|
||||
info.flags = flags;
|
||||
info.dirmode = dirmode;
|
||||
info.from_prefix = path.size;
|
||||
git_buf_init(&info.to, 0);
|
||||
|
||||
/* precalculate mkdir flags */
|
||||
if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) {
|
||||
info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
|
||||
if ((flags & GIT_CPDIR_CHMOD) != 0)
|
||||
info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
|
||||
} else {
|
||||
info.mkdir_flags =
|
||||
((flags & GIT_CPDIR_CHMOD) != 0) ? GIT_MKDIR_CHMOD : 0;
|
||||
}
|
||||
|
||||
error = _cp_r_callback(&info, &path);
|
||||
|
||||
git_buf_free(&path);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
@ -47,19 +47,49 @@ extern int git_futils_creat_locked(const char *path, const mode_t mode);
|
||||
*/
|
||||
extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode);
|
||||
|
||||
/**
|
||||
* Create a directory if it does not exist
|
||||
*/
|
||||
extern int git_futils_mkdir_q(const char *path, const mode_t mode);
|
||||
|
||||
/**
|
||||
* Create a path recursively
|
||||
*
|
||||
* If a base parameter is being passed, it's expected to be valued with a path pointing to an already
|
||||
* exisiting directory.
|
||||
* If a base parameter is being passed, it's expected to be valued with a
|
||||
* path pointing to an already existing directory.
|
||||
*/
|
||||
extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode);
|
||||
|
||||
/**
|
||||
* Flags to pass to `git_futils_mkdir`.
|
||||
*
|
||||
* * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists.
|
||||
* * GIT_MKDIR_PATH says to make all components in the path.
|
||||
* * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation
|
||||
* * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path
|
||||
* * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path
|
||||
*
|
||||
* Note that the chmod options will be executed even if the directory already
|
||||
* exists, unless GIT_MKDIR_EXCL is given.
|
||||
*/
|
||||
typedef enum {
|
||||
GIT_MKDIR_EXCL = 1,
|
||||
GIT_MKDIR_PATH = 2,
|
||||
GIT_MKDIR_CHMOD = 4,
|
||||
GIT_MKDIR_CHMOD_PATH = 8,
|
||||
GIT_MKDIR_SKIP_LAST = 16
|
||||
} git_futils_mkdir_flags;
|
||||
|
||||
/**
|
||||
* Create a directory or entire path.
|
||||
*
|
||||
* This makes a directory (and the entire path leading up to it if requested),
|
||||
* and optionally chmods the directory immediately after (or each part of the
|
||||
* path if requested).
|
||||
*
|
||||
* @param path The path to create.
|
||||
* @param base Root for relative path. These directories will never be made.
|
||||
* @param mode The mode to use for created directories.
|
||||
* @param flags Combination of the mkdir flags above.
|
||||
* @return 0 on success, else error code
|
||||
*/
|
||||
extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Create all the folders required to contain
|
||||
* the full path of a file
|
||||
@ -99,6 +129,47 @@ extern int git_futils_mktmp(git_buf *path_out, const char *filename);
|
||||
*/
|
||||
extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode);
|
||||
|
||||
/**
|
||||
* Copy a file, creating the destination path if needed.
|
||||
*
|
||||
* The filemode will be used for the file and the dirmode will be used for
|
||||
* any intervening directories if necessary.
|
||||
*/
|
||||
extern int git_futils_cp_withpath(
|
||||
const char *from,
|
||||
const char *to,
|
||||
mode_t filemode,
|
||||
mode_t dirmode);
|
||||
|
||||
/**
|
||||
* Flags that can be passed to `git_futils_cp_r`.
|
||||
*/
|
||||
typedef enum {
|
||||
GIT_CPDIR_CREATE_EMPTY_DIRS = 1,
|
||||
GIT_CPDIR_COPY_SYMLINKS = 2,
|
||||
GIT_CPDIR_COPY_DOTFILES = 4,
|
||||
GIT_CPDIR_OVERWRITE = 8,
|
||||
GIT_CPDIR_CHMOD = 16
|
||||
} git_futils_cpdir_flags;
|
||||
|
||||
/**
|
||||
* Copy a directory tree.
|
||||
*
|
||||
* This copies directories and files from one root to another. You can
|
||||
* pass a combinationof GIT_CPDIR flags as defined above.
|
||||
*
|
||||
* If you pass the CHMOD flag, then the dirmode will be applied to all
|
||||
* directories that are created during the copy, overiding the natural
|
||||
* permissions. If you do not pass the CHMOD flag, then the dirmode
|
||||
* will actually be copied from the source files and the `dirmode` arg
|
||||
* will be ignored.
|
||||
*/
|
||||
extern int git_futils_cp_r(
|
||||
const char *from,
|
||||
const char *to,
|
||||
uint32_t flags,
|
||||
mode_t dirmode);
|
||||
|
||||
/**
|
||||
* Open a file readonly and set error if needed.
|
||||
*/
|
||||
|
46
src/path.c
46
src/path.c
@ -147,6 +147,20 @@ char *git_path_basename(const char *path)
|
||||
return basename;
|
||||
}
|
||||
|
||||
size_t git_path_basename_offset(git_buf *buffer)
|
||||
{
|
||||
ssize_t slash;
|
||||
|
||||
if (!buffer || buffer->size <= 0)
|
||||
return 0;
|
||||
|
||||
slash = git_buf_rfind_next(buffer, '/');
|
||||
|
||||
if (slash >= 0 && buffer->ptr[slash] == '/')
|
||||
return (size_t)(slash + 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *git_path_topdir(const char *path)
|
||||
{
|
||||
@ -193,6 +207,31 @@ int git_path_root(const char *path)
|
||||
return -1; /* Not a real error - signals that path is not rooted */
|
||||
}
|
||||
|
||||
int git_path_join_unrooted(
|
||||
git_buf *path_out, const char *path, const char *base, ssize_t *root_at)
|
||||
{
|
||||
int error, root;
|
||||
|
||||
assert(path && path_out);
|
||||
|
||||
root = git_path_root(path);
|
||||
|
||||
if (base != NULL && root < 0) {
|
||||
error = git_buf_joinpath(path_out, base, path);
|
||||
|
||||
if (root_at)
|
||||
*root_at = (ssize_t)strlen(base);
|
||||
}
|
||||
else {
|
||||
error = git_buf_sets(path_out, path);
|
||||
|
||||
if (root_at)
|
||||
*root_at = (root < 0) ? 0 : (ssize_t)root;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_path_prettify(git_buf *path_out, const char *path, const char *base)
|
||||
{
|
||||
char buf[GIT_PATH_MAX];
|
||||
@ -502,12 +541,7 @@ bool git_path_contains_file(git_buf *base, const char *file)
|
||||
|
||||
int git_path_find_dir(git_buf *dir, const char *path, const char *base)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (base != NULL && git_path_root(path) < 0)
|
||||
error = git_buf_joinpath(dir, base, path);
|
||||
else
|
||||
error = git_buf_sets(dir, path);
|
||||
int error = git_path_join_unrooted(dir, path, base, NULL);
|
||||
|
||||
if (!error) {
|
||||
char buf[GIT_PATH_MAX];
|
||||
|
14
src/path.h
14
src/path.h
@ -58,6 +58,11 @@ extern int git_path_dirname_r(git_buf *buffer, const char *path);
|
||||
extern char *git_path_basename(const char *path);
|
||||
extern int git_path_basename_r(git_buf *buffer, const char *path);
|
||||
|
||||
/* Return the offset of the start of the basename. Unlike the other
|
||||
* basename functions, this returns 0 if the path is empty.
|
||||
*/
|
||||
extern size_t git_path_basename_offset(git_buf *buffer);
|
||||
|
||||
extern const char *git_path_topdir(const char *path);
|
||||
|
||||
/**
|
||||
@ -185,6 +190,15 @@ extern bool git_path_contains_dir(git_buf *parent, const char *subdir);
|
||||
*/
|
||||
extern bool git_path_contains_file(git_buf *dir, const char *file);
|
||||
|
||||
/**
|
||||
* Prepend base to unrooted path or just copy path over.
|
||||
*
|
||||
* This will optionally return the index into the path where the "root"
|
||||
* is, either the end of the base directory prefix or the path root.
|
||||
*/
|
||||
extern int git_path_join_unrooted(
|
||||
git_buf *path_out, const char *path, const char *base, ssize_t *root_at);
|
||||
|
||||
/**
|
||||
* Clean up path, prepending base if it is not already rooted.
|
||||
*/
|
||||
|
@ -11,8 +11,15 @@
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifndef S_IFGITLINK
|
||||
#define S_IFGITLINK 0160000
|
||||
#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK)
|
||||
#endif
|
||||
|
||||
/* if S_ISGID is not defined, then don't try to set it */
|
||||
#ifndef S_ISGID
|
||||
#define S_ISGID 0
|
||||
#endif
|
||||
|
||||
#if !defined(O_BINARY)
|
||||
#define O_BINARY 0
|
||||
|
228
src/repository.c
228
src/repository.c
@ -24,6 +24,8 @@
|
||||
|
||||
#define GIT_REPO_VERSION 0
|
||||
|
||||
#define GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
|
||||
|
||||
static void drop_odb(git_repository *repo)
|
||||
{
|
||||
if (repo->_odb != NULL) {
|
||||
@ -681,6 +683,7 @@ static bool is_chmod_supported(const char *file_path)
|
||||
return false;
|
||||
|
||||
_is_supported = (st1.st_mode != st2.st_mode);
|
||||
|
||||
return _is_supported;
|
||||
}
|
||||
|
||||
@ -702,20 +705,45 @@ cleanup:
|
||||
return _is_insensitive;
|
||||
}
|
||||
|
||||
static int repo_init_config(
|
||||
const char *git_dir, git_repository_init_options *opts)
|
||||
static bool are_symlinks_supported(const char *wd_path)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
int fd;
|
||||
struct stat st;
|
||||
static int _symlinks_supported = -1;
|
||||
|
||||
if (_symlinks_supported > -1)
|
||||
return _symlinks_supported;
|
||||
|
||||
if ((fd = git_futils_mktmp(&path, wd_path)) < 0 ||
|
||||
p_close(fd) < 0 ||
|
||||
p_unlink(path.ptr) < 0 ||
|
||||
p_symlink("testing", path.ptr) < 0 ||
|
||||
p_lstat(path.ptr, &st) < 0)
|
||||
_symlinks_supported = false;
|
||||
else
|
||||
_symlinks_supported = (S_ISLNK(st.st_mode) != 0);
|
||||
|
||||
(void)p_unlink(path.ptr);
|
||||
git_buf_free(&path);
|
||||
|
||||
return _symlinks_supported;
|
||||
}
|
||||
|
||||
static int repo_init_config(
|
||||
const char *repo_dir,
|
||||
const char *work_dir,
|
||||
git_repository_init_options *opts)
|
||||
{
|
||||
int error = 0;
|
||||
git_buf cfg_path = GIT_BUF_INIT;
|
||||
git_config *config = NULL;
|
||||
|
||||
#define SET_REPO_CONFIG(type, name, val) {\
|
||||
if (git_config_set_##type(config, name, val) < 0) { \
|
||||
git_buf_free(&cfg_path); \
|
||||
git_config_free(config); \
|
||||
return -1; } \
|
||||
}
|
||||
#define SET_REPO_CONFIG(TYPE, NAME, VAL) do {\
|
||||
if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \
|
||||
goto cleanup; } while (0)
|
||||
|
||||
if (git_buf_joinpath(&cfg_path, git_dir, GIT_CONFIG_FILENAME_INREPO) < 0)
|
||||
if (git_buf_joinpath(&cfg_path, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) {
|
||||
@ -724,12 +752,8 @@ static int repo_init_config(
|
||||
}
|
||||
|
||||
if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0 &&
|
||||
check_repositoryformatversion(config) < 0)
|
||||
{
|
||||
git_buf_free(&cfg_path);
|
||||
git_config_free(config);
|
||||
return -1;
|
||||
}
|
||||
(error = check_repositoryformatversion(config)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
SET_REPO_CONFIG(
|
||||
bool, "core.bare", (opts->flags & GIT_REPOSITORY_INIT_BARE) != 0);
|
||||
@ -738,25 +762,42 @@ static int repo_init_config(
|
||||
SET_REPO_CONFIG(
|
||||
bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path)));
|
||||
|
||||
if (!(opts->flags & GIT_REPOSITORY_INIT_BARE))
|
||||
if (!(opts->flags & GIT_REPOSITORY_INIT_BARE)) {
|
||||
SET_REPO_CONFIG(bool, "core.logallrefupdates", true);
|
||||
|
||||
if (!are_symlinks_supported(work_dir))
|
||||
SET_REPO_CONFIG(bool, "core.symlinks", false);
|
||||
|
||||
if (!(opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD)) {
|
||||
SET_REPO_CONFIG(string, "core.worktree", work_dir);
|
||||
}
|
||||
else if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0) {
|
||||
if ((error = git_config_delete(config, "core.worktree")) < 0)
|
||||
goto cleanup;
|
||||
}
|
||||
} else {
|
||||
if (!are_symlinks_supported(repo_dir))
|
||||
SET_REPO_CONFIG(bool, "core.symlinks", false);
|
||||
}
|
||||
|
||||
if (!(opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) &&
|
||||
is_filesystem_case_insensitive(git_dir))
|
||||
is_filesystem_case_insensitive(repo_dir))
|
||||
SET_REPO_CONFIG(bool, "core.ignorecase", true);
|
||||
|
||||
if (opts->flags & GIT_REPOSITORY_INIT_SHARED_GROUP) {
|
||||
if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) {
|
||||
SET_REPO_CONFIG(int32, "core.sharedrepository", 1);
|
||||
SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true);
|
||||
} else if (opts->flags & GIT_REPOSITORY_INIT_SHARED_ALL) {
|
||||
}
|
||||
else if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) {
|
||||
SET_REPO_CONFIG(int32, "core.sharedrepository", 2);
|
||||
SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
git_buf_free(&cfg_path);
|
||||
git_config_free(config);
|
||||
|
||||
return 0;
|
||||
return error;
|
||||
}
|
||||
|
||||
static int repo_write_template(
|
||||
@ -848,6 +889,17 @@ cleanup:
|
||||
return error;
|
||||
}
|
||||
|
||||
static mode_t pick_dir_mode(git_repository_init_options *opts)
|
||||
{
|
||||
if (opts->mode == GIT_REPOSITORY_INIT_SHARED_UMASK)
|
||||
return 0755;
|
||||
if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP)
|
||||
return (0775 | S_ISGID);
|
||||
if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL)
|
||||
return (0777 | S_ISGID);
|
||||
return opts->mode;
|
||||
}
|
||||
|
||||
#include "repo_template.h"
|
||||
|
||||
static int repo_init_structure(
|
||||
@ -855,8 +907,11 @@ static int repo_init_structure(
|
||||
const char *work_dir,
|
||||
git_repository_init_options *opts)
|
||||
{
|
||||
int error = 0;
|
||||
repo_template_item *tpl;
|
||||
mode_t gid = 0;
|
||||
bool external_tpl =
|
||||
((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0);
|
||||
mode_t dmode = pick_dir_mode(opts);
|
||||
|
||||
/* Hide the ".git" directory */
|
||||
if ((opts->flags & GIT_REPOSITORY_INIT_BARE) != 0) {
|
||||
@ -874,32 +929,60 @@ static int repo_init_structure(
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((opts->flags & GIT_REPOSITORY_INIT_SHARED_GROUP) != 0 ||
|
||||
(opts->flags & GIT_REPOSITORY_INIT_SHARED_ALL) != 0)
|
||||
gid = S_ISGID;
|
||||
/* Copy external template if requested */
|
||||
if (external_tpl) {
|
||||
git_config *cfg;
|
||||
const char *tdir;
|
||||
|
||||
/* TODO: honor GIT_REPOSITORY_INIT_USE_EXTERNAL_TEMPLATE if set */
|
||||
|
||||
/* Copy internal template as needed */
|
||||
|
||||
for (tpl = repo_template; tpl->path; ++tpl) {
|
||||
if (!tpl->content) {
|
||||
if (git_futils_mkdir_r(tpl->path, repo_dir, tpl->mode | gid) < 0)
|
||||
return -1;
|
||||
}
|
||||
if (opts->template_path)
|
||||
tdir = opts->template_path;
|
||||
else if ((error = git_config_open_outside_repo(&cfg)) < 0)
|
||||
return error;
|
||||
else {
|
||||
error = git_config_get_string(&tdir, cfg, "init.templatedir");
|
||||
|
||||
git_config_free(cfg);
|
||||
|
||||
if (error && error != GIT_ENOTFOUND)
|
||||
return error;
|
||||
|
||||
giterr_clear();
|
||||
tdir = GIT_TEMPLATE_DIR;
|
||||
}
|
||||
|
||||
error = git_futils_cp_r(tdir, repo_dir,
|
||||
GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD, dmode);
|
||||
|
||||
if (error < 0) {
|
||||
if (strcmp(tdir, GIT_TEMPLATE_DIR) != 0)
|
||||
return error;
|
||||
|
||||
/* if template was default, ignore error and use internal */
|
||||
giterr_clear();
|
||||
external_tpl = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy internal template
|
||||
* - always ensure existence of dirs
|
||||
* - only create files if no external template was specified
|
||||
*/
|
||||
for (tpl = repo_template; !error && tpl->path; ++tpl) {
|
||||
if (!tpl->content)
|
||||
error = git_futils_mkdir(
|
||||
tpl->path, repo_dir, dmode, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD);
|
||||
else if (!external_tpl) {
|
||||
const char *content = tpl->content;
|
||||
|
||||
if (opts->description && strcmp(tpl->path, GIT_DESC_FILE) == 0)
|
||||
content = opts->description;
|
||||
|
||||
if (repo_write_template(
|
||||
repo_dir, false, tpl->path, tpl->mode, false, content) < 0)
|
||||
return -1;
|
||||
error = repo_write_template(
|
||||
repo_dir, false, tpl->path, tpl->mode, false, content);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return error;
|
||||
}
|
||||
|
||||
static int repo_init_directories(
|
||||
@ -910,6 +993,7 @@ static int repo_init_directories(
|
||||
{
|
||||
int error = 0;
|
||||
bool add_dotgit, has_dotgit, natural_wd;
|
||||
mode_t dirmode;
|
||||
|
||||
/* set up repo path */
|
||||
|
||||
@ -930,15 +1014,9 @@ static int repo_init_directories(
|
||||
|
||||
if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0) {
|
||||
if (opts->workdir_path) {
|
||||
if (git_path_root(opts->workdir_path) < 0) {
|
||||
if (git_path_dirname_r(wd_path, repo_path->ptr) < 0 ||
|
||||
git_buf_putc(wd_path, '/') < 0 ||
|
||||
git_buf_puts(wd_path, opts->workdir_path) < 0)
|
||||
return -1;
|
||||
} else {
|
||||
if (git_buf_sets(wd_path, opts->workdir_path) < 0)
|
||||
return -1;
|
||||
}
|
||||
if (git_path_join_unrooted(
|
||||
wd_path, opts->workdir_path, repo_path->ptr, NULL) < 0)
|
||||
return -1;
|
||||
} else if (has_dotgit) {
|
||||
if (git_path_dirname_r(wd_path, repo_path->ptr) < 0)
|
||||
return -1;
|
||||
@ -962,43 +1040,33 @@ static int repo_init_directories(
|
||||
if (natural_wd)
|
||||
opts->flags |= GIT_REPOSITORY_INIT__NATURAL_WD;
|
||||
|
||||
/* pick mode */
|
||||
|
||||
if ((opts->flags & GIT_REPOSITORY_INIT_SHARED_CUSTOM) != 0)
|
||||
/* leave mode as is */;
|
||||
else if ((opts->flags & GIT_REPOSITORY_INIT_SHARED_GROUP) != 0)
|
||||
opts->mode = 0775 | S_ISGID;
|
||||
else if ((opts->flags & GIT_REPOSITORY_INIT_SHARED_ALL) != 0)
|
||||
opts->mode = 0777 | S_ISGID;
|
||||
else if ((opts->flags & GIT_REPOSITORY_INIT_BARE) != 0)
|
||||
opts->mode = 0755;
|
||||
else
|
||||
opts->mode = 0755;
|
||||
|
||||
/* create directories as needed / requested */
|
||||
|
||||
if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) {
|
||||
error = git_futils_mkdir_r(repo_path->ptr, NULL, opts->mode);
|
||||
dirmode = pick_dir_mode(opts);
|
||||
|
||||
if (!error && !natural_wd && wd_path->size > 0)
|
||||
error = git_futils_mkdir_r(wd_path->ptr, NULL, opts->mode);
|
||||
if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 && has_dotgit) {
|
||||
git_buf p = GIT_BUF_INIT;
|
||||
if ((error = git_path_dirname_r(&p, repo_path->ptr)) >= 0)
|
||||
error = git_futils_mkdir(p.ptr, NULL, dirmode, 0);
|
||||
git_buf_free(&p);
|
||||
}
|
||||
else if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0) {
|
||||
if (has_dotgit) {
|
||||
git_buf p = GIT_BUF_INIT;
|
||||
if ((error = git_path_dirname_r(&p, repo_path->ptr)) >= 0)
|
||||
error = git_futils_mkdir_q(p.ptr, opts->mode);
|
||||
git_buf_free(&p);
|
||||
}
|
||||
|
||||
if (!error)
|
||||
error = git_futils_mkdir_q(repo_path->ptr, opts->mode);
|
||||
|
||||
if (!error && !natural_wd && wd_path->size > 0)
|
||||
error = git_futils_mkdir_q(wd_path->ptr, opts->mode);
|
||||
if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 ||
|
||||
(opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0 ||
|
||||
has_dotgit)
|
||||
{
|
||||
uint32_t mkflag = GIT_MKDIR_CHMOD;
|
||||
if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0)
|
||||
mkflag |= GIT_MKDIR_PATH;
|
||||
error = git_futils_mkdir(repo_path->ptr, NULL, dirmode, mkflag);
|
||||
}
|
||||
else if (has_dotgit)
|
||||
error = git_futils_mkdir_q(repo_path->ptr, opts->mode);
|
||||
|
||||
if (wd_path->size > 0 &&
|
||||
!natural_wd &&
|
||||
((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 ||
|
||||
(opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0))
|
||||
error = git_futils_mkdir(wd_path->ptr, NULL, dirmode & ~S_ISGID,
|
||||
(opts->flags & GIT_REPOSITORY_INIT_MKPATH) ? GIT_MKDIR_PATH : 0);
|
||||
|
||||
/* prettify both directories now that they are created */
|
||||
|
||||
@ -1063,14 +1131,16 @@ int git_repository_init_ext(
|
||||
|
||||
opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT;
|
||||
|
||||
error = repo_init_config(git_buf_cstr(&repo_path), opts);
|
||||
error = repo_init_config(
|
||||
git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts);
|
||||
|
||||
/* TODO: reinitialize the templates */
|
||||
}
|
||||
else {
|
||||
if (!(error = repo_init_structure(
|
||||
git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts)) &&
|
||||
!(error = repo_init_config(git_buf_cstr(&repo_path), opts)))
|
||||
!(error = repo_init_config(
|
||||
git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts)))
|
||||
error = repo_init_create_head(
|
||||
git_buf_cstr(&repo_path), opts->initial_head);
|
||||
}
|
||||
|
@ -75,7 +75,6 @@ enum {
|
||||
GIT_REPOSITORY_INIT__IS_REINIT = (1u << 18),
|
||||
};
|
||||
|
||||
|
||||
/** Base git object for inheritance */
|
||||
struct git_object {
|
||||
git_cached_obj cached;
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#define p_lstat(p,b) lstat(p,b)
|
||||
#define p_readlink(a, b, c) readlink(a, b, c)
|
||||
#define p_symlink(o,n) symlink(o, n)
|
||||
#define p_link(o,n) link(o, n)
|
||||
#define p_symlink(o,n) symlink(o,n)
|
||||
#define p_unlink(p) unlink(p)
|
||||
|
@ -19,6 +19,14 @@ GIT_INLINE(int) p_link(const char *old, const char *new)
|
||||
return -1;
|
||||
}
|
||||
|
||||
GIT_INLINE(int) p_symlink(const char *old, const char *new)
|
||||
{
|
||||
GIT_UNUSED(old);
|
||||
GIT_UNUSED(new);
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
GIT_INLINE(int) p_mkdir(const char *path, mode_t mode)
|
||||
{
|
||||
wchar_t* buf = gitwin_to_utf16(path);
|
||||
|
123
tests-clar/core/copy.c
Normal file
123
tests-clar/core/copy.c
Normal file
@ -0,0 +1,123 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "fileops.h"
|
||||
#include "path.h"
|
||||
#include "posix.h"
|
||||
|
||||
void test_core_copy__file(void)
|
||||
{
|
||||
struct stat st;
|
||||
const char *content = "This is some stuff to copy\n";
|
||||
|
||||
cl_git_mkfile("copy_me", content);
|
||||
|
||||
cl_git_pass(git_futils_cp_withpath("copy_me", "copy_me_two", 0664, 0775));
|
||||
|
||||
cl_git_pass(git_path_lstat("copy_me_two", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert(strlen(content) == (size_t)st.st_size);
|
||||
|
||||
cl_git_pass(p_unlink("copy_me_two"));
|
||||
cl_git_pass(p_unlink("copy_me"));
|
||||
}
|
||||
|
||||
void test_core_copy__file_in_dir(void)
|
||||
{
|
||||
struct stat st;
|
||||
const char *content = "This is some other stuff to copy\n";
|
||||
|
||||
cl_git_pass(git_futils_mkdir("an_dir/in_a_dir", NULL, 0775, GIT_MKDIR_PATH));
|
||||
cl_git_mkfile("an_dir/in_a_dir/copy_me", content);
|
||||
cl_assert(git_path_isdir("an_dir"));
|
||||
|
||||
cl_git_pass(git_futils_cp_withpath
|
||||
("an_dir/in_a_dir/copy_me",
|
||||
"an_dir/second_dir/and_more/copy_me_two",
|
||||
0664, 0775));
|
||||
|
||||
cl_git_pass(git_path_lstat("an_dir/second_dir/and_more/copy_me_two", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert(strlen(content) == (size_t)st.st_size);
|
||||
|
||||
cl_git_pass(git_futils_rmdir_r("an_dir", GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_assert(!git_path_isdir("an_dir"));
|
||||
}
|
||||
|
||||
void test_core_copy__tree(void)
|
||||
{
|
||||
struct stat st;
|
||||
const char *content = "File content\n";
|
||||
|
||||
cl_git_pass(git_futils_mkdir("src/b", NULL, 0775, GIT_MKDIR_PATH));
|
||||
cl_git_pass(git_futils_mkdir("src/c/d", NULL, 0775, GIT_MKDIR_PATH));
|
||||
cl_git_pass(git_futils_mkdir("src/c/e", NULL, 0775, GIT_MKDIR_PATH));
|
||||
|
||||
cl_git_mkfile("src/f1", content);
|
||||
cl_git_mkfile("src/b/f2", content);
|
||||
cl_git_mkfile("src/c/f3", content);
|
||||
cl_git_mkfile("src/c/d/f4", content);
|
||||
cl_git_mkfile("src/c/d/.f5", content);
|
||||
|
||||
#ifndef GIT_WIN32
|
||||
cl_assert(p_symlink("../../b/f2", "src/c/d/l1") == 0);
|
||||
#endif
|
||||
|
||||
cl_assert(git_path_isdir("src"));
|
||||
cl_assert(git_path_isdir("src/b"));
|
||||
cl_assert(git_path_isdir("src/c/d"));
|
||||
cl_assert(git_path_isfile("src/c/d/f4"));
|
||||
|
||||
/* copy with no empty dirs, yes links, no dotfiles, no overwrite */
|
||||
|
||||
cl_git_pass(
|
||||
git_futils_cp_r("src", "t1", GIT_CPDIR_COPY_SYMLINKS, 0) );
|
||||
|
||||
cl_assert(git_path_isdir("t1"));
|
||||
cl_assert(git_path_isdir("t1/b"));
|
||||
cl_assert(git_path_isdir("t1/c"));
|
||||
cl_assert(git_path_isdir("t1/c/d"));
|
||||
cl_assert(!git_path_isdir("t1/c/e"));
|
||||
|
||||
cl_assert(git_path_isfile("t1/f1"));
|
||||
cl_assert(git_path_isfile("t1/b/f2"));
|
||||
cl_assert(git_path_isfile("t1/c/f3"));
|
||||
cl_assert(git_path_isfile("t1/c/d/f4"));
|
||||
cl_assert(!git_path_isfile("t1/c/d/.f5"));
|
||||
|
||||
cl_git_pass(git_path_lstat("t1/c/f3", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert(strlen(content) == (size_t)st.st_size);
|
||||
|
||||
#ifndef GIT_WIN32
|
||||
cl_git_pass(git_path_lstat("t1/c/d/l1", &st));
|
||||
cl_assert(S_ISLNK(st.st_mode));
|
||||
#endif
|
||||
|
||||
cl_git_pass(git_futils_rmdir_r("t1", GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_assert(!git_path_isdir("t1"));
|
||||
|
||||
/* copy with empty dirs, no links, yes dotfiles, no overwrite */
|
||||
|
||||
cl_git_pass(
|
||||
git_futils_cp_r("src", "t2", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_COPY_DOTFILES, 0) );
|
||||
|
||||
cl_assert(git_path_isdir("t2"));
|
||||
cl_assert(git_path_isdir("t2/b"));
|
||||
cl_assert(git_path_isdir("t2/c"));
|
||||
cl_assert(git_path_isdir("t2/c/d"));
|
||||
cl_assert(git_path_isdir("t2/c/e"));
|
||||
|
||||
cl_assert(git_path_isfile("t2/f1"));
|
||||
cl_assert(git_path_isfile("t2/b/f2"));
|
||||
cl_assert(git_path_isfile("t2/c/f3"));
|
||||
cl_assert(git_path_isfile("t2/c/d/f4"));
|
||||
cl_assert(git_path_isfile("t2/c/d/.f5"));
|
||||
|
||||
#ifndef GIT_WIN32
|
||||
cl_git_fail(git_path_lstat("t2/c/d/l1", &st));
|
||||
#endif
|
||||
|
||||
cl_git_pass(git_futils_rmdir_r("t2", GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_assert(!git_path_isdir("t2"));
|
||||
|
||||
cl_git_pass(git_futils_rmdir_r("src", GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
}
|
169
tests-clar/core/mkdir.c
Normal file
169
tests-clar/core/mkdir.c
Normal file
@ -0,0 +1,169 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "fileops.h"
|
||||
#include "path.h"
|
||||
#include "posix.h"
|
||||
|
||||
static void cleanup_basic_dirs(void *ref)
|
||||
{
|
||||
GIT_UNUSED(ref);
|
||||
git_futils_rmdir_r("d0", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d1", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d2", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d3", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d4", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
}
|
||||
|
||||
void test_core_mkdir__basic(void)
|
||||
{
|
||||
cl_set_cleanup(cleanup_basic_dirs, NULL);
|
||||
|
||||
/* make a directory */
|
||||
cl_assert(!git_path_isdir("d0"));
|
||||
cl_git_pass(git_futils_mkdir("d0", NULL, 0755, 0));
|
||||
cl_assert(git_path_isdir("d0"));
|
||||
|
||||
/* make a path */
|
||||
cl_assert(!git_path_isdir("d1"));
|
||||
cl_git_pass(git_futils_mkdir("d1/d1.1/d1.2", NULL, 0755, GIT_MKDIR_PATH));
|
||||
cl_assert(git_path_isdir("d1"));
|
||||
cl_assert(git_path_isdir("d1/d1.1"));
|
||||
cl_assert(git_path_isdir("d1/d1.1/d1.2"));
|
||||
|
||||
/* make a dir exclusively */
|
||||
cl_assert(!git_path_isdir("d2"));
|
||||
cl_git_pass(git_futils_mkdir("d2", NULL, 0755, GIT_MKDIR_EXCL));
|
||||
cl_assert(git_path_isdir("d2"));
|
||||
|
||||
/* make exclusive failure */
|
||||
cl_git_fail(git_futils_mkdir("d2", NULL, 0755, GIT_MKDIR_EXCL));
|
||||
|
||||
/* make a path exclusively */
|
||||
cl_assert(!git_path_isdir("d3"));
|
||||
cl_git_pass(git_futils_mkdir("d3/d3.1/d3.2", NULL, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
|
||||
cl_assert(git_path_isdir("d3"));
|
||||
cl_assert(git_path_isdir("d3/d3.1/d3.2"));
|
||||
|
||||
/* make exclusive path failure */
|
||||
cl_git_fail(git_futils_mkdir("d3/d3.1/d3.2", NULL, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
|
||||
/* ??? Should EXCL only apply to the last item in the path? */
|
||||
|
||||
/* path with trailing slash? */
|
||||
cl_assert(!git_path_isdir("d4"));
|
||||
cl_git_pass(git_futils_mkdir("d4/d4.1/", NULL, 0755, GIT_MKDIR_PATH));
|
||||
cl_assert(git_path_isdir("d4/d4.1"));
|
||||
}
|
||||
|
||||
static void cleanup_basedir(void *ref)
|
||||
{
|
||||
GIT_UNUSED(ref);
|
||||
git_futils_rmdir_r("base", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
}
|
||||
|
||||
void test_core_mkdir__with_base(void)
|
||||
{
|
||||
#define BASEDIR "base/dir/here"
|
||||
|
||||
cl_set_cleanup(cleanup_basedir, NULL);
|
||||
|
||||
cl_git_pass(git_futils_mkdir(BASEDIR, NULL, 0755, GIT_MKDIR_PATH));
|
||||
|
||||
cl_git_pass(git_futils_mkdir("a", BASEDIR, 0755, 0));
|
||||
cl_assert(git_path_isdir(BASEDIR "/a"));
|
||||
|
||||
cl_git_pass(git_futils_mkdir("b/b1/b2", BASEDIR, 0755, GIT_MKDIR_PATH));
|
||||
cl_assert(git_path_isdir(BASEDIR "/b/b1/b2"));
|
||||
|
||||
/* exclusive with existing base */
|
||||
cl_git_pass(git_futils_mkdir("c/c1/c2", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
|
||||
|
||||
/* fail: exclusive with duplicated suffix */
|
||||
cl_git_fail(git_futils_mkdir("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
|
||||
|
||||
/* fail: exclusive with any duplicated component */
|
||||
cl_git_fail(git_futils_mkdir("c/cz/cz", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
|
||||
|
||||
/* success: exclusive without path */
|
||||
cl_git_pass(git_futils_mkdir("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_EXCL));
|
||||
|
||||
/* path with shorter base and existing dirs */
|
||||
cl_git_pass(git_futils_mkdir("dir/here/d/", "base", 0755, GIT_MKDIR_PATH));
|
||||
cl_assert(git_path_isdir("base/dir/here/d"));
|
||||
|
||||
/* fail: path with shorter base and existing dirs */
|
||||
cl_git_fail(git_futils_mkdir("dir/here/e/", "base", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
|
||||
|
||||
/* fail: base with missing components */
|
||||
cl_git_fail(git_futils_mkdir("f/", "base/missing", 0755, GIT_MKDIR_PATH));
|
||||
|
||||
/* success: shift missing component to path */
|
||||
cl_git_pass(git_futils_mkdir("missing/f/", "base/", 0755, GIT_MKDIR_PATH));
|
||||
}
|
||||
|
||||
static void cleanup_chmod_root(void *ref)
|
||||
{
|
||||
mode_t *mode = ref;
|
||||
if (*mode != 0)
|
||||
(void)p_umask(*mode);
|
||||
|
||||
git_futils_rmdir_r("r", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
}
|
||||
|
||||
void test_core_mkdir__chmods(void)
|
||||
{
|
||||
struct stat st;
|
||||
mode_t old = 0;
|
||||
|
||||
cl_set_cleanup(cleanup_chmod_root, &old);
|
||||
|
||||
cl_git_pass(git_futils_mkdir("r", NULL, 0777, 0));
|
||||
old = p_umask(022);
|
||||
|
||||
cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH));
|
||||
|
||||
cl_git_pass(git_path_lstat("r/mode", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0755);
|
||||
cl_git_pass(git_path_lstat("r/mode/is", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0755);
|
||||
cl_git_pass(git_path_lstat("r/mode/is/important", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0755);
|
||||
|
||||
cl_git_pass(git_futils_mkdir("mode2/is2/important2", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD));
|
||||
|
||||
cl_git_pass(git_path_lstat("r/mode2", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0755);
|
||||
cl_git_pass(git_path_lstat("r/mode2/is2", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0755);
|
||||
cl_git_pass(git_path_lstat("r/mode2/is2/important2", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0777);
|
||||
|
||||
cl_git_pass(git_futils_mkdir("mode3/is3/important3", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH));
|
||||
|
||||
cl_git_pass(git_path_lstat("r/mode3", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0777);
|
||||
cl_git_pass(git_path_lstat("r/mode3/is3", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0777);
|
||||
cl_git_pass(git_path_lstat("r/mode3/is3/important3", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0777);
|
||||
|
||||
/* test that we chmod existing dir */
|
||||
|
||||
cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD));
|
||||
|
||||
cl_git_pass(git_path_lstat("r/mode", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0755);
|
||||
cl_git_pass(git_path_lstat("r/mode/is", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0755);
|
||||
cl_git_pass(git_path_lstat("r/mode/is/important", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0777);
|
||||
|
||||
/* test that we chmod even existing dirs if CHMOD_PATH is set */
|
||||
|
||||
cl_git_pass(git_futils_mkdir("mode2/is2/important2.1", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH));
|
||||
|
||||
cl_git_pass(git_path_lstat("r/mode2", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0777);
|
||||
cl_git_pass(git_path_lstat("r/mode2/is2", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0777);
|
||||
cl_git_pass(git_path_lstat("r/mode2/is2/important2.1", &st));
|
||||
cl_assert((st.st_mode & 0777) == 0777);
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
#include "fileops.h"
|
||||
#include "repository.h"
|
||||
#include "config.h"
|
||||
#include "path.h"
|
||||
|
||||
enum repo_mode {
|
||||
STANDARD_REPOSITORY = 0,
|
||||
@ -83,7 +84,7 @@ void test_repo_init__bare_repo_escaping_current_workdir(void)
|
||||
git_buf path_current_workdir = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_path_prettify_dir(&path_current_workdir, ".", NULL));
|
||||
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path_repository, git_buf_cstr(&path_current_workdir), "a/b/c"));
|
||||
cl_git_pass(git_futils_mkdir_r(git_buf_cstr(&path_repository), NULL, GIT_DIR_MODE));
|
||||
|
||||
@ -295,3 +296,82 @@ void test_repo_init__sets_logAllRefUpdates_according_to_type_of_repository(void)
|
||||
git_repository_free(_repo);
|
||||
assert_config_entry_on_init_bytype("core.logallrefupdates", true, false);
|
||||
}
|
||||
|
||||
void test_repo_init__extended_0(void)
|
||||
{
|
||||
git_repository_init_options opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
|
||||
/* without MKDIR this should fail */
|
||||
cl_git_fail(git_repository_init_ext(&_repo, "extended", &opts));
|
||||
|
||||
/* make the directory first, then it should succeed */
|
||||
cl_git_pass(git_futils_mkdir("extended", NULL, 0775, 0));
|
||||
cl_git_pass(git_repository_init_ext(&_repo, "extended", &opts));
|
||||
|
||||
cl_assert(!git__suffixcmp(git_repository_workdir(_repo), "/extended/"));
|
||||
cl_assert(!git__suffixcmp(git_repository_path(_repo), "/extended/.git/"));
|
||||
cl_assert(!git_repository_is_bare(_repo));
|
||||
cl_assert(git_repository_is_empty(_repo));
|
||||
|
||||
cleanup_repository("extended");
|
||||
}
|
||||
|
||||
void test_repo_init__extended_1(void)
|
||||
{
|
||||
git_reference *ref;
|
||||
git_remote *remote;
|
||||
struct stat st;
|
||||
git_repository_init_options opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
|
||||
opts.flags = GIT_REPOSITORY_INIT_MKPATH |
|
||||
GIT_REPOSITORY_INIT_NO_DOTGIT_DIR;
|
||||
opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP;
|
||||
opts.workdir_path = "../c_wd";
|
||||
opts.description = "Awesomest test repository evah";
|
||||
opts.initial_head = "development";
|
||||
opts.origin_url = "https://github.com/libgit2/libgit2.git";
|
||||
|
||||
cl_git_pass(git_repository_init_ext(&_repo, "root/b/c.git", &opts));
|
||||
|
||||
cl_assert(!git__suffixcmp(git_repository_workdir(_repo), "/c_wd/"));
|
||||
cl_assert(!git__suffixcmp(git_repository_path(_repo), "/c.git/"));
|
||||
cl_assert(git_path_isfile("root/b/c_wd/.git"));
|
||||
cl_assert(!git_repository_is_bare(_repo));
|
||||
/* repo will not be counted as empty because we set head to "development" */
|
||||
cl_assert(!git_repository_is_empty(_repo));
|
||||
|
||||
cl_git_pass(git_path_lstat(git_repository_path(_repo), &st));
|
||||
cl_assert(S_ISDIR(st.st_mode));
|
||||
cl_assert((S_ISGID & st.st_mode) == S_ISGID);
|
||||
|
||||
cl_git_pass(git_reference_lookup(&ref, _repo, "HEAD"));
|
||||
cl_assert(git_reference_type(ref) == GIT_REF_SYMBOLIC);
|
||||
cl_assert_equal_s("refs/heads/development", git_reference_target(ref));
|
||||
git_reference_free(ref);
|
||||
|
||||
cl_git_pass(git_remote_load(&remote, _repo, "origin"));
|
||||
cl_assert_equal_s("origin", git_remote_name(remote));
|
||||
cl_assert_equal_s(opts.origin_url, git_remote_url(remote));
|
||||
git_remote_free(remote);
|
||||
|
||||
git_repository_free(_repo);
|
||||
cl_fixture_cleanup("root");
|
||||
}
|
||||
|
||||
void test_repo_init__extended_with_template(void)
|
||||
{
|
||||
git_repository_init_options opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
|
||||
opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE;
|
||||
opts.template_path = cl_fixture("template");
|
||||
|
||||
cl_git_pass(git_repository_init_ext(&_repo, "templated.git", &opts));
|
||||
|
||||
cl_assert(git_repository_is_bare(_repo));
|
||||
cl_assert(!git__suffixcmp(git_repository_path(_repo), "/templated.git/"));
|
||||
|
||||
cleanup_repository("templated.git");
|
||||
}
|
||||
|
2
tests-clar/resources/template/branches/.gitignore
vendored
Normal file
2
tests-clar/resources/template/branches/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# This file should not be copied, nor should the
|
||||
# containing directory, since it is effectively "empty"
|
1
tests-clar/resources/template/description
Normal file
1
tests-clar/resources/template/description
Normal file
@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
15
tests-clar/resources/template/hooks/applypatch-msg.sample
Executable file
15
tests-clar/resources/template/hooks/applypatch-msg.sample
Executable file
@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to check the commit log message taken by
|
||||
# applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit. The hook is
|
||||
# allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "applypatch-msg".
|
||||
|
||||
. git-sh-setup
|
||||
test -x "$GIT_DIR/hooks/commit-msg" &&
|
||||
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
|
||||
:
|
24
tests-clar/resources/template/hooks/commit-msg.sample
Executable file
24
tests-clar/resources/template/hooks/commit-msg.sample
Executable file
@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to check the commit log message.
|
||||
# Called by "git commit" with one argument, the name of the file
|
||||
# that has the commit message. The hook should exit with non-zero
|
||||
# status after issuing an appropriate message if it wants to stop the
|
||||
# commit. The hook is allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "commit-msg".
|
||||
|
||||
# Uncomment the below to add a Signed-off-by line to the message.
|
||||
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
||||
# hook is more suited to it.
|
||||
#
|
||||
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||
|
||||
# This example catches duplicate Signed-off-by lines.
|
||||
|
||||
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
||||
echo >&2 Duplicate Signed-off-by lines.
|
||||
exit 1
|
||||
}
|
8
tests-clar/resources/template/hooks/post-commit.sample
Executable file
8
tests-clar/resources/template/hooks/post-commit.sample
Executable file
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script that is called after a successful
|
||||
# commit is made.
|
||||
#
|
||||
# To enable this hook, rename this file to "post-commit".
|
||||
|
||||
: Nothing
|
15
tests-clar/resources/template/hooks/post-receive.sample
Executable file
15
tests-clar/resources/template/hooks/post-receive.sample
Executable file
@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script for the "post-receive" event.
|
||||
#
|
||||
# The "post-receive" script is run after receive-pack has accepted a pack
|
||||
# and the repository has been updated. It is passed arguments in through
|
||||
# stdin in the form
|
||||
# <oldrev> <newrev> <refname>
|
||||
# For example:
|
||||
# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
|
||||
#
|
||||
# see contrib/hooks/ for a sample, or uncomment the next line and
|
||||
# rename the file to "post-receive".
|
||||
|
||||
#. /usr/share/doc/git-core/contrib/hooks/post-receive-email
|
8
tests-clar/resources/template/hooks/post-update.sample
Executable file
8
tests-clar/resources/template/hooks/post-update.sample
Executable file
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to prepare a packed repository for use over
|
||||
# dumb transports.
|
||||
#
|
||||
# To enable this hook, rename this file to "post-update".
|
||||
|
||||
exec git update-server-info
|
14
tests-clar/resources/template/hooks/pre-applypatch.sample
Executable file
14
tests-clar/resources/template/hooks/pre-applypatch.sample
Executable file
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to verify what is about to be committed
|
||||
# by applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-applypatch".
|
||||
|
||||
. git-sh-setup
|
||||
test -x "$GIT_DIR/hooks/pre-commit" &&
|
||||
exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
|
||||
:
|
46
tests-clar/resources/template/hooks/pre-commit.sample
Executable file
46
tests-clar/resources/template/hooks/pre-commit.sample
Executable file
@ -0,0 +1,46 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to verify what is about to be committed.
|
||||
# Called by "git commit" with no arguments. The hook should
|
||||
# exit with non-zero status after issuing an appropriate message if
|
||||
# it wants to stop the commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-commit".
|
||||
|
||||
if git rev-parse --verify HEAD >/dev/null 2>&1
|
||||
then
|
||||
against=HEAD
|
||||
else
|
||||
# Initial commit: diff against an empty tree object
|
||||
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
|
||||
fi
|
||||
|
||||
# If you want to allow non-ascii filenames set this variable to true.
|
||||
allownonascii=$(git config hooks.allownonascii)
|
||||
|
||||
# Cross platform projects tend to avoid non-ascii filenames; prevent
|
||||
# them from being added to the repository. We exploit the fact that the
|
||||
# printable range starts at the space character and ends with tilde.
|
||||
if [ "$allownonascii" != "true" ] &&
|
||||
# Note that the use of brackets around a tr range is ok here, (it's
|
||||
# even required, for portability to Solaris 10's /usr/bin/tr), since
|
||||
# the square bracket bytes happen to fall in the designated range.
|
||||
test "$(git diff --cached --name-only --diff-filter=A -z $against |
|
||||
LC_ALL=C tr -d '[ -~]\0')"
|
||||
then
|
||||
echo "Error: Attempt to add a non-ascii file name."
|
||||
echo
|
||||
echo "This can cause problems if you want to work"
|
||||
echo "with people on other platforms."
|
||||
echo
|
||||
echo "To be portable it is advisable to rename the file ..."
|
||||
echo
|
||||
echo "If you know what you are doing you can disable this"
|
||||
echo "check using:"
|
||||
echo
|
||||
echo " git config hooks.allownonascii true"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec git diff-index --check --cached $against --
|
169
tests-clar/resources/template/hooks/pre-rebase.sample
Executable file
169
tests-clar/resources/template/hooks/pre-rebase.sample
Executable file
@ -0,0 +1,169 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2006, 2008 Junio C Hamano
|
||||
#
|
||||
# The "pre-rebase" hook is run just before "git rebase" starts doing
|
||||
# its job, and can prevent the command from running by exiting with
|
||||
# non-zero status.
|
||||
#
|
||||
# The hook is called with the following parameters:
|
||||
#
|
||||
# $1 -- the upstream the series was forked from.
|
||||
# $2 -- the branch being rebased (or empty when rebasing the current branch).
|
||||
#
|
||||
# This sample shows how to prevent topic branches that are already
|
||||
# merged to 'next' branch from getting rebased, because allowing it
|
||||
# would result in rebasing already published history.
|
||||
|
||||
publish=next
|
||||
basebranch="$1"
|
||||
if test "$#" = 2
|
||||
then
|
||||
topic="refs/heads/$2"
|
||||
else
|
||||
topic=`git symbolic-ref HEAD` ||
|
||||
exit 0 ;# we do not interrupt rebasing detached HEAD
|
||||
fi
|
||||
|
||||
case "$topic" in
|
||||
refs/heads/??/*)
|
||||
;;
|
||||
*)
|
||||
exit 0 ;# we do not interrupt others.
|
||||
;;
|
||||
esac
|
||||
|
||||
# Now we are dealing with a topic branch being rebased
|
||||
# on top of master. Is it OK to rebase it?
|
||||
|
||||
# Does the topic really exist?
|
||||
git show-ref -q "$topic" || {
|
||||
echo >&2 "No such branch $topic"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Is topic fully merged to master?
|
||||
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
|
||||
if test -z "$not_in_master"
|
||||
then
|
||||
echo >&2 "$topic is fully merged to master; better remove it."
|
||||
exit 1 ;# we could allow it, but there is no point.
|
||||
fi
|
||||
|
||||
# Is topic ever merged to next? If so you should not be rebasing it.
|
||||
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
|
||||
only_next_2=`git rev-list ^master ${publish} | sort`
|
||||
if test "$only_next_1" = "$only_next_2"
|
||||
then
|
||||
not_in_topic=`git rev-list "^$topic" master`
|
||||
if test -z "$not_in_topic"
|
||||
then
|
||||
echo >&2 "$topic is already up-to-date with master"
|
||||
exit 1 ;# we could allow it, but there is no point.
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
|
||||
/usr/bin/perl -e '
|
||||
my $topic = $ARGV[0];
|
||||
my $msg = "* $topic has commits already merged to public branch:\n";
|
||||
my (%not_in_next) = map {
|
||||
/^([0-9a-f]+) /;
|
||||
($1 => 1);
|
||||
} split(/\n/, $ARGV[1]);
|
||||
for my $elem (map {
|
||||
/^([0-9a-f]+) (.*)$/;
|
||||
[$1 => $2];
|
||||
} split(/\n/, $ARGV[2])) {
|
||||
if (!exists $not_in_next{$elem->[0]}) {
|
||||
if ($msg) {
|
||||
print STDERR $msg;
|
||||
undef $msg;
|
||||
}
|
||||
print STDERR " $elem->[1]\n";
|
||||
}
|
||||
}
|
||||
' "$topic" "$not_in_next" "$not_in_master"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
################################################################
|
||||
|
||||
This sample hook safeguards topic branches that have been
|
||||
published from being rewound.
|
||||
|
||||
The workflow assumed here is:
|
||||
|
||||
* Once a topic branch forks from "master", "master" is never
|
||||
merged into it again (either directly or indirectly).
|
||||
|
||||
* Once a topic branch is fully cooked and merged into "master",
|
||||
it is deleted. If you need to build on top of it to correct
|
||||
earlier mistakes, a new topic branch is created by forking at
|
||||
the tip of the "master". This is not strictly necessary, but
|
||||
it makes it easier to keep your history simple.
|
||||
|
||||
* Whenever you need to test or publish your changes to topic
|
||||
branches, merge them into "next" branch.
|
||||
|
||||
The script, being an example, hardcodes the publish branch name
|
||||
to be "next", but it is trivial to make it configurable via
|
||||
$GIT_DIR/config mechanism.
|
||||
|
||||
With this workflow, you would want to know:
|
||||
|
||||
(1) ... if a topic branch has ever been merged to "next". Young
|
||||
topic branches can have stupid mistakes you would rather
|
||||
clean up before publishing, and things that have not been
|
||||
merged into other branches can be easily rebased without
|
||||
affecting other people. But once it is published, you would
|
||||
not want to rewind it.
|
||||
|
||||
(2) ... if a topic branch has been fully merged to "master".
|
||||
Then you can delete it. More importantly, you should not
|
||||
build on top of it -- other people may already want to
|
||||
change things related to the topic as patches against your
|
||||
"master", so if you need further changes, it is better to
|
||||
fork the topic (perhaps with the same name) afresh from the
|
||||
tip of "master".
|
||||
|
||||
Let's look at this example:
|
||||
|
||||
o---o---o---o---o---o---o---o---o---o "next"
|
||||
/ / / /
|
||||
/ a---a---b A / /
|
||||
/ / / /
|
||||
/ / c---c---c---c B /
|
||||
/ / / \ /
|
||||
/ / / b---b C \ /
|
||||
/ / / / \ /
|
||||
---o---o---o---o---o---o---o---o---o---o---o "master"
|
||||
|
||||
|
||||
A, B and C are topic branches.
|
||||
|
||||
* A has one fix since it was merged up to "next".
|
||||
|
||||
* B has finished. It has been fully merged up to "master" and "next",
|
||||
and is ready to be deleted.
|
||||
|
||||
* C has not merged to "next" at all.
|
||||
|
||||
We would want to allow C to be rebased, refuse A, and encourage
|
||||
B to be deleted.
|
||||
|
||||
To compute (1):
|
||||
|
||||
git rev-list ^master ^topic next
|
||||
git rev-list ^master next
|
||||
|
||||
if these match, topic has not merged in next at all.
|
||||
|
||||
To compute (2):
|
||||
|
||||
git rev-list master..topic
|
||||
|
||||
if this is empty, it is fully merged to "master".
|
36
tests-clar/resources/template/hooks/prepare-commit-msg.sample
Executable file
36
tests-clar/resources/template/hooks/prepare-commit-msg.sample
Executable file
@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to prepare the commit log message.
|
||||
# Called by "git commit" with the name of the file that has the
|
||||
# commit message, followed by the description of the commit
|
||||
# message's source. The hook's purpose is to edit the commit
|
||||
# message file. If the hook fails with a non-zero status,
|
||||
# the commit is aborted.
|
||||
#
|
||||
# To enable this hook, rename this file to "prepare-commit-msg".
|
||||
|
||||
# This hook includes three examples. The first comments out the
|
||||
# "Conflicts:" part of a merge commit.
|
||||
#
|
||||
# The second includes the output of "git diff --name-status -r"
|
||||
# into the message, just before the "git status" output. It is
|
||||
# commented because it doesn't cope with --amend or with squashed
|
||||
# commits.
|
||||
#
|
||||
# The third example adds a Signed-off-by line to the message, that can
|
||||
# still be edited. This is rarely a good idea.
|
||||
|
||||
case "$2,$3" in
|
||||
merge,)
|
||||
/usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
|
||||
|
||||
# ,|template,)
|
||||
# /usr/bin/perl -i.bak -pe '
|
||||
# print "\n" . `git diff --cached --name-status -r`
|
||||
# if /^#/ && $first++ == 0' "$1" ;;
|
||||
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
128
tests-clar/resources/template/hooks/update.sample
Executable file
128
tests-clar/resources/template/hooks/update.sample
Executable file
@ -0,0 +1,128 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to blocks unannotated tags from entering.
|
||||
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
|
||||
#
|
||||
# To enable this hook, rename this file to "update".
|
||||
#
|
||||
# Config
|
||||
# ------
|
||||
# hooks.allowunannotated
|
||||
# This boolean sets whether unannotated tags will be allowed into the
|
||||
# repository. By default they won't be.
|
||||
# hooks.allowdeletetag
|
||||
# This boolean sets whether deleting tags will be allowed in the
|
||||
# repository. By default they won't be.
|
||||
# hooks.allowmodifytag
|
||||
# This boolean sets whether a tag may be modified after creation. By default
|
||||
# it won't be.
|
||||
# hooks.allowdeletebranch
|
||||
# This boolean sets whether deleting branches will be allowed in the
|
||||
# repository. By default they won't be.
|
||||
# hooks.denycreatebranch
|
||||
# This boolean sets whether remotely creating branches will be denied
|
||||
# in the repository. By default this is allowed.
|
||||
#
|
||||
|
||||
# --- Command line
|
||||
refname="$1"
|
||||
oldrev="$2"
|
||||
newrev="$3"
|
||||
|
||||
# --- Safety check
|
||||
if [ -z "$GIT_DIR" ]; then
|
||||
echo "Don't run this script from the command line." >&2
|
||||
echo " (if you want, you could supply GIT_DIR then run" >&2
|
||||
echo " $0 <ref> <oldrev> <newrev>)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
|
||||
echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Config
|
||||
allowunannotated=$(git config --bool hooks.allowunannotated)
|
||||
allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
|
||||
denycreatebranch=$(git config --bool hooks.denycreatebranch)
|
||||
allowdeletetag=$(git config --bool hooks.allowdeletetag)
|
||||
allowmodifytag=$(git config --bool hooks.allowmodifytag)
|
||||
|
||||
# check for no description
|
||||
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
|
||||
case "$projectdesc" in
|
||||
"Unnamed repository"* | "")
|
||||
echo "*** Project description file hasn't been set" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Check types
|
||||
# if $newrev is 0000...0000, it's a commit to delete a ref.
|
||||
zero="0000000000000000000000000000000000000000"
|
||||
if [ "$newrev" = "$zero" ]; then
|
||||
newrev_type=delete
|
||||
else
|
||||
newrev_type=$(git cat-file -t $newrev)
|
||||
fi
|
||||
|
||||
case "$refname","$newrev_type" in
|
||||
refs/tags/*,commit)
|
||||
# un-annotated tag
|
||||
short_refname=${refname##refs/tags/}
|
||||
if [ "$allowunannotated" != "true" ]; then
|
||||
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
|
||||
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/tags/*,delete)
|
||||
# delete tag
|
||||
if [ "$allowdeletetag" != "true" ]; then
|
||||
echo "*** Deleting a tag is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/tags/*,tag)
|
||||
# annotated tag
|
||||
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
|
||||
then
|
||||
echo "*** Tag '$refname' already exists." >&2
|
||||
echo "*** Modifying a tag is not allowed in this repository." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/heads/*,commit)
|
||||
# branch
|
||||
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
|
||||
echo "*** Creating a branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/heads/*,delete)
|
||||
# delete branch
|
||||
if [ "$allowdeletebranch" != "true" ]; then
|
||||
echo "*** Deleting a branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/remotes/*,commit)
|
||||
# tracking branch
|
||||
;;
|
||||
refs/remotes/*,delete)
|
||||
# delete tracking branch
|
||||
if [ "$allowdeletebranch" != "true" ]; then
|
||||
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
# Anything else (is there anything else?)
|
||||
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Finished
|
||||
exit 0
|
6
tests-clar/resources/template/info/exclude
Normal file
6
tests-clar/resources/template/info/exclude
Normal file
@ -0,0 +1,6 @@
|
||||
# git ls-files --others --exclude-from=.git/info/exclude
|
||||
# Lines that start with '#' are comments.
|
||||
# For a project mostly in C, the following would be a good set of
|
||||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
# *~
|
Loading…
Reference in New Issue
Block a user