1251881Speter/*
2251881Speter * ====================================================================
3251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
4251881Speter *    or more contributor license agreements.  See the NOTICE file
5251881Speter *    distributed with this work for additional information
6251881Speter *    regarding copyright ownership.  The ASF licenses this file
7251881Speter *    to you under the Apache License, Version 2.0 (the
8251881Speter *    "License"); you may not use this file except in compliance
9251881Speter *    with the License.  You may obtain a copy of the License at
10251881Speter *
11251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
12251881Speter *
13251881Speter *    Unless required by applicable law or agreed to in writing,
14251881Speter *    software distributed under the License is distributed on an
15251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16251881Speter *    KIND, either express or implied.  See the License for the
17251881Speter *    specific language governing permissions and limitations
18251881Speter *    under the License.
19251881Speter * ====================================================================
20251881Speter */
21251881Speter
22251881Speter#include "svn_cmdline.h"
23251881Speter#include "svn_dirent_uri.h"
24251881Speter#include "svn_pools.h"
25251881Speter#include "svn_wc.h"
26251881Speter#include "svn_utf.h"
27251881Speter#include "svn_opt.h"
28251881Speter#include "svn_version.h"
29251881Speter
30251881Speter#include "private/svn_opt_private.h"
31251881Speter#include "private/svn_cmdline_private.h"
32262253Speter#include "private/svn_subr_private.h"
33251881Speter
34251881Speter#include "svn_private_config.h"
35251881Speter
36251881Speter#define SVNVERSION_OPT_VERSION SVN_OPT_FIRST_LONGOPT_ID
37251881Speter
38251881Speter
39251881Speterstatic svn_error_t *
40251881Speterversion(svn_boolean_t quiet, apr_pool_t *pool)
41251881Speter{
42251881Speter  return svn_opt_print_help4(NULL, "svnversion", TRUE, quiet, FALSE,
43251881Speter                             NULL, NULL, NULL, NULL, NULL, NULL, pool);
44251881Speter}
45251881Speter
46251881Speterstatic void
47251881Speterusage(apr_pool_t *pool)
48251881Speter{
49251881Speter  svn_error_clear(svn_cmdline_fprintf
50251881Speter                  (stderr, pool, _("Type 'svnversion --help' for usage.\n")));
51251881Speter  exit(1);
52251881Speter}
53251881Speter
54251881Speter
55251881Speterstatic void
56251881Speterhelp(const apr_getopt_option_t *options, apr_pool_t *pool)
57251881Speter{
58251881Speter  svn_error_clear
59251881Speter    (svn_cmdline_fprintf
60251881Speter     (stdout, pool,
61251881Speter      _("usage: svnversion [OPTIONS] [WC_PATH [TRAIL_URL]]\n\n"
62251881Speter        "  Produce a compact version identifier for the working copy path\n"
63251881Speter        "  WC_PATH.  TRAIL_URL is the trailing portion of the URL used to\n"
64251881Speter        "  determine if WC_PATH itself is switched (detection of switches\n"
65251881Speter        "  within WC_PATH does not rely on TRAIL_URL).  The version identifier\n"
66251881Speter        "  is written to standard output.  For example:\n"
67251881Speter        "\n"
68251881Speter        "    $ svnversion . /repos/svn/trunk\n"
69251881Speter        "    4168\n"
70251881Speter        "\n"
71251881Speter        "  The version identifier will be a single number if the working\n"
72251881Speter        "  copy is single revision, unmodified, not switched and with\n"
73251881Speter        "  a URL that matches the TRAIL_URL argument.  If the working\n"
74251881Speter        "  copy is unusual the version identifier will be more complex:\n"
75251881Speter        "\n"
76251881Speter        "   4123:4168     mixed revision working copy\n"
77251881Speter        "   4168M         modified working copy\n"
78251881Speter        "   4123S         switched working copy\n"
79251881Speter        "   4123P         partial working copy, from a sparse checkout\n"
80251881Speter        "   4123:4168MS   mixed revision, modified, switched working copy\n"
81251881Speter        "\n"
82251881Speter        "  If WC_PATH is an unversioned path, the program will output\n"
83251881Speter        "  'Unversioned directory' or 'Unversioned file'.  If WC_PATH is\n"
84251881Speter        "  an added or copied or moved path, the program will output\n"
85251881Speter        "  'Uncommitted local addition, copy or move'.\n"
86251881Speter        "\n"
87251881Speter        "  If invoked without arguments WC_PATH will be the current directory.\n"
88251881Speter        "\n"
89251881Speter        "Valid options:\n")));
90251881Speter  while (options->description)
91251881Speter    {
92251881Speter      const char *optstr;
93251881Speter      svn_opt_format_option(&optstr, options, TRUE, pool);
94251881Speter      svn_error_clear(svn_cmdline_fprintf(stdout, pool, "  %s\n", optstr));
95251881Speter      ++options;
96251881Speter    }
97251881Speter  svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n"));
98251881Speter  exit(0);
99251881Speter}
100251881Speter
101251881Speter
102251881Speter/* Version compatibility check */
103251881Speterstatic svn_error_t *
104251881Spetercheck_lib_versions(void)
105251881Speter{
106251881Speter  static const svn_version_checklist_t checklist[] =
107251881Speter    {
108251881Speter      { "svn_subr",   svn_subr_version },
109251881Speter      { "svn_wc",     svn_wc_version },
110251881Speter      { NULL, NULL }
111251881Speter    };
112251881Speter  SVN_VERSION_DEFINE(my_version);
113251881Speter
114262253Speter  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
115251881Speter}
116251881Speter
117251881Speter/*
118251881Speter * Why is this not an svn subcommand?  I have this vague idea that it could
119251881Speter * be run as part of the build process, with the output embedded in the svn
120251881Speter * program.  Obviously we don't want to have to run svn when building svn.
121251881Speter */
122251881Speterint
123251881Spetermain(int argc, const char *argv[])
124251881Speter{
125251881Speter  const char *wc_path, *trail_url;
126251881Speter  const char *local_abspath;
127251881Speter  apr_pool_t *pool;
128251881Speter  svn_wc_revision_status_t *res;
129251881Speter  svn_boolean_t no_newline = FALSE, committed = FALSE;
130251881Speter  svn_error_t *err;
131251881Speter  apr_getopt_t *os;
132251881Speter  svn_wc_context_t *wc_ctx;
133251881Speter  svn_boolean_t quiet = FALSE;
134251881Speter  svn_boolean_t is_version = FALSE;
135251881Speter  const apr_getopt_option_t options[] =
136251881Speter    {
137251881Speter      {"no-newline", 'n', 0, N_("do not output the trailing newline")},
138251881Speter      {"committed",  'c', 0, N_("last changed rather than current revisions")},
139251881Speter      {"help", 'h', 0, N_("display this help")},
140251881Speter      {"version", SVNVERSION_OPT_VERSION, 0,
141251881Speter       N_("show program version information")},
142251881Speter      {"quiet",         'q', 0,
143251881Speter       N_("no progress (only errors) to stderr")},
144251881Speter      {0,             0,  0,  0}
145251881Speter    };
146251881Speter
147251881Speter  /* Initialize the app. */
148251881Speter  if (svn_cmdline_init("svnversion", stderr) != EXIT_SUCCESS)
149251881Speter    return EXIT_FAILURE;
150251881Speter
151251881Speter  /* Create our top-level pool.  Use a separate mutexless allocator,
152251881Speter   * given this application is single threaded.
153251881Speter   */
154251881Speter  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
155251881Speter
156251881Speter  /* Check library versions */
157251881Speter  err = check_lib_versions();
158251881Speter  if (err)
159251881Speter    return svn_cmdline_handle_exit_error(err, pool, "svnversion: ");
160251881Speter
161251881Speter#if defined(WIN32) || defined(__CYGWIN__)
162251881Speter  /* Set the working copy administrative directory name. */
163251881Speter  if (getenv("SVN_ASP_DOT_NET_HACK"))
164251881Speter    {
165251881Speter      err = svn_wc_set_adm_dir("_svn", pool);
166251881Speter      if (err)
167251881Speter        return svn_cmdline_handle_exit_error(err, pool, "svnversion: ");
168251881Speter    }
169251881Speter#endif
170251881Speter
171251881Speter  err = svn_cmdline__getopt_init(&os, argc, argv, pool);
172251881Speter  if (err)
173251881Speter    return svn_cmdline_handle_exit_error(err, pool, "svnversion: ");
174251881Speter
175251881Speter  os->interleave = 1;
176251881Speter  while (1)
177251881Speter    {
178251881Speter      int opt;
179251881Speter      const char *arg;
180251881Speter      apr_status_t status = apr_getopt_long(os, options, &opt, &arg);
181251881Speter      if (APR_STATUS_IS_EOF(status))
182251881Speter        break;
183251881Speter      if (status != APR_SUCCESS)
184251881Speter        usage(pool);  /* this will exit() */
185251881Speter
186251881Speter      switch (opt)
187251881Speter        {
188251881Speter        case 'n':
189251881Speter          no_newline = TRUE;
190251881Speter          break;
191251881Speter        case 'c':
192251881Speter          committed = TRUE;
193251881Speter          break;
194251881Speter        case 'q':
195251881Speter          quiet = TRUE;
196251881Speter          break;
197251881Speter        case 'h':
198251881Speter          help(options, pool);
199251881Speter          break;
200251881Speter        case SVNVERSION_OPT_VERSION:
201251881Speter          is_version = TRUE;
202251881Speter          break;
203251881Speter        default:
204251881Speter          usage(pool);  /* this will exit() */
205251881Speter        }
206251881Speter    }
207251881Speter
208251881Speter  if (is_version)
209251881Speter    {
210251881Speter      SVN_INT_ERR(version(quiet, pool));
211251881Speter      exit(0);
212251881Speter    }
213251881Speter  if (os->ind > argc || os->ind < argc - 2)
214251881Speter    usage(pool);  /* this will exit() */
215251881Speter
216251881Speter  SVN_INT_ERR(svn_utf_cstring_to_utf8(&wc_path,
217251881Speter                                      (os->ind < argc) ? os->argv[os->ind]
218251881Speter                                                       : ".",
219251881Speter                                      pool));
220251881Speter
221251881Speter  SVN_INT_ERR(svn_opt__arg_canonicalize_path(&wc_path, wc_path, pool));
222251881Speter  SVN_INT_ERR(svn_dirent_get_absolute(&local_abspath, wc_path, pool));
223251881Speter  SVN_INT_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool));
224251881Speter
225251881Speter  if (os->ind+1 < argc)
226251881Speter    SVN_INT_ERR(svn_utf_cstring_to_utf8(&trail_url, os->argv[os->ind+1],
227251881Speter                                        pool));
228251881Speter  else
229251881Speter    trail_url = NULL;
230251881Speter
231251881Speter  err = svn_wc_revision_status2(&res, wc_ctx, local_abspath, trail_url,
232251881Speter                                committed, NULL, NULL, pool, pool);
233251881Speter
234251881Speter  if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
235251881Speter              || err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY))
236251881Speter    {
237251881Speter      svn_node_kind_t kind;
238251881Speter      svn_boolean_t special;
239251881Speter
240251881Speter      svn_error_clear(err);
241251881Speter
242251881Speter      SVN_INT_ERR(svn_io_check_special_path(local_abspath, &kind, &special,
243251881Speter                                            pool));
244251881Speter
245251881Speter      if (special)
246251881Speter        SVN_INT_ERR(svn_cmdline_printf(pool, _("Unversioned symlink%s"),
247251881Speter                                       no_newline ? "" : "\n"));
248251881Speter      else if (kind == svn_node_dir)
249251881Speter        SVN_INT_ERR(svn_cmdline_printf(pool, _("Unversioned directory%s"),
250251881Speter                                       no_newline ? "" : "\n"));
251251881Speter      else if (kind == svn_node_file)
252251881Speter        SVN_INT_ERR(svn_cmdline_printf(pool, _("Unversioned file%s"),
253251881Speter                                       no_newline ? "" : "\n"));
254251881Speter      else
255251881Speter        {
256251881Speter          SVN_INT_ERR(svn_cmdline_fprintf(stderr, pool,
257251881Speter                                          kind == svn_node_none
258251881Speter                                           ? _("'%s' doesn't exist\n")
259251881Speter                                           : _("'%s' is of unknown type\n"),
260251881Speter                                          svn_dirent_local_style(local_abspath,
261251881Speter                                                                 pool)));
262251881Speter          svn_pool_destroy(pool);
263251881Speter          return EXIT_FAILURE;
264251881Speter        }
265251881Speter      svn_pool_destroy(pool);
266251881Speter      return EXIT_SUCCESS;
267251881Speter    }
268251881Speter
269251881Speter  SVN_INT_ERR(err);
270251881Speter
271251881Speter  if (! SVN_IS_VALID_REVNUM(res->min_rev))
272251881Speter    {
273251881Speter      /* Local uncommitted modifications, no revision info was found. */
274251881Speter      SVN_INT_ERR(svn_cmdline_printf(pool, _("Uncommitted local addition, "
275251881Speter                                             "copy or move%s"),
276251881Speter                                             no_newline ? "" : "\n"));
277251881Speter      svn_pool_destroy(pool);
278251881Speter      return EXIT_SUCCESS;
279251881Speter    }
280251881Speter
281251881Speter  /* Build compact '123[:456]M?S?' string. */
282251881Speter  SVN_INT_ERR(svn_cmdline_printf(pool, "%ld", res->min_rev));
283251881Speter  if (res->min_rev != res->max_rev)
284251881Speter    SVN_INT_ERR(svn_cmdline_printf(pool, ":%ld", res->max_rev));
285251881Speter  if (res->modified)
286251881Speter    SVN_INT_ERR(svn_cmdline_fputs("M", stdout, pool));
287251881Speter  if (res->switched)
288251881Speter    SVN_INT_ERR(svn_cmdline_fputs("S", stdout, pool));
289251881Speter  if (res->sparse_checkout)
290251881Speter    SVN_INT_ERR(svn_cmdline_fputs("P", stdout, pool));
291251881Speter
292251881Speter  if (! no_newline)
293251881Speter    SVN_INT_ERR(svn_cmdline_fputs("\n", stdout, pool));
294251881Speter
295251881Speter  svn_pool_destroy(pool);
296251881Speter
297251881Speter  /* Flush stdout to make sure that the user will see any printing errors. */
298251881Speter  SVN_INT_ERR(svn_cmdline_fflush(stdout));
299251881Speter
300251881Speter  return EXIT_SUCCESS;
301251881Speter}
302