changelist_tests.py   [plain text]


#!/usr/bin/env python
#
#  changelist_tests.py:  testing changelist uses.
#
#  Subversion is a tool for revision control.
#  See http://subversion.tigris.org for more information.
#
# ====================================================================
# Copyright (c) 2008-2009 CollabNet.  All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.  The terms
# are also available at http://subversion.tigris.org/license-1.html.
# If newer versions of this license are posted there, you may use a
# newer version instead, at your option.
#
######################################################################

# General modules
import string, sys, os, re

# Our testing module
import svntest

# (abbreviation)
Skip = svntest.testcase.Skip
SkipUnless = svntest.testcase.SkipUnless
XFail = svntest.testcase.XFail
Item = svntest.wc.StateItem


######################################################################
# Utilities


def mod_all_files(wc_dir, new_text):
  """Walk over working copy WC_DIR, appending NEW_TEXT to all the
  files in that tree (but not inside the .svn areas of that tree)."""

  def tweak_files(new_text, dirname, names):
    if os.path.basename(dirname) == ".svn":
      del names[:]
    else:
      for name in names:
        full_path = os.path.join(dirname, name)
        if os.path.isfile(full_path):
          svntest.main.file_append(full_path, new_text)

  os.path.walk(wc_dir, tweak_files, new_text)

def changelist_all_files(wc_dir, name_func):
  """Walk over working copy WC_DIR, adding versioned files to
  changelists named by invoking NAME_FUNC(full-path-of-file) and
  noting its string return value (or None, if we wish to remove the
  file from a changelist)."""

  def do_changelist(name_func, dirname, names):
    if os.path.basename(dirname) == ".svn":
      del names[:]
    else:
      for name in names:
        full_path = os.path.join(dirname, name)
        if os.path.isfile(full_path):
          clname = name_func(full_path)
          if not clname:
            svntest.main.run_svn(None, "changelist", "--remove", full_path)
          else:
            svntest.main.run_svn(None, "changelist", clname, full_path)

  os.path.walk(wc_dir, do_changelist, name_func)

def clname_from_lastchar_cb(full_path):
  """Callback for changelist_all_files() that returns a changelist
  name matching the last character in the file's name.  For example,
  after running this on a greek tree where every file has some text
  modification, 'svn status' shows:

    --- Changelist 'a':
    M      A/B/lambda
    M      A/B/E/alpha
    M      A/B/E/beta
    M      A/D/gamma
    M      A/D/H/omega
    M      iota

    --- Changelist 'u':
    M      A/mu
    M      A/D/G/tau

    --- Changelist 'i':
    M      A/D/G/pi
    M      A/D/H/chi
    M      A/D/H/psi

    --- Changelist 'o':
    M      A/D/G/rho
    """
  return full_path[-1]


# Regular expressions for 'svn changelist' output.
_re_cl_skip = re.compile("Skipped '(.*)'")
_re_cl_add  = re.compile("Path '(.*)' is now a member of changelist '(.*)'.")
_re_cl_rem  = re.compile("Path '(.*)' is no longer a member of a changelist.")

def verify_changelist_output(output, expected_adds=None,
                             expected_removals=None,
                             expected_skips=None):
  """Compare lines of OUTPUT from 'svn changelist' against
  EXPECTED_ADDS (a dictionary mapping paths to changelist names),
  EXPECTED_REMOVALS (a dictionary mapping paths to ... whatever), and
  EXPECTED_SKIPS (a dictionary mapping paths to ... whatever).

  EXPECTED_SKIPS is ignored if None."""

  num_expected = 0
  if expected_adds:
    num_expected += len(expected_adds)
  if expected_removals:
    num_expected += len(expected_removals)
  if expected_skips:
    num_expected += len(expected_skips)

  if not expected_skips:
    output = [line for line in output if (not _re_cl_skip.match(line))]

  if len(output) != num_expected:
    raise svntest.Failure("Unexpected number of 'svn changelist' output lines")

  for line in output:
    line = line.rstrip()
    match = _re_cl_rem.match(line)
    if match \
       and expected_removals \
       and match.group(1) in expected_removals:
        continue
    elif match:
      raise svntest.Failure("Unexpected changelist removal line: " + line)
    match = _re_cl_add.match(line)
    if match \
       and expected_adds \
       and expected_adds.get(match.group(1)) == match.group(2):
        continue
    elif match:
      raise svntest.Failure("Unexpected changelist add line: " + line)
    match = _re_cl_skip.match(line)
    if match \
       and expected_skips \
       and match.group(1) in expected_skips:
        continue
    elif match:
      raise svntest.Failure("Unexpected changelist skip line: " + line)
    raise svntest.Failure("Unexpected line: " + line)

