The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env python
#
#  import_tests.py:  import tests
#
#  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 re, os.path, sys

# Our testing module
import svntest
from svntest import wc
from prop_tests import create_inherited_ignores_config
from svntest.main import SVN_PROP_INHERITABLE_IGNORES

# (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
exp_noop_up_out = svntest.actions.expected_noop_update_output

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

#----------------------------------------------------------------------
# this test should be SKIPped on systems without the executable bit
@SkipUnless(svntest.main.is_posix_os)
def import_executable(sbox):
  "import of executable files"

  sbox.build()
  wc_dir = sbox.wc_dir

  # create a new directory with files of various permissions
  xt_path = os.path.join(wc_dir, "XT")
  os.makedirs(xt_path)
  all_path = os.path.join(wc_dir, "XT/all_exe")
  none_path = os.path.join(wc_dir, "XT/none_exe")
  user_path = os.path.join(wc_dir, "XT/user_exe")
  group_path = os.path.join(wc_dir, "XT/group_exe")
  other_path = os.path.join(wc_dir, "XT/other_exe")

  for path in [all_path, none_path, user_path, group_path, other_path]:
    svntest.main.file_append(path, "some text")

  # set executable bits
  os.chmod(all_path, 0777)
  os.chmod(none_path, 0666)
  os.chmod(user_path, 0766)
  os.chmod(group_path, 0676)
  os.chmod(other_path, 0667)

  # import new files into repository
  url = sbox.repo_url
  exit_code, output, errput =   svntest.actions.run_and_verify_svn(
    None, None, [], 'import',
    '-m', 'Log message for new import', xt_path, url)

  lastline = output.pop().strip()
  cm = re.compile ("(Committed|Imported) revision [0-9]+.")
  match = cm.search (lastline)
  if not match:
    ### we should raise a less generic error here. which?
    raise svntest.Failure

  # remove (uncontrolled) local files
  svntest.main.safe_rmtree(xt_path)

  # Create expected disk tree for the update (disregarding props)
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'all_exe' :   Item('some text', props={'svn:executable' : ''}),
    'none_exe' :  Item('some text'),
    'user_exe' :  Item('some text', props={'svn:executable' : ''}),
    'group_exe' : Item('some text'),
    'other_exe' : Item('some text'),
    })

  # Create expected status tree for the update (disregarding props).
  # Newly imported file should be at revision 2.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'all_exe' : Item(status='  ', wc_rev=2),
    'none_exe' : Item(status='  ', wc_rev=2),
    'user_exe' : Item(status='  ', wc_rev=2),
    'group_exe' : Item(status='  ', wc_rev=2),
    'other_exe' : Item(status='  ', wc_rev=2),
    })

  # Create expected output tree for the update.
  expected_output = svntest.wc.State(wc_dir, {
    'all_exe' : Item(status='A '),
    'none_exe' : Item(status='A '),
    'user_exe' : Item(status='A '),
    'group_exe' : Item(status='A '),
    'other_exe' : Item(status='A '),
  })
  # do update and check three ways
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1)

#----------------------------------------------------------------------
def import_ignores(sbox):
  'do not import ignored files in imported dirs'

  # The bug was that
  #
  #   $ svn import dir
  #
  # where dir contains some items that match the ignore list and some
  # do not would add all items, ignored or not.
  #
  # This has been fixed by testing each item with the new
  # svn_wc_is_ignored function.

  sbox.build()
  wc_dir = sbox.wc_dir

  dir_path = os.path.join(wc_dir, 'dir')
  foo_c_path = os.path.join(dir_path, 'foo.c')
  foo_o_path = os.path.join(dir_path, 'foo.o')

  os.mkdir(dir_path, 0755)
  open(foo_c_path, 'w')
  open(foo_o_path, 'w')

  # import new dir into repository
  url = sbox.repo_url + '/dir'

  exit_code, output, errput = svntest.actions.run_and_verify_svn(
    None, None, [], 'import',
    '-m', 'Log message for new import',
    dir_path, url)

  lastline = output.pop().strip()
  cm = re.compile ("(Committed|Imported) revision [0-9]+.")
  match = cm.search (lastline)
  if not match:
    ### we should raise a less generic error here. which?
    raise svntest.verify.SVNUnexpectedOutput

  # remove (uncontrolled) local dir
  svntest.main.safe_rmtree(dir_path)

  # Create expected disk tree for the update (disregarding props)
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'dir/foo.c' : Item(''),
    })

  # Create expected status tree for the update (disregarding props).
  # Newly imported file should be at revision 2.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'dir' : Item(status='  ', wc_rev=2),
    'dir/foo.c' : Item(status='  ', wc_rev=2),
    })

  # Create expected output tree for the update.
  expected_output = svntest.wc.State(wc_dir, {
    'dir' : Item(status='A '),
    'dir/foo.c' : Item(status='A '),
  })

  # do update and check three ways
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1)

