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