def verify_pget_output(output, expected_props):
  """Compare lines of OUTPUT from 'svn propget' against EXPECTED_PROPS
  (a dictionary mapping paths to property values)."""

  _re_pget = re.compile('^(.*) - (.*)$')
  actual_props = {}
  for line in output:
    try:
      path, prop = line.rstrip().split(' - ')
    except:
      raise svntest.Failure("Unexpected output line: " + line)
    actual_props[path] = prop
  if expected_props != actual_props:
    raise svntest.Failure("Got unexpected property results")


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


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

def add_remove_changelists(sbox):
  "add and remove files from changelists"

  sbox.build()
  wc_dir = sbox.wc_dir

  ### 'Skip' notifications

  def expected_skips_under(*greek_path):
    "return a dict mapping Greek-tree directories below GREEK_PATH to None"

    expected_skips = {}
    for path in expected_skips_all:
      if path.startswith(os.path.join(wc_dir, *greek_path)):
        expected_skips[path] = None

    return expected_skips

  def all_parents(expected_adds):
    """return a dict mapping Greek-tree directories above directories in
       EXPECTED_ADDS to None"""

    expected_skips = {}
    for path in expected_adds.keys():
      if not os.path.isdir(path):
        path = os.path.dirname(path)

      while path != wc_dir:
        expected_skips[path] = None
        path = os.path.dirname(path)

    expected_skips[wc_dir] = None
    return expected_skips

  # all dirs in the Greek tree
  expected_skips_all = dict([(x, None) for x in [
        os.path.join(wc_dir),
    os.path.join(wc_dir, 'A'),
    os.path.join(wc_dir, 'A', 'B'),
    os.path.join(wc_dir, 'A', 'B', 'E'),
    os.path.join(wc_dir, 'A', 'B', 'F'),
    os.path.join(wc_dir, 'A', 'C'),
    os.path.join(wc_dir, 'A', 'D'),
    os.path.join(wc_dir, 'A', 'D', 'G'),
    os.path.join(wc_dir, 'A', 'D', 'H'),
    ]])

  expected_skips_wc_dir = { wc_dir : None }

  ### First, we play with just adding to changelists ###

  # svn changelist foo WC_DIR
  exit_code, output, errput = svntest.main.run_svn(None, "changelist", "foo",
                                                   wc_dir)
  verify_changelist_output(output) # nothing expected

  # svn changelist foo WC_DIR --depth files
  exit_code, output, errput = svntest.main.run_svn(None, "changelist", "foo",
                                                   "--depth", "files",
                                                   wc_dir)
  expected_adds = {
    os.path.join(wc_dir, 'iota') : 'foo',
    }
  verify_changelist_output(output, expected_adds)

  # svn changelist foo WC_DIR --depth infinity
  exit_code, output, errput = svntest.main.run_svn(None, "changelist", "foo",
                                                   "--depth", "infinity",
                                                   wc_dir)
  expected_adds = {
    os.path.join(wc_dir, 'A', 'B', 'E', 'alpha') : 'foo',
    os.path.join(wc_dir, 'A', 'B', 'E', 'beta') : 'foo',
    os.path.join(wc_dir, 'A', 'B', 'lambda') : 'foo',
    os.path.join(wc_dir, 'A', 'D', 'G', 'pi') : 'foo',
    os.path.join(wc_dir, 'A', 'D', 'G', 'rho') : 'foo',
    os.path.join(wc_dir, 'A', 'D', 'G', 'tau') : 'foo',
    os.path.join(wc_dir, 'A', 'D', 'H', 'chi') : 'foo',
    os.path.join(wc_dir, 'A', 'D', 'H', 'omega') : 'foo',
    os.path.join(wc_dir, 'A', 'D', 'H', 'psi') : 'foo',
    os.path.join(wc_dir, 'A', 'D', 'gamma') : 'foo',
    os.path.join(wc_dir, 'A', 'mu') : 'foo',
    }
  verify_changelist_output(output, expected_adds)

  ### Now, change some changelists ###

  # svn changelist bar WC_DIR/A/D --depth infinity
  exit_code, output, errput = svntest.main.run_svn(".*", "changelist", "bar",
                                                   "--depth", "infinity",
                                                   os.path.join(wc_dir,
                                                                'A', 'D'))
  expected_adds = {
    os.path.join(wc_dir, 'A', 'D', 'G', 'pi') : 'bar',
    os.path.join(wc_dir, 'A', 'D', 'G', 'rho') : 'bar',
    os.path.join(wc_dir, 'A', 'D', 'G', 'tau') : 'bar',
    os.path.join(wc_dir, 'A', 'D', 'H', 'chi') : 'bar',
    os.path.join(wc_dir, 'A', 'D', 'H', 'omega') : 'bar',
    os.path.join(wc_dir, 'A', 'D', 'H', 'psi') : 'bar',
    os.path.join(wc_dir, 'A', 'D', 'gamma') : 'bar',
    }
  verify_changelist_output(output, expected_adds)

  # svn changelist baz WC_DIR/A/D/H --depth infinity
  exit_code, output, errput = svntest.main.run_svn(".*", "changelist", "baz",
                                                   "--depth", "infinity",
                                                   os.path.join(wc_dir, 'A',
                                                                'D', 'H'))
  expected_adds = {
    os.path.join(wc_dir, 'A', 'D', 'H', 'chi') : 'baz',
    os.path.join(wc_dir, 'A', 'D', 'H', 'omega') : 'baz',
    os.path.join(wc_dir, 'A', 'D', 'H', 'psi') : 'baz',
    }
  verify_changelist_output(output, expected_adds)

  ### Now, let's selectively rename some changelists ###

  # svn changelist foo-rename WC_DIR --depth infinity --changelist foo
  exit_code, output, errput = svntest.main.run_svn(".*", "changelist",
                                                   "foo-rename",
                                                   "--depth", "infinity",
                                                   "--changelist", "foo",
                                                   wc_dir)
  expected_adds = {
    os.path.join(wc_dir, 'A', 'B', 'E', 'alpha') : 'foo-rename',
    os.path.join(wc_dir, 'A', 'B', 'E', 'beta') : 'foo-rename',
    os.path.join(wc_dir, 'A', 'B', 'lambda') : 'foo-rename',
    os.path.join(wc_dir, 'A', 'mu') : 'foo-rename',
    os.path.join(wc_dir, 'iota') : 'foo-rename',
    }
  verify_changelist_output(output, expected_adds)

  # svn changelist bar WC_DIR --depth infinity
  #     --changelist foo-rename --changelist baz
  exit_code, output, errput = svntest.main.run_svn(
    ".*", "changelist", "bar", "--depth", "infinity",
    "--changelist", "foo-rename", "--changelist", "baz", wc_dir)

  expected_adds = {
    os.path.join(wc_dir, 'A', 'B', 'E', 'alpha') : 'bar',
    os.path.join(wc_dir, 'A', 'B', 'E', 'beta') : 'bar',
    os.path.join(wc_dir, 'A', 'B', 'lambda') : 'bar',
    os.path.join(wc_dir, 'A', 'D', 'H', 'chi') : 'bar',
    os.path.join(wc_dir, 'A', 'D', 'H', 'omega') : 'bar',
    os.path.join(wc_dir, 'A', 'D', 'H', 'psi') : 'bar',
    os.path.join(wc_dir, 'A', 'mu') : 'bar',
    os.path.join(wc_dir, 'iota') : 'bar',
    }
  verify_changelist_output(output, expected_adds)

  ### Okay.  Time to remove some stuff from changelists now. ###

  # svn changelist --remove WC_DIR
  exit_code, output, errput = svntest.main.run_svn(None, "changelist",
                                                   "--remove", wc_dir)
  verify_changelist_output(output) # nothing expected

  # svn changelist --remove WC_DIR --depth files
  exit_code, output, errput = svntest.main.run_svn(None, "changelist",
                                                   "--remove",
                                                   "--depth", "files",
                                                   wc_dir)
  expected_removals = {
    os.path.join(wc_dir, 'iota') : None,
    }
  verify_changelist_output(output, None, expected_removals)

  # svn changelist --remove WC_DIR --depth infinity
  exit_code, output, errput = svntest.main.run_svn(None, "changelist",
                                                   "--remove",
                                                   "--depth", "infinity",
                                                   wc_dir)
  expected_removals = {
    os.path.join(wc_dir, 'A', 'B', 'E', 'alpha') : None,
    os.path.join(wc_dir, 'A', 'B', 'E', 'beta') : None,
    os.path.join(wc_dir, 'A', 'B', 'lambda') : None,
    os.path.join(wc_dir, 'A', 'D', 'G', 'pi') : None,
    os.path.join(wc_dir, 'A', 'D', 'G', 'rho') : None,
    os.path.join(wc_dir, 'A', 'D', 'G', 'tau') : None,
    os.path.join(wc_dir, 'A', 'D', 'H', 'chi') : None,
    os.path.join(wc_dir, 'A', 'D', 'H', 'omega') : None,
    os.path.join(wc_dir, 'A', 'D', 'H', 'psi') : None,
    os.path.join(wc_dir, 'A', 'D', 'gamma') : None,
    os.path.join(wc_dir, 'A', 'mu') : None,
    }
  verify_changelist_output(output, None, expected_removals)

  ### Add files to changelists based on the last character in their names ###

  changelist_all_files(wc_dir, clname_from_lastchar_cb)

  ### Now, do selective changelist removal ###

  # svn changelist --remove WC_DIR --depth infinity --changelist a
  exit_code, output, errput = svntest.main.run_svn(None, "changelist",
                                                   "--remove",
                                                   "--depth", "infinity",
                                                   "--changelist", "a",
                                                   wc_dir)
  expected_removals = {
    os.path.join(wc_dir, 'A', 'B', 'E', 'alpha') : None,
    os.path.join(wc_dir, 'A', 'B', 'E', 'beta') : None,
    os.path.join(wc_dir, 'A', 'B', 'lambda') : None,
    os.path.join(wc_dir, 'A', 'D', 'H', 'omega') : None,
    os.path.join(wc_dir, 'A', 'D', 'gamma') : None,
    os.path.join(wc_dir, 'iota') : None,
    }
  verify_changelist_output(output, None, expected_removals)

  # svn changelist --remove WC_DIR --depth infinity
  #     --changelist i --changelist o
  exit_code, output, errput = svntest.main.run_svn(None, "changelist",
                                                   "--remove",
                                                   "--depth", "infinity",
                                                   "--changelist", "i",
                                                   "--changelist", "o",
                                                   wc_dir)
  expected_removals = {
    os.path.join(wc_dir, 'A', 'D', 'G', 'pi') : None,
    os.path.join(wc_dir, 'A', 'D', 'G', 'rho') : None,
    os.path.join(wc_dir, 'A', 'D', 'H', 'chi') : None,
    os.path.join(wc_dir, 'A', 'D', 'H', 'psi') : None,
    }
  verify_changelist_output(output, None, expected_removals)

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

