1/*
2 * status-cmd.c -- Display status information in current directory
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/* ==================================================================== */
25
26
27
28/*** Includes. ***/
29
30#include "svn_hash.h"
31#include "svn_string.h"
32#include "svn_wc.h"
33#include "svn_client.h"
34#include "svn_error_codes.h"
35#include "svn_error.h"
36#include "svn_pools.h"
37#include "svn_xml.h"
38#include "svn_dirent_uri.h"
39#include "svn_path.h"
40#include "svn_cmdline.h"
41#include "cl.h"
42
43#include "svn_private_config.h"
44#include "private/svn_wc_private.h"
45
46
47
48/*** Code. ***/
49
50struct status_baton
51{
52  /* These fields all correspond to the ones in the
53     svn_cl__print_status() interface. */
54  const char *cwd_abspath;
55  svn_boolean_t suppress_externals_placeholders;
56  svn_boolean_t detailed;
57  svn_boolean_t show_last_committed;
58  svn_boolean_t skip_unrecognized;
59  svn_boolean_t repos_locks;
60
61  apr_hash_t *cached_changelists;
62  apr_pool_t *cl_pool;          /* where cached changelists are allocated */
63
64  svn_boolean_t had_print_error;  /* To avoid printing lots of errors if we get
65                                     errors while printing to stdout */
66  svn_boolean_t xml_mode;
67
68  /* Conflict stats. */
69  unsigned int text_conflicts;
70  unsigned int prop_conflicts;
71  unsigned int tree_conflicts;
72
73  svn_client_ctx_t *ctx;
74};
75
76
77struct status_cache
78{
79  const char *path;
80  svn_client_status_t *status;
81};
82
83/* Print conflict stats accumulated in status baton SB.
84 * Do temporary allocations in POOL. */
85static svn_error_t *
86print_conflict_stats(struct status_baton *sb, apr_pool_t *pool)
87{
88  if (sb->text_conflicts > 0 || sb->prop_conflicts > 0 ||
89      sb->tree_conflicts > 0)
90      SVN_ERR(svn_cmdline_printf(pool, "%s", _("Summary of conflicts:\n")));
91
92  if (sb->text_conflicts > 0)
93    SVN_ERR(svn_cmdline_printf
94      (pool, _("  Text conflicts: %u\n"), sb->text_conflicts));
95
96  if (sb->prop_conflicts > 0)
97    SVN_ERR(svn_cmdline_printf
98      (pool, _("  Property conflicts: %u\n"), sb->prop_conflicts));
99
100  if (sb->tree_conflicts > 0)
101    SVN_ERR(svn_cmdline_printf
102      (pool, _("  Tree conflicts: %u\n"), sb->tree_conflicts));
103
104  return SVN_NO_ERROR;
105}
106
107/* Prints XML target element with path attribute TARGET, using POOL for
108   temporary allocations. */
109static svn_error_t *
110print_start_target_xml(const char *target, apr_pool_t *pool)
111{
112  svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
113
114  svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
115                        "path", target, NULL);
116
117  return svn_cl__error_checked_fputs(sb->data, stdout);
118}
119
120
121/* Finish a target element by optionally printing an against element if
122 * REPOS_REV is a valid revision number, and then printing an target end tag.
123 * Use POOL for temporary allocations. */
124static svn_error_t *
125print_finish_target_xml(svn_revnum_t repos_rev,
126                        apr_pool_t *pool)
127{
128  svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
129
130  if (SVN_IS_VALID_REVNUM(repos_rev))
131    {
132      const char *repos_rev_str;
133      repos_rev_str = apr_psprintf(pool, "%ld", repos_rev);
134      svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "against",
135                            "revision", repos_rev_str, NULL);
136    }
137
138  svn_xml_make_close_tag(&sb, pool, "target");
139
140  return svn_cl__error_checked_fputs(sb->data, stdout);
141}
142
143
144/* Function which *actually* causes a status structure to be output to
145   the user.  Called by both print_status() and svn_cl__status(). */
146static svn_error_t *
147print_status_normal_or_xml(void *baton,
148                           const char *path,
149                           const svn_client_status_t *status,
150                           apr_pool_t *pool)
151{
152  struct status_baton *sb = baton;
153
154  if (sb->xml_mode)
155    return svn_cl__print_status_xml(sb->cwd_abspath, path, status,
156                                    sb->ctx, pool);
157  else
158    return svn_cl__print_status(sb->cwd_abspath, path, status,
159                                sb->suppress_externals_placeholders,
160                                sb->detailed,
161                                sb->show_last_committed,
162                                sb->skip_unrecognized,
163                                sb->repos_locks,
164                                &sb->text_conflicts,
165                                &sb->prop_conflicts,
166                                &sb->tree_conflicts,
167                                sb->ctx,
168                                pool);
169}
170
171
172/* A status callback function for printing STATUS for PATH. */
173static svn_error_t *
174print_status(void *baton,
175             const char *path,
176             const svn_client_status_t *status,
177             apr_pool_t *pool)
178{
179  struct status_baton *sb = baton;
180  const char *local_abspath = status->local_abspath;
181
182  /* ### The revision information with associates are based on what
183   * ### _read_info() returns. The svn_wc_status_func4_t callback is
184   * ### suppposed to handle the gathering of additional information from the
185   * ### WORKING nodes on its own. Until we've agreed on how the CLI should
186   * ### handle the revision information, we use this appproach to stay compat
187   * ### with our testsuite. */
188  if (status->versioned
189      && !SVN_IS_VALID_REVNUM(status->revision)
190      && !status->copied
191      && (status->node_status == svn_wc_status_deleted
192          || status->node_status == svn_wc_status_replaced))
193    {
194      svn_client_status_t *twks = svn_client_status_dup(status, sb->cl_pool);
195
196      /* Copied is FALSE, so either we have a local addition, or we have
197         a delete that directly shadows a BASE node */
198
199      switch(status->node_status)
200        {
201          case svn_wc_status_replaced:
202            /* Just retrieve the revision below the replacement.
203               The other fields are filled by a copy.
204               (With ! copied, we know we have a BASE node)
205
206               ### Is this really what we want to provide? */
207            SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision,
208                                                        NULL, NULL, NULL,
209                                                        sb->ctx->wc_ctx,
210                                                        local_abspath,
211                                                        sb->cl_pool, pool));
212            break;
213          case svn_wc_status_deleted:
214            /* Retrieve some data from the original version below the delete */
215            SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision,
216                                                        &twks->changed_rev,
217                                                        &twks->changed_date,
218                                                        &twks->changed_author,
219                                                        sb->ctx->wc_ctx,
220                                                        local_abspath,
221                                                        sb->cl_pool, pool));
222            break;
223
224          default:
225            /* This space intentionally left blank. */
226            break;
227        }
228
229      status = twks;
230    }
231
232  /* If the path is part of a changelist, then we don't print
233     the item, but instead dup & cache the status structure for later. */
234  if (status->changelist)
235    {
236      /* The hash maps a changelist name to an array of status_cache
237         structures. */
238      apr_array_header_t *path_array;
239      const char *cl_key = apr_pstrdup(sb->cl_pool, status->changelist);
240      struct status_cache *scache = apr_pcalloc(sb->cl_pool, sizeof(*scache));
241      scache->path = apr_pstrdup(sb->cl_pool, path);
242      scache->status = svn_client_status_dup(status, sb->cl_pool);
243
244      path_array =
245        svn_hash_gets(sb->cached_changelists, cl_key);
246      if (path_array == NULL)
247        {
248          path_array = apr_array_make(sb->cl_pool, 1,
249                                      sizeof(struct status_cache *));
250          svn_hash_sets(sb->cached_changelists, cl_key, path_array);
251        }
252
253      APR_ARRAY_PUSH(path_array, struct status_cache *) = scache;
254      return SVN_NO_ERROR;
255    }
256
257  return print_status_normal_or_xml(baton, path, status, pool);
258}
259
260/* This implements the `svn_opt_subcommand_t' interface. */
261svn_error_t *
262svn_cl__status(apr_getopt_t *os,
263               void *baton,
264               apr_pool_t *scratch_pool)
265{
266  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
267  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
268  apr_array_header_t *targets;
269  apr_pool_t *iterpool;
270  apr_hash_t *master_cl_hash = apr_hash_make(scratch_pool);
271  int i;
272  svn_opt_revision_t rev;
273  struct status_baton sb;
274
275  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
276                                                      opt_state->targets,
277                                                      ctx, FALSE,
278                                                      scratch_pool));
279
280  /* Add "." if user passed 0 arguments */
281  svn_opt_push_implicit_dot_target(targets, scratch_pool);
282
283  SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
284
285  /* We want our -u statuses to be against HEAD. */
286  rev.kind = svn_opt_revision_head;
287
288  sb.had_print_error = FALSE;
289
290  if (opt_state->xml)
291    {
292      /* If output is not incremental, output the XML header and wrap
293         everything in a top-level element. This makes the output in
294         its entirety a well-formed XML document. */
295      if (! opt_state->incremental)
296        SVN_ERR(svn_cl__xml_print_header("status", scratch_pool));
297    }
298  else
299    {
300      if (opt_state->incremental)
301        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
302                                _("'incremental' option only valid in XML "
303                                  "mode"));
304    }
305
306  SVN_ERR(svn_dirent_get_absolute(&(sb.cwd_abspath), "", scratch_pool));
307  sb.suppress_externals_placeholders = (opt_state->quiet
308                                        && (! opt_state->verbose));
309  sb.detailed = (opt_state->verbose || opt_state->update);
310  sb.show_last_committed = opt_state->verbose;
311  sb.skip_unrecognized = opt_state->quiet;
312  sb.repos_locks = opt_state->update;
313  sb.xml_mode = opt_state->xml;
314  sb.cached_changelists = master_cl_hash;
315  sb.cl_pool = scratch_pool;
316  sb.text_conflicts = 0;
317  sb.prop_conflicts = 0;
318  sb.tree_conflicts = 0;
319  sb.ctx = ctx;
320
321  SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
322
323  iterpool = svn_pool_create(scratch_pool);
324  for (i = 0; i < targets->nelts; i++)
325    {
326      const char *target = APR_ARRAY_IDX(targets, i, const char *);
327      svn_revnum_t repos_rev = SVN_INVALID_REVNUM;
328
329      svn_pool_clear(iterpool);
330
331      SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
332
333      if (opt_state->xml)
334        SVN_ERR(print_start_target_xml(svn_dirent_local_style(target, iterpool),
335                                       iterpool));
336
337      /* Retrieve a hash of status structures with the information
338         requested by the user. */
339      SVN_ERR(svn_cl__try(svn_client_status5(&repos_rev, ctx, target, &rev,
340                                             opt_state->depth,
341                                             opt_state->verbose,
342                                             opt_state->update,
343                                             opt_state->no_ignore,
344                                             opt_state->ignore_externals,
345                                             FALSE /* depth_as_sticky */,
346                                             opt_state->changelists,
347                                             print_status, &sb,
348                                             iterpool),
349                          NULL, opt_state->quiet,
350                          /* not versioned: */
351                          SVN_ERR_WC_NOT_WORKING_COPY,
352                          SVN_ERR_WC_PATH_NOT_FOUND));
353
354      if (opt_state->xml)
355        SVN_ERR(print_finish_target_xml(repos_rev, iterpool));
356    }
357
358  /* If any paths were cached because they were associatied with
359     changelists, we can now display them as grouped changelists. */
360  if (apr_hash_count(master_cl_hash) > 0)
361    {
362      apr_hash_index_t *hi;
363      svn_stringbuf_t *buf;
364
365      if (opt_state->xml)
366        buf = svn_stringbuf_create_empty(scratch_pool);
367
368      for (hi = apr_hash_first(scratch_pool, master_cl_hash); hi;
369           hi = apr_hash_next(hi))
370        {
371          const char *changelist_name = svn__apr_hash_index_key(hi);
372          apr_array_header_t *path_array = svn__apr_hash_index_val(hi);
373          int j;
374
375          /* ### TODO: For non-XML output, we shouldn't print the
376             ### leading \n on the first changelist if there were no
377             ### non-changelist entries. */
378          if (opt_state->xml)
379            {
380              svn_stringbuf_setempty(buf);
381              svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
382                                    "changelist", "name", changelist_name,
383                                    NULL);
384              SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout));
385            }
386          else
387            SVN_ERR(svn_cmdline_printf(scratch_pool,
388                                       _("\n--- Changelist '%s':\n"),
389                                       changelist_name));
390
391          for (j = 0; j < path_array->nelts; j++)
392            {
393              struct status_cache *scache =
394                APR_ARRAY_IDX(path_array, j, struct status_cache *);
395              SVN_ERR(print_status_normal_or_xml(&sb, scache->path,
396                                                 scache->status, scratch_pool));
397            }
398
399          if (opt_state->xml)
400            {
401              svn_stringbuf_setempty(buf);
402              svn_xml_make_close_tag(&buf, scratch_pool, "changelist");
403              SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout));
404            }
405        }
406    }
407  svn_pool_destroy(iterpool);
408
409  if (opt_state->xml && (! opt_state->incremental))
410    SVN_ERR(svn_cl__xml_print_footer("status", scratch_pool));
411
412  if (! opt_state->quiet && ! opt_state->xml)
413      SVN_ERR(print_conflict_stats(&sb, scratch_pool));
414
415  return SVN_NO_ERROR;
416}
417