Followup to r29625: fix getopt tests.
[svn.git] / subversion / svn / blame-cmd.c
blob9ed45abcfbc4da279bae30edcb8b80daba220bf4
1 /*
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 * ====================================================================
20 /*** Includes. ***/
22 #include "svn_client.h"
23 #include "svn_error.h"
24 #include "svn_path.h"
25 #include "svn_pools.h"
26 #include "svn_cmdline.h"
27 #include "svn_xml.h"
28 #include "svn_time.h"
29 #include "cl.h"
31 #include "svn_private_config.h"
33 typedef struct
35 svn_cl__opt_state_t *opt_state;
36 svn_stream_t *out;
37 svn_stringbuf_t *sbuf;
38 } blame_baton_t;
41 /*** Code. ***/
43 /* This implements the svn_client_blame_receiver2_t interface, printing
44 XML to stdout. */
45 static svn_error_t *
46 blame_receiver_xml(void *baton,
47 apr_int64_t line_no,
48 svn_revnum_t revision,
49 const char *author,
50 const char *date,
51 svn_revnum_t merged_revision,
52 const char *merged_author,
53 const char *merged_date,
54 const char *merged_path,
55 const char *line,
56 apr_pool_t *pool)
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;
62 /* "<entry ...>" */
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",
66 "line-number",
67 apr_psprintf(pool, "%" APR_INT64_T_FMT,
68 line_no + 1),
69 NULL);
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))
76 /* "<merged>" */
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,
81 merged_date, pool);
83 /* "</merged>" */
84 svn_xml_make_close_tag(&sb, pool, "merged");
88 /* "</entry>" */
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);
94 return SVN_NO_ERROR;
98 static svn_error_t *
99 print_line_info(svn_stream_t *out,
100 svn_revnum_t revision,
101 const char *author,
102 const char *date,
103 const char *path,
104 svn_boolean_t verbose,
105 apr_pool_t *pool)
107 apr_time_t atime;
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)
112 : " -";
114 if (verbose)
116 if (date)
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,
121 pool));
122 } else
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. */
127 time_stdout = " -";
128 SVN_ERR(svn_stream_printf(out, pool, "%s %10s %s ", rev_str,
129 author ? author : " -",
130 time_stdout));
132 if (path)
133 SVN_ERR(svn_stream_printf(out, pool, "%-14s ", path));
135 else
137 return svn_stream_printf(out, pool, "%s %10s ", rev_str,
138 author ? author : " -");
141 return SVN_NO_ERROR;
144 /* This implements the svn_client_blame_receiver2_t interface. */
145 static svn_error_t *
146 blame_receiver(void *baton,
147 apr_int64_t line_no,
148 svn_revnum_t revision,
149 const char *author,
150 const char *date,
151 svn_revnum_t merged_revision,
152 const char *merged_author,
153 const char *merged_date,
154 const char *merged_path,
155 const char *line,
156 apr_pool_t *pool)
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 ");
173 use_merged = TRUE;
175 else
176 svn_stream_printf(out, pool, " ");
179 if (use_merged)
180 SVN_ERR(print_line_info(out, merged_revision, merged_author, merged_date,
181 merged_path, opt_state->verbose, pool));
182 else
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. */
191 svn_error_t *
192 svn_cl__blame(apr_getopt_t *os,
193 void *baton,
194 apr_pool_t *pool)
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;
198 apr_pool_t *subpool;
199 apr_array_header_t *targets;
200 blame_baton_t bl;
201 int i;
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,
206 opt_state->targets,
207 pool));
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;
224 else
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));
240 else
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));
254 if (opt_state->xml)
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));
266 else
268 if (opt_state->incremental)
269 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
270 _("'incremental' option only valid in XML "
271 "mode"));
274 for (i = 0; i < targets->nelts; i++)
276 svn_error_t *err;
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,
287 subpool));
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;
295 else
296 opt_state->end_revision.kind = svn_opt_revision_base;
299 if (opt_state->xml)
301 /* "<target ...>" */
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;
312 else
313 receiver = blame_receiver;
315 err = svn_client_blame4(truepath,
316 &peg_revision,
317 &opt_state->start_revision,
318 &opt_state->end_revision,
319 diff_options,
320 opt_state->force,
321 opt_state->use_merge_history,
322 receiver,
323 &bl,
324 ctx,
325 subpool);
327 if (err)
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"),
334 target));
336 else
338 return err;
341 else if (opt_state->xml)
343 /* "</target>" */
344 svn_xml_make_close_tag(&(bl.sbuf), pool, "target");
345 SVN_ERR(svn_cl__error_checked_fputs(bl.sbuf->data, stdout));
348 if (opt_state->xml)
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));
355 return SVN_NO_ERROR;