Commit bd0e8814 by Edward Thomson

checkout: introduce git_checkout_perfdata

Checkout can now provide performance data about the number of (some)
syscalls performed using an optional callback.

This structure remains internal-only in maintenance branches.
parent 04bdd97f
...@@ -67,6 +67,7 @@ typedef struct { ...@@ -67,6 +67,7 @@ typedef struct {
bool reload_submodules; bool reload_submodules;
size_t total_steps; size_t total_steps;
size_t completed_steps; size_t completed_steps;
git_checkout_perfdata perfdata;
} checkout_data; } checkout_data;
typedef struct { typedef struct {
...@@ -1289,50 +1290,86 @@ fail: ...@@ -1289,50 +1290,86 @@ fail:
return error; return error;
} }
static int checkout_mkdir(
checkout_data *data,
const char *path,
const char *base,
mode_t mode,
unsigned int flags)
{
struct git_futils_mkdir_perfdata mkdir_perfdata = {0};
int error = git_futils_mkdir_withperf(
path, base, mode, flags, &mkdir_perfdata);
data->perfdata.mkdir_calls += mkdir_perfdata.mkdir_calls;
data->perfdata.stat_calls += mkdir_perfdata.stat_calls;
data->perfdata.chmod_calls += mkdir_perfdata.chmod_calls;
return error;
}
static int mkpath2file(
checkout_data *data, const char *path, unsigned int mode)
{
return checkout_mkdir(
data, path, git_repository_workdir(data->repo), mode,
GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
}
static int buffer_to_file( static int buffer_to_file(
checkout_data *data,
struct stat *st, struct stat *st,
git_buf *buf, git_buf *buf,
const char *path, const char *path,
mode_t dir_mode,
int file_open_flags,
mode_t file_mode) mode_t file_mode)
{ {
int error; int error;
if ((error = git_futils_mkpath2file(path, dir_mode)) < 0) if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0)
return error; return error;
if ((error = git_futils_writebuffer( if ((error = git_futils_writebuffer(
buf, path, file_open_flags, file_mode)) < 0) buf, path, data->opts.file_open_flags, file_mode)) < 0)
return error; return error;
if (st != NULL && (error = p_stat(path, st)) < 0) if (st) {
data->perfdata.stat_calls++;
if ((error = p_stat(path, st)) < 0) {
giterr_set(GITERR_OS, "Error statting '%s'", path); giterr_set(GITERR_OS, "Error statting '%s'", path);
return error;
}
}
if (GIT_PERMS_IS_EXEC(file_mode)) {
data->perfdata.chmod_calls++;
else if (GIT_PERMS_IS_EXEC(file_mode) && if ((error = p_chmod(path, file_mode)) < 0)
(error = p_chmod(path, file_mode)) < 0)
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
}
return error; return error;
} }
static int blob_content_to_file( static int blob_content_to_file(
checkout_data *data,
struct stat *st, struct stat *st,
git_blob *blob, git_blob *blob,
const char *path, const char *path,
const char * hint_path, const char * hint_path,
mode_t entry_filemode, mode_t entry_filemode)
git_checkout_options *opts)
{ {
int error = 0; mode_t file_mode = data->opts.file_mode ?
mode_t file_mode = opts->file_mode ? opts->file_mode : entry_filemode; data->opts.file_mode : entry_filemode;
git_buf out = GIT_BUF_INIT; git_buf out = GIT_BUF_INIT;
git_filter_list *fl = NULL; git_filter_list *fl = NULL;
int error = 0;
if (hint_path == NULL) if (hint_path == NULL)
hint_path = path; hint_path = path;
if (!opts->disable_filters) if (!data->opts.disable_filters)
error = git_filter_list_load( error = git_filter_list_load(
&fl, git_blob_owner(blob), blob, hint_path, &fl, git_blob_owner(blob), blob, hint_path,
GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT); GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT);
...@@ -1343,9 +1380,7 @@ static int blob_content_to_file( ...@@ -1343,9 +1380,7 @@ static int blob_content_to_file(
git_filter_list_free(fl); git_filter_list_free(fl);
if (!error) { if (!error) {
error = buffer_to_file( error = buffer_to_file(data, st, &out, path, file_mode);
st, &out, path, opts->dir_mode, opts->file_open_flags, file_mode);
st->st_mode = entry_filemode; st->st_mode = entry_filemode;
git_buf_free(&out); git_buf_free(&out);
...@@ -1355,22 +1390,21 @@ static int blob_content_to_file( ...@@ -1355,22 +1390,21 @@ static int blob_content_to_file(
} }
static int blob_content_to_link( static int blob_content_to_link(
checkout_data *data,
struct stat *st, struct stat *st,
git_blob *blob, git_blob *blob,
const char *path, const char *path)
mode_t dir_mode,
int can_symlink)
{ {
git_buf linktarget = GIT_BUF_INIT; git_buf linktarget = GIT_BUF_INIT;
int error; int error;
if ((error = git_futils_mkpath2file(path, dir_mode)) < 0) if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0)
return error; return error;
if ((error = git_blob__getbuf(&linktarget, blob)) < 0) if ((error = git_blob__getbuf(&linktarget, blob)) < 0)
return error; return error;
if (can_symlink) { if (data->can_symlink) {
if ((error = p_symlink(git_buf_cstr(&linktarget), path)) < 0) if ((error = p_symlink(git_buf_cstr(&linktarget), path)) < 0)
giterr_set(GITERR_OS, "Could not create symlink %s\n", path); giterr_set(GITERR_OS, "Could not create symlink %s\n", path);
} else { } else {
...@@ -1378,6 +1412,8 @@ static int blob_content_to_link( ...@@ -1378,6 +1412,8 @@ static int blob_content_to_link(
} }
if (!error) { if (!error) {
data->perfdata.stat_calls++;
if ((error = p_lstat(path, st)) < 0) if ((error = p_lstat(path, st)) < 0)
giterr_set(GITERR_CHECKOUT, "Could not stat symlink %s", path); giterr_set(GITERR_CHECKOUT, "Could not stat symlink %s", path);
...@@ -1421,6 +1457,7 @@ static int checkout_submodule_update_index( ...@@ -1421,6 +1457,7 @@ static int checkout_submodule_update_index(
if (git_buf_puts(&data->path, file->path) < 0) if (git_buf_puts(&data->path, file->path) < 0)
return -1; return -1;
data->perfdata.stat_calls++;
if (p_stat(git_buf_cstr(&data->path), &st) < 0) { if (p_stat(git_buf_cstr(&data->path), &st) < 0) {
giterr_set( giterr_set(
GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path); GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path);
...@@ -1442,7 +1479,8 @@ static int checkout_submodule( ...@@ -1442,7 +1479,8 @@ static int checkout_submodule(
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
return 0; return 0;
if ((error = git_futils_mkdir( if ((error = checkout_mkdir(
data,
file->path, data->opts.target_directory, file->path, data->opts.target_directory,
data->opts.dir_mode, GIT_MKDIR_PATH)) < 0) data->opts.dir_mode, GIT_MKDIR_PATH)) < 0)
return error; return error;
...@@ -1481,10 +1519,13 @@ static void report_progress( ...@@ -1481,10 +1519,13 @@ static void report_progress(
data->opts.progress_payload); data->opts.progress_payload);
} }
static int checkout_safe_for_update_only(const char *path, mode_t expected_mode) static int checkout_safe_for_update_only(
checkout_data *data, const char *path, mode_t expected_mode)
{ {
struct stat st; struct stat st;
data->perfdata.stat_calls++;
if (p_lstat(path, &st) < 0) { if (p_lstat(path, &st) < 0) {
/* if doesn't exist, then no error and no update */ /* if doesn't exist, then no error and no update */
if (errno == ENOENT || errno == ENOTDIR) if (errno == ENOENT || errno == ENOTDIR)
...@@ -1517,11 +1558,9 @@ static int checkout_write_content( ...@@ -1517,11 +1558,9 @@ static int checkout_write_content(
return error; return error;
if (S_ISLNK(mode)) if (S_ISLNK(mode))
error = blob_content_to_link( error = blob_content_to_link(data, st, blob, full_path);
st, blob, full_path, data->opts.dir_mode, data->can_symlink);
else else
error = blob_content_to_file( error = blob_content_to_file(data, st, blob, full_path, hint_path, mode);
st, blob, full_path, hint_path, mode, &data->opts);
git_blob_free(blob); git_blob_free(blob);
...@@ -1552,7 +1591,7 @@ static int checkout_blob( ...@@ -1552,7 +1591,7 @@ static int checkout_blob(
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) {
int rval = checkout_safe_for_update_only( int rval = checkout_safe_for_update_only(
git_buf_cstr(&data->path), file->mode); data, git_buf_cstr(&data->path), file->mode);
if (rval <= 0) if (rval <= 0)
return rval; return rval;
} }
...@@ -1807,7 +1846,7 @@ static int checkout_write_entry( ...@@ -1807,7 +1846,7 @@ static int checkout_write_entry(
} }
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 &&
(error = checkout_safe_for_update_only(git_buf_cstr(&data->path), side->mode)) <= 0) (error = checkout_safe_for_update_only(data, git_buf_cstr(&data->path), side->mode)) <= 0)
return error; return error;
return checkout_write_content(data, return checkout_write_content(data,
...@@ -1906,7 +1945,7 @@ static int checkout_write_merge( ...@@ -1906,7 +1945,7 @@ static int checkout_write_merge(
goto done; goto done;
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 &&
(error = checkout_safe_for_update_only(git_buf_cstr(&path_workdir), result.mode)) <= 0) (error = checkout_safe_for_update_only(data, git_buf_cstr(&path_workdir), result.mode)) <= 0)
goto done; goto done;
if (!data->opts.disable_filters) { if (!data->opts.disable_filters) {
...@@ -1922,7 +1961,7 @@ static int checkout_write_merge( ...@@ -1922,7 +1961,7 @@ static int checkout_write_merge(
out_data.size = result.len; out_data.size = result.len;
} }
if ((error = git_futils_mkpath2file(path_workdir.ptr, 0755)) < 0 || if ((error = mkpath2file(data, path_workdir.ptr, data->opts.dir_mode)) < 0 ||
(error = git_filebuf_open(&output, git_buf_cstr(&path_workdir), GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 || (error = git_filebuf_open(&output, git_buf_cstr(&path_workdir), GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 ||
(error = git_filebuf_write(&output, out_data.ptr, out_data.size)) < 0 || (error = git_filebuf_write(&output, out_data.ptr, out_data.size)) < 0 ||
(error = git_filebuf_commit(&output)) < 0) (error = git_filebuf_commit(&output)) < 0)
...@@ -2157,7 +2196,8 @@ static int checkout_data_init( ...@@ -2157,7 +2196,8 @@ static int checkout_data_init(
if (!data->opts.target_directory) if (!data->opts.target_directory)
data->opts.target_directory = git_repository_workdir(repo); data->opts.target_directory = git_repository_workdir(repo);
else if (!git_path_isdir(data->opts.target_directory) && else if (!git_path_isdir(data->opts.target_directory) &&
(error = git_futils_mkdir(data->opts.target_directory, NULL, (error = checkout_mkdir(data,
data->opts.target_directory, NULL,
GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0) GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0)
goto cleanup; goto cleanup;
......
...@@ -12,6 +12,13 @@ ...@@ -12,6 +12,13 @@
#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12) #define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12)
/** Internal structure; this is exposed in future versions. */
typedef struct {
size_t mkdir_calls;
size_t stat_calls;
size_t chmod_calls;
} git_checkout_perfdata;
/** /**
* Update the working directory to match the target iterator. The * Update the working directory to match the target iterator. The
* expected baseline value can be passed in via the checkout options * expected baseline value can be passed in via the checkout options
......
...@@ -279,11 +279,12 @@ void git_futils_mmap_free(git_map *out) ...@@ -279,11 +279,12 @@ void git_futils_mmap_free(git_map *out)
p_munmap(out); p_munmap(out);
} }
int git_futils_mkdir( int git_futils_mkdir_withperf(
const char *path, const char *path,
const char *base, const char *base,
mode_t mode, mode_t mode,
uint32_t flags) uint32_t flags,
struct git_futils_mkdir_perfdata *perfdata)
{ {
int error = -1; int error = -1;
git_buf make_path = GIT_BUF_INIT; git_buf make_path = GIT_BUF_INIT;
...@@ -352,16 +353,21 @@ int git_futils_mkdir( ...@@ -352,16 +353,21 @@ int git_futils_mkdir(
st.st_mode = 0; st.st_mode = 0;
/* make directory */ /* make directory */
perfdata->mkdir_calls++;
if (p_mkdir(make_path.ptr, mode) < 0) { if (p_mkdir(make_path.ptr, mode) < 0) {
int tmp_errno = giterr_system_last(); int tmp_errno = giterr_system_last();
/* ignore error if not at end or if directory already exists */ /* ignore error if not at end or if directory already exists */
if (lastch == '\0' && if (lastch == '\0') {
(p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode))) { perfdata->stat_calls++;
if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
giterr_system_set(tmp_errno); giterr_system_set(tmp_errno);
giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr); giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr);
goto done; goto done;
} }
}
/* with exclusive create, existing dir is an error */ /* with exclusive create, existing dir is an error */
if ((flags & GIT_MKDIR_EXCL) != 0) { if ((flags & GIT_MKDIR_EXCL) != 0) {
...@@ -374,29 +380,46 @@ int git_futils_mkdir( ...@@ -374,29 +380,46 @@ int git_futils_mkdir(
/* chmod if requested and necessary */ /* chmod if requested and necessary */
if (((flags & GIT_MKDIR_CHMOD_PATH) != 0 || if (((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||
(lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) && (lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) &&
st.st_mode != mode && st.st_mode != mode) {
(error = p_chmod(make_path.ptr, mode)) < 0 &&
perfdata->chmod_calls++;
if ((error = p_chmod(make_path.ptr, mode)) < 0 &&
lastch == '\0') { lastch == '\0') {
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr); giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr);
goto done; goto done;
} }
} }
}
error = 0; error = 0;
/* check that full path really is a directory if requested & needed */ /* check that full path really is a directory if requested & needed */
if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 && if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
lastch != '\0' && lastch != '\0') {
(p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode))) { perfdata->stat_calls++;
if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
giterr_set(GITERR_OS, "Path is not a directory '%s'", make_path.ptr); giterr_set(GITERR_OS, "Path is not a directory '%s'", make_path.ptr);
error = GIT_ENOTFOUND; error = GIT_ENOTFOUND;
} }
}
done: done:
git_buf_free(&make_path); git_buf_free(&make_path);
return error; return error;
} }
int git_futils_mkdir(
const char *path,
const char *base,
mode_t mode,
uint32_t flags)
{
struct git_futils_mkdir_perfdata perfdata = {0};
return git_futils_mkdir_withperf(path, base, mode, flags, &perfdata);
}
int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) 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); return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH);
......
...@@ -84,6 +84,13 @@ typedef enum { ...@@ -84,6 +84,13 @@ typedef enum {
GIT_MKDIR_VERIFY_DIR = 64, GIT_MKDIR_VERIFY_DIR = 64,
} git_futils_mkdir_flags; } git_futils_mkdir_flags;
struct git_futils_mkdir_perfdata
{
size_t stat_calls;
size_t mkdir_calls;
size_t chmod_calls;
};
/** /**
* Create a directory or entire path. * Create a directory or entire path.
* *
...@@ -95,8 +102,15 @@ typedef enum { ...@@ -95,8 +102,15 @@ typedef enum {
* @param base Root for relative path. These directories will never be made. * @param base Root for relative path. These directories will never be made.
* @param mode The mode to use for created directories. * @param mode The mode to use for created directories.
* @param flags Combination of the mkdir flags above. * @param flags Combination of the mkdir flags above.
* @param perfdata Performance data, use `git_futils_mkdir` if you don't want this data.
* @return 0 on success, else error code * @return 0 on success, else error code
*/ */
extern int git_futils_mkdir_withperf(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_perfdata *perfdata);
/**
* Create a directory or entire path. Similar to `git_futils_mkdir_withperf`
* without performance data.
*/
extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uint32_t flags); extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uint32_t flags);
/** /**
......
...@@ -1101,3 +1101,4 @@ void test_checkout_tree__case_changing_rename(void) ...@@ -1101,3 +1101,4 @@ void test_checkout_tree__case_changing_rename(void)
git_commit_free(dir_commit); git_commit_free(dir_commit);
git_commit_free(master_commit); git_commit_free(master_commit);
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment