#!/usr/bin/env python
#
#  info_tests.py:  testing the svn info command
#
#  Subversion is a tool for revision control.
#  See http://subversion.apache.org for more information.
#
# ====================================================================
#    Licensed to the Apache Software Foundation (ASF) under one
#    or more contributor license agreements.  See the NOTICE file
#    distributed with this work for additional information
#    regarding copyright ownership.  The ASF licenses this file
#    to you under the Apache License, Version 2.0 (the
#    "License"); you may not use this file except in compliance
#    with the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing,
#    software distributed under the License is distributed on an
#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#    KIND, either express or implied.  See the License for the
#    specific language governing permissions and limitations
#    under the License.
######################################################################

# See basic-tests.py for more svn info tests.

# General modules
import shutil, stat, re, os, logging

logger = logging.getLogger()

# Our testing module
import svntest

from prop_tests import binary_mime_type_on_text_file_warning

# (abbreviation)
Skip = svntest.testcase.Skip_deco
SkipUnless = svntest.testcase.SkipUnless_deco
XFail = svntest.testcase.XFail_deco
Issues = svntest.testcase.Issues_deco
Issue = svntest.testcase.Issue_deco
Wimp = svntest.testcase.Wimp_deco
Item = svntest.wc.StateItem

######################################################################
# Tests
#
#   Each test must return on success or raise on failure.

#----------------------------------------------------------------------

# Helpers for XML output
def verify_xml_elements(lines, exprs):
  """Verify that each of the given expressions matches exactly one XML
     element in the list of lines. Each expression is a tuple containing
     a name (a string), a set of attribute name-value pairs (a dict of
     string->string), and element content (a string).  The attribute dict
     and the content string are by default empty.

     Expression format: [ ('name', {'att': 'val', ...}, 'text') , ...]

     Limitations:
     We don't verify that the input is a valid XML document.
     We can't verify text mixed with child elements.
     We don't handle XML comments.
     All of these are taken care of by the Relax NG schemas.
  """
  xml_version_re = re.compile(r"<\?xml\s+[^?]+\?>")

  str = ""
  for line in lines:
    str += line
  m = xml_version_re.match(str)
  if m:
    str = str[m.end():] # skip xml version tag
  (unmatched_str, unmatched_exprs) = match_xml_element(str, exprs)
  if unmatched_exprs:
    logger.warn("Failed to find the following expressions:")
    for expr in unmatched_exprs:
      logger.warn(expr)
    raise svntest.tree.SVNTreeUnequal

def match_xml_element(str, exprs):
  """Read from STR until the start of an element. If no element is found,
     return the arguments.  Get the element name, attributes and text content.
     If not empty, call recursively on the text content.  Compare the current
     element to all expressions in EXPRS.  If no elements were found in the
     current element's text, include the text in the comparison (i.e., we
     don't support mixed content).  Return the unmatched part of the string
     and any unmatched expressions.
  """
  start_tag_re = re.compile(r"[^<]*<(?P<name>[\w-]+)", re.M)
  atttribute_re = re.compile(
                 r"\s+(?P<key>[\w-]+)\s*=\s*(['\"])(?P<val>[^'\"]*)\2", re.M)
  self_closing_re = re.compile(r"\s*/>", re.M)
  content_re_str = r"\s*>(?P<content>.*?)</%s\s*>"

  m = start_tag_re.match(str)
  if not m:
    return (str, exprs)
  name = m.group('name')
  str = str[m.end():]
  atts = {}
  while True:
    m = atttribute_re.match(str)
    if not m:
      break
    else:
      atts[m.group('key')] = m.group('val')
      str = str[m.end():]
  m = self_closing_re.match(str)
  if m:
    content = ''
    str = str[m.end():]
  else:
    content_re = re.compile(content_re_str % name, re.DOTALL)
    m = content_re.match(str)
    if not m:
      logger.warn("No XML end-tag for '%s' found in '%s...'" % (name, str[:100]))
      raise(svntest.tree.SVNTreeUnequal)
    content = m.group('content')
    str = str[m.end():]
  if content != '':
    while True:
      (new_content, exprs) = match_xml_element(content, exprs)
      if new_content == content:
        # there are no (more) child elements
        break
      else:
        content = new_content
  if exprs:
    for expr in exprs:
      # compare element names
      e_name = expr[0]
      if (e_name != name):
        continue
      # compare element attributes
      e_atts = {}
      if len(expr) > 1:
        e_atts = expr[1]
      if e_atts != atts:
        continue
      # compare element content (text only)
      e_content = ''
      if len(expr) > 2:
        e_content = expr[2]
      if (not re.search(e_content, content)):
        continue
      # success!
      exprs.remove(expr)
  return (str, exprs)

