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