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