def commit_one_changelist(sbox):
  "commit with single --changelist"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Add a line of text to all the versioned files in the tree.
  mod_all_files(wc_dir, "New text.\n")

  # Add files to changelists based on the last character in their names.
  changelist_all_files(wc_dir, clname_from_lastchar_cb)

  # Now, test a commit that uses a single changelist filter (--changelist a).
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/lambda' : Item(verb='Sending'),
    'A/B/E/alpha' : Item(verb='Sending'),
    'A/B/E/beta' : Item(verb='Sending'),
    'A/D/gamma' : Item(verb='Sending'),
    'A/D/H/omega' : Item(verb='Sending'),
    'iota' : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', 'A/D/G/tau', 'A/D/G/pi', 'A/D/H/chi',
                        'A/D/H/psi', 'A/D/G/rho', wc_rev=1, status='M ')
  expected_status.tweak('iota', 'A/B/lambda', 'A/B/E/alpha', 'A/B/E/beta',
                        'A/D/gamma', 'A/D/H/omega', wc_rev=2, status='  ')
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        wc_dir,
                                        "--changelist",
                                        "a")

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

def commit_multiple_changelists(sbox):
  "commit with multiple --changelist's"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Add a line of text to all the versioned files in the tree.
  mod_all_files(wc_dir, "New text.\n")

  # Add files to changelists based on the last character in their names.
  changelist_all_files(wc_dir, clname_from_lastchar_cb)

  # Now, test a commit that uses multiple changelist filters
  # (--changelist=a --changelist=i).
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/lambda' : Item(verb='Sending'),
    'A/B/E/alpha' : Item(verb='Sending'),
    'A/B/E/beta' : Item(verb='Sending'),
    'A/D/gamma' : Item(verb='Sending'),
    'A/D/H/omega' : Item(verb='Sending'),
    'iota' : Item(verb='Sending'),
    'A/D/G/pi' : Item(verb='Sending'),
    'A/D/H/chi' : Item(verb='Sending'),
    'A/D/H/psi' : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', 'A/D/G/tau', 'A/D/G/rho',
                        wc_rev=1, status='M ')
  expected_status.tweak('iota', 'A/B/lambda', 'A/B/E/alpha', 'A/B/E/beta',
                        'A/D/gamma', 'A/D/H/omega', 'A/D/G/pi', 'A/D/H/chi',
                        'A/D/H/psi', wc_rev=2, status='  ')
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        wc_dir,
                                        "--changelist", "a",
                                        "--changelist", "i")

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

