2 * blame-cmd.c -- Display blame information
4 * ====================================================================
5 * Copyright (c) 2000-2007 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
;
162 if (opt_state
->use_merge_history
)
164 if (revision
!= merged_revision
)
165 svn_stream_printf(out
, pool
, "G ");
167 svn_stream_printf(out
, pool
, " ");
169 SVN_ERR(print_line_info(out
, merged_revision
, merged_author
, merged_date
,
170 merged_path
, opt_state
->verbose
, pool
));
173 SVN_ERR(print_line_info(out
, revision
, author
, date
, NULL
,
174 opt_state
->verbose
, pool
));
176 return svn_stream_printf(out
, pool
, "%s%s", line
, APR_EOL_STR
);
180 /* This implements the `svn_opt_subcommand_t' interface. */
182 svn_cl__blame(apr_getopt_t
*os
,
186 svn_cl__opt_state_t
*opt_state
= ((svn_cl__cmd_baton_t
*) baton
)->opt_state
;
187 svn_client_ctx_t
*ctx
= ((svn_cl__cmd_baton_t
*) baton
)->ctx
;
189 apr_array_header_t
*targets
;
192 svn_boolean_t end_revision_unspecified
= FALSE
;
193 svn_diff_file_options_t
*diff_options
= svn_diff_file_options_create(pool
);
195 SVN_ERR(svn_opt_args_to_target_array2(&targets
, os
,
196 opt_state
->targets
, pool
));
198 /* Blame needs a file on which to operate. */
199 if (! targets
->nelts
)
200 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS
, 0, NULL
);
202 if (opt_state
->end_revision
.kind
== svn_opt_revision_unspecified
)
204 if (opt_state
->start_revision
.kind
!= svn_opt_revision_unspecified
)
206 /* In the case that -rX was specified, we actually want to set the
207 range to be -r1:X. */
209 opt_state
->end_revision
= opt_state
->start_revision
;
210 opt_state
->start_revision
.kind
= svn_opt_revision_number
;
211 opt_state
->start_revision
.value
.number
= 1;
214 end_revision_unspecified
= TRUE
;
217 if (opt_state
->start_revision
.kind
== svn_opt_revision_unspecified
)
219 opt_state
->start_revision
.kind
= svn_opt_revision_number
;
220 opt_state
->start_revision
.value
.number
= 1;
223 /* The final conclusion from issue #2431 is that blame info
224 is client output (unlike 'svn cat' which plainly cats the file),
225 so the EOL style should be the platform local one.
227 if (! opt_state
->xml
)
228 SVN_ERR(svn_stream_for_stdout(&bl
.out
, pool
));
230 bl
.sbuf
= svn_stringbuf_create("", pool
);
232 bl
.opt_state
= opt_state
;
234 subpool
= svn_pool_create(pool
);
236 if (opt_state
->extensions
)
238 apr_array_header_t
*opts
;
239 opts
= svn_cstring_split(opt_state
->extensions
, " \t\n\r", TRUE
, pool
);
240 SVN_ERR(svn_diff_file_options_parse(diff_options
, opts
, pool
));
245 if (opt_state
->verbose
)
246 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
247 _("'verbose' option invalid in XML mode"));
249 /* If output is not incremental, output the XML header and wrap
250 everything in a top-level element. This makes the output in
251 its entirety a well-formed XML document. */
252 if (! opt_state
->incremental
)
253 SVN_ERR(svn_cl__xml_print_header("blame", pool
));
257 if (opt_state
->incremental
)
258 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
259 _("'incremental' option only valid in XML "
263 for (i
= 0; i
< targets
->nelts
; i
++)
266 const char *target
= APR_ARRAY_IDX(targets
, i
, const char *);
267 const char *truepath
;
268 svn_opt_revision_t peg_revision
;
269 svn_client_blame_receiver2_t receiver
;
271 svn_pool_clear(subpool
);
272 SVN_ERR(svn_cl__check_cancel(ctx
->cancel_baton
));
274 /* Check for a peg revision. */
275 SVN_ERR(svn_opt_parse_path(&peg_revision
, &truepath
, target
,
278 if (end_revision_unspecified
)
280 if (peg_revision
.kind
!= svn_opt_revision_unspecified
)
281 opt_state
->end_revision
= peg_revision
;
282 else if (svn_path_is_url(target
))
283 opt_state
->end_revision
.kind
= svn_opt_revision_head
;
285 opt_state
->end_revision
.kind
= svn_opt_revision_base
;
291 /* We don't output this tag immediately, which avoids creating
292 a target element if this path is skipped. */
293 const char *outpath
= truepath
;
294 if (! svn_path_is_url(target
))
295 outpath
= svn_path_local_style(truepath
, subpool
);
296 svn_xml_make_open_tag(&bl
.sbuf
, pool
, svn_xml_normal
, "target",
297 "path", outpath
, NULL
);
299 receiver
= blame_receiver_xml
;
302 receiver
= blame_receiver
;
304 err
= svn_client_blame4(truepath
,
306 &opt_state
->start_revision
,
307 &opt_state
->end_revision
,
310 opt_state
->use_merge_history
,
318 if (err
->apr_err
== SVN_ERR_CLIENT_IS_BINARY_FILE
)
320 svn_error_clear(err
);
321 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
322 _("Skipping binary file: '%s'\n"),
330 else if (opt_state
->xml
)
333 svn_xml_make_close_tag(&(bl
.sbuf
), pool
, "target");
334 SVN_ERR(svn_cl__error_checked_fputs(bl
.sbuf
->data
, stdout
));
338 svn_stringbuf_setempty(bl
.sbuf
);
340 svn_pool_destroy(subpool
);
341 if (opt_state
->xml
&& ! opt_state
->incremental
)
342 SVN_ERR(svn_cl__xml_print_footer("blame", pool
));