]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/svn/status.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.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           svn_client_conflict_t *tree_conflict;
286
287           SVN_ERR(svn_client_conflict_get(&tree_conflict, local_abspath,
288                                           ctx, pool, pool));
289           tree_status_code = 'C';
290           SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
291                             &desc, tree_conflict, pool));
292           tree_desc_line = apr_psprintf(pool, "\n      >   %s", desc);
293           (*tree_conflicts)++;
294         }
295       else if (text_conflicted)
296         (*text_conflicts)++;
297       else if (prop_conflicted)
298         (*prop_conflicts)++;
299     }
300
301   /* Note that moved-from and moved-to information is only available in STATUS
302    * for (op-)roots of a move. Those are exactly the nodes we want to show
303    * move info for in 'svn status'. See also comments in svn_wc_status3_t. */
304   if (status->moved_from_abspath && status->moved_to_abspath &&
305       strcmp(status->moved_from_abspath, status->moved_to_abspath) == 0)
306     {
307       const char *relpath;
308
309       relpath = make_relpath(target_abspath, target_path,
310                              status->moved_from_abspath,
311                              pool, pool);
312       relpath = svn_dirent_local_style(relpath, pool);
313       moved_from_line = apr_pstrcat(pool, "\n        > ",
314                                     apr_psprintf(pool,
315                                                  _("swapped places with %s"),
316                                                  relpath),
317                                     SVN_VA_NULL);
318     }
319   else if (status->moved_from_abspath || status->moved_to_abspath)
320     {
321       const char *relpath;
322
323       if (status->moved_from_abspath)
324         {
325           relpath = make_relpath(target_abspath, target_path,
326                                  status->moved_from_abspath,
327                                  pool, pool);
328           relpath = svn_dirent_local_style(relpath, pool);
329           moved_from_line = apr_pstrcat(pool, "\n        > ",
330                                         apr_psprintf(pool, _("moved from %s"),
331                                                      relpath),
332                                         SVN_VA_NULL);
333         }
334
335       if (status->moved_to_abspath)
336         {
337           relpath = make_relpath(target_abspath, target_path,
338                                  status->moved_to_abspath,
339                                  pool, pool);
340           relpath = svn_dirent_local_style(relpath, pool);
341           moved_to_line = apr_pstrcat(pool, "\n        > ",
342                                       apr_psprintf(pool, _("moved to %s"),
343                                                    relpath),
344                                       SVN_VA_NULL);
345         }
346     }
347
348   path = svn_dirent_local_style(path, pool);
349
350   if (detailed)
351     {
352       char ood_status, lock_status;
353       const char *working_rev;
354
355       if (! status->versioned)
356         working_rev = "";
357       else if (status->copied
358                || ! SVN_IS_VALID_REVNUM(status->revision))
359         working_rev = "-";
360       else
361         working_rev = apr_psprintf(pool, "%ld", status->revision);
362
363       if (status->repos_node_status != svn_wc_status_none)
364         ood_status = '*';
365       else
366         ood_status = ' ';
367
368       if (repos_locks)
369         {
370           if (status->repos_lock)
371             {
372               if (status->lock)
373                 {
374                   if (strcmp(status->repos_lock->token, status->lock->token)
375                       == 0)
376                     lock_status = 'K';
377                   else
378                     lock_status = 'T';
379                 }
380               else
381                 lock_status = 'O';
382             }
383           else if (status->lock)
384             lock_status = 'B';
385           else
386             lock_status = ' ';
387         }
388       else
389         lock_status = (status->lock) ? 'K' : ' ';
390
391       if (show_last_committed)
392         {
393           const char *commit_rev;
394           const char *commit_author;
395
396           if (SVN_IS_VALID_REVNUM(status->changed_rev))
397             commit_rev = apr_psprintf(pool, "%ld", status->changed_rev);
398           else if (status->versioned)
399             commit_rev = " ? ";
400           else
401             commit_rev = "";
402
403           if (status->changed_author)
404             commit_author = status->changed_author;
405           else if (status->versioned)
406             commit_author = " ? ";
407           else
408             commit_author = "";
409
410           SVN_ERR
411             (svn_cmdline_printf(pool,
412                                 "%c%c%c%c%c%c%c %c %8s %8s %-12s %s%s%s%s\n",
413                                 generate_status_code(combined_status(status)),
414                                 generate_status_code(prop_status),
415                                 status->wc_is_locked ? 'L' : ' ',
416                                 status->copied ? '+' : ' ',
417                                 generate_switch_column_code(status),
418                                 lock_status,
419                                 tree_status_code,
420                                 ood_status,
421                                 working_rev,
422                                 commit_rev,
423                                 commit_author,
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 %c %8s   %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                               lock_status,
438                               tree_status_code,
439                               ood_status,
440                               working_rev,
441                               path,
442                               moved_to_line,
443                               moved_from_line,
444                               tree_desc_line));
445     }
446   else
447     SVN_ERR(
448        svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s%s%s\n",
449                           generate_status_code(combined_status(status)),
450                           generate_status_code(prop_status),
451                           status->wc_is_locked ? 'L' : ' ',
452                           status->copied ? '+' : ' ',
453                           generate_switch_column_code(status),
454                           ((status->lock)
455                            ? 'K' : ' '),
456                           tree_status_code,
457                           path,
458                           moved_to_line,
459                           moved_from_line,
460                           tree_desc_line));
461
462   return svn_cmdline_fflush(stdout);
463 }
464
465
466 svn_error_t *
467 svn_cl__print_status_xml(const char *target_abspath,
468                          const char *target_path,
469                          const char *path,
470                          const svn_client_status_t *status,
471                          svn_client_ctx_t *ctx,
472                          apr_pool_t *pool)
473 {
474   svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
475   apr_hash_t *att_hash;
476   const char *local_abspath = status->local_abspath;
477   svn_boolean_t tree_conflicted = FALSE;
478
479   if (status->node_status == svn_wc_status_none
480       && status->repos_node_status == svn_wc_status_none)
481     return SVN_NO_ERROR;
482
483   if (status->conflicted)
484     SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
485                                  ctx->wc_ctx, local_abspath, pool));
486
487   svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
488                         "path", svn_dirent_local_style(path, pool),
489                         SVN_VA_NULL);
490
491   att_hash = apr_hash_make(pool);
492   svn_hash_sets(att_hash, "item",
493                 generate_status_desc(combined_status(status)));
494
495   svn_hash_sets(att_hash, "props",
496                 generate_status_desc(
497                    (status->node_status != svn_wc_status_deleted)
498                    ? status->prop_status
499                    : svn_wc_status_none));
500   if (status->wc_is_locked)
501     svn_hash_sets(att_hash, "wc-locked", "true");
502   if (status->copied)
503     svn_hash_sets(att_hash, "copied", "true");
504   if (status->switched)
505     svn_hash_sets(att_hash, "switched", "true");
506   if (status->file_external)
507     svn_hash_sets(att_hash, "file-external", "true");
508   if (status->versioned && ! status->copied)
509     svn_hash_sets(att_hash, "revision",
510                   apr_psprintf(pool, "%ld", status->revision));
511   if (tree_conflicted)
512     svn_hash_sets(att_hash, "tree-conflicted", "true");
513   if (status->moved_from_abspath || status->moved_to_abspath)
514     {
515       const char *relpath;
516
517       if (status->moved_from_abspath)
518         {
519           relpath = make_relpath(target_abspath, target_path,
520                                  status->moved_from_abspath,
521                                  pool, pool);
522           relpath = svn_dirent_local_style(relpath, pool);
523           svn_hash_sets(att_hash, "moved-from", relpath);
524         }
525       if (status->moved_to_abspath)
526         {
527           relpath = make_relpath(target_abspath, target_path,
528                                  status->moved_to_abspath,
529                                  pool, pool);
530           relpath = svn_dirent_local_style(relpath, pool);
531           svn_hash_sets(att_hash, "moved-to", relpath);
532         }
533     }
534   svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status",
535                              att_hash);
536
537   if (SVN_IS_VALID_REVNUM(status->changed_rev))
538     {
539       svn_cl__print_xml_commit(&sb, status->changed_rev,
540                                status->changed_author,
541                                svn_time_to_cstring(status->changed_date,
542                                                    pool),
543                                pool);
544     }
545
546   if (status->lock)
547     svn_cl__print_xml_lock(&sb, status->lock, pool);
548
549   svn_xml_make_close_tag(&sb, pool, "wc-status");
550
551   if (status->repos_node_status != svn_wc_status_none
552       || status->repos_lock)
553     {
554       svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status",
555                             "item",
556                             generate_status_desc(combined_repos_status(status)),
557                             "props",
558                             generate_status_desc(status->repos_prop_status),
559                             SVN_VA_NULL);
560       if (status->repos_lock)
561         svn_cl__print_xml_lock(&sb, status->repos_lock, pool);
562
563       svn_xml_make_close_tag(&sb, pool, "repos-status");
564     }
565
566   svn_xml_make_close_tag(&sb, pool, "entry");
567
568   return svn_cl__error_checked_fputs(sb->data, stdout);
569 }
570
571 /* Called by status-cmd.c */
572 svn_error_t *
573 svn_cl__print_status(const char *target_abspath,
574                      const char *target_path,
575                      const char *path,
576                      const svn_client_status_t *status,
577                      svn_boolean_t suppress_externals_placeholders,
578                      svn_boolean_t detailed,
579                      svn_boolean_t show_last_committed,
580                      svn_boolean_t skip_unrecognized,
581                      svn_boolean_t repos_locks,
582                      unsigned int *text_conflicts,
583                      unsigned int *prop_conflicts,
584                      unsigned int *tree_conflicts,
585                      svn_client_ctx_t *ctx,
586                      apr_pool_t *pool)
587 {
588   if (! status
589       || (skip_unrecognized
590           && !(status->versioned
591                || status->conflicted
592                || status->node_status == svn_wc_status_external))
593       || (status->node_status == svn_wc_status_none
594           && status->repos_node_status == svn_wc_status_none))
595     return SVN_NO_ERROR;
596
597   /* If we're trying not to print boring "X  /path/to/external"
598      lines..." */
599   if (suppress_externals_placeholders)
600     {
601       /* ... skip regular externals unmodified in the repository. */
602       if ((status->node_status == svn_wc_status_external)
603           && (status->repos_node_status == svn_wc_status_none)
604           && (! status->conflicted))
605         return SVN_NO_ERROR;
606
607       /* ... skip file externals that aren't modified locally or
608          remotely, changelisted, or locked (in either sense of the
609          word). */
610       if ((status->file_external)
611           && (status->repos_node_status == svn_wc_status_none)
612           && ((status->node_status == svn_wc_status_normal)
613               || (status->node_status == svn_wc_status_none))
614           && ((status->prop_status == svn_wc_status_normal)
615               || (status->prop_status == svn_wc_status_none))
616           && (! status->changelist)
617           && (! status->lock)
618           && (! status->wc_is_locked)
619           && (! status->conflicted))
620         return SVN_NO_ERROR;
621     }
622
623   return print_status(target_abspath, target_path, path,
624                       detailed, show_last_committed, repos_locks, status,
625                       text_conflicts, prop_conflicts, tree_conflicts,
626                       ctx, pool);
627 }