1251881Speter/*
2251881Speter * util.c: Subversion command line client utility functions. Any
3251881Speter * functions that need to be shared across subcommands should be put
4251881Speter * in here.
5251881Speter *
6251881Speter * ====================================================================
7251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
8251881Speter *    or more contributor license agreements.  See the NOTICE file
9251881Speter *    distributed with this work for additional information
10251881Speter *    regarding copyright ownership.  The ASF licenses this file
11251881Speter *    to you under the Apache License, Version 2.0 (the
12251881Speter *    "License"); you may not use this file except in compliance
13251881Speter *    with the License.  You may obtain a copy of the License at
14251881Speter *
15251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
16251881Speter *
17251881Speter *    Unless required by applicable law or agreed to in writing,
18251881Speter *    software distributed under the License is distributed on an
19251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20251881Speter *    KIND, either express or implied.  See the License for the
21251881Speter *    specific language governing permissions and limitations
22251881Speter *    under the License.
23251881Speter * ====================================================================
24251881Speter */
25251881Speter
26251881Speter/* ==================================================================== */
27251881Speter
28251881Speter
29251881Speter
30251881Speter/*** Includes. ***/
31251881Speter
32251881Speter#include <string.h>
33251881Speter#include <ctype.h>
34251881Speter#include <assert.h>
35251881Speter
36251881Speter#include <apr_env.h>
37251881Speter#include <apr_errno.h>
38251881Speter#include <apr_file_info.h>
39251881Speter#include <apr_strings.h>
40251881Speter#include <apr_tables.h>
41251881Speter#include <apr_general.h>
42251881Speter#include <apr_lib.h>
43251881Speter
44251881Speter#include "svn_pools.h"
45251881Speter#include "svn_error.h"
46251881Speter#include "svn_ctype.h"
47251881Speter#include "svn_client.h"
48251881Speter#include "svn_cmdline.h"
49251881Speter#include "svn_string.h"
50251881Speter#include "svn_dirent_uri.h"
51251881Speter#include "svn_path.h"
52251881Speter#include "svn_hash.h"
53251881Speter#include "svn_io.h"
54251881Speter#include "svn_utf.h"
55251881Speter#include "svn_subst.h"
56251881Speter#include "svn_config.h"
57251881Speter#include "svn_wc.h"
58251881Speter#include "svn_xml.h"
59251881Speter#include "svn_time.h"
60251881Speter#include "svn_props.h"
61251881Speter#include "svn_private_config.h"
62251881Speter#include "cl.h"
63251881Speter
64251881Speter#include "private/svn_token.h"
65251881Speter#include "private/svn_opt_private.h"
66251881Speter#include "private/svn_client_private.h"
67251881Speter#include "private/svn_cmdline_private.h"
68251881Speter#include "private/svn_string_private.h"
69251896Speter#ifdef HAS_ORGANIZATION_NAME
70251896Speter#include "freebsd-organization.h"
71251896Speter#endif
72251881Speter
73251881Speter
74251881Speter
75251881Speter
76251881Spetersvn_error_t *
77251881Spetersvn_cl__print_commit_info(const svn_commit_info_t *commit_info,
78251881Speter                          void *baton,
79251881Speter                          apr_pool_t *pool)
80251881Speter{
81299742Sdim  /* Be very careful with returning errors from this callback as those
82299742Sdim     will be returned as errors from editor->close_edit(...), which may
83299742Sdim     cause callers to assume that the commit itself failed.
84299742Sdim
85299742Sdim     See log message of r1659867 and the svn_ra_get_commit_editor3
86299742Sdim     documentation for details on error scenarios. */
87299742Sdim
88251881Speter  if (SVN_IS_VALID_REVNUM(commit_info->revision))
89299742Sdim    SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld%s.\n"),
90251881Speter                               commit_info->revision,
91251881Speter                               commit_info->revision == 42 &&
92251881Speter                               getenv("SVN_I_LOVE_PANGALACTIC_GARGLE_BLASTERS")
93251881Speter                                 ?  _(" (the answer to life, the universe, "
94251881Speter                                      "and everything)")
95251881Speter                                 : ""));
96251881Speter
97251881Speter  /* Writing to stdout, as there maybe systems that consider the
98251881Speter   * presence of stderr as an indication of commit failure.
99251881Speter   * OTOH, this is only of informational nature to the user as
100251881Speter   * the commit has succeeded. */
101251881Speter  if (commit_info->post_commit_err)
102251881Speter    SVN_ERR(svn_cmdline_printf(pool, _("\nWarning: %s\n"),
103251881Speter                               commit_info->post_commit_err));
104251881Speter
105251881Speter  return SVN_NO_ERROR;
106251881Speter}
107251881Speter
108251881Speter
109251881Spetersvn_error_t *
110251881Spetersvn_cl__merge_file_externally(const char *base_path,
111251881Speter                              const char *their_path,
112251881Speter                              const char *my_path,
113251881Speter                              const char *merged_path,
114251881Speter                              const char *wc_path,
115251881Speter                              apr_hash_t *config,
116251881Speter                              svn_boolean_t *remains_in_conflict,
117251881Speter                              apr_pool_t *pool)
118251881Speter{
119251881Speter  char *merge_tool;
120251881Speter  /* Error if there is no editor specified */
121251881Speter  if (apr_env_get(&merge_tool, "SVN_MERGE", pool) != APR_SUCCESS)
122251881Speter    {
123251881Speter      struct svn_config_t *cfg;
124251881Speter      merge_tool = NULL;
125251881Speter      cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
126251881Speter      /* apr_env_get wants char **, this wants const char ** */
127251881Speter      svn_config_get(cfg, (const char **)&merge_tool,
128251881Speter                     SVN_CONFIG_SECTION_HELPERS,
129251881Speter                     SVN_CONFIG_OPTION_MERGE_TOOL_CMD, NULL);
130251881Speter    }
131251881Speter
132251881Speter  if (merge_tool)
133251881Speter    {
134251881Speter      const char *c;
135251881Speter
136251881Speter      for (c = merge_tool; *c; c++)
137251881Speter        if (!svn_ctype_isspace(*c))
138251881Speter          break;
139251881Speter
140251881Speter      if (! *c)
141251881Speter        return svn_error_create
142251881Speter          (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL,
143251881Speter           _("The SVN_MERGE environment variable is empty or "
144251881Speter             "consists solely of whitespace. Expected a shell command.\n"));
145251881Speter    }
146251881Speter  else
147251881Speter      return svn_error_create
148251881Speter        (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL,
149251881Speter         _("The environment variable SVN_MERGE and the merge-tool-cmd run-time "
150251881Speter           "configuration option were not set.\n"));
151251881Speter
152251881Speter  {
153251881Speter    const char *arguments[7] = { 0 };
154251881Speter    char *cwd;
155251881Speter    int exitcode;
156251881Speter
157251881Speter    apr_status_t status = apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, pool);
158251881Speter    if (status != 0)
159251881Speter      return svn_error_wrap_apr(status, NULL);
160251881Speter
161251881Speter    arguments[0] = merge_tool;
162251881Speter    arguments[1] = base_path;
163251881Speter    arguments[2] = their_path;
164251881Speter    arguments[3] = my_path;
165251881Speter    arguments[4] = merged_path;
166251881Speter    arguments[5] = wc_path;
167251881Speter    arguments[6] = NULL;
168251881Speter
169251881Speter    SVN_ERR(svn_io_run_cmd(svn_dirent_internal_style(cwd, pool), merge_tool,
170251881Speter                           arguments, &exitcode, NULL, TRUE, NULL, NULL, NULL,
171251881Speter                           pool));
172251881Speter    /* Exit code 0 means the merge was successful.
173251881Speter     * Exit code 1 means the file was left in conflict but it
174251881Speter     * is OK to continue with the merge.
175251881Speter     * Any other exit code means there was a real problem. */
176251881Speter    if (exitcode != 0 && exitcode != 1)
177299742Sdim      return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
178299742Sdim        _("The external merge tool '%s' exited with exit code %d."),
179299742Sdim        merge_tool, exitcode);
180251881Speter    else if (remains_in_conflict)
181251881Speter      *remains_in_conflict = exitcode == 1;
182251881Speter  }
183251881Speter  return SVN_NO_ERROR;
184251881Speter}
185251881Speter
186251881Speter
187251881Speter/* A svn_client_ctx_t's log_msg_baton3, for use with
188251881Speter   svn_cl__make_log_msg_baton(). */
189251881Speterstruct log_msg_baton
190251881Speter{
191251881Speter  const char *editor_cmd;  /* editor specified via --editor-cmd, else NULL */
192251881Speter  const char *message;  /* the message. */
193251881Speter  const char *message_encoding; /* the locale/encoding of the message. */
194251881Speter  const char *base_dir; /* the base directory for an external edit. UTF-8! */
195251881Speter  const char *tmpfile_left; /* the tmpfile left by an external edit. UTF-8! */
196251881Speter  svn_boolean_t non_interactive; /* if true, don't pop up an editor */
197251881Speter  apr_hash_t *config; /* client configuration hash */
198251881Speter  svn_boolean_t keep_locks; /* Keep repository locks? */
199251881Speter  apr_pool_t *pool; /* a pool. */
200251881Speter};
201251881Speter
202251881Speter
203251881Spetersvn_error_t *
204251881Spetersvn_cl__make_log_msg_baton(void **baton,
205251881Speter                           svn_cl__opt_state_t *opt_state,
206251881Speter                           const char *base_dir /* UTF-8! */,
207251881Speter                           apr_hash_t *config,
208251881Speter                           apr_pool_t *pool)
209251881Speter{
210299742Sdim  struct log_msg_baton *lmb = apr_pcalloc(pool, sizeof(*lmb));
211251881Speter
212251881Speter  if (opt_state->filedata)
213251881Speter    {
214251881Speter      if (strlen(opt_state->filedata->data) < opt_state->filedata->len)
215251881Speter        {
216251881Speter          /* The data contains a zero byte, and therefore can't be
217251881Speter             represented as a C string.  Punt now; it's probably not
218251881Speter             a deliberate encoding, and even if it is, we still
219251881Speter             can't handle it. */
220251881Speter          return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE, NULL,
221251881Speter                                  _("Log message contains a zero byte"));
222251881Speter        }
223251881Speter      lmb->message = opt_state->filedata->data;
224251881Speter    }
225251881Speter  else
226251881Speter    {
227251881Speter      lmb->message = opt_state->message;
228251881Speter    }
229251881Speter
230251881Speter  lmb->editor_cmd = opt_state->editor_cmd;
231251881Speter  if (opt_state->encoding)
232251881Speter    {
233251881Speter      lmb->message_encoding = opt_state->encoding;
234251881Speter    }
235251881Speter  else if (config)
236251881Speter    {
237251881Speter      svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
238251881Speter      svn_config_get(cfg, &(lmb->message_encoding),
239251881Speter                     SVN_CONFIG_SECTION_MISCELLANY,
240251881Speter                     SVN_CONFIG_OPTION_LOG_ENCODING,
241251881Speter                     NULL);
242251881Speter    }
243299742Sdim  else
244299742Sdim    lmb->message_encoding = NULL;
245251881Speter
246299742Sdim  lmb->base_dir = base_dir;
247251881Speter  lmb->tmpfile_left = NULL;
248251881Speter  lmb->config = config;
249251881Speter  lmb->keep_locks = opt_state->no_unlock;
250251881Speter  lmb->non_interactive = opt_state->non_interactive;
251251881Speter  lmb->pool = pool;
252251881Speter  *baton = lmb;
253251881Speter  return SVN_NO_ERROR;
254251881Speter}
255251881Speter
256251881Speter
257251881Spetersvn_error_t *
258251881Spetersvn_cl__cleanup_log_msg(void *log_msg_baton,
259251881Speter                        svn_error_t *commit_err,
260251881Speter                        apr_pool_t *pool)
261251881Speter{
262251881Speter  struct log_msg_baton *lmb = log_msg_baton;
263251881Speter  svn_error_t *err;
264251881Speter
265251881Speter  /* If there was no tmpfile left, or there is no log message baton,
266251881Speter     return COMMIT_ERR. */
267251881Speter  if ((! lmb) || (! lmb->tmpfile_left))
268251881Speter    return commit_err;
269251881Speter
270251881Speter  /* If there was no commit error, cleanup the tmpfile and return. */
271251881Speter  if (! commit_err)
272251881Speter    return svn_io_remove_file2(lmb->tmpfile_left, FALSE, lmb->pool);
273251881Speter
274251881Speter  /* There was a commit error; there is a tmpfile.  Leave the tmpfile
275251881Speter     around, and add message about its presence to the commit error
276251881Speter     chain.  Then return COMMIT_ERR.  If the conversion from UTF-8 to
277251881Speter     native encoding fails, we have to compose that error with the
278251881Speter     commit error chain, too. */
279251881Speter
280251881Speter  err = svn_error_createf(commit_err->apr_err, NULL,
281251881Speter                          _("   '%s'"),
282251881Speter                          svn_dirent_local_style(lmb->tmpfile_left, pool));
283251881Speter  svn_error_compose(commit_err,
284251881Speter                    svn_error_create(commit_err->apr_err, err,
285251881Speter                      _("Your commit message was left in "
286251881Speter                        "a temporary file:")));
287251881Speter  return commit_err;
288251881Speter}
289251881Speter
290251881Speter
291251881Speter/* Remove line-starting PREFIX and everything after it from BUFFER.
292251881Speter   If NEW_LEN is non-NULL, return the new length of BUFFER in
293251881Speter   *NEW_LEN.  */
294251881Speterstatic void
295251881Spetertruncate_buffer_at_prefix(apr_size_t *new_len,
296251881Speter                          char *buffer,
297251881Speter                          const char *prefix)
298251881Speter{
299251881Speter  char *substring = buffer;
300251881Speter
301251881Speter  assert(buffer && prefix);
302251881Speter
303251881Speter  /* Initialize *NEW_LEN. */
304251881Speter  if (new_len)
305251881Speter    *new_len = strlen(buffer);
306251881Speter
307251881Speter  while (1)
308251881Speter    {
309251881Speter      /* Find PREFIX in BUFFER. */
310251881Speter      substring = strstr(substring, prefix);
311251881Speter      if (! substring)
312251881Speter        return;
313251881Speter
314251881Speter      /* We found PREFIX.  Is it really a PREFIX?  Well, if it's the first
315251881Speter         thing in the file, or if the character before it is a
316251881Speter         line-terminator character, it sure is. */
317251881Speter      if ((substring == buffer)
318251881Speter          || (*(substring - 1) == '\r')
319251881Speter          || (*(substring - 1) == '\n'))
320251881Speter        {
321251881Speter          *substring = '\0';
322251881Speter          if (new_len)
323251881Speter            *new_len = substring - buffer;
324251881Speter        }
325251881Speter      else if (substring)
326251881Speter        {
327251881Speter          /* Well, it wasn't really a prefix, so just advance by 1
328251881Speter             character and continue. */
329251881Speter          substring++;
330251881Speter        }
331251881Speter    }
332251881Speter
333251881Speter  /* NOTREACHED */
334251881Speter}
335251881Speter
336251881Speter
337251896Speter/*
338251896Speter * Since we're adding freebsd-specific tokens to the log message,
339251896Speter * clean out any leftovers to avoid accidently sending them to other
340251896Speter * projects that won't be expecting them.
341251896Speter */
342251896Speter
343251896Speterstatic const char *prefixes[] = {
344251896Speter  "PR:",
345251896Speter  "Submitted by:",
346299742Sdim  "Reported by:",
347251896Speter  "Reviewed by:",
348251896Speter  "Approved by:",
349251896Speter  "Obtained from:",
350251896Speter  "MFC after:",
351299742Sdim  "MFH:",
352263655Sgjb  "Relnotes:",
353251896Speter  "Security:",
354289166Speter  "Sponsored by:",
355289166Speter  "Differential Revision:",
356251896Speter};
357251896Speter
358251896Spetervoid
359251896Spetercleanmsg(apr_size_t *l, char *s)
360251896Speter{
361251896Speter  int i;
362251896Speter  char *pos;
363251896Speter  char *kw;
364251896Speter  char *p;
365251896Speter  int empty;
366251896Speter
367251896Speter  for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++) {
368251896Speter    pos = s;
369251896Speter    while ((kw = strstr(pos, prefixes[i])) != NULL) {
370251896Speter      /* Check to see if keyword is at start of line (or buffer) */
371251896Speter      if (!(kw == s || kw[-1] == '\r' || kw[-1] == '\n')) {
372251896Speter	pos = kw + 1;
373251896Speter	continue;
374251896Speter      }
375251896Speter      p = kw + strlen(prefixes[i]);
376251896Speter      empty = 1;
377251896Speter      while (1) {
378251896Speter	if (*p == ' ' || *p == '\t') {
379251896Speter	  p++;
380251896Speter	  continue;
381251896Speter	}
382251896Speter	if (*p == '\0' || *p == '\r' || *p == '\n')
383251896Speter	  break;
384251896Speter	empty = 0;
385251896Speter	break;
386251896Speter      }
387251896Speter      if (empty && (*p == '\r' || *p == '\n')) {
388251896Speter	memmove(kw, p + 1, strlen(p + 1) + 1);
389251896Speter	if (l)
390251896Speter	  *l -= (p + 1 - kw);
391251896Speter      } else if (empty) {
392251896Speter	*kw = '\0';
393251896Speter	if (l)
394251896Speter	  *l -= (p - kw);
395251896Speter      } else {
396251896Speter	pos = p;
397251896Speter      }
398251896Speter    }
399251896Speter  }
400251896Speter}
401251896Speter
402251881Speter#define EDITOR_EOF_PREFIX  _("--This line, and those below, will be ignored--")
403251881Speter
404251881Spetersvn_error_t *
405251881Spetersvn_cl__get_log_message(const char **log_msg,
406251881Speter                        const char **tmp_file,
407251881Speter                        const apr_array_header_t *commit_items,
408251881Speter                        void *baton,
409251881Speter                        apr_pool_t *pool)
410251881Speter{
411251881Speter  svn_stringbuf_t *default_msg = NULL;
412251881Speter  struct log_msg_baton *lmb = baton;
413251881Speter  svn_stringbuf_t *message = NULL;
414289166Speter  svn_config_t *cfg;
415289166Speter  const char *mfc_after, *sponsored_by;
416251881Speter
417289166Speter  cfg = lmb->config ? svn_hash_gets(lmb->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
418289166Speter
419251881Speter  /* Set default message.  */
420251881Speter  default_msg = svn_stringbuf_create(APR_EOL_STR, pool);
421251896Speter  svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
422251896Speter  svn_stringbuf_appendcstr(default_msg, "PR:\t\t" APR_EOL_STR);
423251896Speter  svn_stringbuf_appendcstr(default_msg, "Submitted by:\t" APR_EOL_STR);
424299742Sdim  svn_stringbuf_appendcstr(default_msg, "Reported by:\t" APR_EOL_STR);
425251896Speter  svn_stringbuf_appendcstr(default_msg, "Reviewed by:\t" APR_EOL_STR);
426251896Speter  svn_stringbuf_appendcstr(default_msg, "Approved by:\t" APR_EOL_STR);
427251896Speter  svn_stringbuf_appendcstr(default_msg, "Obtained from:\t" APR_EOL_STR);
428289166Speter  svn_stringbuf_appendcstr(default_msg, "MFC after:\t");
429289166Speter  svn_config_get(cfg, &mfc_after, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-mfc-after", NULL);
430289166Speter  if (mfc_after != NULL)
431289166Speter	  svn_stringbuf_appendcstr(default_msg, mfc_after);
432289166Speter  svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
433299742Sdim  svn_stringbuf_appendcstr(default_msg, "MFH:\t\t" APR_EOL_STR);
434263655Sgjb  svn_stringbuf_appendcstr(default_msg, "Relnotes:\t" APR_EOL_STR);
435251896Speter  svn_stringbuf_appendcstr(default_msg, "Security:\t" APR_EOL_STR);
436289166Speter  svn_stringbuf_appendcstr(default_msg, "Sponsored by:\t");
437289166Speter  svn_config_get(cfg, &sponsored_by, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-sponsored-by",
438251896Speter#ifdef HAS_ORGANIZATION_NAME
439289166Speter  	ORGANIZATION_NAME);
440289166Speter#else
441289166Speter	NULL);
442251896Speter#endif
443289166Speter  if (sponsored_by != NULL)
444289166Speter	  svn_stringbuf_appendcstr(default_msg, sponsored_by);
445289166Speter  svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
446289166Speter  svn_stringbuf_appendcstr(default_msg, "Differential Revision:\t" APR_EOL_STR);
447251881Speter  svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX);
448251896Speter  svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
449251896Speter  svn_stringbuf_appendcstr(default_msg, "> Description of fields to fill in above:                     76 columns --|" APR_EOL_STR);
450299742Sdim  svn_stringbuf_appendcstr(default_msg, "> PR:                       If and which Problem Report is related." APR_EOL_STR);
451289166Speter  svn_stringbuf_appendcstr(default_msg, "> Submitted by:             If someone else sent in the change." APR_EOL_STR);
452299742Sdim  svn_stringbuf_appendcstr(default_msg, "> Reported by:              If someone else reported the issue." APR_EOL_STR);
453289166Speter  svn_stringbuf_appendcstr(default_msg, "> Reviewed by:              If someone else reviewed your modification." APR_EOL_STR);
454289166Speter  svn_stringbuf_appendcstr(default_msg, "> Approved by:              If you needed approval for this commit." APR_EOL_STR);
455289166Speter  svn_stringbuf_appendcstr(default_msg, "> Obtained from:            If the change is from a third party." APR_EOL_STR);
456289166Speter  svn_stringbuf_appendcstr(default_msg, "> MFC after:                N [day[s]|week[s]|month[s]].  Request a reminder email." APR_EOL_STR);
457289166Speter  svn_stringbuf_appendcstr(default_msg, "> MFH:                      Ports tree branch name.  Request approval for merge." APR_EOL_STR);
458289166Speter  svn_stringbuf_appendcstr(default_msg, "> Relnotes:                 Set to 'yes' for mention in release notes." APR_EOL_STR);
459289166Speter  svn_stringbuf_appendcstr(default_msg, "> Security:                 Vulnerability reference (one per line) or description." APR_EOL_STR);
460289166Speter  svn_stringbuf_appendcstr(default_msg, "> Sponsored by:             If the change was sponsored by an organization." APR_EOL_STR);
461289166Speter  svn_stringbuf_appendcstr(default_msg, "> Differential Revision:    https://reviews.freebsd.org/D### (*full* phabric URL needed)." APR_EOL_STR);
462251896Speter  svn_stringbuf_appendcstr(default_msg, "> Empty fields above will be automatically removed." APR_EOL_STR);
463251896Speter  svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
464251881Speter
465251881Speter  *tmp_file = NULL;
466251881Speter  if (lmb->message)
467251881Speter    {
468299742Sdim      svn_string_t *log_msg_str = svn_string_create(lmb->message, pool);
469251881Speter
470299742Sdim      SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, NULL, NULL,
471251881Speter                                            log_msg_str, lmb->message_encoding,
472251881Speter                                            FALSE, pool, pool),
473251881Speter                _("Error normalizing log message to internal format"));
474251881Speter
475299742Sdim      /* Strip off the EOF marker text and the junk that follows it. */
476299742Sdim      truncate_buffer_at_prefix(&(log_msg_str->len), (char *)log_msg_str->data,
477299742Sdim                                EDITOR_EOF_PREFIX);
478299742Sdim
479299742Sdim      cleanmsg(&(log_msg_str->len), (char*)log_msg_str->data);
480299742Sdim
481251881Speter      *log_msg = log_msg_str->data;
482251881Speter      return SVN_NO_ERROR;
483251881Speter    }
484251881Speter
485251881Speter  if (! commit_items->nelts)
486251881Speter    {
487251881Speter      *log_msg = "";
488251881Speter      return SVN_NO_ERROR;
489251881Speter    }
490251881Speter
491251881Speter  while (! message)
492251881Speter    {
493251881Speter      /* We still don't have a valid commit message.  Use $EDITOR to
494251881Speter         get one.  Note that svn_cl__edit_string_externally will still
495251881Speter         return a UTF-8'ized log message. */
496251881Speter      int i;
497251881Speter      svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
498251881Speter      svn_error_t *err = SVN_NO_ERROR;
499251881Speter      svn_string_t *msg_string = svn_string_create_empty(pool);
500251881Speter
501251881Speter      for (i = 0; i < commit_items->nelts; i++)
502251881Speter        {
503251881Speter          svn_client_commit_item3_t *item
504251881Speter            = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
505251881Speter          const char *path = item->path;
506251881Speter          char text_mod = '_', prop_mod = ' ', unlock = ' ';
507251881Speter
508251881Speter          if (! path)
509251881Speter            path = item->url;
510299742Sdim          else if (lmb->base_dir)
511251881Speter            path = svn_dirent_is_child(lmb->base_dir, path, pool);
512251881Speter
513251881Speter          /* If still no path, then just use current directory. */
514299742Sdim          if (! path || !*path)
515251881Speter            path = ".";
516251881Speter
517251881Speter          if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
518251881Speter              && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
519251881Speter            text_mod = 'R';
520251881Speter          else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
521251881Speter            text_mod = 'A';
522251881Speter          else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
523251881Speter            text_mod = 'D';
524251881Speter          else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
525251881Speter            text_mod = 'M';
526251881Speter
527251881Speter          if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
528251881Speter            prop_mod = 'M';
529251881Speter
530251881Speter          if (! lmb->keep_locks
531251881Speter              && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
532251881Speter            unlock = 'U';
533251881Speter
534251881Speter          svn_stringbuf_appendbyte(tmp_message, text_mod);
535251881Speter          svn_stringbuf_appendbyte(tmp_message, prop_mod);
536251881Speter          svn_stringbuf_appendbyte(tmp_message, unlock);
537251881Speter          if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
538251881Speter            /* History included via copy/move. */
539251881Speter            svn_stringbuf_appendcstr(tmp_message, "+ ");
540251881Speter          else
541251881Speter            svn_stringbuf_appendcstr(tmp_message, "  ");
542251881Speter          svn_stringbuf_appendcstr(tmp_message, path);
543251881Speter          svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR);
544251881Speter        }
545251881Speter
546251881Speter      msg_string->data = tmp_message->data;
547251881Speter      msg_string->len = tmp_message->len;
548251881Speter
549251881Speter      /* Use the external edit to get a log message. */
550251881Speter      if (! lmb->non_interactive)
551251881Speter        {
552251881Speter          err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
553299742Sdim                                                    lmb->editor_cmd,
554299742Sdim                                                    lmb->base_dir ? lmb->base_dir : "",
555251881Speter                                                    msg_string, "svn-commit",
556251881Speter                                                    lmb->config, TRUE,
557251881Speter                                                    lmb->message_encoding,
558251881Speter                                                    pool);
559251881Speter        }
560251881Speter      else /* non_interactive flag says we can't pop up an editor, so error */
561251881Speter        {
562251881Speter          return svn_error_create
563251881Speter            (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
564251881Speter             _("Cannot invoke editor to get log message "
565251881Speter               "when non-interactive"));
566251881Speter        }
567251881Speter
568251881Speter      /* Dup the tmpfile path into its baton's pool. */
569251881Speter      *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool,
570251881Speter                                                  lmb->tmpfile_left);
571251881Speter
572251881Speter      /* If the edit returned an error, handle it. */
573251881Speter      if (err)
574251881Speter        {
575251881Speter          if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)
576251881Speter            err = svn_error_quick_wrap
577251881Speter              (err, _("Could not use external editor to fetch log message; "
578251881Speter                      "consider setting the $SVN_EDITOR environment variable "
579251881Speter                      "or using the --message (-m) or --file (-F) options"));
580251881Speter          return svn_error_trace(err);
581251881Speter        }
582251881Speter
583251881Speter      if (msg_string)
584251881Speter        message = svn_stringbuf_create_from_string(msg_string, pool);
585251881Speter
586299742Sdim      /* Strip off the EOF marker text and the junk that follows it. */
587251881Speter      if (message)
588251881Speter        truncate_buffer_at_prefix(&message->len, message->data,
589251881Speter                                  EDITOR_EOF_PREFIX);
590251896Speter      /*
591251896Speter       * Since we're adding freebsd-specific tokens to the log message,
592251896Speter       * clean out any leftovers to avoid accidently sending them to other
593251896Speter       * projects that won't be expecting them.
594251896Speter       */
595251896Speter      if (message)
596251896Speter	cleanmsg(&message->len, message->data);
597251881Speter
598251881Speter      if (message)
599251881Speter        {
600251881Speter          /* We did get message, now check if it is anything more than just
601251881Speter             white space as we will consider white space only as empty */
602251881Speter          apr_size_t len;
603251881Speter
604251881Speter          for (len = 0; len < message->len; len++)
605251881Speter            {
606251881Speter              /* FIXME: should really use an UTF-8 whitespace test
607251881Speter                 rather than svn_ctype_isspace, which is ASCII only */
608251881Speter              if (! svn_ctype_isspace(message->data[len]))
609251881Speter                break;
610251881Speter            }
611251881Speter          if (len == message->len)
612251881Speter            message = NULL;
613251881Speter        }
614251881Speter
615251881Speter      if (! message)
616251881Speter        {
617251881Speter          const char *reply;
618251881Speter          SVN_ERR(svn_cmdline_prompt_user2
619251881Speter                  (&reply,
620251881Speter                   _("\nLog message unchanged or not specified\n"
621251881Speter                     "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool));
622251881Speter          if (reply)
623251881Speter            {
624251881Speter              int letter = apr_tolower(reply[0]);
625251881Speter
626251881Speter              /* If the user chooses to abort, we cleanup the
627251881Speter                 temporary file and exit the loop with a NULL
628251881Speter                 message. */
629251881Speter              if ('a' == letter)
630251881Speter                {
631251881Speter                  SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
632251881Speter                  *tmp_file = lmb->tmpfile_left = NULL;
633251881Speter                  break;
634251881Speter                }
635251881Speter
636251881Speter              /* If the user chooses to continue, we make an empty
637251881Speter                 message, which will cause us to exit the loop.  We
638251881Speter                 also cleanup the temporary file. */
639251881Speter              if ('c' == letter)
640251881Speter                {
641251881Speter                  SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
642251881Speter                  *tmp_file = lmb->tmpfile_left = NULL;
643251881Speter                  message = svn_stringbuf_create_empty(pool);
644251881Speter                }
645251881Speter
646251881Speter              /* If the user chooses anything else, the loop will
647251881Speter                 continue on the NULL message. */
648251881Speter            }
649251881Speter        }
650251881Speter    }
651251881Speter
652251881Speter  *log_msg = message ? message->data : NULL;
653251881Speter  return SVN_NO_ERROR;
654251881Speter}
655251881Speter
656251881Speter
657251881Speter/* ### The way our error wrapping currently works, the error returned
658251881Speter * from here will look as though it originates in this source file,
659251881Speter * instead of in the caller's source file.  This can be a bit
660251881Speter * misleading, until one starts debugging.  Ideally, there'd be a way
661251881Speter * to wrap an error while preserving its FILE/LINE info.
662251881Speter */
663251881Spetersvn_error_t *
664251881Spetersvn_cl__may_need_force(svn_error_t *err)
665251881Speter{
666251881Speter  if (err
667251881Speter      && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE ||
668251881Speter          err->apr_err == SVN_ERR_CLIENT_MODIFIED))
669251881Speter    {
670251881Speter      /* Should this svn_error_compose a new error number? Probably not,
671251881Speter         the error hasn't changed. */
672251881Speter      err = svn_error_quick_wrap
673251881Speter        (err, _("Use --force to override this restriction (local modifications "
674251881Speter         "may be lost)"));
675251881Speter    }
676251881Speter
677251881Speter  return svn_error_trace(err);
678251881Speter}
679251881Speter
680251881Speter
681251881Spetersvn_error_t *
682251881Spetersvn_cl__error_checked_fputs(const char *string, FILE* stream)
683251881Speter{
684251881Speter  /* On POSIX systems, errno will be set on an error in fputs, but this might
685251881Speter     not be the case on other platforms.  We reset errno and only
686251881Speter     use it if it was set by the below fputs call.  Else, we just return
687251881Speter     a generic error. */
688251881Speter  errno = 0;
689251881Speter
690251881Speter  if (fputs(string, stream) == EOF)
691251881Speter    {
692299742Sdim      if (apr_get_os_error()) /* is errno on POSIX */
693299742Sdim        return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
694251881Speter      else
695251881Speter        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
696251881Speter    }
697251881Speter
698251881Speter  return SVN_NO_ERROR;
699251881Speter}
700251881Speter
701251881Speter
702251881Spetersvn_error_t *
703251881Spetersvn_cl__try(svn_error_t *err,
704251881Speter            apr_array_header_t *errors_seen,
705251881Speter            svn_boolean_t quiet,
706251881Speter            ...)
707251881Speter{
708251881Speter  if (err)
709251881Speter    {
710251881Speter      apr_status_t apr_err;
711251881Speter      va_list ap;
712251881Speter
713251881Speter      va_start(ap, quiet);
714251881Speter      while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS)
715251881Speter        {
716251881Speter          if (errors_seen)
717251881Speter            {
718251881Speter              int i;
719251881Speter              svn_boolean_t add = TRUE;
720251881Speter
721251881Speter              /* Don't report duplicate error codes. */
722251881Speter              for (i = 0; i < errors_seen->nelts; i++)
723251881Speter                {
724251881Speter                  if (APR_ARRAY_IDX(errors_seen, i,
725251881Speter                                    apr_status_t) == err->apr_err)
726251881Speter                    {
727251881Speter                      add = FALSE;
728251881Speter                      break;
729251881Speter                    }
730251881Speter                }
731251881Speter              if (add)
732251881Speter                APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err;
733251881Speter            }
734251881Speter          if (err->apr_err == apr_err)
735251881Speter            {
736251881Speter              if (! quiet)
737251881Speter                svn_handle_warning2(stderr, err, "svn: ");
738251881Speter              svn_error_clear(err);
739251881Speter              return SVN_NO_ERROR;
740251881Speter            }
741251881Speter        }
742251881Speter      va_end(ap);
743251881Speter    }
744251881Speter
745251881Speter  return svn_error_trace(err);
746251881Speter}
747251881Speter
748251881Speter
749251881Spetervoid
750251881Spetersvn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
751251881Speter                         apr_pool_t *pool,
752251881Speter                         const char *tagname,
753251881Speter                         const char *string)
754251881Speter{
755251881Speter  if (string)
756251881Speter    {
757251881Speter      svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata,
758299742Sdim                            tagname, SVN_VA_NULL);
759251881Speter      svn_xml_escape_cdata_cstring(sb, string, pool);
760251881Speter      svn_xml_make_close_tag(sb, pool, tagname);
761251881Speter    }
762251881Speter}
763251881Speter
764251881Speter
765251881Spetervoid
766251881Spetersvn_cl__print_xml_commit(svn_stringbuf_t **sb,
767251881Speter                         svn_revnum_t revision,
768251881Speter                         const char *author,
769251881Speter                         const char *date,
770251881Speter                         apr_pool_t *pool)
771251881Speter{
772251881Speter  /* "<commit ...>" */
773251881Speter  svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit",
774251881Speter                        "revision",
775299742Sdim                        apr_psprintf(pool, "%ld", revision), SVN_VA_NULL);
776251881Speter
777251881Speter  /* "<author>xx</author>" */
778251881Speter  if (author)
779251881Speter    svn_cl__xml_tagged_cdata(sb, pool, "author", author);
780251881Speter
781251881Speter  /* "<date>xx</date>" */
782251881Speter  if (date)
783251881Speter    svn_cl__xml_tagged_cdata(sb, pool, "date", date);
784251881Speter
785251881Speter  /* "</commit>" */
786251881Speter  svn_xml_make_close_tag(sb, pool, "commit");
787251881Speter}
788251881Speter
789251881Speter
790251881Spetervoid
791251881Spetersvn_cl__print_xml_lock(svn_stringbuf_t **sb,
792251881Speter                       const svn_lock_t *lock,
793251881Speter                       apr_pool_t *pool)
794251881Speter{
795251881Speter  /* "<lock>" */
796299742Sdim  svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", SVN_VA_NULL);
797251881Speter
798251881Speter  /* "<token>xx</token>" */
799251881Speter  svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token);
800251881Speter
801251881Speter  /* "<owner>xx</owner>" */
802251881Speter  svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner);
803251881Speter
804251881Speter  /* "<comment>xx</comment>" */
805251881Speter  svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment);
806251881Speter
807251881Speter  /* "<created>xx</created>" */
808251881Speter  svn_cl__xml_tagged_cdata(sb, pool, "created",
809251881Speter                           svn_time_to_cstring(lock->creation_date, pool));
810251881Speter
811251881Speter  /* "<expires>xx</expires>" */
812251881Speter  if (lock->expiration_date != 0)
813251881Speter    svn_cl__xml_tagged_cdata(sb, pool, "expires",
814251881Speter                             svn_time_to_cstring(lock->expiration_date, pool));
815251881Speter
816251881Speter  /* "</lock>" */
817251881Speter  svn_xml_make_close_tag(sb, pool, "lock");
818251881Speter}
819251881Speter
820251881Speter
821251881Spetersvn_error_t *
822251881Spetersvn_cl__xml_print_header(const char *tagname,
823251881Speter                         apr_pool_t *pool)
824251881Speter{
825251881Speter  svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
826251881Speter
827251881Speter  /* <?xml version="1.0" encoding="UTF-8"?> */
828251881Speter  svn_xml_make_header2(&sb, "UTF-8", pool);
829251881Speter
830251881Speter  /* "<TAGNAME>" */
831299742Sdim  svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, SVN_VA_NULL);
832251881Speter
833251881Speter  return svn_cl__error_checked_fputs(sb->data, stdout);
834251881Speter}
835251881Speter
836251881Speter
837251881Spetersvn_error_t *
838251881Spetersvn_cl__xml_print_footer(const char *tagname,
839251881Speter                         apr_pool_t *pool)
840251881Speter{
841251881Speter  svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
842251881Speter
843251881Speter  /* "</TAGNAME>" */
844251881Speter  svn_xml_make_close_tag(&sb, pool, tagname);
845251881Speter  return svn_cl__error_checked_fputs(sb->data, stdout);
846251881Speter}
847251881Speter
848251881Speter
849251881Speter/* A map for svn_node_kind_t values to XML strings */
850251881Speterstatic const svn_token_map_t map_node_kind_xml[] =
851251881Speter{
852251881Speter  { "none", svn_node_none },
853251881Speter  { "file", svn_node_file },
854251881Speter  { "dir",  svn_node_dir },
855251881Speter  { "",     svn_node_unknown },
856251881Speter  { NULL,   0 }
857251881Speter};
858251881Speter
859251881Speter/* A map for svn_node_kind_t values to human-readable strings */
860251881Speterstatic const svn_token_map_t map_node_kind_human[] =
861251881Speter{
862251881Speter  { N_("none"), svn_node_none },
863251881Speter  { N_("file"), svn_node_file },
864251881Speter  { N_("dir"),  svn_node_dir },
865251881Speter  { "",         svn_node_unknown },
866251881Speter  { NULL,       0 }
867251881Speter};
868251881Speter
869251881Speterconst char *
870251881Spetersvn_cl__node_kind_str_xml(svn_node_kind_t kind)
871251881Speter{
872251881Speter  return svn_token__to_word(map_node_kind_xml, kind);
873251881Speter}
874251881Speter
875251881Speterconst char *
876251881Spetersvn_cl__node_kind_str_human_readable(svn_node_kind_t kind)
877251881Speter{
878251881Speter  return _(svn_token__to_word(map_node_kind_human, kind));
879251881Speter}
880251881Speter
881251881Speter
882251881Speter/* A map for svn_wc_operation_t values to XML strings */
883251881Speterstatic const svn_token_map_t map_wc_operation_xml[] =
884251881Speter{
885251881Speter  { "none",   svn_wc_operation_none },
886251881Speter  { "update", svn_wc_operation_update },
887251881Speter  { "switch", svn_wc_operation_switch },
888251881Speter  { "merge",  svn_wc_operation_merge },
889251881Speter  { NULL,     0 }
890251881Speter};
891251881Speter
892251881Speter/* A map for svn_wc_operation_t values to human-readable strings */
893251881Speterstatic const svn_token_map_t map_wc_operation_human[] =
894251881Speter{
895251881Speter  { N_("none"),   svn_wc_operation_none },
896251881Speter  { N_("update"), svn_wc_operation_update },
897251881Speter  { N_("switch"), svn_wc_operation_switch },
898251881Speter  { N_("merge"),  svn_wc_operation_merge },
899251881Speter  { NULL,         0 }
900251881Speter};
901251881Speter
902251881Speterconst char *
903251881Spetersvn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool)
904251881Speter{
905251881Speter  return svn_token__to_word(map_wc_operation_xml, operation);
906251881Speter}
907251881Speter
908251881Speterconst char *
909251881Spetersvn_cl__operation_str_human_readable(svn_wc_operation_t operation,
910251881Speter                                     apr_pool_t *pool)
911251881Speter{
912251881Speter  return _(svn_token__to_word(map_wc_operation_human, operation));
913251881Speter}
914251881Speter
915251881Speter
916251881Spetersvn_error_t *
917251881Spetersvn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
918251881Speter                                            apr_getopt_t *os,
919251881Speter                                            const apr_array_header_t *known_targets,
920251881Speter                                            svn_client_ctx_t *ctx,
921251881Speter                                            svn_boolean_t keep_last_origpath_on_truepath_collision,
922251881Speter                                            apr_pool_t *pool)
923251881Speter{
924251881Speter  svn_error_t *err = svn_client_args_to_target_array2(targets,
925251881Speter                                                      os,
926251881Speter                                                      known_targets,
927251881Speter                                                      ctx,
928251881Speter                                                      keep_last_origpath_on_truepath_collision,
929251881Speter                                                      pool);
930251881Speter  if (err)
931251881Speter    {
932251881Speter      if (err->apr_err ==  SVN_ERR_RESERVED_FILENAME_SPECIFIED)
933251881Speter        {
934251881Speter          svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
935251881Speter          svn_error_clear(err);
936251881Speter        }
937251881Speter      else
938251881Speter        return svn_error_trace(err);
939251881Speter    }
940251881Speter  return SVN_NO_ERROR;
941251881Speter}
942251881Speter
943251881Speter
944251881Speter/* Helper for svn_cl__get_changelist(); implements
945251881Speter   svn_changelist_receiver_t. */
946251881Speterstatic svn_error_t *
947251881Speterchangelist_receiver(void *baton,
948251881Speter                    const char *path,
949251881Speter                    const char *changelist,
950251881Speter                    apr_pool_t *pool)
951251881Speter{
952251881Speter  /* No need to check CHANGELIST; our caller only asked about one of them. */
953251881Speter  apr_array_header_t *paths = baton;
954251881Speter  APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path);
955251881Speter  return SVN_NO_ERROR;
956251881Speter}
957251881Speter
958251881Speter
959251881Spetersvn_error_t *
960251881Spetersvn_cl__changelist_paths(apr_array_header_t **paths,
961251881Speter                         const apr_array_header_t *changelists,
962251881Speter                         const apr_array_header_t *targets,
963251881Speter                         svn_depth_t depth,
964251881Speter                         svn_client_ctx_t *ctx,
965251881Speter                         apr_pool_t *result_pool,
966251881Speter                         apr_pool_t *scratch_pool)
967251881Speter{
968251881Speter  apr_array_header_t *found;
969251881Speter  apr_hash_t *paths_hash;
970251881Speter  apr_pool_t *iterpool;
971251881Speter  int i;
972251881Speter
973251881Speter  if (! (changelists && changelists->nelts))
974251881Speter    {
975251881Speter      *paths = (apr_array_header_t *)targets;
976251881Speter      return SVN_NO_ERROR;
977251881Speter    }
978251881Speter
979251881Speter  found = apr_array_make(scratch_pool, 8, sizeof(const char *));
980251881Speter  iterpool = svn_pool_create(scratch_pool);
981251881Speter  for (i = 0; i < targets->nelts; i++)
982251881Speter    {
983251881Speter      const char *target = APR_ARRAY_IDX(targets, i, const char *);
984251881Speter      svn_pool_clear(iterpool);
985251881Speter      SVN_ERR(svn_client_get_changelists(target, changelists, depth,
986251881Speter                                         changelist_receiver, found,
987251881Speter                                         ctx, iterpool));
988251881Speter    }
989251881Speter  svn_pool_destroy(iterpool);
990251881Speter
991251881Speter  SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool));
992251881Speter  return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool));
993251881Speter}
994251881Speter
995251881Spetersvn_cl__show_revs_t
996251881Spetersvn_cl__show_revs_from_word(const char *word)
997251881Speter{
998251881Speter  if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0)
999251881Speter    return svn_cl__show_revs_merged;
1000251881Speter  if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0)
1001251881Speter    return svn_cl__show_revs_eligible;
1002251881Speter  /* word is an invalid flavor. */
1003251881Speter  return svn_cl__show_revs_invalid;
1004251881Speter}
1005251881Speter
1006251881Speter
1007251881Spetersvn_error_t *
1008251881Spetersvn_cl__time_cstring_to_human_cstring(const char **human_cstring,
1009251881Speter                                      const char *data,
1010251881Speter                                      apr_pool_t *pool)
1011251881Speter{
1012251881Speter  svn_error_t *err;
1013251881Speter  apr_time_t when;
1014251881Speter
1015251881Speter  err = svn_time_from_cstring(&when, data, pool);
1016251881Speter  if (err && err->apr_err == SVN_ERR_BAD_DATE)
1017251881Speter    {
1018251881Speter      svn_error_clear(err);
1019251881Speter
1020251881Speter      *human_cstring = _("(invalid date)");
1021251881Speter      return SVN_NO_ERROR;
1022251881Speter    }
1023251881Speter  else if (err)
1024251881Speter    return svn_error_trace(err);
1025251881Speter
1026251881Speter  *human_cstring = svn_time_to_human_cstring(when, pool);
1027251881Speter
1028251881Speter  return SVN_NO_ERROR;
1029251881Speter}
1030251881Speter
1031251881Speterconst char *
1032251881Spetersvn_cl__node_description(const svn_wc_conflict_version_t *node,
1033251881Speter                         const char *wc_repos_root_URL,
1034251881Speter                         apr_pool_t *pool)
1035251881Speter{
1036251881Speter  const char *root_str = "^";
1037251881Speter  const char *path_str = "...";
1038251881Speter
1039251881Speter  if (!node)
1040251881Speter    /* Printing "(none)" the harder way to ensure conformity (mostly with
1041251881Speter     * translations). */
1042251881Speter    return apr_psprintf(pool, "(%s)",
1043251881Speter                        svn_cl__node_kind_str_human_readable(svn_node_none));
1044251881Speter
1045251881Speter  /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL.
1046251881Speter   * Otherwise show the complete URL, and if we can't, show dots. */
1047251881Speter
1048251881Speter  if (node->repos_url &&
1049251881Speter      (wc_repos_root_URL == NULL ||
1050251881Speter       strcmp(node->repos_url, wc_repos_root_URL) != 0))
1051251881Speter    root_str = node->repos_url;
1052251881Speter
1053251881Speter  if (node->path_in_repos)
1054251881Speter    path_str = node->path_in_repos;
1055251881Speter
1056251881Speter  return apr_psprintf(pool, "(%s) %s@%ld",
1057251881Speter                      svn_cl__node_kind_str_human_readable(node->node_kind),
1058251881Speter                      svn_path_url_add_component2(root_str, path_str, pool),
1059251881Speter                      node->peg_rev);
1060251881Speter}
1061251881Speter
1062251881Spetersvn_error_t *
1063251881Spetersvn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
1064251881Speter                          const apr_array_header_t *targets,
1065251881Speter                          apr_pool_t *pool)
1066251881Speter{
1067251881Speter  int i;
1068251881Speter  apr_array_header_t *true_targets;
1069251881Speter
1070251881Speter  true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
1071251881Speter
1072251881Speter  for (i = 0; i < targets->nelts; i++)
1073251881Speter    {
1074251881Speter      const char *target = APR_ARRAY_IDX(targets, i, const char *);
1075251881Speter      const char *true_target, *peg;
1076251881Speter
1077251881Speter      SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg,
1078251881Speter                                                 target, pool));
1079251881Speter      if (peg[0] && peg[1])
1080251881Speter        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1081251881Speter                                 _("'%s': a peg revision is not allowed here"),
1082251881Speter                                 target);
1083251881Speter      APR_ARRAY_PUSH(true_targets, const char *) = true_target;
1084251881Speter    }
1085251881Speter
1086251881Speter  SVN_ERR_ASSERT(true_targets_p);
1087251881Speter  *true_targets_p = true_targets;
1088251881Speter
1089251881Speter  return SVN_NO_ERROR;
1090251881Speter}
1091251881Speter
1092251881Spetersvn_error_t *
1093251881Spetersvn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets)
1094251881Speter{
1095251881Speter  svn_error_t *err;
1096251881Speter
1097251881Speter  err = svn_client__assert_homogeneous_target_type(targets);
1098251881Speter  if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
1099251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL);
1100251881Speter  return err;
1101251881Speter}
1102251881Speter
1103251881Spetersvn_error_t *
1104251881Spetersvn_cl__check_target_is_local_path(const char *target)
1105251881Speter{
1106251881Speter  if (svn_path_is_url(target))
1107251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1108251881Speter                             _("'%s' is not a local path"), target);
1109251881Speter  return SVN_NO_ERROR;
1110251881Speter}
1111251881Speter
1112251881Spetersvn_error_t *
1113251881Spetersvn_cl__check_targets_are_local_paths(const apr_array_header_t *targets)
1114251881Speter{
1115251881Speter  int i;
1116251881Speter
1117251881Speter  for (i = 0; i < targets->nelts; i++)
1118251881Speter    {
1119251881Speter      const char *target = APR_ARRAY_IDX(targets, i, const char *);
1120251881Speter
1121251881Speter      SVN_ERR(svn_cl__check_target_is_local_path(target));
1122251881Speter    }
1123251881Speter  return SVN_NO_ERROR;
1124251881Speter}
1125251881Speter
1126251881Speterconst char *
1127251881Spetersvn_cl__local_style_skip_ancestor(const char *parent_path,
1128251881Speter                                  const char *path,
1129251881Speter                                  apr_pool_t *pool)
1130251881Speter{
1131251881Speter  const char *relpath = NULL;
1132251881Speter
1133251881Speter  if (parent_path)
1134251881Speter    relpath = svn_dirent_skip_ancestor(parent_path, path);
1135251881Speter
1136251881Speter  return svn_dirent_local_style(relpath ? relpath : path, pool);
1137251881Speter}
1138251881Speter
1139251881Spetersvn_error_t *
1140251881Spetersvn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
1141251881Speter                                               const char *propname,
1142251881Speter                                               const svn_string_t *propval,
1143251881Speter                                               apr_pool_t *scratch_pool)
1144251881Speter{
1145251881Speter  if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
1146251881Speter    {
1147251881Speter      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1148251881Speter      int i;
1149251881Speter
1150251881Speter      for (i = 0; i < targets->nelts; i++)
1151251881Speter        {
1152251881Speter          const char *detected_mimetype;
1153251881Speter          const char *target = APR_ARRAY_IDX(targets, i, const char *);
1154251881Speter          const char *local_abspath;
1155251881Speter          const svn_string_t *canon_propval;
1156251881Speter          svn_node_kind_t node_kind;
1157251881Speter
1158251881Speter          svn_pool_clear(iterpool);
1159251881Speter
1160251881Speter          SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
1161251881Speter          SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool));
1162251881Speter          if (node_kind != svn_node_file)
1163251881Speter            continue;
1164251881Speter
1165251881Speter          SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval,
1166251881Speter                                               propname, propval,
1167251881Speter                                               local_abspath,
1168251881Speter                                               svn_node_file,
1169251881Speter                                               FALSE, NULL, NULL,
1170251881Speter                                               iterpool));
1171251881Speter
1172251881Speter          if (svn_mime_type_is_binary(canon_propval->data))
1173251881Speter            {
1174251881Speter              SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype,
1175251881Speter                                              local_abspath, NULL,
1176251881Speter                                              iterpool));
1177251881Speter              if (detected_mimetype == NULL ||
1178251881Speter                  !svn_mime_type_is_binary(detected_mimetype))
1179251881Speter                svn_error_clear(svn_cmdline_fprintf(stderr, iterpool,
1180251881Speter                  _("svn: warning: '%s' is a binary mime-type but file '%s' "
1181251881Speter                    "looks like text; diff, merge, blame, and other "
1182251881Speter                    "operations will stop working on this file\n"),
1183251881Speter                    canon_propval->data,
1184251881Speter                    svn_dirent_local_style(local_abspath, iterpool)));
1185251881Speter
1186251881Speter            }
1187251881Speter        }
1188251881Speter      svn_pool_destroy(iterpool);
1189251881Speter    }
1190251881Speter
1191251881Speter  return SVN_NO_ERROR;
1192251881Speter}
1193251881Speter
1194