]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_client/shelve.c
MFC r362056:
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_client / shelve.c
1 /*
2  * shelve.c:  implementation of the 'shelve' commands
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23
24 /* ==================================================================== */
25
26 /* We define this here to remove any further warnings about the usage of
27    experimental functions in this file. */
28 #define SVN_EXPERIMENTAL
29
30 #include "svn_client.h"
31 #include "svn_wc.h"
32 #include "svn_pools.h"
33 #include "svn_dirent_uri.h"
34 #include "svn_path.h"
35 #include "svn_hash.h"
36 #include "svn_utf.h"
37 #include "svn_ctype.h"
38
39 #include "client.h"
40 #include "private/svn_client_private.h"
41 #include "private/svn_wc_private.h"
42 #include "svn_private_config.h"
43
44
45 static svn_error_t *
46 shelf_name_encode(char **encoded_name_p,
47                   const char *name,
48                   apr_pool_t *result_pool)
49 {
50   char *encoded_name
51     = apr_palloc(result_pool, strlen(name) * 2 + 1);
52   char *out_pos = encoded_name;
53
54   if (name[0] == '\0')
55     return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
56                             _("Shelf name cannot be the empty string"));
57
58   while (*name)
59     {
60       apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++));
61       out_pos += 2;
62     }
63   *encoded_name_p = encoded_name;
64   return SVN_NO_ERROR;
65 }
66
67 static svn_error_t *
68 shelf_name_decode(char **decoded_name_p,
69                   const char *codename,
70                   apr_pool_t *result_pool)
71 {
72   svn_stringbuf_t *sb
73     = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool);
74   const char *input = codename;
75
76   while (*input)
77     {
78       int c;
79       int nchars;
80       int nitems = sscanf(input, "%02x%n", &c, &nchars);
81
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);
86       input += 2;
87     }
88   *decoded_name_p = sb->data;
89   return SVN_NO_ERROR;
90 }
91
92 /* Set *NAME to the shelf name from FILENAME. */
93 static svn_error_t *
94 shelf_name_from_filename(char **name,
95                          const char *filename,
96                          apr_pool_t *result_pool)
97 {
98   size_t len = strlen(filename);
99
100   if (len > 6 && strcmp(filename + len - 6, ".patch") == 0)
101     {
102       char *codename = apr_pstrndup(result_pool, filename, len - 6);
103       SVN_ERR(shelf_name_decode(name, codename, result_pool));
104     }
105   return SVN_NO_ERROR;
106 }
107
108 /* Set *PATCH_ABSPATH to the abspath of the patch file for shelved change
109  * NAME, no matter whether it exists.
110  */
111 static svn_error_t *
112 get_patch_abspath(char **patch_abspath,
113                   const char *name,
114                   const char *wc_root_abspath,
115                   svn_client_ctx_t *ctx,
116                   apr_pool_t *result_pool,
117                   apr_pool_t *scratch_pool)
118 {
119   char *dir;
120   char *filename;
121
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);
127   return SVN_NO_ERROR;
128 }
129
130 /** Write local changes to a patch file for shelved change @a name.
131  *
132  * @a message: An optional log message.
133  *
134  * @a wc_root_abspath: The WC root dir.
135  *
136  * @a overwrite_existing: If a file at @a patch_abspath exists, overwrite it.
137  *
138  * @a paths, @a depth, @a changelists: The selection of local paths to diff.
139  */
140 static svn_error_t *
141 shelf_write_patch(const char *name,
142                   const char *message,
143                   const char *wc_root_abspath,
144                   svn_boolean_t overwrite_existing,
145                   const apr_array_header_t *paths,
146                   svn_depth_t depth,
147                   const apr_array_header_t *changelists,
148                   svn_client_ctx_t *ctx,
149                   apr_pool_t *scratch_pool)
150 {
151   char *patch_abspath;
152   apr_int32_t flag;
153   apr_file_t *outfile;
154   svn_stream_t *outstream;
155   svn_stream_t *errstream;
156   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
157   int i;
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}};
161
162   SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
163                             ctx, scratch_pool, scratch_pool));
164
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));
176
177   /* Write the patch file header (log message, etc.) */
178   if (message)
179     {
180       SVN_ERR(svn_stream_printf(outstream, scratch_pool, "%s\n",
181                                 message));
182     }
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"));
187
188   for (i = 0; i < paths->nelts; i++)
189     {
190       const char *path = APR_ARRAY_IDX(paths, i, const char *);
191
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));
196
197       SVN_ERR(svn_client_diff_peg6(
198                      NULL /*options*/,
199                      path,
200                      &peg_revision,
201                      &start_revision,
202                      &end_revision,
203                      wc_root_abspath,
204                      depth,
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,
214                      outstream,
215                      errstream,
216                      changelists,
217                      ctx, iterpool));
218     }
219   SVN_ERR(svn_stream_close(outstream));
220   SVN_ERR(svn_stream_close(errstream));
221
222   return SVN_NO_ERROR;
223 }
224
225 /** Apply the patch file for shelved change @a name to the WC.
226  *
227  * @a wc_root_abspath: The WC root dir.
228  *
229  * @a reverse: Apply the patch in reverse.
230  *
231  * @a dry_run: Don't really apply the changes, just notify what would be done.
232  */
233 static svn_error_t *
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)
240 {
241   char *patch_abspath;
242
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*/,
247                            reverse,
248                            FALSE /*ignore_whitespace*/,
249                            TRUE /*remove_tempfiles*/, NULL, NULL,
250                            ctx, scratch_pool));
251
252   return SVN_NO_ERROR;
253 }
254
255 /** Delete the patch file for shelved change @a name.
256  *
257  * @a wc_root_abspath: The WC root dir.
258  */
259 static svn_error_t *
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)
264 {
265   char *patch_abspath, *to_abspath;
266
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);
270
271   /* remove any previous backup */
272   SVN_ERR(svn_io_remove_file2(to_abspath, TRUE /*ignore_enoent*/,
273                               scratch_pool));
274
275   /* move the patch to a backup file */
276   SVN_ERR(svn_io_file_rename2(patch_abspath, to_abspath, FALSE /*flush_to_disk*/,
277                               scratch_pool));
278   return SVN_NO_ERROR;
279 }
280
281 svn_error_t *
282 svn_client_shelve(const char *name,
283                   const apr_array_header_t *paths,
284                   svn_depth_t depth,
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,
289                   apr_pool_t *pool)
290 {
291   const char *local_abspath;
292   const char *wc_root_abspath;
293   const char *message = "";
294   svn_error_t *err;
295
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));
301
302   /* Fetch the log message and any other revprops */
303   if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
304     {
305       const char *tmp_file;
306       apr_array_header_t *commit_items = apr_array_make(pool, 1, sizeof(void *));
307
308       SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
309                                       ctx, pool));
310       if (! message)
311         return SVN_NO_ERROR;
312     }
313
314   err = shelf_write_patch(name, message, wc_root_abspath,
315                           FALSE /*overwrite_existing*/,
316                           paths, depth, changelists,
317                           ctx, pool);
318   if (err && APR_STATUS_IS_EEXIST(err->apr_err))
319     {
320       return svn_error_quick_wrapf(err,
321                                    "Shelved change '%s' already exists",
322                                    name);
323     }
324   else
325     SVN_ERR(err);
326
327   if (!keep_local)
328     {
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,
333                                 ctx, pool));
334     }
335
336   if (dry_run)
337     {
338       SVN_ERR(shelf_delete_patch(name, wc_root_abspath,
339                                  ctx, pool));
340     }
341
342   return SVN_NO_ERROR;
343 }
344
345 svn_error_t *
346 svn_client_unshelve(const char *name,
347                     const char *local_abspath,
348                     svn_boolean_t keep,
349                     svn_boolean_t dry_run,
350                     svn_client_ctx_t *ctx,
351                     apr_pool_t *pool)
352 {
353   const char *wc_root_abspath;
354   svn_error_t *err;
355
356   SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
357                                  local_abspath, ctx, pool, pool));
358
359   /* Apply the patch. */
360   err = shelf_apply_patch(name, wc_root_abspath,
361                           FALSE /*reverse*/, dry_run,
362                           ctx, pool);
363   if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
364     {
365       return svn_error_quick_wrapf(err,
366                                    "Shelved change '%s' not found",
367                                    name);
368     }
369   else
370     SVN_ERR(err);
371
372   /* Remove the patch. */
373   if (! keep && ! dry_run)
374     {
375       SVN_ERR(shelf_delete_patch(name, wc_root_abspath,
376                                  ctx, pool));
377     }
378
379   return SVN_NO_ERROR;
380 }
381
382 svn_error_t *
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,
387                           apr_pool_t *pool)
388 {
389   const char *wc_root_abspath;
390
391   SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
392                                  local_abspath, ctx, pool, pool));
393
394   /* Remove the patch. */
395   if (! dry_run)
396     {
397       svn_error_t *err;
398
399       err = shelf_delete_patch(name, wc_root_abspath,
400                                ctx, pool);
401       if (err && APR_STATUS_IS_ENOENT(err->apr_err))
402         {
403           return svn_error_quick_wrapf(err,
404                                        "Shelved change '%s' not found",
405                                        name);
406         }
407       else
408         SVN_ERR(err);
409     }
410
411   return SVN_NO_ERROR;
412 }
413
414 svn_error_t *
415 svn_client_shelf_get_paths(apr_hash_t **affected_paths,
416                            const char *name,
417                            const char *local_abspath,
418                            svn_client_ctx_t *ctx,
419                            apr_pool_t *result_pool,
420                            apr_pool_t *scratch_pool)
421 {
422   const char *wc_root_abspath;
423   char *patch_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);
427
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));
433
434   while (1)
435     {
436       svn_patch_t *patch;
437
438       svn_pool_clear(iterpool);
439       SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
440                                         FALSE /*reverse*/,
441                                         FALSE /*ignore_whitespace*/,
442                                         iterpool, iterpool));
443       if (! patch)
444         break;
445       svn_hash_sets(paths,
446                     apr_pstrdup(result_pool, patch->old_filename),
447                     apr_pstrdup(result_pool, patch->new_filename));
448     }
449   SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
450   svn_pool_destroy(iterpool);
451
452   *affected_paths = paths;
453   return SVN_NO_ERROR;
454 }
455
456 svn_error_t *
457 svn_client_shelf_has_changes(svn_boolean_t *has_changes,
458                              const char *name,
459                              const char *local_abspath,
460                              svn_client_ctx_t *ctx,
461                              apr_pool_t *scratch_pool)
462 {
463   apr_hash_t *patch_paths;
464
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);
468   return SVN_NO_ERROR;
469 }
470
471 /* Set *LOGMSG to the log message stored in the file PATCH_ABSPATH.
472  *
473  * ### Currently just reads the first line.
474  */
475 static svn_error_t *
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)
480 {
481   apr_file_t *file;
482   svn_stream_t *stream;
483   svn_boolean_t eof;
484   svn_stringbuf_t *line;
485
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;
492   return SVN_NO_ERROR;
493 }
494
495 svn_error_t *
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)
501 {
502   char *shelves_dir;
503   apr_hash_t *dirents;
504   apr_hash_index_t *hi;
505
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));
510
511   *shelved_patch_infos = apr_hash_make(result_pool);
512
513   /* Remove non-shelves */
514   for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
515     {
516       const char *filename = apr_hash_this_key(hi);
517       svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
518       char *name = NULL;
519
520       svn_error_clear(shelf_name_from_filename(&name, filename, result_pool));
521       if (name && dirent->kind == svn_node_file)
522         {
523           svn_client_shelved_patch_info_t *info
524             = apr_palloc(result_pool, sizeof(*info));
525
526           info->dirent = dirent;
527           info->mtime = info->dirent->mtime;
528           info->patch_path
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));
532
533           svn_hash_sets(*shelved_patch_infos, name, info);
534         }
535     }
536
537   return SVN_NO_ERROR;
538 }
539
540 svn_error_t *
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)
545 {
546   apr_hash_t *shelved_patch_infos;
547
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;
551   return SVN_NO_ERROR;
552 }