1/*
2 * ====================================================================
3 *    Licensed to the Apache Software Foundation (ASF) under one
4 *    or more contributor license agreements.  See the NOTICE file
5 *    distributed with this work for additional information
6 *    regarding copyright ownership.  The ASF licenses this file
7 *    to you under the Apache License, Version 2.0 (the
8 *    "License"); you may not use this file except in compliance
9 *    with the License.  You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 *    Unless required by applicable law or agreed to in writing,
14 *    software distributed under the License is distributed on an
15 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 *    KIND, either express or implied.  See the License for the
17 *    specific language governing permissions and limitations
18 *    under the License.
19 * ====================================================================
20 */
21
22#include "svn_cmdline.h"
23#include "svn_dirent_uri.h"
24#include "svn_pools.h"
25#include "svn_wc.h"
26#include "svn_utf.h"
27#include "svn_opt.h"
28#include "svn_version.h"
29
30#include "private/svn_opt_private.h"
31#include "private/svn_cmdline_private.h"
32
33#include "svn_private_config.h"
34
35#define SVNVERSION_OPT_VERSION SVN_OPT_FIRST_LONGOPT_ID
36
37
38static svn_error_t *
39version(svn_boolean_t quiet, apr_pool_t *pool)
40{
41  return svn_opt_print_help4(NULL, "svnversion", TRUE, quiet, FALSE,
42                             NULL, NULL, NULL, NULL, NULL, NULL, pool);
43}
44
45static void
46usage(apr_pool_t *pool)
47{
48  svn_error_clear(svn_cmdline_fprintf
49                  (stderr, pool, _("Type 'svnversion --help' for usage.\n")));
50}
51
52
53static void
54help(const apr_getopt_option_t *options, apr_pool_t *pool)
55{
56  svn_error_clear
57    (svn_cmdline_fprintf
58     (stdout, pool,
59      _("usage: svnversion [OPTIONS] [WC_PATH [TRAIL_URL]]\n"
60        "Subversion working copy identification tool.\n"
61        "Type 'svnversion --version' to see the program version.\n"
62        "\n"
63        "  Produce a compact version identifier for the working copy path\n"
64        "  WC_PATH.  TRAIL_URL is the trailing portion of the URL used to\n"
65        "  determine if WC_PATH itself is switched (detection of switches\n"
66        "  within WC_PATH does not rely on TRAIL_URL).  The version identifier\n"
67        "  is written to standard output.  For example:\n"
68        "\n"
69        "    $ svnversion . /repos/svn/trunk\n"
70        "    4168\n"
71        "\n"
72        "  The version identifier will be a single number if the working\n"
73        "  copy is single revision, unmodified, not switched and with\n"
74        "  a URL that matches the TRAIL_URL argument.  If the working\n"
75        "  copy is unusual the version identifier will be more complex:\n"
76        "\n"
77        "   4123:4168     mixed revision working copy\n"
78        "   4168M         modified working copy\n"
79        "   4123S         switched working copy\n"
80        "   4123P         partial working copy, from a sparse checkout\n"
81        "   4123:4168MS   mixed revision, modified, switched working copy\n"
82        "\n"
83        "  If WC_PATH is an unversioned path, the program will output\n"
84        "  'Unversioned directory' or 'Unversioned file'.  If WC_PATH is\n"
85        "  an added or copied or moved path, the program will output\n"
86        "  'Uncommitted local addition, copy or move'.\n"
87        "\n"
88        "  If invoked without arguments WC_PATH will be the current directory.\n"
89        "\n"
90        "Valid options:\n")));
91  while (options->description)
92    {
93      const char *optstr;
94      svn_opt_format_option(&optstr, options, TRUE, pool);
95      svn_error_clear(svn_cmdline_fprintf(stdout, pool, "  %s\n", optstr));
96      ++options;
97    }
98  svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n"));
99}
100
101
102/* Version compatibility check */
103static svn_error_t *
104check_lib_versions(void)
105{
106  static const svn_version_checklist_t checklist[] =
107    {
108      { "svn_subr",   svn_subr_version },
109      { "svn_wc",     svn_wc_version },
110      { NULL, NULL }
111    };
112  SVN_VERSION_DEFINE(my_version);
113
114  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
115}
116
117/*
118 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
119 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
120 * return SVN_NO_ERROR.
121 *
122 * Why is this not an svn subcommand?  I have this vague idea that it could
123 * be run as part of the build process, with the output embedded in the svn
124 * program.  Obviously we don't want to have to run svn when building svn.
125 */
126static svn_error_t *
127sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
128{
129  const char *wc_path, *trail_url;
130  const char *local_abspath;
131  svn_wc_revision_status_t *res;
132  svn_boolean_t no_newline = FALSE, committed = FALSE;
133  svn_error_t *err;
134  apr_getopt_t *os;
135  svn_wc_context_t *wc_ctx;
136  svn_boolean_t quiet = FALSE;
137  svn_boolean_t is_version = FALSE;
138  const apr_getopt_option_t options[] =
139    {
140      {"no-newline", 'n', 0, N_("do not output the trailing newline")},
141      {"committed",  'c', 0, N_("last changed rather than current revisions")},
142      {"help", 'h', 0, N_("display this help")},
143      {"version", SVNVERSION_OPT_VERSION, 0,
144       N_("show program version information")},
145      {"quiet",         'q', 0,
146       N_("no progress (only errors) to stderr")},
147      {0,             0,  0,  0}
148    };
149
150  /* Check library versions */
151  SVN_ERR(check_lib_versions());
152
153#if defined(WIN32) || defined(__CYGWIN__)
154  /* Set the working copy administrative directory name. */
155  if (getenv("SVN_ASP_DOT_NET_HACK"))
156    {
157      SVN_ERR(svn_wc_set_adm_dir("_svn", pool));
158    }
159#endif
160
161  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
162
163  os->interleave = 1;
164  while (1)
165    {
166      int opt;
167      const char *arg;
168      apr_status_t status = apr_getopt_long(os, options, &opt, &arg);
169      if (APR_STATUS_IS_EOF(status))
170        break;
171      if (status != APR_SUCCESS)
172        {
173          *exit_code = EXIT_FAILURE;
174          usage(pool);
175          return SVN_NO_ERROR;
176        }
177
178      switch (opt)
179        {
180        case 'n':
181          no_newline = TRUE;
182          break;
183        case 'c':
184          committed = TRUE;
185          break;
186        case 'q':
187          quiet = TRUE;
188          break;
189        case 'h':
190          help(options, pool);
191          return SVN_NO_ERROR;
192        case SVNVERSION_OPT_VERSION:
193          is_version = TRUE;
194          break;
195        default:
196          *exit_code = EXIT_FAILURE;
197          usage(pool);
198          return SVN_NO_ERROR;
199        }
200    }
201
202  if (is_version)
203    {
204      SVN_ERR(version(quiet, pool));
205      return SVN_NO_ERROR;
206    }
207  if (os->ind > argc || os->ind < argc - 2)
208    {
209      *exit_code = EXIT_FAILURE;
210      usage(pool);
211      return SVN_NO_ERROR;
212    }
213
214  SVN_ERR(svn_utf_cstring_to_utf8(&wc_path,
215                                  (os->ind < argc) ? os->argv[os->ind] : ".",
216                                  pool));
217
218  SVN_ERR(svn_opt__arg_canonicalize_path(&wc_path, wc_path, pool));
219  SVN_ERR(svn_dirent_get_absolute(&local_abspath, wc_path, pool));
220  SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool));
221
222  if (os->ind+1 < argc)
223    SVN_ERR(svn_utf_cstring_to_utf8(&trail_url, os->argv[os->ind+1], pool));
224  else
225    trail_url = NULL;
226
227  err = svn_wc_revision_status2(&res, wc_ctx, local_abspath, trail_url,
228                                committed, NULL, NULL, pool, pool);
229
230  if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
231              || err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY))
232    {
233      svn_node_kind_t kind;
234      svn_boolean_t special;
235
236      svn_error_clear(err);
237
238      SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &special, pool));
239
240      if (special)
241        SVN_ERR(svn_cmdline_printf(pool, _("Unversioned symlink%s"),
242                                   no_newline ? "" : "\n"));
243      else if (kind == svn_node_dir)
244        SVN_ERR(svn_cmdline_printf(pool, _("Unversioned directory%s"),
245                                   no_newline ? "" : "\n"));
246      else if (kind == svn_node_file)
247        SVN_ERR(svn_cmdline_printf(pool, _("Unversioned file%s"),
248                                   no_newline ? "" : "\n"));
249      else
250        {
251          SVN_ERR(svn_cmdline_fprintf(stderr, pool,
252                                      kind == svn_node_none
253                                       ? _("'%s' doesn't exist\n")
254                                       : _("'%s' is of unknown type\n"),
255                                      svn_dirent_local_style(local_abspath,
256                                                             pool)));
257          *exit_code = EXIT_FAILURE;
258          return SVN_NO_ERROR;
259        }
260      return SVN_NO_ERROR;
261    }
262
263  SVN_ERR(err);
264
265  if (! SVN_IS_VALID_REVNUM(res->min_rev))
266    {
267      /* Local uncommitted modifications, no revision info was found. */
268      SVN_ERR(svn_cmdline_printf(pool, _("Uncommitted local addition, "
269                                         "copy or move%s"),
270                                 no_newline ? "" : "\n"));
271      return SVN_NO_ERROR;
272    }
273
274  /* Build compact '123[:456]M?S?' string. */
275  SVN_ERR(svn_cmdline_printf(pool, "%ld", res->min_rev));
276  if (res->min_rev != res->max_rev)
277    SVN_ERR(svn_cmdline_printf(pool, ":%ld", res->max_rev));
278  if (res->modified)
279    SVN_ERR(svn_cmdline_fputs("M", stdout, pool));
280  if (res->switched)
281    SVN_ERR(svn_cmdline_fputs("S", stdout, pool));
282  if (res->sparse_checkout)
283    SVN_ERR(svn_cmdline_fputs("P", stdout, pool));
284
285  if (! no_newline)
286    SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
287
288  return SVN_NO_ERROR;
289}
290
291int
292main(int argc, const char *argv[])
293{
294  apr_pool_t *pool;
295  int exit_code = EXIT_SUCCESS;
296  svn_error_t *err;
297
298  /* Initialize the app. */
299  if (svn_cmdline_init("svnversion", stderr) != EXIT_SUCCESS)
300    return EXIT_FAILURE;
301
302  /* Create our top-level pool.  Use a separate mutexless allocator,
303   * given this application is single threaded.
304   */
305  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
306
307  err = sub_main(&exit_code, argc, argv, pool);
308
309  /* Flush stdout and report if it fails. It would be flushed on exit anyway
310     but this makes sure that output is not silently lost if it fails. */
311  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
312
313  if (err)
314    {
315      exit_code = EXIT_FAILURE;
316      svn_cmdline_handle_exit_error(err, NULL, "svnversion: ");
317    }
318
319  svn_pool_destroy(pool);
320  return exit_code;
321}
322