]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svn/status.c
Copy head (r256279) to stable/10 as part of the 10.0-RELEASE cycle.
[FreeBSD/stable/10.git] / contrib / subversion / subversion / svn / status.c
1 /*
2  * status.c:  the command-line's portion of the "svn status" command
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 \f
28 /*** Includes. ***/
29 #include "svn_hash.h"
30 #include "svn_cmdline.h"
31 #include "svn_wc.h"
32 #include "svn_dirent_uri.h"
33 #include "svn_xml.h"
34 #include "svn_time.h"
35 #include "cl.h"
36 #include "svn_private_config.h"
37 #include "cl-conflicts.h"
38 #include "private/svn_wc_private.h"
39 \f
40 /* Return the single character representation of STATUS */
41 static char
42 generate_status_code(enum svn_wc_status_kind status)
43 {
44   switch (status)
45     {
46     case svn_wc_status_none:        return ' ';
47     case svn_wc_status_normal:      return ' ';
48     case svn_wc_status_added:       return 'A';
49     case svn_wc_status_missing:     return '!';
50     case svn_wc_status_incomplete:  return '!';
51     case svn_wc_status_deleted:     return 'D';
52     case svn_wc_status_replaced:    return 'R';
53     case svn_wc_status_modified:    return 'M';
54     case svn_wc_status_conflicted:  return 'C';
55     case svn_wc_status_obstructed:  return '~';
56     case svn_wc_status_ignored:     return 'I';
57     case svn_wc_status_external:    return 'X';
58     case svn_wc_status_unversioned: return '?';
59     default:                        return '?';
60     }
61 }
62
63 /* Return the combined STATUS as shown in 'svn status' based
64    on the node status and text status */
65 static enum svn_wc_status_kind
66 combined_status(const svn_client_status_t *status)
67 {
68   enum svn_wc_status_kind new_status = status->node_status;
69
70   switch (status->node_status)
71     {
72       case svn_wc_status_conflicted:
73         if (!status->versioned && status->conflicted)
74           {
75             /* Report unversioned tree conflict victims as missing: '!' */
76             new_status = svn_wc_status_missing;
77             break;
78           }
79         /* fall through */
80       case svn_wc_status_modified:
81         /* This value might be the property status */
82         new_status = status->text_status;
83         break;
84       default:
85         break;
86     }
87
88   return new_status;
89 }
90
91 /* Return the combined repository STATUS as shown in 'svn status' based
92    on the repository node status and repository text status */
93 static enum svn_wc_status_kind
94 combined_repos_status(const svn_client_status_t *status)
95 {
96   if (status->repos_node_status == svn_wc_status_modified)
97     return status->repos_text_status;
98
99   return status->repos_node_status;
100 }
101
102 /* Return the single character representation of the switched column
103    status. */
104 static char
105 generate_switch_column_code(const svn_client_status_t *status)
106 {
107   if (status->switched)
108     return 'S';
109   else if (status->file_external)
110     return 'X';
111   else
112     return ' ';
113 }
114
115 /* Return the detailed string representation of STATUS */
116 static const char *
117 generate_status_desc(enum svn_wc_status_kind status)
118 {
119   switch (status)
120     {
121     case svn_wc_status_none:        return "none";
122     case svn_wc_status_normal:      return "normal";
123     case svn_wc_status_added:       return "added";
124     case svn_wc_status_missing:     return "missing";
125     case svn_wc_status_incomplete:  return "incomplete";
126     case svn_wc_status_deleted:     return "deleted";
127     case svn_wc_status_replaced:    return "replaced";
128     case svn_wc_status_modified:    return "modified";
129     case svn_wc_status_conflicted:  return "conflicted";
130     case svn_wc_status_obstructed:  return "obstructed";
131     case svn_wc_status_ignored:     return "ignored";
132     case svn_wc_status_external:    return "external";
133     case svn_wc_status_unversioned: return "unversioned";
134     default:
135       SVN_ERR_MALFUNCTION_NO_RETURN();
136     }
137 }
138
139 /* Make a relative path containing '..' elements as needed.
140    RELATIVE_TO_PATH must be the path to a directory (not a file!) and
141    TARGET_PATH must be the path to any file or directory. Both
142    RELATIVE_TO_PATH and TARGET_PATH must be based on the same parent path,
143    i.e. they can either both be absolute or they can both be relative to the
144    same parent directory. Both paths are expected to be canonical.
145
146    If above conditions are met, a relative path that leads to TARGET_ABSPATH
147    from RELATIVE_TO_PATH is returned, but there is no error checking involved.
148
149    The returned path is allocated from RESULT_POOL, all other allocations are
150    made in SCRATCH_POOL. */
151 static const char *
152 make_relpath(const char *relative_to_path,
153              const char *target_path,
154              apr_pool_t *result_pool,
155              apr_pool_t *scratch_pool)
156 {
157   const char *la;
158   const char *parent_dir_els = "";
159
160   /* An example:
161    *  relative_to_path = /a/b/c
162    *  target_path      = /a/x/y/z
163    *  result           = ../../x/y/z
164    *
165    * Another example (Windows specific):
166    *  relative_to_path = F:/wc
167    *  target_path      = C:/wc
168    *  result           = C:/wc
169    */
170
171   /* Skip the common ancestor of both paths, here '/a'. */
172   la = svn_dirent_get_longest_ancestor(relative_to_path, target_path,
173                                        scratch_pool);
174   if (*la == '\0')
175     {
176       /* Nothing in common: E.g. C:/ vs F:/ on Windows */
177       return apr_pstrdup(result_pool, target_path);
178     }
179   relative_to_path = svn_dirent_skip_ancestor(la, relative_to_path);
180   target_path = svn_dirent_skip_ancestor(la, target_path);
181
182   /* In above example, we'd now have:
183    *  relative_to_path = b/c
184    *  target_path      = x/y/z */
185
186   /* Count the elements of relative_to_path and prepend as many '..' elements
187    * to target_path. */
188   while (*relative_to_path)
189     {
190       svn_dirent_split(&relative_to_path, NULL, relative_to_path,
191                        scratch_pool);
192       parent_dir_els = svn_dirent_join(parent_dir_els, "..", scratch_pool);
193     }
194
195   return svn_dirent_join(parent_dir_els, target_path, result_pool);
196 }
197
198
199 /* Print STATUS and PATH in a format determined by DETAILED and
200    SHOW_LAST_COMMITTED. */
201 static svn_error_t *
202 print_status(const char *cwd_abspath, const char *path,
203              svn_boolean_t detailed,
204              svn_boolean_t show_last_committed,
205              svn_boolean_t repos_locks,
206              const svn_client_status_t *status,
207              unsigned int *text_conflicts,
208              unsigned int *prop_conflicts,
209              unsigned int *tree_conflicts,
210              svn_client_ctx_t *ctx,
211              apr_pool_t *pool)
212 {
213   enum svn_wc_status_kind node_status = status->node_status;
214   enum svn_wc_status_kind prop_status = status->prop_status;
215   char tree_status_code = ' ';
216   const char *tree_desc_line = "";
217   const char *moved_from_line = "";
218   const char *moved_to_line = "";
219
220   path = make_relpath(cwd_abspath, path, pool, pool);
221
222   /* For historic reasons svn ignores the property status for added nodes, even
223      if these nodes were copied and have local property changes.
224
225      Note that it doesn't do this on replacements, or children of copies.
226
227      ### Our test suite would catch more errors if we reported property
228          changes on copies. */
229   if (node_status == svn_wc_status_added)
230       prop_status = svn_wc_status_none;
231
232   /* To indicate this node is the victim of a tree conflict, we show
233      'C' in the tree-conflict column, overriding any other status.
234      We also print a separate line describing the nature of the tree
235      conflict. */
236   if (status->conflicted)
237     {
238       const char *desc;
239       const char *local_abspath = status->local_abspath;
240       svn_boolean_t text_conflicted;
241       svn_boolean_t prop_conflicted;
242       svn_boolean_t tree_conflicted;
243
244       if (status->versioned)
245         {
246           svn_error_t *err;
247
248           err = svn_wc_conflicted_p3(&text_conflicted,
249                                      &prop_conflicted,
250                                      &tree_conflicted, ctx->wc_ctx,
251                                      local_abspath, pool);
252
253           if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
254             {
255               svn_error_clear(err);
256               text_conflicted = FALSE;
257               prop_conflicted = FALSE;
258               tree_conflicted = FALSE;
259             }
260           else
261             SVN_ERR(err);
262         }
263       else
264         {
265           text_conflicted = FALSE;
266           prop_conflicted = FALSE;
267           tree_conflicted = TRUE;
268         }
269
270       if (tree_conflicted)
271         {
272           const svn_wc_conflict_description2_t *tree_conflict;
273           SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, ctx->wc_ctx,
274                                             local_abspath, pool, pool));
275           SVN_ERR_ASSERT(tree_conflict != NULL);
276
277           tree_status_code = 'C';
278           SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
279                             &desc, tree_conflict, pool));
280           tree_desc_line = apr_psprintf(pool, "\n      >   %s", desc);
281           (*tree_conflicts)++;
282         }
283       else if (text_conflicted)
284         (*text_conflicts)++;
285       else if (prop_conflicted)
286         (*prop_conflicts)++;
287     }
288
289   /* Note that moved-from and moved-to information is only available in STATUS
290    * for (op-)roots of a move. Those are exactly the nodes we want to show
291    * move info for in 'svn status'. See also comments in svn_wc_status3_t. */
292   if (status->moved_from_abspath && status->moved_to_abspath &&
293       strcmp(status->moved_from_abspath, status->moved_to_abspath) == 0)
294     {
295       const char *relpath;
296
297       relpath = make_relpath(cwd_abspath, status->moved_from_abspath,
298                              pool, pool);
299       relpath = svn_dirent_local_style(relpath, pool);
300       moved_from_line = apr_pstrcat(pool, "\n        > ",
301                                     apr_psprintf(pool,
302                                                  _("swapped places with %s"),
303                                                  relpath),
304                                     (char *)NULL);
305     }
306   else if (status->moved_from_abspath || status->moved_to_abspath)
307     {
308       const char *relpath;
309
310       if (status->moved_from_abspath)
311         {
312           relpath = make_relpath(cwd_abspath, status->moved_from_abspath,
313                                  pool, pool);
314           relpath = svn_dirent_local_style(relpath, pool);
315           moved_from_line = apr_pstrcat(pool, "\n        > ",
316                                         apr_psprintf(pool, _("moved from %s"),
317                                                      relpath),
318                                         (char *)NULL);
319         }
320
321       if (status->moved_to_abspath)
322         {
323           relpath = make_relpath(cwd_abspath, status->moved_to_abspath,
324                                  pool, pool);
325           relpath = svn_dirent_local_style(relpath, pool);
326           moved_to_line = apr_pstrcat(pool, "\n        > ",
327                                       apr_psprintf(pool, _("moved to %s"),
328                                                    relpath),
329                                       (char *)NULL);
330         }
331     }
332
333   if (detailed)
334     {
335       char ood_status, lock_status;
336       const char *working_rev;
337
338       if (! status->versioned)
339         working_rev = "";
340       else if (status->copied
341                || ! SVN_IS_VALID_REVNUM(status->revision))
342         working_rev = "-";
343       else
344         working_rev = apr_psprintf(pool, "%ld", status->revision);
345
346       if (status->repos_node_status != svn_wc_status_none)
347         ood_status = '*';
348       else
349         ood_status = ' ';
350
351       if (repos_locks)
352         {
353           if (status->repos_lock)
354             {
355               if (status->lock)
356                 {
357                   if (strcmp(status->repos_lock->token, status->lock->token)
358                       == 0)
359                     lock_status = 'K';
360                   else
361                     lock_status = 'T';
362                 }
363               else
364                 lock_status = 'O';
365             }
366           else if (status->lock)
367             lock_status = 'B';
368           else
369             lock_status = ' ';
370         }
371       else
372         lock_status = (status->lock) ? 'K' : ' ';
373
374       if (show_last_committed)
375         {
376           const char *commit_rev;
377           const char *commit_author;
378
379           if (SVN_IS_VALID_REVNUM(status->changed_rev))
380             commit_rev = apr_psprintf(pool, "%ld", status->changed_rev);
381           else if (status->versioned)
382             commit_rev = " ? ";
383           else
384             commit_rev = "";
385
386           if (status->changed_author)
387             commit_author = status->changed_author;
388           else if (status->versioned)
389             commit_author = " ? ";
390           else
391             commit_author = "";
392
393           SVN_ERR
394             (svn_cmdline_printf(pool,
395                                 "%c%c%c%c%c%c%c %c %8s %8s %-12s %s%s%s%s\n",
396                                 generate_status_code(combined_status(status)),
397                                 generate_status_code(prop_status),
398                                 status->wc_is_locked ? 'L' : ' ',
399                                 status->copied ? '+' : ' ',
400                                 generate_switch_column_code(status),
401                                 lock_status,
402                                 tree_status_code,
403                                 ood_status,
404                                 working_rev,
405                                 commit_rev,
406                                 commit_author,
407                                 path,
408                                 moved_to_line,
409                                 moved_from_line,
410                                 tree_desc_line));
411         }
412       else
413         SVN_ERR(
414            svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %8s   %s%s%s%s\n",
415                               generate_status_code(combined_status(status)),
416                               generate_status_code(prop_status),
417                               status->wc_is_locked ? 'L' : ' ',
418                               status->copied ? '+' : ' ',
419                               generate_switch_column_code(status),
420                               lock_status,
421                               tree_status_code,
422                               ood_status,
423                               working_rev,
424                               path,
425                               moved_to_line,
426                               moved_from_line,
427                               tree_desc_line));
428     }
429   else
430     SVN_ERR(
431        svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s%s%s\n",
432                           generate_status_code(combined_status(status)),
433                           generate_status_code(prop_status),
434                           status->wc_is_locked ? 'L' : ' ',
435                           status->copied ? '+' : ' ',
436                           generate_switch_column_code(status),
437                           ((status->lock)
438                            ? 'K' : ' '),
439                           tree_status_code,
440                           path,
441                           moved_to_line,
442                           moved_from_line,
443                           tree_desc_line));
444
445   return svn_cmdline_fflush(stdout);
446 }
447
448
449 svn_error_t *
450 svn_cl__print_status_xml(const char *cwd_abspath,
451                          const char *path,
452                          const svn_client_status_t *status,
453                          svn_client_ctx_t *ctx,
454                          apr_pool_t *pool)
455 {
456   svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
457   apr_hash_t *att_hash;
458   const char *local_abspath = status->local_abspath;
459   svn_boolean_t tree_conflicted = FALSE;
460
461   if (status->node_status == svn_wc_status_none
462       && status->repos_node_status == svn_wc_status_none)
463     return SVN_NO_ERROR;
464
465   if (status->conflicted)
466     SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
467                                  ctx->wc_ctx, local_abspath, pool));
468
469   path = make_relpath(cwd_abspath, path, pool, pool);
470
471   svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
472                         "path", svn_dirent_local_style(path, pool), NULL);
473
474   att_hash = apr_hash_make(pool);
475   svn_hash_sets(att_hash, "item",
476                 generate_status_desc(combined_status(status)));
477
478   svn_hash_sets(att_hash, "props",
479                 generate_status_desc(
480                    (status->node_status != svn_wc_status_deleted)
481                    ? status->prop_status
482                    : svn_wc_status_none));
483   if (status->wc_is_locked)
484     svn_hash_sets(att_hash, "wc-locked", "true");
485   if (status->copied)
486     svn_hash_sets(att_hash, "copied", "true");
487   if (status->switched)
488     svn_hash_sets(att_hash, "switched", "true");
489   if (status->file_external)
490     svn_hash_sets(att_hash, "file-external", "true");
491   if (status->versioned && ! status->copied)
492     svn_hash_sets(att_hash, "revision",
493                   apr_psprintf(pool, "%ld", status->revision));
494   if (tree_conflicted)
495     svn_hash_sets(att_hash, "tree-conflicted", "true");
496   if (status->moved_from_abspath || status->moved_to_abspath)
497     {
498       const char *relpath;
499
500       if (status->moved_from_abspath)
501         {
502           relpath = make_relpath(cwd_abspath, status->moved_from_abspath,
503                                  pool, pool);
504           relpath = svn_dirent_local_style(relpath, pool);
505           svn_hash_sets(att_hash, "moved-from", relpath);
506         }
507       if (status->moved_to_abspath)
508         {
509           relpath = make_relpath(cwd_abspath, status->moved_to_abspath,
510                                  pool, pool);
511           relpath = svn_dirent_local_style(relpath, pool);
512           svn_hash_sets(att_hash, "moved-to", relpath);
513         }
514     }
515   svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status",
516                              att_hash);
517
518   if (SVN_IS_VALID_REVNUM(status->changed_rev))
519     {
520       svn_cl__print_xml_commit(&sb, status->changed_rev,
521                                status->changed_author,
522                                svn_time_to_cstring(status->changed_date,
523                                                    pool),
524                                pool);
525     }
526
527   if (status->lock)
528     svn_cl__print_xml_lock(&sb, status->lock, pool);
529
530   svn_xml_make_close_tag(&sb, pool, "wc-status");
531
532   if (status->repos_node_status != svn_wc_status_none
533       || status->repos_lock)
534     {
535       svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status",
536                             "item",
537                             generate_status_desc(combined_repos_status(status)),
538                             "props",
539                             generate_status_desc(status->repos_prop_status),
540                             NULL);
541       if (status->repos_lock)
542         svn_cl__print_xml_lock(&sb, status->repos_lock, pool);
543
544       svn_xml_make_close_tag(&sb, pool, "repos-status");
545     }
546
547   svn_xml_make_close_tag(&sb, pool, "entry");
548
549   return svn_cl__error_checked_fputs(sb->data, stdout);
550 }
551
552 /* Called by status-cmd.c */
553 svn_error_t *
554 svn_cl__print_status(const char *cwd_abspath,
555                      const char *path,
556                      const svn_client_status_t *status,
557                      svn_boolean_t suppress_externals_placeholders,
558                      svn_boolean_t detailed,
559                      svn_boolean_t show_last_committed,
560                      svn_boolean_t skip_unrecognized,
561                      svn_boolean_t repos_locks,
562                      unsigned int *text_conflicts,
563                      unsigned int *prop_conflicts,
564                      unsigned int *tree_conflicts,
565                      svn_client_ctx_t *ctx,
566                      apr_pool_t *pool)
567 {
568   if (! status
569       || (skip_unrecognized
570           && !(status->versioned
571                || status->conflicted
572                || status->node_status == svn_wc_status_external))
573       || (status->node_status == svn_wc_status_none
574           && status->repos_node_status == svn_wc_status_none))
575     return SVN_NO_ERROR;
576
577   /* If we're trying not to print boring "X  /path/to/external"
578      lines..." */
579   if (suppress_externals_placeholders)
580     {
581       /* ... skip regular externals unmodified in the repository. */
582       if ((status->node_status == svn_wc_status_external)
583           && (status->repos_node_status == svn_wc_status_none)
584           && (! status->conflicted))
585         return SVN_NO_ERROR;
586
587       /* ... skip file externals that aren't modified locally or
588          remotely, changelisted, or locked (in either sense of the
589          word). */
590       if ((status->file_external)
591           && (status->repos_node_status == svn_wc_status_none)
592           && ((status->node_status == svn_wc_status_normal)
593               || (status->node_status == svn_wc_status_none))
594           && ((status->prop_status == svn_wc_status_normal)
595               || (status->prop_status == svn_wc_status_none))
596           && (! status->changelist)
597           && (! status->lock)
598           && (! status->wc_is_locked)
599           && (! status->conflicted))
600         return SVN_NO_ERROR;
601     }
602
603   return print_status(cwd_abspath, svn_dirent_local_style(path, pool),
604                       detailed, show_last_committed, repos_locks, status,
605                       text_conflicts, prop_conflicts, tree_conflicts,
606                       ctx, pool);
607 }