vendor/libgit2/src/fileops.c in rugged-0.17.0.b7 vs vendor/libgit2/src/fileops.c in rugged-0.18.0.b1

- old
+ new

@@ -1,7 +1,7 @@ /* - * Copyright (C) 2009-2012 the libgit2 contributors + * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" @@ -12,11 +12,12 @@ #endif int git_futils_mkpath2file(const char *file_path, const mode_t mode) { return git_futils_mkdir( - file_path, NULL, mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST); + file_path, NULL, mode, + GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); } int git_futils_mktmp(git_buf *path_out, const char *filename) { int fd; @@ -118,11 +119,11 @@ return 0; } int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len) { - ssize_t read_size; + ssize_t read_size = 0; git_buf_clear(buf); if (git_buf_grow(buf, len + 1) < 0) return -1; @@ -140,14 +141,15 @@ return 0; } int git_futils_readbuffer_updated( - git_buf *buf, const char *path, time_t *mtime, int *updated) + git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated) { git_file fd; struct stat st; + bool changed = false; assert(buf && path && *path); if (updated != NULL) *updated = 0; @@ -160,20 +162,29 @@ giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path); return -1; } /* - * If we were given a time, we only want to read the file if it - * has been modified. + * If we were given a time and/or a size, we only want to read the file + * if it has been modified. */ - if (mtime != NULL && *mtime >= st.st_mtime) { + if (size && *size != (size_t)st.st_size) + changed = true; + if (mtime && *mtime != st.st_mtime) + changed = true; + if (!size && !mtime) + changed = true; + + if (!changed) { p_close(fd); return 0; } if (mtime != NULL) *mtime = st.st_mtime; + if (size != NULL) + *size = (size_t)st.st_size; if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) { p_close(fd); return -1; } @@ -186,11 +197,11 @@ return 0; } int git_futils_readbuffer(git_buf *buf, const char *path) { - return git_futils_readbuffer_updated(buf, path, NULL, NULL); + return git_futils_readbuffer_updated(buf, path, NULL, NULL, NULL); } int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) { if (git_futils_mkpath2file(to, dirmode) < 0) @@ -238,10 +249,11 @@ const char *path, const char *base, mode_t mode, uint32_t flags) { + int error = -1; 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 */ @@ -258,10 +270,14 @@ 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_LAST2) != 0) { + git_buf_rtruncate_at_char(&make_path, '/'); + flags |= GIT_MKDIR_SKIP_LAST; + } 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) @@ -285,16 +301,50 @@ /* 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; + if (p_mkdir(make_path.ptr, mode) < 0) { + int already_exists = 0; + + switch (errno) { + case EEXIST: + if (!lastch && (flags & GIT_MKDIR_VERIFY_DIR) != 0 && + !git_path_isdir(make_path.ptr)) { + giterr_set( + GITERR_OS, "Existing path is not a directory '%s'", + make_path.ptr); + error = GIT_ENOTFOUND; + goto fail; + } + + already_exists = 1; + break; + case ENOSYS: + /* Solaris can generate this error if you try to mkdir + * a path which is already a mount point. In that case, + * the path does already exist; but it's not implied by + * the definition of the error, so let's recheck */ + if (git_path_isdir(make_path.ptr)) { + already_exists = 1; + break; + } + + /* Fall through */ + errno = ENOSYS; + default: + giterr_set(GITERR_OS, "Failed to make directory '%s'", + make_path.ptr); + goto fail; + } + + if (already_exists && (flags & GIT_MKDIR_EXCL) != 0) { + giterr_set(GITERR_OS, "Directory already exists '%s'", + make_path.ptr); + error = GIT_EEXISTS; + goto fail; + } } /* chmod if requested */ if ((flags & GIT_MKDIR_CHMOD_PATH) != 0 || ((flags & GIT_MKDIR_CHMOD) != 0 && lastch == '\0')) @@ -312,152 +362,386 @@ git_buf_free(&make_path); return 0; fail: git_buf_free(&make_path); - return -1; + return error; } int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) { return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH); } -static int _rmdir_recurs_foreach(void *opaque, git_buf *path) +typedef struct { + const char *base; + size_t baselen; + uint32_t flags; + int error; +} futils__rmdir_data; + +static int futils__error_cannot_rmdir(const char *path, const char *filemsg) { - git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque; + if (filemsg) + giterr_set(GITERR_OS, "Could not remove directory. File '%s' %s", + path, filemsg); + else + giterr_set(GITERR_OS, "Could not remove directory '%s'", path); - if (git_path_isdir(path->ptr) == true) { - if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0) - return -1; + return -1; +} - if (p_rmdir(path->ptr) < 0) { - if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && (errno == ENOTEMPTY || errno == EEXIST)) - return 0; +static int futils__rm_first_parent(git_buf *path, const char *ceiling) +{ + int error = GIT_ENOTFOUND; + struct stat st; - giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr); - return -1; - } + while (error == GIT_ENOTFOUND) { + git_buf_rtruncate_at_char(path, '/'); - return 0; + if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0) + error = 0; + else if (p_lstat_posixly(path->ptr, &st) == 0) { + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) + error = p_unlink(path->ptr); + else if (!S_ISDIR(st.st_mode)) + error = -1; /* fail to remove non-regular file */ + } else if (errno != ENOTDIR) + error = -1; } - if (removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS) { - if (p_unlink(path->ptr) < 0) { - giterr_set(GITERR_OS, "Could not remove directory. File '%s' cannot be removed", path->ptr); - return -1; + if (error) + futils__error_cannot_rmdir(path->ptr, "cannot remove parent"); + + return error; +} + +static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path) +{ + struct stat st; + futils__rmdir_data *data = opaque; + + if ((data->error = p_lstat_posixly(path->ptr, &st)) < 0) { + if (errno == ENOENT) + data->error = 0; + else if (errno == ENOTDIR) { + /* asked to remove a/b/c/d/e and a/b is a normal file */ + if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0) + data->error = futils__rm_first_parent(path, data->base); + else + futils__error_cannot_rmdir( + path->ptr, "parent is not directory"); } + else + futils__error_cannot_rmdir(path->ptr, "cannot access"); + } - return 0; + else if (S_ISDIR(st.st_mode)) { + int error = git_path_direach(path, futils__rmdir_recurs_foreach, data); + if (error < 0) + return (error == GIT_EUSER) ? data->error : error; + + data->error = p_rmdir(path->ptr); + + if (data->error < 0) { + if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 && + (errno == ENOTEMPTY || errno == EEXIST)) + data->error = 0; + else + futils__error_cannot_rmdir(path->ptr, NULL); + } } - if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) { - giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr); - return -1; + else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) { + data->error = p_unlink(path->ptr); + + if (data->error < 0) + futils__error_cannot_rmdir(path->ptr, "cannot be removed"); } - return 0; + else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) + data->error = futils__error_cannot_rmdir(path->ptr, "still present"); + + return data->error; } +static int futils__rmdir_empty_parent(void *opaque, git_buf *path) +{ + futils__rmdir_data *data = opaque; + int error; + + if (git_buf_len(path) <= data->baselen) + return GIT_ITEROVER; + + error = p_rmdir(git_buf_cstr(path)); + + if (error) { + int en = errno; + + if (en == ENOENT || en == ENOTDIR) { + giterr_clear(); + error = 0; + } else if (en == ENOTEMPTY || en == EEXIST) { + giterr_clear(); + error = GIT_ITEROVER; + } else { + futils__error_cannot_rmdir(git_buf_cstr(path), NULL); + } + } + + return error; +} + int git_futils_rmdir_r( - const char *path, const char *base, git_directory_removal_type removal_type) + const char *path, const char *base, uint32_t flags) { int error; git_buf fullpath = GIT_BUF_INIT; + futils__rmdir_data data; - assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY - || removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS - || removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS); - /* build path and find "root" where we should start calling mkdir */ if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0) return -1; - error = _rmdir_recurs_foreach(&removal_type, &fullpath); + data.base = base ? base : ""; + data.baselen = base ? strlen(base) : 0; + data.flags = flags; + data.error = 0; + error = futils__rmdir_recurs_foreach(&data, &fullpath); + + /* remove now-empty parents if requested */ + if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0) { + error = git_path_walk_up( + &fullpath, base, futils__rmdir_empty_parent, &data); + + if (error == GIT_ITEROVER) + error = 0; + } + git_buf_free(&fullpath); return error; } -int git_futils_find_system_file(git_buf *path, const char *filename) +int git_futils_cleanupdir_r(const char *path) { + int error; + git_buf fullpath = GIT_BUF_INIT; + futils__rmdir_data data; + + if ((error = git_buf_put(&fullpath, path, strlen(path))) < 0) + goto clean_up; + + data.base = ""; + data.baselen = 0; + data.flags = GIT_RMDIR_REMOVE_FILES; + data.error = 0; + + if (!git_path_exists(path)) { + giterr_set(GITERR_OS, "Path does not exist: %s" , path); + error = GIT_ERROR; + goto clean_up; + } + + if (!git_path_isdir(path)) { + giterr_set(GITERR_OS, "Path is not a directory: %s" , path); + error = GIT_ERROR; + goto clean_up; + } + + error = git_path_direach(&fullpath, futils__rmdir_recurs_foreach, &data); + if (error == GIT_EUSER) + error = data.error; + +clean_up: + git_buf_free(&fullpath); + return error; +} + + +static int git_futils_guess_system_dirs(git_buf *out) +{ #ifdef GIT_WIN32 - // try to find git.exe/git.cmd on path - if (!win32_find_system_file_using_path(path, filename)) - return 0; + return git_win32__find_system_dirs(out); +#else + return git_buf_sets(out, "/etc"); +#endif +} - // try to find msysgit installation path using registry - if (!win32_find_system_file_using_registry(path, filename)) - return 0; +static int git_futils_guess_global_dirs(git_buf *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_global_dirs(out); #else - if (git_buf_joinpath(path, "/etc", filename) < 0) - return -1; + return git_buf_sets(out, getenv("HOME")); +#endif +} - if (git_path_exists(path->ptr) == true) - return 0; +static int git_futils_guess_xdg_dirs(git_buf *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_xdg_dirs(out); +#else + const char *env = NULL; + + if ((env = getenv("XDG_CONFIG_HOME")) != NULL) + return git_buf_joinpath(out, env, "git"); + else if ((env = getenv("HOME")) != NULL) + return git_buf_joinpath(out, env, ".config/git"); + + git_buf_clear(out); + return 0; #endif +} - git_buf_clear(path); - giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename); - return GIT_ENOTFOUND; +typedef int (*git_futils_dirs_guess_cb)(git_buf *out); + +static git_buf git_futils__dirs[GIT_FUTILS_DIR__MAX] = + { GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT }; + +static git_futils_dirs_guess_cb git_futils__dir_guess[GIT_FUTILS_DIR__MAX] = { + git_futils_guess_system_dirs, + git_futils_guess_global_dirs, + git_futils_guess_xdg_dirs, +}; + +static int git_futils_check_selector(git_futils_dir_t which) +{ + if (which < GIT_FUTILS_DIR__MAX) + return 0; + giterr_set(GITERR_INVALID, "config directory selector out of range"); + return -1; } -int git_futils_find_global_file(git_buf *path, const char *filename) +int git_futils_dirs_get(const git_buf **out, git_futils_dir_t which) { -#ifdef GIT_WIN32 - struct win32_path root; - static const wchar_t *tmpls[4] = { - L"%HOME%\\", - L"%HOMEDRIVE%%HOMEPATH%\\", - L"%USERPROFILE%\\", - NULL, - }; - const wchar_t **tmpl; + assert(out); - for (tmpl = tmpls; *tmpl != NULL; tmpl++) { - /* try to expand environment variable, skipping if not set */ - if (win32_expand_path(&root, *tmpl) != 0 || root.path[0] == L'%') - continue; + *out = NULL; - /* try to look up file under path */ - if (!win32_find_file(path, &root, filename)) - return 0; + GITERR_CHECK_ERROR(git_futils_check_selector(which)); - /* No error if file not found under %HOME%, b/c we don't trust it, - * but do error if another var is set and yet file is not found. - */ - if (tmpl != tmpls) - break; - } + if (!git_buf_len(&git_futils__dirs[which])) + GITERR_CHECK_ERROR( + git_futils__dir_guess[which](&git_futils__dirs[which])); - giterr_set(GITERR_OS, "The global file '%s' doesn't exist", filename); - git_buf_clear(path); + *out = &git_futils__dirs[which]; + return 0; +} - return GIT_ENOTFOUND; -#else - const char *home = getenv("HOME"); +int git_futils_dirs_get_str(char *out, size_t outlen, git_futils_dir_t which) +{ + const git_buf *path = NULL; - if (home == NULL) { - giterr_set(GITERR_OS, "Global file lookup failed. " - "Cannot locate the user's home directory"); - return GIT_ENOTFOUND; + GITERR_CHECK_ERROR(git_futils_check_selector(which)); + GITERR_CHECK_ERROR(git_futils_dirs_get(&path, which)); + + if (!out || path->size >= outlen) { + giterr_set(GITERR_NOMEMORY, "Buffer is too short for the path"); + return GIT_EBUFS; } - if (git_buf_joinpath(path, home, filename) < 0) - return -1; + git_buf_copy_cstr(out, outlen, path); + return 0; +} - if (git_path_exists(path->ptr) == false) { - giterr_set(GITERR_OS, "The global file '%s' doesn't exist", filename); - git_buf_clear(path); - return GIT_ENOTFOUND; +#define PATH_MAGIC "$PATH" + +int git_futils_dirs_set(git_futils_dir_t which, const char *search_path) +{ + const char *expand_path = NULL; + git_buf merge = GIT_BUF_INIT; + + GITERR_CHECK_ERROR(git_futils_check_selector(which)); + + if (search_path != NULL) + expand_path = strstr(search_path, PATH_MAGIC); + + /* init with default if not yet done and needed (ignoring error) */ + if ((!search_path || expand_path) && + !git_buf_len(&git_futils__dirs[which])) + git_futils__dir_guess[which](&git_futils__dirs[which]); + + /* if $PATH is not referenced, then just set the path */ + if (!expand_path) + return git_buf_sets(&git_futils__dirs[which], search_path); + + /* otherwise set to join(before $PATH, old value, after $PATH) */ + if (expand_path > search_path) + git_buf_set(&merge, search_path, expand_path - search_path); + + if (git_buf_len(&git_futils__dirs[which])) + git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR, + merge.ptr, git_futils__dirs[which].ptr); + + expand_path += strlen(PATH_MAGIC); + if (*expand_path) + git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR, merge.ptr, expand_path); + + git_buf_swap(&git_futils__dirs[which], &merge); + git_buf_free(&merge); + + return git_buf_oom(&git_futils__dirs[which]) ? -1 : 0; +} + +void git_futils_dirs_free(void) +{ + int i; + for (i = 0; i < GIT_FUTILS_DIR__MAX; ++i) + git_buf_free(&git_futils__dirs[i]); +} + +static int git_futils_find_in_dirlist( + git_buf *path, const char *name, git_futils_dir_t which, const char *label) +{ + size_t len; + const char *scan, *next = NULL; + const git_buf *syspath; + + GITERR_CHECK_ERROR(git_futils_dirs_get(&syspath, which)); + + for (scan = git_buf_cstr(syspath); scan; scan = next) { + for (next = strchr(scan, GIT_PATH_LIST_SEPARATOR); + next && next > scan && next[-1] == '\\'; + next = strchr(next + 1, GIT_PATH_LIST_SEPARATOR)) + /* find unescaped separator or end of string */; + + len = next ? (size_t)(next++ - scan) : strlen(scan); + if (!len) + continue; + + GITERR_CHECK_ERROR(git_buf_set(path, scan, len)); + GITERR_CHECK_ERROR(git_buf_joinpath(path, path->ptr, name)); + + if (git_path_exists(path->ptr)) + return 0; } - return 0; -#endif + git_buf_clear(path); + giterr_set(GITERR_OS, "The %s file '%s' doesn't exist", label, name); + return GIT_ENOTFOUND; } +int git_futils_find_system_file(git_buf *path, const char *filename) +{ + return git_futils_find_in_dirlist( + path, filename, GIT_FUTILS_DIR_SYSTEM, "system"); +} + +int git_futils_find_global_file(git_buf *path, const char *filename) +{ + return git_futils_find_in_dirlist( + path, filename, GIT_FUTILS_DIR_GLOBAL, "global"); +} + +int git_futils_find_xdg_file(git_buf *path, const char *filename) +{ + return git_futils_find_in_dirlist( + path, filename, GIT_FUTILS_DIR_XDG, "global/xdg"); +} + int git_futils_fake_symlink(const char *old, const char *new) { int retcode = GIT_ERROR; int fd = git_futils_creat_withpath(new, 0755, 0644); if (fd >= 0) { @@ -543,12 +827,37 @@ uint32_t flags; uint32_t mkdir_flags; mode_t dirmode; } cp_r_info; +#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10) + +static int _cp_r_mkdir(cp_r_info *info, git_buf *from) +{ + int error = 0; + + /* create root directory the first time we need to create a directory */ + if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) { + error = git_futils_mkdir( + info->to_root, NULL, info->dirmode, + (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0); + + info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT; + } + + /* create directory with root as base to prevent excess chmods */ + if (!error) + error = git_futils_mkdir( + from->ptr + info->from_prefix, info->to_root, + info->dirmode, info->mkdir_flags); + + return error; +} + static int _cp_r_callback(void *ref, git_buf *from) { + int error = 0; cp_r_info *info = ref; struct stat from_st, to_st; bool exists = false; if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 && @@ -566,28 +875,26 @@ return -1; } } else exists = true; - if (git_path_lstat(from->ptr, &from_st) < 0) - return -1; + if ((error = git_path_lstat(from->ptr, &from_st)) < 0) + return error; 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) + if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 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); + error = _cp_r_mkdir(info, from); /* recurse onto target directory */ - if (!exists || S_ISDIR(to_st.st_mode)) + if (!error && (!exists || S_ISDIR(to_st.st_mode))) error = git_path_direach(from, _cp_r_callback, info); if (oldmode != 0) info->dirmode = oldmode; @@ -611,19 +918,26 @@ (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; + (error = _cp_r_mkdir(info, from)) < 0) + return error; /* make symlink or regular file */ if (S_ISLNK(from_st.st_mode)) - return cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); - else - return git_futils_cp(from->ptr, info->to.ptr, from_st.st_mode); + error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); + else { + mode_t usemode = from_st.st_mode; + + if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) + usemode = (usemode & 0111) ? 0777 : 0666; + + error = git_futils_cp(from->ptr, info->to.ptr, usemode); + } + + return error; } int git_futils_cp_r( const char *from, const char *to, @@ -632,31 +946,70 @@ { int error; git_buf path = GIT_BUF_INIT; cp_r_info info; - if (git_buf_sets(&path, from) < 0) + if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */ 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) { + /* if not creating empty dirs, then use mkdir to create the path on + * demand right before files are copied. + */ info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST; - if ((flags & GIT_CPDIR_CHMOD) != 0) + if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH; } else { + /* otherwise, we will do simple mkdir as directories are encountered */ info.mkdir_flags = - ((flags & GIT_CPDIR_CHMOD) != 0) ? GIT_MKDIR_CHMOD : 0; + ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0; } error = _cp_r_callback(&info, &path); git_buf_free(&path); git_buf_free(&info.to); return error; +} + +int git_futils_filestamp_check( + git_futils_filestamp *stamp, const char *path) +{ + struct stat st; + + /* if the stamp is NULL, then always reload */ + if (stamp == NULL) + return 1; + + if (p_stat(path, &st) < 0) + return GIT_ENOTFOUND; + + if (stamp->mtime == (git_time_t)st.st_mtime && + stamp->size == (git_off_t)st.st_size && + stamp->ino == (unsigned int)st.st_ino) + return 0; + + stamp->mtime = (git_time_t)st.st_mtime; + stamp->size = (git_off_t)st.st_size; + stamp->ino = (unsigned int)st.st_ino; + + return 1; +} + +void git_futils_filestamp_set( + git_futils_filestamp *target, const git_futils_filestamp *source) +{ + assert(target); + + if (source) + memcpy(target, source, sizeof(*target)); + else + memset(target, 0, sizeof(*target)); }