1/* 2 * cat.c: implementation of the 'cat' command 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include "svn_hash.h" 31#include "svn_client.h" 32#include "svn_string.h" 33#include "svn_error.h" 34#include "svn_subst.h" 35#include "svn_io.h" 36#include "svn_time.h" 37#include "svn_dirent_uri.h" 38#include "svn_path.h" 39#include "svn_props.h" 40#include "client.h" 41 42#include "svn_private_config.h" 43#include "private/svn_wc_private.h" 44 45 46/*** Code. ***/ 47 48svn_error_t * 49svn_client__get_normalized_stream(svn_stream_t **normal_stream, 50 svn_wc_context_t *wc_ctx, 51 const char *local_abspath, 52 const svn_opt_revision_t *revision, 53 svn_boolean_t expand_keywords, 54 svn_boolean_t normalize_eols, 55 svn_cancel_func_t cancel_func, 56 void *cancel_baton, 57 apr_pool_t *result_pool, 58 apr_pool_t *scratch_pool) 59{ 60 apr_hash_t *kw = NULL; 61 svn_subst_eol_style_t style; 62 apr_hash_t *props; 63 svn_string_t *eol_style, *keywords, *special; 64 const char *eol = NULL; 65 svn_boolean_t local_mod = FALSE; 66 svn_stream_t *input; 67 svn_node_kind_t kind; 68 69 SVN_ERR_ASSERT(SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind)); 70 71 SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, local_abspath, 72 (revision->kind != svn_opt_revision_working), 73 FALSE, scratch_pool)); 74 75 if (kind == svn_node_unknown || kind == svn_node_none) 76 return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, 77 _("'%s' is not under version control"), 78 svn_dirent_local_style(local_abspath, 79 scratch_pool)); 80 if (kind != svn_node_file) 81 return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, NULL, 82 _("'%s' refers to a directory"), 83 svn_dirent_local_style(local_abspath, 84 scratch_pool)); 85 86 if (revision->kind != svn_opt_revision_working) 87 { 88 SVN_ERR(svn_wc_get_pristine_contents2(&input, wc_ctx, local_abspath, 89 result_pool, scratch_pool)); 90 if (input == NULL) 91 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 92 _("'%s' has no pristine version until it is committed"), 93 svn_dirent_local_style(local_abspath, scratch_pool)); 94 95 SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, local_abspath, 96 scratch_pool, scratch_pool)); 97 } 98 else 99 { 100 svn_wc_status3_t *status; 101 102 SVN_ERR(svn_stream_open_readonly(&input, local_abspath, scratch_pool, 103 result_pool)); 104 105 SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, scratch_pool, 106 scratch_pool)); 107 SVN_ERR(svn_wc_status3(&status, wc_ctx, local_abspath, scratch_pool, 108 scratch_pool)); 109 if (status->node_status != svn_wc_status_normal) 110 local_mod = TRUE; 111 } 112 113 eol_style = svn_hash_gets(props, SVN_PROP_EOL_STYLE); 114 keywords = svn_hash_gets(props, SVN_PROP_KEYWORDS); 115 special = svn_hash_gets(props, SVN_PROP_SPECIAL); 116 117 if (eol_style) 118 svn_subst_eol_style_from_value(&style, &eol, eol_style->data); 119 120 if (keywords) 121 { 122 svn_revnum_t changed_rev; 123 const char *rev_str; 124 const char *author; 125 const char *url; 126 apr_time_t tm; 127 const char *repos_root_url; 128 const char *repos_relpath; 129 130 SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, &tm, &author, wc_ctx, 131 local_abspath, scratch_pool, 132 scratch_pool)); 133 SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, &repos_root_url, 134 NULL, 135 wc_ctx, local_abspath, scratch_pool, 136 scratch_pool)); 137 url = svn_path_url_add_component2(repos_root_url, repos_relpath, 138 scratch_pool); 139 140 if (local_mod) 141 { 142 /* For locally modified files, we'll append an 'M' 143 to the revision number, and set the author to 144 "(local)" since we can't always determine the 145 current user's username */ 146 rev_str = apr_psprintf(scratch_pool, "%ldM", changed_rev); 147 author = _("(local)"); 148 149 if (! special) 150 { 151 /* Use the modified time from the working copy for files */ 152 SVN_ERR(svn_io_file_affected_time(&tm, local_abspath, 153 scratch_pool)); 154 } 155 } 156 else 157 { 158 rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev); 159 } 160 161 SVN_ERR(svn_subst_build_keywords3(&kw, keywords->data, rev_str, url, 162 repos_root_url, tm, author, 163 scratch_pool)); 164 } 165 166 /* Wrap the output stream if translation is needed. */ 167 if (eol != NULL || kw != NULL) 168 input = svn_subst_stream_translated( 169 input, 170 (eol_style && normalize_eols) ? SVN_SUBST_NATIVE_EOL_STR : eol, 171 FALSE, kw, expand_keywords, result_pool); 172 173 *normal_stream = input; 174 175 return SVN_NO_ERROR; 176} 177 178svn_error_t * 179svn_client_cat3(apr_hash_t **returned_props, 180 svn_stream_t *out, 181 const char *path_or_url, 182 const svn_opt_revision_t *peg_revision, 183 const svn_opt_revision_t *revision, 184 svn_boolean_t expand_keywords, 185 svn_client_ctx_t *ctx, 186 apr_pool_t *result_pool, 187 apr_pool_t *scratch_pool) 188{ 189 svn_ra_session_t *ra_session; 190 svn_client__pathrev_t *loc; 191 svn_string_t *eol_style; 192 svn_string_t *keywords; 193 apr_hash_t *props = NULL; 194 const char *repos_root_url; 195 svn_stream_t *output = out; 196 svn_error_t *err; 197 198 /* ### Inconsistent default revision logic in this command. */ 199 if (peg_revision->kind == svn_opt_revision_unspecified) 200 { 201 peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision, 202 path_or_url); 203 revision = svn_cl__rev_default_to_head_or_base(revision, path_or_url); 204 } 205 else 206 { 207 revision = svn_cl__rev_default_to_peg(revision, peg_revision); 208 } 209 210 if (! svn_path_is_url(path_or_url) 211 && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind) 212 && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind)) 213 { 214 const char *local_abspath; 215 svn_stream_t *normal_stream; 216 217 SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url, 218 scratch_pool)); 219 SVN_ERR(svn_client__get_normalized_stream(&normal_stream, ctx->wc_ctx, 220 local_abspath, revision, 221 expand_keywords, FALSE, 222 ctx->cancel_func, ctx->cancel_baton, 223 scratch_pool, scratch_pool)); 224 225 /* We don't promise to close output, so disown it to ensure we don't. */ 226 output = svn_stream_disown(output, scratch_pool); 227 228 if (returned_props) 229 SVN_ERR(svn_wc_prop_list2(returned_props, ctx->wc_ctx, local_abspath, 230 result_pool, scratch_pool)); 231 232 return svn_error_trace(svn_stream_copy3(normal_stream, output, 233 ctx->cancel_func, 234 ctx->cancel_baton, scratch_pool)); 235 } 236 237 /* Get an RA plugin for this filesystem object. */ 238 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc, 239 path_or_url, NULL, 240 peg_revision, 241 revision, ctx, scratch_pool)); 242 243 /* Find the repos root URL */ 244 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, scratch_pool)); 245 246 /* Grab some properties we need to know in order to figure out if anything 247 special needs to be done with this file. */ 248 err = svn_ra_get_file(ra_session, "", loc->rev, NULL, NULL, &props, 249 result_pool); 250 if (err) 251 { 252 if (err->apr_err == SVN_ERR_FS_NOT_FILE) 253 { 254 return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, err, 255 _("URL '%s' refers to a directory"), 256 loc->url); 257 } 258 else 259 { 260 return svn_error_trace(err); 261 } 262 } 263 264 eol_style = svn_hash_gets(props, SVN_PROP_EOL_STYLE); 265 keywords = svn_hash_gets(props, SVN_PROP_KEYWORDS); 266 267 if (eol_style || keywords) 268 { 269 /* It's a file with no special eol style or keywords. */ 270 svn_subst_eol_style_t eol; 271 const char *eol_str; 272 apr_hash_t *kw; 273 274 if (eol_style) 275 svn_subst_eol_style_from_value(&eol, &eol_str, eol_style->data); 276 else 277 { 278 eol = svn_subst_eol_style_none; 279 eol_str = NULL; 280 } 281 282 283 if (keywords && expand_keywords) 284 { 285 svn_string_t *cmt_rev, *cmt_date, *cmt_author; 286 apr_time_t when = 0; 287 288 cmt_rev = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_REV); 289 cmt_date = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_DATE); 290 cmt_author = svn_hash_gets(props, SVN_PROP_ENTRY_LAST_AUTHOR); 291 if (cmt_date) 292 SVN_ERR(svn_time_from_cstring(&when, cmt_date->data, scratch_pool)); 293 294 SVN_ERR(svn_subst_build_keywords3(&kw, keywords->data, 295 cmt_rev->data, loc->url, 296 repos_root_url, when, 297 cmt_author ? 298 cmt_author->data : NULL, 299 scratch_pool)); 300 } 301 else 302 kw = NULL; 303 304 /* Interject a translating stream */ 305 output = svn_subst_stream_translated(svn_stream_disown(out, 306 scratch_pool), 307 eol_str, FALSE, kw, TRUE, 308 scratch_pool); 309 } 310 311 if (returned_props) 312 { 313 /* filter entry and WC props */ 314 apr_hash_index_t *hi; 315 const void *key; 316 apr_ssize_t klen; 317 318 for (hi = apr_hash_first(scratch_pool, props); 319 hi; hi = apr_hash_next(hi)) 320 { 321 apr_hash_this(hi, &key, &klen, NULL); 322 if (!svn_wc_is_normal_prop(key)) 323 apr_hash_set(props, key, klen, NULL); 324 } 325 326 *returned_props = props; 327 } 328 329 SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, output, NULL, NULL, 330 scratch_pool)); 331 332 if (out != output) 333 /* Close the interjected stream */ 334 SVN_ERR(svn_stream_close(output)); 335 336 return SVN_NO_ERROR; 337} 338