def info_with_tree_conflicts(sbox):
  "info with tree conflicts"

  # Info messages reflecting tree conflict status.
  # These tests correspond to use cases 1-3 in
  # notes/tree-conflicts/use-cases.txt.

  svntest.actions.build_greek_tree_conflicts(sbox)
  wc_dir = sbox.wc_dir
  G = os.path.join(wc_dir, 'A', 'D', 'G')

  scenarios = [
    # (filename, action, reason)
    ('pi',  'edit',   'delete'),
    ('rho', 'delete', 'edit'),
    ('tau', 'delete', 'delete'),
    ]

  for fname, action, reason in scenarios:
    path = os.path.join(G, fname)

    # check plain info
    expected_str1 = ".*local file %s, incoming file %s.*" % (reason, action)
    expected_info = { 'Tree conflict' : expected_str1 }
    svntest.actions.run_and_verify_info([expected_info], path)

    # check XML info
    exit_code, output, error = svntest.actions.run_and_verify_svn(None,
                                                                  [], 'info',
                                                                  path,
                                                                  '--xml')

    # In the XML, action and reason are past tense: 'edited' not 'edit'.
    verify_xml_elements(output,
                        [('tree-conflict', {'victim'   : fname,
                                            'kind'     : 'file',
                                            'operation': 'update',
                                            'action'   : action,
                                            'reason'   : reason,
                                            },
                          )])

  # Check recursive info.
  expected_infos = [{ 'Path' : re.escape(G) }]
  for fname, action, reason in scenarios:
    path = os.path.join(G, fname)
    tree_conflict_re = ".*local file %s, incoming file %s.*" % (reason, action)
    expected_infos.append({ 'Path' : re.escape(path),
                            'Tree conflict' : tree_conflict_re })
  expected_infos.sort(key=lambda info: info['Path'])
  svntest.actions.run_and_verify_info(expected_infos, G, '-R')

def info_on_added_file(sbox):
  """info on added file"""

  sbox.build()
  wc_dir = sbox.wc_dir

  # create new file
  new_file = os.path.join(wc_dir, 'new_file')
  svntest.main.file_append(new_file, '')

  svntest.main.run_svn(None, 'add', new_file)

  uuid_regex = '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}'

  # check that we have a Repository Root and Repository UUID
  expected = {'Path' : re.escape(new_file),
              'Name' : 'new_file',
              'URL' : '.*/new_file',
              'Relative URL' : '.*/new_file',
              'Repository Root' : '.*',
              'Node Kind' : 'file',
              'Schedule' : 'add',
              'Repository UUID' : uuid_regex,
             }

  svntest.actions.run_and_verify_info([expected], new_file)

  # check XML info
  exit_code, output, error = svntest.actions.run_and_verify_svn(None,
                                                                [], 'info',
                                                                new_file,
                                                                '--xml')

  verify_xml_elements(output,
                      [('entry',    {'kind'     : 'file',
                                     'path'     : new_file,
                                     'revision' : 'Resource is not under version control.'}),
                       ('url',      {}, '.*/new_file'),
                       ('relative-url', {}, '.*/new_file'),
                       ('root',     {}, '.*'),
                       ('uuid',     {}, uuid_regex),
                       ('depth',    {}, 'infinity'),
                       ('schedule', {}, 'add')])

def info_on_mkdir(sbox):
  """info on new dir with mkdir"""
  sbox.build()
  wc_dir = sbox.wc_dir

  # create a new directory using svn mkdir
  new_dir = os.path.join(wc_dir, 'new_dir')
  svntest.main.run_svn(None, 'mkdir', new_dir)

  uuid_regex = '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}'

  # check that we have a Repository Root and Repository UUID
  expected = {'Path' : re.escape(new_dir),
              'URL' : '.*/new_dir',
              'Relative URL' : '.*/new_dir',
              'Repository Root' : '.*',
              'Node Kind' : 'directory',
              'Schedule' : 'add',
              'Repository UUID' : uuid_regex,
             }

  svntest.actions.run_and_verify_info([expected], new_dir)

  # check XML info
  exit_code, output, error = svntest.actions.run_and_verify_svn(None,
                                                                [], 'info',
                                                                new_dir,
                                                                '--xml')
  verify_xml_elements(output,
                      [('entry',    {'kind'     : 'dir',
                                     'path'     : new_dir,
                                     'revision' : 'Resource is not under version control.'}),
                       ('url',      {}, '.*/new_dir'),
                       ('relative-url', {}, '.*/new_dir'),
                       ('root',     {}, '.*'),
                       ('uuid',     {}, uuid_regex),
                       ('depth',    {}, 'infinity'),
                       ('schedule', {}, 'add')])

def info_wcroot_abspaths(sbox):
  """wc root paths in 'svn info' output"""

  def check_wcroot_paths(lines, wcroot_abspath):
    "check that paths found on input lines beginning 'Path: ' are as expected"
    path = None
    target = None
    for line in lines:
      if line.startswith('Path: '):
        target = line[6:].rstrip()
      if line.startswith('Working Copy Root Path: '):
        path = line[24:].rstrip()
      if target is not None and path is not None:
        break

    if target is None:
      target = "(UNKNOWN)"

    if path is None:
      logger.warn("No WC root path for '%s'", target)
      raise svntest.Failure

    if path != wcroot_abspath:
      logger.warn("For target '%s'...", target)
      logger.warn("   Reported WC root path: %s", path)
      logger.warn("   Expected WC root path: %s", wcroot_abspath)
      raise svntest.Failure

  sbox.build(read_only=True)
  exit_code, output, errput = svntest.main.run_svn(None, 'info', '-R', sbox.wc_dir)
  check_wcroot_paths(output, os.path.abspath(sbox.wc_dir))

def info_url_special_characters(sbox):
  """special characters in svn info URL"""
  sbox.build(create_wc = False)
  wc_dir = sbox.wc_dir

  special_urls = [sbox.repo_url + '/A' + '/%2E',
                  sbox.repo_url + '%2F' + 'A']

  expected = {'Path' : 'A',
              'Repository Root' : re.escape(sbox.repo_url),
              'Revision' : '1',
              'Node Kind' : 'dir',
             }

  for url in special_urls:
    svntest.actions.run_and_verify_info([expected], url)

def info_multiple_targets(sbox):
  "info multiple targets"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  def multiple_wc_targets():
    "multiple wc targets"

    alpha = sbox.ospath('A/B/E/alpha')
    beta = sbox.ospath('A/B/E/beta')
    non_existent_path = os.path.join(wc_dir, 'non-existent')

    # All targets are existing
    svntest.actions.run_and_verify_svn2(None, [],
                                        0, 'info', alpha, beta)

    # One non-existing target
    expected_err = ".*W155010.*\n\n.*E200009.*"
    expected_err_re = re.compile(expected_err, re.DOTALL)

    exit_code, output, error = svntest.main.run_svn(1, 'info', alpha,
                                                    non_existent_path, beta)

    # Verify error
    if not expected_err_re.match("".join(error)):
      raise svntest.Failure('info failed: expected error "%s", but received '
                            '"%s"' % (expected_err, "".join(error)))

  def multiple_url_targets():
    "multiple url targets"

    alpha = sbox.repo_url +  '/A/B/E/alpha'
    beta = sbox.repo_url +  '/A/B/E/beta'
    non_existent_url = sbox.repo_url +  '/non-existent'

    # All targets are existing
    svntest.actions.run_and_verify_svn2(None, [],
                                        0, 'info', alpha, beta)

    # One non-existing target
    expected_err = ".*W170000.*\n\n.*E200009.*"
    expected_err_re = re.compile(expected_err, re.DOTALL)

    exit_code, output, error = svntest.main.run_svn(1, 'info', alpha,
                                                    non_existent_url, beta)

    # Verify error
    if not expected_err_re.match("".join(error)):
      raise svntest.Failure('info failed: expected error "%s", but received '
                            '"%s"' % (expected_err, "".join(error)))
  # Test one by one
  multiple_wc_targets()
  multiple_url_targets()

def info_repos_root_url(sbox):
  """verify values for repository root"""
  sbox.build(create_wc = False)
  wc_dir = sbox.wc_dir

  expected_info = [
    {
        'Path'              : re.escape(os.path.basename(sbox.repo_dir)),
        'Repository Root'   : re.escape(sbox.repo_url),
        'URL'               : re.escape(sbox.repo_url),
        'Relative URL'      : r'\^/', # escape ^ -- this isn't a regexp
        'Revision'          : '1',
        'Node Kind'         : 'directory',
        'Last Changed Rev'  : '1',
    },
    {
        'Path'              : 'iota',
        'Name'              : 'iota',
        'Repository Root'   : re.escape(sbox.repo_url),
        'URL'               : re.escape(sbox.repo_url + '/iota'),
        'Relative URL'      : r'\^/iota', # escape ^ -- this isn't a regexp
        'Revision'          : '1',
        'Node Kind'         : 'file',
        'Last Changed Rev'  : '1',
    }
  ]

  svntest.actions.run_and_verify_info(expected_info, sbox.repo_url,
                                      '--depth', 'files')

@Issue(3787)
def info_show_exclude(sbox):
  "tests 'info --depth' variants on excluded node"

  sbox.build()
  wc_dir = sbox.wc_dir

  A_path = os.path.join(wc_dir, 'A')
  iota = os.path.join(wc_dir, 'iota')
  svntest.main.run_svn(None, 'up', '--set-depth', 'exclude', A_path)
  wc_uuid = svntest.actions.get_wc_uuid(wc_dir)

  expected_info = [{
      'Path' : re.escape(wc_dir),
      'Repository Root' : sbox.repo_url,
      'Repository UUID' : wc_uuid,
  }]

  svntest.actions.run_and_verify_info(expected_info, '--depth', 'empty',
                                      wc_dir)

  expected_info = [{
      'Path' : '.*%sA' % re.escape(os.sep),
      'Repository Root' : sbox.repo_url,
      'Repository UUID' : wc_uuid,
      'Depth' : 'exclude',
  }]

  svntest.actions.run_and_verify_info(expected_info, '--depth',
                                      'empty', A_path)
  svntest.actions.run_and_verify_info(expected_info, '--depth',
                                      'infinity', A_path)
  svntest.actions.run_and_verify_info(expected_info, '--depth',
                                      'immediates', A_path)

  expected_info = [{
      'Path' : '.*%siota' % re.escape(os.sep),
      'Repository Root' : sbox.repo_url,
      'Repository UUID' : wc_uuid,
  }]
  svntest.main.run_svn(None, 'up', '--set-depth', 'exclude', iota)
  svntest.actions.run_and_verify_info(expected_info, iota)

  # And now get iota back, to allow testing other states
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='A '),
    })

  expected_status = svntest.wc.State(iota, {
    '' : Item(status='  ', wc_rev='1')
  })

  svntest.actions.run_and_verify_update(iota,
                                        expected_output, None, expected_status)

  sbox.simple_rm('iota')
  sbox.simple_commit()

  expected_error = 'svn: E200009: Could not display info for all targets.*'

  # Expect error on iota (status = not-present)
  svntest.actions.run_and_verify_svn([], expected_error, 'info', iota)

  sbox.simple_update()

  # Expect error on iota (unversioned)
  svntest.actions.run_and_verify_svn([], expected_error, 'info', iota)

@Issue(3998)
def binary_tree_conflict(sbox):
  "svn info shouldn't crash on conflict"
  sbox.build()
  wc_dir = sbox.wc_dir

  svntest.main.run_svn(binary_mime_type_on_text_file_warning,
                       'propset', 'svn:mime-type', 'binary/octet-stream',
                       sbox.ospath('iota'))
  sbox.simple_commit()

  iota = sbox.ospath('iota')

  svntest.main.file_write(iota, 'something-else')
  sbox.simple_commit()

  svntest.main.file_write(iota, 'third')

  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='C '),
    })
  expected_status = svntest.wc.State(iota, {
    '' : Item(status='C ', wc_rev='2')
  })
  svntest.actions.run_and_verify_update(iota,
                                        expected_output, None, expected_status,
                                        [], False,
                                        iota, '-r', '2')

  expected_info = [{
      'Path' : '%s' % re.escape(iota),
      'Conflict Previous Base File' : re.escape(iota + '.r3'),
      'Conflict Current Base File' : re.escape(iota + '.r2'),
  }]
  svntest.actions.run_and_verify_info(expected_info, iota)

  expected_info = [{
      'Path' : '%s' % re.escape(wc_dir),
  }]
  svntest.actions.run_and_verify_info(expected_info, wc_dir)

def relpath_escaping(sbox):
  "relpath escaping should be usable as-is"

  sbox.build()
  wc_dir = sbox.wc_dir

  name = 'path with space, +, % and #'
  name2 = 'path with %20'
  sbox.simple_copy('iota', name)
  sbox.simple_copy('iota', name2)
  sbox.simple_commit()

  testpath = sbox.ospath(name)

  expected = {'Path' : re.escape(testpath),
              'URL' : '.*/path.*with.*space.*',
              'Relative URL' : '.*/path.*with.*space.*',
             }

  svntest.actions.run_and_verify_info([expected], sbox.ospath(name))

  info = svntest.actions.run_and_parse_info(sbox.ospath(name), sbox.ospath(name2))

  # And now verify that the returned URL and relative url are usable

  # Also test the local path (to help resolving the relative path) and an
  # unescaped path which the client should automatically encode
  svntest.actions.run_and_verify_svn(None, [], 'info',
                                     info[0]['Relative URL'],
                                     info[0]['URL'],
                                     testpath,
                                     '^/' + name,

                                     info[1]['Relative URL'],
                                     info[1]['URL'])

  # And now do the same thing with a the file external handling
  sbox.simple_propset('svn:externals',
                        info[0]['Relative URL'] + " f1\n" +
                        info[0]['URL'] + " f2\n" +
                        '"^/' + name + "\" f3\n" +

                        info[1]['Relative URL'] + " g1\n" +
                        info[1]['URL'] + " g2\n",
                      ''
                     )

  # And now we expect to see 3 file externals
  expected_output = svntest.wc.State(wc_dir, {
    'f1'                : Item(status='A '),
    'f2'                : Item(status='A '),
    'f3'                : Item(status='A '),

    'g1'                : Item(status='A '),
    'g2'                : Item(status='A '),
  })

  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output, None, None)

def node_hidden_info(sbox):
  "fetch svn info on 'hidden' nodes"

  sbox.build()

  sbox.simple_rm('A/B/E/alpha')
  sbox.simple_commit()
  svntest.actions.run_and_verify_svn(None, [],
                                     'up', '--set-depth', 'exclude',
                                     sbox.ospath('A/B/E/beta'))

  sbox.simple_copy('A/B/E', 'E')

  # Running info on BASE not-present fails
  expected_err = '.*(E|W)155010: The node \'.*alpha\' was not found.*'
  svntest.actions.run_and_verify_svn(None, expected_err,
                                     'info', sbox.ospath('A/B/E/alpha'))

  expected_info = [
    {
        'Path': re.escape(sbox.ospath('A/B/E/beta')),
        'Schedule': 'normal',
        'Depth': 'exclude',
        'Node Kind': 'file',
    },
    {
        'Path': re.escape(sbox.ospath('E/alpha')),
        'Schedule': 'delete',
        'Depth': 'exclude',
        'Node Kind': 'unknown',
    },
    {
        'Path': re.escape(sbox.ospath('E/beta')),
        'Schedule': 'normal',
        'Depth': 'exclude',
        'Node Kind': 'file',
    }
  ]

  svntest.actions.run_and_verify_info(expected_info,
                                      sbox.ospath('A/B/E/beta'),
                                      sbox.ospath('E/alpha'),
                                      sbox.ospath('E/beta'))


def info_item_simple(sbox):
  "show one info item"

  sbox.build(read_only=True)
  svntest.actions.run_and_verify_svn(
    ['1'], [],
    'info', '--show-item=revision', '--no-newline',
    sbox.ospath(''))


def info_item_simple_multiple(sbox):
  "show one info item with multiple targets"

  sbox.build(read_only=True)

  svntest.actions.run_and_verify_svn(
    r'^jrandom\s+\S+(/|\\)info_tests-\d+((/|\\)[^/\\]+)?$', [],
    'info', '--show-item=last-changed-author',
    '--depth=immediates', sbox.ospath(''))

  svntest.actions.run_and_verify_svn(
    r'^1\s+\S+(/|\\)info_tests-\d+(/|\\)[^/\\]+$', [],
    'info', '--show-item=last-changed-revision',
    sbox.ospath('A'), sbox.ospath('iota'))


def info_item_url(sbox):
  "show one info item with URL targets"

  sbox.build(create_wc=False, read_only=True)

  svntest.actions.run_and_verify_svn(
    '1', [],
    'info', '--show-item=last-changed-revision',
    sbox.repo_url)


  svntest.actions.run_and_verify_svn(
    r'^1\s+[^/:]+://.+/repos/[^/]+$', [],
    'info', '--show-item=last-changed-revision',
    sbox.repo_url + '/A', sbox.repo_url + '/iota')


  # Empty working copy root on URL targets
  svntest.actions.run_and_verify_svn(
    '', [],
    'info', '--show-item=wc-root',
    sbox.repo_url)


def info_item_uncommmitted(sbox):
  "show one info item on uncommitted targets"

  sbox.build()

  svntest.main.file_write(sbox.ospath('newfile'), 'newfile')
  sbox.simple_add('newfile')
  sbox.simple_mkdir('newdir')

  svntest.actions.run_and_verify_svn(
    '', [],
    'info', '--show-item=last-changed-revision',
    sbox.ospath('newfile'))

  svntest.actions.run_and_verify_svn(
    '', [],
    'info', '--show-item=last-changed-author',
    sbox.ospath('newdir'))

  svntest.actions.run_and_verify_svn(
    r'\s+\S+(/|\\)new(file|dir)', [],
    'info', '--show-item=last-changed-date',
    sbox.ospath('newfile'), sbox.ospath('newdir'))

  svntest.actions.run_and_verify_svn(
    r'\^/new(file|dir)\s+\S+(/|\\)new(file|dir)', [],
    'info', '--show-item=relative-url',
    sbox.ospath('newfile'), sbox.ospath('newdir'))


def info_item_size_wc_recursive(sbox):
  "recursive '--show-item=repos-size' on local path"

  sbox.build(read_only=True)

  svntest.actions.run_and_verify_svn(
    [], [],
    'info', '--show-item=repos-size', '--recursive',
    sbox.ospath(''))


def info_item_size_repos(sbox):
  "non-recursive '--show-item=repos-size' on URL"

  sbox.build(read_only=True)

  svntest.actions.run_and_verify_svn(
    "25\n", [],
    'info', '--show-item=repos-size',
    sbox.repo_url + "/iota")

  # Same, but without the newline.
  svntest.actions.run_and_verify_svn(
    "25", [],
    'info', '--show-item=repos-size', '--no-newline',
    sbox.repo_url + "/iota")

  # Same, but with "human-readable" output.
  svntest.actions.run_and_verify_svn(
    "25 B", [],
    'info', '--show-item=repos-size', '--human-readable',
    sbox.repo_url + "/iota")

  # No output when the URL is a directory.
  svntest.actions.run_and_verify_svn(
    [], [],
    'info', '--show-item=repos-size',
    sbox.repo_url)


