In the command-line client, forbid
[svn.git] / subversion / svn / blame-cmd.c
blob0d4df014661b3f8d4a0179e207df114041425f90
1 /*
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 * ====================================================================
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;
162 if (opt_state->use_merge_history)
164 if (revision != merged_revision)
165 svn_stream_printf(out, pool, "G ");
166 else
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));
172 else
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. */
181 svn_error_t *
182 svn_cl__blame(apr_getopt_t *os,
183 void *baton,
184 apr_pool_t *pool)
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;
188 apr_pool_t *subpool;
189 apr_array_header_t *targets;
190 blame_baton_t bl;
191 int i;
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;
213 else
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));
229 else
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));
243 if (opt_state->xml)
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));
255 else
257 if (opt_state->incremental)
258 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
259 _("'incremental' option only valid in XML "
260 "mode"));
263 for (i = 0; i < targets->nelts; i++)
265 svn_error_t *err;
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,
276 subpool));
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;
284 else
285 opt_state->end_revision.kind = svn_opt_revision_base;
288 if (opt_state->xml)
290 /* "<target ...>" */
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;
301 else
302 receiver = blame_receiver;
304 err = svn_client_blame4(truepath,
305 &peg_revision,
306 &opt_state->start_revision,
307 &opt_state->end_revision,
308 diff_options,
309 opt_state->force,
310 opt_state->use_merge_history,
311 receiver,
312 &bl,
313 ctx,
314 subpool);
316 if (err)
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"),
323 target));
325 else
327 return err;
330 else if (opt_state->xml)
332 /* "</target>" */
333 svn_xml_make_close_tag(&(bl.sbuf), pool, "target");
334 SVN_ERR(svn_cl__error_checked_fputs(bl.sbuf->data, stdout));
337 if (opt_state->xml)
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));
344 return SVN_NO_ERROR;