2 * util.c: Subversion command line client utility functions. Any
3 * functions that need to be shared across subcommands should be put
6 * ====================================================================
7 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
9 * This software is licensed as described in the file COPYING, which
10 * you should have received as part of this distribution. The terms
11 * are also available at http://subversion.tigris.org/license-1.html.
12 * If newer versions of this license are posted there, you may use a
13 * newer version instead, at your option.
15 * This software consists of voluntary contributions made by many
16 * individuals. For exact contribution history, see the revision
17 * history and logs, available at http://subversion.tigris.org/.
18 * ====================================================================
21 /* ==================================================================== */
32 #include <apr_errno.h>
33 #include <apr_file_info.h>
34 #include <apr_strings.h>
35 #include <apr_tables.h>
36 #include <apr_general.h>
39 #include "svn_pools.h"
40 #include "svn_error.h"
41 #include "svn_ctype.h"
42 #include "svn_client.h"
43 #include "svn_cmdline.h"
44 #include "svn_string.h"
49 #include "svn_subst.h"
50 #include "svn_config.h"
52 #include "svn_private_config.h"
59 svn_cl__print_commit_info(svn_commit_info_t
*commit_info
,
62 if (SVN_IS_VALID_REVNUM(commit_info
->revision
))
63 SVN_ERR(svn_cmdline_printf(pool
, _("\nCommitted revision %ld.\n"),
64 commit_info
->revision
));
66 /* Writing to stdout, as there maybe systems that consider the
67 * presence of stderr as an indication of commit failure.
68 * OTOH, this is only of informational nature to the user as
69 * the commit has succeeded. */
70 if (commit_info
->post_commit_err
)
71 SVN_ERR(svn_cmdline_printf(pool
, _("\nWarning: %s\n"),
72 commit_info
->post_commit_err
));
78 /* Helper for the next two functions. Set *EDITOR to some path to an
79 editor binary. Sources to search include: the EDITOR_CMD argument
80 (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG
81 is not NULL), $VISUAL, $EDITOR. Return
82 SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */
84 find_editor_binary(const char **editor
,
85 const char *editor_cmd
,
89 struct svn_config_t
*cfg
;
91 /* Use the editor specified on the command line via --editor-cmd, if any. */
94 /* Otherwise look for the Subversion-specific environment variable. */
96 e
= getenv("SVN_EDITOR");
98 /* If not found then fall back on the config file. */
101 cfg
= config
? apr_hash_get(config
, SVN_CONFIG_CATEGORY_CONFIG
,
102 APR_HASH_KEY_STRING
) : NULL
;
103 svn_config_get(cfg
, &e
, SVN_CONFIG_SECTION_HELPERS
,
104 SVN_CONFIG_OPTION_EDITOR_CMD
, NULL
);
107 /* If not found yet then try general purpose environment variables. */
109 e
= getenv("VISUAL");
111 e
= getenv("EDITOR");
113 #ifdef SVN_CLIENT_EDITOR
114 /* If still not found then fall back on the hard-coded default. */
116 e
= SVN_CLIENT_EDITOR
;
119 /* Error if there is no editor specified */
125 if (!svn_ctype_isspace(*c
))
129 return svn_error_create
130 (SVN_ERR_CL_NO_EXTERNAL_EDITOR
, NULL
,
131 _("The EDITOR, SVN_EDITOR or VISUAL environment variable or "
132 "'editor-cmd' run-time configuration option is empty or "
133 "consists solely of whitespace. Expected a shell command."));
136 return svn_error_create
137 (SVN_ERR_CL_NO_EXTERNAL_EDITOR
, NULL
,
138 _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are "
139 "set, and no 'editor-cmd' run-time configuration option was found"));
146 /* Use the visual editor to edit files. This requires that the file name itself
147 be shell-safe, although the path to reach that file need not be shell-safe.
150 svn_cl__edit_file_externally(const char *path
,
151 const char *editor_cmd
,
155 const char *editor
, *cmd
, *base_dir
, *file_name
, *base_dir_apr
;
158 apr_status_t apr_err
;
160 svn_path_split(path
, &base_dir
, &file_name
, pool
);
162 SVN_ERR(find_editor_binary(&editor
, editor_cmd
, config
));
164 apr_err
= apr_filepath_get(&old_cwd
, APR_FILEPATH_NATIVE
, pool
);
166 return svn_error_wrap_apr(apr_err
, _("Can't get working directory"));
168 /* APR doesn't like "" directories */
169 if (base_dir
[0] == '\0')
172 SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr
, base_dir
, pool
));
174 apr_err
= apr_filepath_set(base_dir_apr
, pool
);
176 return svn_error_wrap_apr
177 (apr_err
, _("Can't change working directory to '%s'"), base_dir
);
179 cmd
= apr_psprintf(pool
, "%s %s", editor
, file_name
);
180 sys_err
= system(cmd
);
182 apr_err
= apr_filepath_set(old_cwd
, pool
);
184 svn_handle_error2(svn_error_wrap_apr
185 (apr_err
, _("Can't restore working directory")),
186 stderr
, TRUE
/* fatal */, "svn: ");
189 /* Extracting any meaning from sys_err is platform specific, so just
190 use the raw value. */
191 return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM
, NULL
,
192 _("system('%s') returned %d"), cmd
, sys_err
);
198 svn_cl__merge_file_externally(const char *base_path
,
199 const char *their_path
,
201 const char *merged_path
,
206 /* Error if there is no editor specified */
207 if (apr_env_get(&merge_tool
, "SVN_MERGE", pool
) != APR_SUCCESS
)
209 struct svn_config_t
*cfg
;
211 cfg
= config
? apr_hash_get(config
, SVN_CONFIG_CATEGORY_CONFIG
,
212 APR_HASH_KEY_STRING
) : NULL
;
213 /* apr_env_get wants char **, this wants const char ** */
214 svn_config_get(cfg
, (const char **)&merge_tool
,
215 SVN_CONFIG_SECTION_HELPERS
,
216 SVN_CONFIG_OPTION_MERGE_TOOL_CMD
, NULL
);
223 for (c
= merge_tool
; *c
; c
++)
224 if (!svn_ctype_isspace(*c
))
228 return svn_error_create
229 (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL
, NULL
,
230 _("The SVN_MERGE environment variable is empty or "
231 "consists solely of whitespace. Expected a shell command.\n"));
234 return svn_error_create
235 (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL
, NULL
,
236 _("The environment variable SVN_MERGE and the merge-tool-cmd run-time "
237 "configuration option were not set.\n"));
240 const char *arguments
[] = { merge_tool
, base_path
, their_path
,
241 my_path
, merged_path
, NULL
};
243 apr_status_t status
= apr_filepath_get(&cwd
, APR_FILEPATH_NATIVE
, pool
);
245 return svn_error_wrap_apr(status
, NULL
);
246 return svn_io_run_cmd(svn_path_internal_style(cwd
, pool
), merge_tool
,
247 arguments
, NULL
, NULL
, TRUE
, NULL
, NULL
, NULL
,
253 svn_cl__edit_string_externally(svn_string_t
**edited_contents
/* UTF-8! */,
254 const char **tmpfile_left
/* UTF-8! */,
255 const char *editor_cmd
,
256 const char *base_dir
/* UTF-8! */,
257 const svn_string_t
*contents
/* UTF-8! */,
260 svn_boolean_t as_text
,
261 const char *encoding
,
266 apr_file_t
*tmp_file
;
267 const char *tmpfile_name
;
268 const char *tmpfile_native
;
269 const char *tmpfile_apr
, *base_dir_apr
;
270 svn_string_t
*translated_contents
;
271 apr_status_t apr_err
, apr_err2
;
273 apr_finfo_t finfo_before
, finfo_after
;
274 svn_error_t
*err
= SVN_NO_ERROR
, *err2
;
277 svn_boolean_t remove_file
= TRUE
;
279 SVN_ERR(find_editor_binary(&editor
, editor_cmd
, config
));
281 /* Convert file contents from UTF-8/LF if desired. */
284 const char *translated
;
285 SVN_ERR(svn_subst_translate_cstring2(contents
->data
, &translated
,
288 translated_contents
= svn_string_create("", pool
);
290 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents
->data
,
291 translated
, encoding
, pool
));
293 SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents
->data
,
295 translated_contents
->len
= strlen(translated_contents
->data
);
298 translated_contents
= svn_string_dup(contents
, pool
);
300 /* Move to BASE_DIR to avoid getting characters that need quoting
302 apr_err
= apr_filepath_get(&old_cwd
, APR_FILEPATH_NATIVE
, pool
);
304 return svn_error_wrap_apr(apr_err
, _("Can't get working directory"));
306 /* APR doesn't like "" directories */
307 if (base_dir
[0] == '\0')
310 SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr
, base_dir
, pool
));
311 apr_err
= apr_filepath_set(base_dir_apr
, pool
);
314 return svn_error_wrap_apr
315 (apr_err
, _("Can't change working directory to '%s'"), base_dir
);
318 /*** From here on, any problems that occur require us to cd back!! ***/
320 /* Ask the working copy for a temporary file that starts with
322 err
= svn_io_open_unique_file2(&tmp_file
, &tmpfile_name
,
323 prefix
, ".tmp", svn_io_file_del_none
, pool
);
325 if (err
&& (APR_STATUS_IS_EACCES(err
->apr_err
) || err
->apr_err
== EROFS
))
327 const char *temp_dir_apr
;
329 svn_error_clear(err
);
331 SVN_ERR(svn_io_temp_dir(&base_dir
, pool
));
333 SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr
, base_dir
, pool
));
334 apr_err
= apr_filepath_set(temp_dir_apr
, pool
);
337 return svn_error_wrap_apr
338 (apr_err
, _("Can't change working directory to '%s'"), base_dir
);
341 err
= svn_io_open_unique_file2(&tmp_file
, &tmpfile_name
,
343 svn_io_file_del_none
, pool
);
349 /*** From here on, any problems that occur require us to cleanup
350 the file we just created!! ***/
352 /* Dump initial CONTENTS to TMP_FILE. */
353 apr_err
= apr_file_write_full(tmp_file
, translated_contents
->data
,
354 translated_contents
->len
, &written
);
356 apr_err2
= apr_file_close(tmp_file
);
360 /* Make sure the whole CONTENTS were written, else return an error. */
363 err
= svn_error_wrap_apr(apr_err
, _("Can't write to '%s'"),
368 err
= svn_path_cstring_from_utf8(&tmpfile_apr
, tmpfile_name
, pool
);
372 /* Get information about the temporary file before the user has
373 been allowed to edit its contents. */
374 apr_err
= apr_stat(&finfo_before
, tmpfile_apr
,
375 APR_FINFO_MTIME
, pool
);
378 err
= svn_error_wrap_apr(apr_err
, _("Can't stat '%s'"), tmpfile_name
);
382 /* Backdate the file a little bit in case the editor is very fast
383 and doesn't change the size. (Use two seconds, since some
384 filesystems have coarse granularity.) It's OK if this call
385 fails, so we don't check its return value.*/
386 apr_file_mtime_set(tmpfile_apr
, finfo_before
.mtime
- 2000, pool
);
388 /* Stat it again to get the mtime we actually set. */
389 apr_err
= apr_stat(&finfo_before
, tmpfile_apr
,
390 APR_FINFO_MTIME
| APR_FINFO_SIZE
, pool
);
393 err
= svn_error_wrap_apr(apr_err
, _("Can't stat '%s'"), tmpfile_name
);
397 /* Now, run the editor command line. */
398 err
= svn_utf_cstring_from_utf8(&tmpfile_native
, tmpfile_name
, pool
);
401 cmd
= apr_psprintf(pool
, "%s %s", editor
, tmpfile_native
);
402 sys_err
= system(cmd
);
405 /* Extracting any meaning from sys_err is platform specific, so just
406 use the raw value. */
407 err
= svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM
, NULL
,
408 _("system('%s') returned %d"), cmd
, sys_err
);
412 /* Get information about the temporary file after the assumed editing. */
413 apr_err
= apr_stat(&finfo_after
, tmpfile_apr
,
414 APR_FINFO_MTIME
| APR_FINFO_SIZE
, pool
);
417 err
= svn_error_wrap_apr(apr_err
, _("Can't stat '%s'"), tmpfile_name
);
421 /* If the caller wants us to leave the file around, return the path
422 of the file we used, and make a note not to destroy it. */
425 *tmpfile_left
= svn_path_join(base_dir
, tmpfile_name
, pool
);
429 /* If the file looks changed... */
430 if ((finfo_before
.mtime
!= finfo_after
.mtime
) ||
431 (finfo_before
.size
!= finfo_after
.size
))
433 svn_stringbuf_t
*edited_contents_s
;
434 err
= svn_stringbuf_from_file(&edited_contents_s
, tmpfile_name
, pool
);
438 *edited_contents
= svn_string_create_from_buf(edited_contents_s
, pool
);
440 /* Translate back to UTF8/LF if desired. */
443 err
= svn_subst_translate_string(edited_contents
, *edited_contents
,
447 err
= svn_error_quick_wrap
449 _("Error normalizing edited contents to internal format"));
456 /* No edits seem to have been made */
457 *edited_contents
= NULL
;
463 /* Remove the file from disk. */
464 err2
= svn_io_remove_file(tmpfile_name
, pool
);
466 /* Only report remove error if there was no previous error. */
470 svn_error_clear(err2
);
474 /* If we against all probability can't cd back, all further relative
475 file references would be screwed up, so we have to abort. */
476 apr_err
= apr_filepath_set(old_cwd
, pool
);
479 svn_handle_error2(svn_error_wrap_apr
480 (apr_err
, _("Can't restore working directory")),
481 stderr
, TRUE
/* fatal */, "svn: ");
488 /* A svn_client_ctx_t's log_msg_baton3, for use with
489 svn_cl__make_log_msg_baton(). */
492 const char *editor_cmd
; /* editor specified via --editor-cmd, else NULL */
493 const char *message
; /* the message. */
494 const char *message_encoding
; /* the locale/encoding of the message. */
495 const char *base_dir
; /* the base directory for an external edit. UTF-8! */
496 const char *tmpfile_left
; /* the tmpfile left by an external edit. UTF-8! */
497 svn_boolean_t non_interactive
; /* if true, don't pop up an editor */
498 apr_hash_t
*config
; /* client configuration hash */
499 svn_boolean_t keep_locks
; /* Keep repository locks? */
500 apr_pool_t
*pool
; /* a pool. */
505 svn_cl__make_log_msg_baton(void **baton
,
506 svn_cl__opt_state_t
*opt_state
,
507 const char *base_dir
/* UTF-8! */,
511 struct log_msg_baton
*lmb
= apr_palloc(pool
, sizeof(*lmb
));
513 if (opt_state
->filedata
)
515 if (strlen(opt_state
->filedata
->data
) < opt_state
->filedata
->len
)
517 /* The data contains a zero byte, and therefore can't be
518 represented as a C string. Punt now; it's probably not
519 a deliberate encoding, and even if it is, we still
521 return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE
, NULL
,
522 _("Log message contains a zero byte"));
524 lmb
->message
= opt_state
->filedata
->data
;
528 lmb
->message
= opt_state
->message
;
531 lmb
->editor_cmd
= opt_state
->editor_cmd
;
532 if (opt_state
->encoding
)
534 lmb
->message_encoding
= opt_state
->encoding
;
538 svn_config_t
*cfg
= apr_hash_get(config
, SVN_CONFIG_CATEGORY_CONFIG
,
539 APR_HASH_KEY_STRING
);
540 svn_config_get(cfg
, &(lmb
->message_encoding
),
541 SVN_CONFIG_SECTION_MISCELLANY
,
542 SVN_CONFIG_OPTION_LOG_ENCODING
,
546 lmb
->base_dir
= base_dir
? base_dir
: "";
547 lmb
->tmpfile_left
= NULL
;
548 lmb
->config
= config
;
549 lmb
->keep_locks
= opt_state
->no_unlock
;
550 lmb
->non_interactive
= opt_state
->non_interactive
;
558 svn_cl__cleanup_log_msg(void *log_msg_baton
,
559 svn_error_t
*commit_err
)
561 struct log_msg_baton
*lmb
= log_msg_baton
;
563 /* If there was no tmpfile left, or there is no log message baton,
564 return COMMIT_ERR. */
565 if ((! lmb
) || (! lmb
->tmpfile_left
))
568 /* If there was no commit error, cleanup the tmpfile and return. */
570 return svn_io_remove_file(lmb
->tmpfile_left
, lmb
->pool
);
572 /* There was a commit error; there is a tmpfile. Leave the tmpfile
573 around, and add message about its presence to the commit error
574 chain. Then return COMMIT_ERR. If the conversion from UTF-8 to
575 native encoding fails, we have to compose that error with the
576 commit error chain, too. */
579 svn_error_create(commit_err
->apr_err
,
580 svn_error_createf(commit_err
->apr_err
, NULL
,
581 " '%s'", lmb
->tmpfile_left
),
582 _("Your commit message was left in "
583 "a temporary file:")));
588 /* Remove line-starting PREFIX and everything after it from BUFFER.
589 If NEW_LEN is non-NULL, return the new length of BUFFER in
592 truncate_buffer_at_prefix(apr_size_t
*new_len
,
596 char *substring
= buffer
;
598 assert(buffer
&& prefix
);
600 /* Initialize *NEW_LEN. */
602 *new_len
= strlen(buffer
);
606 /* Find PREFIX in BUFFER. */
607 substring
= strstr(substring
, prefix
);
611 /* We found PREFIX. Is it really a PREFIX? Well, if it's the first
612 thing in the file, or if the character before it is a
613 line-terminator character, it sure is. */
614 if ((substring
== buffer
)
615 || (*(substring
- 1) == '\r')
616 || (*(substring
- 1) == '\n'))
620 *new_len
= substring
- buffer
;
624 /* Well, it wasn't really a prefix, so just advance by 1
625 character and continue. */
634 #define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--")
637 svn_cl__get_log_message(const char **log_msg
,
638 const char **tmp_file
,
639 const apr_array_header_t
*commit_items
,
643 svn_stringbuf_t
*default_msg
= NULL
;
644 struct log_msg_baton
*lmb
= baton
;
645 svn_stringbuf_t
*message
= NULL
;
647 /* Set default message. */
648 default_msg
= svn_stringbuf_create(APR_EOL_STR
, pool
);
649 svn_stringbuf_appendcstr(default_msg
, EDITOR_EOF_PREFIX
);
650 svn_stringbuf_appendcstr(default_msg
, APR_EOL_STR APR_EOL_STR
);
655 svn_string_t
*log_msg_string
= svn_string_create(lmb
->message
, pool
);
657 SVN_ERR_W(svn_subst_translate_string(&log_msg_string
, log_msg_string
,
658 lmb
->message_encoding
, pool
),
659 _("Error normalizing log message to internal format"));
661 *log_msg
= log_msg_string
->data
;
663 /* Trim incoming messages the EOF marker text and the junk that
665 truncate_buffer_at_prefix(NULL
, (char*)*log_msg
, EDITOR_EOF_PREFIX
);
670 /* OS400 supports only -F and -m for specifying log messages. */
672 return svn_error_create
673 (SVN_ERR_CL_NO_EXTERNAL_EDITOR
, NULL
,
674 _("Use of an external editor to fetch log message is not supported "
675 "on OS400; consider using the --message (-m) or --file (-F) "
679 if (! commit_items
->nelts
)
687 /* We still don't have a valid commit message. Use $EDITOR to
688 get one. Note that svn_cl__edit_externally will still return
689 a UTF-8'ized log message. */
691 svn_stringbuf_t
*tmp_message
= svn_stringbuf_dup(default_msg
, pool
);
692 svn_error_t
*err
= SVN_NO_ERROR
;
693 svn_string_t
*msg_string
= svn_string_create("", pool
);
695 for (i
= 0; i
< commit_items
->nelts
; i
++)
697 svn_client_commit_item3_t
*item
698 = APR_ARRAY_IDX(commit_items
, i
, svn_client_commit_item3_t
*);
699 const char *path
= item
->path
;
700 char text_mod
= '_', prop_mod
= ' ', unlock
= ' ';
707 if (path
&& lmb
->base_dir
)
708 path
= svn_path_is_child(lmb
->base_dir
, path
, pool
);
710 /* If still no path, then just use current directory. */
714 if ((item
->state_flags
& SVN_CLIENT_COMMIT_ITEM_DELETE
)
715 && (item
->state_flags
& SVN_CLIENT_COMMIT_ITEM_ADD
))
717 else if (item
->state_flags
& SVN_CLIENT_COMMIT_ITEM_ADD
)
719 else if (item
->state_flags
& SVN_CLIENT_COMMIT_ITEM_DELETE
)
721 else if (item
->state_flags
& SVN_CLIENT_COMMIT_ITEM_TEXT_MODS
)
724 if (item
->state_flags
& SVN_CLIENT_COMMIT_ITEM_PROP_MODS
)
727 if (! lmb
->keep_locks
728 && item
->state_flags
& SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN
)
731 svn_stringbuf_appendbytes(tmp_message
, &text_mod
, 1);
732 svn_stringbuf_appendbytes(tmp_message
, &prop_mod
, 1);
733 svn_stringbuf_appendbytes(tmp_message
, &unlock
, 1);
734 if (item
->state_flags
& SVN_CLIENT_COMMIT_ITEM_IS_COPY
)
735 /* History included via copy/move. */
736 svn_stringbuf_appendcstr(tmp_message
, "+ ");
738 svn_stringbuf_appendcstr(tmp_message
, " ");
739 svn_stringbuf_appendcstr(tmp_message
, path
);
740 svn_stringbuf_appendcstr(tmp_message
, APR_EOL_STR
);
743 msg_string
->data
= tmp_message
->data
;
744 msg_string
->len
= tmp_message
->len
;
746 /* Use the external edit to get a log message. */
747 if (! lmb
->non_interactive
)
749 err
= svn_cl__edit_string_externally(&msg_string
, &lmb
->tmpfile_left
,
750 lmb
->editor_cmd
, lmb
->base_dir
,
751 msg_string
, "svn-commit",
753 lmb
->message_encoding
,
756 else /* non_interactive flag says we can't pop up an editor, so error */
758 return svn_error_create
759 (SVN_ERR_CL_INSUFFICIENT_ARGS
, NULL
,
760 _("Cannot invoke editor to get log message "
761 "when non-interactive"));
764 /* Dup the tmpfile path into its baton's pool. */
765 *tmp_file
= lmb
->tmpfile_left
= apr_pstrdup(lmb
->pool
,
768 /* If the edit returned an error, handle it. */
771 if (err
->apr_err
== SVN_ERR_CL_NO_EXTERNAL_EDITOR
)
772 err
= svn_error_quick_wrap
773 (err
, _("Could not use external editor to fetch log message; "
774 "consider setting the $SVN_EDITOR environment variable "
775 "or using the --message (-m) or --file (-F) options"));
780 message
= svn_stringbuf_create_from_string(msg_string
, pool
);
782 /* Strip the prefix from the buffer. */
784 truncate_buffer_at_prefix(&message
->len
, message
->data
,
789 /* We did get message, now check if it is anything more than just
790 white space as we will consider white space only as empty */
793 for (len
= message
->len
- 1; len
>= 0; len
--)
795 /* FIXME: should really use an UTF-8 whitespace test
796 rather than apr_isspace, which is locale dependant */
797 if (! apr_isspace(message
->data
[len
]))
807 SVN_ERR(svn_cmdline_prompt_user
809 _("\nLog message unchanged or not specified\n"
810 "(a)bort, (c)ontinue, (e)dit :\n"), pool
));
813 char letter
= apr_tolower(reply
[0]);
815 /* If the user chooses to abort, we cleanup the
816 temporary file and exit the loop with a NULL
820 SVN_ERR(svn_io_remove_file(lmb
->tmpfile_left
, pool
));
821 *tmp_file
= lmb
->tmpfile_left
= NULL
;
825 /* If the user chooses to continue, we make an empty
826 message, which will cause us to exit the loop. We
827 also cleanup the temporary file. */
830 SVN_ERR(svn_io_remove_file(lmb
->tmpfile_left
, pool
));
831 *tmp_file
= lmb
->tmpfile_left
= NULL
;
832 message
= svn_stringbuf_create("", pool
);
835 /* If the user chooses anything else, the loop will
836 continue on the NULL message. */
841 *log_msg
= message
? message
->data
: NULL
;
846 /* ### The way our error wrapping currently works, the error returned
847 * from here will look as though it originates in this source file,
848 * instead of in the caller's source file. This can be a bit
849 * misleading, until one starts debugging. Ideally, there'd be a way
850 * to wrap an error while preserving its FILE/LINE info.
853 svn_cl__may_need_force(svn_error_t
*err
)
856 && (err
->apr_err
== SVN_ERR_UNVERSIONED_RESOURCE
||
857 err
->apr_err
== SVN_ERR_CLIENT_MODIFIED
))
859 /* Should this svn_error_compose a new error number? Probably not,
860 the error hasn't changed. */
861 err
= svn_error_quick_wrap
862 (err
, _("Use --force to override this restriction") );
870 svn_cl__error_checked_fputs(const char *string
, FILE* stream
)
872 /* This function is equal to svn_cmdline_fputs() minus
873 the utf8->local encoding translation */
875 /* On POSIX systems, errno will be set on an error in fputs, but this might
876 not be the case on other platforms. We reset errno and only
877 use it if it was set by the below fputs call. Else, we just return
881 if (fputs(string
, stream
) == EOF
)
884 return svn_error_wrap_apr(errno
, _("Write error"));
886 return svn_error_create(SVN_ERR_IO_WRITE_ERROR
, NULL
, NULL
);
894 svn_cl__try(svn_error_t
*err
,
895 svn_boolean_t
*success
,
901 apr_status_t apr_err
;
908 while ((apr_err
= va_arg(ap
, apr_status_t
)) != SVN_NO_ERROR
)
910 if (err
->apr_err
== apr_err
)
913 svn_handle_warning(stderr
, err
);
914 svn_error_clear(err
);
930 svn_cl__xml_tagged_cdata(svn_stringbuf_t
**sb
,
937 svn_xml_make_open_tag(sb
, pool
, svn_xml_protect_pcdata
,
939 svn_xml_escape_cdata_cstring(sb
, string
, pool
);
940 svn_xml_make_close_tag(sb
, pool
, tagname
);
946 svn_cl__print_xml_commit(svn_stringbuf_t
**sb
,
947 svn_revnum_t revision
,
953 svn_xml_make_open_tag(sb
, pool
, svn_xml_normal
, "commit",
955 apr_psprintf(pool
, "%ld", revision
), NULL
);
957 /* "<author>xx</author>" */
959 svn_cl__xml_tagged_cdata(sb
, pool
, "author", author
);
961 /* "<date>xx</date>" */
963 svn_cl__xml_tagged_cdata(sb
, pool
, "date", date
);
966 svn_xml_make_close_tag(sb
, pool
, "commit");
971 svn_cl__xml_print_header(const char *tagname
,
974 svn_stringbuf_t
*sb
= svn_stringbuf_create("", pool
);
976 /* <?xml version="1.0"?> */
977 svn_xml_make_header(&sb
, pool
);
980 svn_xml_make_open_tag(&sb
, pool
, svn_xml_normal
, tagname
, NULL
);
982 return svn_cl__error_checked_fputs(sb
->data
, stdout
);
987 svn_cl__xml_print_footer(const char *tagname
,
990 svn_stringbuf_t
*sb
= svn_stringbuf_create("", pool
);
993 svn_xml_make_close_tag(&sb
, pool
, tagname
);
994 return svn_cl__error_checked_fputs(sb
->data
, stdout
);
999 svn_cl__node_kind_str(svn_node_kind_t kind
)
1014 svn_cl__args_to_target_array_print_reserved(apr_array_header_t
**targets
,
1016 apr_array_header_t
*known_targets
,
1017 svn_client_ctx_t
*ctx
,
1020 svn_error_t
*error
= svn_client_args_to_target_array(targets
, os
,
1025 if (error
->apr_err
== SVN_ERR_RESERVED_FILENAME_SPECIFIED
)
1027 svn_handle_error2(error
, stderr
, FALSE
, "svn: Skipping argument: ");
1028 svn_error_clear(error
);
1033 return SVN_NO_ERROR
;
1037 /* Helper for svn_cl__get_changelist(); implements
1038 svn_changelist_receiver_t. */
1039 static svn_error_t
*
1040 changelist_receiver(void *baton
,
1042 const char *changelist
,
1045 /* No need to check CHANGELIST; our caller only asked about one of them. */
1046 apr_array_header_t
*paths
= baton
;
1047 APR_ARRAY_PUSH(paths
, const char *) = apr_pstrdup(paths
->pool
, path
);
1048 return SVN_NO_ERROR
;
1053 svn_cl__changelist_paths(apr_array_header_t
**paths
,
1054 const apr_array_header_t
*changelists
,
1055 const apr_array_header_t
*targets
,
1057 svn_client_ctx_t
*ctx
,
1060 apr_array_header_t
*found
;
1061 apr_pool_t
*subpool
= svn_pool_create(pool
);
1062 apr_hash_t
*paths_hash
;
1065 if (! (changelists
&& changelists
->nelts
))
1067 *paths
= (apr_array_header_t
*)targets
;
1068 return SVN_NO_ERROR
;
1071 found
= apr_array_make(pool
, 8, sizeof(const char *));
1072 for (i
= 0; i
< targets
->nelts
; i
++)
1074 const char *target
= APR_ARRAY_IDX(targets
, i
, const char *);
1075 svn_pool_clear(subpool
);
1076 SVN_ERR(svn_client_get_changelists(target
, changelists
, depth
,
1077 changelist_receiver
, (void *)found
,
1080 svn_pool_destroy(subpool
);
1082 SVN_ERR(svn_hash_from_cstring_keys(&paths_hash
, found
, pool
));
1083 return svn_hash_keys(paths
, paths_hash
, pool
);
1087 svn_cl__show_revs_from_word(const char *word
)
1089 if (strcmp(word
, SVN_CL__SHOW_REVS_MERGED
) == 0)
1090 return svn_cl__show_revs_merged
;
1091 if (strcmp(word
, SVN_CL__SHOW_REVS_ELIGIBLE
) == 0)
1092 return svn_cl__show_revs_eligible
;
1093 /* word is an invalid flavor. */
1094 return svn_cl__show_revs_invalid
;