def info_item_size_repos_recursive(sbox):
  "recursive '--show-item=repos-size' on dir URL"

  sbox.build(read_only=True)

  expected_output = svntest.verify.UnorderedOutput([
    "25         " + sbox.repo_url + "/iota\n",
    "27         " + sbox.repo_url + "/A/B/lambda\n",
    "25         " + sbox.repo_url + "/A/B/E/beta\n",
    "26         " + sbox.repo_url + "/A/B/E/alpha\n",
    "23         " + sbox.repo_url + "/A/mu\n",
    "26         " + sbox.repo_url + "/A/D/gamma\n",
    "23         " + sbox.repo_url + "/A/D/G/pi\n",
    "24         " + sbox.repo_url + "/A/D/G/rho\n",
    "24         " + sbox.repo_url + "/A/D/G/tau\n",
    "26         " + sbox.repo_url + "/A/D/H/omega\n",
    "24         " + sbox.repo_url + "/A/D/H/psi\n",
    "24         " + sbox.repo_url + "/A/D/H/chi\n",
  ])

  svntest.actions.run_and_verify_svn(
    expected_output, [],
    'info', '--show-item=repos-size', '--recursive',
    sbox.repo_url)


def info_item_failures(sbox):
  "failure modes of 'svn info --show-item'"

  sbox.build(read_only=True)

  svntest.actions.run_and_verify_svn(
    None, r'.*E200009:.*',
    'info', '--show-item=revision',
    sbox.ospath('not-there'))

  svntest.actions.run_and_verify_svn(
    None, r".*E205000: .*; did you mean 'wc-root'\?",
    'info', '--show-item=root',
    sbox.ospath(''))

  svntest.actions.run_and_verify_svn(
    None, (r".*E205000: --show-item is not valid in --xml mode"),
    'info', '--show-item=revision', '--xml',
    sbox.ospath(''))

  svntest.actions.run_and_verify_svn(
    None, (r".*E205000: --incremental is only valid in --xml mode"),
    'info', '--show-item=revision', '--incremental',
    sbox.ospath(''))

  svntest.actions.run_and_verify_svn(
    None, (r".*E205000: --no-newline is only available.*"),
    'info', '--show-item=revision', '--no-newline',
    sbox.ospath('A'), sbox.ospath('iota'))

  svntest.actions.run_and_verify_svn(
    None, (r".*E200007: can't show in-repository size.*"),
    'info', '--show-item=repos-size',
    sbox.ospath('iota'))


@Issue(4837)
def info_file_in_file_replaced_dir(sbox):
  "info, file in file-replaced dir"

  sbox.build(empty=True)
  sbox.simple_mkdir('dir')
  sbox.simple_add_text('text\n', 'dir/file')
  sbox.simple_commit(message='Add file')

  sbox.simple_copy('dir/file', 'file-moved')
  sbox.simple_rm('dir')
  sbox.simple_add_text('replaced\n', 'dir')
  sbox.simple_commit(message='Replace dir with file')

  sbox.simple_update()

  expected = {'Relative URL' : r'\^/dir/file',
              'Node Kind' : 'file',
              'Revision': '1',
              'Last Changed Rev': '1',
             }

  svntest.actions.run_and_verify_info([expected],
                                      sbox.repo_url + '/dir/file@1')

