]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/svn/util.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.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   "Relnotes:",
342   "Security:",
343   "Sponsored by:"
344 };
345
346 void
347 cleanmsg(apr_size_t *l, char *s)
348 {
349   int i;
350   char *pos;
351   char *kw;
352   char *p;
353   int empty;
354
355   for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++) {
356     pos = s;
357     while ((kw = strstr(pos, prefixes[i])) != NULL) {
358       /* Check to see if keyword is at start of line (or buffer) */
359       if (!(kw == s || kw[-1] == '\r' || kw[-1] == '\n')) {
360         pos = kw + 1;
361         continue;
362       }
363       p = kw + strlen(prefixes[i]);
364       empty = 1;
365       while (1) {
366         if (*p == ' ' || *p == '\t') {
367           p++;
368           continue;
369         }
370         if (*p == '\0' || *p == '\r' || *p == '\n')
371           break;
372         empty = 0;
373         break;
374       }
375       if (empty && (*p == '\r' || *p == '\n')) {
376         memmove(kw, p + 1, strlen(p + 1) + 1);
377         if (l)
378           *l -= (p + 1 - kw);
379       } else if (empty) {
380         *kw = '\0';
381         if (l)
382           *l -= (p - kw);
383       } else {
384         pos = p;
385       }
386     }
387   }
388 }
389
390 #define EDITOR_EOF_PREFIX  _("--This line, and those below, will be ignored--")
391
392 svn_error_t *
393 svn_cl__get_log_message(const char **log_msg,
394                         const char **tmp_file,
395                         const apr_array_header_t *commit_items,
396                         void *baton,
397                         apr_pool_t *pool)
398 {
399   svn_stringbuf_t *default_msg = NULL;
400   struct log_msg_baton *lmb = baton;
401   svn_stringbuf_t *message = NULL;
402
403   /* Set default message.  */
404   default_msg = svn_stringbuf_create(APR_EOL_STR, pool);
405   svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
406   svn_stringbuf_appendcstr(default_msg, "PR:\t\t" APR_EOL_STR);
407   svn_stringbuf_appendcstr(default_msg, "Submitted by:\t" APR_EOL_STR);
408   svn_stringbuf_appendcstr(default_msg, "Reviewed by:\t" APR_EOL_STR);
409   svn_stringbuf_appendcstr(default_msg, "Approved by:\t" APR_EOL_STR);
410   svn_stringbuf_appendcstr(default_msg, "Obtained from:\t" APR_EOL_STR);
411   svn_stringbuf_appendcstr(default_msg, "MFC after:\t" APR_EOL_STR);
412   svn_stringbuf_appendcstr(default_msg, "Relnotes:\t" APR_EOL_STR);
413   svn_stringbuf_appendcstr(default_msg, "Security:\t" APR_EOL_STR);
414   svn_stringbuf_appendcstr(default_msg, "Sponsored by:\t"
415 #ifdef HAS_ORGANIZATION_NAME
416       ORGANIZATION_NAME
417 #endif
418       APR_EOL_STR);
419   svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX);
420   svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
421   svn_stringbuf_appendcstr(default_msg, "> Description of fields to fill in above:                     76 columns --|" APR_EOL_STR);
422   svn_stringbuf_appendcstr(default_msg, "> PR:            If a Bugzilla PR is affected by the change." APR_EOL_STR);
423   svn_stringbuf_appendcstr(default_msg, "> Submitted by:  If someone else sent in the change." APR_EOL_STR);
424   svn_stringbuf_appendcstr(default_msg, "> Reviewed by:   If someone else reviewed your modification." APR_EOL_STR);
425   svn_stringbuf_appendcstr(default_msg, "> Approved by:   If you needed approval for this commit." APR_EOL_STR);
426   svn_stringbuf_appendcstr(default_msg, "> Obtained from: If the change is from a third party." APR_EOL_STR);
427   svn_stringbuf_appendcstr(default_msg, "> MFC after:     N [day[s]|week[s]|month[s]].  Request a reminder email." APR_EOL_STR);
428   svn_stringbuf_appendcstr(default_msg, "> Relnotes:      Set to 'yes' for mention in release notes." APR_EOL_STR);
429   svn_stringbuf_appendcstr(default_msg, "> Security:      Vulnerability reference (one per line) or description." APR_EOL_STR);
430   svn_stringbuf_appendcstr(default_msg, "> Sponsored by:  If the change was sponsored by an organization." APR_EOL_STR);
431   svn_stringbuf_appendcstr(default_msg, "> Empty fields above will be automatically removed." APR_EOL_STR);
432   svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
433
434   *tmp_file = NULL;
435   if (lmb->message)
436     {
437       svn_stringbuf_t *log_msg_buf = svn_stringbuf_create(lmb->message, pool);
438       svn_string_t *log_msg_str = apr_pcalloc(pool, sizeof(*log_msg_str));
439
440       /* Trim incoming messages of the EOF marker text and the junk
441          that follows it.  */
442       truncate_buffer_at_prefix(&(log_msg_buf->len), log_msg_buf->data,
443                                 EDITOR_EOF_PREFIX);
444       cleanmsg(NULL, (char*)log_msg_buf->data);
445
446       /* Make a string from a stringbuf, sharing the data allocation. */
447       log_msg_str->data = log_msg_buf->data;
448       log_msg_str->len = log_msg_buf->len;
449       SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, FALSE, FALSE,
450                                             log_msg_str, lmb->message_encoding,
451                                             FALSE, pool, pool),
452                 _("Error normalizing log message to internal format"));
453
454       *log_msg = log_msg_str->data;
455       return SVN_NO_ERROR;
456     }
457
458   if (! commit_items->nelts)
459     {
460       *log_msg = "";
461       return SVN_NO_ERROR;
462     }
463
464   while (! message)
465     {
466       /* We still don't have a valid commit message.  Use $EDITOR to
467          get one.  Note that svn_cl__edit_string_externally will still
468          return a UTF-8'ized log message. */
469       int i;
470       svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
471       svn_error_t *err = SVN_NO_ERROR;
472       svn_string_t *msg_string = svn_string_create_empty(pool);
473
474       for (i = 0; i < commit_items->nelts; i++)
475         {
476           svn_client_commit_item3_t *item
477             = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
478           const char *path = item->path;
479           char text_mod = '_', prop_mod = ' ', unlock = ' ';
480
481           if (! path)
482             path = item->url;
483           else if (! *path)
484             path = ".";
485
486           if (! svn_path_is_url(path) && lmb->base_dir)
487             path = svn_dirent_is_child(lmb->base_dir, path, pool);
488
489           /* If still no path, then just use current directory. */
490           if (! path)
491             path = ".";
492
493           if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
494               && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
495             text_mod = 'R';
496           else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
497             text_mod = 'A';
498           else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
499             text_mod = 'D';
500           else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
501             text_mod = 'M';
502
503           if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
504             prop_mod = 'M';
505
506           if (! lmb->keep_locks
507               && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
508             unlock = 'U';
509
510           svn_stringbuf_appendbyte(tmp_message, text_mod);
511           svn_stringbuf_appendbyte(tmp_message, prop_mod);
512           svn_stringbuf_appendbyte(tmp_message, unlock);
513           if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
514             /* History included via copy/move. */
515             svn_stringbuf_appendcstr(tmp_message, "+ ");
516           else
517             svn_stringbuf_appendcstr(tmp_message, "  ");
518           svn_stringbuf_appendcstr(tmp_message, path);
519           svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR);
520         }
521
522       msg_string->data = tmp_message->data;
523       msg_string->len = tmp_message->len;
524
525       /* Use the external edit to get a log message. */
526       if (! lmb->non_interactive)
527         {
528           err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
529                                                     lmb->editor_cmd, lmb->base_dir,
530                                                     msg_string, "svn-commit",
531                                                     lmb->config, TRUE,
532                                                     lmb->message_encoding,
533                                                     pool);
534         }
535       else /* non_interactive flag says we can't pop up an editor, so error */
536         {
537           return svn_error_create
538             (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
539              _("Cannot invoke editor to get log message "
540                "when non-interactive"));
541         }
542
543       /* Dup the tmpfile path into its baton's pool. */
544       *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool,
545                                                   lmb->tmpfile_left);
546
547       /* If the edit returned an error, handle it. */
548       if (err)
549         {
550           if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)
551             err = svn_error_quick_wrap
552               (err, _("Could not use external editor to fetch log message; "
553                       "consider setting the $SVN_EDITOR environment variable "
554                       "or using the --message (-m) or --file (-F) options"));
555           return svn_error_trace(err);
556         }
557
558       if (msg_string)
559         message = svn_stringbuf_create_from_string(msg_string, pool);
560
561       /* Strip the prefix from the buffer. */
562       if (message)
563         truncate_buffer_at_prefix(&message->len, message->data,
564                                   EDITOR_EOF_PREFIX);
565       /*
566        * Since we're adding freebsd-specific tokens to the log message,
567        * clean out any leftovers to avoid accidently sending them to other
568        * projects that won't be expecting them.
569        */
570       if (message)
571         cleanmsg(&message->len, message->data);
572
573       if (message)
574         {
575           /* We did get message, now check if it is anything more than just
576              white space as we will consider white space only as empty */
577           apr_size_t len;
578
579           for (len = 0; len < message->len; len++)
580             {
581               /* FIXME: should really use an UTF-8 whitespace test
582                  rather than svn_ctype_isspace, which is ASCII only */
583               if (! svn_ctype_isspace(message->data[len]))
584                 break;
585             }
586           if (len == message->len)
587             message = NULL;
588         }
589
590       if (! message)
591         {
592           const char *reply;
593           SVN_ERR(svn_cmdline_prompt_user2
594                   (&reply,
595                    _("\nLog message unchanged or not specified\n"
596                      "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool));
597           if (reply)
598             {
599               int letter = apr_tolower(reply[0]);
600
601               /* If the user chooses to abort, we cleanup the
602                  temporary file and exit the loop with a NULL
603                  message. */
604               if ('a' == letter)
605                 {
606                   SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
607                   *tmp_file = lmb->tmpfile_left = NULL;
608                   break;
609                 }
610
611               /* If the user chooses to continue, we make an empty
612                  message, which will cause us to exit the loop.  We
613                  also cleanup the temporary file. */
614               if ('c' == letter)
615                 {
616                   SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
617                   *tmp_file = lmb->tmpfile_left = NULL;
618                   message = svn_stringbuf_create_empty(pool);
619                 }
620
621               /* If the user chooses anything else, the loop will
622                  continue on the NULL message. */
623             }
624         }
625     }
626
627   *log_msg = message ? message->data : NULL;
628   return SVN_NO_ERROR;
629 }
630
631
632 /* ### The way our error wrapping currently works, the error returned
633  * from here will look as though it originates in this source file,
634  * instead of in the caller's source file.  This can be a bit
635  * misleading, until one starts debugging.  Ideally, there'd be a way
636  * to wrap an error while preserving its FILE/LINE info.
637  */
638 svn_error_t *
639 svn_cl__may_need_force(svn_error_t *err)
640 {
641   if (err
642       && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE ||
643           err->apr_err == SVN_ERR_CLIENT_MODIFIED))
644     {
645       /* Should this svn_error_compose a new error number? Probably not,
646          the error hasn't changed. */
647       err = svn_error_quick_wrap
648         (err, _("Use --force to override this restriction (local modifications "
649          "may be lost)"));
650     }
651
652   return svn_error_trace(err);
653 }
654
655
656 svn_error_t *
657 svn_cl__error_checked_fputs(const char *string, FILE* stream)
658 {
659   /* On POSIX systems, errno will be set on an error in fputs, but this might
660      not be the case on other platforms.  We reset errno and only
661      use it if it was set by the below fputs call.  Else, we just return
662      a generic error. */
663   errno = 0;
664
665   if (fputs(string, stream) == EOF)
666     {
667       if (errno)
668         return svn_error_wrap_apr(errno, _("Write error"));
669       else
670         return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
671     }
672
673   return SVN_NO_ERROR;
674 }
675
676
677 svn_error_t *
678 svn_cl__try(svn_error_t *err,
679             apr_array_header_t *errors_seen,
680             svn_boolean_t quiet,
681             ...)
682 {
683   if (err)
684     {
685       apr_status_t apr_err;
686       va_list ap;
687
688       va_start(ap, quiet);
689       while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS)
690         {
691           if (errors_seen)
692             {
693               int i;
694               svn_boolean_t add = TRUE;
695
696               /* Don't report duplicate error codes. */
697               for (i = 0; i < errors_seen->nelts; i++)
698                 {
699                   if (APR_ARRAY_IDX(errors_seen, i,
700                                     apr_status_t) == err->apr_err)
701                     {
702                       add = FALSE;
703                       break;
704                     }
705                 }
706               if (add)
707                 APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err;
708             }
709           if (err->apr_err == apr_err)
710             {
711               if (! quiet)
712                 svn_handle_warning2(stderr, err, "svn: ");
713               svn_error_clear(err);
714               return SVN_NO_ERROR;
715             }
716         }
717       va_end(ap);
718     }
719
720   return svn_error_trace(err);
721 }
722
723
724 void
725 svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
726                          apr_pool_t *pool,
727                          const char *tagname,
728                          const char *string)
729 {
730   if (string)
731     {
732       svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata,
733                             tagname, NULL);
734       svn_xml_escape_cdata_cstring(sb, string, pool);
735       svn_xml_make_close_tag(sb, pool, tagname);
736     }
737 }
738
739
740 void
741 svn_cl__print_xml_commit(svn_stringbuf_t **sb,
742                          svn_revnum_t revision,
743                          const char *author,
744                          const char *date,
745                          apr_pool_t *pool)
746 {
747   /* "<commit ...>" */
748   svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit",
749                         "revision",
750                         apr_psprintf(pool, "%ld", revision), NULL);
751
752   /* "<author>xx</author>" */
753   if (author)
754     svn_cl__xml_tagged_cdata(sb, pool, "author", author);
755
756   /* "<date>xx</date>" */
757   if (date)
758     svn_cl__xml_tagged_cdata(sb, pool, "date", date);
759
760   /* "</commit>" */
761   svn_xml_make_close_tag(sb, pool, "commit");
762 }
763
764
765 void
766 svn_cl__print_xml_lock(svn_stringbuf_t **sb,
767                        const svn_lock_t *lock,
768                        apr_pool_t *pool)
769 {
770   /* "<lock>" */
771   svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", NULL);
772
773   /* "<token>xx</token>" */
774   svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token);
775
776   /* "<owner>xx</owner>" */
777   svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner);
778
779   /* "<comment>xx</comment>" */
780   svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment);
781
782   /* "<created>xx</created>" */
783   svn_cl__xml_tagged_cdata(sb, pool, "created",
784                            svn_time_to_cstring(lock->creation_date, pool));
785
786   /* "<expires>xx</expires>" */
787   if (lock->expiration_date != 0)
788     svn_cl__xml_tagged_cdata(sb, pool, "expires",
789                              svn_time_to_cstring(lock->expiration_date, pool));
790
791   /* "</lock>" */
792   svn_xml_make_close_tag(sb, pool, "lock");
793 }
794
795
796 svn_error_t *
797 svn_cl__xml_print_header(const char *tagname,
798                          apr_pool_t *pool)
799 {
800   svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
801
802   /* <?xml version="1.0" encoding="UTF-8"?> */
803   svn_xml_make_header2(&sb, "UTF-8", pool);
804
805   /* "<TAGNAME>" */
806   svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, NULL);
807
808   return svn_cl__error_checked_fputs(sb->data, stdout);
809 }
810
811
812 svn_error_t *
813 svn_cl__xml_print_footer(const char *tagname,
814                          apr_pool_t *pool)
815 {
816   svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
817
818   /* "</TAGNAME>" */
819   svn_xml_make_close_tag(&sb, pool, tagname);
820   return svn_cl__error_checked_fputs(sb->data, stdout);
821 }
822
823
824 /* A map for svn_node_kind_t values to XML strings */
825 static const svn_token_map_t map_node_kind_xml[] =
826 {
827   { "none", svn_node_none },
828   { "file", svn_node_file },
829   { "dir",  svn_node_dir },
830   { "",     svn_node_unknown },
831   { NULL,   0 }
832 };
833
834 /* A map for svn_node_kind_t values to human-readable strings */
835 static const svn_token_map_t map_node_kind_human[] =
836 {
837   { N_("none"), svn_node_none },
838   { N_("file"), svn_node_file },
839   { N_("dir"),  svn_node_dir },
840   { "",         svn_node_unknown },
841   { NULL,       0 }
842 };
843
844 const char *
845 svn_cl__node_kind_str_xml(svn_node_kind_t kind)
846 {
847   return svn_token__to_word(map_node_kind_xml, kind);
848 }
849
850 const char *
851 svn_cl__node_kind_str_human_readable(svn_node_kind_t kind)
852 {
853   return _(svn_token__to_word(map_node_kind_human, kind));
854 }
855
856
857 /* A map for svn_wc_operation_t values to XML strings */
858 static const svn_token_map_t map_wc_operation_xml[] =
859 {
860   { "none",   svn_wc_operation_none },
861   { "update", svn_wc_operation_update },
862   { "switch", svn_wc_operation_switch },
863   { "merge",  svn_wc_operation_merge },
864   { NULL,     0 }
865 };
866
867 /* A map for svn_wc_operation_t values to human-readable strings */
868 static const svn_token_map_t map_wc_operation_human[] =
869 {
870   { N_("none"),   svn_wc_operation_none },
871   { N_("update"), svn_wc_operation_update },
872   { N_("switch"), svn_wc_operation_switch },
873   { N_("merge"),  svn_wc_operation_merge },
874   { NULL,         0 }
875 };
876
877 const char *
878 svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool)
879 {
880   return svn_token__to_word(map_wc_operation_xml, operation);
881 }
882
883 const char *
884 svn_cl__operation_str_human_readable(svn_wc_operation_t operation,
885                                      apr_pool_t *pool)
886 {
887   return _(svn_token__to_word(map_wc_operation_human, operation));
888 }
889
890
891 svn_error_t *
892 svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
893                                             apr_getopt_t *os,
894                                             const apr_array_header_t *known_targets,
895                                             svn_client_ctx_t *ctx,
896                                             svn_boolean_t keep_last_origpath_on_truepath_collision,
897                                             apr_pool_t *pool)
898 {
899   svn_error_t *err = svn_client_args_to_target_array2(targets,
900                                                       os,
901                                                       known_targets,
902                                                       ctx,
903                                                       keep_last_origpath_on_truepath_collision,
904                                                       pool);
905   if (err)
906     {
907       if (err->apr_err ==  SVN_ERR_RESERVED_FILENAME_SPECIFIED)
908         {
909           svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
910           svn_error_clear(err);
911         }
912       else
913         return svn_error_trace(err);
914     }
915   return SVN_NO_ERROR;
916 }
917
918
919 /* Helper for svn_cl__get_changelist(); implements
920    svn_changelist_receiver_t. */
921 static svn_error_t *
922 changelist_receiver(void *baton,
923                     const char *path,
924                     const char *changelist,
925                     apr_pool_t *pool)
926 {
927   /* No need to check CHANGELIST; our caller only asked about one of them. */
928   apr_array_header_t *paths = baton;
929   APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path);
930   return SVN_NO_ERROR;
931 }
932
933
934 svn_error_t *
935 svn_cl__changelist_paths(apr_array_header_t **paths,
936                          const apr_array_header_t *changelists,
937                          const apr_array_header_t *targets,
938                          svn_depth_t depth,
939                          svn_client_ctx_t *ctx,
940                          apr_pool_t *result_pool,
941                          apr_pool_t *scratch_pool)
942 {
943   apr_array_header_t *found;
944   apr_hash_t *paths_hash;
945   apr_pool_t *iterpool;
946   int i;
947
948   if (! (changelists && changelists->nelts))
949     {
950       *paths = (apr_array_header_t *)targets;
951       return SVN_NO_ERROR;
952     }
953
954   found = apr_array_make(scratch_pool, 8, sizeof(const char *));
955   iterpool = svn_pool_create(scratch_pool);
956   for (i = 0; i < targets->nelts; i++)
957     {
958       const char *target = APR_ARRAY_IDX(targets, i, const char *);
959       svn_pool_clear(iterpool);
960       SVN_ERR(svn_client_get_changelists(target, changelists, depth,
961                                          changelist_receiver, found,
962                                          ctx, iterpool));
963     }
964   svn_pool_destroy(iterpool);
965
966   SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool));
967   return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool));
968 }
969
970 svn_cl__show_revs_t
971 svn_cl__show_revs_from_word(const char *word)
972 {
973   if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0)
974     return svn_cl__show_revs_merged;
975   if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0)
976     return svn_cl__show_revs_eligible;
977   /* word is an invalid flavor. */
978   return svn_cl__show_revs_invalid;
979 }
980
981
982 svn_error_t *
983 svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
984                                       const char *data,
985                                       apr_pool_t *pool)
986 {
987   svn_error_t *err;
988   apr_time_t when;
989
990   err = svn_time_from_cstring(&when, data, pool);
991   if (err && err->apr_err == SVN_ERR_BAD_DATE)
992     {
993       svn_error_clear(err);
994
995       *human_cstring = _("(invalid date)");
996       return SVN_NO_ERROR;
997     }
998   else if (err)
999     return svn_error_trace(err);
1000
1001   *human_cstring = svn_time_to_human_cstring(when, pool);
1002
1003   return SVN_NO_ERROR;
1004 }
1005
1006 const char *
1007 svn_cl__node_description(const svn_wc_conflict_version_t *node,
1008                          const char *wc_repos_root_URL,
1009                          apr_pool_t *pool)
1010 {
1011   const char *root_str = "^";
1012   const char *path_str = "...";
1013
1014   if (!node)
1015     /* Printing "(none)" the harder way to ensure conformity (mostly with
1016      * translations). */
1017     return apr_psprintf(pool, "(%s)",
1018                         svn_cl__node_kind_str_human_readable(svn_node_none));
1019
1020   /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL.
1021    * Otherwise show the complete URL, and if we can't, show dots. */
1022
1023   if (node->repos_url &&
1024       (wc_repos_root_URL == NULL ||
1025        strcmp(node->repos_url, wc_repos_root_URL) != 0))
1026     root_str = node->repos_url;
1027
1028   if (node->path_in_repos)
1029     path_str = node->path_in_repos;
1030
1031   return apr_psprintf(pool, "(%s) %s@%ld",
1032                       svn_cl__node_kind_str_human_readable(node->node_kind),
1033                       svn_path_url_add_component2(root_str, path_str, pool),
1034                       node->peg_rev);
1035 }
1036
1037 svn_error_t *
1038 svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
1039                           const apr_array_header_t *targets,
1040                           apr_pool_t *pool)
1041 {
1042   int i;
1043   apr_array_header_t *true_targets;
1044
1045   true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
1046
1047   for (i = 0; i < targets->nelts; i++)
1048     {
1049       const char *target = APR_ARRAY_IDX(targets, i, const char *);
1050       const char *true_target, *peg;
1051
1052       SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg,
1053                                                  target, pool));
1054       if (peg[0] && peg[1])
1055         return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1056                                  _("'%s': a peg revision is not allowed here"),
1057                                  target);
1058       APR_ARRAY_PUSH(true_targets, const char *) = true_target;
1059     }
1060
1061   SVN_ERR_ASSERT(true_targets_p);
1062   *true_targets_p = true_targets;
1063
1064   return SVN_NO_ERROR;
1065 }
1066
1067 svn_error_t *
1068 svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets)
1069 {
1070   svn_error_t *err;
1071
1072   err = svn_client__assert_homogeneous_target_type(targets);
1073   if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
1074     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL);
1075   return err;
1076 }
1077
1078 svn_error_t *
1079 svn_cl__check_target_is_local_path(const char *target)
1080 {
1081   if (svn_path_is_url(target))
1082     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1083                              _("'%s' is not a local path"), target);
1084   return SVN_NO_ERROR;
1085 }
1086
1087 svn_error_t *
1088 svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets)
1089 {
1090   int i;
1091
1092   for (i = 0; i < targets->nelts; i++)
1093     {
1094       const char *target = APR_ARRAY_IDX(targets, i, const char *);
1095
1096       SVN_ERR(svn_cl__check_target_is_local_path(target));
1097     }
1098   return SVN_NO_ERROR;
1099 }
1100
1101 const char *
1102 svn_cl__local_style_skip_ancestor(const char *parent_path,
1103                                   const char *path,
1104                                   apr_pool_t *pool)
1105 {
1106   const char *relpath = NULL;
1107
1108   if (parent_path)
1109     relpath = svn_dirent_skip_ancestor(parent_path, path);
1110
1111   return svn_dirent_local_style(relpath ? relpath : path, pool);
1112 }
1113
1114 svn_error_t *
1115 svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
1116                                                const char *propname,
1117                                                const svn_string_t *propval,
1118                                                apr_pool_t *scratch_pool)
1119 {
1120   if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
1121     {
1122       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1123       int i;
1124
1125       for (i = 0; i < targets->nelts; i++)
1126         {
1127           const char *detected_mimetype;
1128           const char *target = APR_ARRAY_IDX(targets, i, const char *);
1129           const char *local_abspath;
1130           const svn_string_t *canon_propval;
1131           svn_node_kind_t node_kind;
1132
1133           svn_pool_clear(iterpool);
1134
1135           SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
1136           SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool));
1137           if (node_kind != svn_node_file)
1138             continue;
1139
1140           SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval,
1141                                                propname, propval,
1142                                                local_abspath,
1143                                                svn_node_file,
1144                                                FALSE, NULL, NULL,
1145                                                iterpool));
1146
1147           if (svn_mime_type_is_binary(canon_propval->data))
1148             {
1149               SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype,
1150                                               local_abspath, NULL,
1151                                               iterpool));
1152               if (detected_mimetype == NULL ||
1153                   !svn_mime_type_is_binary(detected_mimetype))
1154                 svn_error_clear(svn_cmdline_fprintf(stderr, iterpool,
1155                   _("svn: warning: '%s' is a binary mime-type but file '%s' "
1156                     "looks like text; diff, merge, blame, and other "
1157                     "operations will stop working on this file\n"),
1158                     canon_propval->data,
1159                     svn_dirent_local_style(local_abspath, iterpool)));
1160
1161             }
1162         }
1163       svn_pool_destroy(iterpool);
1164     }
1165
1166   return SVN_NO_ERROR;
1167 }
1168