You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2016/03/14 16:57:04 UTC

svn commit: r1734950 - /subversion/trunk/subversion/libsvn_client/conflicts.c

Author: stsp
Date: Mon Mar 14 15:57:04 2016
New Revision: 1734950

URL: http://svn.apache.org/viewvc?rev=1734950&view=rev
Log:
Implement fancy descriptions for conflicts involving an incoming addition.

* subversion/libsvn_client/conflicts.c
  (conflict_tree_incoming_add_details): New struct.
  (conflict_tree_get_details_incoming_add): New. Type-specific get_details()
   implementation for 'incoming add' conflicts.
  (describe_incoming_add_upon_update,
   describe_incoming_add_upon_switch,
   describe_incoming_add_upon_merge,
   describe_incoming_reverse_deletion_upon_merge): New functions. Describe
    incoming additions in more detail than the default message does.
  (conflict_tree_get_description_incoming_add): New. Type-specific
   get_description() implementation for 'incoming add' conflicts.
  (conflict_type_specific_setup): Add type-specific handlers for 'incoming add'.

Modified:
    subversion/trunk/subversion/libsvn_client/conflicts.c

Modified: subversion/trunk/subversion/libsvn_client/conflicts.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/conflicts.c?rev=1734950&r1=1734949&r2=1734950&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/conflicts.c (original)
+++ subversion/trunk/subversion/libsvn_client/conflicts.c Mon Mar 14 15:57:04 2016
@@ -1537,6 +1537,499 @@ conflict_tree_get_details_incoming_delet
   return SVN_NO_ERROR;
 }
 