@Issue(4869)
def info_tree_conflict_source(sbox):
  "info --xml: verify source-left, source-right"

  sbox.build()
  wc_dir = sbox.wc_dir

  B_path = os.path.join(wc_dir, 'A', 'B')
  lambda_path = os.path.join(B_path, 'lambda')
  alpha_path = os.path.join(B_path, 'E', 'alpha')
  F_path = os.path.join(B_path, 'F')

  B2_url = sbox.repo_url + '/A/B2'
  B2_path = os.path.join(wc_dir, 'A', 'B2')
  lambda2_path = os.path.join(B2_path, 'lambda')

  # Rev 2 copy B to B2
  sbox.simple_repo_copy('A/B', 'A/B2')
  sbox.simple_update()

  # Rev 3:
  # edit A/B/lambda to test text conflict case
  # add property to A/B/E/alpha to test property conflict case
  # rename A/B/F to A/B/Z to test tree conflict case

  svntest.main.file_write(lambda_path, 'B/lambda side of conflict')
  sbox.simple_propset('blue', 'azul', 'A/B/E/alpha')
  sbox.simple_move('A/B/F', 'A/B/Z');
  sbox.simple_commit()

  # Rev 4:
  # edit A/B2/lambda
  # add property to A/B2/E/alpha
  # rename A/B2/F to A/B2/Y

  svntest.main.file_write(lambda2_path, 'B2/lambda side of conflict')
  sbox.simple_propset('blue', 'bleue', 'A/B2/E/alpha')
  sbox.simple_move('A/B2/F', 'A/B2/Y');
  sbox.simple_commit()

  # Now merge B2 into B to cause a text conflict, property conflict, and
  # tree conflict
  sbox.simple_update()

  svntest.actions.run_and_verify_svn2(svntest.verify.AnyOutput, [],
                                      0, 'merge', B2_url, B_path)

  # Verify 'svn info --xml' on the text conflicted case

  exit_code, output, error = svntest.actions.run_and_verify_svn(None,
                                                                [], 'info',
                                                                lambda_path,
                                                                '--xml')

  verify_xml_elements(output,
                      [('version', {'revision'     : '1',
                                    'side'         : 'source-left',
                                    'kind'         : 'file',
                                    'path-in-repos': 'A/B/lambda',
                                    'repos-url'    : sbox.repo_url,
                                   },
                       )])

  verify_xml_elements(output,
                      [('version', {'revision'     : '4',
                                    'side'         : 'source-right',
                                    'kind'         : 'file',
                                    'path-in-repos': 'A/B2/lambda',
                                    'repos-url'    : sbox.repo_url,
                                   },
                       )])

  # Verify 'svn info --xml' on the property conflicted case

  exit_code, output, error = svntest.actions.run_and_verify_svn(None,
                                                                [], 'info',
                                                                alpha_path,
                                                                '--xml')

  verify_xml_elements(output,
                      [('version', {'revision'     : '1',
                                    'side'         : 'source-left',
                                    'kind'         : 'file',
                                    'path-in-repos': 'A/B/E/alpha',
                                    'repos-url'    : sbox.repo_url,
                                   },
                       )])

  verify_xml_elements(output,
                      [('version', {'revision'     : '4',
                                    'side'         : 'source-right',
                                    'kind'         : 'file',
                                    'path-in-repos': 'A/B2/E/alpha',
                                    'repos-url'    : sbox.repo_url,
                                   },
                       )])

  # Verify 'svn info --xml' on the tree conflicted case

  exit_code, output, error = svntest.actions.run_and_verify_svn(None,
                                                                [], 'info',
                                                                F_path,
                                                                '--xml')

  verify_xml_elements(output,
                      [('version', {'revision'     : '1',
                                    'side'         : 'source-left',
                                    'kind'         : 'dir',
                                    'path-in-repos': 'A/B/F',
                                    'repos-url'    : sbox.repo_url,
                                   },
                       )])

  verify_xml_elements(output,
                      [('version', {'revision'     : '4',
                                    'side'         : 'source-right',
                                    'kind'         : 'none',
                                    'path-in-repos': 'A/B2/F',
                                    'repos-url'    : sbox.repo_url,
                                   },
                       )])

########################################################################
# Run the tests

# list all tests here, starting with None:
test_list = [ None,
              info_with_tree_conflicts,
              info_on_added_file,
              info_on_mkdir,
              info_wcroot_abspaths,
              info_url_special_characters,
              info_multiple_targets,
              info_repos_root_url,
              info_show_exclude,
              binary_tree_conflict,
              relpath_escaping,
              node_hidden_info,
              info_item_simple,
              info_item_simple_multiple,
              info_item_url,
              info_item_uncommmitted,
              info_item_size_wc_recursive,
              info_item_size_repos,
              info_item_size_repos_recursive,
              info_item_failures,
              info_file_in_file_replaced_dir,
              info_tree_conflict_source,
             ]

if __name__ == '__main__':
  svntest.main.run_tests(test_list)
  # NOTREACHED


### End of file.