#----------------------------------------------------------------------
def import_no_ignores(sbox):
  'import ignored files in imported dirs'

  # import ignored files using the "--no-ignore" option

  sbox.build()
  wc_dir = sbox.wc_dir

  dir_path = os.path.join(wc_dir, 'dir')
  foo_c_path = os.path.join(dir_path, 'foo.c')
  foo_o_path = os.path.join(dir_path, 'foo.o')
  foo_lo_path = os.path.join(dir_path, 'foo.lo')
  foo_rej_path = os.path.join(dir_path, 'foo.rej')

  os.mkdir(dir_path, 0755)
  open(foo_c_path, 'w')
  open(foo_o_path, 'w')
  open(foo_lo_path, 'w')
  open(foo_rej_path, 'w')

  # import new dir into repository
  url = sbox.repo_url + '/dir'

  exit_code, output, errput = svntest.actions.run_and_verify_svn(
    None, None, [], 'import',
    '-m', 'Log message for new import', '--no-ignore',
    dir_path, url)

  lastline = output.pop().strip()
  cm = re.compile ("(Committed|Imported) revision [0-9]+.")
  match = cm.search (lastline)
  if not match:
    raise svntest.Failure

  # remove (uncontrolled) local dir
  svntest.main.safe_rmtree(dir_path)

  # Create expected disk tree for the update (disregarding props)
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'dir/foo.c' : Item(''),
    'dir/foo.o' : Item(''),
    'dir/foo.lo' : Item(''),
    'dir/foo.rej' : Item(''),
    })

  # Create expected status tree for the update (disregarding props).
  # Newly imported file should be at revision 2.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'dir' : Item(status='  ', wc_rev=2),
    'dir/foo.c' : Item(status='  ', wc_rev=2),
    'dir/foo.o' : Item(status='  ', wc_rev=2),
    'dir/foo.lo' : Item(status='  ', wc_rev=2),
    'dir/foo.rej' : Item(status='  ', wc_rev=2),
    })

  # Create expected output tree for the update.
  expected_output = svntest.wc.State(wc_dir, {
    'dir' : Item(status='A '),
    'dir/foo.c' : Item(status='A '),
    'dir/foo.o' : Item(status='A '),
    'dir/foo.lo' : Item(status='A '),
    'dir/foo.rej' : Item(status='A '),
    })

  # do update and check three ways
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1)
#----------------------------------------------------------------------
def import_avoid_empty_revision(sbox):
  "avoid creating empty revisions with import"

  sbox.build()
  wc_dir = sbox.wc_dir

  # create a new directory
  empty_dir = os.path.join(wc_dir, "empty_dir")
  os.makedirs(empty_dir)

  url = sbox.repo_url
  svntest.actions.run_and_verify_svn(None, None, [], 'import',
                                     '-m', 'Log message for new import',
                                     empty_dir, url)

  svntest.main.safe_rmtree(empty_dir)

  # Verify that an empty revision has not been created
  svntest.actions.run_and_verify_svn(None,
                                     exp_noop_up_out(1),
                                     [], "update",
                                     empty_dir)
#----------------------------------------------------------------------