def info_with_changelists(sbox):
  "info --changelist"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Add files to changelists based on the last character in their names.
  changelist_all_files(wc_dir, clname_from_lastchar_cb)

  # Now, test various combinations of changelist specification and depths.
  for clname in [['a'], ['i'], ['a', 'i']]:
    for depth in [None, 'files', 'infinity']:

      # Figure out what we expect to see in our info output.
      expected_paths = []
      if 'a' in clname:
        if depth == 'infinity':
          expected_paths.append('A/B/lambda')
          expected_paths.append('A/B/E/alpha')
          expected_paths.append('A/B/E/beta')
          expected_paths.append('A/D/gamma')
          expected_paths.append('A/D/H/omega')
        if depth == 'files' or depth == 'infinity':
          expected_paths.append('iota')
      if 'i' in clname:
        if depth == 'infinity':
          expected_paths.append('A/D/G/pi')
          expected_paths.append('A/D/H/chi')
          expected_paths.append('A/D/H/psi')
      expected_paths = sorted([os.path.join(wc_dir, x.replace('/', os.sep)) for x in expected_paths])

      # Build the command line.
      args = ['info', wc_dir]
      for cl in clname:
        args.append('--changelist')
        args.append(cl)
      if depth:
        args.append('--depth')
        args.append(depth)

      # Run 'svn info ...'
      exit_code, output, errput = svntest.main.run_svn(None, *args)

      # Filter the output for lines that begin with 'Path:', and
      # reduce even those lines to just the actual path.
      def startswith_path(line):
        return line[:6] == 'Path: ' and 1 or 0
      paths = [x[6:].rstrip() for x in filter(startswith_path, output)]
      paths.sort()

      # And, compare!
      if (paths != expected_paths):
        raise svntest.Failure("Expected paths (%s) and actual paths (%s) "
                              "don't gel" % (str(expected_paths), str(paths)))

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

def diff_with_changelists(sbox):
  "diff --changelist (wc-wc and repos-wc)"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Add a line of text to all the versioned files in the tree.
  mod_all_files(wc_dir, "New text.\n")

  # Add files to changelists based on the last character in their names.
  changelist_all_files(wc_dir, clname_from_lastchar_cb)

  # Now, test various combinations of changelist specification and depths.
  for is_repos_wc in [0, 1]:
    for clname in [['a'], ['i'], ['a', 'i']]:
      for depth in ['files', 'infinity']:

        # Figure out what we expect to see in our diff output.
        expected_paths = []
        if 'a' in clname:
          if depth == 'infinity':
            expected_paths.append('A/B/lambda')
            expected_paths.append('A/B/E/alpha')
            expected_paths.append('A/B/E/beta')
            expected_paths.append('A/D/gamma')
            expected_paths.append('A/D/H/omega')
          if depth == 'files' or depth == 'infinity':
            expected_paths.append('iota')
        if 'i' in clname:
          if depth == 'infinity':
            expected_paths.append('A/D/G/pi')
            expected_paths.append('A/D/H/chi')
            expected_paths.append('A/D/H/psi')
        expected_paths = sorted([os.path.join(wc_dir, x.replace('/', os.sep)) for x in expected_paths])

        # Build the command line.
        args = ['diff']
        for cl in clname:
          args.append('--changelist')
          args.append(cl)
        if depth:
          args.append('--depth')
          args.append(depth)
        if is_repos_wc:
          args.append('--old')
          args.append(sbox.repo_url)
          args.append('--new')
          args.append(sbox.wc_dir)
        else:
          args.append(wc_dir)

        # Run 'svn diff ...'
        exit_code, output, errput = svntest.main.run_svn(None, *args)

        # Filter the output for lines that begin with 'Index:', and
        # reduce even those lines to just the actual path.
        def startswith_path(line):
          return line[:7] == 'Index: ' and 1 or 0
        paths = [x[7:].rstrip() for x in filter(startswith_path, output)]
        paths.sort()

        # Diff output on Win32 uses '/' path separators.
        if sys.platform == 'win32':
          paths = [x.replace('/', os.sep) for x in paths]

        # And, compare!
        if (paths != expected_paths):
          raise svntest.Failure("Expected paths (%s) and actual paths (%s) "
                                "don't gel"
                                % (str(expected_paths), str(paths)))

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

