2 * blame-cmd.c -- Display blame information
4 * ====================================================================
5 * Copyright (c) 2000-2008 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
22 #include "svn_client.h"
23 #include "svn_error.h"
25 #include "svn_pools.h"
26 #include "svn_cmdline.h"
31 #include "svn_private_config.h"
35 svn_cl__opt_state_t
*opt_state
;
37 svn_stringbuf_t
*sbuf
;
43 /* This implements the svn_client_blame_receiver2_t interface, printing
46 blame_receiver_xml(void *baton
,
48 svn_revnum_t revision
,
51 svn_revnum_t merged_revision
,
52 const char *merged_author
,
53 const char *merged_date
,
54 const char *merged_path
,
58 svn_cl__opt_state_t
*opt_state
=
59 ((blame_baton_t
*) baton
)->opt_state
;
60 svn_stringbuf_t
*sb
= ((blame_baton_t
*) baton
)->sbuf
;
63 /* line_no is 0-based, but the rest of the world is probably Pascal
64 programmers, so we make them happy and output 1-based line numbers. */
65 svn_xml_make_open_tag(&sb
, pool
, svn_xml_normal
, "entry",
67 apr_psprintf(pool
, "%" APR_INT64_T_FMT
,
71 if (SVN_IS_VALID_REVNUM(revision
))
72 svn_cl__print_xml_commit(&sb
, revision
, author
, date
, pool
);
74 if (opt_state
->use_merge_history
&& SVN_IS_VALID_REVNUM(merged_revision
))
77 svn_xml_make_open_tag(&sb
, pool
, svn_xml_normal
, "merged",
78 "path", merged_path
, NULL
);
80 svn_cl__print_xml_commit(&sb
, merged_revision
, merged_author
,
84 svn_xml_make_close_tag(&sb
, pool
, "merged");
89 svn_xml_make_close_tag(&sb
, pool
, "entry");
91 SVN_ERR(svn_cl__error_checked_fputs(sb
->data
, stdout
));
92 svn_stringbuf_setempty(sb
);
99 print_line_info(svn_stream_t
*out
,
100 svn_revnum_t revision
,
104 svn_boolean_t verbose
,
108 const char *time_utf8
;
109 const char *time_stdout
;
110 const char *rev_str
= SVN_IS_VALID_REVNUM(revision
)
111 ? apr_psprintf(pool
, "%6ld", revision
)
118 SVN_ERR(svn_time_from_cstring(&atime
, date
, pool
));
119 time_utf8
= svn_time_to_human_cstring(atime
, pool
);
120 SVN_ERR(svn_cmdline_cstring_from_utf8(&time_stdout
, time_utf8
,
123 /* ### This is a 44 characters long string. It assumes the current
124 format of svn_time_to_human_cstring and also 3 letter
125 abbreviations for the month and weekday names. Else, the
126 line contents will be misaligned. */
128 SVN_ERR(svn_stream_printf(out
, pool
, "%s %10s %s ", rev_str
,
129 author
? author
: " -",
133 SVN_ERR(svn_stream_printf(out
, pool
, "%-14s ", path
));
137 return svn_stream_printf(out
, pool
, "%s %10s ", rev_str
,
138 author
? author
: " -");
144 /* This implements the svn_client_blame_receiver2_t interface. */
146 blame_receiver(void *baton
,
148 svn_revnum_t revision
,
151 svn_revnum_t merged_revision
,
152 const char *merged_author
,
153 const char *merged_date
,
154 const char *merged_path
,
158 svn_cl__opt_state_t
*opt_state
=
159 ((blame_baton_t
*) baton
)->opt_state
;
160 svn_stream_t
*out
= ((blame_baton_t
*)baton
)->out
;
161 svn_boolean_t use_merged
= FALSE
;
163 if (opt_state
->use_merge_history
)
165 /* Choose which revision to use. If they aren't equal, prefer the
166 earliest revision. Since we do a forward blame, we want to the first
167 revision which put the line in its current state, so we use the
168 earliest revision. If we ever switch to a backward blame algorithm,
169 we may need to adjust this. */
170 if (merged_revision
< revision
)
172 svn_stream_printf(out
, pool
, "G ");
176 svn_stream_printf(out
, pool
, " ");
180 SVN_ERR(print_line_info(out
, merged_revision
, merged_author
, merged_date
,
181 merged_path
, opt_state
->verbose
, pool
));
183 SVN_ERR(print_line_info(out
, revision
, author
, date
, NULL
,
184 opt_state
->verbose
, pool
));
186 return svn_stream_printf(out
, pool
, "%s%s", line
, APR_EOL_STR
);
190 /* This implements the `svn_opt_subcommand_t' interface. */
192 svn_cl__blame(apr_getopt_t
*os
,
196 svn_cl__opt_state_t
*opt_state
= ((svn_cl__cmd_baton_t
*) baton
)->opt_state
;
197 svn_client_ctx_t
*ctx
= ((svn_cl__cmd_baton_t
*) baton
)->ctx
;
199 apr_array_header_t
*targets
;
202 svn_boolean_t end_revision_unspecified
= FALSE
;
203 svn_diff_file_options_t
*diff_options
= svn_diff_file_options_create(pool
);
205 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets
, os
,
209 /* Blame needs a file on which to operate. */
210 if (! targets
->nelts
)
211 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS
, 0, NULL
);
213 if (opt_state
->end_revision
.kind
== svn_opt_revision_unspecified
)
215 if (opt_state
->start_revision
.kind
!= svn_opt_revision_unspecified
)
217 /* In the case that -rX was specified, we actually want to set the
218 range to be -r1:X. */
220 opt_state
->end_revision
= opt_state
->start_revision
;
221 opt_state
->start_revision
.kind
= svn_opt_revision_number
;
222 opt_state
->start_revision
.value
.number
= 1;
225 end_revision_unspecified
= TRUE
;
228 if (opt_state
->start_revision
.kind
== svn_opt_revision_unspecified
)
230 opt_state
->start_revision
.kind
= svn_opt_revision_number
;
231 opt_state
->start_revision
.value
.number
= 1;
234 /* The final conclusion from issue #2431 is that blame info
235 is client output (unlike 'svn cat' which plainly cats the file),
236 so the EOL style should be the platform local one.
238 if (! opt_state
->xml
)
239 SVN_ERR(svn_stream_for_stdout(&bl
.out
, pool
));
241 bl
.sbuf
= svn_stringbuf_create("", pool
);
243 bl
.opt_state
= opt_state
;
245 subpool
= svn_pool_create(pool
);
247 if (opt_state
->extensions
)
249 apr_array_header_t
*opts
;
250 opts
= svn_cstring_split(opt_state
->extensions
, " \t\n\r", TRUE
, pool
);
251 SVN_ERR(svn_diff_file_options_parse(diff_options
, opts
, pool
));
256 if (opt_state
->verbose
)
257 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
258 _("'verbose' option invalid in XML mode"));
260 /* If output is not incremental, output the XML header and wrap
261 everything in a top-level element. This makes the output in
262 its entirety a well-formed XML document. */
263 if (! opt_state
->incremental
)
264 SVN_ERR(svn_cl__xml_print_header("blame", pool
));
268 if (opt_state
->incremental
)
269 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
270 _("'incremental' option only valid in XML "
274 for (i
= 0; i
< targets
->nelts
; i
++)
277 const char *target
= APR_ARRAY_IDX(targets
, i
, const char *);
278 const char *truepath
;
279 svn_opt_revision_t peg_revision
;
280 svn_client_blame_receiver2_t receiver
;
282 svn_pool_clear(subpool
);
283 SVN_ERR(svn_cl__check_cancel(ctx
->cancel_baton
));
285 /* Check for a peg revision. */
286 SVN_ERR(svn_opt_parse_path(&peg_revision
, &truepath
, target
,
289 if (end_revision_unspecified
)
291 if (peg_revision
.kind
!= svn_opt_revision_unspecified
)
292 opt_state
->end_revision
= peg_revision
;
293 else if (svn_path_is_url(target
))
294 opt_state
->end_revision
.kind
= svn_opt_revision_head
;
296 opt_state
->end_revision
.kind
= svn_opt_revision_base
;
302 /* We don't output this tag immediately, which avoids creating
303 a target element if this path is skipped. */
304 const char *outpath
= truepath
;
305 if (! svn_path_is_url(target
))
306 outpath
= svn_path_local_style(truepath
, subpool
);
307 svn_xml_make_open_tag(&bl
.sbuf
, pool
, svn_xml_normal
, "target",
308 "path", outpath
, NULL
);
310 receiver
= blame_receiver_xml
;
313 receiver
= blame_receiver
;
315 err
= svn_client_blame4(truepath
,
317 &opt_state
->start_revision
,
318 &opt_state
->end_revision
,
321 opt_state
->use_merge_history
,
329 if (err
->apr_err
== SVN_ERR_CLIENT_IS_BINARY_FILE
)
331 svn_error_clear(err
);
332 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
333 _("Skipping binary file: '%s'\n"),
341 else if (opt_state
->xml
)
344 svn_xml_make_close_tag(&(bl
.sbuf
), pool
, "target");
345 SVN_ERR(svn_cl__error_checked_fputs(bl
.sbuf
->data
, stdout
));
349 svn_stringbuf_setempty(bl
.sbuf
);
351 svn_pool_destroy(subpool
);
352 if (opt_state
->xml
&& ! opt_state
->incremental
)
353 SVN_ERR(svn_cl__xml_print_footer("blame", pool
));