2 * blame-cmd.c -- Display blame information
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
27 #include "svn_client.h"
28 #include "svn_error.h"
29 #include "svn_dirent_uri.h"
31 #include "svn_pools.h"
32 #include "svn_props.h"
33 #include "svn_cmdline.h"
38 #include "svn_private_config.h"
40 typedef struct blame_baton_t
42 svn_cl__opt_state_t *opt_state;
44 svn_stringbuf_t *sbuf;
50 /* This implements the svn_client_blame_receiver3_t interface, printing
53 blame_receiver_xml(void *baton,
54 svn_revnum_t start_revnum,
55 svn_revnum_t end_revnum,
57 svn_revnum_t revision,
58 apr_hash_t *rev_props,
59 svn_revnum_t merged_revision,
60 apr_hash_t *merged_rev_props,
61 const char *merged_path,
63 svn_boolean_t local_change,
66 svn_cl__opt_state_t *opt_state =
67 ((blame_baton_t *) baton)->opt_state;
68 svn_stringbuf_t *sb = ((blame_baton_t *) baton)->sbuf;
71 /* line_no is 0-based, but the rest of the world is probably Pascal
72 programmers, so we make them happy and output 1-based line numbers. */
73 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
75 apr_psprintf(pool, "%" APR_INT64_T_FMT,
79 if (SVN_IS_VALID_REVNUM(revision))
80 svn_cl__print_xml_commit(&sb, revision,
81 svn_prop_get_value(rev_props,
82 SVN_PROP_REVISION_AUTHOR),
83 svn_prop_get_value(rev_props,
84 SVN_PROP_REVISION_DATE),
87 if (opt_state->use_merge_history && SVN_IS_VALID_REVNUM(merged_revision))
90 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "merged",
91 "path", merged_path, NULL);
93 svn_cl__print_xml_commit(&sb, merged_revision,
94 svn_prop_get_value(merged_rev_props,
95 SVN_PROP_REVISION_AUTHOR),
96 svn_prop_get_value(merged_rev_props,
97 SVN_PROP_REVISION_DATE),
101 svn_xml_make_close_tag(&sb, pool, "merged");
106 svn_xml_make_close_tag(&sb, pool, "entry");
108 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
109 svn_stringbuf_setempty(sb);
116 print_line_info(svn_stream_t *out,
117 svn_revnum_t revision,
121 svn_boolean_t verbose,
122 svn_revnum_t end_revnum,
125 const char *time_utf8;
126 const char *time_stdout;
130 /* The standard column width for the revision number is 6 characters.
131 If the revision number can potentially be larger (i.e. if the end_revnum
132 is larger than 1000000), we increase the column width as needed. */
134 while (end_revnum >= 1000000)
137 end_revnum = end_revnum / 10;
139 rev_str = SVN_IS_VALID_REVNUM(revision)
140 ? apr_psprintf(pool, "%*ld", rev_maxlength, revision)
141 : apr_psprintf(pool, "%*s", rev_maxlength, "-");
147 SVN_ERR(svn_cl__time_cstring_to_human_cstring(&time_utf8,
149 SVN_ERR(svn_cmdline_cstring_from_utf8(&time_stdout, time_utf8,
154 /* ### This is a 44 characters long string. It assumes the current
155 format of svn_time_to_human_cstring and also 3 letter
156 abbreviations for the month and weekday names. Else, the
157 line contents will be misaligned. */
161 SVN_ERR(svn_stream_printf(out, pool, "%s %10s %s ", rev_str,
162 author ? author : " -",
166 SVN_ERR(svn_stream_printf(out, pool, "%-14s ", path));
170 return svn_stream_printf(out, pool, "%s %10.10s ", rev_str,
171 author ? author : " -");
177 /* This implements the svn_client_blame_receiver3_t interface. */
179 blame_receiver(void *baton,
180 svn_revnum_t start_revnum,
181 svn_revnum_t end_revnum,
183 svn_revnum_t revision,
184 apr_hash_t *rev_props,
185 svn_revnum_t merged_revision,
186 apr_hash_t *merged_rev_props,
187 const char *merged_path,
189 svn_boolean_t local_change,
192 svn_cl__opt_state_t *opt_state =
193 ((blame_baton_t *) baton)->opt_state;
194 svn_stream_t *out = ((blame_baton_t *)baton)->out;
195 svn_boolean_t use_merged = FALSE;
197 if (opt_state->use_merge_history)
199 /* Choose which revision to use. If they aren't equal, prefer the
200 earliest revision. Since we do a forward blame, we want to the first
201 revision which put the line in its current state, so we use the
202 earliest revision. If we ever switch to a backward blame algorithm,
203 we may need to adjust this. */
204 if (merged_revision < revision)
206 SVN_ERR(svn_stream_puts(out, "G "));
210 SVN_ERR(svn_stream_puts(out, " "));
214 SVN_ERR(print_line_info(out, merged_revision,
215 svn_prop_get_value(merged_rev_props,
216 SVN_PROP_REVISION_AUTHOR),
217 svn_prop_get_value(merged_rev_props,
218 SVN_PROP_REVISION_DATE),
219 merged_path, opt_state->verbose, end_revnum,
222 SVN_ERR(print_line_info(out, revision,
223 svn_prop_get_value(rev_props,
224 SVN_PROP_REVISION_AUTHOR),
225 svn_prop_get_value(rev_props,
226 SVN_PROP_REVISION_DATE),
227 NULL, opt_state->verbose, end_revnum,
230 return svn_stream_printf(out, pool, "%s%s", line, APR_EOL_STR);
234 /* This implements the `svn_opt_subcommand_t' interface. */
236 svn_cl__blame(apr_getopt_t *os,
240 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
241 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
243 apr_array_header_t *targets;
246 svn_boolean_t end_revision_unspecified = FALSE;
247 svn_diff_file_options_t *diff_options = svn_diff_file_options_create(pool);
248 svn_boolean_t seen_nonexistent_target = FALSE;
250 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
254 /* Blame needs a file on which to operate. */
255 if (! targets->nelts)
256 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
258 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
260 if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
262 /* In the case that -rX was specified, we actually want to set the
263 range to be -r1:X. */
265 opt_state->end_revision = opt_state->start_revision;
266 opt_state->start_revision.kind = svn_opt_revision_number;
267 opt_state->start_revision.value.number = 1;
270 end_revision_unspecified = TRUE;
273 if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
275 opt_state->start_revision.kind = svn_opt_revision_number;
276 opt_state->start_revision.value.number = 1;
279 /* The final conclusion from issue #2431 is that blame info
280 is client output (unlike 'svn cat' which plainly cats the file),
281 so the EOL style should be the platform local one.
283 if (! opt_state->xml)
284 SVN_ERR(svn_stream_for_stdout(&bl.out, pool));
286 bl.sbuf = svn_stringbuf_create_empty(pool);
288 bl.opt_state = opt_state;
290 subpool = svn_pool_create(pool);
292 if (opt_state->extensions)
294 apr_array_header_t *opts;
295 opts = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
296 SVN_ERR(svn_diff_file_options_parse(diff_options, opts, pool));
301 if (opt_state->verbose)
302 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
303 _("'verbose' option invalid in XML mode"));
305 /* If output is not incremental, output the XML header and wrap
306 everything in a top-level element. This makes the output in
307 its entirety a well-formed XML document. */
308 if (! opt_state->incremental)
309 SVN_ERR(svn_cl__xml_print_header("blame", pool));
313 if (opt_state->incremental)
314 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
315 _("'incremental' option only valid in XML "
319 for (i = 0; i < targets->nelts; i++)
322 const char *target = APR_ARRAY_IDX(targets, i, const char *);
323 const char *truepath;
324 svn_opt_revision_t peg_revision;
325 svn_client_blame_receiver3_t receiver;
327 svn_pool_clear(subpool);
328 SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
330 /* Check for a peg revision. */
331 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
334 if (end_revision_unspecified)
336 if (peg_revision.kind != svn_opt_revision_unspecified)
337 opt_state->end_revision = peg_revision;
338 else if (svn_path_is_url(target))
339 opt_state->end_revision.kind = svn_opt_revision_head;
341 opt_state->end_revision.kind = svn_opt_revision_working;
347 /* We don't output this tag immediately, which avoids creating
348 a target element if this path is skipped. */
349 const char *outpath = truepath;
350 if (! svn_path_is_url(target))
351 outpath = svn_dirent_local_style(truepath, subpool);
352 svn_xml_make_open_tag(&bl.sbuf, pool, svn_xml_normal, "target",
353 "path", outpath, NULL);
355 receiver = blame_receiver_xml;
358 receiver = blame_receiver;
360 err = svn_client_blame5(truepath,
362 &opt_state->start_revision,
363 &opt_state->end_revision,
366 opt_state->use_merge_history,
374 if (err->apr_err == SVN_ERR_CLIENT_IS_BINARY_FILE)
376 svn_error_clear(err);
377 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
378 _("Skipping binary file "
379 "(use --force to treat as text): "
383 else if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
384 err->apr_err == SVN_ERR_ENTRY_NOT_FOUND ||
385 err->apr_err == SVN_ERR_FS_NOT_FILE ||
386 err->apr_err == SVN_ERR_FS_NOT_FOUND)
388 svn_handle_warning2(stderr, err, "svn: ");
389 svn_error_clear(err);
391 seen_nonexistent_target = TRUE;
395 return svn_error_trace(err);
398 else if (opt_state->xml)
401 svn_xml_make_close_tag(&(bl.sbuf), pool, "target");
402 SVN_ERR(svn_cl__error_checked_fputs(bl.sbuf->data, stdout));
406 svn_stringbuf_setempty(bl.sbuf);
408 svn_pool_destroy(subpool);
409 if (opt_state->xml && ! opt_state->incremental)
410 SVN_ERR(svn_cl__xml_print_footer("blame", pool));
412 if (seen_nonexistent_target)
413 return svn_error_create(
414 SVN_ERR_ILLEGAL_TARGET, NULL,
415 _("Could not perform blame on all targets because some "
416 "targets don't exist"));