# test for issue 2433: "import" does not handle eol-style correctly
# and for normalising files with mixed line-endings upon import (r1205193)
@Issue(2433)
def import_eol_style(sbox):
  "import should honor the eol-style property"

  sbox.build()
  os.chdir(sbox.wc_dir)

  # setup a custom config, we need autoprops
  config_contents = '''\
[auth]
password-stores =

[miscellany]
enable-auto-props = yes

[auto-props]
*.dsp = svn:eol-style=CRLF
*.txt = svn:eol-style=native
'''
  tmp_dir = os.path.abspath(svntest.main.temp_dir)
  config_dir = os.path.join(tmp_dir, 'autoprops_config')
  svntest.main.create_config_dir(config_dir, config_contents)

  # create a new file and import it
  file_name = "test.dsp"
  file_path = file_name
  imp_dir_path = 'dir'
  imp_file_path = os.path.join(imp_dir_path, file_name)

  os.mkdir(imp_dir_path, 0755)
  svntest.main.file_write(imp_file_path, "This is file test.dsp.\n")

  svntest.actions.run_and_verify_svn(None, None, [], 'import',
                                     '-m', 'Log message for new import',
                                     imp_dir_path,
                                     sbox.repo_url,
                                     '--config-dir', config_dir)

  svntest.main.run_svn(None, 'update', '.', '--config-dir', config_dir)

  # change part of the file
  svntest.main.file_append(file_path, "Extra line\n")

  # get a diff of the file, if the eol style is handled correctly, we'll
  # only see our added line here.
  # Before the issue was fixed, we would have seen something like this:
  # @@ -1 +1,2 @@
  # -This is file test.dsp.
  # +This is file test.dsp.
  # +Extra line

  # eol styl of test.dsp is CRLF, so diff will use that too. Make sure we
  # define CRLF in a platform independent way.
  # CRLF is a string that will match a CRLF sequence read from a text file.
  # ### On Windows, we assume CRLF will be read as LF, so it's a poor test.
  if os.name == 'nt':
    crlf = '\n'
  else:
    crlf = '\r\n'

  expected_output = [
  "Index: test.dsp\n",
  "===================================================================\n",
  "--- test.dsp\t(revision 2)\n",
  "+++ test.dsp\t(working copy)\n",
  "@@ -1 +1,2 @@\n",
  " This is file test.dsp." + crlf,
  "+Extra line" + crlf
  ]

  svntest.actions.run_and_verify_svn(None, expected_output, [],
                                     'diff',
                                     file_path,
                                     '--config-dir', config_dir)

  # create a file with inconsistent EOLs and eol-style=native, and import it
  file_name = "test.txt"
  file_path = file_name
  imp_dir_path = 'dir2'
  imp_file_path = os.path.join(imp_dir_path, file_name)

  os.mkdir(imp_dir_path, 0755)
  svntest.main.file_append_binary(imp_file_path,
                                  "This is file test.txt.\n" + \
                                  "The second line.\r\n" + \
                                  "The third line.\r")

  # The import should succeed and not error out
  svntest.actions.run_and_verify_svn(None, None, [], 'import',
                                     '-m', 'Log message for new import',
                                     imp_dir_path,
                                     sbox.repo_url,
                                     '--config-dir', config_dir)


#----------------------------------------------------------------------
@Issue(3983)
def import_into_foreign_repo(sbox):
  "import into a foreign repo"

  sbox.build(read_only=True)

  other_repo_dir, other_repo_url = sbox.add_repo_path('other')
  svntest.main.safe_rmtree(other_repo_dir, 1)
  svntest.main.create_repos(other_repo_dir)

  svntest.actions.run_and_verify_svn(None, None, [], 'import',
                                     '-m', 'Log message for new import',
                                     sbox.ospath('A/mu'), other_repo_url + '/f')

