vendor/libgit2/src/checkout.c in rugged-0.22.0b5 vs vendor/libgit2/src/checkout.c in rugged-0.22.1b1

- old
+ new

@@ -65,10 +65,12 @@ unsigned int strategy; int can_symlink; bool reload_submodules; size_t total_steps; size_t completed_steps; + git_checkout_perfdata perfdata; + git_buf last_mkdir; } checkout_data; typedef struct { const git_index_entry *ancestor; const git_index_entry *ours; @@ -1099,11 +1101,11 @@ "Index inconsistency, truncated index while loading expected conflict '%s'", path); error = -1; goto done; } - prefixed = git_path_equal_or_prefixed(path, entry->path); + prefixed = git_path_equal_or_prefixed(path, entry->path, NULL); if (prefixed == GIT_PATH_EQUAL) continue; if (prefixed == GIT_PATH_PREFIX) @@ -1287,99 +1289,188 @@ git_pool_clear(&pathpool); 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 bool should_remove_existing(checkout_data *data) +{ + int ignorecase = 0; + + git_repository__cvar(&ignorecase, data->repo, GIT_CVAR_IGNORECASE); + + return (ignorecase && + (data->strategy & GIT_CHECKOUT_DONT_REMOVE_EXISTING) == 0); +} + +#define MKDIR_NORMAL \ + GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR +#define MKDIR_REMOVE_EXISTING \ + MKDIR_NORMAL | GIT_MKDIR_REMOVE_FILES | GIT_MKDIR_REMOVE_SYMLINKS + +static int mkpath2file( + checkout_data *data, const char *path, unsigned int mode) +{ + git_buf *mkdir_path = &data->tmp; + struct stat st; + bool remove_existing = should_remove_existing(data); + int error; + + if ((error = git_buf_sets(mkdir_path, path)) < 0) + return error; + + git_buf_rtruncate_at_char(mkdir_path, '/'); + + if (!data->last_mkdir.size || + data->last_mkdir.size != mkdir_path->size || + memcmp(mkdir_path->ptr, data->last_mkdir.ptr, mkdir_path->size) != 0) { + + if ((error = checkout_mkdir( + data, mkdir_path->ptr, data->opts.target_directory, mode, + remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL)) < 0) + return error; + + git_buf_swap(&data->last_mkdir, mkdir_path); + } + + if (remove_existing) { + data->perfdata.stat_calls++; + + if (p_lstat(path, &st) == 0) { + + /* Some file, symlink or folder already exists at this name. + * We would have removed it in remove_the_old unless we're on + * a case inensitive filesystem (or the user has asked us not + * to). Remove the similarly named file to write the new. + */ + error = git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES); + } else if (errno != ENOENT) { + giterr_set(GITERR_OS, "Failed to stat file '%s'", path); + return GIT_EEXISTS; + } else { + giterr_clear(); + } + } + + return error; +} + 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); if (!error) error = git_filter_list_apply_to_blob(&out, fl, blob); 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); } return error; } 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); + giterr_set(GITERR_OS, "Could not create symlink %s", path); } else { error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path); } if (!error) { + data->perfdata.stat_calls++; + if ((error = p_lstat(path, st)) < 0) giterr_set(GITERR_CHECKOUT, "Could not stat symlink %s", path); st->st_mode = GIT_FILEMODE_LINK; } @@ -1419,10 +1510,11 @@ git_buf_truncate(&data->path, data->workdir_len); 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); return GIT_ENOTFOUND; } @@ -1434,19 +1526,21 @@ static int checkout_submodule( checkout_data *data, const git_diff_file *file) { + bool remove_existing = should_remove_existing(data); int error = 0; /* Until submodules are supported, UPDATE_ONLY means do nothing here */ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) return 0; - if ((error = git_futils_mkdir( - file->path, data->opts.target_directory, - data->opts.dir_mode, GIT_MKDIR_PATH)) < 0) + if ((error = checkout_mkdir( + data, + file->path, data->opts.target_directory, data->opts.dir_mode, + remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL)) < 0) return error; if ((error = git_submodule_lookup(NULL, data->repo, file->path)) < 0) { /* I've observed repos with submodules in the tree that do not * have a .gitmodules - core Git just makes an empty directory @@ -1479,14 +1573,17 @@ data->opts.progress_cb( path, data->completed_steps, data->total_steps, 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) return 0; @@ -1515,15 +1612,13 @@ if ((error = git_blob_lookup(&blob, data->repo, oid)) < 0) 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); /* if we try to create the blob and an existing directory blocks it from * being written, then there must have been a typechange conflict in a @@ -1550,11 +1645,11 @@ if (git_buf_puts(&data->path, file->path) < 0) return -1; 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; } error = checkout_write_content( @@ -1805,11 +1900,11 @@ hint_path = side->path; } 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, &side->id, git_buf_cstr(&data->path), hint_path, side->mode, &st); } @@ -1904,11 +1999,11 @@ if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0) 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) { in_data.ptr = (char *)result.ptr; in_data.size = result.len; @@ -1920,11 +2015,11 @@ } else { out_data.ptr = (char *)result.ptr; 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) goto done; @@ -2155,12 +2250,13 @@ memmove(&data->opts, proposed, sizeof(git_checkout_options)); 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 */ if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) { git_config *cfg; @@ -2472,10 +2568,10 @@ } if ((error = git_repository_index(&index, repo)) < 0) return error; - if (!(error = git_iterator_for_tree(&tree_i, tree, 0, NULL, NULL))) + if (!(error = git_iterator_for_tree(&tree_i, tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL))) error = git_checkout_iterator(tree_i, index, opts); git_iterator_free(tree_i); git_index_free(index); git_tree_free(tree);