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