1/*
2 * null-blame-cmd.c -- Subversion export 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_client.h"
31#include "svn_cmdline.h"
32#include "svn_error.h"
33#include "svn_dirent_uri.h"
34#include "svn_path.h"
35#include "svn_pools.h"
36#include "svn_sorts.h"
37#include "cl.h"
38
39#include "svn_private_config.h"
40#include "private/svn_string_private.h"
41#include "private/svn_client_private.h"
42
43struct file_rev_baton {
44  apr_int64_t byte_count;
45  apr_int64_t delta_count;
46  apr_int64_t rev_count;
47};
48
49/* Implements svn_txdelta_window_handler_t */
50static svn_error_t *
51delta_handler(svn_txdelta_window_t *window, void *baton)
52{
53  struct file_rev_baton *frb = baton;
54
55  if (window != NULL)
56    frb->byte_count += window->tview_len;
57
58  return SVN_NO_ERROR;
59}
60
61/* Implementes svn_file_rev_handler_t */
62static svn_error_t *
63file_rev_handler(void *baton, const char *path, svn_revnum_t revnum,
64                 apr_hash_t *rev_props,
65                 svn_boolean_t merged_revision,
66                 svn_txdelta_window_handler_t *content_delta_handler,
67                 void **content_delta_baton,
68                 apr_array_header_t *prop_diffs,
69                 apr_pool_t *pool)
70{
71  struct file_rev_baton *frb = baton;
72
73  frb->rev_count++;
74
75  if (content_delta_handler)
76    {
77      *content_delta_handler = delta_handler;
78      *content_delta_baton = baton;
79      frb->delta_count++;
80    }
81
82  return SVN_NO_ERROR;
83}
84
85static svn_error_t *
86bench_null_blame(const char *target,
87                 const svn_opt_revision_t *peg_revision,
88                 const svn_opt_revision_t *start,
89                 const svn_opt_revision_t *end,
90                 svn_boolean_t include_merged_revisions,
91                 svn_boolean_t quiet,
92                 svn_client_ctx_t *ctx,
93                 apr_pool_t *pool)
94{
95  struct file_rev_baton frb = { 0, 0, 0};
96  svn_ra_session_t *ra_session;
97  svn_revnum_t start_revnum, end_revnum;
98  svn_boolean_t backwards;
99  const char *target_abspath_or_url;
100
101  if (start->kind == svn_opt_revision_unspecified
102      || end->kind == svn_opt_revision_unspecified)
103    return svn_error_create
104      (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
105
106  if (svn_path_is_url(target))
107    target_abspath_or_url = target;
108  else
109    SVN_ERR(svn_dirent_get_absolute(&target_abspath_or_url, target, pool));
110
111
112  /* Get an RA plugin for this filesystem object. */
113  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, NULL,
114                                            target, NULL, peg_revision,
115                                            peg_revision,
116                                            ctx, pool));
117
118  SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx,
119                                          target_abspath_or_url, ra_session,
120                                          start, pool));
121
122  SVN_ERR(svn_client__get_revision_number(&end_revnum, NULL, ctx->wc_ctx,
123                                          target_abspath_or_url, ra_session,
124                                          end, pool));
125
126  {
127    svn_client__pathrev_t *loc;
128    svn_opt_revision_t younger_end;
129    younger_end.kind = svn_opt_revision_number;
130    younger_end.value.number = MAX(start_revnum, end_revnum);
131
132    SVN_ERR(svn_client__resolve_rev_and_url(&loc, ra_session,
133                                            target, peg_revision,
134                                            &younger_end,
135                                            ctx, pool));
136
137    /* Make the session point to the real URL. */
138    SVN_ERR(svn_ra_reparent(ra_session, loc->url, pool));
139  }
140
141  backwards = (start_revnum > end_revnum);
142
143  /* Collect all blame information.
144     We need to ensure that we get one revision before the start_rev,
145     if available so that we can know what was actually changed in the start
146     revision. */
147  SVN_ERR(svn_ra_get_file_revs2(ra_session, "",
148                                backwards ? start_revnum
149                                          : MAX(0, start_revnum-1),
150                                end_revnum,
151                                include_merged_revisions,
152                                file_rev_handler, &frb, pool));
153
154  if (!quiet)
155    SVN_ERR(svn_cmdline_printf(pool,
156                               _("%15s revisions\n"
157                                 "%15s deltas\n"
158                                 "%15s bytes in deltas\n"),
159                               svn__ui64toa_sep(frb.rev_count, ',', pool),
160                               svn__ui64toa_sep(frb.delta_count, ',', pool),
161                               svn__ui64toa_sep(frb.byte_count, ',', pool)));
162
163  return SVN_NO_ERROR;
164}
165
166
167/* This implements the `svn_opt_subcommand_t' interface. */
168svn_error_t *
169svn_cl__null_blame(apr_getopt_t *os,
170                   void *baton,
171                   apr_pool_t *pool)
172{
173  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
174  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
175  apr_pool_t *iterpool;
176  apr_array_header_t *targets;
177  int i;
178  svn_boolean_t end_revision_unspecified = FALSE;
179  svn_boolean_t seen_nonexistent_target = FALSE;
180
181  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
182                                                      opt_state->targets,
183                                                      ctx, FALSE, pool));
184
185  /* Blame needs a file on which to operate. */
186  if (! targets->nelts)
187    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
188
189  if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
190    {
191      if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
192        {
193          /* In the case that -rX was specified, we actually want to set the
194             range to be -r1:X. */
195
196          opt_state->end_revision = opt_state->start_revision;
197          opt_state->start_revision.kind = svn_opt_revision_number;
198          opt_state->start_revision.value.number = 1;
199        }
200      else
201        end_revision_unspecified = TRUE;
202    }
203
204  if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
205    {
206      opt_state->start_revision.kind = svn_opt_revision_number;
207      opt_state->start_revision.value.number = 1;
208    }
209
210  /* The final conclusion from issue #2431 is that blame info
211     is client output (unlike 'svn cat' which plainly cats the file),
212     so the EOL style should be the platform local one.
213  */
214  iterpool = svn_pool_create(pool);
215
216  for (i = 0; i < targets->nelts; i++)
217    {
218      svn_error_t *err;
219      const char *target = APR_ARRAY_IDX(targets, i, const char *);
220      const char *parsed_path;
221      svn_opt_revision_t peg_revision;
222
223      svn_pool_clear(iterpool);
224      SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
225
226      /* Check for a peg revision. */
227      SVN_ERR(svn_opt_parse_path(&peg_revision, &parsed_path, target,
228                                 iterpool));
229
230      if (end_revision_unspecified)
231        {
232          if (peg_revision.kind != svn_opt_revision_unspecified)
233            opt_state->end_revision = peg_revision;
234          else if (svn_path_is_url(target))
235            opt_state->end_revision.kind = svn_opt_revision_head;
236          else
237            opt_state->end_revision.kind = svn_opt_revision_working;
238        }
239
240      err = bench_null_blame(parsed_path,
241                             &peg_revision,
242                             &opt_state->start_revision,
243                             &opt_state->end_revision,
244                             opt_state->use_merge_history,
245                             opt_state->quiet,
246                             ctx,
247                             iterpool);
248
249      if (err)
250        {
251          if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
252                   err->apr_err == SVN_ERR_ENTRY_NOT_FOUND ||
253                   err->apr_err == SVN_ERR_FS_NOT_FILE ||
254                   err->apr_err == SVN_ERR_FS_NOT_FOUND)
255            {
256              svn_handle_warning2(stderr, err, "svn: ");
257              svn_error_clear(err);
258              err = NULL;
259              seen_nonexistent_target = TRUE;
260            }
261          else
262            {
263              return svn_error_trace(err);
264            }
265        }
266    }
267  svn_pool_destroy(iterpool);
268
269  if (seen_nonexistent_target)
270    return svn_error_create(
271      SVN_ERR_ILLEGAL_TARGET, NULL,
272      _("Could not perform blame on all targets because some "
273        "targets don't exist"));
274  else
275    return SVN_NO_ERROR;
276}
277