2 * shelve.c: implementation of the 'shelve' commands
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
26 /* We define this here to remove any further warnings about the usage of
27 experimental functions in this file. */
28 #define SVN_EXPERIMENTAL
30 #include "svn_client.h"
32 #include "svn_pools.h"
33 #include "svn_dirent_uri.h"
37 #include "svn_ctype.h"
40 #include "private/svn_client_private.h"
41 #include "private/svn_wc_private.h"
42 #include "svn_private_config.h"
46 shelf_name_encode(char **encoded_name_p,
48 apr_pool_t *result_pool)
51 = apr_palloc(result_pool, strlen(name) * 2 + 1);
52 char *out_pos = encoded_name;
55 return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
56 _("Shelf name cannot be the empty string"));
60 apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++));
63 *encoded_name_p = encoded_name;
68 shelf_name_decode(char **decoded_name_p,
70 apr_pool_t *result_pool)
73 = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool);
74 const char *input = codename;
80 int nitems = sscanf(input, "%02x%n", &c, &nchars);
82 if (nitems != 1 || nchars != 2)
83 return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
84 _("Shelve: Bad encoded name '%s'"), codename);
85 svn_stringbuf_appendbyte(sb, c);
88 *decoded_name_p = sb->data;
92 /* Set *NAME to the shelf name from FILENAME. */
94 shelf_name_from_filename(char **name,
96 apr_pool_t *result_pool)
98 size_t len = strlen(filename);
100 if (len > 6 && strcmp(filename + len - 6, ".patch") == 0)
102 char *codename = apr_pstrndup(result_pool, filename, len - 6);
103 SVN_ERR(shelf_name_decode(name, codename, result_pool));
108 /* Set *PATCH_ABSPATH to the abspath of the patch file for shelved change
109 * NAME, no matter whether it exists.
112 get_patch_abspath(char **patch_abspath,
114 const char *wc_root_abspath,
115 svn_client_ctx_t *ctx,
116 apr_pool_t *result_pool,
117 apr_pool_t *scratch_pool)
122 SVN_ERR(svn_wc__get_shelves_dir(&dir, ctx->wc_ctx, wc_root_abspath,
123 scratch_pool, scratch_pool));
124 SVN_ERR(shelf_name_encode(&filename, name, scratch_pool));
125 filename = apr_pstrcat(scratch_pool, filename, ".patch", SVN_VA_NULL);
126 *patch_abspath = svn_dirent_join(dir, filename, result_pool);
130 /** Write local changes to a patch file for shelved change @a name.
132 * @a message: An optional log message.
134 * @a wc_root_abspath: The WC root dir.
136 * @a overwrite_existing: If a file at @a patch_abspath exists, overwrite it.
138 * @a paths, @a depth, @a changelists: The selection of local paths to diff.
141 shelf_write_patch(const char *name,
143 const char *wc_root_abspath,
144 svn_boolean_t overwrite_existing,
145 const apr_array_header_t *paths,
147 const apr_array_header_t *changelists,
148 svn_client_ctx_t *ctx,
149 apr_pool_t *scratch_pool)
154 svn_stream_t *outstream;
155 svn_stream_t *errstream;
156 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
158 svn_opt_revision_t peg_revision = {svn_opt_revision_unspecified, {0}};
159 svn_opt_revision_t start_revision = {svn_opt_revision_base, {0}};
160 svn_opt_revision_t end_revision = {svn_opt_revision_working, {0}};
162 SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
163 ctx, scratch_pool, scratch_pool));
165 /* Get streams for the output and any error output of the diff. */
166 /* ### svn_stream_open_writable() doesn't work here: the buffering
167 goes wrong so that diff headers appear after their hunks.
168 For now, fix by opening the file without APR_BUFFERED. */
169 flag = APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE;
170 if (! overwrite_existing)
171 flag |= APR_FOPEN_EXCL;
172 SVN_ERR(svn_io_file_open(&outfile, patch_abspath,
173 flag, APR_FPROT_OS_DEFAULT, scratch_pool));
174 outstream = svn_stream_from_aprfile2(outfile, FALSE /*disown*/, scratch_pool);
175 SVN_ERR(svn_stream_for_stderr(&errstream, scratch_pool));
177 /* Write the patch file header (log message, etc.) */
180 SVN_ERR(svn_stream_printf(outstream, scratch_pool, "%s\n",
183 SVN_ERR(svn_stream_printf(outstream, scratch_pool,
184 "--This line, and those below, will be ignored--\n\n"));
185 SVN_ERR(svn_stream_printf(outstream, scratch_pool,
186 "--This patch was generated by 'svn shelve'--\n\n"));
188 for (i = 0; i < paths->nelts; i++)
190 const char *path = APR_ARRAY_IDX(paths, i, const char *);
192 if (svn_path_is_url(path))
193 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
194 _("'%s' is not a local path"), path);
195 SVN_ERR(svn_dirent_get_absolute(&path, path, scratch_pool));
197 SVN_ERR(svn_client_diff_peg6(
205 TRUE /*notice_ancestry*/,
206 FALSE /*no_diff_added*/,
207 FALSE /*no_diff_deleted*/,
208 TRUE /*show_copies_as_adds*/,
209 FALSE /*ignore_content_type: FALSE -> omit binary files*/,
210 FALSE /*ignore_properties*/,
211 FALSE /*properties_only*/,
212 FALSE /*use_git_diff_format*/,
213 SVN_APR_LOCALE_CHARSET,
219 SVN_ERR(svn_stream_close(outstream));
220 SVN_ERR(svn_stream_close(errstream));
225 /** Apply the patch file for shelved change @a name to the WC.
227 * @a wc_root_abspath: The WC root dir.
229 * @a reverse: Apply the patch in reverse.
231 * @a dry_run: Don't really apply the changes, just notify what would be done.
234 shelf_apply_patch(const char *name,
235 const char *wc_root_abspath,
236 svn_boolean_t reverse,
237 svn_boolean_t dry_run,
238 svn_client_ctx_t *ctx,
239 apr_pool_t *scratch_pool)
243 SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
244 ctx, scratch_pool, scratch_pool));
245 SVN_ERR(svn_client_patch(patch_abspath, wc_root_abspath,
246 dry_run, 0 /*strip*/,
248 FALSE /*ignore_whitespace*/,
249 TRUE /*remove_tempfiles*/, NULL, NULL,
255 /** Delete the patch file for shelved change @a name.
257 * @a wc_root_abspath: The WC root dir.
260 shelf_delete_patch(const char *name,
261 const char *wc_root_abspath,
262 svn_client_ctx_t *ctx,
263 apr_pool_t *scratch_pool)
265 char *patch_abspath, *to_abspath;
267 SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
268 ctx, scratch_pool, scratch_pool));
269 to_abspath = apr_pstrcat(scratch_pool, patch_abspath, ".bak", SVN_VA_NULL);
271 /* remove any previous backup */
272 SVN_ERR(svn_io_remove_file2(to_abspath, TRUE /*ignore_enoent*/,
275 /* move the patch to a backup file */
276 SVN_ERR(svn_io_file_rename2(patch_abspath, to_abspath, FALSE /*flush_to_disk*/,
282 svn_client_shelve(const char *name,
283 const apr_array_header_t *paths,
285 const apr_array_header_t *changelists,
286 svn_boolean_t keep_local,
287 svn_boolean_t dry_run,
288 svn_client_ctx_t *ctx,
291 const char *local_abspath;
292 const char *wc_root_abspath;
293 const char *message = "";
296 /* ### TODO: check all paths are in same WC; for now use first path */
297 SVN_ERR(svn_dirent_get_absolute(&local_abspath,
298 APR_ARRAY_IDX(paths, 0, char *), pool));
299 SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
300 local_abspath, ctx, pool, pool));
302 /* Fetch the log message and any other revprops */
303 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
305 const char *tmp_file;
306 apr_array_header_t *commit_items = apr_array_make(pool, 1, sizeof(void *));
308 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
314 err = shelf_write_patch(name, message, wc_root_abspath,
315 FALSE /*overwrite_existing*/,
316 paths, depth, changelists,
318 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
320 return svn_error_quick_wrapf(err,
321 "Shelved change '%s' already exists",
329 /* Reverse-apply the patch. This should be a safer way to remove those
330 changes from the WC than running a 'revert' operation. */
331 SVN_ERR(shelf_apply_patch(name, wc_root_abspath,
332 TRUE /*reverse*/, dry_run,
338 SVN_ERR(shelf_delete_patch(name, wc_root_abspath,
346 svn_client_unshelve(const char *name,
347 const char *local_abspath,
349 svn_boolean_t dry_run,
350 svn_client_ctx_t *ctx,
353 const char *wc_root_abspath;
356 SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
357 local_abspath, ctx, pool, pool));
359 /* Apply the patch. */
360 err = shelf_apply_patch(name, wc_root_abspath,
361 FALSE /*reverse*/, dry_run,
363 if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
365 return svn_error_quick_wrapf(err,
366 "Shelved change '%s' not found",
372 /* Remove the patch. */
373 if (! keep && ! dry_run)
375 SVN_ERR(shelf_delete_patch(name, wc_root_abspath,
383 svn_client_shelves_delete(const char *name,
384 const char *local_abspath,
385 svn_boolean_t dry_run,
386 svn_client_ctx_t *ctx,
389 const char *wc_root_abspath;
391 SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
392 local_abspath, ctx, pool, pool));
394 /* Remove the patch. */
399 err = shelf_delete_patch(name, wc_root_abspath,
401 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
403 return svn_error_quick_wrapf(err,
404 "Shelved change '%s' not found",
415 svn_client_shelf_get_paths(apr_hash_t **affected_paths,
417 const char *local_abspath,
418 svn_client_ctx_t *ctx,
419 apr_pool_t *result_pool,
420 apr_pool_t *scratch_pool)
422 const char *wc_root_abspath;
424 svn_patch_file_t *patch_file;
425 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
426 apr_hash_t *paths = apr_hash_make(result_pool);
428 SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
429 local_abspath, ctx, scratch_pool, scratch_pool));
430 SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
431 ctx, scratch_pool, scratch_pool));
432 SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, result_pool));
438 svn_pool_clear(iterpool);
439 SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
441 FALSE /*ignore_whitespace*/,
442 iterpool, iterpool));
446 apr_pstrdup(result_pool, patch->old_filename),
447 apr_pstrdup(result_pool, patch->new_filename));
449 SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
450 svn_pool_destroy(iterpool);
452 *affected_paths = paths;
457 svn_client_shelf_has_changes(svn_boolean_t *has_changes,
459 const char *local_abspath,
460 svn_client_ctx_t *ctx,
461 apr_pool_t *scratch_pool)
463 apr_hash_t *patch_paths;
465 SVN_ERR(svn_client_shelf_get_paths(&patch_paths, name, local_abspath,
466 ctx, scratch_pool, scratch_pool));
467 *has_changes = (apr_hash_count(patch_paths) != 0);
471 /* Set *LOGMSG to the log message stored in the file PATCH_ABSPATH.
473 * ### Currently just reads the first line.
476 read_logmsg_from_patch(const char **logmsg,
477 const char *patch_abspath,
478 apr_pool_t *result_pool,
479 apr_pool_t *scratch_pool)
482 svn_stream_t *stream;
484 svn_stringbuf_t *line;
486 SVN_ERR(svn_io_file_open(&file, patch_abspath,
487 APR_FOPEN_READ, APR_FPROT_OS_DEFAULT, scratch_pool));
488 stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool);
489 SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool));
490 SVN_ERR(svn_stream_close(stream));
491 *logmsg = line->data;
496 svn_client_shelves_list(apr_hash_t **shelved_patch_infos,
497 const char *local_abspath,
498 svn_client_ctx_t *ctx,
499 apr_pool_t *result_pool,
500 apr_pool_t *scratch_pool)
504 apr_hash_index_t *hi;
506 SVN_ERR(svn_wc__get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
507 scratch_pool, scratch_pool));
508 SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/,
509 result_pool, scratch_pool));
511 *shelved_patch_infos = apr_hash_make(result_pool);
513 /* Remove non-shelves */
514 for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
516 const char *filename = apr_hash_this_key(hi);
517 svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
520 svn_error_clear(shelf_name_from_filename(&name, filename, result_pool));
521 if (name && dirent->kind == svn_node_file)
523 svn_client_shelved_patch_info_t *info
524 = apr_palloc(result_pool, sizeof(*info));
526 info->dirent = dirent;
527 info->mtime = info->dirent->mtime;
529 = svn_dirent_join(shelves_dir, filename, result_pool);
530 SVN_ERR(read_logmsg_from_patch(&info->message, info->patch_path,
531 result_pool, scratch_pool));
533 svn_hash_sets(*shelved_patch_infos, name, info);
541 svn_client_shelves_any(svn_boolean_t *any_shelved,
542 const char *local_abspath,
543 svn_client_ctx_t *ctx,
544 apr_pool_t *scratch_pool)
546 apr_hash_t *shelved_patch_infos;
548 SVN_ERR(svn_client_shelves_list(&shelved_patch_infos, local_abspath,
549 ctx, scratch_pool, scratch_pool));
550 *any_shelved = apr_hash_count(shelved_patch_infos) != 0;