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