mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-03 02:40:09 +00:00

Hey. Apologies in advance -- I broke your bindings. This is a major commit that includes a long-overdue redesign of the whole object-database structure. This is expected to be the last major external API redesign of the library until the first non-alpha release. Please get your bindings up to date with these changes. They will be included in the next minor release. Sorry again! Major features include: - Real caching and refcounting on parsed objects - Real caching and refcounting on objects read from the ODB - Streaming writes & reads from the ODB - Single-method writes for all object types - The external API is now partially thread-safe The speed increases are significant in all aspects, specially when reading an object several times from the ODB (revwalking) and when writing big objects to the ODB. Here's a full changelog for the external API: blob.h ------ - Remove `git_blob_new` - Remove `git_blob_set_rawcontent` - Remove `git_blob_set_rawcontent_fromfile` - Rename `git_blob_writefile` -> `git_blob_create_fromfile` - Change `git_blob_create_fromfile`: The `path` argument is now relative to the repository's working dir - Add `git_blob_create_frombuffer` commit.h -------- - Remove `git_commit_new` - Remove `git_commit_add_parent` - Remove `git_commit_set_message` - Remove `git_commit_set_committer` - Remove `git_commit_set_author` - Remove `git_commit_set_tree` - Add `git_commit_create` - Add `git_commit_create_v` - Add `git_commit_create_o` - Add `git_commit_create_ov` tag.h ----- - Remove `git_tag_new` - Remove `git_tag_set_target` - Remove `git_tag_set_name` - Remove `git_tag_set_tagger` - Remove `git_tag_set_message` - Add `git_tag_create` - Add `git_tag_create_o` tree.h ------ - Change `git_tree_entry_2object`: New signature is `(git_object **object_out, git_repository *repo, git_tree_entry *entry)` - Remove `git_tree_new` - Remove `git_tree_add_entry` - Remove `git_tree_remove_entry_byindex` - Remove `git_tree_remove_entry_byname` - Remove `git_tree_clearentries` - Remove `git_tree_entry_set_id` - Remove `git_tree_entry_set_name` - Remove `git_tree_entry_set_attributes` object.h ------------ - Remove `git_object_new - Remove `git_object_write` - Change `git_object_close`: This method is now *mandatory*. Not closing an object causes a memory leak. odb.h ----- - Remove type `git_rawobj` - Remove `git_rawobj_close` - Rename `git_rawobj_hash` -> `git_odb_hash` - Change `git_odb_hash`: New signature is `(git_oid *id, const void *data, size_t len, git_otype type)` - Add type `git_odb_object` - Add `git_odb_object_close` - Change `git_odb_read`: New signature is `(git_odb_object **out, git_odb *db, const git_oid *id)` - Change `git_odb_read_header`: New signature is `(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id)` - Remove `git_odb_write` - Add `git_odb_open_wstream` - Add `git_odb_open_rstream` odb_backend.h ------------- - Change type `git_odb_backend`: New internal signatures are as follows int (* read)(void **, size_t *, git_otype *, struct git_odb_backend *, const git_oid *) int (* read_header)(size_t *, git_otype *, struct git_odb_backend *, const git_oid *) int (* writestream)(struct git_odb_stream **, struct git_odb_backend *, size_t, git_otype) int (* readstream)( struct git_odb_stream **, struct git_odb_backend *, const git_oid *) - Add type `git_odb_stream` - Add enum `git_odb_streammode` Signed-off-by: Vicent Marti <tanoku@gmail.com>
636 lines
12 KiB
C
636 lines
12 KiB
C
#include "common.h"
|
|
#include "fileops.h"
|
|
#include <ctype.h>
|
|
|
|
int gitfo_mkdir_2file(const char *file_path)
|
|
{
|
|
const int mode = 0755; /* or 0777 ? */
|
|
int error = GIT_SUCCESS;
|
|
char target_folder_path[GIT_PATH_MAX];
|
|
|
|
error = git__dirname_r(target_folder_path, sizeof(target_folder_path), file_path);
|
|
if (error < GIT_SUCCESS)
|
|
return error;
|
|
|
|
/* Does the containing folder exist? */
|
|
if (gitfo_isdir(target_folder_path)) {
|
|
git__joinpath(target_folder_path, target_folder_path, ""); /* Ensure there's a trailing slash */
|
|
|
|
/* Let's create the tree structure */
|
|
error = gitfo_mkdir_recurs(target_folder_path, mode);
|
|
if (error < GIT_SUCCESS)
|
|
return error;
|
|
}
|
|
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
static int creat_tempfile(char *path_out, const char *tmp_dir, const char *filename)
|
|
{
|
|
int fd;
|
|
|
|
git__joinpath(path_out, tmp_dir, filename);
|
|
strcat(path_out, "_git2_XXXXXX");
|
|
|
|
#ifdef GIT_WIN32
|
|
/* FIXME: there may be race conditions when multi-threading
|
|
* with the library */
|
|
if (_mktemp_s(path_out, GIT_PATH_MAX) != 0)
|
|
return GIT_EOSERR;
|
|
|
|
fd = gitfo_creat(path_out, 0744);
|
|
#else
|
|
fd = mkstemp(path_out);
|
|
#endif
|
|
|
|
return fd >= 0 ? fd : GIT_EOSERR;
|
|
}
|
|
|
|
static const char *find_tmpdir(void)
|
|
{
|
|
static int tmpdir_not_found = 0;
|
|
static char temp_dir[GIT_PATH_MAX];
|
|
static const char *env_vars[] = {
|
|
"TEMP", "TMP", "TMPDIR"
|
|
};
|
|
|
|
unsigned int i, j;
|
|
char test_file[GIT_PATH_MAX];
|
|
|
|
if (tmpdir_not_found)
|
|
return NULL;
|
|
|
|
if (temp_dir[0] != '\0')
|
|
return temp_dir;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(env_vars); ++i) {
|
|
char *env_path;
|
|
|
|
env_path = getenv(env_vars[i]);
|
|
if (env_path == NULL)
|
|
continue;
|
|
|
|
strcpy(temp_dir, env_path);
|
|
|
|
/* Fix backslashes because Windows environment vars
|
|
* are probably fucked up */
|
|
for (j = 0; j < strlen(temp_dir); ++j)
|
|
if (temp_dir[j] == '\\')
|
|
temp_dir[j] = '/';
|
|
|
|
if (creat_tempfile(test_file, temp_dir, "writetest") >= 0) {
|
|
gitfo_unlink(test_file);
|
|
return temp_dir;
|
|
}
|
|
}
|
|
|
|
/* last resort: current folder. */
|
|
strcpy(temp_dir, "./");
|
|
if (creat_tempfile(test_file, temp_dir, "writetest") >= 0) {
|
|
gitfo_unlink(test_file);
|
|
return temp_dir;
|
|
}
|
|
|
|
tmpdir_not_found = 1;
|
|
return NULL;
|
|
}
|
|
|
|
int gitfo_creat_tmp(char *path_out, const char *filename)
|
|
{
|
|
const char *tmp_dir;
|
|
|
|
tmp_dir = find_tmpdir();
|
|
if (tmp_dir == NULL)
|
|
return GIT_EOSERR;
|
|
|
|
return creat_tempfile(path_out, tmp_dir, filename);
|
|
}
|
|
|
|
int gitfo_open(const char *path, int flags)
|
|
{
|
|
int fd = open(path, flags | O_BINARY);
|
|
return fd >= 0 ? fd : GIT_EOSERR;
|
|
}
|
|
|
|
int gitfo_creat(const char *path, int mode)
|
|
{
|
|
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode);
|
|
return fd >= 0 ? fd : GIT_EOSERR;
|
|
}
|
|
|
|
int gitfo_creat_force(const char *path, int mode)
|
|
{
|
|
if (gitfo_mkdir_2file(path) < GIT_SUCCESS)
|
|
return GIT_EOSERR;
|
|
|
|
return gitfo_creat(path, mode);
|
|
}
|
|
|
|
int gitfo_read(git_file fd, void *buf, size_t cnt)
|
|
{
|
|
char *b = buf;
|
|
while (cnt) {
|
|
ssize_t r = read(fd, b, cnt);
|
|
if (r < 0) {
|
|
if (errno == EINTR || errno == EAGAIN)
|
|
continue;
|
|
return GIT_EOSERR;
|
|
}
|
|
if (!r) {
|
|
errno = EPIPE;
|
|
return GIT_EOSERR;
|
|
}
|
|
cnt -= r;
|
|
b += r;
|
|
}
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
int gitfo_write(git_file fd, void *buf, size_t cnt)
|
|
{
|
|
char *b = buf;
|
|
while (cnt) {
|
|
ssize_t r = write(fd, b, cnt);
|
|
if (r < 0) {
|
|
if (errno == EINTR || errno == EAGAIN)
|
|
continue;
|
|
return GIT_EOSERR;
|
|
}
|
|
if (!r) {
|
|
errno = EPIPE;
|
|
return GIT_EOSERR;
|
|
}
|
|
cnt -= r;
|
|
b += r;
|
|
}
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
int gitfo_isdir(const char *path)
|
|
{
|
|
struct stat st;
|
|
int len, stat_error;
|
|
|
|
if (!path)
|
|
return GIT_ENOTFOUND;
|
|
|
|
len = strlen(path);
|
|
|
|
/* win32: stat path for folders cannot end in a slash */
|
|
if (path[len - 1] == '/') {
|
|
char *path_fixed = NULL;
|
|
path_fixed = git__strdup(path);
|
|
path_fixed[len - 1] = 0;
|
|
stat_error = gitfo_stat(path_fixed, &st);
|
|
free(path_fixed);
|
|
} else {
|
|
stat_error = gitfo_stat(path, &st);
|
|
}
|
|
|
|
if (stat_error < GIT_SUCCESS)
|
|
return GIT_ENOTFOUND;
|
|
|
|
if (!S_ISDIR(st.st_mode))
|
|
return GIT_ENOTFOUND;
|
|
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
int gitfo_exists(const char *path)
|
|
{
|
|
assert(path);
|
|
return access(path, F_OK);
|
|
}
|
|
|
|
git_off_t gitfo_size(git_file fd)
|
|
{
|
|
struct stat sb;
|
|
if (gitfo_fstat(fd, &sb))
|
|
return GIT_EOSERR;
|
|
return sb.st_size;
|
|
}
|
|
|
|
int gitfo_read_file(gitfo_buf *obj, const char *path)
|
|
{
|
|
git_file fd;
|
|
size_t len;
|
|
git_off_t size;
|
|
unsigned char *buff;
|
|
|
|
assert(obj && path && *path);
|
|
|
|
if ((fd = gitfo_open(path, O_RDONLY)) < 0)
|
|
return GIT_ERROR;
|
|
|
|
if (((size = gitfo_size(fd)) < 0) || !git__is_sizet(size+1)) {
|
|
gitfo_close(fd);
|
|
return GIT_ERROR;
|
|
}
|
|
len = (size_t) size;
|
|
|
|
if ((buff = git__malloc(len + 1)) == NULL) {
|
|
gitfo_close(fd);
|
|
return GIT_ERROR;
|
|
}
|
|
|
|
if (gitfo_read(fd, buff, len) < 0) {
|
|
gitfo_close(fd);
|
|
free(buff);
|
|
return GIT_ERROR;
|
|
}
|
|
buff[len] = '\0';
|
|
|
|
gitfo_close(fd);
|
|
|
|
obj->data = buff;
|
|
obj->len = len;
|
|
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
void gitfo_free_buf(gitfo_buf *obj)
|
|
{
|
|
assert(obj);
|
|
free(obj->data);
|
|
obj->data = NULL;
|
|
}
|
|
|
|
int gitfo_mv(const char *from, const char *to)
|
|
{
|
|
#ifdef GIT_WIN32
|
|
/*
|
|
* Win32 POSIX compilance my ass. If the destination
|
|
* file exists, the `rename` call fails. This is as
|
|
* close as it gets with the Win32 API.
|
|
*/
|
|
return MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING) ? GIT_SUCCESS : GIT_EOSERR;
|
|
#else
|
|
/* Don't even try this on Win32 */
|
|
if (!link(from, to)) {
|
|
gitfo_unlink(from);
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
if (!rename(from, to))
|
|
return GIT_SUCCESS;
|
|
|
|
return GIT_EOSERR;
|
|
#endif
|
|
}
|
|
|
|
int gitfo_mv_force(const char *from, const char *to)
|
|
{
|
|
if (gitfo_mkdir_2file(to) < GIT_SUCCESS)
|
|
return GIT_EOSERR;
|
|
|
|
return gitfo_mv(from, to);
|
|
}
|
|
|
|
int gitfo_map_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
|
|
{
|
|
if (git__mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin) < GIT_SUCCESS)
|
|
return GIT_EOSERR;
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
void gitfo_free_map(git_map *out)
|
|
{
|
|
git__munmap(out);
|
|
}
|
|
|
|
/* cached diskio */
|
|
struct gitfo_cache {
|
|
git_file fd;
|
|
size_t cache_size, pos;
|
|
unsigned char *cache;
|
|
};
|
|
|
|
gitfo_cache *gitfo_enable_caching(git_file fd, size_t cache_size)
|
|
{
|
|
gitfo_cache *ioc;
|
|
|
|
ioc = git__malloc(sizeof(*ioc));
|
|
if (!ioc)
|
|
return NULL;
|
|
|
|
ioc->fd = fd;
|
|
ioc->pos = 0;
|
|
ioc->cache_size = cache_size;
|
|
ioc->cache = git__malloc(cache_size);
|
|
if (!ioc->cache) {
|
|
free(ioc);
|
|
return NULL;
|
|
}
|
|
|
|
return ioc;
|
|
}
|
|
|
|
GIT_INLINE(void) gitfo_add_to_cache(gitfo_cache *ioc, void *buf, size_t len)
|
|
{
|
|
memcpy(ioc->cache + ioc->pos, buf, len);
|
|
ioc->pos += len;
|
|
}
|
|
|
|
int gitfo_flush_cached(gitfo_cache *ioc)
|
|
{
|
|
int result = GIT_SUCCESS;
|
|
|
|
if (ioc->pos) {
|
|
result = gitfo_write(ioc->fd, ioc->cache, ioc->pos);
|
|
ioc->pos = 0;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int gitfo_write_cached(gitfo_cache *ioc, void *buff, size_t len)
|
|
{
|
|
unsigned char *buf = buff;
|
|
|
|
for (;;) {
|
|
size_t space_left = ioc->cache_size - ioc->pos;
|
|
/* cache if it's small */
|
|
if (space_left > len) {
|
|
gitfo_add_to_cache(ioc, buf, len);
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
/* flush the cache if it doesn't fit */
|
|
if (ioc->pos) {
|
|
int rc;
|
|
gitfo_add_to_cache(ioc, buf, space_left);
|
|
rc = gitfo_flush_cached(ioc);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
len -= space_left;
|
|
buf += space_left;
|
|
}
|
|
|
|
/* write too-large chunks immediately */
|
|
if (len > ioc->cache_size)
|
|
return gitfo_write(ioc->fd, buf, len);
|
|
}
|
|
}
|
|
|
|
int gitfo_close_cached(gitfo_cache *ioc)
|
|
{
|
|
git_file fd;
|
|
|
|
if (gitfo_flush_cached(ioc) < GIT_SUCCESS)
|
|
return GIT_ERROR;
|
|
|
|
fd = ioc->fd;
|
|
free(ioc->cache);
|
|
free(ioc);
|
|
|
|
return gitfo_close(fd);
|
|
}
|
|
|
|
int gitfo_dirent(
|
|
char *path,
|
|
size_t path_sz,
|
|
int (*fn)(void *, char *),
|
|
void *arg)
|
|
{
|
|
size_t wd_len = strlen(path);
|
|
DIR *dir;
|
|
struct dirent *de;
|
|
|
|
if (!wd_len || path_sz < wd_len + 2)
|
|
return GIT_ERROR;
|
|
|
|
while (path[wd_len - 1] == '/')
|
|
wd_len--;
|
|
path[wd_len++] = '/';
|
|
path[wd_len] = '\0';
|
|
|
|
dir = opendir(path);
|
|
if (!dir)
|
|
return GIT_EOSERR;
|
|
|
|
while ((de = readdir(dir)) != NULL) {
|
|
size_t de_len;
|
|
int result;
|
|
|
|
/* always skip '.' and '..' */
|
|
if (de->d_name[0] == '.') {
|
|
if (de->d_name[1] == '\0')
|
|
continue;
|
|
if (de->d_name[1] == '.' && de->d_name[2] == '\0')
|
|
continue;
|
|
}
|
|
|
|
de_len = strlen(de->d_name);
|
|
if (path_sz < wd_len + de_len + 1) {
|
|
closedir(dir);
|
|
return GIT_ERROR;
|
|
}
|
|
|
|
strcpy(path + wd_len, de->d_name);
|
|
result = fn(arg, path);
|
|
if (result < GIT_SUCCESS) {
|
|
closedir(dir);
|
|
return result;
|
|
}
|
|
if (result > 0) {
|
|
closedir(dir);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
closedir(dir);
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
#ifdef GIT_WIN32
|
|
|
|
static int is_windows_rooted_path(const char *path)
|
|
{
|
|
/* Does the root of the path look like a windows drive ? */
|
|
if (isalpha(path[0]) && (path[1] == ':'))
|
|
return GIT_SUCCESS;
|
|
|
|
return GIT_ERROR;
|
|
}
|
|
|
|
#endif
|
|
|
|
int gitfo_mkdir_recurs(const char *path, int mode)
|
|
{
|
|
int error;
|
|
char *pp, *sp;
|
|
char *path_copy = git__strdup(path);
|
|
|
|
if (path_copy == NULL)
|
|
return GIT_ENOMEM;
|
|
|
|
error = GIT_SUCCESS;
|
|
pp = path_copy;
|
|
|
|
#ifdef GIT_WIN32
|
|
|
|
if (!is_windows_rooted_path(pp))
|
|
pp += 2; /* Skip the drive name (eg. C: or D:) */
|
|
|
|
#endif
|
|
|
|
while (error == GIT_SUCCESS && (sp = strchr(pp, '/')) != 0) {
|
|
if (sp != pp && gitfo_isdir(path_copy) < GIT_SUCCESS) {
|
|
*sp = 0;
|
|
error = gitfo_mkdir(path_copy, mode);
|
|
|
|
/* Do not choke while trying to recreate an existing directory */
|
|
if (errno == EEXIST)
|
|
error = GIT_SUCCESS;
|
|
|
|
*sp = '/';
|
|
}
|
|
|
|
pp = sp + 1;
|
|
}
|
|
|
|
if (*(pp - 1) != '/' && error == GIT_SUCCESS)
|
|
error = gitfo_mkdir(path, mode);
|
|
|
|
free(path_copy);
|
|
return error;
|
|
}
|
|
|
|
static int retrieve_previous_path_component_start(const char *path)
|
|
{
|
|
int offset, len, start = 0;
|
|
|
|
len = strlen(path);
|
|
offset = len - 1;
|
|
|
|
/* Skip leading slash */
|
|
if (path[start] == '/')
|
|
start++;
|
|
|
|
/* Skip trailing slash */
|
|
if (path[offset] == '/')
|
|
offset--;
|
|
|
|
if (offset < 0)
|
|
return GIT_ERROR;
|
|
|
|
while (offset > start && path[offset-1] != '/') {
|
|
offset--;
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
int gitfo_prettify_dir_path(char *buffer_out, const char *path)
|
|
{
|
|
int len = 0, segment_len, only_dots;
|
|
char *current;
|
|
const char *buffer_out_start, *buffer_end;
|
|
|
|
buffer_out_start = buffer_out;
|
|
current = (char *)path;
|
|
buffer_end = path + strlen(path);
|
|
|
|
while (current < buffer_end) {
|
|
/* Prevent multiple slashes from being added to the output */
|
|
if (*current == '/' && len > 0 && buffer_out_start[len - 1] == '/') {
|
|
current++;
|
|
continue;
|
|
}
|
|
|
|
only_dots = 1;
|
|
segment_len = 0;
|
|
|
|
/* Copy path segment to the output */
|
|
while (current < buffer_end && *current !='/')
|
|
{
|
|
only_dots &= (*current == '.');
|
|
*buffer_out++ = *current++;
|
|
segment_len++;
|
|
len++;
|
|
}
|
|
|
|
/* Skip current directory */
|
|
if (only_dots && segment_len == 1)
|
|
{
|
|
current++;
|
|
buffer_out -= segment_len;
|
|
len -= segment_len;
|
|
continue;
|
|
}
|
|
|
|
/* Handle the double-dot upward directory navigation */
|
|
if (only_dots && segment_len == 2)
|
|
{
|
|
current++;
|
|
buffer_out -= segment_len;
|
|
|
|
*buffer_out ='\0';
|
|
len = retrieve_previous_path_component_start(buffer_out_start);
|
|
if (len < GIT_SUCCESS)
|
|
return GIT_EINVALIDPATH;
|
|
|
|
buffer_out = (char *)buffer_out_start + len;
|
|
continue;
|
|
}
|
|
|
|
/* Guard against potential multiple dot path traversal (cf http://cwe.mitre.org/data/definitions/33.html) */
|
|
if (only_dots &&segment_len > 0)
|
|
return GIT_EINVALIDPATH;
|
|
|
|
*buffer_out++ = '/';
|
|
len++;
|
|
}
|
|
|
|
*buffer_out = '\0';
|
|
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
int gitfo_prettify_file_path(char *buffer_out, const char *path)
|
|
{
|
|
int error, path_len, i;
|
|
const char* pattern = "/..";
|
|
|
|
path_len = strlen(path);
|
|
|
|
/* Let's make sure the filename doesn't end with "/", "/." or "/.." */
|
|
for (i = 1; path_len > i && i < 4; i++) {
|
|
if (!strncmp(path + path_len - i, pattern, i))
|
|
return GIT_EINVALIDPATH;
|
|
}
|
|
|
|
error = gitfo_prettify_dir_path(buffer_out, path);
|
|
if (error < GIT_SUCCESS)
|
|
return error;
|
|
|
|
path_len = strlen(buffer_out);
|
|
if (path_len < 2)
|
|
return GIT_EINVALIDPATH;
|
|
|
|
/* Remove the trailing slash */
|
|
buffer_out[path_len - 1] = '\0';
|
|
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
int gitfo_cmp_path(const char *name1, int len1, int isdir1,
|
|
const char *name2, int len2, int isdir2)
|
|
{
|
|
int len = len1 < len2 ? len1 : len2;
|
|
int cmp;
|
|
|
|
cmp = memcmp(name1, name2, len);
|
|
if (cmp)
|
|
return cmp;
|
|
if (len1 < len2)
|
|
return ((!isdir1 && !isdir2) ? -1 :
|
|
(isdir1 ? '/' - name2[len1] : name2[len1] - '/'));
|
|
if (len1 > len2)
|
|
return ((!isdir1 && !isdir2) ? 1 :
|
|
(isdir2 ? name1[len2] - '/' : '/' - name1[len2]));
|
|
return 0;
|
|
}
|
|
|