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
38 static svn_error_t *
version(svn_boolean_t quiet,apr_pool_t * pool)39 version(svn_boolean_t quiet, apr_pool_t *pool)
40 {
41 return svn_opt_print_help5(NULL, "svnversion", TRUE, quiet, FALSE,
42 NULL, NULL, NULL, NULL, NULL, NULL, pool);
43 }
44
45 static void
usage(apr_pool_t * pool)46 usage(apr_pool_t *pool)
47 {
48 svn_error_clear(svn_cmdline_fprintf
49 (stderr, pool, _("Type 'svnversion --help' for usage.\n")));
50 }
51
52
53 static void
help(const apr_getopt_option_t * options,apr_pool_t * pool)54 help(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 */
103 static svn_error_t *
check_lib_versions(void)104 check_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 */
126 static svn_error_t *
sub_main(int * exit_code,int argc,const char * argv[],apr_pool_t * pool)127 sub_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
291 int
main(int argc,const char * argv[])292 main(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