| #!/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. |