1251881Speter/*
2251881Speter * status-cmd.c -- Display status information in current directory
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include "svn_hash.h"
31251881Speter#include "svn_string.h"
32251881Speter#include "svn_wc.h"
33251881Speter#include "svn_client.h"
34251881Speter#include "svn_error_codes.h"
35251881Speter#include "svn_error.h"
36251881Speter#include "svn_pools.h"
37251881Speter#include "svn_xml.h"
38251881Speter#include "svn_dirent_uri.h"
39251881Speter#include "svn_path.h"
40251881Speter#include "svn_cmdline.h"
41251881Speter#include "cl.h"
42251881Speter
43251881Speter#include "svn_private_config.h"
44251881Speter#include "private/svn_wc_private.h"
45251881Speter
46251881Speter
47251881Speter
48251881Speter/*** Code. ***/
49251881Speter
50251881Speterstruct status_baton
51251881Speter{
52251881Speter  /* These fields all correspond to the ones in the
53251881Speter     svn_cl__print_status() interface. */
54251881Speter  const char *cwd_abspath;
55251881Speter  svn_boolean_t suppress_externals_placeholders;
56251881Speter  svn_boolean_t detailed;
57251881Speter  svn_boolean_t show_last_committed;
58251881Speter  svn_boolean_t skip_unrecognized;
59251881Speter  svn_boolean_t repos_locks;
60251881Speter
61251881Speter  apr_hash_t *cached_changelists;
62251881Speter  apr_pool_t *cl_pool;          /* where cached changelists are allocated */
63251881Speter
64251881Speter  svn_boolean_t had_print_error;  /* To avoid printing lots of errors if we get
65251881Speter                                     errors while printing to stdout */
66251881Speter  svn_boolean_t xml_mode;
67251881Speter
68251881Speter  /* Conflict stats. */
69251881Speter  unsigned int text_conflicts;
70251881Speter  unsigned int prop_conflicts;
71251881Speter  unsigned int tree_conflicts;
72251881Speter
73251881Speter  svn_client_ctx_t *ctx;
74251881Speter};
75251881Speter
76251881Speter
77251881Speterstruct status_cache
78251881Speter{
79251881Speter  const char *path;
80251881Speter  svn_client_status_t *status;
81251881Speter};
82251881Speter
83251881Speter/* Print conflict stats accumulated in status baton SB.
84251881Speter * Do temporary allocations in POOL. */
85251881Speterstatic svn_error_t *
86251881Speterprint_conflict_stats(struct status_baton *sb, apr_pool_t *pool)
87251881Speter{
88251881Speter  if (sb->text_conflicts > 0 || sb->prop_conflicts > 0 ||
89251881Speter      sb->tree_conflicts > 0)
90251881Speter      SVN_ERR(svn_cmdline_printf(pool, "%s", _("Summary of conflicts:\n")));
91251881Speter
92251881Speter  if (sb->text_conflicts > 0)
93251881Speter    SVN_ERR(svn_cmdline_printf
94251881Speter      (pool, _("  Text conflicts: %u\n"), sb->text_conflicts));
95251881Speter
96251881Speter  if (sb->prop_conflicts > 0)
97251881Speter    SVN_ERR(svn_cmdline_printf
98251881Speter      (pool, _("  Property conflicts: %u\n"), sb->prop_conflicts));
99251881Speter
100251881Speter  if (sb->tree_conflicts > 0)
101251881Speter    SVN_ERR(svn_cmdline_printf
102251881Speter      (pool, _("  Tree conflicts: %u\n"), sb->tree_conflicts));
103251881Speter
104251881Speter  return SVN_NO_ERROR;
105251881Speter}
106251881Speter
107251881Speter/* Prints XML target element with path attribute TARGET, using POOL for
108251881Speter   temporary allocations. */
109251881Speterstatic svn_error_t *
110251881Speterprint_start_target_xml(const char *target, apr_pool_t *pool)
111251881Speter{
112251881Speter  svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
113251881Speter
114251881Speter  svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
115251881Speter                        "path", target, NULL);
116251881Speter
117251881Speter  return svn_cl__error_checked_fputs(sb->data, stdout);
118251881Speter}
119251881Speter
120251881Speter
121251881Speter/* Finish a target element by optionally printing an against element if
122251881Speter * REPOS_REV is a valid revision number, and then printing an target end tag.
123251881Speter * Use POOL for temporary allocations. */
124251881Speterstatic svn_error_t *
125251881Speterprint_finish_target_xml(svn_revnum_t repos_rev,
126251881Speter                        apr_pool_t *pool)
127251881Speter{
128251881Speter  svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
129251881Speter
130251881Speter  if (SVN_IS_VALID_REVNUM(repos_rev))
131251881Speter    {
132251881Speter      const char *repos_rev_str;
133251881Speter      repos_rev_str = apr_psprintf(pool, "%ld", repos_rev);
134251881Speter      svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "against",
135251881Speter                            "revision", repos_rev_str, NULL);
136251881Speter    }
137251881Speter
138251881Speter  svn_xml_make_close_tag(&sb, pool, "target");
139251881Speter
140251881Speter  return svn_cl__error_checked_fputs(sb->data, stdout);
141251881Speter}
142251881Speter
143251881Speter
144251881Speter/* Function which *actually* causes a status structure to be output to
145251881Speter   the user.  Called by both print_status() and svn_cl__status(). */
146251881Speterstatic svn_error_t *
147251881Speterprint_status_normal_or_xml(void *baton,
148251881Speter                           const char *path,
149251881Speter                           const svn_client_status_t *status,
150251881Speter                           apr_pool_t *pool)
151251881Speter{
152251881Speter  struct status_baton *sb = baton;
153251881Speter
154251881Speter  if (sb->xml_mode)
155251881Speter    return svn_cl__print_status_xml(sb->cwd_abspath, path, status,
156251881Speter                                    sb->ctx, pool);
157251881Speter  else
158251881Speter    return svn_cl__print_status(sb->cwd_abspath, path, status,
159251881Speter                                sb->suppress_externals_placeholders,
160251881Speter                                sb->detailed,
161251881Speter                                sb->show_last_committed,
162251881Speter                                sb->skip_unrecognized,
163251881Speter                                sb->repos_locks,
164251881Speter                                &sb->text_conflicts,
165251881Speter                                &sb->prop_conflicts,
166251881Speter                                &sb->tree_conflicts,
167251881Speter                                sb->ctx,
168251881Speter                                pool);
169251881Speter}
170251881Speter
171251881Speter
172251881Speter/* A status callback function for printing STATUS for PATH. */
173251881Speterstatic svn_error_t *
174251881Speterprint_status(void *baton,
175251881Speter             const char *path,
176251881Speter             const svn_client_status_t *status,
177251881Speter             apr_pool_t *pool)
178251881Speter{
179251881Speter  struct status_baton *sb = baton;
180251881Speter  const char *local_abspath = status->local_abspath;
181251881Speter
182251881Speter  /* ### The revision information with associates are based on what
183251881Speter   * ### _read_info() returns. The svn_wc_status_func4_t callback is
184251881Speter   * ### suppposed to handle the gathering of additional information from the
185251881Speter   * ### WORKING nodes on its own. Until we've agreed on how the CLI should
186251881Speter   * ### handle the revision information, we use this appproach to stay compat
187251881Speter   * ### with our testsuite. */
188251881Speter  if (status->versioned
189251881Speter      && !SVN_IS_VALID_REVNUM(status->revision)
190251881Speter      && !status->copied
191251881Speter      && (status->node_status == svn_wc_status_deleted
192251881Speter          || status->node_status == svn_wc_status_replaced))
193251881Speter    {
194251881Speter      svn_client_status_t *twks = svn_client_status_dup(status, sb->cl_pool);
195251881Speter
196251881Speter      /* Copied is FALSE, so either we have a local addition, or we have
197251881Speter         a delete that directly shadows a BASE node */
198251881Speter
199251881Speter      switch(status->node_status)
200251881Speter        {
201251881Speter          case svn_wc_status_replaced:
202251881Speter            /* Just retrieve the revision below the replacement.
203251881Speter               The other fields are filled by a copy.
204251881Speter               (With ! copied, we know we have a BASE node)
205251881Speter
206251881Speter               ### Is this really what we want to provide? */
207251881Speter            SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision,
208251881Speter                                                        NULL, NULL, NULL,
209251881Speter                                                        sb->ctx->wc_ctx,
210251881Speter                                                        local_abspath,
211251881Speter                                                        sb->cl_pool, pool));
212251881Speter            break;
213251881Speter          case svn_wc_status_deleted:
214251881Speter            /* Retrieve some data from the original version below the delete */
215251881Speter            SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision,
216251881Speter                                                        &twks->changed_rev,
217251881Speter                                                        &twks->changed_date,
218251881Speter                                                        &twks->changed_author,
219251881Speter                                                        sb->ctx->wc_ctx,
220251881Speter                                                        local_abspath,
221251881Speter                                                        sb->cl_pool, pool));
222251881Speter            break;
223251881Speter
224251881Speter          default:
225251881Speter            /* This space intentionally left blank. */
226251881Speter            break;
227251881Speter        }
228251881Speter
229251881Speter      status = twks;
230251881Speter    }
231251881Speter
232251881Speter  /* If the path is part of a changelist, then we don't print
233251881Speter     the item, but instead dup & cache the status structure for later. */
234251881Speter  if (status->changelist)
235251881Speter    {
236251881Speter      /* The hash maps a changelist name to an array of status_cache
237251881Speter         structures. */
238251881Speter      apr_array_header_t *path_array;
239251881Speter      const char *cl_key = apr_pstrdup(sb->cl_pool, status->changelist);
240251881Speter      struct status_cache *scache = apr_pcalloc(sb->cl_pool, sizeof(*scache));
241251881Speter      scache->path = apr_pstrdup(sb->cl_pool, path);
242251881Speter      scache->status = svn_client_status_dup(status, sb->cl_pool);
243251881Speter
244251881Speter      path_array =
245251881Speter        svn_hash_gets(sb->cached_changelists, cl_key);
246251881Speter      if (path_array == NULL)
247251881Speter        {
248251881Speter          path_array = apr_array_make(sb->cl_pool, 1,
249251881Speter                                      sizeof(struct status_cache *));
250251881Speter          svn_hash_sets(sb->cached_changelists, cl_key, path_array);
251251881Speter        }
252251881Speter
253251881Speter      APR_ARRAY_PUSH(path_array, struct status_cache *) = scache;
254251881Speter      return SVN_NO_ERROR;
255251881Speter    }
256251881Speter
257251881Speter  return print_status_normal_or_xml(baton, path, status, pool);
258251881Speter}
259251881Speter
260251881Speter/* This implements the `svn_opt_subcommand_t' interface. */
261251881Spetersvn_error_t *
262251881Spetersvn_cl__status(apr_getopt_t *os,
263251881Speter               void *baton,
264251881Speter               apr_pool_t *scratch_pool)
265251881Speter{
266251881Speter  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
267251881Speter  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
268251881Speter  apr_array_header_t *targets;
269251881Speter  apr_pool_t *iterpool;
270251881Speter  apr_hash_t *master_cl_hash = apr_hash_make(scratch_pool);
271251881Speter  int i;
272251881Speter  svn_opt_revision_t rev;
273251881Speter  struct status_baton sb;
274251881Speter
275251881Speter  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
276251881Speter                                                      opt_state->targets,
277251881Speter                                                      ctx, FALSE,
278251881Speter                                                      scratch_pool));
279251881Speter
280251881Speter  /* Add "." if user passed 0 arguments */
281251881Speter  svn_opt_push_implicit_dot_target(targets, scratch_pool);
282251881Speter
283251881Speter  SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
284251881Speter
285251881Speter  /* We want our -u statuses to be against HEAD. */
286251881Speter  rev.kind = svn_opt_revision_head;
287251881Speter
288251881Speter  sb.had_print_error = FALSE;
289251881Speter
290251881Speter  if (opt_state->xml)
291251881Speter    {
292251881Speter      /* If output is not incremental, output the XML header and wrap
293251881Speter         everything in a top-level element. This makes the output in
294251881Speter         its entirety a well-formed XML document. */
295251881Speter      if (! opt_state->incremental)
296251881Speter        SVN_ERR(svn_cl__xml_print_header("status", scratch_pool));
297251881Speter    }
298251881Speter  else
299251881Speter    {
300251881Speter      if (opt_state->incremental)
301251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
302251881Speter                                _("'incremental' option only valid in XML "
303251881Speter                                  "mode"));
304251881Speter    }
305251881Speter
306251881Speter  SVN_ERR(svn_dirent_get_absolute(&(sb.cwd_abspath), "", scratch_pool));
307251881Speter  sb.suppress_externals_placeholders = (opt_state->quiet
308251881Speter                                        && (! opt_state->verbose));
309251881Speter  sb.detailed = (opt_state->verbose || opt_state->update);
310251881Speter  sb.show_last_committed = opt_state->verbose;
311251881Speter  sb.skip_unrecognized = opt_state->quiet;
312251881Speter  sb.repos_locks = opt_state->update;
313251881Speter  sb.xml_mode = opt_state->xml;
314251881Speter  sb.cached_changelists = master_cl_hash;
315251881Speter  sb.cl_pool = scratch_pool;
316251881Speter  sb.text_conflicts = 0;
317251881Speter  sb.prop_conflicts = 0;
318251881Speter  sb.tree_conflicts = 0;
319251881Speter  sb.ctx = ctx;
320251881Speter
321251881Speter  SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
322251881Speter
323251881Speter  iterpool = svn_pool_create(scratch_pool);
324251881Speter  for (i = 0; i < targets->nelts; i++)
325251881Speter    {
326251881Speter      const char *target = APR_ARRAY_IDX(targets, i, const char *);
327251881Speter      svn_revnum_t repos_rev = SVN_INVALID_REVNUM;
328251881Speter
329251881Speter      svn_pool_clear(iterpool);
330251881Speter
331251881Speter      SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
332251881Speter
333251881Speter      if (opt_state->xml)
334251881Speter        SVN_ERR(print_start_target_xml(svn_dirent_local_style(target, iterpool),
335251881Speter                                       iterpool));
336251881Speter
337251881Speter      /* Retrieve a hash of status structures with the information
338251881Speter         requested by the user. */
339251881Speter      SVN_ERR(svn_cl__try(svn_client_status5(&repos_rev, ctx, target, &rev,
340251881Speter                                             opt_state->depth,
341251881Speter                                             opt_state->verbose,
342251881Speter                                             opt_state->update,
343251881Speter                                             opt_state->no_ignore,
344251881Speter                                             opt_state->ignore_externals,
345251881Speter                                             FALSE /* depth_as_sticky */,
346251881Speter                                             opt_state->changelists,
347251881Speter                                             print_status, &sb,
348251881Speter                                             iterpool),
349251881Speter                          NULL, opt_state->quiet,
350251881Speter                          /* not versioned: */
351251881Speter                          SVN_ERR_WC_NOT_WORKING_COPY,
352251881Speter                          SVN_ERR_WC_PATH_NOT_FOUND));
353251881Speter
354251881Speter      if (opt_state->xml)
355251881Speter        SVN_ERR(print_finish_target_xml(repos_rev, iterpool));
356251881Speter    }
357251881Speter
358251881Speter  /* If any paths were cached because they were associatied with
359251881Speter     changelists, we can now display them as grouped changelists. */
360251881Speter  if (apr_hash_count(master_cl_hash) > 0)
361251881Speter    {
362251881Speter      apr_hash_index_t *hi;
363251881Speter      svn_stringbuf_t *buf;
364251881Speter
365251881Speter      if (opt_state->xml)
366251881Speter        buf = svn_stringbuf_create_empty(scratch_pool);
367251881Speter
368251881Speter      for (hi = apr_hash_first(scratch_pool, master_cl_hash); hi;
369251881Speter           hi = apr_hash_next(hi))
370251881Speter        {
371251881Speter          const char *changelist_name = svn__apr_hash_index_key(hi);
372251881Speter          apr_array_header_t *path_array = svn__apr_hash_index_val(hi);
373251881Speter          int j;
374251881Speter
375251881Speter          /* ### TODO: For non-XML output, we shouldn't print the
376251881Speter             ### leading \n on the first changelist if there were no
377251881Speter             ### non-changelist entries. */
378251881Speter          if (opt_state->xml)
379251881Speter            {
380251881Speter              svn_stringbuf_setempty(buf);
381251881Speter              svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
382251881Speter                                    "changelist", "name", changelist_name,
383251881Speter                                    NULL);
384251881Speter              SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout));
385251881Speter            }
386251881Speter          else
387251881Speter            SVN_ERR(svn_cmdline_printf(scratch_pool,
388251881Speter                                       _("\n--- Changelist '%s':\n"),
389251881Speter                                       changelist_name));
390251881Speter
391251881Speter          for (j = 0; j < path_array->nelts; j++)
392251881Speter            {
393251881Speter              struct status_cache *scache =
394251881Speter                APR_ARRAY_IDX(path_array, j, struct status_cache *);
395251881Speter              SVN_ERR(print_status_normal_or_xml(&sb, scache->path,
396251881Speter                                                 scache->status, scratch_pool));
397251881Speter            }
398251881Speter
399251881Speter          if (opt_state->xml)
400251881Speter            {
401251881Speter              svn_stringbuf_setempty(buf);
402251881Speter              svn_xml_make_close_tag(&buf, scratch_pool, "changelist");
403251881Speter              SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout));
404251881Speter            }
405251881Speter        }
406251881Speter    }
407251881Speter  svn_pool_destroy(iterpool);
408251881Speter
409251881Speter  if (opt_state->xml && (! opt_state->incremental))
410251881Speter    SVN_ERR(svn_cl__xml_print_footer("status", scratch_pool));
411251881Speter
412251881Speter  if (! opt_state->quiet && ! opt_state->xml)
413251881Speter      SVN_ERR(print_conflict_stats(&sb, scratch_pool));
414251881Speter
415251881Speter  return SVN_NO_ERROR;
416251881Speter}
417