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