#!/usr/bin/env python
#
#  history_tests.py:  testing history-tracing code
#
#  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.
######################################################################

# General modules
import os

# Our testing module
import svntest
from svntest import wc

# (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 = wc.StateItem

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

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

def cat_traces_renames(sbox):
  "verify that 'svn cat' traces renames"

  sbox.build()
  wc_dir = sbox.wc_dir
  rho_path   = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
  pi_path    = os.path.join(wc_dir, 'A', 'D', 'G', 'pi')
  bloo_path  = os.path.join(wc_dir, 'A', 'D', 'G', 'bloo')

  # rename rho to bloo. commit r2.
  svntest.main.run_svn(None, 'mv', rho_path, bloo_path)

  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/rho' : Item(verb='Deleting'),
    'A/D/G/bloo' : Item(verb='Adding')
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/D/G/rho')
  expected_status.add({ 'A/D/G/bloo' :
                        Item(wc_rev=2, status='  ') })

  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)

  # rename pi to rho.  commit r3.
  svntest.main.run_svn(None, 'mv', pi_path, rho_path)

  # svn cat -r1 rho  --> should show pi's contents.
  svntest.actions.run_and_verify_svn([ "This is the file 'pi'.\n"], [],
                                     'cat',  '-r', '1', rho_path)

  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/pi' : Item(verb='Deleting'),
    'A/D/G/rho' : Item(verb='Adding')
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/D/G/pi')
  expected_status.tweak('A/D/G/rho', wc_rev=3)
  expected_status.add({ 'A/D/G/bloo' :
                        Item(wc_rev=2, status='  ') })

  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)

  # update whole wc to HEAD
  expected_output = svntest.wc.State(wc_dir, { }) # no output
  expected_status.tweak(wc_rev=3)
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/D/G/pi', 'A/D/G/rho')
  expected_disk.add({
    'A/D/G/rho' : Item("This is the file 'pi'.\n"),
    })
  expected_disk.add({
    'A/D/G/bloo' : Item("This is the file 'rho'.\n"),
    })
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

  # 'svn cat bloo' --> should show rho's contents.
  svntest.actions.run_and_verify_svn([ "This is the file 'rho'.\n"], [],
                                     'cat',  bloo_path)

  # svn cat -r1 bloo --> should still show rho's contents.
  svntest.actions.run_and_verify_svn([ "This is the file 'rho'.\n"], [],
                                     'cat',  '-r', '1', bloo_path)

  # svn cat -r1 rho  --> should show pi's contents.
  svntest.actions.run_and_verify_svn([ "This is the file 'pi'.\n"], [],
                                     'cat',  '-r', '1', rho_path)

  # svn up -r1
  svntest.actions.run_and_verify_svn(None, [], 'up', '-r', '1', wc_dir)
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # svn cat -rHEAD rho --> should see 'unrelated object' error.
  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput,
                                     'cat',  '-r', 'HEAD', rho_path)

@Issue(1970)
def cat_avoids_false_identities(sbox):
  "verify that 'svn cat' avoids false identities"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Issue #1970
  #
  # Highlight a bug in the client side use of the repository's
  # location searching algorithm.
  #
  # The buggy history-following algorithm determines the paths that a
  # line of history would be *expected to be* found in a given revision,
  # but doesn't treat copies as gaps in the historical sequence.  If
  # some other object fills those gaps at the same expected path, the
  # client will find the wrong object.
  #
  # In the recipe below, iota gets created in r1.  In r2, it is
  # deleted and replaced with an unrelated object at the same path.
  # In r3, the interloper is deleted.  In r4, the original iota is
  # resurrected via a copy from r1.
  #
  #     ,- - - - - - --.
  #    o---| o---| o    o----->
  #
  #    |     |     |    |
  #   r1    r2    r3   r4
  #
  # In a working copy at r4, running
  #
  #    $ svn cat -r2 iota
  #
  # should result in an error, but with the bug it instead cats the r2
  # interloper.
  #
  # To reassure yourself that that's wrong, recall that the above
  # command is equivalent to
  #
  #    $ svn cat -r2 iota@4
  #
  # Now do you see the evil that lies within us?

  iota_path = os.path.join(wc_dir, 'iota')
  iota_url = sbox.repo_url + '/iota'

  # r2
  svntest.main.run_svn(None, 'del', iota_path)
  svntest.main.file_append(iota_path, "YOU SHOULD NOT SEE THIS\n")
  svntest.main.run_svn(None, 'add', iota_path)
  sbox.simple_commit(message='log msg')
  svntest.main.run_svn(None, 'up', wc_dir)

  # r3
  svntest.main.run_svn(None, 'del', iota_path)
  sbox.simple_commit(message='log msg')
  svntest.main.run_svn(None, 'up', wc_dir)

  # r4
  svntest.main.run_svn(None, 'cp', iota_url + '@1', wc_dir)
  sbox.simple_commit(message='log msg')
  svntest.main.run_svn(None, 'up', wc_dir)

  # 'svn cat -r2 iota' should error, because the line of history
  # currently identified by /iota did not exist in r2, even though a
  # totally unrelated file of the same name did.
  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput,
                                     'cat', '-r', '2', iota_path)


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

# list all tests here, starting with None:
test_list = [ None,
              cat_traces_renames,
              cat_avoids_false_identities,
              ]

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


### End of file.