def propmods_with_changelists(sbox):
  "propset/del/get --changelist"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Add files to changelists based on the last character in their names.
  changelist_all_files(wc_dir, clname_from_lastchar_cb)

  # Set property 'name'='value' on all working copy items.
  svntest.main.run_svn(None, "pset", "--depth", "infinity",
                       "name", "value", wc_dir)
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({'' : Item(props={ 'name' : 'value' })})
  expected_disk.tweak('A', 'A/B', 'A/B/E', 'A/B/E/alpha', 'A/B/E/beta',
                      'A/B/F', 'A/B/lambda', 'A/C', 'A/D', 'A/D/G',
                      'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau', 'A/D/H',
                      'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi', 'A/D/gamma',
                      'A/mu', 'iota', props={ 'name' : 'value' })
  actual_disk_tree = svntest.tree.build_tree_from_wc(wc_dir, 1)
  svntest.tree.compare_trees("disk", actual_disk_tree,
                             expected_disk.old_tree())

  # Remove the 'name' property from files in the 'o' and 'i' changelists.
  svntest.main.run_svn(None, "pdel", "--depth", "infinity",
                       "name", "--changelist", "o", "--changelist", "i",
                       wc_dir)
  expected_disk.tweak('A/D/G/pi', 'A/D/G/rho', 'A/D/H/chi', 'A/D/H/psi',
                      props={})
  actual_disk_tree = svntest.tree.build_tree_from_wc(wc_dir, 1)
  svntest.tree.compare_trees("disk", actual_disk_tree,
                             expected_disk.old_tree())

  # Add 'foo'='bar' property on all files under A/B to depth files and
  # in changelist 'a'.
  svntest.main.run_svn(None, "pset", "--depth", "files",
                       "foo", "bar", "--changelist", "a",
                       os.path.join(wc_dir, 'A', 'B'))
  expected_disk.tweak('A/B/lambda', props={ 'name' : 'value',
                                            'foo'  : 'bar' })
  actual_disk_tree = svntest.tree.build_tree_from_wc(wc_dir, 1)
  svntest.tree.compare_trees("disk", actual_disk_tree,
                             expected_disk.old_tree())

  # Add 'bloo'='blarg' property to all files in changelist 'a'.
  svntest.main.run_svn(None, "pset", "--depth", "infinity",
                       "bloo", "blarg", "--changelist", "a",
                       wc_dir)
  expected_disk.tweak('A/B/lambda', props={ 'name' : 'value',
                                            'foo'  : 'bar',
                                            'bloo' : 'blarg' })
  expected_disk.tweak('A/B/E/alpha', 'A/B/E/beta', 'A/D/H/omega', 'A/D/gamma',
                      'iota', props={ 'name' : 'value',
                                      'bloo' : 'blarg' })
  actual_disk_tree = svntest.tree.build_tree_from_wc(wc_dir, 1)
  svntest.tree.compare_trees("disk", actual_disk_tree,
                             expected_disk.old_tree())

  # Propget 'name' in files in changelists 'a' and 'i' to depth files.
  exit_code, output, errput = svntest.main.run_svn(None, "pget",
                                                   "--depth", "files", "name",
                                                   "--changelist", "a",
                                                   "--changelist", "i",
                                                   wc_dir)
  verify_pget_output(output, {
    os.path.join(wc_dir, 'iota') : 'value',
    })

  # Propget 'name' in files in changelists 'a' and 'i' to depth infinity.
  exit_code, output, errput = svntest.main.run_svn(None, "pget",
                                                   "--depth", "infinity",
                                                   "name",
                                                   "--changelist", "a",
                                                   "--changelist", "i",
                                                   wc_dir)
  verify_pget_output(output, {
    os.path.join(wc_dir, 'A', 'D', 'gamma')      : 'value',
    os.path.join(wc_dir, 'A', 'B', 'E', 'alpha') : 'value',
    os.path.join(wc_dir, 'iota')                 : 'value',
    os.path.join(wc_dir, 'A', 'B', 'E', 'beta')  : 'value',
    os.path.join(wc_dir, 'A', 'B', 'lambda')     : 'value',
    os.path.join(wc_dir, 'A', 'D', 'H', 'omega') : 'value',
    })


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

