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