+/* Details for tree conflicts involving incoming additions. */
+struct conflict_tree_incoming_add_details
+{
+  /* If not SVN_INVALID_REVNUM, the node was added in ADDED_REV. */
+  svn_revnum_t added_rev;
+
+  /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV.
+   * Note that both ADDED_REV and DELETED_REV may be valid for update/switch.
+   * See comment in conflict_tree_get_details_incoming_add() for details. */
+  svn_revnum_t deleted_rev;
+
+  /* The path which was added/deleted relative to the repository root. */
+  const char *repos_relpath;
+
+  /* Authors who committed ADDED_REV/DELETED_REV. */
+  const char *added_rev_author;
+  const char *deleted_rev_author;
+};
+
+/* Implements tree_conflict_get_details_func_t.
+ * Find the revision in which the victim was added in the repository. */
+static svn_error_t *
+conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict,
+                                       apr_pool_t *scratch_pool)
+{
+  const char *old_repos_relpath;
+  const char *new_repos_relpath;
+  const char *repos_root_url;
+  const char *repos_uuid;
+  svn_revnum_t old_rev;
+  svn_revnum_t new_rev;
+  struct conflict_tree_incoming_add_details *details;
+  svn_wc_operation_t operation;
+
+  SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+            &old_repos_relpath, &old_rev, NULL, conflict, scratch_pool,
+            scratch_pool));
+  SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+            &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool,
+            scratch_pool));
+  SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
+                                             conflict,
+                                             scratch_pool, scratch_pool));
+  operation = svn_client_conflict_get_operation(conflict);
+
+  if (operation == svn_wc_operation_update ||
+      operation == svn_wc_operation_switch)
+    {
+      /* Only the new repository location is recorded for the node which
+       * caused an incoming addition. There is no pre-update/pre-switch
+       * revision to be recorded for the node since it does not exist in
+       * the repository at that revision.
+       * The implication is that we cannot know whether the operation went
+       * forward or backwards in history. So always try to find an added
+       * and a deleted revision for the node. Users must figure out by whether
+       * the addition or deletion caused the conflict. */
+      const char *url;
+      const char *corrected_url;
+      svn_string_t *author_revprop;
+      struct find_added_rev_baton b;
+      svn_ra_session_t *ra_session;
+      svn_revnum_t deleted_rev;
+      svn_revnum_t head_rev;
+
+      url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
+                                        scratch_pool);
+      SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
+                                                   &corrected_url,
+                                                   url, NULL, NULL,
+                                                   FALSE,
+                                                   FALSE,
+                                                   conflict->ctx,
+                                                   scratch_pool,
+                                                   scratch_pool));
+
+      details = apr_pcalloc(conflict->pool, sizeof(*details));
+      b.added_rev = SVN_INVALID_REVNUM;
+      b.repos_relpath = NULL;
+      b.pool = scratch_pool;
+      /* Figure out when this node was added. */
+      SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev,
+                                           new_rev, SVN_INVALID_REVNUM,
+                                           find_added_rev, &b,
+                                           scratch_pool));
+      SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev,
+                              SVN_PROP_REVISION_AUTHOR,
+                              &author_revprop, scratch_pool));
+      details->repos_relpath = apr_pstrdup(conflict->pool, b.repos_relpath);
+      details->added_rev = b.added_rev;
+      details->added_rev_author = apr_pstrdup(conflict->pool,
+                                        author_revprop->data);
+
+      details->deleted_rev = SVN_INVALID_REVNUM;
+      details->deleted_rev_author = NULL;
+
+      /* Figure out whether this node was deleted later.
+       * ### Could probably optimize by infering both addition and deletion
+       * ### from svn_ra_get_location_segments() call above. */
+      SVN_ERR(svn_ra_get_latest_revnum(ra_session, &head_rev, scratch_pool));
+      if (new_rev < head_rev)
+        {
+          SVN_ERR(svn_ra_get_deleted_rev(ra_session, "", new_rev, head_rev,
+                                         &deleted_rev, scratch_pool));
+          if (SVN_IS_VALID_REVNUM(deleted_rev))
+           {
+              SVN_ERR(svn_ra_rev_prop(ra_session, deleted_rev,
+                                      SVN_PROP_REVISION_AUTHOR,
+                                      &author_revprop, scratch_pool));
+              details->deleted_rev = deleted_rev;
+              details->deleted_rev_author = apr_pstrdup(conflict->pool,
+                                                        author_revprop->data);
+            }
+        }
+    }
+  else if (operation == svn_wc_operation_merge)
+    {
+      if (old_rev < new_rev)
+        {
+          /* The merge operation went forwards in history.
+           * The addition of the node happened on the branch we merged form.
+           * Scan the nodes's history to find the revision which added it. */
+          const char *url;
+          const char *corrected_url;
+          svn_string_t *author_revprop;
+          struct find_added_rev_baton b;
+          svn_ra_session_t *ra_session;
+
+          url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
+                                            scratch_pool);
+          SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
+                                                       &corrected_url,
+                                                       url, NULL, NULL,
+                                                       FALSE,
+                                                       FALSE,
+                                                       conflict->ctx,
+                                                       scratch_pool,
+                                                       scratch_pool));
+
+          details = apr_pcalloc(conflict->pool, sizeof(*details));
+          b.added_rev = SVN_INVALID_REVNUM;
+          b.repos_relpath = NULL;
+          b.pool = scratch_pool;
+          /* Figure out when this node was added. */
+          SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev,
+                                               new_rev, old_rev,
+                                               find_added_rev, &b,
+                                               scratch_pool));
+          SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev,
+                                  SVN_PROP_REVISION_AUTHOR,
+                                  &author_revprop, scratch_pool));
+          details->repos_relpath = apr_pstrdup(conflict->pool, b.repos_relpath);
+          details->added_rev = b.added_rev;
+          details->added_rev_author = apr_pstrdup(conflict->pool,
+                                                  author_revprop->data);
+
+          details->deleted_rev = SVN_INVALID_REVNUM;
+          details->deleted_rev_author = NULL;
+        }
+      else
+        {
+          /* The merge operation was a reverse-merge.
+           * This addition is in fact a deletion, applied in reverse,
+           * which happened on the branch we merged from.
+           * Find the revision which deleted the node. */
+          svn_ra_session_t *ra_session;
+          const char *url;
+          const char *corrected_url;
+          svn_string_t *author_revprop;
+          svn_revnum_t deleted_rev;
+
+          url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
+                                            scratch_pool);
+          SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
+                                                       &corrected_url,
+                                                       url, NULL, NULL,
+                                                       FALSE,
+                                                       FALSE,
+                                                       conflict->ctx,
+                                                       scratch_pool,
+                                                       scratch_pool));
+          SVN_ERR(svn_ra_get_deleted_rev(ra_session, "", new_rev, old_rev,
+                                         &deleted_rev, scratch_pool));
+          SVN_ERR(svn_ra_rev_prop(ra_session, deleted_rev,
+                                  SVN_PROP_REVISION_AUTHOR,
+                                  &author_revprop, scratch_pool));
+          details = apr_pcalloc(conflict->pool, sizeof(*details));
+          details->repos_relpath = apr_pstrdup(conflict->pool,
+                                               new_repos_relpath);
+          details->deleted_rev = deleted_rev;
+          details->deleted_rev_author = apr_pstrdup(conflict->pool,
+                                                    author_revprop->data);
+
+          details->added_rev = SVN_INVALID_REVNUM;
+          details->added_rev_author = NULL;
+        }
+    }
+
+  conflict->tree_conflict_details = details;
+
+  return SVN_NO_ERROR;
+}
+
+static const char *
+describe_incoming_add_upon_update(
+  struct conflict_tree_incoming_add_details *details,
+  svn_node_kind_t new_node_kind,
+  svn_revnum_t new_rev,
+  apr_pool_t *result_pool)
+{
+  if (new_node_kind == svn_node_dir)
+    {
+      if (SVN_IS_VALID_REVNUM(details->added_rev) &&
+          SVN_IS_VALID_REVNUM(details->deleted_rev))
+        return apr_psprintf(result_pool,
+                            _("directory appeared during update to r%ld; "
+                              "it was added by %s in r%ld and later deleted "
+                              "by %s in r%ld"), new_rev,
+                            details->added_rev_author, details->added_rev,
+                            details->deleted_rev_author, details->deleted_rev);
+      else if (SVN_IS_VALID_REVNUM(details->added_rev))
+        return apr_psprintf(result_pool,
+                            _("directory appeared during update to r%ld; "
+                              "it was added by %s in r%ld"), new_rev,
+                            details->added_rev_author, details->added_rev);
+      else
+        return apr_psprintf(result_pool,
+                            _("directory appeared during update to r%ld; "
+                              "it was deleted by %s in r%ld"), new_rev,
+                            details->deleted_rev_author, details->deleted_rev);
+    }
+  else if (new_node_kind == svn_node_file ||
+           new_node_kind == svn_node_symlink)
+      if (SVN_IS_VALID_REVNUM(details->added_rev) &&
+          SVN_IS_VALID_REVNUM(details->deleted_rev))
+        return apr_psprintf(result_pool,
+                            _("file appeared during update to r%ld; "
+                              "it was added by %s in r%ld and later deleted "
+                              "by %s in r%ld"), new_rev,
+                            details->added_rev_author, details->added_rev,
+                            details->deleted_rev_author, details->deleted_rev);
+      else if (SVN_IS_VALID_REVNUM(details->added_rev))
+        return apr_psprintf(result_pool,
+                            _("file appeared during update to r%ld; "
+                              "it was added by %s in r%ld"), new_rev,
+                            details->added_rev_author, details->added_rev);
+      else
+        return apr_psprintf(result_pool,
+                            _("file appeared during update to r%ld; "
+                              "it was deleted by %s in r%ld"), new_rev,
+                            details->deleted_rev_author, details->deleted_rev);
+  else
+      if (SVN_IS_VALID_REVNUM(details->added_rev) &&
+          SVN_IS_VALID_REVNUM(details->deleted_rev))
+        return apr_psprintf(result_pool,
+                            _("item appeared during update to r%ld; "
+                              "it was added by %s in r%ld and later deleted "
+                              "by %s in r%ld"), new_rev,
+                            details->added_rev_author, details->added_rev,
+                            details->deleted_rev_author, details->deleted_rev);
+      else if (SVN_IS_VALID_REVNUM(details->added_rev))
+        return apr_psprintf(result_pool,
+                            _("item appeared during update to r%ld; "
+                              "it was added by %s in r%ld"), new_rev,
+                            details->added_rev_author, details->added_rev);
+      else
+        return apr_psprintf(result_pool,
+                            _("item appeared during update to r%ld; "
+                              "it was deleted by %s in r%ld"), new_rev,
+                            details->deleted_rev_author, details->deleted_rev);
+
+  return SVN_NO_ERROR;
+}
+
+static const char *
+describe_incoming_add_upon_switch(
+  struct conflict_tree_incoming_add_details *details,
+  svn_node_kind_t victim_node_kind,
+  const char *new_repos_relpath,
+  svn_revnum_t new_rev,
+  apr_pool_t *result_pool)
+{
+  if (victim_node_kind == svn_node_dir)
+    {
+      if (SVN_IS_VALID_REVNUM(details->added_rev) &&
+          SVN_IS_VALID_REVNUM(details->deleted_rev))
+        return apr_psprintf(result_pool,
+                            _("directory appeared during switch to ^/%s@%ld; "
+                              "it was added by %s in r%ld and later deleted "
+                              "by %s in r%ld"), new_repos_relpath, new_rev,
+                            details->added_rev_author, details->added_rev,
+                            details->deleted_rev_author, details->deleted_rev);
+      else if (SVN_IS_VALID_REVNUM(details->added_rev))
+        return apr_psprintf(result_pool,
+                            _("directory appeared during switch to ^/%s@%ld; "
+                              "it was added by %s in r%ld"),
+                            new_repos_relpath, new_rev,
+                            details->added_rev_author, details->added_rev);
+      else
+        return apr_psprintf(result_pool,
+                            _("directory appeared during switch to ^/%s@%ld; "
+                              "it was deleted by %s in r%ld"),
+                            new_repos_relpath, new_rev,
+                            details->deleted_rev_author, details->deleted_rev);
+    }
+  else if (victim_node_kind == svn_node_file ||
+           victim_node_kind == svn_node_symlink)
+    {
+      if (SVN_IS_VALID_REVNUM(details->added_rev) &&
+          SVN_IS_VALID_REVNUM(details->deleted_rev))
+        return apr_psprintf(result_pool,
+                            _("file appeared during switch to ^/%s@%ld; "
+                              "it was added by %s in r%ld and later deleted "
+                              "by %s in r%ld"), new_repos_relpath, new_rev,
+                            details->added_rev_author, details->added_rev,
+                            details->deleted_rev_author, details->deleted_rev);
+      else if (SVN_IS_VALID_REVNUM(details->added_rev))
+        return apr_psprintf(result_pool,
+                            _("file appeared during switch to ^/%s@%ld; "
+                              "it was added by %s in r%ld"),
+                            new_repos_relpath, new_rev,
+                            details->added_rev_author, details->added_rev);
+      else
+        return apr_psprintf(result_pool,
+                            _("file appeared during switch to ^/%s@%ld; "
+                              "it was deleted by %s in r%ld"),
+                            new_repos_relpath, new_rev,
+                            details->deleted_rev_author, details->deleted_rev);
+    }
+  else
+    {
+      if (SVN_IS_VALID_REVNUM(details->added_rev) &&
+          SVN_IS_VALID_REVNUM(details->deleted_rev))
+        return apr_psprintf(result_pool,
+                            _("item appeared during switch to ^/%s@%ld; "
+                              "it was added by %s in r%ld and later deleted "
+                              "by %s in r%ld"), new_repos_relpath, new_rev,
+                            details->added_rev_author, details->added_rev,
+                            details->deleted_rev_author, details->deleted_rev);
+      else if (SVN_IS_VALID_REVNUM(details->added_rev))
+        return apr_psprintf(result_pool,
+                            _("item appeared during switch to ^/%s@%ld; "
+                              "it was added by %s in r%ld"),
+                            new_repos_relpath, new_rev,
+                            details->added_rev_author, details->added_rev);
+      else
+        return apr_psprintf(result_pool,
+                            _("item appeared during switch to ^/%s@%ld; "
+                              "it was deleted by %s in r%ld"),
+                            new_repos_relpath, new_rev,
+                            details->deleted_rev_author, details->deleted_rev);
+    }
+  return SVN_NO_ERROR;
+}
+
+static const char *
+describe_incoming_add_upon_merge(
+  struct conflict_tree_incoming_add_details *details,
+  svn_node_kind_t new_node_kind,
+  svn_revnum_t old_rev,
+  const char *new_repos_relpath,
+  svn_revnum_t new_rev,
+  apr_pool_t *result_pool)
+{
+  if (new_node_kind == svn_node_dir)
+    return apr_psprintf(result_pool,
+                        _("directory appeared during merge of ^/%s:%ld-%ld; "
+                          "it was added by %s in r%ld"),
+                        new_repos_relpath, old_rev, new_rev,
+                        details->added_rev_author, details->added_rev);
+  else if (new_node_kind == svn_node_file ||
+           new_node_kind == svn_node_symlink)
+    return apr_psprintf(result_pool,
+                        _("file appeared during merge of ^/%s:%ld-%ld; "
+                          "it was added by %s in r%ld"),
+                        new_repos_relpath, old_rev, new_rev,
+                        details->added_rev_author, details->added_rev);
+  else
+    return apr_psprintf(result_pool,
+                        _("item appeared during merge of ^/%s:%ld-%ld; "
+                          "it was added by %s in r%ld"),
+                        new_repos_relpath, old_rev, new_rev,
+                        details->added_rev_author, details->added_rev);
+  return SVN_NO_ERROR;
+}
+
+static const char *
+describe_incoming_reverse_deletion_upon_merge(
+  struct conflict_tree_incoming_add_details *details,
+  svn_node_kind_t new_node_kind,
+  const char *old_repos_relpath,
+  svn_revnum_t old_rev,
+  svn_revnum_t new_rev,
+  apr_pool_t *result_pool)
+{
+  if (new_node_kind == svn_node_dir)
+    return apr_psprintf(result_pool,
+                        _("directory appeared during reverse-merge of "
+                          "^/%s:%ld-%ld; it was deleted by %s in r%ld"),
+                        old_repos_relpath, new_rev, old_rev,
+                        details->deleted_rev_author, details->deleted_rev);
+  else if (new_node_kind == svn_node_file ||
+           new_node_kind == svn_node_symlink)
+    return apr_psprintf(result_pool,
+                        _("file appeared during reverse-merge of "
+                          "^/%s:%ld-%ld; it was deleted by %s in r%ld"),
+                        old_repos_relpath, new_rev, old_rev,
+                        details->deleted_rev_author, details->deleted_rev);
+  else
+    return apr_psprintf(result_pool,
+                        _("item appeared during reverse-merge of "
+                          "^/%s:%ld-%ld; it was deleted by %s in r%ld"),
+                        old_repos_relpath, new_rev, old_rev,
+                        details->deleted_rev_author, details->deleted_rev);
+  return SVN_NO_ERROR;
+}
+
+/* Implements tree_conflict_get_description_func_t. */
+static svn_error_t *
+conflict_tree_get_description_incoming_add(const char **description,
+                                           svn_client_conflict_t *conflict,
+                                           apr_pool_t *result_pool,
+                                           apr_pool_t *scratch_pool)
+{
+  const char *action, *reason;
+  svn_node_kind_t victim_node_kind;
+  svn_wc_conflict_reason_t local_change;
+  svn_wc_operation_t conflict_operation;
+  const char *old_repos_relpath;
+  svn_revnum_t old_rev;
+  svn_node_kind_t old_node_kind;
+  const char *new_repos_relpath;
+  svn_revnum_t new_rev;
+  svn_node_kind_t new_node_kind;
+  struct conflict_tree_incoming_add_details *details;
+
+  if (conflict->tree_conflict_details == NULL)
+    return svn_error_trace(conflict_tree_get_description_generic(description,
+                                                                 conflict,
+                                                                 result_pool,
+                                                                 scratch_pool));
+
+  local_change = svn_client_conflict_get_local_change(conflict);
+  conflict_operation = svn_client_conflict_get_operation(conflict);
+  victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
+  reason = local_reason_str(victim_node_kind, local_change, conflict_operation);
+  if (reason == NULL)
+    return svn_error_trace(conflict_tree_get_description_generic(description,
+                                                                 conflict,
+                                                                 result_pool,
+                                                                 scratch_pool));
+  SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+            &old_repos_relpath, &old_rev, &old_node_kind, conflict,
+            scratch_pool, scratch_pool));
+  SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+            &new_repos_relpath, &new_rev, &new_node_kind, conflict,
+            scratch_pool, scratch_pool));
+
+  details = conflict->tree_conflict_details;
+
+  if (conflict_operation == svn_wc_operation_update)
+    {
+      action = describe_incoming_add_upon_update(details,
+                                                 new_node_kind,
+                                                 new_rev,
+                                                 result_pool);
+    }
+  else if (conflict_operation == svn_wc_operation_switch)
+    {
+      action = describe_incoming_add_upon_switch(details,
+                                                 victim_node_kind,
+                                                 new_repos_relpath,
+                                                 new_rev,
+                                                 result_pool);
+    }
+  else if (conflict_operation == svn_wc_operation_merge)
+    {
+      if (old_rev < new_rev)
+        action = describe_incoming_add_upon_merge(details,
+                                                  new_node_kind,
+                                                  old_rev,
+                                                  new_repos_relpath,
+                                                  new_rev,
+                                                  result_pool);
+      else
+        action = describe_incoming_reverse_deletion_upon_merge(
+                   details, new_node_kind, old_repos_relpath,
+                   old_rev, new_rev, result_pool);
+    }
+
+  *description = apr_psprintf(result_pool, _("%s, %s"), reason, action);
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_client_conflict_tree_get_description(const char **description,
                                          svn_client_conflict_t *conflict,
@@ -2720,6 +3213,13 @@ conflict_type_specific_setup(svn_client_
       conflict->tree_conflict_get_details_func =
         conflict_tree_get_details_incoming_delete;
     }
+  else if (incoming_change == svn_wc_conflict_action_add)
+    {
+      conflict->tree_conflict_get_description_func =
+        conflict_tree_get_description_incoming_add;
+      conflict->tree_conflict_get_details_func =
+        conflict_tree_get_details_incoming_add;
+    }
 
   return SVN_NO_ERROR;
 }