def revert_with_changelists(sbox):
  "revert --changelist"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Add files to changelists based on the last character in their names.
  changelist_all_files(wc_dir, clname_from_lastchar_cb)

  # Add a line of text to all the versioned files in the tree.
  mod_all_files(wc_dir, "Please, oh please, revert me!\n")
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/B/lambda', 'A/B/E/alpha', 'A/B/E/beta',
                        'A/D/gamma', 'A/D/H/omega', 'iota', 'A/mu',
                        'A/D/G/tau', 'A/D/G/pi', 'A/D/H/chi',
                        'A/D/H/psi', 'A/D/G/rho', status='M ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # 'svn revert --changelist a WC_DIR' (without depth, no change expected)
  svntest.main.run_svn(None, "revert", "--changelist", "a", wc_dir)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # 'svn revert --changelist o --depth files WC_DIR WC_DIR/A/B' (no change)
  svntest.main.run_svn(None, "revert", "--depth", "files",
                       "--changelist", "o",
                       wc_dir, os.path.join(wc_dir, 'A', 'B'))
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # 'svn revert --changelist a --depth files WC_DIR WC_DIR/A/B'
  # (iota, lambda reverted)
  svntest.main.run_svn(None, "revert", "--depth", "files",
                       "--changelist", "a",
                       wc_dir, os.path.join(wc_dir, 'A', 'B'))
  expected_status.tweak('iota', 'A/B/lambda', status='  ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # 'svn revert --changelist a --changelist i --depth infinity WC_DIR'
  # (alpha, beta, gamma, omega, pi, chi, psi reverted)
  svntest.main.run_svn(None, "revert", "--depth", "infinity",
                       "--changelist", "a", "--changelist", "i",
                       wc_dir)
  expected_status.tweak('A/B/E/alpha', 'A/B/E/beta', 'A/D/gamma',
                        'A/D/H/omega', 'A/D/G/pi', 'A/D/H/chi',
                        'A/D/H/psi', status='  ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # 'svn revert --depth infinity WC_DIR' (back to pristine-ness)
  svntest.main.run_svn(None, "revert", "--depth", "infinity",
                       wc_dir)
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

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

def update_with_changelists(sbox):
  "update --changelist"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Add a line of text to all the versioned files in the tree, commit, update.
  mod_all_files(wc_dir, "Added line.\n")
  svntest.main.run_svn(None, "commit", "-m", "logmsg", wc_dir)
  svntest.main.run_svn(None, "update", wc_dir)

  # Add files to changelists based on the last character in their names.
  changelist_all_files(wc_dir, clname_from_lastchar_cb)

  ### Backdate only the files in the 'a' and 'i' changelists at depth
  ### files under WC_DIR and WC_DIR/A/B.

  # We expect update to only touch lambda and iota.
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/lambda' : Item(status='U '),
    'iota' : Item(status='U '),
    })

  # Disk state should have all the files except iota and lambda
  # carrying new text.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/B/E/alpha',
                      contents="This is the file 'alpha'.\nAdded line.\n")
  expected_disk.tweak('A/B/E/beta',
                      contents="This is the file 'beta'.\nAdded line.\n")
  expected_disk.tweak('A/D/gamma',
                      contents="This is the file 'gamma'.\nAdded line.\n")
  expected_disk.tweak('A/D/H/omega',
                      contents="This is the file 'omega'.\nAdded line.\n")
  expected_disk.tweak('A/mu',
                      contents="This is the file 'mu'.\nAdded line.\n")
  expected_disk.tweak('A/D/G/tau',
                      contents="This is the file 'tau'.\nAdded line.\n")
  expected_disk.tweak('A/D/G/pi',
                      contents="This is the file 'pi'.\nAdded line.\n")
  expected_disk.tweak('A/D/H/chi',
                      contents="This is the file 'chi'.\nAdded line.\n")
  expected_disk.tweak('A/D/H/psi',
                      contents="This is the file 'psi'.\nAdded line.\n")
  expected_disk.tweak('A/D/G/rho',
                      contents="This is the file 'rho'.\nAdded line.\n")

  # Status is clean, but with iota and lambda at r1 and all else at r2.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.tweak('iota', 'A/B/lambda', wc_rev=1)

  # Update.
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1,
                                        "-r", "1",
                                        "--changelist", "a",
                                        "--changelist", "i",
                                        "--depth", "files",
                                        wc_dir,
                                        os.path.join(wc_dir, 'A', 'B'))

  ### Backdate to depth infinity all changelists "a", "i", and "o" now.

  # We expect update to only touch all the files ending in 'a', 'i',
  # and 'o' (except lambda and iota which were previously updated).
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/pi' : Item(status='U '),
    'A/D/H/chi' : Item(status='U '),
    'A/D/H/psi' : Item(status='U '),
    'A/D/G/rho' : Item(status='U '),
    'A/B/E/alpha' : Item(status='U '),
    'A/B/E/beta' : Item(status='U '),
    'A/D/gamma' : Item(status='U '),
    'A/D/H/omega' : Item(status='U '),
    })

  # Disk state should have only tau and mu carrying new text.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu',
                      contents="This is the file 'mu'.\nAdded line.\n")
  expected_disk.tweak('A/D/G/tau',
                      contents="This is the file 'tau'.\nAdded line.\n")

  # Status is clean, but with iota and lambda at r1 and all else at r2.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.tweak('iota', 'A/B/lambda', 'A/D/G/pi', 'A/D/H/chi',
                        'A/D/H/psi', 'A/D/G/rho', 'A/B/E/alpha',
                        'A/B/E/beta', 'A/D/gamma', 'A/D/H/omega', wc_rev=1)

  # Update.
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1,
                                        "-r", "1",
                                        "--changelist", "a",
                                        "--changelist", "i",
                                        "--changelist", "o",
                                        "--depth", "infinity",
                                        wc_dir)


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

# list all tests here, starting with None:
test_list = [ None,
              add_remove_changelists,
              commit_one_changelist,
              commit_multiple_changelists,
              info_with_changelists,
              diff_with_changelists,
              propmods_with_changelists,
              revert_with_changelists,
              update_with_changelists,
             ]

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


### End of file.