#----------------------------------------------------------------------
def import_inherited_ignores(sbox):
  'import and inherited ignores'

  sbox.build()
  wc_dir = sbox.wc_dir

  # Create this config file:
  #
  #   [miscellany]
  #   global-ignores = *.boo *.goo
  tmp_dir = os.path.abspath(svntest.main.temp_dir)
  config_dir = os.path.join(tmp_dir, 'autoprops_config_' + sbox.name)
  create_inherited_ignores_config(config_dir)

  # Set some ignore properties.
  sbox.simple_propset(SVN_PROP_INHERITABLE_IGNORES, '*.voo *.noo *.loo', '.')
  sbox.simple_propset(SVN_PROP_INHERITABLE_IGNORES, '*.yoo\t*.doo', 'A/B')
  sbox.simple_propset(SVN_PROP_INHERITABLE_IGNORES, '*.moo', 'A/D')
  sbox.simple_propset('svn:ignore', '*.zoo\n*.foo\n*.poo', 'A/B/E')
  sbox.simple_commit()

  # Use this tree for importing:
  #
  # DIR1.noo
  # DIR2.doo
  #   file1.txt
  # DIR3.foo
  #   file2.txt
  # DIR4.goo
  #   file3.txt
  #   file4.noo
  # DIR5.moo
  #   file5.txt
  # DIR6
  #   file6.foo
  #   DIR7
  #     file7.foo
  #     DIR8.noo
  import_tree_dir = os.path.join(os.path.dirname(sys.argv[0]),
                                 'import_tests_data', 'import_tree')

  # Relative WC paths of the imported tree.
  dir1_path  = os.path.join('DIR1.noo')
  dir2_path  = os.path.join('DIR2.doo')
  file1_path = os.path.join('DIR2.doo', 'file1.txt')
  dir3_path  = os.path.join('DIR3.foo')
  file2_path = os.path.join('DIR3.foo', 'file2.txt')
  dir4_path  = os.path.join('DIR4.goo')
  file3_path = os.path.join('DIR4.goo', 'file3.txt')
  file4_path = os.path.join('DIR4.goo', 'file4.noo')
  dir5_path  = os.path.join('DIR5.moo')
  file5_path = os.path.join('DIR5.moo', 'file5.txt')
  dir6_path  = os.path.join('DIR6')
  file6_path = os.path.join('DIR6', 'file6.foo')
  dir7_path  = os.path.join('DIR6', 'DIR7')
  file7_path = os.path.join('DIR6', 'DIR7', 'file7.foo')
  dir8_path  = os.path.join('DIR6', 'DIR7', 'DIR8.noo')

  # Import the tree to ^/A/B/E.
  # We should not see any *.noo paths because those are blocked at the
  # root of the repository by the svn:global-ignores property.  Likewise
  # *.doo paths are blocked by the svn:global-ignores on ^/A/B.  Nor
  # should we see and *.boo or *.goo paths, as those are blocked by the
  # global-ignores config. Lastly, ^/A/B/E should not get any *.foo paths
  # because of the svn:ignore property on ^/A/B/E, but non-immediate children
  # of ^/A/B/E are permitted *.foo paths.
  svntest.actions.run_and_verify_svn(None, None, [], 'import',
                                     '--config-dir', config_dir,
                                     import_tree_dir,
                                     sbox.repo_url + '/A/B/E',
                                     '-m', 'import')
  E_path = os.path.join(wc_dir, 'A', 'B', 'E')
  expected_output = svntest.verify.UnorderedOutput(
    ["Updating '" + wc_dir + "':\n",
     'A    ' + os.path.join(E_path, dir5_path)  + '\n',
     'A    ' + os.path.join(E_path, file5_path) + '\n',
     'A    ' + os.path.join(E_path, dir6_path)  + '\n',
     'A    ' + os.path.join(E_path, file6_path) + '\n',
     'A    ' + os.path.join(E_path, dir7_path)  + '\n',
     'A    ' + os.path.join(E_path, file7_path) + '\n',
     'Updated to revision 3.\n'])
  svntest.actions.run_and_verify_svn(None, expected_output, [], 'up', wc_dir)

  # Import the tree to ^/A/B/E/Z.  The only difference from above is that
  # DIR3.foo and its child file2.txt are also imported.  Why? Because now
  # we are creating a new directory in ^/A/B/E, so the svn:ignore property
  # set on ^/A/B/E doesn't apply.
  svntest.actions.run_and_verify_svn(None, None, [], 'import',
                                     '--config-dir', config_dir,
                                     import_tree_dir,
                                     sbox.repo_url + '/A/B/E/Z',
                                     '-m', 'import')
  Z_path = os.path.join(wc_dir, 'A', 'B', 'E', 'Z')
  expected_output = svntest.verify.UnorderedOutput(
    ["Updating '" + wc_dir + "':\n",
     'A    ' + os.path.join(Z_path)             + '\n',
     'A    ' + os.path.join(Z_path, dir5_path)  + '\n',
     'A    ' + os.path.join(Z_path, file5_path) + '\n',
     'A    ' + os.path.join(Z_path, dir6_path)  + '\n',
     'A    ' + os.path.join(Z_path, file6_path) + '\n',
     'A    ' + os.path.join(Z_path, dir7_path)  + '\n',
     'A    ' + os.path.join(Z_path, file7_path) + '\n',
     'A    ' + os.path.join(Z_path, dir3_path)  + '\n',
     'A    ' + os.path.join(Z_path, file2_path) + '\n',
     'Updated to revision 4.\n'])
  svntest.actions.run_and_verify_svn(None, expected_output, [], 'up', wc_dir)

  # Import the tree to ^/A/B/F with the --no-ignore option.
  # No ignores should be considered and the whole tree should
  # be imported.
  svntest.actions.run_and_verify_svn(None, None, [], 'import',
                                     '--config-dir', config_dir,
                                     '--no-ignore', import_tree_dir,
                                     sbox.repo_url + '/A/B/F',
                                     '-m', 'import')
  F_path = os.path.join(wc_dir, 'A', 'B', 'F')
  expected_output = svntest.verify.UnorderedOutput(
    ["Updating '" + wc_dir + "':\n",
     'A    ' + os.path.join(F_path, dir1_path)  + '\n',
     'A    ' + os.path.join(F_path, dir2_path)  + '\n',
     'A    ' + os.path.join(F_path, file1_path) + '\n',
     'A    ' + os.path.join(F_path, dir3_path)  + '\n',
     'A    ' + os.path.join(F_path, file2_path) + '\n',
     'A    ' + os.path.join(F_path, dir4_path)  + '\n',
     'A    ' + os.path.join(F_path, file3_path) + '\n',
     'A    ' + os.path.join(F_path, file4_path) + '\n',
     'A    ' + os.path.join(F_path, dir5_path)  + '\n',
     'A    ' + os.path.join(F_path, file5_path) + '\n',
     'A    ' + os.path.join(F_path, dir6_path)  + '\n',
     'A    ' + os.path.join(F_path, file6_path) + '\n',
     'A    ' + os.path.join(F_path, dir7_path)  + '\n',
     'A    ' + os.path.join(F_path, file7_path) + '\n',
     'A    ' + os.path.join(F_path, dir8_path)  + '\n',
     'Updated to revision 5.\n'])
  svntest.actions.run_and_verify_svn(None, expected_output, [], 'up', wc_dir)

  # Try importing a single file into a directory which has svn:ignore set
  # on it with a matching pattern of the imported file.  The import should
  # be a no-op.
  svntest.actions.run_and_verify_svn(None, [], [], 'import',
                                     '--config-dir', config_dir,
                                     os.path.join(import_tree_dir,
                                                  'DIR6', 'file6.foo'),
                                     sbox.repo_url + '/A/B/E/file6.foo',
                                     '-m', 'This import should fail!')

  # Try the above, but this time with --no-ignore, this time the import
  # should succeed.
  svntest.actions.run_and_verify_svn(None, None, [], 'import', '--no-ignore',
                                     '--config-dir', config_dir,
                                     os.path.join(import_tree_dir,
                                                  'DIR6', 'file6.foo'),
                                     sbox.repo_url + '/A/B/E/file6.foo',
                                     '-m', 'import')
  expected_output = svntest.verify.UnorderedOutput(
    ["Updating '" + wc_dir + "':\n",
     'A    ' + os.path.join(E_path, 'file6.foo') + '\n',
     'Updated to revision 6.\n'])
  svntest.actions.run_and_verify_svn(None, expected_output, [], 'up', wc_dir)

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

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


# list all tests here, starting with None:
test_list = [ None,
              import_executable,
              import_ignores,
              import_avoid_empty_revision,
              import_no_ignores,
              import_eol_style,
              import_into_foreign_repo,
              import_inherited_ignores,
             ]

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


### End of file.