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