From 1d50b3649d578f12918b7996ad7b98a438bc0616 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 12 Jan 2015 16:16:27 -0600 Subject: [PATCH] checkout: introduce git_checkout_perfdata Checkout can now provide performance data about the number of (some) syscalls performed using an optional callback. --- include/git2/checkout.h | 15 ++++++ src/checkout.c | 111 ++++++++++++++++++++++++++++------------ src/fileops.c | 55 ++++++++++++++------ src/fileops.h | 14 +++++ tests/checkout/tree.c | 29 +++++++++++ 5 files changed, 174 insertions(+), 50 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index fe4966aa2..ffbb53fc5 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -206,6 +206,12 @@ typedef enum { GIT_CHECKOUT_NOTIFY_ALL = 0x0FFFFu } git_checkout_notify_t; +typedef struct { + size_t mkdir_calls; + size_t stat_calls; + size_t chmod_calls; +} git_checkout_perfdata; + /** Checkout notification callback function */ typedef int (*git_checkout_notify_cb)( git_checkout_notify_t why, @@ -222,6 +228,11 @@ typedef void (*git_checkout_progress_cb)( size_t total_steps, void *payload); +/** Checkout perfdata notification function */ +typedef void (*git_checkout_perfdata_cb)( + const git_checkout_perfdata *perfdata, + void *payload); + /** * Checkout options structure * @@ -261,6 +272,10 @@ typedef struct git_checkout_options { const char *ancestor_label; /**< the name of the common ancestor side of conflicts */ const char *our_label; /**< the name of the "our" side of conflicts */ const char *their_label; /**< the name of the "their" side of conflicts */ + + /** Optional callback to notify the consumer of performance data. */ + git_checkout_perfdata_cb perfdata_cb; + void *perfdata_payload; } git_checkout_options; #define GIT_CHECKOUT_OPTIONS_VERSION 1 diff --git a/src/checkout.c b/src/checkout.c index 45e02e956..1074fe6bb 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -67,6 +67,7 @@ typedef struct { bool reload_submodules; size_t total_steps; size_t completed_steps; + git_checkout_perfdata perfdata; } checkout_data; typedef struct { @@ -1289,50 +1290,86 @@ fail: 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( + checkout_data *data, struct stat *st, git_buf *buf, const char *path, - mode_t dir_mode, - int file_open_flags, mode_t file_mode) { int error; - if ((error = git_futils_mkpath2file(path, dir_mode)) < 0) + if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0) return error; 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; - if (st != NULL && (error = p_stat(path, st)) < 0) - giterr_set(GITERR_OS, "Error statting '%s'", path); + if (st) { + data->perfdata.stat_calls++; - else if (GIT_PERMS_IS_EXEC(file_mode) && - (error = p_chmod(path, file_mode)) < 0) - giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); + if ((error = p_stat(path, st)) < 0) { + giterr_set(GITERR_OS, "Error statting '%s'", path); + return error; + } + } + + if (GIT_PERMS_IS_EXEC(file_mode)) { + data->perfdata.chmod_calls++; + + if ((error = p_chmod(path, file_mode)) < 0) + giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); + } return error; } static int blob_content_to_file( + checkout_data *data, struct stat *st, git_blob *blob, const char *path, const char * hint_path, - mode_t entry_filemode, - git_checkout_options *opts) + mode_t entry_filemode) { - int error = 0; - mode_t file_mode = opts->file_mode ? opts->file_mode : entry_filemode; + mode_t file_mode = data->opts.file_mode ? + data->opts.file_mode : entry_filemode; git_buf out = GIT_BUF_INIT; git_filter_list *fl = NULL; + int error = 0; if (hint_path == NULL) hint_path = path; - if (!opts->disable_filters) + if (!data->opts.disable_filters) error = git_filter_list_load( &fl, git_blob_owner(blob), blob, hint_path, GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT); @@ -1343,9 +1380,7 @@ static int blob_content_to_file( git_filter_list_free(fl); if (!error) { - error = buffer_to_file( - st, &out, path, opts->dir_mode, opts->file_open_flags, file_mode); - + error = buffer_to_file(data, st, &out, path, file_mode); st->st_mode = entry_filemode; git_buf_free(&out); @@ -1355,22 +1390,21 @@ static int blob_content_to_file( } static int blob_content_to_link( + checkout_data *data, struct stat *st, git_blob *blob, - const char *path, - mode_t dir_mode, - int can_symlink) + const char *path) { git_buf linktarget = GIT_BUF_INIT; int error; - if ((error = git_futils_mkpath2file(path, dir_mode)) < 0) + if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0) return error; if ((error = git_blob__getbuf(&linktarget, blob)) < 0) return error; - if (can_symlink) { + if (data->can_symlink) { if ((error = p_symlink(git_buf_cstr(&linktarget), path)) < 0) giterr_set(GITERR_OS, "Could not create symlink %s\n", path); } else { @@ -1378,6 +1412,8 @@ static int blob_content_to_link( } if (!error) { + data->perfdata.stat_calls++; + if ((error = p_lstat(path, st)) < 0) giterr_set(GITERR_CHECKOUT, "Could not stat symlink %s", path); @@ -1421,6 +1457,7 @@ static int checkout_submodule_update_index( if (git_buf_puts(&data->path, file->path) < 0) return -1; + data->perfdata.stat_calls++; if (p_stat(git_buf_cstr(&data->path), &st) < 0) { giterr_set( GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path); @@ -1442,7 +1479,8 @@ static int checkout_submodule( if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) return 0; - if ((error = git_futils_mkdir( + if ((error = checkout_mkdir( + data, file->path, data->opts.target_directory, data->opts.dir_mode, GIT_MKDIR_PATH)) < 0) return error; @@ -1481,10 +1519,13 @@ static void report_progress( 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; + data->perfdata.stat_calls++; + if (p_lstat(path, &st) < 0) { /* if doesn't exist, then no error and no update */ if (errno == ENOENT || errno == ENOTDIR) @@ -1517,11 +1558,9 @@ static int checkout_write_content( return error; if (S_ISLNK(mode)) - error = blob_content_to_link( - st, blob, full_path, data->opts.dir_mode, data->can_symlink); + error = blob_content_to_link(data, st, blob, full_path); else - error = blob_content_to_file( - st, blob, full_path, hint_path, mode, &data->opts); + error = blob_content_to_file(data, st, blob, full_path, hint_path, mode); git_blob_free(blob); @@ -1552,7 +1591,7 @@ static int checkout_blob( if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { 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) return rval; } @@ -1807,7 +1846,7 @@ static int checkout_write_entry( } 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 checkout_write_content(data, @@ -1906,7 +1945,7 @@ static int checkout_write_merge( goto done; 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; if (!data->opts.disable_filters) { @@ -1922,7 +1961,7 @@ static int checkout_write_merge( 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_write(&output, out_data.ptr, out_data.size)) < 0 || (error = git_filebuf_commit(&output)) < 0) @@ -2157,8 +2196,9 @@ static int checkout_data_init( if (!data->opts.target_directory) data->opts.target_directory = git_repository_workdir(repo); else if (!git_path_isdir(data->opts.target_directory) && - (error = git_futils_mkdir(data->opts.target_directory, NULL, - GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0) + (error = checkout_mkdir(data, + data->opts.target_directory, NULL, + GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0) goto cleanup; /* refresh config and index content unless NO_REFRESH is given */ @@ -2371,6 +2411,9 @@ int git_checkout_iterator( assert(data.completed_steps == data.total_steps); + if (data.opts.perfdata_cb) + data.opts.perfdata_cb(&data.perfdata, data.opts.perfdata_payload); + cleanup: if (!error && data.index != NULL && (data.strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) diff --git a/src/fileops.c b/src/fileops.c index bd9d27c7a..926813f5e 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -279,11 +279,12 @@ void git_futils_mmap_free(git_map *out) p_munmap(out); } -int git_futils_mkdir( +int git_futils_mkdir_withperf( const char *path, const char *base, mode_t mode, - uint32_t flags) + uint32_t flags, + struct git_futils_mkdir_perfdata *perfdata) { int error = -1; git_buf make_path = GIT_BUF_INIT; @@ -352,15 +353,20 @@ int git_futils_mkdir( st.st_mode = 0; /* make directory */ + perfdata->mkdir_calls++; + if (p_mkdir(make_path.ptr, mode) < 0) { int tmp_errno = giterr_system_last(); /* ignore error if not at end or if directory already exists */ - if (lastch == '\0' && - (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode))) { - giterr_system_set(tmp_errno); - giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr); - goto done; + if (lastch == '\0') { + perfdata->stat_calls++; + + if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) { + giterr_system_set(tmp_errno); + giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr); + goto done; + } } /* with exclusive create, existing dir is an error */ @@ -374,11 +380,15 @@ int git_futils_mkdir( /* chmod if requested and necessary */ if (((flags & GIT_MKDIR_CHMOD_PATH) != 0 || (lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) && - st.st_mode != mode && - (error = p_chmod(make_path.ptr, mode)) < 0 && - lastch == '\0') { - giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr); - goto done; + st.st_mode != mode) { + + perfdata->chmod_calls++; + + if ((error = p_chmod(make_path.ptr, mode)) < 0 && + lastch == '\0') { + giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr); + goto done; + } } } @@ -386,10 +396,13 @@ int git_futils_mkdir( /* check that full path really is a directory if requested & needed */ if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 && - lastch != '\0' && - (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); - error = GIT_ENOTFOUND; + lastch != '\0') { + 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); + error = GIT_ENOTFOUND; + } } done: @@ -397,6 +410,16 @@ done: 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) { return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH); diff --git a/src/fileops.h b/src/fileops.h index 4f5700a99..65b59522c 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -84,6 +84,13 @@ typedef enum { GIT_MKDIR_VERIFY_DIR = 64, } 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. * @@ -95,8 +102,15 @@ typedef enum { * @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. + * @param perfdata Performance data, use `git_futils_mkdir` if you don't want this data. * @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); /** diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index ffb513e23..f37e6d359 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -1101,3 +1101,32 @@ void test_checkout_tree__case_changing_rename(void) git_commit_free(dir_commit); git_commit_free(master_commit); } + +void perfdata_cb(const git_checkout_perfdata *in, void *payload) +{ + memcpy(payload, in, sizeof(git_checkout_perfdata)); +} + +void test_checkout_tree__can_collect_perfdata(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + git_checkout_perfdata perfdata = {0}; + + opts.perfdata_cb = perfdata_cb; + opts.perfdata_payload = &perfdata; + + assert_on_branch(g_repo, "master"); + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_assert(perfdata.mkdir_calls > 0); + cl_assert(perfdata.stat_calls > 0); + + git_object_free(obj); +}