summaryrefslogtreecommitdiff
path: root/subversion/libsvn_client/shelve.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/shelve.c')
-rw-r--r--subversion/libsvn_client/shelve.c552
1 files changed, 552 insertions, 0 deletions
diff --git a/subversion/libsvn_client/shelve.c b/subversion/libsvn_client/shelve.c
new file mode 100644
index 0000000000000..af8dd67bc6053
--- /dev/null
+++ b/subversion/libsvn_client/shelve.c
@@ -0,0 +1,552 @@
+/*
+ * shelve.c: implementation of the 'shelve' commands
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+/* We define this here to remove any further warnings about the usage of
+ experimental functions in this file. */
+#define SVN_EXPERIMENTAL
+
+#include "svn_client.h"
+#include "svn_wc.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_utf.h"
+#include "svn_ctype.h"
+
+#include "client.h"
+#include "private/svn_client_private.h"
+#include "private/svn_wc_private.h"
+#include "svn_private_config.h"
+
+
+static svn_error_t *
+shelf_name_encode(char **encoded_name_p,
+ const char *name,
+ apr_pool_t *result_pool)
+{
+ char *encoded_name
+ = apr_palloc(result_pool, strlen(name) * 2 + 1);
+ char *out_pos = encoded_name;
+
+ if (name[0] == '\0')
+ return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
+ _("Shelf name cannot be the empty string"));
+
+ while (*name)
+ {
+ apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++));
+ out_pos += 2;
+ }
+ *encoded_name_p = encoded_name;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+shelf_name_decode(char **decoded_name_p,
+ const char *codename,
+ apr_pool_t *result_pool)
+{
+ svn_stringbuf_t *sb
+ = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool);
+ const char *input = codename;
+
+ while (*input)
+ {
+ int c;
+ int nchars;
+ int nitems = sscanf(input, "%02x%n", &c, &nchars);
+
+ if (nitems != 1 || nchars != 2)
+ return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
+ _("Shelve: Bad encoded name '%s'"), codename);
+ svn_stringbuf_appendbyte(sb, c);
+ input += 2;
+ }
+ *decoded_name_p = sb->data;
+ return SVN_NO_ERROR;
+}
+
+/* Set *NAME to the shelf name from FILENAME. */
+static svn_error_t *
+shelf_name_from_filename(char **name,
+ const char *filename,
+ apr_pool_t *result_pool)
+{
+ size_t len = strlen(filename);
+
+ if (len > 6 && strcmp(filename + len - 6, ".patch") == 0)
+ {
+ char *codename = apr_pstrndup(result_pool, filename, len - 6);
+ SVN_ERR(shelf_name_decode(name, codename, result_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Set *PATCH_ABSPATH to the abspath of the patch file for shelved change
+ * NAME, no matter whether it exists.
+ */
+static svn_error_t *
+get_patch_abspath(char **patch_abspath,
+ const char *name,
+ const char *wc_root_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *dir;
+ char *filename;
+
+ SVN_ERR(svn_wc__get_shelves_dir(&dir, ctx->wc_ctx, wc_root_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(shelf_name_encode(&filename, name, scratch_pool));
+ filename = apr_pstrcat(scratch_pool, filename, ".patch", SVN_VA_NULL);
+ *patch_abspath = svn_dirent_join(dir, filename, result_pool);
+ return SVN_NO_ERROR;
+}
+
+/** Write local changes to a patch file for shelved change @a name.
+ *
+ * @a message: An optional log message.
+ *
+ * @a wc_root_abspath: The WC root dir.
+ *
+ * @a overwrite_existing: If a file at @a patch_abspath exists, overwrite it.
+ *
+ * @a paths, @a depth, @a changelists: The selection of local paths to diff.
+ */
+static svn_error_t *
+shelf_write_patch(const char *name,
+ const char *message,
+ const char *wc_root_abspath,
+ svn_boolean_t overwrite_existing,
+ const apr_array_header_t *paths,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ char *patch_abspath;
+ 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);
+ 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}};
+
+ SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
+ ctx, scratch_pool, scratch_pool));
+
+ /* 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;
+ if (! overwrite_existing)
+ flag |= APR_FOPEN_EXCL;
+ 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);
+ SVN_ERR(svn_stream_for_stderr(&errstream, scratch_pool));
+
+ /* Write the patch file header (log message, etc.) */
+ if (message)
+ {
+ SVN_ERR(svn_stream_printf(outstream, scratch_pool, "%s\n",
+ message));
+ }
+ SVN_ERR(svn_stream_printf(outstream, scratch_pool,
+ "--This line, and those below, will be ignored--\n\n"));
+ SVN_ERR(svn_stream_printf(outstream, scratch_pool,
+ "--This patch was generated by 'svn shelve'--\n\n"));
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+ SVN_ERR(svn_dirent_get_absolute(&path, path, scratch_pool));
+
+ SVN_ERR(svn_client_diff_peg6(
+ 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*/,
+ SVN_APR_LOCALE_CHARSET,
+ outstream,
+ errstream,
+ changelists,
+ ctx, iterpool));
+ }
+ SVN_ERR(svn_stream_close(outstream));
+ SVN_ERR(svn_stream_close(errstream));
+
+ return SVN_NO_ERROR;
+}
+
+/** Apply the patch file for shelved change @a name to the WC.
+ *
+ * @a wc_root_abspath: The WC root dir.
+ *
+ * @a reverse: Apply the patch in reverse.
+ *
+ * @a dry_run: Don't really apply the changes, just notify what would be done.
+ */
+static svn_error_t *
+shelf_apply_patch(const char *name,
+ const char *wc_root_abspath,
+ svn_boolean_t reverse,
+ svn_boolean_t dry_run,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ char *patch_abspath;
+
+ SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
+ ctx, scratch_pool, scratch_pool));
+ SVN_ERR(svn_client_patch(patch_abspath, wc_root_abspath,
+ dry_run, 0 /*strip*/,
+ reverse,
+ FALSE /*ignore_whitespace*/,
+ TRUE /*remove_tempfiles*/, NULL, NULL,
+ ctx, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/** Delete the patch file for shelved change @a name.
+ *
+ * @a wc_root_abspath: The WC root dir.
+ */
+static svn_error_t *
+shelf_delete_patch(const char *name,
+ const char *wc_root_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ char *patch_abspath, *to_abspath;
+
+ SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
+ ctx, scratch_pool, scratch_pool));
+ to_abspath = apr_pstrcat(scratch_pool, patch_abspath, ".bak", SVN_VA_NULL);
+
+ /* remove any previous backup */
+ SVN_ERR(svn_io_remove_file2(to_abspath, TRUE /*ignore_enoent*/,
+ scratch_pool));
+
+ /* move the patch to a backup file */
+ SVN_ERR(svn_io_file_rename2(patch_abspath, to_abspath, FALSE /*flush_to_disk*/,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_shelve(const char *name,
+ const apr_array_header_t *paths,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_boolean_t keep_local,
+ svn_boolean_t dry_run,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ const char *wc_root_abspath;
+ const char *message = "";
+ svn_error_t *err;
+
+ /* ### TODO: check all paths are in same WC; for now use first path */
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath,
+ APR_ARRAY_IDX(paths, 0, char *), pool));
+ SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
+ local_abspath, ctx, pool, pool));
+
+ /* 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(pool, 1, sizeof(void *));
+
+ SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
+ ctx, pool));
+ if (! message)
+ return SVN_NO_ERROR;
+ }
+
+ err = shelf_write_patch(name, message, wc_root_abspath,
+ FALSE /*overwrite_existing*/,
+ paths, depth, changelists,
+ ctx, pool);
+ if (err && APR_STATUS_IS_EEXIST(err->apr_err))
+ {
+ return svn_error_quick_wrapf(err,
+ "Shelved change '%s' already exists",
+ name);
+ }
+ else
+ SVN_ERR(err);
+
+ if (!keep_local)
+ {
+ /* Reverse-apply the patch. This should be a safer way to remove those
+ changes from the WC than running a 'revert' operation. */
+ SVN_ERR(shelf_apply_patch(name, wc_root_abspath,
+ TRUE /*reverse*/, dry_run,
+ ctx, pool));
+ }
+
+ if (dry_run)
+ {
+ SVN_ERR(shelf_delete_patch(name, wc_root_abspath,
+ ctx, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_unshelve(const char *name,
+ const char *local_abspath,
+ svn_boolean_t keep,
+ svn_boolean_t dry_run,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *wc_root_abspath;
+ svn_error_t *err;
+
+ SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
+ local_abspath, ctx, pool, pool));
+
+ /* Apply the patch. */
+ err = shelf_apply_patch(name, wc_root_abspath,
+ FALSE /*reverse*/, dry_run,
+ ctx, pool);
+ if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
+ {
+ return svn_error_quick_wrapf(err,
+ "Shelved change '%s' not found",
+ name);
+ }
+ else
+ SVN_ERR(err);
+
+ /* Remove the patch. */
+ if (! keep && ! dry_run)
+ {
+ SVN_ERR(shelf_delete_patch(name, wc_root_abspath,
+ ctx, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_shelves_delete(const char *name,
+ const char *local_abspath,
+ svn_boolean_t dry_run,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *wc_root_abspath;
+
+ SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
+ local_abspath, ctx, pool, pool));
+
+ /* Remove the patch. */
+ if (! dry_run)
+ {
+ svn_error_t *err;
+
+ err = shelf_delete_patch(name, wc_root_abspath,
+ ctx, pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ return svn_error_quick_wrapf(err,
+ "Shelved change '%s' not found",
+ name);
+ }
+ else
+ SVN_ERR(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_shelf_get_paths(apr_hash_t **affected_paths,
+ const char *name,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *wc_root_abspath;
+ char *patch_abspath;
+ svn_patch_file_t *patch_file;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *paths = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
+ local_abspath, ctx, scratch_pool, scratch_pool));
+ SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
+ ctx, scratch_pool, scratch_pool));
+ SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, result_pool));
+
+ while (1)
+ {
+ svn_patch_t *patch;
+
+ 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_hash_sets(paths,
+ apr_pstrdup(result_pool, patch->old_filename),
+ apr_pstrdup(result_pool, patch->new_filename));
+ }
+ SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
+ svn_pool_destroy(iterpool);
+
+ *affected_paths = paths;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_shelf_has_changes(svn_boolean_t *has_changes,
+ const char *name,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *patch_paths;
+
+ SVN_ERR(svn_client_shelf_get_paths(&patch_paths, name, local_abspath,
+ ctx, scratch_pool, scratch_pool));
+ *has_changes = (apr_hash_count(patch_paths) != 0);
+ return SVN_NO_ERROR;
+}
+
+/* Set *LOGMSG to the log message stored in the file PATCH_ABSPATH.
+ *
+ * ### Currently just reads the first line.
+ */
+static svn_error_t *
+read_logmsg_from_patch(const char **logmsg,
+ const char *patch_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *file;
+ svn_stream_t *stream;
+ svn_boolean_t eof;
+ svn_stringbuf_t *line;
+
+ SVN_ERR(svn_io_file_open(&file, patch_abspath,
+ APR_FOPEN_READ, APR_FPROT_OS_DEFAULT, scratch_pool));
+ stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool);
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool));
+ SVN_ERR(svn_stream_close(stream));
+ *logmsg = line->data;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_shelves_list(apr_hash_t **shelved_patch_infos,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *shelves_dir;
+ apr_hash_t *dirents;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_wc__get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/,
+ result_pool, scratch_pool));
+
+ *shelved_patch_infos = apr_hash_make(result_pool);
+
+ /* Remove non-shelves */
+ for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ const char *filename = apr_hash_this_key(hi);
+ svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
+ char *name = NULL;
+
+ svn_error_clear(shelf_name_from_filename(&name, filename, result_pool));
+ if (name && dirent->kind == svn_node_file)
+ {
+ svn_client_shelved_patch_info_t *info
+ = apr_palloc(result_pool, sizeof(*info));
+
+ info->dirent = dirent;
+ info->mtime = info->dirent->mtime;
+ info->patch_path
+ = svn_dirent_join(shelves_dir, filename, result_pool);
+ SVN_ERR(read_logmsg_from_patch(&info->message, info->patch_path,
+ result_pool, scratch_pool));
+
+ svn_hash_sets(*shelved_patch_infos, name, info);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_shelves_any(svn_boolean_t *any_shelved,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *shelved_patch_infos;
+
+ SVN_ERR(svn_client_shelves_list(&shelved_patch_infos, local_abspath,
+ ctx, scratch_pool, scratch_pool));
+ *any_shelved = apr_hash_count(shelved_patch_infos) != 0;
+ return SVN_NO_ERROR;
+}