]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svn/status.c
MFC r275385 (by bapt):
[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    TARGET_ABSPATH shall be the absolute version of TARGET_PATH.
141    TARGET_ABSPATH, TARGET_PATH and LOCAL_ABSPATH shall be canonical
142
143    If above conditions are met, a relative path that leads to PATH
144    from TARGET_PATH is returned, but there is no error checking involved.
145
146    The returned path is allocated from RESULT_POOL, all other
147    allocations are made in SCRATCH_POOL.
148
149    Note that it is not possible to just join the resulting path to
150    reconstruct the real path as the "../" paths are relative from
151    a different base than the normal relative paths!
152  */
153 static const char *
154 make_relpath(const char *target_abspath,
155              const char *target_path,
156              const char *local_abspath,
157              apr_pool_t *result_pool,
158              apr_pool_t *scratch_pool)
159 {
160   const char *la;
161   const char *parent_dir_els = "";
162   const char *t_relpath;
163   const char *p_relpath;
164
165 #ifdef SVN_DEBUG
166   SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_abspath));
167 #endif
168
169   t_relpath = svn_dirent_skip_ancestor(target_abspath, local_abspath);
170
171   if (t_relpath)
172     return svn_dirent_join(target_path, t_relpath, result_pool);
173
174   /* An example:
175    *  relative_to_path = /a/b/c
176    *  path             = /a/x/y/z
177    *  result           = ../../x/y/z
178    *
179    * Another example (Windows specific):
180    *  relative_to_path = F:/wc
181    *  path             = C:/wc
182    *  result           = C:/wc
183    */
184   /* Skip the common ancestor of both paths, here '/a'. */
185   la = svn_dirent_get_longest_ancestor(target_abspath, local_abspath,
186                                        scratch_pool);
187   if (*la == '\0')
188     {
189       /* Nothing in common: E.g. C:/ vs F:/ on Windows */
190       return apr_pstrdup(result_pool, local_abspath);
191     }
192   t_relpath = svn_dirent_skip_ancestor(la, target_abspath);
193   p_relpath = svn_dirent_skip_ancestor(la, local_abspath);
194
195   /* In above example, we'd now have:
196    *  relative_to_path = b/c
197    *  path             = x/y/z */
198
199   /* Count the elements of relative_to_path and prepend as many '..' elements
200    * to path. */
201   while (*t_relpath)
202     {
203       t_relpath = svn_dirent_dirname(t_relpath, scratch_pool);
204       parent_dir_els = svn_dirent_join(parent_dir_els, "..", scratch_pool);
205     }
206
207   /* This returns a ../ style path relative from the status target */
208   return svn_dirent_join(parent_dir_els, p_relpath, result_pool);
209 }
210
211
212 /* Print STATUS and PATH in a format determined by DETAILED and
213    SHOW_LAST_COMMITTED. */
214 static svn_error_t *
215 print_status(const char *target_abspath,
216              const char *target_path,
217              const char *path,
218              svn_boolean_t detailed,
219              svn_boolean_t show_last_committed,
220              svn_boolean_t repos_locks,
221              const svn_client_status_t *status,
222              unsigned int *text_conflicts,
223              unsigned int *prop_conflicts,
224              unsigned int *tree_conflicts,
225              svn_client_ctx_t *ctx,
226              apr_pool_t *pool)
227 {
228   enum svn_wc_status_kind node_status = status->node_status;
229   enum svn_wc_status_kind prop_status = status->prop_status;
230   char tree_status_code = ' ';
231   const char *tree_desc_line = "";
232   const char *moved_from_line = "";
233   const char *moved_to_line = "";
234
235   /* For historic reasons svn ignores the property status for added nodes, even
236      if these nodes were copied and have local property changes.
237
238      Note that it doesn't do this on replacements, or children of copies.
239
240      ### Our test suite would catch more errors if we reported property
241          changes on copies. */
242   if (node_status == svn_wc_status_added)
243       prop_status = svn_wc_status_none;
244
245   /* To indicate this node is the victim of a tree conflict, we show
246      'C' in the tree-conflict column, overriding any other status.
247      We also print a separate line describing the nature of the tree
248      conflict. */
249   if (status->conflicted)
250     {
251       const char *desc;
252       const char *local_abspath = status->local_abspath;
253       svn_boolean_t text_conflicted;
254       svn_boolean_t prop_conflicted;
255       svn_boolean_t tree_conflicted;
256
257       if (status->versioned)
258         {
259           svn_error_t *err;
260
261           err = svn_wc_conflicted_p3(&text_conflicted,
262                                      &prop_conflicted,
263                                      &tree_conflicted, ctx->wc_ctx,
264                                      local_abspath, pool);
265
266           if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
267             {
268               svn_error_clear(err);
269               text_conflicted = FALSE;
270               prop_conflicted = FALSE;
271               tree_conflicted = FALSE;
272             }
273           else
274             SVN_ERR(err);
275         }
276       else
277         {
278           text_conflicted = FALSE;
279           prop_conflicted = FALSE;
280           tree_conflicted = TRUE;
281         }
282
283       if (tree_conflicted)
284         {
285           const svn_wc_conflict_description2_t *tree_conflict;
286           SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, ctx->wc_ctx,
287                                             local_abspath, pool, pool));
288           SVN_ERR_ASSERT(tree_conflict != NULL);
289
290           tree_status_code = 'C';
291           SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
292                             &desc, tree_conflict, pool));
293           tree_desc_line = apr_psprintf(pool, "\n      >   %s", desc);
294           (*tree_conflicts)++;
295         }
296       else if (text_conflicted)
297         (*text_conflicts)++;
298       else if (prop_conflicted)
299         (*prop_conflicts)++;
300     }
301
302   /* Note that moved-from and moved-to information is only available in STATUS
303    * for (op-)roots of a move. Those are exactly the nodes we want to show
304    * move info for in 'svn status'. See also comments in svn_wc_status3_t. */
305   if (status->moved_from_abspath && status->moved_to_abspath &&
306       strcmp(status->moved_from_abspath, status->moved_to_abspath) == 0)
307     {
308       const char *relpath;
309
310       relpath = make_relpath(target_abspath, target_path,
311                              status->moved_from_abspath,
312                              pool, pool);
313       relpath = svn_dirent_local_style(relpath, pool);
314       moved_from_line = apr_pstrcat(pool, "\n        > ",
315                                     apr_psprintf(pool,
316                                                  _("swapped places with %s"),
317                                                  relpath),
318                                     SVN_VA_NULL);
319     }
320   else if (status->moved_from_abspath || status->moved_to_abspath)
321     {
322       const char *relpath;
323
324       if (status->moved_from_abspath)
325         {
326           relpath = make_relpath(target_abspath, target_path,
327                                  status->moved_from_abspath,
328                                  pool, pool);
329           relpath = svn_dirent_local_style(relpath, pool);
330           moved_from_line = apr_pstrcat(pool, "\n        > ",
331                                         apr_psprintf(pool, _("moved from %s"),
332                                                      relpath),
333                                         SVN_VA_NULL);
334         }
335
336       if (status->moved_to_abspath)
337         {
338           relpath = make_relpath(target_abspath, target_path,
339                                  status->moved_to_abspath,
340                                  pool, pool);
341           relpath = svn_dirent_local_style(relpath, pool);
342           moved_to_line = apr_pstrcat(pool, "\n        > ",
343                                       apr_psprintf(pool, _("moved to %s"),
344                                                    relpath),
345                                       SVN_VA_NULL);
346         }
347     }
348
349   path = svn_dirent_local_style(path, pool);
350
351   if (detailed)
352     {
353       char ood_status, lock_status;
354       const char *working_rev;
355
356       if (! status->versioned)
357         working_rev = "";
358       else if (status->copied
359                || ! SVN_IS_VALID_REVNUM(status->revision))
360         working_rev = "-";
361       else
362         working_rev = apr_psprintf(pool, "%ld", status->revision);
363
364       if (status->repos_node_status != svn_wc_status_none)
365         ood_status = '*';
366       else
367         ood_status = ' ';
368
369       if (repos_locks)
370         {
371           if (status->repos_lock)
372             {
373               if (status->lock)
374                 {
375                   if (strcmp(status->repos_lock->token, status->lock->token)
376                       == 0)
377                     lock_status = 'K';
378                   else
379                     lock_status = 'T';
380                 }
381               else
382                 lock_status = 'O';
383             }
384           else if (status->lock)
385             lock_status = 'B';
386           else
387             lock_status = ' ';
388         }
389       else
390         lock_status = (status->lock) ? 'K' : ' ';
391
392       if (show_last_committed)
393         {
394           const char *commit_rev;
395           const char *commit_author;
396
397           if (SVN_IS_VALID_REVNUM(status->changed_rev))
398             commit_rev = apr_psprintf(pool, "%ld", status->changed_rev);
399           else if (status->versioned)
400             commit_rev = " ? ";
401           else
402             commit_rev = "";
403
404           if (status->changed_author)
405             commit_author = status->changed_author;
406           else if (status->versioned)
407             commit_author = " ? ";
408           else
409             commit_author = "";
410
411           SVN_ERR
412             (svn_cmdline_printf(pool,
413                                 "%c%c%c%c%c%c%c %c %8s %8s %-12s %s%s%s%s\n",
414                                 generate_status_code(combined_status(status)),
415                                 generate_status_code(prop_status),
416                                 status->wc_is_locked ? 'L' : ' ',
417                                 status->copied ? '+' : ' ',
418                                 generate_switch_column_code(status),
419                                 lock_status,
420                                 tree_status_code,
421                                 ood_status,
422                                 working_rev,
423                                 commit_rev,
424                                 commit_author,
425                                 path,
426                                 moved_to_line,
427                                 moved_from_line,
428                                 tree_desc_line));
429         }
430       else
431         SVN_ERR(
432            svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %8s   %s%s%s%s\n",
433                               generate_status_code(combined_status(status)),
434                               generate_status_code(prop_status),
435                               status->wc_is_locked ? 'L' : ' ',
436                               status->copied ? '+' : ' ',
437                               generate_switch_column_code(status),
438                               lock_status,
439                               tree_status_code,
440                               ood_status,
441                               working_rev,
442                               path,
443                               moved_to_line,
444                               moved_from_line,
445                               tree_desc_line));
446     }
447   else
448     SVN_ERR(
449        svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s%s%s\n",
450                           generate_status_code(combined_status(status)),
451                           generate_status_code(prop_status),
452                           status->wc_is_locked ? 'L' : ' ',
453                           status->copied ? '+' : ' ',
454                           generate_switch_column_code(status),
455                           ((status->lock)
456                            ? 'K' : ' '),
457                           tree_status_code,
458                           path,
459                           moved_to_line,
460                           moved_from_line,
461                           tree_desc_line));
462
463   return svn_cmdline_fflush(stdout);
464 }
465
466
467 svn_error_t *
468 svn_cl__print_status_xml(const char *target_abspath,
469                          const char *target_path,
470                          const char *path,
471                          const svn_client_status_t *status,
472                          svn_client_ctx_t *ctx,
473                          apr_pool_t *pool)
474 {
475   svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
476   apr_hash_t *att_hash;
477   const char *local_abspath = status->local_abspath;
478   svn_boolean_t tree_conflicted = FALSE;
479
480   if (status->node_status == svn_wc_status_none
481       && status->repos_node_status == svn_wc_status_none)
482     return SVN_NO_ERROR;
483
484   if (status->conflicted)
485     SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
486                                  ctx->wc_ctx, local_abspath, pool));
487
488   svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
489                         "path", svn_dirent_local_style(path, pool),
490                         SVN_VA_NULL);
491
492   att_hash = apr_hash_make(pool);
493   svn_hash_sets(att_hash, "item",
494                 generate_status_desc(combined_status(status)));
495
496   svn_hash_sets(att_hash, "props",
497                 generate_status_desc(
498                    (status->node_status != svn_wc_status_deleted)
499                    ? status->prop_status
500                    : svn_wc_status_none));
501   if (status->wc_is_locked)
502     svn_hash_sets(att_hash, "wc-locked", "true");
503   if (status->copied)
504     svn_hash_sets(att_hash, "copied", "true");
505   if (status->switched)
506     svn_hash_sets(att_hash, "switched", "true");
507   if (status->file_external)
508     svn_hash_sets(att_hash, "file-external", "true");
509   if (status->versioned && ! status->copied)
510     svn_hash_sets(att_hash, "revision",
511                   apr_psprintf(pool, "%ld", status->revision));
512   if (tree_conflicted)
513     svn_hash_sets(att_hash, "tree-conflicted", "true");
514   if (status->moved_from_abspath || status->moved_to_abspath)
515     {
516       const char *relpath;
517
518       if (status->moved_from_abspath)
519         {
520           relpath = make_relpath(target_abspath, target_path,
521                                  status->moved_from_abspath,
522                                  pool, pool);
523           relpath = svn_dirent_local_style(relpath, pool);
524           svn_hash_sets(att_hash, "moved-from", relpath);
525         }
526       if (status->moved_to_abspath)
527         {
528           relpath = make_relpath(target_abspath, target_path,
529                                  status->moved_to_abspath,
530                                  pool, pool);
531           relpath = svn_dirent_local_style(relpath, pool);
532           svn_hash_sets(att_hash, "moved-to", relpath);
533         }
534     }
535   svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status",
536                              att_hash);
537
538   if (SVN_IS_VALID_REVNUM(status->changed_rev))
539     {
540       svn_cl__print_xml_commit(&sb, status->changed_rev,
541                                status->changed_author,
542                                svn_time_to_cstring(status->changed_date,
543                                                    pool),
544                                pool);
545     }
546
547   if (status->lock)
548     svn_cl__print_xml_lock(&sb, status->lock, pool);
549
550   svn_xml_make_close_tag(&sb, pool, "wc-status");
551
552   if (status->repos_node_status != svn_wc_status_none
553       || status->repos_lock)
554     {
555       svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status",
556                             "item",
557                             generate_status_desc(combined_repos_status(status)),
558                             "props",
559                             generate_status_desc(status->repos_prop_status),
560                             SVN_VA_NULL);
561       if (status->repos_lock)
562         svn_cl__print_xml_lock(&sb, status->repos_lock, pool);
563
564       svn_xml_make_close_tag(&sb, pool, "repos-status");
565     }
566
567   svn_xml_make_close_tag(&sb, pool, "entry");
568
569   return svn_cl__error_checked_fputs(sb->data, stdout);
570 }
571
572 /* Called by status-cmd.c */
573 svn_error_t *
574 svn_cl__print_status(const char *target_abspath,
575                      const char *target_path,
576                      const char *path,
577                      const svn_client_status_t *status,
578                      svn_boolean_t suppress_externals_placeholders,
579                      svn_boolean_t detailed,
580                      svn_boolean_t show_last_committed,
581                      svn_boolean_t skip_unrecognized,
582                      svn_boolean_t repos_locks,
583                      unsigned int *text_conflicts,
584                      unsigned int *prop_conflicts,
585                      unsigned int *tree_conflicts,
586                      svn_client_ctx_t *ctx,
587                      apr_pool_t *pool)
588 {
589   if (! status
590       || (skip_unrecognized
591           && !(status->versioned
592                || status->conflicted
593                || status->node_status == svn_wc_status_external))
594       || (status->node_status == svn_wc_status_none
595           && status->repos_node_status == svn_wc_status_none))
596     return SVN_NO_ERROR;
597
598   /* If we're trying not to print boring "X  /path/to/external"
599      lines..." */
600   if (suppress_externals_placeholders)
601     {
602       /* ... skip regular externals unmodified in the repository. */
603       if ((status->node_status == svn_wc_status_external)
604           && (status->repos_node_status == svn_wc_status_none)
605           && (! status->conflicted))
606         return SVN_NO_ERROR;
607
608       /* ... skip file externals that aren't modified locally or
609          remotely, changelisted, or locked (in either sense of the
610          word). */
611       if ((status->file_external)
612           && (status->repos_node_status == svn_wc_status_none)
613           && ((status->node_status == svn_wc_status_normal)
614               || (status->node_status == svn_wc_status_none))
615           && ((status->prop_status == svn_wc_status_normal)
616               || (status->prop_status == svn_wc_status_none))
617           && (! status->changelist)
618           && (! status->lock)
619           && (! status->wc_is_locked)
620           && (! status->conflicted))
621         return SVN_NO_ERROR;
622     }
623
624   return print_status(target_abspath, target_path, path,
625                       detailed, show_last_committed, repos_locks, status,
626                       text_conflicts, prop_conflicts, tree_conflicts,
627                       ctx, pool);
628 }