svnversion.c revision 251886
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  exit(1);
51}
52
53
54static void
55help(const apr_getopt_option_t *options, apr_pool_t *pool)
56{
57  svn_error_clear
58    (svn_cmdline_fprintf
59     (stdout, pool,
60      _("usage: svnversion [OPTIONS] [WC_PATH [TRAIL_URL]]\n\n"
61        "  Produce a compact version identifier for the working copy path\n"
62        "  WC_PATH.  TRAIL_URL is the trailing portion of the URL used to\n"
63        "  determine if WC_PATH itself is switched (detection of switches\n"
64        "  within WC_PATH does not rely on TRAIL_URL).  The version identifier\n"
65        "  is written to standard output.  For example:\n"
66        "\n"
67        "    $ svnversion . /repos/svn/trunk\n"
68        "    4168\n"
69        "\n"
70        "  The version identifier will be a single number if the working\n"
71        "  copy is single revision, unmodified, not switched and with\n"
72        "  a URL that matches the TRAIL_URL argument.  If the working\n"
73        "  copy is unusual the version identifier will be more complex:\n"
74        "\n"
75        "   4123:4168     mixed revision working copy\n"
76        "   4168M         modified working copy\n"
77        "   4123S         switched working copy\n"
78        "   4123P         partial working copy, from a sparse checkout\n"
79        "   4123:4168MS   mixed revision, modified, switched working copy\n"
80        "\n"
81        "  If WC_PATH is an unversioned path, the program will output\n"
82        "  'Unversioned directory' or 'Unversioned file'.  If WC_PATH is\n"
83        "  an added or copied or moved path, the program will output\n"
84        "  'Uncommitted local addition, copy or move'.\n"
85        "\n"
86        "  If invoked without arguments WC_PATH will be the current directory.\n"
87        "\n"
88        "Valid options:\n")));
89  while (options->description)
90    {
91      const char *optstr;
92      svn_opt_format_option(&optstr, options, TRUE, pool);
93      svn_error_clear(svn_cmdline_fprintf(stdout, pool, "  %s\n", optstr));
94      ++options;
95    }
96  svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n"));
97  exit(0);
98}
99
100
101/* Version compatibility check */
102static svn_error_t *
103check_lib_versions(void)
104{
105  static const svn_version_checklist_t checklist[] =
106    {
107      { "svn_subr",   svn_subr_version },
108      { "svn_wc",     svn_wc_version },
109      { NULL, NULL }
110    };
111  SVN_VERSION_DEFINE(my_version);
112
113  return svn_ver_check_list(&my_version, checklist);
114}
115
116/*
117 * Why is this not an svn subcommand?  I have this vague idea that it could
118 * be run as part of the build process, with the output embedded in the svn
119 * program.  Obviously we don't want to have to run svn when building svn.
120 */
121int
122main(int argc, const char *argv[])
123{
124  const char *wc_path, *trail_url;
125  const char *local_abspath;
126  apr_pool_t *pool;
127  svn_wc_revision_status_t *res;
128  svn_boolean_t no_newline = FALSE, committed = FALSE;
129  svn_error_t *err;
130  apr_getopt_t *os;
131  svn_wc_context_t *wc_ctx;
132  svn_boolean_t quiet = FALSE;
133  svn_boolean_t is_version = FALSE;
134  const apr_getopt_option_t options[] =
135    {
136      {"no-newline", 'n', 0, N_("do not output the trailing newline")},
137      {"committed",  'c', 0, N_("last changed rather than current revisions")},
138      {"help", 'h', 0, N_("display this help")},
139      {"version", SVNVERSION_OPT_VERSION, 0,
140       N_("show program version information")},
141      {"quiet",         'q', 0,
142       N_("no progress (only errors) to stderr")},
143      {0,             0,  0,  0}
144    };
145
146  /* Initialize the app. */
147  if (svn_cmdline_init("svnversion", stderr) != EXIT_SUCCESS)
148    return EXIT_FAILURE;
149
150  /* Create our top-level pool.  Use a separate mutexless allocator,
151   * given this application is single threaded.
152   */
153  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
154
155  /* Check library versions */
156  err = check_lib_versions();
157  if (err)
158    return svn_cmdline_handle_exit_error(err, pool, "svnversion: ");
159
160#if defined(WIN32) || defined(__CYGWIN__)
161  /* Set the working copy administrative directory name. */
162  if (getenv("SVN_ASP_DOT_NET_HACK"))
163    {
164      err = svn_wc_set_adm_dir("_svn", pool);
165      if (err)
166        return svn_cmdline_handle_exit_error(err, pool, "svnversion: ");
167    }
168#endif
169
170  err = svn_cmdline__getopt_init(&os, argc, argv, pool);
171  if (err)
172    return svn_cmdline_handle_exit_error(err, pool, "svnversion: ");
173
174  os->interleave = 1;
175  while (1)
176    {
177      int opt;
178      const char *arg;
179      apr_status_t status = apr_getopt_long(os, options, &opt, &arg);
180      if (APR_STATUS_IS_EOF(status))
181        break;
182      if (status != APR_SUCCESS)
183        usage(pool);  /* this will exit() */
184
185      switch (opt)
186        {
187        case 'n':
188          no_newline = TRUE;
189          break;
190        case 'c':
191          committed = TRUE;
192          break;
193        case 'q':
194          quiet = TRUE;
195          break;
196        case 'h':
197          help(options, pool);
198          break;
199        case SVNVERSION_OPT_VERSION:
200          is_version = TRUE;
201          break;
202        default:
203          usage(pool);  /* this will exit() */
204        }
205    }
206
207  if (is_version)
208    {
209      SVN_INT_ERR(version(quiet, pool));
210      exit(0);
211    }
212  if (os->ind > argc || os->ind < argc - 2)
213    usage(pool);  /* this will exit() */
214
215  SVN_INT_ERR(svn_utf_cstring_to_utf8(&wc_path,
216                                      (os->ind < argc) ? os->argv[os->ind]
217                                                       : ".",
218                                      pool));
219
220  SVN_INT_ERR(svn_opt__arg_canonicalize_path(&wc_path, wc_path, pool));
221  SVN_INT_ERR(svn_dirent_get_absolute(&local_abspath, wc_path, pool));
222  SVN_INT_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool));
223
224  if (os->ind+1 < argc)
225    SVN_INT_ERR(svn_utf_cstring_to_utf8(&trail_url, os->argv[os->ind+1],
226                                        pool));
227  else
228    trail_url = NULL;
229
230  err = svn_wc_revision_status2(&res, wc_ctx, local_abspath, trail_url,
231                                committed, NULL, NULL, pool, pool);
232
233  if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
234              || err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY))
235    {
236      svn_node_kind_t kind;
237      svn_boolean_t special;
238
239      svn_error_clear(err);
240
241      SVN_INT_ERR(svn_io_check_special_path(local_abspath, &kind, &special,
242                                            pool));
243
244      if (special)
245        SVN_INT_ERR(svn_cmdline_printf(pool, _("Unversioned symlink%s"),
246                                       no_newline ? "" : "\n"));
247      else if (kind == svn_node_dir)
248        SVN_INT_ERR(svn_cmdline_printf(pool, _("Unversioned directory%s"),
249                                       no_newline ? "" : "\n"));
250      else if (kind == svn_node_file)
251        SVN_INT_ERR(svn_cmdline_printf(pool, _("Unversioned file%s"),
252                                       no_newline ? "" : "\n"));
253      else
254        {
255          SVN_INT_ERR(svn_cmdline_fprintf(stderr, pool,
256                                          kind == svn_node_none
257                                           ? _("'%s' doesn't exist\n")
258                                           : _("'%s' is of unknown type\n"),
259                                          svn_dirent_local_style(local_abspath,
260                                                                 pool)));
261          svn_pool_destroy(pool);
262          return EXIT_FAILURE;
263        }
264      svn_pool_destroy(pool);
265      return EXIT_SUCCESS;
266    }
267
268  SVN_INT_ERR(err);
269
270  if (! SVN_IS_VALID_REVNUM(res->min_rev))
271    {
272      /* Local uncommitted modifications, no revision info was found. */
273      SVN_INT_ERR(svn_cmdline_printf(pool, _("Uncommitted local addition, "
274                                             "copy or move%s"),
275                                             no_newline ? "" : "\n"));
276      svn_pool_destroy(pool);
277      return EXIT_SUCCESS;
278    }
279
280  /* Build compact '123[:456]M?S?' string. */
281  SVN_INT_ERR(svn_cmdline_printf(pool, "%ld", res->min_rev));
282  if (res->min_rev != res->max_rev)
283    SVN_INT_ERR(svn_cmdline_printf(pool, ":%ld", res->max_rev));
284  if (res->modified)
285    SVN_INT_ERR(svn_cmdline_fputs("M", stdout, pool));
286  if (res->switched)
287    SVN_INT_ERR(svn_cmdline_fputs("S", stdout, pool));
288  if (res->sparse_checkout)
289    SVN_INT_ERR(svn_cmdline_fputs("P", stdout, pool));
290
291  if (! no_newline)
292    SVN_INT_ERR(svn_cmdline_fputs("\n", stdout, pool));
293
294  svn_pool_destroy(pool);
295
296  /* Flush stdout to make sure that the user will see any printing errors. */
297  SVN_INT_ERR(svn_cmdline_fflush(stdout));
298
299  return EXIT_SUCCESS;
300}
301