xref: /freebsd-13-stable/contrib/subversion/subversion/svn/util.c (revision b7ec5dea64b6513b41316a38cc72efa9139bc4ae)
1 /*
2  * util.c: Subversion command line client utility functions. Any
3  * functions that need to be shared across subcommands should be put
4  * in here.
5  *
6  * ====================================================================
7  *    Licensed to the Apache Software Foundation (ASF) under one
8  *    or more contributor license agreements.  See the NOTICE file
9  *    distributed with this work for additional information
10  *    regarding copyright ownership.  The ASF licenses this file
11  *    to you under the Apache License, Version 2.0 (the
12  *    "License"); you may not use this file except in compliance
13  *    with the License.  You may obtain a copy of the License at
14  *
15  *      http://www.apache.org/licenses/LICENSE-2.0
16  *
17  *    Unless required by applicable law or agreed to in writing,
18  *    software distributed under the License is distributed on an
19  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20  *    KIND, either express or implied.  See the License for the
21  *    specific language governing permissions and limitations
22  *    under the License.
23  * ====================================================================
24  */
25 
26 /* ==================================================================== */
27 
28 
29 
30 /*** Includes. ***/
31 
32 #include <string.h>
33 #include <ctype.h>
34 #include <assert.h>
35 
36 #include <apr_env.h>
37 #include <apr_errno.h>
38 #include <apr_file_info.h>
39 #include <apr_strings.h>
40 #include <apr_tables.h>
41 #include <apr_general.h>
42 #include <apr_lib.h>
43 
44 #include "svn_pools.h"
45 #include "svn_error.h"
46 #include "svn_ctype.h"
47 #include "svn_client.h"
48 #include "svn_cmdline.h"
49 #include "svn_string.h"
50 #include "svn_dirent_uri.h"
51 #include "svn_path.h"
52 #include "svn_hash.h"
53 #include "svn_io.h"
54 #include "svn_utf.h"
55 #include "svn_subst.h"
56 #include "svn_config.h"
57 #include "svn_wc.h"
58 #include "svn_xml.h"
59 #include "svn_time.h"
60 #include "svn_props.h"
61 #include "svn_private_config.h"
62 #include "cl.h"
63 
64 #include "private/svn_token.h"
65 #include "private/svn_opt_private.h"
66 #include "private/svn_client_private.h"
67 #include "private/svn_cmdline_private.h"
68 #include "private/svn_string_private.h"
69 #ifdef HAS_ORGANIZATION_NAME
70 #include "freebsd-organization.h"
71 #endif
72 
73 
74 
75 
76 svn_error_t *
svn_cl__print_commit_info(const svn_commit_info_t * commit_info,void * baton,apr_pool_t * pool)77 svn_cl__print_commit_info(const svn_commit_info_t *commit_info,
78                           void *baton,
79                           apr_pool_t *pool)
80 {
81   /* Be very careful with returning errors from this callback as those
82      will be returned as errors from editor->close_edit(...), which may
83      cause callers to assume that the commit itself failed.
84 
85      See log message of r1659867 and the svn_ra_get_commit_editor3
86      documentation for details on error scenarios. */
87 
88   if (SVN_IS_VALID_REVNUM(commit_info->revision))
89     SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld%s.\n"),
90                                commit_info->revision,
91                                commit_info->revision == 42 &&
92                                getenv("SVN_I_LOVE_PANGALACTIC_GARGLE_BLASTERS")
93                                  ?  _(" (the answer to life, the universe, "
94                                       "and everything)")
95                                  : ""));
96 
97   /* Writing to stdout, as there maybe systems that consider the
98    * presence of stderr as an indication of commit failure.
99    * OTOH, this is only of informational nature to the user as
100    * the commit has succeeded. */
101   if (commit_info->post_commit_err)
102     SVN_ERR(svn_cmdline_printf(pool, _("\nWarning: %s\n"),
103                                commit_info->post_commit_err));
104 
105   return SVN_NO_ERROR;
106 }
107 
108 
109 svn_error_t *
svn_cl__merge_file_externally(const char * base_path,const char * their_path,const char * my_path,const char * merged_path,const char * wc_path,apr_hash_t * config,svn_boolean_t * remains_in_conflict,apr_pool_t * pool)110 svn_cl__merge_file_externally(const char *base_path,
111                               const char *their_path,
112                               const char *my_path,
113                               const char *merged_path,
114                               const char *wc_path,
115                               apr_hash_t *config,
116                               svn_boolean_t *remains_in_conflict,
117                               apr_pool_t *pool)
118 {
119   char *merge_tool;
120   /* Error if there is no editor specified */
121   if (apr_env_get(&merge_tool, "SVN_MERGE", pool) != APR_SUCCESS)
122     {
123       struct svn_config_t *cfg;
124       merge_tool = NULL;
125       cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
126       /* apr_env_get wants char **, this wants const char ** */
127       svn_config_get(cfg, (const char **)&merge_tool,
128                      SVN_CONFIG_SECTION_HELPERS,
129                      SVN_CONFIG_OPTION_MERGE_TOOL_CMD, NULL);
130     }
131 
132   if (merge_tool)
133     {
134       const char *c;
135 
136       for (c = merge_tool; *c; c++)
137         if (!svn_ctype_isspace(*c))
138           break;
139 
140       if (! *c)
141         return svn_error_create
142           (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL,
143            _("The SVN_MERGE environment variable is empty or "
144              "consists solely of whitespace. Expected a shell command.\n"));
145     }
146   else
147       return svn_error_create
148         (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL,
149          _("The environment variable SVN_MERGE and the merge-tool-cmd run-time "
150            "configuration option were not set.\n"));
151 
152   {
153     const char *arguments[7] = { 0 };
154     char *cwd;
155     int exitcode;
156 
157     apr_status_t status = apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, pool);
158     if (status != 0)
159       return svn_error_wrap_apr(status, NULL);
160 
161     arguments[0] = merge_tool;
162     arguments[1] = base_path;
163     arguments[2] = their_path;
164     arguments[3] = my_path;
165     arguments[4] = merged_path;
166     arguments[5] = wc_path;
167     arguments[6] = NULL;
168 
169     /* Presumably apr_filepath_get() returns a valid path, so we don't have
170        to use the safe version of svn_dirent_internal_style() here. */
171     SVN_ERR(svn_io_run_cmd(svn_dirent_internal_style(cwd, pool), merge_tool,
172                            arguments, &exitcode, NULL, TRUE, NULL, NULL, NULL,
173                            pool));
174     /* Exit code 0 means the merge was successful.
175      * Exit code 1 means the file was left in conflict but it
176      * is OK to continue with the merge.
177      * Any other exit code means there was a real problem. */
178     if (exitcode != 0 && exitcode != 1)
179       return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
180         _("The external merge tool '%s' exited with exit code %d."),
181         merge_tool, exitcode);
182     else if (remains_in_conflict)
183       *remains_in_conflict = exitcode == 1;
184   }
185   return SVN_NO_ERROR;
186 }
187 
188 
189 /* A svn_client_ctx_t's log_msg_baton3, for use with
190    svn_cl__make_log_msg_baton(). */
191 struct log_msg_baton
192 {
193   const char *editor_cmd;  /* editor specified via --editor-cmd, else NULL */
194   const char *message;  /* the message. */
195   const char *message_encoding; /* the locale/encoding of the message. */
196   const char *base_dir; /* the base directory for an external edit. UTF-8! */
197   const char *tmpfile_left; /* the tmpfile left by an external edit. UTF-8! */
198   svn_boolean_t non_interactive; /* if true, don't pop up an editor */
199   apr_hash_t *config; /* client configuration hash */
200   svn_boolean_t keep_locks; /* Keep repository locks? */
201   apr_pool_t *pool; /* a pool. */
202 };
203 
204 
205 svn_error_t *
svn_cl__make_log_msg_baton(void ** baton,svn_cl__opt_state_t * opt_state,const char * base_dir,apr_hash_t * config,apr_pool_t * pool)206 svn_cl__make_log_msg_baton(void **baton,
207                            svn_cl__opt_state_t *opt_state,
208                            const char *base_dir /* UTF-8! */,
209                            apr_hash_t *config,
210                            apr_pool_t *pool)
211 {
212   struct log_msg_baton *lmb = apr_pcalloc(pool, sizeof(*lmb));
213 
214   if (opt_state->filedata)
215     {
216       if (strlen(opt_state->filedata->data) < opt_state->filedata->len)
217         {
218           /* The data contains a zero byte, and therefore can't be
219              represented as a C string.  Punt now; it's probably not
220              a deliberate encoding, and even if it is, we still
221              can't handle it. */
222           return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE, NULL,
223                                   _("Log message contains a zero byte"));
224         }
225       lmb->message = opt_state->filedata->data;
226     }
227   else
228     {
229       lmb->message = opt_state->message;
230     }
231 
232   lmb->editor_cmd = opt_state->editor_cmd;
233   if (opt_state->encoding)
234     {
235       lmb->message_encoding = opt_state->encoding;
236     }
237   else if (config)
238     {
239       svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
240       svn_config_get(cfg, &(lmb->message_encoding),
241                      SVN_CONFIG_SECTION_MISCELLANY,
242                      SVN_CONFIG_OPTION_LOG_ENCODING,
243                      NULL);
244     }
245   else
246     lmb->message_encoding = NULL;
247 
248   lmb->base_dir = base_dir;
249   lmb->tmpfile_left = NULL;
250   lmb->config = config;
251   lmb->keep_locks = opt_state->no_unlock;
252   lmb->non_interactive = opt_state->non_interactive;
253   lmb->pool = pool;
254   *baton = lmb;
255   return SVN_NO_ERROR;
256 }
257 
258 
259 svn_error_t *
svn_cl__cleanup_log_msg(void * log_msg_baton,svn_error_t * commit_err,apr_pool_t * pool)260 svn_cl__cleanup_log_msg(void *log_msg_baton,
261                         svn_error_t *commit_err,
262                         apr_pool_t *pool)
263 {
264   struct log_msg_baton *lmb = log_msg_baton;
265   svn_error_t *err;
266 
267   /* If there was no tmpfile left, or there is no log message baton,
268      return COMMIT_ERR. */
269   if ((! lmb) || (! lmb->tmpfile_left))
270     return commit_err;
271 
272   /* If there was no commit error, cleanup the tmpfile and return. */
273   if (! commit_err)
274     return svn_io_remove_file2(lmb->tmpfile_left, FALSE, lmb->pool);
275 
276   /* There was a commit error; there is a tmpfile.  Leave the tmpfile
277      around, and add message about its presence to the commit error
278      chain.  Then return COMMIT_ERR.  If the conversion from UTF-8 to
279      native encoding fails, we have to compose that error with the
280      commit error chain, too. */
281 
282   err = svn_error_createf(commit_err->apr_err, NULL,
283                           _("   '%s'"),
284                           svn_dirent_local_style(lmb->tmpfile_left, pool));
285   svn_error_compose(commit_err,
286                     svn_error_create(commit_err->apr_err, err,
287                       _("Your commit message was left in "
288                         "a temporary file:")));
289   return commit_err;
290 }
291 
292 
293 /* Remove line-starting PREFIX and everything after it from BUFFER.
294    If NEW_LEN is non-NULL, return the new length of BUFFER in
295    *NEW_LEN.  */
296 static void
truncate_buffer_at_prefix(apr_size_t * new_len,char * buffer,const char * prefix)297 truncate_buffer_at_prefix(apr_size_t *new_len,
298                           char *buffer,
299                           const char *prefix)
300 {
301   char *substring = buffer;
302 
303   assert(buffer && prefix);
304 
305   /* Initialize *NEW_LEN. */
306   if (new_len)
307     *new_len = strlen(buffer);
308 
309   while (1)
310     {
311       /* Find PREFIX in BUFFER. */
312       substring = strstr(substring, prefix);
313       if (! substring)
314         return;
315 
316       /* We found PREFIX.  Is it really a PREFIX?  Well, if it's the first
317          thing in the file, or if the character before it is a
318          line-terminator character, it sure is. */
319       if ((substring == buffer)
320           || (*(substring - 1) == '\r')
321           || (*(substring - 1) == '\n'))
322         {
323           *substring = '\0';
324           if (new_len)
325             *new_len = substring - buffer;
326         }
327       else if (substring)
328         {
329           /* Well, it wasn't really a prefix, so just advance by 1
330              character and continue. */
331           substring++;
332         }
333     }
334 
335   /* NOTREACHED */
336 }
337 
338 
339 /*
340  * Since we're adding freebsd-specific tokens to the log message,
341  * clean out any leftovers to avoid accidently sending them to other
342  * projects that won't be expecting them.
343  */
344 
345 static const char *prefixes[] = {
346   "PR:",
347   "Submitted by:",
348   "Reported by:",
349   "Reviewed by:",
350   "Approved by:",
351   "Obtained from:",
352   "MFC after:",
353   "MFH:",
354   "Relnotes:",
355   "Security:",
356   "Sponsored by:",
357   "Pull Request:",
358   "Differential Revision:",
359 };
360 
361 void
cleanmsg(apr_size_t * l,char * s)362 cleanmsg(apr_size_t *l, char *s)
363 {
364   int i;
365   char *pos;
366   char *kw;
367   char *p;
368   int empty;
369 
370   for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++) {
371     pos = s;
372     while ((kw = strstr(pos, prefixes[i])) != NULL) {
373       /* Check to see if keyword is at start of line (or buffer) */
374       if (!(kw == s || kw[-1] == '\r' || kw[-1] == '\n')) {
375 	pos = kw + 1;
376 	continue;
377       }
378       p = kw + strlen(prefixes[i]);
379       empty = 1;
380       while (1) {
381 	if (*p == ' ' || *p == '\t') {
382 	  p++;
383 	  continue;
384 	}
385 	if (*p == '\0' || *p == '\r' || *p == '\n')
386 	  break;
387 	empty = 0;
388 	break;
389       }
390       if (empty && (*p == '\r' || *p == '\n')) {
391 	memmove(kw, p + 1, strlen(p + 1) + 1);
392 	if (l)
393 	  *l -= (p + 1 - kw);
394       } else if (empty) {
395 	*kw = '\0';
396 	if (l)
397 	  *l -= (p - kw);
398       } else {
399 	pos = p;
400       }
401     }
402   }
403 }
404 
405 #define EDITOR_EOF_PREFIX  _("--This line, and those below, will be ignored--")
406 
407 svn_error_t *
svn_cl__get_log_message(const char ** log_msg,const char ** tmp_file,const apr_array_header_t * commit_items,void * baton,apr_pool_t * pool)408 svn_cl__get_log_message(const char **log_msg,
409                         const char **tmp_file,
410                         const apr_array_header_t *commit_items,
411                         void *baton,
412                         apr_pool_t *pool)
413 {
414   svn_stringbuf_t *default_msg = NULL;
415   struct log_msg_baton *lmb = baton;
416   svn_stringbuf_t *message = NULL;
417   svn_config_t *cfg;
418   const char *mfc_after, *sponsored_by;
419 
420   cfg = lmb->config ? svn_hash_gets(lmb->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
421 
422   /* Set default message.  */
423   default_msg = svn_stringbuf_create(APR_EOL_STR, pool);
424   svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
425   svn_stringbuf_appendcstr(default_msg, "PR:\t\t" APR_EOL_STR);
426   svn_stringbuf_appendcstr(default_msg, "Submitted by:\t" APR_EOL_STR);
427   svn_stringbuf_appendcstr(default_msg, "Reported by:\t" APR_EOL_STR);
428   svn_stringbuf_appendcstr(default_msg, "Reviewed by:\t" APR_EOL_STR);
429   svn_stringbuf_appendcstr(default_msg, "Approved by:\t" APR_EOL_STR);
430   svn_stringbuf_appendcstr(default_msg, "Obtained from:\t" APR_EOL_STR);
431   svn_stringbuf_appendcstr(default_msg, "MFC after:\t");
432   svn_config_get(cfg, &mfc_after, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-mfc-after", NULL);
433   if (mfc_after != NULL)
434 	  svn_stringbuf_appendcstr(default_msg, mfc_after);
435   svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
436   svn_stringbuf_appendcstr(default_msg, "MFH:\t\t" APR_EOL_STR);
437   svn_stringbuf_appendcstr(default_msg, "Relnotes:\t" APR_EOL_STR);
438   svn_stringbuf_appendcstr(default_msg, "Security:\t" APR_EOL_STR);
439   svn_stringbuf_appendcstr(default_msg, "Sponsored by:\t");
440   svn_config_get(cfg, &sponsored_by, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-sponsored-by",
441 #ifdef HAS_ORGANIZATION_NAME
442   	ORGANIZATION_NAME);
443 #else
444 	NULL);
445 #endif
446   if (sponsored_by != NULL)
447 	  svn_stringbuf_appendcstr(default_msg, sponsored_by);
448   svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
449   svn_stringbuf_appendcstr(default_msg, "Pull Request:\t" APR_EOL_STR);
450   svn_stringbuf_appendcstr(default_msg, "Differential Revision:\t" APR_EOL_STR);
451   svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX);
452   svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
453   svn_stringbuf_appendcstr(default_msg, "> Description of fields to fill in above:                     76 columns --|" APR_EOL_STR);
454   svn_stringbuf_appendcstr(default_msg, "> PR:                       If and which Problem Report is related." APR_EOL_STR);
455   svn_stringbuf_appendcstr(default_msg, "> Submitted by:             If someone else sent in the change." APR_EOL_STR);
456   svn_stringbuf_appendcstr(default_msg, "> Reported by:              If someone else reported the issue." APR_EOL_STR);
457   svn_stringbuf_appendcstr(default_msg, "> Reviewed by:              If someone else reviewed your modification." APR_EOL_STR);
458   svn_stringbuf_appendcstr(default_msg, "> Approved by:              If you needed approval for this commit." APR_EOL_STR);
459   svn_stringbuf_appendcstr(default_msg, "> Obtained from:            If the change is from a third party." APR_EOL_STR);
460   svn_stringbuf_appendcstr(default_msg, "> MFC after:                N [day[s]|week[s]|month[s]].  Request a reminder email." APR_EOL_STR);
461   svn_stringbuf_appendcstr(default_msg, "> MFH:                      Ports tree branch name.  Request approval for merge." APR_EOL_STR);
462   svn_stringbuf_appendcstr(default_msg, "> Relnotes:                 Set to 'yes' for mention in release notes." APR_EOL_STR);
463   svn_stringbuf_appendcstr(default_msg, "> Security:                 Vulnerability reference (one per line) or description." APR_EOL_STR);
464   svn_stringbuf_appendcstr(default_msg, "> Sponsored by:             If the change was sponsored by an organization." APR_EOL_STR);
465   svn_stringbuf_appendcstr(default_msg, "> Pull Request:             https://github.com/freebsd/freebsd/pull/### (*full* GitHub URL needed)." APR_EOL_STR);
466   svn_stringbuf_appendcstr(default_msg, "> Differential Revision:    https://reviews.freebsd.org/D### (*full* phabric URL needed)." APR_EOL_STR);
467   svn_stringbuf_appendcstr(default_msg, "> Empty fields above will be automatically removed." APR_EOL_STR);
468   svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
469 
470   *tmp_file = NULL;
471   if (lmb->message)
472     {
473       svn_string_t *log_msg_str = svn_string_create(lmb->message, pool);
474 
475       SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, NULL, NULL,
476                                             log_msg_str, lmb->message_encoding,
477                                             FALSE, pool, pool),
478                 _("Error normalizing log message to internal format"));
479 
480       /* Strip off the EOF marker text and the junk that follows it. */
481       truncate_buffer_at_prefix(&(log_msg_str->len), (char *)log_msg_str->data,
482                                 EDITOR_EOF_PREFIX);
483 
484       cleanmsg(&(log_msg_str->len), (char*)log_msg_str->data);
485 
486       *log_msg = log_msg_str->data;
487       return SVN_NO_ERROR;
488     }
489 
490   if (! commit_items->nelts)
491     {
492       *log_msg = "";
493       return SVN_NO_ERROR;
494     }
495 
496   while (! message)
497     {
498       /* We still don't have a valid commit message.  Use $EDITOR to
499          get one.  Note that svn_cl__edit_string_externally will still
500          return a UTF-8'ized log message. */
501       int i;
502       svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
503       svn_error_t *err = SVN_NO_ERROR;
504       svn_string_t *msg_string = svn_string_create_empty(pool);
505 
506       for (i = 0; i < commit_items->nelts; i++)
507         {
508           svn_client_commit_item3_t *item
509             = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
510           const char *path = item->path;
511           char text_mod = '_', prop_mod = ' ', unlock = ' ';
512 
513           if (! path)
514             path = item->url;
515           else if (lmb->base_dir)
516             path = svn_dirent_is_child(lmb->base_dir, path, pool);
517 
518           /* If still no path, then just use current directory. */
519           if (! path || !*path)
520             path = ".";
521 
522           if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
523               && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
524             text_mod = 'R';
525           else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
526             text_mod = 'A';
527           else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
528             text_mod = 'D';
529           else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
530             text_mod = 'M';
531 
532           if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
533             prop_mod = 'M';
534 
535           if (! lmb->keep_locks
536               && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
537             unlock = 'U';
538 
539           svn_stringbuf_appendbyte(tmp_message, text_mod);
540           svn_stringbuf_appendbyte(tmp_message, prop_mod);
541           svn_stringbuf_appendbyte(tmp_message, unlock);
542           if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
543             /* History included via copy/move. */
544             svn_stringbuf_appendcstr(tmp_message, "+ ");
545           else
546             svn_stringbuf_appendcstr(tmp_message, "  ");
547           svn_stringbuf_appendcstr(tmp_message, path);
548           svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR);
549         }
550 
551       msg_string->data = tmp_message->data;
552       msg_string->len = tmp_message->len;
553 
554       /* Use the external edit to get a log message. */
555       if (! lmb->non_interactive)
556         {
557           err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
558                                                     lmb->editor_cmd,
559                                                     lmb->base_dir ? lmb->base_dir : "",
560                                                     msg_string, "svn-commit",
561                                                     lmb->config, TRUE,
562                                                     lmb->message_encoding,
563                                                     pool);
564         }
565       else /* non_interactive flag says we can't pop up an editor, so error */
566         {
567           return svn_error_create
568             (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
569              _("Cannot invoke editor to get log message "
570                "when non-interactive"));
571         }
572 
573       /* Dup the tmpfile path into its baton's pool. */
574       *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool,
575                                                   lmb->tmpfile_left);
576 
577       /* If the edit returned an error, handle it. */
578       if (err)
579         {
580           if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)
581             err = svn_error_quick_wrap
582               (err, _("Could not use external editor to fetch log message; "
583                       "consider setting the $SVN_EDITOR environment variable "
584                       "or using the --message (-m) or --file (-F) options"));
585           return svn_error_trace(err);
586         }
587 
588       if (msg_string)
589         message = svn_stringbuf_create_from_string(msg_string, pool);
590 
591       /* Strip off the EOF marker text and the junk that follows it. */
592       if (message)
593         truncate_buffer_at_prefix(&message->len, message->data,
594                                   EDITOR_EOF_PREFIX);
595       /*
596        * Since we're adding freebsd-specific tokens to the log message,
597        * clean out any leftovers to avoid accidently sending them to other
598        * projects that won't be expecting them.
599        */
600       if (message)
601 	cleanmsg(&message->len, message->data);
602 
603       if (message)
604         {
605           /* We did get message, now check if it is anything more than just
606              white space as we will consider white space only as empty */
607           apr_size_t len;
608 
609           for (len = 0; len < message->len; len++)
610             {
611               /* FIXME: should really use an UTF-8 whitespace test
612                  rather than svn_ctype_isspace, which is ASCII only */
613               if (! svn_ctype_isspace(message->data[len]))
614                 break;
615             }
616           if (len == message->len)
617             message = NULL;
618         }
619 
620       if (! message)
621         {
622           const char *reply;
623           SVN_ERR(svn_cmdline_prompt_user2
624                   (&reply,
625                    _("\nLog message unchanged or not specified\n"
626                      "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool));
627           if (reply)
628             {
629               int letter = apr_tolower(reply[0]);
630 
631               /* If the user chooses to abort, we cleanup the
632                  temporary file and exit the loop with a NULL
633                  message. */
634               if ('a' == letter)
635                 {
636                   SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
637                   *tmp_file = lmb->tmpfile_left = NULL;
638                   break;
639                 }
640 
641               /* If the user chooses to continue, we make an empty
642                  message, which will cause us to exit the loop.  We
643                  also cleanup the temporary file. */
644               if ('c' == letter)
645                 {
646                   SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
647                   *tmp_file = lmb->tmpfile_left = NULL;
648                   message = svn_stringbuf_create_empty(pool);
649                 }
650 
651               /* If the user chooses anything else, the loop will
652                  continue on the NULL message. */
653             }
654         }
655     }
656 
657   *log_msg = message ? message->data : NULL;
658   return SVN_NO_ERROR;
659 }
660 
661 
662 /* ### The way our error wrapping currently works, the error returned
663  * from here will look as though it originates in this source file,
664  * instead of in the caller's source file.  This can be a bit
665  * misleading, until one starts debugging.  Ideally, there'd be a way
666  * to wrap an error while preserving its FILE/LINE info.
667  */
668 svn_error_t *
svn_cl__may_need_force(svn_error_t * err)669 svn_cl__may_need_force(svn_error_t *err)
670 {
671   if (err
672       && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE ||
673           err->apr_err == SVN_ERR_CLIENT_MODIFIED))
674     {
675       /* Should this svn_error_compose a new error number? Probably not,
676          the error hasn't changed. */
677       err = svn_error_quick_wrap
678         (err, _("Use --force to override this restriction (local modifications "
679          "may be lost)"));
680     }
681 
682   return svn_error_trace(err);
683 }
684 
685 
686 svn_error_t *
svn_cl__error_checked_fputs(const char * string,FILE * stream)687 svn_cl__error_checked_fputs(const char *string, FILE* stream)
688 {
689   /* On POSIX systems, errno will be set on an error in fputs, but this might
690      not be the case on other platforms.  We reset errno and only
691      use it if it was set by the below fputs call.  Else, we just return
692      a generic error. */
693   errno = 0;
694 
695   if (fputs(string, stream) == EOF)
696     {
697       if (apr_get_os_error()) /* is errno on POSIX */
698         return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
699       else
700         return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
701     }
702 
703   return SVN_NO_ERROR;
704 }
705 
706 
707 svn_error_t *
svn_cl__try(svn_error_t * err,apr_array_header_t * errors_seen,svn_boolean_t quiet,...)708 svn_cl__try(svn_error_t *err,
709             apr_array_header_t *errors_seen,
710             svn_boolean_t quiet,
711             ...)
712 {
713   if (err)
714     {
715       apr_status_t apr_err;
716       va_list ap;
717 
718       va_start(ap, quiet);
719       while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS)
720         {
721           if (errors_seen)
722             {
723               int i;
724               svn_boolean_t add = TRUE;
725 
726               /* Don't report duplicate error codes. */
727               for (i = 0; i < errors_seen->nelts; i++)
728                 {
729                   if (APR_ARRAY_IDX(errors_seen, i,
730                                     apr_status_t) == err->apr_err)
731                     {
732                       add = FALSE;
733                       break;
734                     }
735                 }
736               if (add)
737                 APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err;
738             }
739           if (err->apr_err == apr_err)
740             {
741               if (! quiet)
742                 svn_handle_warning2(stderr, err, "svn: ");
743               svn_error_clear(err);
744               va_end(ap);
745               return SVN_NO_ERROR;
746             }
747         }
748       va_end(ap);
749     }
750 
751   return svn_error_trace(err);
752 }
753 
754 
755 void
svn_cl__xml_tagged_cdata(svn_stringbuf_t ** sb,apr_pool_t * pool,const char * tagname,const char * string)756 svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
757                          apr_pool_t *pool,
758                          const char *tagname,
759                          const char *string)
760 {
761   if (string)
762     {
763       svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata,
764                             tagname, SVN_VA_NULL);
765       svn_xml_escape_cdata_cstring(sb, string, pool);
766       svn_xml_make_close_tag(sb, pool, tagname);
767     }
768 }
769 
770 
771 void
svn_cl__print_xml_commit(svn_stringbuf_t ** sb,svn_revnum_t revision,const char * author,const char * date,apr_pool_t * pool)772 svn_cl__print_xml_commit(svn_stringbuf_t **sb,
773                          svn_revnum_t revision,
774                          const char *author,
775                          const char *date,
776                          apr_pool_t *pool)
777 {
778   /* "<commit ...>" */
779   svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit",
780                         "revision",
781                         apr_psprintf(pool, "%ld", revision), SVN_VA_NULL);
782 
783   /* "<author>xx</author>" */
784   if (author)
785     svn_cl__xml_tagged_cdata(sb, pool, "author", author);
786 
787   /* "<date>xx</date>" */
788   if (date)
789     svn_cl__xml_tagged_cdata(sb, pool, "date", date);
790 
791   /* "</commit>" */
792   svn_xml_make_close_tag(sb, pool, "commit");
793 }
794 
795 
796 void
svn_cl__print_xml_lock(svn_stringbuf_t ** sb,const svn_lock_t * lock,apr_pool_t * pool)797 svn_cl__print_xml_lock(svn_stringbuf_t **sb,
798                        const svn_lock_t *lock,
799                        apr_pool_t *pool)
800 {
801   /* "<lock>" */
802   svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", SVN_VA_NULL);
803 
804   /* "<token>xx</token>" */
805   svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token);
806 
807   /* "<owner>xx</owner>" */
808   svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner);
809 
810   /* "<comment>xx</comment>" */
811   svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment);
812 
813   /* "<created>xx</created>" */
814   svn_cl__xml_tagged_cdata(sb, pool, "created",
815                            svn_time_to_cstring(lock->creation_date, pool));
816 
817   /* "<expires>xx</expires>" */
818   if (lock->expiration_date != 0)
819     svn_cl__xml_tagged_cdata(sb, pool, "expires",
820                              svn_time_to_cstring(lock->expiration_date, pool));
821 
822   /* "</lock>" */
823   svn_xml_make_close_tag(sb, pool, "lock");
824 }
825 
826 
827 svn_error_t *
svn_cl__xml_print_header(const char * tagname,apr_pool_t * pool)828 svn_cl__xml_print_header(const char *tagname,
829                          apr_pool_t *pool)
830 {
831   svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
832 
833   /* <?xml version="1.0" encoding="UTF-8"?> */
834   svn_xml_make_header2(&sb, "UTF-8", pool);
835 
836   /* "<TAGNAME>" */
837   svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, SVN_VA_NULL);
838 
839   return svn_cl__error_checked_fputs(sb->data, stdout);
840 }
841 
842 
843 svn_error_t *
svn_cl__xml_print_footer(const char * tagname,apr_pool_t * pool)844 svn_cl__xml_print_footer(const char *tagname,
845                          apr_pool_t *pool)
846 {
847   svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
848 
849   /* "</TAGNAME>" */
850   svn_xml_make_close_tag(&sb, pool, tagname);
851   return svn_cl__error_checked_fputs(sb->data, stdout);
852 }
853 
854 
855 /* A map for svn_node_kind_t values to XML strings */
856 static const svn_token_map_t map_node_kind_xml[] =
857 {
858   { "none", svn_node_none },
859   { "file", svn_node_file },
860   { "dir",  svn_node_dir },
861   { "",     svn_node_unknown },
862   { NULL,   0 }
863 };
864 
865 /* A map for svn_node_kind_t values to human-readable strings */
866 static const svn_token_map_t map_node_kind_human[] =
867 {
868   { N_("none"), svn_node_none },
869   { N_("file"), svn_node_file },
870   { N_("dir"),  svn_node_dir },
871   { "",         svn_node_unknown },
872   { NULL,       0 }
873 };
874 
875 const char *
svn_cl__node_kind_str_xml(svn_node_kind_t kind)876 svn_cl__node_kind_str_xml(svn_node_kind_t kind)
877 {
878   return svn_token__to_word(map_node_kind_xml, kind);
879 }
880 
881 const char *
svn_cl__node_kind_str_human_readable(svn_node_kind_t kind)882 svn_cl__node_kind_str_human_readable(svn_node_kind_t kind)
883 {
884   return _(svn_token__to_word(map_node_kind_human, kind));
885 }
886 
887 
888 /* A map for svn_wc_operation_t values to XML strings */
889 static const svn_token_map_t map_wc_operation_xml[] =
890 {
891   { "none",   svn_wc_operation_none },
892   { "update", svn_wc_operation_update },
893   { "switch", svn_wc_operation_switch },
894   { "merge",  svn_wc_operation_merge },
895   { NULL,     0 }
896 };
897 
898 /* A map for svn_wc_operation_t values to human-readable strings */
899 static const svn_token_map_t map_wc_operation_human[] =
900 {
901   { N_("none"),   svn_wc_operation_none },
902   { N_("update"), svn_wc_operation_update },
903   { N_("switch"), svn_wc_operation_switch },
904   { N_("merge"),  svn_wc_operation_merge },
905   { NULL,         0 }
906 };
907 
908 const char *
svn_cl__operation_str_xml(svn_wc_operation_t operation,apr_pool_t * pool)909 svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool)
910 {
911   return svn_token__to_word(map_wc_operation_xml, operation);
912 }
913 
914 const char *
svn_cl__operation_str_human_readable(svn_wc_operation_t operation,apr_pool_t * pool)915 svn_cl__operation_str_human_readable(svn_wc_operation_t operation,
916                                      apr_pool_t *pool)
917 {
918   return _(svn_token__to_word(map_wc_operation_human, operation));
919 }
920 
921 
922 svn_error_t *
svn_cl__args_to_target_array_print_reserved(apr_array_header_t ** targets,apr_getopt_t * os,const apr_array_header_t * known_targets,svn_client_ctx_t * ctx,svn_boolean_t keep_last_origpath_on_truepath_collision,apr_pool_t * pool)923 svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
924                                             apr_getopt_t *os,
925                                             const apr_array_header_t *known_targets,
926                                             svn_client_ctx_t *ctx,
927                                             svn_boolean_t keep_last_origpath_on_truepath_collision,
928                                             apr_pool_t *pool)
929 {
930   svn_error_t *err = svn_client_args_to_target_array2(targets,
931                                                       os,
932                                                       known_targets,
933                                                       ctx,
934                                                       keep_last_origpath_on_truepath_collision,
935                                                       pool);
936   if (err)
937     {
938       if (err->apr_err ==  SVN_ERR_RESERVED_FILENAME_SPECIFIED)
939         {
940           svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
941           svn_error_clear(err);
942         }
943       else
944         return svn_error_trace(err);
945     }
946   return SVN_NO_ERROR;
947 }
948 
949 
950 /* Helper for svn_cl__get_changelist(); implements
951    svn_changelist_receiver_t. */
952 static svn_error_t *
changelist_receiver(void * baton,const char * path,const char * changelist,apr_pool_t * pool)953 changelist_receiver(void *baton,
954                     const char *path,
955                     const char *changelist,
956                     apr_pool_t *pool)
957 {
958   /* No need to check CHANGELIST; our caller only asked about one of them. */
959   apr_array_header_t *paths = baton;
960   APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path);
961   return SVN_NO_ERROR;
962 }
963 
964 
965 svn_error_t *
svn_cl__changelist_paths(apr_array_header_t ** paths,const apr_array_header_t * changelists,const apr_array_header_t * targets,svn_depth_t depth,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)966 svn_cl__changelist_paths(apr_array_header_t **paths,
967                          const apr_array_header_t *changelists,
968                          const apr_array_header_t *targets,
969                          svn_depth_t depth,
970                          svn_client_ctx_t *ctx,
971                          apr_pool_t *result_pool,
972                          apr_pool_t *scratch_pool)
973 {
974   apr_array_header_t *found;
975   apr_hash_t *paths_hash;
976   apr_pool_t *iterpool;
977   int i;
978 
979   if (! (changelists && changelists->nelts))
980     {
981       *paths = (apr_array_header_t *)targets;
982       return SVN_NO_ERROR;
983     }
984 
985   found = apr_array_make(scratch_pool, 8, sizeof(const char *));
986   iterpool = svn_pool_create(scratch_pool);
987   for (i = 0; i < targets->nelts; i++)
988     {
989       const char *target = APR_ARRAY_IDX(targets, i, const char *);
990       svn_pool_clear(iterpool);
991       SVN_ERR(svn_client_get_changelists(target, changelists, depth,
992                                          changelist_receiver, found,
993                                          ctx, iterpool));
994     }
995   svn_pool_destroy(iterpool);
996 
997   SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool));
998   return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool));
999 }
1000 
1001 svn_cl__show_revs_t
svn_cl__show_revs_from_word(const char * word)1002 svn_cl__show_revs_from_word(const char *word)
1003 {
1004   if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0)
1005     return svn_cl__show_revs_merged;
1006   if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0)
1007     return svn_cl__show_revs_eligible;
1008   /* word is an invalid flavor. */
1009   return svn_cl__show_revs_invalid;
1010 }
1011 
1012 
1013 svn_error_t *
svn_cl__time_cstring_to_human_cstring(const char ** human_cstring,const char * data,apr_pool_t * pool)1014 svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
1015                                       const char *data,
1016                                       apr_pool_t *pool)
1017 {
1018   svn_error_t *err;
1019   apr_time_t when;
1020 
1021   err = svn_time_from_cstring(&when, data, pool);
1022   if (err && err->apr_err == SVN_ERR_BAD_DATE)
1023     {
1024       svn_error_clear(err);
1025 
1026       *human_cstring = _("(invalid date)");
1027       return SVN_NO_ERROR;
1028     }
1029   else if (err)
1030     return svn_error_trace(err);
1031 
1032   *human_cstring = svn_time_to_human_cstring(when, pool);
1033 
1034   return SVN_NO_ERROR;
1035 }
1036 
1037 const char *
svn_cl__node_description(const char * repos_root_url,const char * repos_relpath,svn_revnum_t peg_rev,svn_node_kind_t node_kind,const char * wc_repos_root_URL,apr_pool_t * pool)1038 svn_cl__node_description(const char *repos_root_url,
1039                          const char *repos_relpath,
1040                          svn_revnum_t peg_rev,
1041                          svn_node_kind_t node_kind,
1042                          const char *wc_repos_root_URL,
1043                          apr_pool_t *pool)
1044 {
1045   const char *root_str = "^";
1046   const char *path_str = "...";
1047 
1048   if (!repos_root_url || !repos_relpath || !SVN_IS_VALID_REVNUM(peg_rev))
1049     /* Printing "(none)" the harder way to ensure conformity (mostly with
1050      * translations). */
1051     return apr_psprintf(pool, "(%s)",
1052                         svn_cl__node_kind_str_human_readable(svn_node_none));
1053 
1054   /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL.
1055    * Otherwise show the complete URL, and if we can't, show dots. */
1056 
1057   if (repos_root_url &&
1058       (wc_repos_root_URL == NULL ||
1059        strcmp(repos_root_url, wc_repos_root_URL) != 0))
1060     root_str = repos_root_url;
1061 
1062   if (repos_relpath)
1063     path_str = repos_relpath;
1064 
1065   return apr_psprintf(pool, "(%s) %s@%ld",
1066                       svn_cl__node_kind_str_human_readable(node_kind),
1067                       svn_path_url_add_component2(root_str, path_str, pool),
1068                       peg_rev);
1069 }
1070 
1071 svn_error_t *
svn_cl__eat_peg_revisions(apr_array_header_t ** true_targets_p,const apr_array_header_t * targets,apr_pool_t * pool)1072 svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
1073                           const apr_array_header_t *targets,
1074                           apr_pool_t *pool)
1075 {
1076   int i;
1077   apr_array_header_t *true_targets;
1078 
1079   true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
1080 
1081   for (i = 0; i < targets->nelts; i++)
1082     {
1083       const char *target = APR_ARRAY_IDX(targets, i, const char *);
1084       const char *true_target, *peg;
1085 
1086       SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg,
1087                                                  target, pool));
1088       if (peg[0] && peg[1])
1089         return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1090                                  _("'%s': a peg revision is not allowed here"),
1091                                  target);
1092       APR_ARRAY_PUSH(true_targets, const char *) = true_target;
1093     }
1094 
1095   SVN_ERR_ASSERT(true_targets_p);
1096   *true_targets_p = true_targets;
1097 
1098   return SVN_NO_ERROR;
1099 }
1100 
1101 svn_error_t *
svn_cl__assert_homogeneous_target_type(const apr_array_header_t * targets)1102 svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets)
1103 {
1104   svn_error_t *err;
1105 
1106   err = svn_client__assert_homogeneous_target_type(targets);
1107   if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
1108     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL);
1109   return err;
1110 }
1111 
1112 svn_error_t *
svn_cl__check_target_is_local_path(const char * target)1113 svn_cl__check_target_is_local_path(const char *target)
1114 {
1115   if (svn_path_is_url(target))
1116     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1117                              _("'%s' is not a local path"), target);
1118   return SVN_NO_ERROR;
1119 }
1120 
1121 svn_error_t *
svn_cl__check_targets_are_local_paths(const apr_array_header_t * targets)1122 svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets)
1123 {
1124   int i;
1125 
1126   for (i = 0; i < targets->nelts; i++)
1127     {
1128       const char *target = APR_ARRAY_IDX(targets, i, const char *);
1129 
1130       SVN_ERR(svn_cl__check_target_is_local_path(target));
1131     }
1132   return SVN_NO_ERROR;
1133 }
1134 
1135 const char *
svn_cl__local_style_skip_ancestor(const char * parent_path,const char * path,apr_pool_t * pool)1136 svn_cl__local_style_skip_ancestor(const char *parent_path,
1137                                   const char *path,
1138                                   apr_pool_t *pool)
1139 {
1140   const char *relpath = NULL;
1141 
1142   if (parent_path)
1143     relpath = svn_dirent_skip_ancestor(parent_path, path);
1144 
1145   return svn_dirent_local_style(relpath ? relpath : path, pool);
1146 }
1147 
1148 svn_error_t *
svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t * targets,const char * propname,const svn_string_t * propval,apr_pool_t * scratch_pool)1149 svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
1150                                                const char *propname,
1151                                                const svn_string_t *propval,
1152                                                apr_pool_t *scratch_pool)
1153 {
1154   if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
1155     {
1156       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1157       int i;
1158 
1159       for (i = 0; i < targets->nelts; i++)
1160         {
1161           const char *detected_mimetype;
1162           const char *target = APR_ARRAY_IDX(targets, i, const char *);
1163           const char *local_abspath;
1164           const svn_string_t *canon_propval;
1165           svn_node_kind_t node_kind;
1166 
1167           svn_pool_clear(iterpool);
1168 
1169           SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
1170           SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool));
1171           if (node_kind != svn_node_file)
1172             continue;
1173 
1174           SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval,
1175                                                propname, propval,
1176                                                local_abspath,
1177                                                svn_node_file,
1178                                                FALSE, NULL, NULL,
1179                                                iterpool));
1180 
1181           if (svn_mime_type_is_binary(canon_propval->data))
1182             {
1183               SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype,
1184                                               local_abspath, NULL,
1185                                               iterpool));
1186               if (detected_mimetype == NULL ||
1187                   !svn_mime_type_is_binary(detected_mimetype))
1188                 svn_error_clear(svn_cmdline_fprintf(stderr, iterpool,
1189                   _("svn: warning: '%s' is a binary mime-type but file '%s' "
1190                     "looks like text; diff, merge, blame, and other "
1191                     "operations will stop working on this file\n"),
1192                     canon_propval->data,
1193                     svn_dirent_local_style(local_abspath, iterpool)));
1194 
1195             }
1196         }
1197       svn_pool_destroy(iterpool);
1198     }
1199 
1200   return SVN_NO_ERROR;
1201 }
1202 
1203