]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svn/blame-cmd.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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_sorts.h"
35 #include "svn_xml.h"
36 #include "svn_time.h"
37 #include "cl.h"
38
39 #include "svn_private_config.h"
40
41 typedef struct blame_baton_t
42 {
43   svn_cl__opt_state_t *opt_state;
44   svn_stream_t *out;
45   svn_stringbuf_t *sbuf;
46
47   int rev_maxlength;
48 } blame_baton_t;
49
50 \f
51 /*** Code. ***/
52
53 /* This implements the svn_client_blame_receiver3_t interface, printing
54    XML to stdout. */
55 static svn_error_t *
56 blame_receiver_xml(void *baton,
57                    svn_revnum_t start_revnum,
58                    svn_revnum_t end_revnum,
59                    apr_int64_t line_no,
60                    svn_revnum_t revision,
61                    apr_hash_t *rev_props,
62                    svn_revnum_t merged_revision,
63                    apr_hash_t *merged_rev_props,
64                    const char *merged_path,
65                    const char *line,
66                    svn_boolean_t local_change,
67                    apr_pool_t *pool)
68 {
69   blame_baton_t *bb = baton;
70   svn_cl__opt_state_t *opt_state = bb->opt_state;
71   svn_stringbuf_t *sb = bb->sbuf;
72
73   /* "<entry ...>" */
74   /* line_no is 0-based, but the rest of the world is probably Pascal
75      programmers, so we make them happy and output 1-based line numbers. */
76   svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
77                         "line-number",
78                         apr_psprintf(pool, "%" APR_INT64_T_FMT,
79                                      line_no + 1),
80                         SVN_VA_NULL);
81
82   if (SVN_IS_VALID_REVNUM(revision))
83     svn_cl__print_xml_commit(&sb, revision,
84                              svn_prop_get_value(rev_props,
85                                                 SVN_PROP_REVISION_AUTHOR),
86                              svn_prop_get_value(rev_props,
87                                                 SVN_PROP_REVISION_DATE),
88                              pool);
89
90   if (opt_state->use_merge_history && SVN_IS_VALID_REVNUM(merged_revision))
91     {
92       /* "<merged>" */
93       svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "merged",
94                             "path", merged_path, SVN_VA_NULL);
95
96       svn_cl__print_xml_commit(&sb, merged_revision,
97                              svn_prop_get_value(merged_rev_props,
98                                                 SVN_PROP_REVISION_AUTHOR),
99                              svn_prop_get_value(merged_rev_props,
100                                                 SVN_PROP_REVISION_DATE),
101                              pool);
102
103       /* "</merged>" */
104       svn_xml_make_close_tag(&sb, pool, "merged");
105
106     }
107
108   /* "</entry>" */
109   svn_xml_make_close_tag(&sb, pool, "entry");
110
111   SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
112   svn_stringbuf_setempty(sb);
113
114   return SVN_NO_ERROR;
115 }
116
117
118 static svn_error_t *
119 print_line_info(svn_stream_t *out,
120                 svn_revnum_t revision,
121                 const char *author,
122                 const char *date,
123                 const char *path,
124                 svn_boolean_t verbose,
125                 int rev_maxlength,
126                 apr_pool_t *pool)
127 {
128   const char *time_utf8;
129   const char *time_stdout;
130   const char *rev_str;
131
132   rev_str = SVN_IS_VALID_REVNUM(revision)
133     ? apr_psprintf(pool, "%*ld", rev_maxlength, revision)
134     : apr_psprintf(pool, "%*s", rev_maxlength, "-");
135
136   if (verbose)
137     {
138       if (date)
139         {
140           SVN_ERR(svn_cl__time_cstring_to_human_cstring(&time_utf8,
141                                                         date, pool));
142           SVN_ERR(svn_cmdline_cstring_from_utf8(&time_stdout, time_utf8,
143                                                 pool));
144         }
145       else
146         {
147           /* ### This is a 44 characters long string. It assumes the current
148              format of svn_time_to_human_cstring and also 3 letter
149              abbreviations for the month and weekday names.  Else, the
150              line contents will be misaligned. */
151           time_stdout = "                                           -";
152         }
153
154       SVN_ERR(svn_stream_printf(out, pool, "%s %10s %s ", rev_str,
155                                 author ? author : "         -",
156                                 time_stdout));
157
158       if (path)
159         SVN_ERR(svn_stream_printf(out, pool, "%-14s ", path));
160     }
161   else
162     {
163       return svn_stream_printf(out, pool, "%s %10.10s ", rev_str,
164                                author ? author : "         -");
165     }
166
167   return SVN_NO_ERROR;
168 }
169
170 /* This implements the svn_client_blame_receiver3_t interface. */
171 static svn_error_t *
172 blame_receiver(void *baton,
173                svn_revnum_t start_revnum,
174                svn_revnum_t end_revnum,
175                apr_int64_t line_no,
176                svn_revnum_t revision,
177                apr_hash_t *rev_props,
178                svn_revnum_t merged_revision,
179                apr_hash_t *merged_rev_props,
180                const char *merged_path,
181                const char *line,
182                svn_boolean_t local_change,
183                apr_pool_t *pool)
184 {
185   blame_baton_t *bb = baton;
186   svn_cl__opt_state_t *opt_state = bb->opt_state;
187   svn_stream_t *out = bb->out;
188   svn_boolean_t use_merged = FALSE;
189
190   if (!bb->rev_maxlength)
191     {
192       svn_revnum_t max_revnum = MAX(start_revnum, end_revnum);
193       /* The standard column width for the revision number is 6 characters.
194          If the revision number can potentially be larger (i.e. if the end_revnum
195           is larger than 1000000), we increase the column width as needed. */
196
197       bb->rev_maxlength = 6;
198       while (max_revnum >= 1000000)
199         {
200           bb->rev_maxlength++;
201           max_revnum = max_revnum / 10;
202         }
203     }
204
205   if (opt_state->use_merge_history)
206     {
207       /* Choose which revision to use.  If they aren't equal, prefer the
208          earliest revision.  Since we do a forward blame, we want to the first
209          revision which put the line in its current state, so we use the
210          earliest revision.  If we ever switch to a backward blame algorithm,
211          we may need to adjust this. */
212       if (merged_revision < revision)
213         {
214           SVN_ERR(svn_stream_puts(out, "G "));
215           use_merged = TRUE;
216         }
217       else
218         SVN_ERR(svn_stream_puts(out, "  "));
219     }
220
221   if (use_merged)
222     SVN_ERR(print_line_info(out, merged_revision,
223                             svn_prop_get_value(merged_rev_props,
224                                                SVN_PROP_REVISION_AUTHOR),
225                             svn_prop_get_value(merged_rev_props,
226                                                SVN_PROP_REVISION_DATE),
227                             merged_path, opt_state->verbose,
228                             bb->rev_maxlength,
229                             pool));
230   else
231     SVN_ERR(print_line_info(out, revision,
232                             svn_prop_get_value(rev_props,
233                                                SVN_PROP_REVISION_AUTHOR),
234                             svn_prop_get_value(rev_props,
235                                                SVN_PROP_REVISION_DATE),
236                             NULL, opt_state->verbose,
237                             bb->rev_maxlength,
238                             pool));
239
240   return svn_stream_printf(out, pool, "%s%s", line, APR_EOL_STR);
241 }
242
243
244 /* This implements the `svn_opt_subcommand_t' interface. */
245 svn_error_t *
246 svn_cl__blame(apr_getopt_t *os,
247               void *baton,
248               apr_pool_t *pool)
249 {
250   svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
251   svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
252   apr_pool_t *subpool;
253   apr_array_header_t *targets;
254   blame_baton_t bl;
255   int i;
256   svn_boolean_t end_revision_unspecified = FALSE;
257   svn_diff_file_options_t *diff_options = svn_diff_file_options_create(pool);
258   svn_boolean_t seen_nonexistent_target = FALSE;
259
260   SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
261                                                       opt_state->targets,
262                                                       ctx, FALSE, pool));
263
264   /* Blame needs a file on which to operate. */
265   if (! targets->nelts)
266     return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
267
268   if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
269     {
270       if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
271         {
272           /* In the case that -rX was specified, we actually want to set the
273              range to be -r1:X. */
274
275           opt_state->end_revision = opt_state->start_revision;
276           opt_state->start_revision.kind = svn_opt_revision_number;
277           opt_state->start_revision.value.number = 1;
278         }
279       else
280         end_revision_unspecified = TRUE;
281     }
282
283   if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
284     {
285       opt_state->start_revision.kind = svn_opt_revision_number;
286       opt_state->start_revision.value.number = 1;
287     }
288
289   /* The final conclusion from issue #2431 is that blame info
290      is client output (unlike 'svn cat' which plainly cats the file),
291      so the EOL style should be the platform local one.
292   */
293   if (! opt_state->xml)
294     SVN_ERR(svn_stream_for_stdout(&bl.out, pool));
295   else
296     bl.sbuf = svn_stringbuf_create_empty(pool);
297
298   bl.opt_state = opt_state;
299   bl.rev_maxlength = 0;
300
301   subpool = svn_pool_create(pool);
302
303   if (opt_state->extensions)
304     {
305       apr_array_header_t *opts;
306       opts = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
307       SVN_ERR(svn_diff_file_options_parse(diff_options, opts, pool));
308     }
309
310   if (opt_state->xml)
311     {
312       if (opt_state->verbose)
313         return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
314                                 _("'verbose' option invalid in XML mode"));
315
316       /* If output is not incremental, output the XML header and wrap
317          everything in a top-level element.  This makes the output in
318          its entirety a well-formed XML document. */
319       if (! opt_state->incremental)
320         SVN_ERR(svn_cl__xml_print_header("blame", pool));
321     }
322   else
323     {
324       if (opt_state->incremental)
325         return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
326                                 _("'incremental' option only valid in XML "
327                                   "mode"));
328     }
329
330   for (i = 0; i < targets->nelts; i++)
331     {
332       svn_error_t *err;
333       const char *target = APR_ARRAY_IDX(targets, i, const char *);
334       const char *truepath;
335       svn_opt_revision_t peg_revision;
336       svn_client_blame_receiver3_t receiver;
337
338       svn_pool_clear(subpool);
339       SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
340
341       /* Check for a peg revision. */
342       SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
343                                  subpool));
344
345       if (end_revision_unspecified)
346         {
347           if (peg_revision.kind != svn_opt_revision_unspecified)
348             opt_state->end_revision = peg_revision;
349           else if (svn_path_is_url(target))
350             opt_state->end_revision.kind = svn_opt_revision_head;
351           else
352             opt_state->end_revision.kind = svn_opt_revision_working;
353         }
354
355       if (opt_state->xml)
356         {
357           /* "<target ...>" */
358           /* We don't output this tag immediately, which avoids creating
359              a target element if this path is skipped. */
360           const char *outpath = truepath;
361           if (! svn_path_is_url(target))
362             outpath = svn_dirent_local_style(truepath, subpool);
363           svn_xml_make_open_tag(&bl.sbuf, pool, svn_xml_normal, "target",
364                                 "path", outpath, SVN_VA_NULL);
365
366           receiver = blame_receiver_xml;
367         }
368       else
369         receiver = blame_receiver;
370
371       err = svn_client_blame5(truepath,
372                               &peg_revision,
373                               &opt_state->start_revision,
374                               &opt_state->end_revision,
375                               diff_options,
376                               opt_state->force,
377                               opt_state->use_merge_history,
378                               receiver,
379                               &bl,
380                               ctx,
381                               subpool);
382
383       if (err)
384         {
385           if (err->apr_err == SVN_ERR_CLIENT_IS_BINARY_FILE)
386             {
387               svn_error_clear(err);
388               SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
389                                           _("Skipping binary file "
390                                             "(use --force to treat as text): "
391                                             "'%s'\n"),
392                                           target));
393             }
394           else if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
395                    err->apr_err == SVN_ERR_ENTRY_NOT_FOUND ||
396                    err->apr_err == SVN_ERR_FS_NOT_FILE ||
397                    err->apr_err == SVN_ERR_FS_NOT_FOUND)
398             {
399               svn_handle_warning2(stderr, err, "svn: ");
400               svn_error_clear(err);
401               err = NULL;
402               seen_nonexistent_target = TRUE;
403             }
404           else
405             {
406               return svn_error_trace(err);
407             }
408         }
409       else if (opt_state->xml)
410         {
411           /* "</target>" */
412           svn_xml_make_close_tag(&(bl.sbuf), pool, "target");
413           SVN_ERR(svn_cl__error_checked_fputs(bl.sbuf->data, stdout));
414         }
415
416       if (opt_state->xml)
417         svn_stringbuf_setempty(bl.sbuf);
418     }
419   svn_pool_destroy(subpool);
420   if (opt_state->xml && ! opt_state->incremental)
421     SVN_ERR(svn_cl__xml_print_footer("blame", pool));
422
423   if (seen_nonexistent_target)
424     return svn_error_create(
425       SVN_ERR_ILLEGAL_TARGET, NULL,
426       _("Could not perform blame on all targets because some "
427         "targets don't exist"));
428   else
429     return SVN_NO_ERROR;
430 }