blob: 36467778f8ca90153352daf0d494e1a89039cb92 [file] [log] [blame]
#!/usr/bin/env python
#
# blame_tests.py: testing line-by-line annotation.
#
# 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, sys, re
# Our testing module
import svntest
from svntest.main import server_has_mergeinfo
from prop_tests import binary_mime_type_on_text_file_warning
# For some basic merge setup used by blame -g tests.
from svntest.mergetrees import set_up_branch
# (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
# Helper function to validate the output of a particular run of blame.
def parse_and_verify_blame(output, expected_blame, with_merged=0):
"tokenize and validate the output of blame"
max_split = 2
keys = ['revision', 'author', 'text']
if with_merged:
keys.append('merged')
results = []
# Tokenize and parse each line
for line_str in output:
this_line = {}
if with_merged:
this_line['merged'] = (line_str[0] == 'G')
line_str = line_str[2:]
tokens = line_str.split(None, max_split)
if tokens[0] == '-':
this_line['revision'] = None
else:
this_line['revision'] = int(tokens[0])
if tokens[1] == '-':
this_line['author'] = None
else:
this_line['author'] = tokens[1]
this_line['text'] = tokens[2]
results.append(this_line)
# Verify the results
if len(results) != len(expected_blame):
raise svntest.Failure("expected and actual results not the same length")
pairs = list(zip(results, expected_blame))
for num in range(len(pairs)):
(item, expected_item) = pairs[num]
for key in keys:
if item[key] != expected_item[key]:
raise svntest.Failure('on line %d, expecting %s "%s", found "%s"' % \
(num+1, key, str(expected_item[key]), str(item[key])))
######################################################################
# Tests
#
# Each test must return on success or raise on failure.
#----------------------------------------------------------------------
def blame_space_in_name(sbox):
"annotate a file whose name contains a space"
sbox.build()
file_path = os.path.join(sbox.wc_dir, 'space in name')
svntest.main.file_append(file_path, "Hello\n")
svntest.main.run_svn(None, 'add', file_path)
svntest.main.run_svn(None, 'ci',
'-m', '', file_path)
svntest.main.run_svn(None, 'blame', file_path)
def blame_binary(sbox):
"annotate a binary file"
sbox.build()
wc_dir = sbox.wc_dir
# First, make a new revision of iota.
iota = os.path.join(wc_dir, 'iota')
svntest.main.file_append(iota, "New contents for iota\n")
svntest.main.run_svn(None, 'ci',
'-m', '', iota)
# Then do it again, but this time we set the mimetype to binary.
iota = os.path.join(wc_dir, 'iota')
svntest.main.file_append(iota, "More new contents for iota\n")
svntest.main.run_svn(binary_mime_type_on_text_file_warning,
'propset', 'svn:mime-type', 'image/jpeg', iota)
# Blame fails when mime-type is locally modified to binary
exit_code, output, errput = svntest.main.run_svn(2, 'blame', iota)
if (len(errput) != 1) or (errput[0].find('Skipping') == -1):
raise svntest.Failure
svntest.main.run_svn(None, 'ci',
'-m', '', iota)
# Blame fails when mime-type is binary
exit_code, output, errput = svntest.main.run_svn(2, 'blame', iota)
if (len(errput) != 1) or (errput[0].find('Skipping') == -1):
raise svntest.Failure
# Once more, but now let's remove that mimetype.
iota = os.path.join(wc_dir, 'iota')
svntest.main.file_append(iota, "Still more new contents for iota\n")
svntest.main.run_svn(None, 'propdel', 'svn:mime-type', iota)
svntest.main.run_svn(None, 'ci',
'-m', '', iota)
# Blame fails when asking about an old revision where the mime-type is binary
exit_code, output, errput = svntest.main.run_svn(2, 'blame', iota + '@3')
if (len(errput) != 1) or (errput[0].find('Skipping') == -1):
raise svntest.Failure
# But with --force, it should work.
exit_code, output, errput = svntest.main.run_svn(2, 'blame', '--force',
iota + '@3')
if (len(errput) != 0 or len(output) != 3):
raise svntest.Failure
# Issue #2154 - annotating a directory should fail
# (change needed if the desired behavior is to
# run blame recursively on all the files in it)
#
@Issue(2154)
def blame_directory(sbox):
"annotating a directory not allowed"
# Issue 2154 - blame on directory fails without error message
import re
# Setup
sbox.build(read_only = True)
wc_dir = sbox.wc_dir
dir = os.path.join(wc_dir, 'A')
# Run blame against directory 'A'. The repository error will
# probably include a leading slash on the path, but we'll tolerate
# it either way, since either way it would still be a clean error.
expected_error = ".*'[/]{0,1}A' is not a file"
exit_code, outlines, errlines = svntest.main.run_svn(1, 'blame', dir)
# Verify expected error message is output
for line in errlines:
if re.match(expected_error, line):
break
else:
raise svntest.Failure('Failed to find %s in %s' %
(expected_error, str(errlines)))
# Basic test for svn blame --xml.
#
def blame_in_xml(sbox):
"blame output in XML format"
sbox.build()
wc_dir = sbox.wc_dir
file_name = "iota"
file_path = os.path.join(wc_dir, file_name)
svntest.main.file_append(file_path, "Testing svn blame --xml\n")
expected_output = svntest.wc.State(wc_dir, {
'iota' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
# Retrieve last changed date from svn info
exit_code, output, error = svntest.actions.run_and_verify_svn(
None, [],
'log', file_path, '--xml', '-r1:2')
date1 = None
date2 = None
for line in output:
if line.find("<date>") >= 0:
if date1 is None:
date1 = line
continue
elif date2 is None:
date2 = line
break
else:
raise svntest.Failure
template = ['<?xml version="1.0" encoding="UTF-8"?>\n',
'<blame>\n',
'<target\n',
' path="' + file_path + '">\n',
'<entry\n',
' line-number="1">\n',
'<commit\n',
' revision="1">\n',
'<author>jrandom</author>\n',
'%s' % date1,
'</commit>\n',
'</entry>\n',
'<entry\n',
' line-number="2">\n',
'<commit\n',
' revision="2">\n',
'<author>jrandom</author>\n',
'%s' % date2,
'</commit>\n',
'</entry>\n',
'</target>\n',
'</blame>\n']
exit_code, output, error = svntest.actions.run_and_verify_svn(
None, [],
'blame', file_path, '--xml')
for i in range(0, len(output)):
if output[i] != template[i]:
raise svntest.Failure
# For a line changed before the requested start revision, blame should not
# print a revision number (as fixed in r848109) or crash (as it did with
# "--verbose" before being fixed in r849964).
#
def blame_on_unknown_revision(sbox):
"blame lines from unknown revisions"
sbox.build()
wc_dir = sbox.wc_dir
file_name = "iota"
file_path = os.path.join(wc_dir, file_name)
for i in range(1,3):
svntest.main.file_append(file_path, "\nExtra line %d" % (i))
expected_output = svntest.wc.State(wc_dir, {
'iota' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
exit_code, output, error = svntest.actions.run_and_verify_svn(
None, [],
'blame', file_path, '-rHEAD:HEAD')
if output[0].find(" - This is the file 'iota'.") == -1:
raise svntest.Failure
exit_code, output, error = svntest.actions.run_and_verify_svn(
None, [],
'blame', file_path, '--verbose', '-rHEAD:HEAD')
if output[0].find(" - This is the file 'iota'.") == -1:
raise svntest.Failure
# The default blame revision range should be 1:N, where N is the
# peg-revision of the target, or BASE or HEAD if no peg-revision is
# specified.
#
def blame_peg_rev(sbox):
"blame targets with peg-revisions"
sbox.build()
expected_output_r1 = [
" 1 jrandom This is the file 'iota'.\n" ]
os.chdir(sbox.wc_dir)
# Modify iota and commit it (r2).
svntest.main.file_write('iota', "This is no longer the file 'iota'.\n")
expected_output = svntest.wc.State('.', {
'iota' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit('.', expected_output, None)
# Check that we get a blame of r1 when we specify a peg revision of r1
# and no explicit revision.
svntest.actions.run_and_verify_svn(expected_output_r1, [],
'blame', 'iota@1')
# Check that an explicit revision overrides the default provided by
# the peg revision.
svntest.actions.run_and_verify_svn(expected_output_r1, [],
'blame', 'iota@2', '-r1')
def blame_eol_styles(sbox):
"blame with different eol styles"
sbox.build()
wc_dir = sbox.wc_dir
# CR
file_name = "iota"
file_path = os.path.join(wc_dir, file_name)
expected_output = svntest.wc.State(wc_dir, {
'iota' : Item(verb='Sending'),
})
# do the test for each eol-style
for eol in ['CR', 'LF', 'CRLF', 'native']:
svntest.main.run_svn(None, 'propdel', 'svn:eol-style', file_path)
svntest.main.file_write(file_path, "This is no longer the file 'iota'.\n")
for i in range(1,3):
svntest.main.file_append(file_path, "Extra line %d" % (i) + "\n")
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
svntest.main.run_svn(None, 'propset', 'svn:eol-style', eol,
file_path)
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
exit_code, output, error = svntest.actions.run_and_verify_svn(
None, [],
'blame', file_path, '-r1:HEAD')
# output is a list of lines, there should be 3 lines
if len(output) != 3:
raise svntest.Failure('Expected 3 lines in blame output but got %d: \n' %
len(output) + str(output))
def blame_ignore_whitespace(sbox):
"ignore whitespace when blaming"
sbox.build()
wc_dir = sbox.wc_dir
file_name = "iota"
file_path = os.path.join(wc_dir, file_name)
svntest.main.file_write(file_path,
"Aa\n"
"Bb\n"
"Cc\n")
expected_output = svntest.wc.State(wc_dir, {
'iota' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
# commit only whitespace changes
svntest.main.file_write(file_path,
" A a \n"
" B b \n"
" C c \n")
expected_output = svntest.wc.State(wc_dir, {
'iota' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
# match the blame output, as defined in the blame code:
# "%6ld %10s %s %s%s", rev, author ? author : " -",
# time_stdout , line, APR_EOL_STR
expected_output = [
" 2 jrandom A a \n",
" 2 jrandom B b \n",
" 2 jrandom C c \n",
]
exit_code, output, error = svntest.actions.run_and_verify_svn(
expected_output, [],
'blame', '-x', '-w', file_path)
# commit some changes
svntest.main.file_write(file_path,
" A a \n"
"Xxxx X\n"
" Bb b \n"
" C c \n")
expected_output = svntest.wc.State(wc_dir, {
'iota' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
expected_output = [
" 2 jrandom A a \n",
" 4 jrandom Xxxx X\n",
" 4 jrandom Bb b \n",
" 2 jrandom C c \n",
]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-x', '-w', file_path)
def blame_ignore_eolstyle(sbox):
"ignore eol styles when blaming"
sbox.build()
wc_dir = sbox.wc_dir
file_name = "iota"
file_path = os.path.join(wc_dir, file_name)
svntest.main.file_write(file_path,
"Aa\n"
"Bb\n"
"Cc\n")
expected_output = svntest.wc.State(wc_dir, {
'iota' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
# commit only eol changes
svntest.main.file_write(file_path,
"Aa\r"
"Bb\r"
"Cc")
expected_output = svntest.wc.State(wc_dir, {
'iota' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
expected_output = [
" 2 jrandom Aa\n",
" 2 jrandom Bb\n",
" 3 jrandom Cc\n",
]
exit_code, output, error = svntest.actions.run_and_verify_svn(
expected_output, [],
'blame', '-x', '--ignore-eol-style', file_path)
@SkipUnless(server_has_mergeinfo)
def blame_merge_info(sbox):
"test 'svn blame -g'"
from log_tests import merge_history_repos
merge_history_repos(sbox)
wc_dir = sbox.wc_dir
iota_path = os.path.join(wc_dir, 'trunk', 'iota')
mu_path = os.path.join(wc_dir, 'trunk', 'A', 'mu')
exit_code, output, error = svntest.actions.run_and_verify_svn(
None, [], 'blame', '-g', iota_path)
expected_blame = [
{ 'revision' : 2,
'author' : 'jrandom',
'text' : "This is the file 'iota'.\n",
'merged' : 0,
},
{ 'revision' : 11,
'author' : 'jrandom',
'text' : "'A' has changed a bit, with 'upsilon', and 'xi'.\n",
'merged' : 1,
},
]
parse_and_verify_blame(output, expected_blame, 1)
exit_code, output, error = svntest.actions.run_and_verify_svn(
None, [], 'blame', '-g', '-r10:11', iota_path)
expected_blame = [
{ 'revision' : None,
'author' : None,
'text' : "This is the file 'iota'.\n",
'merged' : 0,
},
{ 'revision' : None,
'author' : None,
'text' : "'A' has changed a bit.\n",
'merged' : 0,
},
]
parse_and_verify_blame(output, expected_blame, 1)
exit_code, output, error = svntest.actions.run_and_verify_svn(
None, [], 'blame', '-g', '-r16:17', mu_path)
expected_blame = [
{ 'revision' : None,
'author' : None,
'text' : "This is the file 'mu'.\n",
'merged' : 0,
},
{ 'revision' : 16,
'author' : 'jrandom',
'text' : "Don't forget to look at 'upsilon', as well.\n",
'merged' : 1,
},
{ 'revision' : 16,
'author' : 'jrandom',
'text' : "This is yet more content in 'mu'.\n",
'merged' : 1,
},
]
parse_and_verify_blame(output, expected_blame, 1)
@SkipUnless(server_has_mergeinfo)
def blame_merge_out_of_range(sbox):
"don't look for merged files out of range"
from log_tests import merge_history_repos
merge_history_repos(sbox)
wc_dir = sbox.wc_dir
upsilon_path = os.path.join(wc_dir, 'trunk', 'A', 'upsilon')
exit_code, output, error = svntest.actions.run_and_verify_svn(
None, [],
'blame', '-g', upsilon_path)
expected_blame = [
{ 'revision' : 4,
'author' : 'jrandom',
'text' : "This is the file 'upsilon'.\n",
'merged' : 0,
},
{ 'revision' : 11,
'author': 'jrandom',
'text' : "There is also the file 'xi'.\n",
'merged' : 1,
},
]
parse_and_verify_blame(output, expected_blame, 1)
# test for issue #2888: 'svn blame' aborts over ra_serf
@Issue(2888)
def blame_peg_rev_file_not_in_head(sbox):
"blame target not in HEAD with peg-revisions"
sbox.build()
expected_output_r1 = [
" 1 jrandom This is the file 'iota'.\n" ]
os.chdir(sbox.wc_dir)
# Modify iota and commit it (r2).
svntest.main.file_write('iota', "This is no longer the file 'iota'.\n")
expected_output = svntest.wc.State('.', {
'iota' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit('.', expected_output, None)
# Delete iota so that it doesn't exist in HEAD
svntest.main.run_svn(None, 'rm', sbox.repo_url + '/iota',
'-m', 'log message')
# Check that we get a blame of r1 when we specify a peg revision of r1
# and no explicit revision.
svntest.actions.run_and_verify_svn(expected_output_r1, [],
'blame', 'iota@1')
# Check that an explicit revision overrides the default provided by
# the peg revision.
svntest.actions.run_and_verify_svn(expected_output_r1, [],
'blame', 'iota@2', '-r1')
def blame_file_not_in_head(sbox):
"blame target not in HEAD"
sbox.build(create_wc = False, read_only = True)
notexisting_url = sbox.repo_url + '/notexisting'
# Check that a correct error message is printed when blaming a target that
# doesn't exist (in HEAD).
expected_err = ".*notexisting' (is not a file.*|path not found|does not exist)"
svntest.actions.run_and_verify_svn([], expected_err,
'blame', notexisting_url)
@SkipUnless(server_has_mergeinfo)
def blame_output_after_merge(sbox):
"blame -g output with inserted lines"
sbox.build()
wc_dir = sbox.wc_dir
trunk_url = sbox.repo_url + '/trunk'
trunk_A_url = trunk_url + '/A'
A_url = sbox.repo_url + '/A'
# r2: mv greek tree in trunk.
svntest.actions.run_and_verify_svn(["Committing transaction...\n",
"Committed revision 2.\n"], [],
'mv', "--parents", A_url, trunk_A_url,
"-m", "move greek tree to trunk")
svntest.actions.run_and_verify_update(wc_dir, None, None, None)
# r3: modify trunk/A/mu, modify and add some lines.
mu_path = os.path.join(wc_dir, "trunk", "A", "mu")
new_content = "New version of file 'mu'.\n" \
"2nd line in file 'mu'.\n" \
"3rd line in file 'mu'.\n" \
"4th line in file 'mu'.\n" \
"5th line in file 'mu'.\n" \
"6th line in file 'mu'.\n"
svntest.main.file_write(mu_path, new_content)
expected_output = svntest.wc.State(wc_dir, {
'trunk/A/mu' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
# r4: create branches/br from trunk
branches_br_url = sbox.repo_url + "/branches/br"
svntest.actions.run_and_verify_svn(["Committing transaction...\n",
"Committed revision 4.\n"], [],
'cp', '--parents',
trunk_url, branches_br_url,
"-m", "create branch")
svntest.actions.run_and_verify_update(wc_dir, None, None, None)
# r5: modify single line in branches/br/A/mu
branch_mu_path = os.path.join(wc_dir, "branches", "br", "A", "mu")
svntest.main.file_write(branch_mu_path,
"New version of file 'mu'.\n" \
"2nd line in file 'mu'.\n" \
"new 3rd line in file 'mu'.\n" \
"4th line in file 'mu'.\n" \
"5th line in file 'mu'.\n" \
"6th line in file 'mu'.\n")
expected_output = svntest.wc.State(wc_dir, {
'branches/br/A/mu' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
# r6: Insert a single line in branches/A/mu
svntest.main.file_write(branch_mu_path,
"New version of file 'mu'.\n" \
"2nd line in file 'mu'.\n" \
"new 3rd line in file 'mu'.\n" \
"add 3.5 line in file 'mu'.\n" \
"4th line in file 'mu'.\n" \
"5th line in file 'mu'.\n" \
"6th line in file 'mu'.\n")
expected_output = svntest.wc.State(wc_dir, {
'branches/br/A/mu' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
# r7: merge branches/br back to trunk
trunk_path = os.path.join(wc_dir, "trunk")
svntest.actions.run_and_verify_svn(None, [], 'merge',
'-r', '4:HEAD',
branches_br_url, trunk_path)
expected_output = svntest.wc.State(wc_dir, {
'trunk' : Item(verb='Sending'),
'trunk/A/mu' : Item(verb='Sending'),
})
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
None)
# Now test blame, first without the -g option
expected_output = [ " 3 jrandom New version of file 'mu'.\n",
" 3 jrandom 2nd line in file 'mu'.\n",
" 7 jrandom new 3rd line in file 'mu'.\n",
" 7 jrandom add 3.5 line in file 'mu'.\n",
" 3 jrandom 4th line in file 'mu'.\n",
" 3 jrandom 5th line in file 'mu'.\n",
" 3 jrandom 6th line in file 'mu'.\n"]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', mu_path)
# Next test with the -g option
# the branch modifications at revision 5 & 6 should show in the output
expected_output = [ " 3 jrandom New version of file 'mu'.\n",
" 3 jrandom 2nd line in file 'mu'.\n",
"G 5 jrandom new 3rd line in file 'mu'.\n",
"G 6 jrandom add 3.5 line in file 'mu'.\n",
" 3 jrandom 4th line in file 'mu'.\n",
" 3 jrandom 5th line in file 'mu'.\n",
" 3 jrandom 6th line in file 'mu'.\n"]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-g', mu_path)
# Now test with -rN:M
expected_output = [ " - - New version of file 'mu'.\n",
" - - 2nd line in file 'mu'.\n",
" 7 jrandom new 3rd line in file 'mu'.\n",
" 7 jrandom add 3.5 line in file 'mu'.\n",
" - - 4th line in file 'mu'.\n",
" - - 5th line in file 'mu'.\n",
" - - 6th line in file 'mu'.\n"]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-r', '4:head', mu_path)
# Next test with the -g option with -rN:M
expected_output = [ " - - New version of file 'mu'.\n",
" - - 2nd line in file 'mu'.\n",
"G 5 jrandom new 3rd line in file 'mu'.\n",
"G 6 jrandom add 3.5 line in file 'mu'.\n",
" - - 4th line in file 'mu'.\n",
" - - 5th line in file 'mu'.\n",
" - - 6th line in file 'mu'.\n"]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-g', '-r', '6:head', mu_path)
#----------------------------------------------------------------------
@SkipUnless(server_has_mergeinfo)
@XFail()
@Issue(3862)
def merge_sensitive_blame_and_empty_mergeinfo(sbox):
"blame -g handles changes from empty mergeinfo"
sbox.build()
wc_dir = sbox.wc_dir
wc_disk, wc_status = set_up_branch(sbox, True)
A_COPY_path = os.path.join(wc_dir, 'A_COPY')
psi_path = os.path.join(wc_dir, 'A', 'D', 'H', 'psi')
psi_COPY_path = os.path.join(wc_dir, 'A_COPY', 'D', 'H', 'psi')
# Make an edit to A/D/H/psi in r3.
svntest.main.file_append(psi_path, "trunk edit in revision three.\n")
sbox.simple_commit(message='trunk edit')
# Merge r3 from A to A_COPY, reverse merge r3 from A/D/H/psi
# to A_COPY/D/H/psi, and commit as r4. This results in empty
# mergeinfo on A_COPY/D/H/psi.
svntest.main.run_svn(None, 'up', wc_dir)
svntest.main.run_svn(None, 'merge', '-c3',
sbox.repo_url + '/A', A_COPY_path)
svntest.main.run_svn(None, 'merge', '-c-3',
sbox.repo_url + '/A/D/H/psi', psi_COPY_path)
sbox.simple_commit(message='Sync merge A to A_COPY excepting A_COPY/D/H/psi')
# Make an edit to A/D/H/psi in r5.
svntest.main.file_append(psi_path, "trunk edit in revision five.\n")
sbox.simple_commit(message='trunk edit')
# Sync merge A/D/H/psi to A_COPY/D/H/psi and commit as r6. This replaces
# the empty mergeinfo on A_COPY/D/H/psi with '/A/D/H/psi:2-5'.
svntest.main.run_svn(None, 'up', wc_dir)
svntest.main.run_svn(None, 'merge', sbox.repo_url + '/A/D/H/psi',
psi_COPY_path)
sbox.simple_commit(message='Sync merge A/D/H/psi to A_COPY/D/H/psi')
# Check the blame -g output:
# Currently this test fails because the trunk edit done in r3 is
# reported as having been done in r5.
#
# >svn blame -g A_COPY\D\H\psi
# 1 jrandom This is the file 'psi'.
# G 5 jrandom trunk edit in revision three.
# G 5 jrandom trunk edit in revision five.
expected_output = [
" 1 jrandom This is the file 'psi'.\n",
"G 3 jrandom trunk edit in revision three.\n",
"G 5 jrandom trunk edit in revision five.\n"]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-g', psi_COPY_path)
def blame_multiple_targets(sbox):
"blame multiple target"
sbox.build()
# First, make a new revision of iota.
sbox.simple_append('iota', "New contents for iota\n")
sbox.simple_commit()
iota = sbox.ospath('iota')
expected_output = [
" 1 jrandom This is the file 'iota'.\n",
" 2 jrandom New contents for iota\n",
]
# We use --force to avoid an early bail from the current blame code,
# that performs a property check before the actual blame.
non_existent = os.path.join(sbox.wc_dir, 'non-existent')
svntest.actions.run_and_verify_svn(None,
".*W155010: The node.*non-existent'.*",
'blame', non_existent, iota,
'--force')
iota_url = sbox.repo_url + '/iota'
non_existent_url = sbox.repo_url + '/non-existent'
# SVN_ERR_FS_NOT_FILE | SVN_ERR_FS_NOT_FOUND
svntest.actions.run_and_verify_svn(None,
".*W1600(13|17): '.*non-existent' .*not",
'blame', non_existent_url, iota_url,
'--force')
@Issue(4034)
def blame_eol_handling(sbox):
"blame it on the eol handling"
sbox.build()
if os.name == 'nt':
native_eol = '\r\n'
else:
native_eol = '\n'
for eol, prop, rev in [ ('\r', 'CR', 2),
('\n', 'LF', 4),
('\r\n', 'CRLF', 6),
(native_eol, 'native', 8) ]:
f1 = sbox.ospath('blame-%s' % prop)
f2 = sbox.ospath('blame-%s-prop' % prop)
file_data = 'line 1 ' + eol + \
'line 2 ' + eol + \
'line 3 ' + eol + \
'line 4 ' + eol + \
'line 5 ' + eol
svntest.main.file_write(f1, file_data, mode='wb')
svntest.main.file_write(f2, file_data, mode='wb')
sbox.simple_add('blame-%s' % prop,
'blame-%s-prop' % prop)
sbox.simple_propset('svn:eol-style', prop, 'blame-%s-prop' % prop)
sbox.simple_commit()
file_data = 'line 1 ' + eol + \
'line 2 ' + eol + \
'line 2a' + eol + \
'line 3 ' + eol + \
'line 4 ' + eol + \
'line 4a' + eol + \
'line 5 ' + eol
svntest.main.file_write(f1, file_data, mode='wb')
svntest.main.file_write(f2, file_data, mode='wb')
sbox.simple_commit()
expected_output = [
' %d jrandom line 1 \n' % rev,
' %d jrandom line 2 \n' % rev,
' %d jrandom line 2a\n' % (rev + 1),
' %d jrandom line 3 \n' % rev,
' %d jrandom line 4 \n' % rev,
' %d jrandom line 4a\n' % (rev + 1),
' %d jrandom line 5 \n' % rev,
]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', f1)
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', f2)
file_data = 'line 1 ' + eol + \
'line 2 ' + eol + \
'line 2a' + eol + \
'line 3 ' + eol + \
'line 3b' + eol + \
'line 4 ' + eol + \
'line 4a' + eol + \
'line 5 ' + eol
svntest.main.file_write(f1, file_data, mode='wb')
svntest.main.file_write(f2, file_data, mode='wb')
expected_output = [
' %d jrandom line 1 \n' % rev,
' %d jrandom line 2 \n' % rev,
' %d jrandom line 2a\n' % (rev + 1),
' %d jrandom line 3 \n' % rev,
' - - line 3b\n',
' %d jrandom line 4 \n' % rev,
' %d jrandom line 4a\n' % (rev + 1),
' %d jrandom line 5 \n' % rev,
]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', f1)
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', f2)
@SkipUnless(svntest.main.server_has_reverse_get_file_revs)
def blame_youngest_to_oldest(sbox):
"blame_youngest_to_oldest"
sbox.build()
# First, make a new revision of iota.
iota = sbox.ospath('iota')
orig_line = open(iota).read()
line = "New contents for iota\n"
svntest.main.file_append(iota, line)
sbox.simple_commit() #r2
# Move the file, to check that the operation will peg correctly.
iota_moved = sbox.ospath('iota_moved')
sbox.simple_move('iota', 'iota_moved')
sbox.simple_commit() #r3
# Delete a line.
svntest.main.file_write(iota_moved, line)
sbox.simple_commit() #r4
expected_output = [
' %d jrandom %s\n' % (4, orig_line[:-1]),
]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-r4:1', iota_moved)
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-rHEAD:1', iota_moved)
expected_output = [
' %d jrandom %s\n' % (2, line[:-1]),
]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-r1:HEAD', iota_moved)
@Issue(4467)
def blame_reverse_no_change(sbox):
"blame reverse towards a revision with no change"
sbox.build()
# Introduce a revision where iota doesn't change!
sbox.simple_propset('a', 'b', 'A')
sbox.simple_commit('') #r2
sbox.simple_append('iota', 'new line\n')
sbox.simple_commit('') #r3
sbox.simple_append('iota', 'another new line\n')
sbox.simple_commit('') #r4
expected_output = [
' - - This is the file \'iota\'.\n',
' 3 jrandom new line\n',
' 4 jrandom another new line\n',
]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-r2:HEAD', sbox.ospath('iota'))
expected_output = [
' - - This is the file \'iota\'.\n',
]
# This used to trigger an assertion on 1.9.x before 1.9.0
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-rHEAD:2', sbox.ospath('iota'))
# Drop the middle line
sbox.simple_append('iota', 'This is the file \'iota\'.\n'
'another new line\n', truncate=True)
sbox.simple_commit('') #r5
# Back to start
sbox.simple_append('iota', 'This is the file \'iota\'.\n', truncate=True)
sbox.simple_commit('') #r6
expected_output = [
' - - This is the file \'iota\'.\n',
]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-rHEAD:2', sbox.ospath('iota'))
expected_output = [
' - - This is the file \'iota\'.\n',
' 5 jrandom new line\n',
]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-rHEAD:3', sbox.ospath('iota'))
expected_output = [
' - - This is the file \'iota\'.\n',
' 5 jrandom new line\n',
' 6 jrandom another new line\n',
]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-rHEAD:4', sbox.ospath('iota'))
expected_output = [
' - - This is the file \'iota\'.\n',
' 6 jrandom another new line\n',
]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-rHEAD:5', sbox.ospath('iota'))
expected_output = [
' - - This is the file \'iota\'.\n',
]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-rHEAD:6', sbox.ospath('iota'))
expected_output = [
' - - This is the file \'iota\'.\n',
' 5 jrandom new line\n',
]
svntest.actions.run_and_verify_svn(expected_output, [],
'blame', '-r5:3', sbox.ospath('iota'))
########################################################################
# Run the tests
# list all tests here, starting with None:
test_list = [ None,
blame_space_in_name,
blame_binary,
blame_directory,
blame_in_xml,
blame_on_unknown_revision,
blame_peg_rev,
blame_eol_styles,
blame_ignore_whitespace,
blame_ignore_eolstyle,
blame_merge_info,
blame_merge_out_of_range,
blame_peg_rev_file_not_in_head,
blame_file_not_in_head,
blame_output_after_merge,
merge_sensitive_blame_and_empty_mergeinfo,
blame_multiple_targets,
blame_eol_handling,
blame_youngest_to_oldest,
blame_reverse_no_change,
]
if __name__ == '__main__':
svntest.main.run_tests(test_list)
# NOTREACHED
### End of file.