Followup to r29625: fix getopt tests.
[svn.git] / subversion / svn / util.c
blobe6c8c453b433acc4322c6d016c87f1f47a991025
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.
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 /* ==================================================================== */
25 /*** Includes. ***/
27 #include <string.h>
28 #include <ctype.h>
29 #include <assert.h>
31 #include <apr_env.h>
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>
37 #include <apr_lib.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"
45 #include "svn_path.h"
46 #include "svn_hash.h"
47 #include "svn_io.h"
48 #include "svn_utf.h"
49 #include "svn_subst.h"
50 #include "svn_config.h"
51 #include "svn_xml.h"
52 #include "svn_private_config.h"
53 #include "cl.h"
58 svn_error_t *
59 svn_cl__print_commit_info(svn_commit_info_t *commit_info,
60 apr_pool_t *pool)
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));
74 return SVN_NO_ERROR;
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. */
83 static svn_error_t *
84 find_editor_binary(const char **editor,
85 const char *editor_cmd,
86 apr_hash_t *config)
88 const char *e;
89 struct svn_config_t *cfg;
91 /* Use the editor specified on the command line via --editor-cmd, if any. */
92 e = editor_cmd;
94 /* Otherwise look for the Subversion-specific environment variable. */
95 if (! e)
96 e = getenv("SVN_EDITOR");
98 /* If not found then fall back on the config file. */
99 if (! e)
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. */
108 if (! e)
109 e = getenv("VISUAL");
110 if (! e)
111 e = getenv("EDITOR");
113 #ifdef SVN_CLIENT_EDITOR
114 /* If still not found then fall back on the hard-coded default. */
115 if (! e)
116 e = SVN_CLIENT_EDITOR;
117 #endif
119 /* Error if there is no editor specified */
120 if (e)
122 const char *c;
124 for (c = e; *c; c++)
125 if (!svn_ctype_isspace(*c))
126 break;
128 if (! *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."));
135 else
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"));
141 *editor = e;
142 return SVN_NO_ERROR;
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.
149 svn_error_t *
150 svn_cl__edit_file_externally(const char *path,
151 const char *editor_cmd,
152 apr_hash_t *config,
153 apr_pool_t *pool)
155 const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr;
156 char *old_cwd;
157 int sys_err;
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);
165 if (apr_err)
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')
170 base_dir_apr = ".";
171 else
172 SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
174 apr_err = apr_filepath_set(base_dir_apr, pool);
175 if (apr_err)
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);
183 if (apr_err)
184 svn_handle_error2(svn_error_wrap_apr
185 (apr_err, _("Can't restore working directory")),
186 stderr, TRUE /* fatal */, "svn: ");
188 if (sys_err)
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);
194 return SVN_NO_ERROR;
197 svn_error_t *
198 svn_cl__merge_file_externally(const char *base_path,
199 const char *their_path,
200 const char *my_path,
201 const char *merged_path,
202 apr_hash_t *config,
203 apr_pool_t *pool)
205 char *merge_tool;
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;
210 merge_tool = NULL;
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);
219 if (merge_tool)
221 const char *c;
223 for (c = merge_tool; *c; c++)
224 if (!svn_ctype_isspace(*c))
225 break;
227 if (! *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"));
233 else
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};
242 char *cwd;
243 apr_status_t status = apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, pool);
244 if (status != 0)
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,
248 pool);
252 svn_error_t *
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! */,
258 const char *prefix,
259 apr_hash_t *config,
260 svn_boolean_t as_text,
261 const char *encoding,
262 apr_pool_t *pool)
264 const char *editor;
265 const char *cmd;
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;
272 apr_size_t written;
273 apr_finfo_t finfo_before, finfo_after;
274 svn_error_t *err = SVN_NO_ERROR, *err2;
275 char *old_cwd;
276 int sys_err;
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. */
282 if (as_text)
284 const char *translated;
285 SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated,
286 APR_EOL_STR, FALSE,
287 NULL, FALSE, pool));
288 translated_contents = svn_string_create("", pool);
289 if (encoding)
290 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data,
291 translated, encoding, pool));
292 else
293 SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data,
294 translated, pool));
295 translated_contents->len = strlen(translated_contents->data);
297 else
298 translated_contents = svn_string_dup(contents, pool);
300 /* Move to BASE_DIR to avoid getting characters that need quoting
301 into tmpfile_name */
302 apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
303 if (apr_err)
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')
308 base_dir_apr = ".";
309 else
310 SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
311 apr_err = apr_filepath_set(base_dir_apr, pool);
312 if (apr_err)
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
321 PREFIX. */
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);
335 if (apr_err)
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,
342 prefix, ".tmp",
343 svn_io_file_del_none, pool);
346 if (err)
347 goto cleanup2;
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);
357 if (! apr_err)
358 apr_err = apr_err2;
360 /* Make sure the whole CONTENTS were written, else return an error. */
361 if (apr_err)
363 err = svn_error_wrap_apr(apr_err, _("Can't write to '%s'"),
364 tmpfile_name);
365 goto cleanup;
368 err = svn_path_cstring_from_utf8(&tmpfile_apr, tmpfile_name, pool);
369 if (err)
370 goto cleanup;
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);
376 if (apr_err)
378 err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
379 goto cleanup;
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);
391 if (apr_err)
393 err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
394 goto cleanup;
397 /* Now, run the editor command line. */
398 err = svn_utf_cstring_from_utf8(&tmpfile_native, tmpfile_name, pool);
399 if (err)
400 goto cleanup;
401 cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native);
402 sys_err = system(cmd);
403 if (sys_err != 0)
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);
409 goto cleanup;
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);
415 if (apr_err)
417 err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
418 goto cleanup;
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. */
423 if (tmpfile_left)
425 *tmpfile_left = svn_path_join(base_dir, tmpfile_name, pool);
426 remove_file = FALSE;
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);
435 if (err)
436 goto cleanup;
438 *edited_contents = svn_string_create_from_buf(edited_contents_s, pool);
440 /* Translate back to UTF8/LF if desired. */
441 if (as_text)
443 err = svn_subst_translate_string(edited_contents, *edited_contents,
444 encoding, pool);
445 if (err)
447 err = svn_error_quick_wrap
448 (err,
449 _("Error normalizing edited contents to internal format"));
450 goto cleanup;
454 else
456 /* No edits seem to have been made */
457 *edited_contents = NULL;
460 cleanup:
461 if (remove_file)
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. */
467 if (! err && err2)
468 err = err2;
469 else
470 svn_error_clear(err2);
473 cleanup2:
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);
477 if (apr_err)
479 svn_handle_error2(svn_error_wrap_apr
480 (apr_err, _("Can't restore working directory")),
481 stderr, TRUE /* fatal */, "svn: ");
484 return err;
488 /* A svn_client_ctx_t's log_msg_baton3, for use with
489 svn_cl__make_log_msg_baton(). */
490 struct 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. */
504 svn_error_t *
505 svn_cl__make_log_msg_baton(void **baton,
506 svn_cl__opt_state_t *opt_state,
507 const char *base_dir /* UTF-8! */,
508 apr_hash_t *config,
509 apr_pool_t *pool)
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
520 can't handle it. */
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;
526 else
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;
536 else if (config)
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,
543 NULL);
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;
551 lmb->pool = pool;
552 *baton = lmb;
553 return SVN_NO_ERROR;
557 svn_error_t *
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))
566 return commit_err;
568 /* If there was no commit error, cleanup the tmpfile and return. */
569 if (! commit_err)
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. */
577 svn_error_compose
578 (commit_err,
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:")));
584 return commit_err;
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
590 *NEW_LEN. */
591 static void
592 truncate_buffer_at_prefix(apr_size_t *new_len,
593 char *buffer,
594 const char *prefix)
596 char *substring = buffer;
598 assert(buffer && prefix);
600 /* Initialize *NEW_LEN. */
601 if (new_len)
602 *new_len = strlen(buffer);
604 while (1)
606 /* Find PREFIX in BUFFER. */
607 substring = strstr(substring, prefix);
608 if (! substring)
609 return;
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'))
618 *substring = '\0';
619 if (new_len)
620 *new_len = substring - buffer;
622 else if (substring)
624 /* Well, it wasn't really a prefix, so just advance by 1
625 character and continue. */
626 substring++;
630 /* NOTREACHED */
634 #define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--")
636 svn_error_t *
637 svn_cl__get_log_message(const char **log_msg,
638 const char **tmp_file,
639 const apr_array_header_t *commit_items,
640 void *baton,
641 apr_pool_t *pool)
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);
652 *tmp_file = NULL;
653 if (lmb->message)
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
664 follows it. */
665 truncate_buffer_at_prefix(NULL, (char*)*log_msg, EDITOR_EOF_PREFIX);
667 return SVN_NO_ERROR;
669 #ifdef AS400
670 /* OS400 supports only -F and -m for specifying log messages. */
671 else
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) "
676 "options"));
677 #endif
679 if (! commit_items->nelts)
681 *log_msg = "";
682 return SVN_NO_ERROR;
685 while (! message)
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. */
690 int i;
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 = ' ';
702 if (! path)
703 path = item->url;
704 else if (! *path)
705 path = ".";
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. */
711 if (! path)
712 path = ".";
714 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
715 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
716 text_mod = 'R';
717 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
718 text_mod = 'A';
719 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
720 text_mod = 'D';
721 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
722 text_mod = 'M';
724 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
725 prop_mod = 'M';
727 if (! lmb->keep_locks
728 && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
729 unlock = 'U';
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, "+ ");
737 else
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",
752 lmb->config, TRUE,
753 lmb->message_encoding,
754 pool);
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,
766 lmb->tmpfile_left);
768 /* If the edit returned an error, handle it. */
769 if (err)
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"));
776 return err;
779 if (msg_string)
780 message = svn_stringbuf_create_from_string(msg_string, pool);
782 /* Strip the prefix from the buffer. */
783 if (message)
784 truncate_buffer_at_prefix(&message->len, message->data,
785 EDITOR_EOF_PREFIX);
787 if (message)
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 */
791 int len;
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]))
798 break;
800 if (len < 0)
801 message = NULL;
804 if (! message)
806 const char *reply;
807 SVN_ERR(svn_cmdline_prompt_user
808 (&reply,
809 _("\nLog message unchanged or not specified\n"
810 "(a)bort, (c)ontinue, (e)dit :\n"), pool));
811 if (reply)
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
817 message. */
818 if ('a' == letter)
820 SVN_ERR(svn_io_remove_file(lmb->tmpfile_left, pool));
821 *tmp_file = lmb->tmpfile_left = NULL;
822 break;
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. */
828 if ('c' == letter)
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;
842 return SVN_NO_ERROR;
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.
852 svn_error_t *
853 svn_cl__may_need_force(svn_error_t *err)
855 if (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") );
865 return err;
869 svn_error_t *
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
878 a generic error. */
879 errno = 0;
881 if (fputs(string, stream) == EOF)
883 if (errno)
884 return svn_error_wrap_apr(errno, _("Write error"));
885 else
886 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
889 return SVN_NO_ERROR;
893 svn_error_t *
894 svn_cl__try(svn_error_t *err,
895 svn_boolean_t *success,
896 svn_boolean_t quiet,
897 ...)
899 if (err)
901 apr_status_t apr_err;
902 va_list ap;
904 if (success)
905 *success = FALSE;
907 va_start(ap, quiet);
908 while ((apr_err = va_arg(ap, apr_status_t)) != SVN_NO_ERROR)
910 if (err->apr_err == apr_err)
912 if (! quiet)
913 svn_handle_warning(stderr, err);
914 svn_error_clear(err);
915 return SVN_NO_ERROR;
918 va_end(ap);
920 else if (success)
922 *success = TRUE;
925 return err;
929 void
930 svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
931 apr_pool_t *pool,
932 const char *tagname,
933 const char *string)
935 if (string)
937 svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata,
938 tagname, NULL);
939 svn_xml_escape_cdata_cstring(sb, string, pool);
940 svn_xml_make_close_tag(sb, pool, tagname);
945 void
946 svn_cl__print_xml_commit(svn_stringbuf_t **sb,
947 svn_revnum_t revision,
948 const char *author,
949 const char *date,
950 apr_pool_t *pool)
952 /* "<commit ...>" */
953 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit",
954 "revision",
955 apr_psprintf(pool, "%ld", revision), NULL);
957 /* "<author>xx</author>" */
958 if (author)
959 svn_cl__xml_tagged_cdata(sb, pool, "author", author);
961 /* "<date>xx</date>" */
962 if (date)
963 svn_cl__xml_tagged_cdata(sb, pool, "date", date);
965 /* "</commit>" */
966 svn_xml_make_close_tag(sb, pool, "commit");
970 svn_error_t *
971 svn_cl__xml_print_header(const char *tagname,
972 apr_pool_t *pool)
974 svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
976 /* <?xml version="1.0"?> */
977 svn_xml_make_header(&sb, pool);
979 /* "<TAGNAME>" */
980 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, NULL);
982 return svn_cl__error_checked_fputs(sb->data, stdout);
986 svn_error_t *
987 svn_cl__xml_print_footer(const char *tagname,
988 apr_pool_t *pool)
990 svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
992 /* "</TAGNAME>" */
993 svn_xml_make_close_tag(&sb, pool, tagname);
994 return svn_cl__error_checked_fputs(sb->data, stdout);
998 const char *
999 svn_cl__node_kind_str(svn_node_kind_t kind)
1001 switch (kind)
1003 case svn_node_dir:
1004 return "dir";
1005 case svn_node_file:
1006 return "file";
1007 default:
1008 return "";
1013 svn_error_t *
1014 svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
1015 apr_getopt_t *os,
1016 apr_array_header_t *known_targets,
1017 apr_pool_t *pool)
1019 svn_error_t *error = svn_opt_args_to_target_array3(targets, os,
1020 known_targets, pool);
1021 if (error)
1023 if (error->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
1025 svn_handle_error2(error, stderr, FALSE, "svn: Skipping argument: ");
1026 svn_error_clear(error);
1028 else
1029 return error;
1031 return SVN_NO_ERROR;
1035 /* Helper for svn_cl__get_changelist(); implements
1036 svn_changelist_receiver_t. */
1037 static svn_error_t *
1038 changelist_receiver(void *baton,
1039 const char *path,
1040 const char *changelist,
1041 apr_pool_t *pool)
1043 /* No need to check CHANGELIST; our caller only asked about one of them. */
1044 apr_array_header_t *paths = baton;
1045 APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path);
1046 return SVN_NO_ERROR;
1050 svn_error_t *
1051 svn_cl__changelist_paths(apr_array_header_t **paths,
1052 const apr_array_header_t *changelists,
1053 const apr_array_header_t *targets,
1054 svn_depth_t depth,
1055 svn_client_ctx_t *ctx,
1056 apr_pool_t *pool)
1058 apr_array_header_t *found;
1059 apr_pool_t *subpool = svn_pool_create(pool);
1060 apr_hash_t *paths_hash;
1061 int i;
1063 if (! (changelists && changelists->nelts))
1065 *paths = (apr_array_header_t *)targets;
1066 return SVN_NO_ERROR;
1069 found = apr_array_make(pool, 8, sizeof(const char *));
1070 for (i = 0; i < targets->nelts; i++)
1072 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1073 svn_pool_clear(subpool);
1074 SVN_ERR(svn_client_get_changelists(target, changelists, depth,
1075 changelist_receiver, (void *)found,
1076 ctx, subpool));
1078 svn_pool_destroy(subpool);
1080 SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, pool));
1081 return svn_hash_keys(paths, paths_hash, pool);