]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/svn/blame-cmd.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / svn / blame-cmd.c
1 /*
2  * blame-cmd.c -- Display blame information
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23
24 \f
25 /*** Includes. ***/
26
27 #include "svn_client.h"
28 #include "svn_error.h"
29 #include "svn_dirent_uri.h"
30 #include "svn_path.h"
31 #include "svn_pools.h"
32 #include "svn_props.h"
33 #include "svn_cmdline.h"
34 #include "svn_xml.h"
35 #include "svn_time.h"
36 #include "cl.h"
37
38 #include "svn_private_config.h"
39
40 typedef struct blame_baton_t
41 {
42   svn_cl__opt_state_t *opt_state;
43   svn_stream_t *out;
44   svn_stringbuf_t *sbuf;
45 } blame_baton_t;
46
47 \f
48 /*** Code. ***/
49
50 /* This implements the svn_client_blame_receiver3_t interface, printing
51    XML to stdout. */
52 static svn_error_t *
53 blame_receiver_xml(void *baton,
54                    svn_revnum_t start_revnum,
55                    svn_revnum_t end_revnum,
56                    apr_int64_t line_no,
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,
62                    const char *line,
63                    svn_boolean_t local_change,
64                    apr_pool_t *pool)
65 {
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;
69
70   /* "<entry ...>" */
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",
74                         "line-number",
75                         apr_psprintf(pool, "%" APR_INT64_T_FMT,
76                                      line_no + 1),
77                         NULL);
78
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),
85                              pool);
86
87   if (opt_state->use_merge_history && SVN_IS_VALID_REVNUM(merged_revision))
88     {
89       /* "<merged>" */
90       svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "merged",
91                             "path", merged_path, NULL);
92
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),
98                              pool);
99
100       /* "</merged>" */
101       svn_xml_make_close_tag(&sb, pool, "merged");
102
103     }
104
105   /* "</entry>" */
106   svn_xml_make_close_tag(&sb, pool, "entry");
107
108   SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
109   svn_stringbuf_setempty(sb);
110
111   return SVN_NO_ERROR;
112 }
113
114
115 static svn_error_t *
116 print_line_info(svn_stream_t *out,
117                 svn_revnum_t revision,
118                 const char *author,
119                 const char *date,
120                 const char *path,
121                 svn_boolean_t verbose,
122                 svn_revnum_t end_revnum,
123                 apr_pool_t *pool)
124 {
125   const char *time_utf8;
126   const char *time_stdout;
127   const char *rev_str;
128   int rev_maxlength;
129
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. */
133   rev_maxlength = 6;
134   while (end_revnum >= 1000000)
135     {
136       rev_maxlength++;
137       end_revnum = end_revnum / 10;
138     }
139   rev_str = SVN_IS_VALID_REVNUM(revision)
140     ? apr_psprintf(pool, "%*ld", rev_maxlength, revision)
141     : apr_psprintf(pool, "%*s", rev_maxlength, "-");
142
143   if (verbose)
144     {
145       if (date)
146         {
147           SVN_ERR(svn_cl__time_cstring_to_human_cstring(&time_utf8,
148                                                         date, pool));
149           SVN_ERR(svn_cmdline_cstring_from_utf8(&time_stdout, time_utf8,
150                                                 pool));
151         }
152       else
153         {
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. */
158           time_stdout = "                                           -";
159         }
160
161       SVN_ERR(svn_stream_printf(out, pool, "%s %10s %s ", rev_str,
162                                 author ? author : "         -",
163                                 time_stdout));
164
165       if (path)
166         SVN_ERR(svn_stream_printf(out, pool, "%-14s ", path));
167     }
168   else
169     {
170       return svn_stream_printf(out, pool, "%s %10.10s ", rev_str,
171                                author ? author : "         -");
172     }
173
174   return SVN_NO_ERROR;
175 }
176
177 /* This implements the svn_client_blame_receiver3_t interface. */
178 static svn_error_t *
179 blame_receiver(void *baton,
180                svn_revnum_t start_revnum,
181                svn_revnum_t end_revnum,
182                apr_int64_t line_no,
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,
188                const char *line,
189                svn_boolean_t local_change,
190                apr_pool_t *pool)
191 {
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;
196
197   if (opt_state->use_merge_history)
198     {
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)
205         {
206           SVN_ERR(svn_stream_puts(out, "G "));
207           use_merged = TRUE;
208         }
209       else
210         SVN_ERR(svn_stream_puts(out, "  "));
211     }
212
213   if (use_merged)
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,
220                             pool));
221   else
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,
228                             pool));
229
230   return svn_stream_printf(out, pool, "%s%s", line, APR_EOL_STR);
231 }
232
233
234 /* This implements the `svn_opt_subcommand_t' interface. */
235 svn_error_t *
236 svn_cl__blame(apr_getopt_t *os,
237               void *baton,
238               apr_pool_t *pool)
239 {
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;
242   apr_pool_t *subpool;
243   apr_array_header_t *targets;
244   blame_baton_t bl;
245   int i;
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;
249
250   SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
251                                                       opt_state->targets,
252                                                       ctx, FALSE, pool));
253
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);
257
258   if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
259     {
260       if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
261         {
262           /* In the case that -rX was specified, we actually want to set the
263              range to be -r1:X. */
264
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;
268         }
269       else
270         end_revision_unspecified = TRUE;
271     }
272
273   if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
274     {
275       opt_state->start_revision.kind = svn_opt_revision_number;
276       opt_state->start_revision.value.number = 1;
277     }
278
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.
282   */
283   if (! opt_state->xml)
284     SVN_ERR(svn_stream_for_stdout(&bl.out, pool));
285   else
286     bl.sbuf = svn_stringbuf_create_empty(pool);
287
288   bl.opt_state = opt_state;
289
290   subpool = svn_pool_create(pool);
291
292   if (opt_state->extensions)
293     {
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));
297     }
298
299   if (opt_state->xml)
300     {
301       if (opt_state->verbose)
302         return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
303                                 _("'verbose' option invalid in XML mode"));
304
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));
310     }
311   else
312     {
313       if (opt_state->incremental)
314         return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
315                                 _("'incremental' option only valid in XML "
316                                   "mode"));
317     }
318
319   for (i = 0; i < targets->nelts; i++)
320     {
321       svn_error_t *err;
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;
326
327       svn_pool_clear(subpool);
328       SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
329
330       /* Check for a peg revision. */
331       SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
332                                  subpool));
333
334       if (end_revision_unspecified)
335         {
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;
340           else
341             opt_state->end_revision.kind = svn_opt_revision_working;
342         }
343
344       if (opt_state->xml)
345         {
346           /* "<target ...>" */
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);
354
355           receiver = blame_receiver_xml;
356         }
357       else
358         receiver = blame_receiver;
359
360       err = svn_client_blame5(truepath,
361                               &peg_revision,
362                               &opt_state->start_revision,
363                               &opt_state->end_revision,
364                               diff_options,
365                               opt_state->force,
366                               opt_state->use_merge_history,
367                               receiver,
368                               &bl,
369                               ctx,
370                               subpool);
371
372       if (err)
373         {
374           if (err->apr_err == SVN_ERR_CLIENT_IS_BINARY_FILE)
375             {
376               svn_error_clear(err);
377               SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
378                                           _("Skipping binary file "
379                                             "(use --force to treat as text): "
380                                             "'%s'\n"),
381                                           target));
382             }
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)
387             {
388               svn_handle_warning2(stderr, err, "svn: ");
389               svn_error_clear(err);
390               err = NULL;
391               seen_nonexistent_target = TRUE;
392             }
393           else
394             {
395               return svn_error_trace(err);
396             }
397         }
398       else if (opt_state->xml)
399         {
400           /* "</target>" */
401           svn_xml_make_close_tag(&(bl.sbuf), pool, "target");
402           SVN_ERR(svn_cl__error_checked_fputs(bl.sbuf->data, stdout));
403         }
404
405       if (opt_state->xml)
406         svn_stringbuf_setempty(bl.sbuf);
407     }
408   svn_pool_destroy(subpool);
409   if (opt_state->xml && ! opt_state->incremental)
410     SVN_ERR(svn_cl__xml_print_footer("blame", pool));
411
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"));
417   else
418     return SVN_NO_ERROR;
419 }