Modified: subversion/branches/swig-py3/subversion/libsvn_client/shelf.c URL: http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/libsvn_client/shelf.c?rev=1847678&r1=1847677&r2=1847678&view=diff ============================================================================== --- subversion/branches/swig-py3/subversion/libsvn_client/shelf.c (original) +++ subversion/branches/swig-py3/subversion/libsvn_client/shelf.c Wed Nov 28 21:25:32 2018 @@ -1,5 +1,5 @@ /* - * shelve.c: implementation of the 'shelve' commands + * shelf.c: implementation of shelving * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one @@ -59,7 +59,7 @@ shelf_name_encode(char **encoded_name_p, while (*name) { - apr_snprintf(out_pos, 3, "%02x", *name++); + apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++)); out_pos += 2; } *encoded_name_p = encoded_name; @@ -114,67 +114,147 @@ shelf_name_from_filename(char **name, return SVN_NO_ERROR; } -/* Set *PATCH_ABSPATH to the abspath of the patch file for SHELF +/* Set *ABSPATH to the abspath of the file storage dir for SHELF * version VERSION, no matter whether it exists. */ static svn_error_t * -get_patch_abspath(const char **abspath, - svn_client_shelf_t *shelf, - int version, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +shelf_version_files_dir_abspath(const char **abspath, + svn_client__shelf_t *shelf, + int version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { char *codename; char *filename; SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool)); - filename = apr_psprintf(scratch_pool, "%s-%03d.patch", codename, version); + filename = apr_psprintf(scratch_pool, "%s-%03d.d", codename, version); *abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool); return SVN_NO_ERROR; } -/* Set *PATCH_ABSPATH to the abspath of the patch file for SHELF - * version VERSION. Error if VERSION is invalid or nonexistent. +/* Create a shelf-version object for a version that may or may not already + * exist on disk. */ static svn_error_t * -get_existing_patch_abspath(const char **abspath, - svn_client_shelf_t *shelf, - int version, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +shelf_version_create(svn_client__shelf_version_t **new_version_p, + svn_client__shelf_t *shelf, + int version_number, + apr_pool_t *result_pool) { - if (shelf->max_version <= 0) - return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL, - _("shelf '%s': no versions available"), - shelf->name); - if (version <= 0 || version > shelf->max_version) - return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL, - _("shelf '%s' has no version %d: max version is %d"), - shelf->name, version, shelf->max_version); + svn_client__shelf_version_t *shelf_version + = apr_pcalloc(result_pool, sizeof(*shelf_version)); - SVN_ERR(get_patch_abspath(abspath, shelf, version, - result_pool, scratch_pool)); + shelf_version->shelf = shelf; + shelf_version->version_number = version_number; + SVN_ERR(shelf_version_files_dir_abspath(&shelf_version->files_dir_abspath, + shelf, version_number, + result_pool, result_pool)); + *new_version_p = shelf_version; return SVN_NO_ERROR; } +/* Set *ABSPATH to the abspath of the metadata file for SHELF_VERSION + * node at RELPATH, no matter whether it exists. + */ static svn_error_t * -shelf_delete_patch_file(svn_client_shelf_t *shelf, - int version, - apr_pool_t *scratch_pool) +get_metadata_abspath(char **abspath, + svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - const char *patch_abspath; + wc_relpath = apr_psprintf(scratch_pool, "%s.meta", wc_relpath); + *abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, + result_pool); + return SVN_NO_ERROR; +} - SVN_ERR(get_existing_patch_abspath(&patch_abspath, shelf, version, - scratch_pool, scratch_pool)); - SVN_ERR(svn_io_remove_file2(patch_abspath, TRUE /*ignore_enoent*/, - scratch_pool)); +/* Set *ABSPATH to the abspath of the base text file for SHELF_VERSION + * node at RELPATH, no matter whether it exists. + */ +static svn_error_t * +get_base_file_abspath(char **base_abspath, + svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_relpath = apr_psprintf(scratch_pool, "%s.base", wc_relpath); + *base_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, + result_pool); + return SVN_NO_ERROR; +} + +/* Set *ABSPATH to the abspath of the working text file for SHELF_VERSION + * node at RELPATH, no matter whether it exists. + */ +static svn_error_t * +get_working_file_abspath(char **work_abspath, + svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_relpath = apr_psprintf(scratch_pool, "%s.work", wc_relpath); + *work_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, + result_pool); + return SVN_NO_ERROR; +} + +/* Set *ABSPATH to the abspath of the base props file for SHELF_VERSION + * node at RELPATH, no matter whether it exists. + */ +static svn_error_t * +get_base_props_abspath(char **base_abspath, + svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_relpath = apr_psprintf(scratch_pool, "%s.base-props", wc_relpath); + *base_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, + result_pool); + return SVN_NO_ERROR; +} + +/* Set *ABSPATH to the abspath of the working props file for SHELF_VERSION + * node at RELPATH, no matter whether it exists. + */ +static svn_error_t * +get_working_props_abspath(char **work_abspath, + svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_relpath = apr_psprintf(scratch_pool, "%s.work-props", wc_relpath); + *work_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, + result_pool); + return SVN_NO_ERROR; +} + +/* Delete the storage for SHELF:VERSION. */ +static svn_error_t * +shelf_version_delete(svn_client__shelf_t *shelf, + int version, + apr_pool_t *scratch_pool) +{ + const char *files_dir_abspath; + + SVN_ERR(shelf_version_files_dir_abspath(&files_dir_abspath, + shelf, version, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_remove_dir2(files_dir_abspath, TRUE /*ignore_enoent*/, + NULL, NULL, /*cancel*/ + scratch_pool)); return SVN_NO_ERROR; } /* */ static svn_error_t * get_log_abspath(char **log_abspath, - svn_client_shelf_t *shelf, + svn_client__shelf_t *shelf, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -187,10 +267,12 @@ get_log_abspath(char **log_abspath, return SVN_NO_ERROR; } -/* Set SHELF->revprops by reading from its file storage. +/* Set SHELF->revprops by reading from its storage (the '.log' file). + * Set SHELF->revprops to empty if the storage file does not exist; this + * is not an error. */ static svn_error_t * -shelf_read_revprops(svn_client_shelf_t *shelf, +shelf_read_revprops(svn_client__shelf_t *shelf, apr_pool_t *scratch_pool) { char *log_abspath; @@ -217,7 +299,7 @@ shelf_read_revprops(svn_client_shelf_t * /* Write SHELF's revprops to its file storage. */ static svn_error_t * -shelf_write_revprops(svn_client_shelf_t *shelf, +shelf_write_revprops(svn_client__shelf_t *shelf, apr_pool_t *scratch_pool) { char *log_abspath; @@ -237,10 +319,10 @@ shelf_write_revprops(svn_client_shelf_t } svn_error_t * -svn_client__shelf_revprop_set(svn_client_shelf_t *shelf, - const char *prop_name, - const svn_string_t *prop_val, - apr_pool_t *scratch_pool) +svn_client__shelf_revprop_set(svn_client__shelf_t *shelf, + const char *prop_name, + const svn_string_t *prop_val, + apr_pool_t *scratch_pool) { svn_hash_sets(shelf->revprops, apr_pstrdup(shelf->pool, prop_name), svn_string_dup(prop_val, shelf->pool)); @@ -249,10 +331,24 @@ svn_client__shelf_revprop_set(svn_client } svn_error_t * +svn_client__shelf_revprop_set_all(svn_client__shelf_t *shelf, + apr_hash_t *revprop_table, + apr_pool_t *scratch_pool) +{ + if (revprop_table) + shelf->revprops = svn_prop_hash_dup(revprop_table, shelf->pool); + else + shelf->revprops = apr_hash_make(shelf->pool); + + SVN_ERR(shelf_write_revprops(shelf, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * svn_client__shelf_revprop_get(svn_string_t **prop_val, - svn_client_shelf_t *shelf, - const char *prop_name, - apr_pool_t *result_pool) + svn_client__shelf_t *shelf, + const char *prop_name, + apr_pool_t *result_pool) { *prop_val = svn_hash_gets(shelf->revprops, prop_name); return SVN_NO_ERROR; @@ -260,8 +356,8 @@ svn_client__shelf_revprop_get(svn_string svn_error_t * svn_client__shelf_revprop_list(apr_hash_t **props, - svn_client_shelf_t *shelf, - apr_pool_t *result_pool) + svn_client__shelf_t *shelf, + apr_pool_t *result_pool) { *props = shelf->revprops; return SVN_NO_ERROR; @@ -270,91 +366,735 @@ svn_client__shelf_revprop_list(apr_hash_ /* */ static svn_error_t * get_current_abspath(char **current_abspath, - svn_client_shelf_t *shelf, + svn_client__shelf_t *shelf, apr_pool_t *result_pool) { char *codename; char *filename; - SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool)); - filename = apr_psprintf(result_pool, "%s.current", codename); - *current_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool); + SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool)); + filename = apr_psprintf(result_pool, "%s.current", codename); + *current_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool); + return SVN_NO_ERROR; +} + +/* Read SHELF->max_version from its storage (the '.current' file). + * Set SHELF->max_version to -1 if that file does not exist. + */ +static svn_error_t * +shelf_read_current(svn_client__shelf_t *shelf, + apr_pool_t *scratch_pool) +{ + char *current_abspath; + svn_error_t *err; + + SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool)); + err = svn_io_read_version_file(&shelf->max_version, + current_abspath, scratch_pool); + if (err) + { + shelf->max_version = -1; + svn_error_clear(err); + return SVN_NO_ERROR; + } + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +shelf_write_current(svn_client__shelf_t *shelf, + apr_pool_t *scratch_pool) +{ + char *current_abspath; + + SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool)); + SVN_ERR(svn_io_write_version_file(current_abspath, shelf->max_version, + scratch_pool)); + return SVN_NO_ERROR; +} + +/*-------------------------------------------------------------------------*/ +/* Status Reporting */ + +/* Create a status struct with all fields initialized to valid values + * representing 'uninteresting' or 'unknown' status. + */ +static svn_wc_status3_t * +status_create(apr_pool_t *result_pool) +{ + svn_wc_status3_t *s = apr_pcalloc(result_pool, sizeof(*s)); + + s->filesize = SVN_INVALID_FILESIZE; + s->versioned = TRUE; + s->node_status = svn_wc_status_none; + s->text_status = svn_wc_status_none; + s->prop_status = svn_wc_status_none; + s->revision = SVN_INVALID_REVNUM; + s->changed_rev = SVN_INVALID_REVNUM; + s->repos_node_status = svn_wc_status_none; + s->repos_text_status = svn_wc_status_none; + s->repos_prop_status = svn_wc_status_none; + s->ood_changed_rev = SVN_INVALID_REVNUM; + return s; +} + +/* Convert from svn_node_kind_t to a single character representation. */ +static char +kind_to_char(svn_node_kind_t kind) +{ + return (kind == svn_node_dir ? 'd' + : kind == svn_node_file ? 'f' + : kind == svn_node_symlink ? 'l' + : '?'); +} + +/* Convert to svn_node_kind_t from a single character representation. */ +static svn_node_kind_t +char_to_kind(char kind) +{ + return (kind == 'd' ? svn_node_dir + : kind == 'f' ? svn_node_file + : kind == 'l' ? svn_node_symlink + : svn_node_unknown); +} + +/* Return the single character representation of STATUS. + * (Similar to subversion/svn/status.c:generate_status_code() + * and subversion/tests/libsvn_client/client-test.c:status_to_char().) */ +static char +status_to_char(enum svn_wc_status_kind status) +{ + switch (status) + { + case svn_wc_status_none: return '.'; + case svn_wc_status_unversioned: return '?'; + case svn_wc_status_normal: return ' '; + case svn_wc_status_added: return 'A'; + case svn_wc_status_missing: return '!'; + case svn_wc_status_deleted: return 'D'; + case svn_wc_status_replaced: return 'R'; + case svn_wc_status_modified: return 'M'; + case svn_wc_status_merged: return 'G'; + case svn_wc_status_conflicted: return 'C'; + case svn_wc_status_ignored: return 'I'; + case svn_wc_status_obstructed: return '~'; + case svn_wc_status_external: return 'X'; + case svn_wc_status_incomplete: return ':'; + default: return '*'; + } +} + +static enum svn_wc_status_kind +char_to_status(char status) +{ + switch (status) + { + case '.': return svn_wc_status_none; + case '?': return svn_wc_status_unversioned; + case ' ': return svn_wc_status_normal; + case 'A': return svn_wc_status_added; + case '!': return svn_wc_status_missing; + case 'D': return svn_wc_status_deleted; + case 'R': return svn_wc_status_replaced; + case 'M': return svn_wc_status_modified; + case 'G': return svn_wc_status_merged; + case 'C': return svn_wc_status_conflicted; + case 'I': return svn_wc_status_ignored; + case '~': return svn_wc_status_obstructed; + case 'X': return svn_wc_status_external; + case ':': return svn_wc_status_incomplete; + default: return (enum svn_wc_status_kind)0; + } +} + +/* Write a serial representation of (some fields of) STATUS to STREAM. + */ +static svn_error_t * +wc_status_serialize(svn_stream_t *stream, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_stream_printf(stream, scratch_pool, "%c %c%c%c %ld", + kind_to_char(status->kind), + status_to_char(status->node_status), + status_to_char(status->text_status), + status_to_char(status->prop_status), + status->revision)); + return SVN_NO_ERROR; +} + +/* Read a serial representation of (some fields of) STATUS from STREAM. + */ +static svn_error_t * +wc_status_unserialize(svn_wc_status3_t *status, + svn_stream_t *stream, + apr_pool_t *result_pool) +{ + svn_stringbuf_t *sb; + char *string; + + SVN_ERR(svn_stringbuf_from_stream(&sb, stream, 100, result_pool)); + string = sb->data; + status->kind = char_to_kind(string[0]); + status->node_status = char_to_status(string[2]); + status->text_status = char_to_status(string[3]); + status->prop_status = char_to_status(string[4]); + sscanf(string + 6, "%ld", &status->revision); + return SVN_NO_ERROR; +} + +/* Write status to shelf storage. + */ +static svn_error_t * +status_write(svn_client__shelf_version_t *shelf_version, + const char *relpath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + char *file_abspath; + svn_stream_t *stream; + + SVN_ERR(get_metadata_abspath(&file_abspath, shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_writable(&stream, file_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(wc_status_serialize(stream, status, scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + return SVN_NO_ERROR; +} + +/* Read status from shelf storage. + */ +static svn_error_t * +status_read(svn_wc_status3_t **status, + svn_client__shelf_version_t *shelf_version, + const char *relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_status3_t *s = status_create(result_pool); + char *file_abspath; + svn_stream_t *stream; + + SVN_ERR(get_metadata_abspath(&file_abspath, shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_readonly(&stream, file_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(wc_status_unserialize(s, stream, result_pool)); + SVN_ERR(svn_stream_close(stream)); + + s->changelist = apr_psprintf(result_pool, "svn:shelf:%s", + shelf_version->shelf->name); + *status = s; + return SVN_NO_ERROR; +} + +/* A visitor function type for use with shelf_status_walk(). + * The same as svn_wc_status_func4_t except relpath instead of abspath. + * Only some fields in STATUS are available. + */ +typedef svn_error_t *(*shelf_status_visitor_t)(void *baton, + const char *relpath, + svn_wc_status3_t *status, + apr_pool_t *scratch_pool); + +/* Baton for shelved_files_walk_visitor(). */ +struct shelf_status_baton_t +{ + svn_client__shelf_version_t *shelf_version; + const char *top_relpath; + const char *walk_root_abspath; + shelf_status_visitor_t walk_func; + void *walk_baton; +}; + +/* Call BATON->walk_func(BATON->walk_baton, relpath, ...) for the shelved + * 'binary' file stored at ABSPATH. + * Implements svn_io_walk_func_t. */ +static svn_error_t * +shelf_status_visitor(void *baton, + const char *abspath, + const apr_finfo_t *finfo, + apr_pool_t *scratch_pool) +{ + struct shelf_status_baton_t *b = baton; + const char *relpath; + + relpath = svn_dirent_skip_ancestor(b->walk_root_abspath, abspath); + if (finfo->filetype == APR_REG + && (strlen(relpath) >= 5 && strcmp(relpath+strlen(relpath)-5, ".meta") == 0)) + { + svn_wc_status3_t *s; + + relpath = apr_pstrndup(scratch_pool, relpath, strlen(relpath) - 5); + if (!svn_relpath_skip_ancestor(b->top_relpath, relpath)) + return SVN_NO_ERROR; + + SVN_ERR(status_read(&s, b->shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(b->walk_func(b->walk_baton, relpath, s, scratch_pool)); + } + return SVN_NO_ERROR; +} + +/* Report the shelved status of the path SHELF_VERSION:WC_RELPATH + * via WALK_FUNC(WALK_BATON, ...). + */ +static svn_error_t * +shelf_status_visit_path(svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + shelf_status_visitor_t walk_func, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + struct shelf_status_baton_t baton; + char *abspath; + apr_finfo_t finfo; + + baton.shelf_version = shelf_version; + baton.top_relpath = wc_relpath; + baton.walk_root_abspath = shelf_version->files_dir_abspath; + baton.walk_func = walk_func; + baton.walk_baton = walk_baton; + SVN_ERR(get_metadata_abspath(&abspath, shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_stat(&finfo, abspath, APR_FINFO_TYPE, scratch_pool)); + SVN_ERR(shelf_status_visitor(&baton, abspath, &finfo, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Report the shelved status of all the shelved paths in SHELF_VERSION + * via WALK_FUNC(WALK_BATON, ...). + */ +static svn_error_t * +shelf_status_walk(svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + shelf_status_visitor_t walk_func, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + struct shelf_status_baton_t baton; + svn_error_t *err; + + baton.shelf_version = shelf_version; + baton.top_relpath = wc_relpath; + baton.walk_root_abspath = shelf_version->files_dir_abspath; + baton.walk_func = walk_func; + baton.walk_baton = walk_baton; + err = svn_io_dir_walk2(baton.walk_root_abspath, 0 /*wanted*/, + shelf_status_visitor, &baton, + scratch_pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + svn_error_clear(err); + else + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +typedef struct wc_status_baton_t +{ + svn_client__shelf_version_t *shelf_version; + svn_wc_status_func4_t walk_func; + void *walk_baton; +} wc_status_baton_t; + +static svn_error_t * +wc_status_visitor(void *baton, + const char *relpath, + svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + struct wc_status_baton_t *b = baton; + svn_client__shelf_t *shelf = b->shelf_version->shelf; + const char *abspath = svn_dirent_join(shelf->wc_root_abspath, relpath, + scratch_pool); + SVN_ERR(b->walk_func(b->walk_baton, abspath, status, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_version_status_walk(svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + svn_wc_status_func4_t walk_func, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + wc_status_baton_t baton; + + baton.shelf_version = shelf_version; + baton.walk_func = walk_func; + baton.walk_baton = walk_baton; + SVN_ERR(shelf_status_walk(shelf_version, wc_relpath, + wc_status_visitor, &baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/*-------------------------------------------------------------------------*/ +/* Shelf Storage */ + +/* A baton for use with write_changes_visitor(). */ +typedef struct write_changes_baton_t { + const char *wc_root_abspath; + svn_client__shelf_version_t *shelf_version; + svn_client_ctx_t *ctx; + svn_boolean_t any_shelved; /* were any paths successfully shelved? */ + svn_client_status_func_t was_shelved_func; + void *was_shelved_baton; + svn_client_status_func_t was_not_shelved_func; + void *was_not_shelved_baton; + apr_pool_t *pool; /* pool for data in 'unshelvable', etc. */ +} write_changes_baton_t; + +/* */ +static svn_error_t * +notify_shelved(write_changes_baton_t *wb, + const char *wc_relpath, + const char *local_abspath, + const svn_wc_status3_t *wc_status, + apr_pool_t *scratch_pool) +{ + if (wb->was_shelved_func) + { + svn_client_status_t *cst; + + SVN_ERR(svn_client__create_status(&cst, wb->ctx->wc_ctx, local_abspath, + wc_status, + scratch_pool, scratch_pool)); + SVN_ERR(wb->was_shelved_func(wb->was_shelved_baton, + wc_relpath, cst, scratch_pool)); + } + + wb->any_shelved = TRUE; + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +notify_not_shelved(write_changes_baton_t *wb, + const char *wc_relpath, + const char *local_abspath, + const svn_wc_status3_t *wc_status, + apr_pool_t *scratch_pool) +{ + if (wb->was_not_shelved_func) + { + svn_client_status_t *cst; + + SVN_ERR(svn_client__create_status(&cst, wb->ctx->wc_ctx, local_abspath, + wc_status, + scratch_pool, scratch_pool)); + SVN_ERR(wb->was_not_shelved_func(wb->was_not_shelved_baton, + wc_relpath, cst, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* Read BASE_PROPS and WORK_PROPS from the WC, setting each to null if + * the node has no base or working version (respectively). + */ +static svn_error_t * +read_props_from_wc(apr_hash_t **base_props, + apr_hash_t **work_props, + enum svn_wc_status_kind node_status, + const char *from_wc_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (node_status != svn_wc_status_added) + SVN_ERR(svn_wc_get_pristine_props(base_props, ctx->wc_ctx, from_wc_abspath, + result_pool, scratch_pool)); + else + *base_props = NULL; + if (node_status != svn_wc_status_deleted) + SVN_ERR(svn_wc_prop_list2(work_props, ctx->wc_ctx, from_wc_abspath, + result_pool, scratch_pool)); + else + *work_props = NULL; + return SVN_NO_ERROR; +} + +/* Write BASE_PROPS and WORK_PROPS to storage in SHELF_VERSION:WC_RELPATH. + */ +static svn_error_t * +write_props_to_shelf(svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + apr_hash_t *base_props, + apr_hash_t *work_props, + apr_pool_t *scratch_pool) +{ + char *stored_props_abspath; + svn_stream_t *stream; + + if (base_props) + { + SVN_ERR(get_base_props_abspath(&stored_props_abspath, + shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_writable(&stream, stored_props_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_hash_write2(base_props, stream, NULL, scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + } + + if (work_props) + { + SVN_ERR(get_working_props_abspath(&stored_props_abspath, + shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_writable(&stream, stored_props_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_hash_write2(work_props, stream, NULL, scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + } + + return SVN_NO_ERROR; +} + +/* Read BASE_PROPS and WORK_PROPS from storage in SHELF_VERSION:WC_RELPATH. + */ +static svn_error_t * +read_props_from_shelf(apr_hash_t **base_props, + apr_hash_t **work_props, + enum svn_wc_status_kind node_status, + svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *stored_props_abspath; + svn_stream_t *stream; + + if (node_status != svn_wc_status_added) + { + *base_props = apr_hash_make(result_pool); + SVN_ERR(get_base_props_abspath(&stored_props_abspath, + shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_readonly(&stream, stored_props_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_hash_read2(*base_props, stream, NULL, scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + } + else + *base_props = NULL; + + if (node_status != svn_wc_status_deleted) + { + *work_props = apr_hash_make(result_pool); + SVN_ERR(get_working_props_abspath(&stored_props_abspath, + shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_readonly(&stream, stored_props_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_hash_read2(*work_props, stream, NULL, scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + } + else + *work_props = NULL; + + return SVN_NO_ERROR; +} + +/* Store metadata for any node, and base and working files if it's a file. + * + * Copy the WC base and working files at FROM_WC_ABSPATH to the storage + * area in SHELF_VERSION. + */ +static svn_error_t * +store_file(const char *from_wc_abspath, + const char *wc_relpath, + svn_client__shelf_version_t *shelf_version, + const svn_wc_status3_t *status, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + char *stored_abspath; + apr_hash_t *base_props, *work_props; + + SVN_ERR(get_working_file_abspath(&stored_abspath, + shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(stored_abspath, + scratch_pool), + scratch_pool)); + SVN_ERR(status_write(shelf_version, wc_relpath, + status, scratch_pool)); + + /* properties */ + SVN_ERR(read_props_from_wc(&base_props, &work_props, + status->node_status, + from_wc_abspath, ctx, + scratch_pool, scratch_pool)); + SVN_ERR(write_props_to_shelf(shelf_version, wc_relpath, + base_props, work_props, + scratch_pool)); + + /* file text */ + if (status->kind == svn_node_file) + { + svn_stream_t *wc_base_stream; + svn_node_kind_t work_kind; + + /* Copy the base file (copy-from base, if copied/moved), if present */ + SVN_ERR(svn_wc_get_pristine_contents2(&wc_base_stream, + ctx->wc_ctx, from_wc_abspath, + scratch_pool, scratch_pool)); + if (wc_base_stream) + { + char *stored_base_abspath; + svn_stream_t *stored_base_stream; + + SVN_ERR(get_base_file_abspath(&stored_base_abspath, + shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_writable(&stored_base_stream, + stored_base_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_copy3(wc_base_stream, stored_base_stream, + NULL, NULL, scratch_pool)); + } + + /* Copy the working file, if present */ + SVN_ERR(svn_io_check_path(from_wc_abspath, &work_kind, scratch_pool)); + if (work_kind == svn_node_file) + { + SVN_ERR(svn_io_copy_file(from_wc_abspath, stored_abspath, + TRUE /*copy_perms*/, scratch_pool)); + } + } + return SVN_NO_ERROR; +} + +/* An implementation of svn_wc_status_func4_t. */ +static svn_error_t * +write_changes_visitor(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + write_changes_baton_t *wb = baton; + const char *wc_relpath = svn_dirent_skip_ancestor(wb->wc_root_abspath, + local_abspath); + + /* Catch any conflict, even a tree conflict on a path that has + node-status 'unversioned'. */ + if (status->conflicted) + { + SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath, + status, scratch_pool)); + } + else switch (status->node_status) + { + case svn_wc_status_deleted: + case svn_wc_status_added: + case svn_wc_status_replaced: + if (status->kind != svn_node_file + || status->copied) + { + SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath, + status, scratch_pool)); + break; + } + /* fall through */ + case svn_wc_status_modified: + { + /* Store metadata, and base and working versions if it's a file */ + SVN_ERR(store_file(local_abspath, wc_relpath, wb->shelf_version, + status, wb->ctx, scratch_pool)); + SVN_ERR(notify_shelved(wb, wc_relpath, local_abspath, + status, scratch_pool)); + break; + } + + case svn_wc_status_incomplete: + if ((status->text_status != svn_wc_status_normal + && status->text_status != svn_wc_status_none) + || (status->prop_status != svn_wc_status_normal + && status->prop_status != svn_wc_status_none)) + { + /* Incomplete, but local modifications */ + SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath, + status, scratch_pool)); + } + break; + + case svn_wc_status_conflicted: + case svn_wc_status_missing: + case svn_wc_status_obstructed: + SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath, + status, scratch_pool)); + break; + + case svn_wc_status_normal: + case svn_wc_status_ignored: + case svn_wc_status_none: + case svn_wc_status_external: + case svn_wc_status_unversioned: + default: + break; + } + return SVN_NO_ERROR; } -/* */ +/* A baton for use with changelist_filter_func(). */ +struct changelist_filter_baton_t { + apr_hash_t *changelist_hash; + svn_wc_status_func4_t status_func; + void *status_baton; +}; + +/* Filter out paths that are not in the requested changelist(s). + * Implements svn_wc_status_func4_t. */ static svn_error_t * -shelf_read_current(svn_client_shelf_t *shelf, - apr_pool_t *scratch_pool) +changelist_filter_func(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) { - char *current_abspath; - FILE *fp; + struct changelist_filter_baton_t *b = baton; - SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool)); - fp = fopen(current_abspath, "r"); - if (! fp) + if (b->changelist_hash + && (! status->changelist + || ! svn_hash_gets(b->changelist_hash, status->changelist))) { - shelf->max_version = 0; return SVN_NO_ERROR; } - fscanf(fp, "%d", &shelf->max_version); - fclose(fp); - return SVN_NO_ERROR; -} - -/* */ -static svn_error_t * -shelf_write_current(svn_client_shelf_t *shelf, - apr_pool_t *scratch_pool) -{ - char *current_abspath; - FILE *fp; - SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool)); - fp = fopen(current_abspath, "w"); - fprintf(fp, "%d", shelf->max_version); - fclose(fp); + SVN_ERR(b->status_func(b->status_baton, local_abspath, status, + scratch_pool)); return SVN_NO_ERROR; } -/** Write local changes to a patch file. - * - * @a paths, @a depth, @a changelists: The selection of local paths to diff. +/* + * Walk the WC tree(s) rooted at PATHS, to depth DEPTH, omitting paths that + * are not in one of the CHANGELISTS (if not null). * - * @a paths are relative to CWD (or absolute). Paths in patch are relative - * to WC root (@a wc_root_abspath). + * Call STATUS_FUNC(STATUS_BATON, ...) for each visited path. * - * ### TODO: Ignore any external diff cmd as configured in config file. - * This might also solve the buffering problem. + * PATHS are absolute, or relative to CWD. */ static svn_error_t * -write_patch(const char *patch_abspath, - const apr_array_header_t *paths, - svn_depth_t depth, - const apr_array_header_t *changelists, - const char *wc_root_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) +wc_walk_status_multi(const apr_array_header_t *paths, + svn_depth_t depth, + const apr_array_header_t *changelists, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) { - apr_int32_t flag; - apr_file_t *outfile; - svn_stream_t *outstream; - svn_stream_t *errstream; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); + struct changelist_filter_baton_t cb = {0}; int i; - svn_opt_revision_t peg_revision = {svn_opt_revision_unspecified, {0}}; - svn_opt_revision_t start_revision = {svn_opt_revision_base, {0}}; - svn_opt_revision_t end_revision = {svn_opt_revision_working, {0}}; - - /* Get streams for the output and any error output of the diff. */ - /* ### svn_stream_open_writable() doesn't work here: the buffering - goes wrong so that diff headers appear after their hunks. - For now, fix by opening the file without APR_BUFFERED. */ - flag = APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE; - SVN_ERR(svn_io_file_open(&outfile, patch_abspath, - flag, APR_FPROT_OS_DEFAULT, scratch_pool)); - outstream = svn_stream_from_aprfile2(outfile, FALSE /*disown*/, scratch_pool); - errstream = svn_stream_empty(scratch_pool); + + if (changelists && changelists->nelts) + SVN_ERR(svn_hash_from_cstring_keys(&cb.changelist_hash, + changelists, scratch_pool)); + cb.status_func = status_func; + cb.status_baton = status_baton; for (i = 0; i < paths->nelts; i++) { @@ -365,46 +1105,71 @@ write_patch(const char *patch_abspath, _("'%s' is not a local path"), path); SVN_ERR(svn_dirent_get_absolute(&path, path, scratch_pool)); - SVN_ERR(svn_client_diff_peg7( - NULL /*options*/, - path, - &peg_revision, - &start_revision, - &end_revision, - wc_root_abspath, - depth, - TRUE /*notice_ancestry*/, - FALSE /*no_diff_added*/, - FALSE /*no_diff_deleted*/, - TRUE /*show_copies_as_adds*/, - FALSE /*ignore_content_type: FALSE -> omit binary files*/, - FALSE /*ignore_properties*/, - FALSE /*properties_only*/, - FALSE /*use_git_diff_format*/, - FALSE /*pretty_print_mergeinfo*/, - SVN_APR_LOCALE_CHARSET, - outstream, - errstream, - changelists, - ctx, iterpool)); + SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, path, depth, + FALSE /*get_all*/, FALSE /*no_ignore*/, + FALSE /*ignore_text_mods*/, + NULL /*ignore_patterns*/, + changelist_filter_func, &cb, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool)); } - SVN_ERR(svn_stream_close(outstream)); - SVN_ERR(svn_stream_close(errstream)); return SVN_NO_ERROR; } +/** Write local changes to the shelf storage. + * + * @a paths, @a depth, @a changelists: The selection of local paths to diff. + * + * @a paths are relative to CWD (or absolute). + */ +static svn_error_t * +shelf_write_changes(svn_boolean_t *any_shelved, + svn_client__shelf_version_t *shelf_version, + const apr_array_header_t *paths, + svn_depth_t depth, + const apr_array_header_t *changelists, + svn_client_status_func_t shelved_func, + void *shelved_baton, + svn_client_status_func_t not_shelved_func, + void *not_shelved_baton, + const char *wc_root_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + write_changes_baton_t wb = { 0 }; + + wb.wc_root_abspath = wc_root_abspath; + wb.shelf_version = shelf_version; + wb.ctx = ctx; + wb.any_shelved = FALSE; + wb.was_shelved_func = shelved_func; + wb.was_shelved_baton = shelved_baton; + wb.was_not_shelved_func = not_shelved_func; + wb.was_not_shelved_baton = not_shelved_baton; + wb.pool = result_pool; + + /* Walk the WC */ + SVN_ERR(wc_walk_status_multi(paths, depth, changelists, + write_changes_visitor, &wb, + ctx, scratch_pool)); + + *any_shelved = wb.any_shelved; + return SVN_NO_ERROR; +} + /* Construct a shelf object representing an empty shelf: no versions, * no revprops, no looking to see if such a shelf exists on disk. */ static svn_error_t * -shelf_construct(svn_client_shelf_t **shelf_p, +shelf_construct(svn_client__shelf_t **shelf_p, const char *name, const char *local_abspath, svn_client_ctx_t *ctx, apr_pool_t *result_pool) { - svn_client_shelf_t *shelf = apr_palloc(result_pool, sizeof(*shelf)); + svn_client__shelf_t *shelf = apr_palloc(result_pool, sizeof(*shelf)); char *shelves_dir; SVN_ERR(svn_client_get_wc_root(&shelf->wc_root_abspath, @@ -426,7 +1191,7 @@ shelf_construct(svn_client_shelf_t **she } svn_error_t * -svn_client_shelf_open_existing(svn_client_shelf_t **shelf_p, +svn_client__shelf_open_existing(svn_client__shelf_t **shelf_p, const char *name, const char *local_abspath, svn_client_ctx_t *ctx, @@ -436,7 +1201,7 @@ svn_client_shelf_open_existing(svn_clien local_abspath, ctx, result_pool)); SVN_ERR(shelf_read_revprops(*shelf_p, result_pool)); SVN_ERR(shelf_read_current(*shelf_p, result_pool)); - if ((*shelf_p)->max_version <= 0) + if ((*shelf_p)->max_version < 0) { return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("Shelf '%s' not found"), @@ -446,44 +1211,52 @@ svn_client_shelf_open_existing(svn_clien } svn_error_t * -svn_client_shelf_open(svn_client_shelf_t **shelf_p, - const char *name, - const char *local_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *result_pool) +svn_client__shelf_open_or_create(svn_client__shelf_t **shelf_p, + const char *name, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool) { - SVN_ERR(shelf_construct(shelf_p, name, + svn_client__shelf_t *shelf; + + SVN_ERR(shelf_construct(&shelf, name, local_abspath, ctx, result_pool)); - SVN_ERR(shelf_read_revprops(*shelf_p, result_pool)); - SVN_ERR(shelf_read_current(*shelf_p, result_pool)); + SVN_ERR(shelf_read_revprops(shelf, result_pool)); + SVN_ERR(shelf_read_current(shelf, result_pool)); + if (shelf->max_version < 0) + { + shelf->max_version = 0; + SVN_ERR(shelf_write_current(shelf, result_pool)); + } + *shelf_p = shelf; return SVN_NO_ERROR; } svn_error_t * -svn_client_shelf_close(svn_client_shelf_t *shelf, +svn_client__shelf_close(svn_client__shelf_t *shelf, apr_pool_t *scratch_pool) { return SVN_NO_ERROR; } svn_error_t * -svn_client_shelf_delete(const char *name, +svn_client__shelf_delete(const char *name, const char *local_abspath, svn_boolean_t dry_run, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { - svn_client_shelf_t *shelf; + svn_client__shelf_t *shelf; int i; char *abspath; - SVN_ERR(svn_client_shelf_open_existing(&shelf, name, + SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath, ctx, scratch_pool)); - /* Remove the patches. */ + /* Remove the versions. */ for (i = shelf->max_version; i > 0; i--) { - SVN_ERR(shelf_delete_patch_file(shelf, i, scratch_pool)); + SVN_ERR(shelf_version_delete(shelf, i, scratch_pool)); } /* Remove the other files */ @@ -492,99 +1265,597 @@ svn_client_shelf_delete(const char *name SVN_ERR(get_current_abspath(&abspath, shelf, scratch_pool)); SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool)); - SVN_ERR(svn_client_shelf_close(shelf, scratch_pool)); + SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Baton for paths_changed_visitor(). */ +struct paths_changed_walk_baton_t +{ + apr_hash_t *paths_hash; + svn_boolean_t as_abspath; + const char *wc_root_abspath; + apr_pool_t *pool; +}; + +/* Add to the list(s) in BATON, the RELPATH of a shelved 'binary' file. + * Implements shelved_files_walk_func_t. */ +static svn_error_t * +paths_changed_visitor(void *baton, + const char *relpath, + svn_wc_status3_t *s, + apr_pool_t *scratch_pool) +{ + struct paths_changed_walk_baton_t *b = baton; + + relpath = (b->as_abspath + ? svn_dirent_join(b->wc_root_abspath, relpath, b->pool) + : apr_pstrdup(b->pool, relpath)); + svn_hash_sets(b->paths_hash, relpath, relpath); return SVN_NO_ERROR; } -/* Get the paths changed, relative to WC root, as a hash and/or an array. +/* Get the paths changed, relative to WC root or as abspaths, as a hash + * and/or an array (in no particular order). */ static svn_error_t * shelf_paths_changed(apr_hash_t **paths_hash_p, apr_array_header_t **paths_array_p, - svn_client_shelf_version_t *shelf_version, + svn_client__shelf_version_t *shelf_version, svn_boolean_t as_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_client_shelf_t *shelf = shelf_version->shelf; - svn_patch_file_t *patch_file; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); + svn_client__shelf_t *shelf = shelf_version->shelf; + apr_hash_t *paths_hash = apr_hash_make(result_pool); + struct paths_changed_walk_baton_t baton; + + baton.paths_hash = paths_hash; + baton.as_abspath = as_abspath; + baton.wc_root_abspath = shelf->wc_root_abspath; + baton.pool = result_pool; + SVN_ERR(shelf_status_walk(shelf_version, "", + paths_changed_visitor, &baton, + scratch_pool)); if (paths_hash_p) - *paths_hash_p = apr_hash_make(result_pool); + *paths_hash_p = paths_hash; if (paths_array_p) - *paths_array_p = apr_array_make(result_pool, 0, sizeof(void *)); - SVN_ERR(svn_diff_open_patch_file(&patch_file, shelf_version->patch_abspath, - result_pool)); - - while (1) - { - svn_patch_t *patch; - char *old_path, *new_path; - - svn_pool_clear(iterpool); - SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, - FALSE /*reverse*/, - FALSE /*ignore_whitespace*/, - iterpool, iterpool)); - if (! patch) - break; + SVN_ERR(svn_hash_keys(paths_array_p, paths_hash, result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_paths_changed(apr_hash_t **affected_paths, + svn_client__shelf_version_t *shelf_version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR(shelf_paths_changed(affected_paths, NULL, shelf_version, + FALSE /*as_abspath*/, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Send a notification */ +static svn_error_t * +send_notification(const char *local_abspath, + svn_wc_notify_action_t action, + svn_node_kind_t kind, + svn_wc_notify_state_t content_state, + svn_wc_notify_state_t prop_state, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + if (notify_func) + { + svn_wc_notify_t *notify + = svn_wc_create_notify(local_abspath, action, scratch_pool); + + notify->kind = kind; + notify->content_state = content_state; + notify->prop_state = prop_state; + notify_func(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + +/* Merge a shelved change into WC_ABSPATH. + */ +static svn_error_t * +wc_file_merge(const char *wc_abspath, + const char *left_file, + const char *right_file, + /*const*/ apr_hash_t *left_props, + /*const*/ apr_hash_t *right_props, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_wc_notify_state_t property_state; + svn_boolean_t has_local_mods; + enum svn_wc_merge_outcome_t content_outcome; + const char *target_label, *left_label, *right_label; + apr_array_header_t *prop_changes; + + /* xgettext: the '.working', '.merge-left' and '.merge-right' strings + are used to tag onto a file name in case of a merge conflict */ + target_label = apr_psprintf(scratch_pool, _(".working")); + left_label = apr_psprintf(scratch_pool, _(".merge-left")); + right_label = apr_psprintf(scratch_pool, _(".merge-right")); + + SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool)); + SVN_ERR(svn_wc_text_modified_p2(&has_local_mods, ctx->wc_ctx, + wc_abspath, FALSE, scratch_pool)); + + /* Do property merge and text merge in one step so that keyword expansion + takes into account the new property values. */ + SVN_WC__CALL_WITH_WRITE_LOCK( + svn_wc_merge5(&content_outcome, &property_state, ctx->wc_ctx, + left_file, right_file, wc_abspath, + left_label, right_label, target_label, + NULL, NULL, /*left, right conflict-versions*/ + FALSE /*dry_run*/, NULL /*diff3_cmd*/, + NULL /*merge_options*/, + left_props, prop_changes, + NULL, NULL, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool), + ctx->wc_ctx, wc_abspath, + FALSE /*lock_anchor*/, scratch_pool); + + return SVN_NO_ERROR; +} + +/* Merge a shelved change (of properties) into the dir at WC_ABSPATH. + */ +static svn_error_t * +wc_dir_props_merge(const char *wc_abspath, + /*const*/ apr_hash_t *left_props, + /*const*/ apr_hash_t *right_props, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *prop_changes; + svn_wc_notify_state_t property_state; + + SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool)); + SVN_WC__CALL_WITH_WRITE_LOCK( + svn_wc_merge_props3(&property_state, ctx->wc_ctx, + wc_abspath, + NULL, NULL, /*left, right conflict-versions*/ + left_props, prop_changes, + FALSE /*dry_run*/, + NULL, NULL, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool), + ctx->wc_ctx, wc_abspath, + FALSE /*lock_anchor*/, scratch_pool); + + return SVN_NO_ERROR; +} + +/* Apply a shelved "delete" to TO_WC_ABSPATH. + */ +static svn_error_t * +wc_node_delete(const char *to_wc_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + SVN_WC__CALL_WITH_WRITE_LOCK( + svn_wc_delete4(ctx->wc_ctx, + to_wc_abspath, + FALSE /*keep_local*/, + TRUE /*delete_unversioned_target*/, + NULL, NULL, NULL, NULL, /*cancel, notify*/ + scratch_pool), + ctx->wc_ctx, to_wc_abspath, + TRUE /*lock_anchor*/, scratch_pool); + return SVN_NO_ERROR; +} + +/* Apply a shelved "add" to TO_WC_ABSPATH. + * The node must already exist on disk, in a versioned parent dir. + */ +static svn_error_t * +wc_node_add(const char *to_wc_abspath, + apr_hash_t *work_props, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + /* If it was not already versioned, schedule the node for addition. + (Do not apply autoprops, because this isn't a user-facing "add" but + restoring a previously saved state.) */ + SVN_WC__CALL_WITH_WRITE_LOCK( + svn_wc_add_from_disk3(ctx->wc_ctx, + to_wc_abspath, work_props, + FALSE /* skip checks */, + NULL, NULL, scratch_pool), + ctx->wc_ctx, to_wc_abspath, + TRUE /*lock_anchor*/, scratch_pool); + return SVN_NO_ERROR; +} + +/* Baton for apply_file_visitor(). */ +struct apply_files_baton_t +{ + svn_client__shelf_version_t *shelf_version; + svn_boolean_t test_only; /* only check whether it would conflict */ + svn_boolean_t conflict; /* would it conflict? */ + svn_client_ctx_t *ctx; +}; + +/* Copy the file RELPATH from shelf binary file storage to the WC. + * + * If it is not already versioned, schedule the file for addition. + * + * Make any missing parent directories. + * + * In test mode (BATON->test_only): set BATON->conflict if we can't apply + * the change to WC at RELPATH without conflict. But in fact, just check + * if WC at RELPATH is locally modified. + * + * Implements shelved_files_walk_func_t. */ +static svn_error_t * +apply_file_visitor(void *baton, + const char *relpath, + svn_wc_status3_t *s, + apr_pool_t *scratch_pool) +{ + struct apply_files_baton_t *b = baton; + const char *wc_root_abspath = b->shelf_version->shelf->wc_root_abspath; + char *stored_base_abspath, *stored_work_abspath; + apr_hash_t *base_props, *work_props; + const char *to_wc_abspath = svn_dirent_join(wc_root_abspath, relpath, + scratch_pool); + const char *to_dir_abspath = svn_dirent_dirname(to_wc_abspath, scratch_pool); + + SVN_ERR(get_base_file_abspath(&stored_base_abspath, + b->shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(get_working_file_abspath(&stored_work_abspath, + b->shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(read_props_from_shelf(&base_props, &work_props, + s->node_status, + b->shelf_version, relpath, + scratch_pool, scratch_pool)); + + if (b->test_only) + { + svn_wc_status3_t *status; + + SVN_ERR(svn_wc_status3(&status, b->ctx->wc_ctx, to_wc_abspath, + scratch_pool, scratch_pool)); + switch (status->node_status) + { + case svn_wc_status_normal: + case svn_wc_status_none: + break; + default: + b->conflict = TRUE; + } + + return SVN_NO_ERROR; + } + + /* Handle 'delete' and the delete half of 'replace' */ + if (s->node_status == svn_wc_status_deleted + || s->node_status == svn_wc_status_replaced) + { + SVN_ERR(wc_node_delete(to_wc_abspath, b->ctx, scratch_pool)); + if (s->node_status != svn_wc_status_replaced) + { + SVN_ERR(send_notification(to_wc_abspath, svn_wc_notify_update_delete, + s->kind, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable, + b->ctx->notify_func2, b->ctx->notify_baton2, + scratch_pool)); + } + } + + /* If we can merge a file, do so. */ + if (s->node_status == svn_wc_status_modified) + { + if (s->kind == svn_node_dir) + { + SVN_ERR(wc_dir_props_merge(to_wc_abspath, + base_props, work_props, + b->ctx, scratch_pool, scratch_pool)); + } + else if (s->kind == svn_node_file) + { + SVN_ERR(wc_file_merge(to_wc_abspath, + stored_base_abspath, stored_work_abspath, + base_props, work_props, + b->ctx, scratch_pool)); + } + SVN_ERR(send_notification(to_wc_abspath, svn_wc_notify_update_update, + s->kind, + (s->kind == svn_node_dir) + ? svn_wc_notify_state_inapplicable + : svn_wc_notify_state_merged, + (s->kind == svn_node_dir) + ? svn_wc_notify_state_merged + : svn_wc_notify_state_unknown, + b->ctx->notify_func2, b->ctx->notify_baton2, + scratch_pool)); + } - old_path = (as_abspath - ? svn_dirent_join(shelf->wc_root_abspath, - patch->old_filename, result_pool) - : apr_pstrdup(result_pool, patch->old_filename)); - new_path = (as_abspath - ? svn_dirent_join(shelf->wc_root_abspath, - patch->new_filename, result_pool) - : apr_pstrdup(result_pool, patch->new_filename)); - if (paths_hash_p) + /* For an added file, copy it into the WC and ensure it's versioned. */ + if (s->node_status == svn_wc_status_added + || s->node_status == svn_wc_status_replaced) + { + if (s->kind == svn_node_dir) { - svn_hash_sets(*paths_hash_p, old_path, new_path); + SVN_ERR(svn_io_make_dir_recursively(to_wc_abspath, scratch_pool)); } - if (paths_array_p) + else if (s->kind == svn_node_file) { - APR_ARRAY_PUSH(*paths_array_p, void *) = old_path; - if (strcmp(old_path, new_path) != 0) - APR_ARRAY_PUSH(*paths_array_p, void *) = new_path; + SVN_ERR(svn_io_make_dir_recursively(to_dir_abspath, scratch_pool)); + SVN_ERR(svn_io_copy_file(stored_work_abspath, to_wc_abspath, + TRUE /*copy_perms*/, scratch_pool)); } + SVN_ERR(wc_node_add(to_wc_abspath, work_props, b->ctx, scratch_pool)); + SVN_ERR(send_notification(to_wc_abspath, + (s->node_status == svn_wc_status_replaced) + ? svn_wc_notify_update_replace + : svn_wc_notify_update_add, + s->kind, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable, + b->ctx->notify_func2, b->ctx->notify_baton2, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/*-------------------------------------------------------------------------*/ +/* Diff */ + +/* */ +static svn_error_t * +file_changed(svn_client__shelf_version_t *shelf_version, + const char *relpath, + svn_wc_status3_t *s, + const svn_diff_tree_processor_t *diff_processor, + svn_diff_source_t *left_source, + svn_diff_source_t *right_source, + const char *left_stored_abspath, + const char *right_stored_abspath, + void *dir_baton, + apr_pool_t *scratch_pool) +{ + void *fb; + svn_boolean_t skip = FALSE; + + SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath, + left_source, right_source, + NULL /*copyfrom*/, + dir_baton, diff_processor, + scratch_pool, scratch_pool)); + if (!skip) + { + apr_hash_t *left_props, *right_props; + apr_array_header_t *prop_changes; + + SVN_ERR(read_props_from_shelf(&left_props, &right_props, + s->node_status, shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, + scratch_pool)); + SVN_ERR(diff_processor->file_changed( + relpath, + left_source, right_source, + left_stored_abspath, right_stored_abspath, + left_props, right_props, + TRUE /*file_modified*/, prop_changes, + fb, diff_processor, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +file_deleted(svn_client__shelf_version_t *shelf_version, + const char *relpath, + svn_wc_status3_t *s, + const svn_diff_tree_processor_t *diff_processor, + svn_diff_source_t *left_source, + const char *left_stored_abspath, + void *dir_baton, + apr_pool_t *scratch_pool) +{ + void *fb; + svn_boolean_t skip = FALSE; + + SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath, + left_source, NULL, NULL /*copyfrom*/, + dir_baton, diff_processor, + scratch_pool, scratch_pool)); + if (!skip) + { + apr_hash_t *left_props, *right_props; + + SVN_ERR(read_props_from_shelf(&left_props, &right_props, + s->node_status, shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(diff_processor->file_deleted(relpath, + left_source, + left_stored_abspath, + left_props, + fb, diff_processor, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +file_added(svn_client__shelf_version_t *shelf_version, + const char *relpath, + svn_wc_status3_t *s, + const svn_diff_tree_processor_t *diff_processor, + svn_diff_source_t *right_source, + const char *right_stored_abspath, + void *dir_baton, + apr_pool_t *scratch_pool) +{ + void *fb; + svn_boolean_t skip = FALSE; + + SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath, + NULL, right_source, NULL /*copyfrom*/, + dir_baton, diff_processor, + scratch_pool, scratch_pool)); + if (!skip) + { + apr_hash_t *left_props, *right_props; + + SVN_ERR(read_props_from_shelf(&left_props, &right_props, + s->node_status, shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(diff_processor->file_added( + relpath, + NULL /*copyfrom_source*/, right_source, + NULL /*copyfrom_abspath*/, right_stored_abspath, + NULL /*copyfrom_props*/, right_props, + fb, diff_processor, scratch_pool)); } - SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool)); - svn_pool_destroy(iterpool); return SVN_NO_ERROR; } +/* Baton for diff_visitor(). */ +struct diff_baton_t +{ + svn_client__shelf_version_t *shelf_version; + const char *top_relpath; /* top of diff, relative to shelf */ + const char *walk_root_abspath; + const svn_diff_tree_processor_t *diff_processor; +}; + +/* Drive BATON->diff_processor. + * Implements svn_io_walk_func_t. */ +static svn_error_t * +diff_visitor(void *baton, + const char *abspath, + const apr_finfo_t *finfo, + apr_pool_t *scratch_pool) +{ + struct diff_baton_t *b = baton; + const char *relpath; + + relpath = svn_dirent_skip_ancestor(b->walk_root_abspath, abspath); + if (finfo->filetype == APR_REG + && (strlen(relpath) >= 5 && strcmp(relpath+strlen(relpath)-5, ".meta") == 0)) + { + svn_wc_status3_t *s; + void *db = NULL; + svn_diff_source_t *left_source; + svn_diff_source_t *right_source; + char *left_stored_abspath, *right_stored_abspath; + + relpath = apr_pstrndup(scratch_pool, relpath, strlen(relpath) - 5); + if (!svn_relpath_skip_ancestor(b->top_relpath, relpath)) + return SVN_NO_ERROR; + + SVN_ERR(status_read(&s, b->shelf_version, relpath, + scratch_pool, scratch_pool)); + + left_source = svn_diff__source_create(s->revision, scratch_pool); + right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); + SVN_ERR(get_base_file_abspath(&left_stored_abspath, + b->shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(get_working_file_abspath(&right_stored_abspath, + b->shelf_version, relpath, + scratch_pool, scratch_pool)); + + switch (s->node_status) + { + case svn_wc_status_modified: + SVN_ERR(file_changed(b->shelf_version, relpath, s, + b->diff_processor, + left_source, right_source, + left_stored_abspath, right_stored_abspath, + db, scratch_pool)); + break; + case svn_wc_status_added: + SVN_ERR(file_added(b->shelf_version, relpath, s, + b->diff_processor, + right_source, right_stored_abspath, + db, scratch_pool)); + break; + case svn_wc_status_deleted: + SVN_ERR(file_deleted(b->shelf_version, relpath, s, + b->diff_processor, + left_source, left_stored_abspath, + db, scratch_pool)); + break; + case svn_wc_status_replaced: + SVN_ERR(file_deleted(b->shelf_version, relpath, s, + b->diff_processor, + left_source, left_stored_abspath, + db, scratch_pool)); + SVN_ERR(file_added(b->shelf_version, relpath, s, + b->diff_processor, + right_source, right_stored_abspath, + db, scratch_pool)); + default: + break; + } + } + return SVN_NO_ERROR; +} + svn_error_t * -svn_client_shelf_paths_changed(apr_hash_t **affected_paths, - svn_client_shelf_version_t *shelf_version, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +svn_client__shelf_test_apply_file(svn_boolean_t *conflict_p, + svn_client__shelf_version_t *shelf_version, + const char *file_relpath, + apr_pool_t *scratch_pool) { - SVN_ERR(shelf_paths_changed(affected_paths, NULL, shelf_version, - FALSE /*as_abspath*/, - result_pool, scratch_pool)); + struct apply_files_baton_t baton = {0}; + + baton.shelf_version = shelf_version; + baton.test_only = TRUE; + baton.conflict = FALSE; + baton.ctx = shelf_version->shelf->ctx; + SVN_ERR(shelf_status_visit_path(shelf_version, file_relpath, + apply_file_visitor, &baton, + scratch_pool)); + *conflict_p = baton.conflict; + return SVN_NO_ERROR; } svn_error_t * -svn_client_shelf_apply(svn_client_shelf_version_t *shelf_version, +svn_client__shelf_apply(svn_client__shelf_version_t *shelf_version, svn_boolean_t dry_run, apr_pool_t *scratch_pool) { - SVN_ERR(svn_client_patch(shelf_version->patch_abspath, - shelf_version->shelf->wc_root_abspath, - dry_run, 0 /*strip*/, - FALSE /*reverse*/, - FALSE /*ignore_whitespace*/, - TRUE /*remove_tempfiles*/, NULL, NULL, - shelf_version->shelf->ctx, scratch_pool)); + struct apply_files_baton_t baton = {0}; + baton.shelf_version = shelf_version; + baton.ctx = shelf_version->shelf->ctx; + SVN_ERR(shelf_status_walk(shelf_version, "", + apply_file_visitor, &baton, + scratch_pool)); + + svn_io_sleep_for_timestamps(shelf_version->shelf->wc_root_abspath, + scratch_pool); return SVN_NO_ERROR; } svn_error_t * -svn_client_shelf_unapply(svn_client_shelf_version_t *shelf_version, +svn_client__shelf_unapply(svn_client__shelf_version_t *shelf_version, svn_boolean_t dry_run, apr_pool_t *scratch_pool) { @@ -606,80 +1877,97 @@ svn_client_shelf_unapply(svn_client_shel } svn_error_t * -svn_client_shelf_set_current_version(svn_client_shelf_t *shelf, - int version, - apr_pool_t *scratch_pool) +svn_client__shelf_delete_newer_versions(svn_client__shelf_t *shelf, + svn_client__shelf_version_t *shelf_version, + apr_pool_t *scratch_pool) { + int previous_version = shelf_version ? shelf_version->version_number : 0; int i; /* Delete any newer checkpoints */ - for (i = shelf->max_version; i > version; i--) + for (i = shelf->max_version; i > previous_version; i--) { - SVN_ERR(shelf_delete_patch_file(shelf, i, scratch_pool)); + SVN_ERR(shelf_version_delete(shelf, i, scratch_pool)); } - shelf->max_version = version; + shelf->max_version = previous_version; SVN_ERR(shelf_write_current(shelf, scratch_pool)); return SVN_NO_ERROR; } svn_error_t * -svn_client_shelf_get_patch_abspath(const char **patch_abspath, - svn_client_shelf_version_t *shelf_version, - apr_pool_t *scratch_pool) +svn_client__shelf_diff(svn_client__shelf_version_t *shelf_version, + const char *shelf_relpath, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + const svn_diff_tree_processor_t *diff_processor, + apr_pool_t *scratch_pool) { - *patch_abspath = shelf_version->patch_abspath; - return SVN_NO_ERROR; -} + struct diff_baton_t baton; -svn_error_t * -svn_client_shelf_export_patch(svn_client_shelf_version_t *shelf_version, - svn_stream_t *outstream, - apr_pool_t *scratch_pool) -{ - svn_stream_t *instream; + if (shelf_version->version_number == 0) + return SVN_NO_ERROR; + + baton.shelf_version = shelf_version; + baton.top_relpath = shelf_relpath; + baton.walk_root_abspath = shelf_version->files_dir_abspath; + baton.diff_processor = diff_processor; + SVN_ERR(svn_io_dir_walk2(baton.walk_root_abspath, 0 /*wanted*/, + diff_visitor, &baton, + scratch_pool)); - SVN_ERR(svn_stream_open_readonly(&instream, shelf_version->patch_abspath, - scratch_pool, scratch_pool)); - SVN_ERR(svn_stream_copy3(instream, - svn_stream_disown(outstream, scratch_pool), - NULL, NULL, scratch_pool)); return SVN_NO_ERROR; } svn_error_t * -svn_client_shelf_save_new_version(svn_client_shelf_t *shelf, - const apr_array_header_t *paths, - svn_depth_t depth, - const apr_array_header_t *changelists, - apr_pool_t *scratch_pool) +svn_client__shelf_save_new_version3(svn_client__shelf_version_t **new_version_p, + svn_client__shelf_t *shelf, + const apr_array_header_t *paths, + svn_depth_t depth, + const apr_array_header_t *changelists, + svn_client_status_func_t shelved_func, + void *shelved_baton, + svn_client_status_func_t not_shelved_func, + void *not_shelved_baton, + apr_pool_t *scratch_pool) { int next_version = shelf->max_version + 1; - const char *patch_abspath; - apr_finfo_t file_info; + svn_client__shelf_version_t *new_shelf_version; + svn_boolean_t any_shelved; + + SVN_ERR(shelf_version_create(&new_shelf_version, + shelf, next_version, scratch_pool)); + SVN_ERR(shelf_write_changes(&any_shelved, + new_shelf_version, + paths, depth, changelists, + shelved_func, shelved_baton, + not_shelved_func, not_shelved_baton, + shelf->wc_root_abspath, + shelf->ctx, scratch_pool, scratch_pool)); - SVN_ERR(get_patch_abspath(&patch_abspath, shelf, next_version, - scratch_pool, scratch_pool)); - SVN_ERR(write_patch(patch_abspath, - paths, depth, changelists, - shelf->wc_root_abspath, - shelf->ctx, scratch_pool)); + if (any_shelved) + { + shelf->max_version = next_version; + SVN_ERR(shelf_write_current(shelf, scratch_pool)); - SVN_ERR(svn_io_stat(&file_info, patch_abspath, APR_FINFO_MTIME, scratch_pool)); - if (file_info.size > 0) + if (new_version_p) + SVN_ERR(svn_client__shelf_version_open(new_version_p, shelf, next_version, + scratch_pool, scratch_pool)); + } + else { - SVN_ERR(svn_client_shelf_set_current_version(shelf, next_version, - scratch_pool)); + if (new_version_p) + *new_version_p = NULL; } return SVN_NO_ERROR; } svn_error_t * -svn_client_shelf_get_log_message(char **log_message, - svn_client_shelf_t *shelf, +svn_client__shelf_get_log_message(char **log_message, + svn_client__shelf_t *shelf, apr_pool_t *result_pool) { - svn_string_t *propval = svn_hash_gets(shelf->revprops, "svn:log"); + svn_string_t *propval = svn_hash_gets(shelf->revprops, SVN_PROP_REVISION_LOG); if (propval) *log_message = apr_pstrdup(result_pool, propval->data); @@ -689,45 +1977,20 @@ svn_client_shelf_get_log_message(char ** } svn_error_t * -svn_client_shelf_set_log_message(svn_client_shelf_t *shelf, - apr_hash_t *revprop_table, - svn_boolean_t dry_run, +svn_client__shelf_set_log_message(svn_client__shelf_t *shelf, + const char *message, apr_pool_t *scratch_pool) { - svn_client_ctx_t *ctx = shelf->ctx; - const char *message = ""; - - /* Fetch the log message and any other revprops */ - if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) - { - const char *tmp_file; - apr_array_header_t *commit_items - = apr_array_make(scratch_pool, 1, sizeof(void *)); - - SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, - ctx, scratch_pool)); - if (! message) - return SVN_NO_ERROR; - } - - if (revprop_table) - shelf->revprops = svn_prop_hash_dup(revprop_table, shelf->pool); - else - shelf->revprops = apr_hash_make(shelf->pool); - - if (message && !dry_run) - { - svn_string_t *propval = svn_string_create(message, shelf->pool); - - SVN_ERR(svn_client__shelf_revprop_set(shelf, "svn:log", propval, - scratch_pool)); - } + svn_string_t *propval + = message ? svn_string_create(message, shelf->pool) : NULL; + SVN_ERR(svn_client__shelf_revprop_set(shelf, SVN_PROP_REVISION_LOG, propval, + scratch_pool)); return SVN_NO_ERROR; } svn_error_t * -svn_client_shelf_list(apr_hash_t **shelf_infos, +svn_client__shelf_list(apr_hash_t **shelf_infos, const char *local_abspath, svn_client_ctx_t *ctx, apr_pool_t *result_pool, @@ -757,7 +2020,7 @@ svn_client_shelf_list(apr_hash_t **shelf svn_error_clear(shelf_name_from_filename(&name, filename, result_pool)); if (name && dirent->kind == svn_node_file) { - svn_client_shelf_info_t *info + svn_client__shelf_info_t *info = apr_palloc(result_pool, sizeof(*info)); info->mtime = dirent->mtime; @@ -769,26 +2032,70 @@ svn_client_shelf_list(apr_hash_t **shelf } svn_error_t * -svn_client_shelf_version_open(svn_client_shelf_version_t **shelf_version_p, - svn_client_shelf_t *shelf, - int version, +svn_client__shelf_version_open(svn_client__shelf_version_t **shelf_version_p, + svn_client__shelf_t *shelf, + int version_number, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_client_shelf_version_t *shelf_version - = apr_palloc(result_pool, sizeof(*shelf_version)); + svn_client__shelf_version_t *shelf_version; const svn_io_dirent2_t *dirent; - shelf_version->shelf = shelf; - SVN_ERR(get_existing_patch_abspath(&shelf_version->patch_abspath, - shelf, version, - result_pool, scratch_pool)); + SVN_ERR(shelf_version_create(&shelf_version, + shelf, version_number, result_pool)); SVN_ERR(svn_io_stat_dirent2(&dirent, - shelf_version->patch_abspath, + shelf_version->files_dir_abspath, FALSE /*verify_truename*/, TRUE /*ignore_enoent*/, result_pool, scratch_pool)); + if (dirent->kind == svn_node_none) + { + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Shelf '%s' version %d not found"), + shelf->name, version_number); + } shelf_version->mtime = dirent->mtime; *shelf_version_p = shelf_version; return SVN_NO_ERROR; } + +svn_error_t * +svn_client__shelf_get_newest_version(svn_client__shelf_version_t **shelf_version_p, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (shelf->max_version == 0) + { + *shelf_version_p = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_client__shelf_version_open(shelf_version_p, + shelf, shelf->max_version, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_get_all_versions(apr_array_header_t **versions_p, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i; + + *versions_p = apr_array_make(result_pool, shelf->max_version - 1, + sizeof(svn_client__shelf_version_t *)); + + for (i = 1; i <= shelf->max_version; i++) + { + svn_client__shelf_version_t *shelf_version; + + SVN_ERR(svn_client__shelf_version_open(&shelf_version, + shelf, i, + result_pool, scratch_pool)); + APR_ARRAY_PUSH(*versions_p, svn_client__shelf_version_t *) = shelf_version; + } + return SVN_NO_ERROR; +}
Modified: subversion/branches/swig-py3/subversion/libsvn_client/status.c URL: http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/libsvn_client/status.c?rev=1847678&r1=1847677&r2=1847678&view=diff ============================================================================== --- subversion/branches/swig-py3/subversion/libsvn_client/status.c (original) +++ subversion/branches/swig-py3/subversion/libsvn_client/status.c Wed Nov 28 21:25:32 2018 @@ -23,6 +23,9 @@ /* ==================================================================== */ +/* We define this here to remove any further warnings about the usage of + experimental functions in this file. */ +#define SVN_EXPERIMENTAL /*** Includes. ***/ @@ -329,6 +332,79 @@ do_external_status(svn_client_ctx_t *ctx return SVN_NO_ERROR; } + +/* Run status on shelf SHELF_NAME, if it exists. + */ +static svn_error_t * +shelf_status(const char *shelf_name, + const char *target_abspath, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_client__shelf_t *shelf; + svn_client__shelf_version_t *shelf_version; + const char *wc_relpath; + + err = svn_client__shelf_open_existing(&shelf, + shelf_name, target_abspath, + ctx, scratch_pool); + if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + SVN_ERR(svn_client__shelf_version_open(&shelf_version, + shelf, shelf->max_version, + scratch_pool, scratch_pool)); + wc_relpath = svn_dirent_skip_ancestor(shelf->wc_root_abspath, target_abspath); + SVN_ERR(svn_client__shelf_version_status_walk(shelf_version, wc_relpath, + status_func, status_baton, + scratch_pool)); + SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Run status on all shelves named in CHANGELISTS by a changelist name + * of the form "svn:shelf:SHELF_NAME", if they exist. + */ +static svn_error_t * +shelves_status(const apr_array_header_t *changelists, + const char *target_abspath, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + static const char PREFIX[] = "svn:shelf:"; + static const int PREFIX_LEN = 10; + int i; + + if (! changelists) + return SVN_NO_ERROR; + for (i = 0; i < changelists->nelts; i++) + { + const char *cl = APR_ARRAY_IDX(changelists, i, const char *); + + if (strncmp(cl, PREFIX, PREFIX_LEN) == 0) + { + const char *shelf_name = cl + PREFIX_LEN; + + SVN_ERR(shelf_status(shelf_name, target_abspath, + status_func, status_baton, + ctx, scratch_pool)); + } + } + + return SVN_NO_ERROR; +} + /*** Public Interface. ***/ @@ -586,6 +662,9 @@ svn_client_status6(svn_revnum_t *result_ } else { + SVN_ERR(shelves_status(changelists, target_abspath, + tweak_status, &sb, + ctx, pool)); err = svn_wc_walk_status(ctx->wc_ctx, target_abspath, depth, get_all, no_ignore, FALSE, ignores, tweak_status, &sb,
