def test_is_valid_version(self): """Testing 'is_valid_version' method.""" self.assertTrue(checks.is_valid_version((1, 0, 0), (1, 0, 0))) self.assertTrue(checks.is_valid_version((1, 1, 0), (1, 0, 0))) self.assertTrue(checks.is_valid_version((1, 0, 1), (1, 0, 0))) self.assertTrue(checks.is_valid_version((1, 1, 0), (1, 1, 0))) self.assertTrue(checks.is_valid_version((1, 1, 1), (1, 1, 0))) self.assertTrue(checks.is_valid_version((1, 1, 1), (1, 1, 1))) self.assertFalse(checks.is_valid_version((0, 9, 9), (1, 0, 0))) self.assertFalse(checks.is_valid_version((1, 0, 9), (1, 1, 0))) self.assertFalse(checks.is_valid_version((1, 1, 0), (1, 1, 1)))
def test_history_scheduled_with_commit_special_case_exclude(self): """Testing SVNClient.history_scheduled_with_commit with exclude file""" self.client.get_repository_info() # Ensure valid SVN client version. if not is_valid_version(self.client.subversion_client_version, self.client.SHOW_COPIES_AS_ADDS_MIN_VERSION): raise SkipTest('Subversion client is too old to test ' 'history_scheduled_with_commit().') # Lone file with history is also excluded. In this case there should # be no SystemExit raised and an (empty) diff should be produced. Test # from checkout root and via changelist. self._run_svn(['copy', 'foo.txt', 'foo_copy.txt']) revisions = self.client.parse_revision_spec([]) result = self.client.diff(revisions, [], ['foo_copy.txt']) self.assertTrue(isinstance(result, dict)) self.assertTrue('diff' in result) self.assertEqual( md5(result['diff']).hexdigest(), 'd41d8cd98f00b204e9800998ecf8427e') self._run_svn(['changelist', 'cl1', 'foo_copy.txt']) revisions = self.client.parse_revision_spec(['cl1']) result = self.client.diff(revisions, [], ['foo_copy.txt']) self.assertTrue(isinstance(result, dict)) self.assertTrue('diff' in result) self.assertEqual( md5(result['diff']).hexdigest(), 'd41d8cd98f00b204e9800998ecf8427e')
def test_history_scheduled_with_commit_special_case_exclude(self): """Testing SVNClient.history_scheduled_with_commit with exclude file""" self.client.get_repository_info() # Ensure valid SVN client version. if not is_valid_version(self.client.subversion_client_version, self.client.SHOW_COPIES_AS_ADDS_MIN_VERSION): raise SkipTest('Subversion client is too old to test ' 'history_scheduled_with_commit().') # Lone file with history is also excluded. In this case there should # be no SystemExit raised and an (empty) diff should be produced. Test # from checkout root and via changelist. self._run_svn(['copy', 'foo.txt', 'foo_copy.txt']) revisions = self.client.parse_revision_spec([]) result = self.client.diff(revisions, [], ['foo_copy.txt']) self.assertTrue(isinstance(result, dict)) self.assertTrue('diff' in result) self.assertEqual(md5(result['diff']).hexdigest(), 'd41d8cd98f00b204e9800998ecf8427e') self._run_svn(['changelist', 'cl1', 'foo_copy.txt']) revisions = self.client.parse_revision_spec(['cl1']) result = self.client.diff(revisions, [], ['foo_copy.txt']) self.assertTrue(isinstance(result, dict)) self.assertTrue('diff' in result) self.assertEqual(md5(result['diff']).hexdigest(), 'd41d8cd98f00b204e9800998ecf8427e')
def test_history_scheduled_with_commit_special_case_non_local_mods(self): """Testing SVNClient.history_scheduled_with_commit is bypassed when diff is not for local modifications in a working copy""" self.client.get_repository_info() # Ensure valid SVN client version. if not is_valid_version(self.client.subversion_client_version, self.client.SHOW_COPIES_AS_ADDS_MIN_VERSION): raise SkipTest('Subversion client is too old to test ' 'history_scheduled_with_commit().') # While within a working copy which contains a scheduled commit with # addition-with-history, ensure history_scheduled_with_commit() is not # executed when generating a diff between two revisions either # 1) locally or 2) via --reposistory-url option. self._run_svn(['copy', 'foo.txt', 'foo_copy.txt']) revisions = self.client.parse_revision_spec(['1:2']) result = self.client.diff(revisions) self.assertTrue(isinstance(result, dict)) self.assertTrue('diff' in result) self.assertEqual( md5(result['diff']).hexdigest(), 'ed154720a7459c2649cab4d2fa34fa93') self.options.repository_url = self.svn_repo_url revisions = self.client.parse_revision_spec(['2']) result = self.client.diff(revisions) self.assertTrue(isinstance(result, dict)) self.assertTrue('diff' in result) self.assertEqual( md5(result['diff']).hexdigest(), 'ed154720a7459c2649cab4d2fa34fa93')
def test_history_scheduled_with_commit_special_case_non_local_mods(self): """Testing SVNClient.history_scheduled_with_commit is bypassed when diff is not for local modifications in a working copy""" self.client.get_repository_info() # Ensure valid SVN client version. if not is_valid_version(self.client.subversion_client_version, self.client.SHOW_COPIES_AS_ADDS_MIN_VERSION): raise SkipTest('Subversion client is too old to test ' 'history_scheduled_with_commit().') # While within a working copy which contains a scheduled commit with # addition-with-history, ensure history_scheduled_with_commit() is not # executed when generating a diff between two revisions either # 1) locally or 2) via --reposistory-url option. self._run_svn(['copy', 'foo.txt', 'foo_copy.txt']) revisions = self.client.parse_revision_spec(['1:2']) result = self.client.diff(revisions) self.assertTrue(isinstance(result, dict)) self.assertTrue('diff' in result) self.assertEqual(md5(result['diff']).hexdigest(), 'ed154720a7459c2649cab4d2fa34fa93') self.options.repository_url = self.svn_repo_url revisions = self.client.parse_revision_spec(['2']) result = self.client.diff(revisions) self.assertTrue(isinstance(result, dict)) self.assertTrue('diff' in result) self.assertEqual(md5(result['diff']).hexdigest(), 'ed154720a7459c2649cab4d2fa34fa93')
def apply_patch(self, patch_file, base_path, base_dir, p=None, revert=False): """Apply the patch and return a PatchResult indicating its success.""" if not is_valid_version(self.subversion_client_version, self.PATCH_MIN_VERSION): raise MinimumVersionError( 'Using "rbt patch" with the SVN backend requires at least ' 'svn 1.7.0') if base_dir and not base_dir.startswith(base_path): # The patch was created in either a higher level directory or a # directory not under this one. We should exclude files from the # patch that are not under this directory. excluded, empty = self._exclude_files_not_in_tree( patch_file, base_path) if excluded: logging.warn('This patch was generated in a different ' 'directory. To prevent conflicts, all files ' 'not under the current directory have been ' 'excluded. To apply all files in this ' 'patch, apply this patch from the %s directory.' % base_dir) if empty: logging.warn('All files were excluded from the patch.') cmd = ['patch'] p_num = p or self._get_p_number(base_path, base_dir) if p_num >= 0: cmd.append('--strip=%s' % p_num) if revert: cmd.append('--reverse-diff') cmd.append(six.text_type(patch_file)) rc, patch_output = self._run_svn(cmd, return_error_code=True) if self.supports_empty_files(): try: with open(patch_file, 'rb') as f: patch = f.read() except IOError as e: logging.error('Unable to read file %s: %s', patch_file, e) return self.apply_patch_for_empty_files(patch, p_num, revert=revert) # TODO: What is svn's equivalent of a garbage patch message? return PatchResult(applied=(rc == 0), patch_output=patch_output)
def apply_patch(self, patch_file, base_path, base_dir, p=None, revert=False): """Apply the patch and return a PatchResult indicating its success.""" if not is_valid_version(self.subversion_client_version, self.PATCH_MIN_VERSION): raise MinimumVersionError( 'Using "rbt patch" with the SVN backend requires at least ' 'svn 1.7.0') if base_dir and not base_dir.startswith(base_path): # The patch was created in either a higher level directory or a # directory not under this one. We should exclude files from the # patch that are not under this directory. excluded, empty = self._exclude_files_not_in_tree(patch_file, base_path) if excluded: logging.warn('This patch was generated in a different ' 'directory. To prevent conflicts, all files ' 'not under the current directory have been ' 'excluded. To apply all files in this ' 'patch, apply this patch from the %s directory.' % base_dir) if empty: logging.warn('All files were excluded from the patch.') cmd = ['patch'] p_num = p or self._get_p_number(base_path, base_dir) if p_num >= 0: cmd.append('--strip=%s' % p_num) if revert: cmd.append('--reverse-diff') cmd.append(six.text_type(patch_file)) rc, patch_output = self._run_svn(cmd, return_error_code=True) if self.supports_empty_files(): try: with open(patch_file, 'rb') as f: patch = f.read() except IOError as e: logging.error('Unable to read file %s: %s', patch_file, e) return self.apply_patch_for_empty_files(patch, p_num, revert=revert) # TODO: What is svn's equivalent of a garbage patch message? return PatchResult(applied=(rc == 0), patch_output=patch_output)
def check_show_copies_as_adds(self, state, md5sum): """Helper function to evaluate --show-copies-as-adds""" self.client.get_repository_info() # Ensure valid SVN client version. if not is_valid_version(self.client.subversion_client_version, self.client.SHOW_COPIES_AS_ADDS_MIN_VERSION): raise SkipTest('Subversion client is too old to test ' '--show-copies-as-adds.') self.options.svn_show_copies_as_adds = state self._svn_add_dir('dir1') self._svn_add_dir('dir2') self._run_svn(['copy', 'foo.txt', 'dir1']) # Generate identical diff via several methods: # 1) from checkout root # 2) via changelist # 3) from checkout root when all relevant files belong to a changelist # 4) via explicit include target revisions = self.client.parse_revision_spec() result = self.client.diff(revisions) self.assertTrue(isinstance(result, dict)) self.assertTrue('diff' in result) self.assertEqual(md5(result['diff']).hexdigest(), md5sum) self._run_svn(['changelist', 'cl1', 'dir1/foo.txt']) revisions = self.client.parse_revision_spec(['cl1']) result = self.client.diff(revisions) self.assertTrue(isinstance(result, dict)) self.assertTrue('diff' in result) self.assertEqual(md5(result['diff']).hexdigest(), md5sum) revisions = self.client.parse_revision_spec() result = self.client.diff(revisions) self.assertTrue(isinstance(result, dict)) self.assertTrue('diff' in result) self.assertEqual(md5(result['diff']).hexdigest(), md5sum) self._run_svn(['changelist', '--remove', 'dir1/foo.txt']) os.chdir('dir2') revisions = self.client.parse_revision_spec() result = self.client.diff(revisions, ['../dir1']) self.assertTrue(isinstance(result, dict)) self.assertTrue('diff' in result) self.assertEqual(md5(result['diff']).hexdigest(), md5sum)
def test_history_scheduled_with_commit_nominal(self): """Testing SVNClient.history_scheduled_with_commit nominal cases""" self.client.get_repository_info() # Ensure valid SVN client version. if not is_valid_version(self.client.subversion_client_version, self.client.SHOW_COPIES_AS_ADDS_MIN_VERSION): raise SkipTest('Subversion client is too old to test ' 'history_scheduled_with_commit().') self._svn_add_dir('dir1') self._svn_add_dir('dir2') self._run_svn(['copy', 'foo.txt', 'dir1']) # Squash stderr to prevent error message in test output. sys.stderr = StringIO() # Ensure SystemExit is raised when attempting to generate diff via # several methods: # 1) from checkout root # 2) via changelist # 3) from checkout root when all relevant files belong to a changelist # 4) via explicit include target revisions = self.client.parse_revision_spec() self.assertRaises(SystemExit, self.client.diff, revisions) self._run_svn(['changelist', 'cl1', 'dir1/foo.txt']) revisions = self.client.parse_revision_spec(['cl1']) self.assertRaises(SystemExit, self.client.diff, revisions) revisions = self.client.parse_revision_spec() self.assertRaises(SystemExit, self.client.diff, revisions) self._run_svn(['changelist', '--remove', 'dir1/foo.txt']) os.chdir('dir2') revisions = self.client.parse_revision_spec() self.assertRaises(SystemExit, self.client.diff, revisions, ['../dir1'])
def test_history_scheduled_with_commit_nominal(self): """Testing SVNClient.history_scheduled_with_commit nominal cases""" self.client.get_repository_info() # Ensure valid SVN client version. if not is_valid_version(self.client.subversion_client_version, self.client.SHOW_COPIES_AS_ADDS_MIN_VERSION): raise SkipTest('Subversion client is too old to test ' 'history_scheduled_with_commit().') self._svn_add_dir('dir1') self._svn_add_dir('dir2') self._run_svn(['copy', 'foo.txt', 'dir1']) # Squash stderr to prevent error message in test output. sys.stderr = open(os.devnull, 'w') # Ensure SystemExit is raised when attempting to generate diff via # several methods: # 1) from checkout root # 2) via changelist # 3) from checkout root when all relevant files belong to a changelist # 4) via explicit include target revisions = self.client.parse_revision_spec() self.assertRaises(SystemExit, self.client.diff, revisions) self._run_svn(['changelist', 'cl1', 'dir1/foo.txt']) revisions = self.client.parse_revision_spec(['cl1']) self.assertRaises(SystemExit, self.client.diff, revisions) revisions = self.client.parse_revision_spec() self.assertRaises(SystemExit, self.client.diff, revisions) self._run_svn(['changelist', '--remove', 'dir1/foo.txt']) os.chdir('dir2') revisions = self.client.parse_revision_spec() self.assertRaises(SystemExit, self.client.diff, revisions, ['../dir1'])
def get_repository_info(self): """Get repository information for the current Git working tree. Returns: rbtools.clients.RepositoryInfo: The repository info structure. """ # Temporarily reset the toplevel. This is necessary for making things # work correctly in unit tests where we may be moving the cwd around a # lot. self._git_toplevel = None if not check_install(['git', '--help']): # CreateProcess (launched via subprocess, used by check_install) # does not automatically append .cmd for things it finds in PATH. # If we're on Windows, and this works, save it for further use. if (sys.platform.startswith('win') and check_install(['git.cmd', '--help'])): self.git = 'git.cmd' else: logging.debug('Unable to execute "git --help" or "git.cmd ' '--help": skipping Git') return None git_dir = self._execute([self.git, 'rev-parse', '--git-dir'], ignore_errors=True).rstrip('\n') if git_dir.startswith('fatal:') or not os.path.isdir(git_dir): return None # Sometimes core.bare is not set, and generates an error, so ignore # errors. Valid values are 'true' or '1'. bare = execute([self.git, 'config', 'core.bare'], ignore_errors=True).strip() self.bare = bare in ('true', '1') # Running in directories other than the top level of # of a work-tree would result in broken diffs on the server if not self.bare: git_top = execute([self.git, 'rev-parse', '--show-toplevel'], ignore_errors=True).rstrip('\n') # Top level might not work on old git version se we use git dir # to find it. if (git_top.startswith(('fatal:', 'cygdrive')) or not os.path.isdir(git_dir)): git_top = git_dir self._git_toplevel = os.path.abspath(git_top) self._head_ref = self._execute( [self.git, 'symbolic-ref', '-q', 'HEAD'], ignore_errors=True).strip() # We know we have something we can work with. Let's find out # what it is. We'll try SVN first, but only if there's a .git/svn # directory. Otherwise, it may attempt to create one and scan # revisions, which can be slow. Also skip SVN detection if the git # repository was specified on command line. git_svn_dir = os.path.join(git_dir, 'svn') if (not getattr(self.options, 'repository_url', None) and os.path.isdir(git_svn_dir) and len(os.listdir(git_svn_dir)) > 0): data = self._execute([self.git, 'svn', 'info'], ignore_errors=True) m = re.search(r'^Repository Root: (.+)$', data, re.M) if m: path = m.group(1) m = re.search(r'^URL: (.+)$', data, re.M) if m: base_path = m.group(1)[len(path):] or '/' m = re.search(r'^Repository UUID: (.+)$', data, re.M) if m: uuid = m.group(1) self._type = self.TYPE_GIT_SVN m = re.search(r'Working Copy Root Path: (.+)$', data, re.M) if m: local_path = m.group(1) else: local_path = self._git_toplevel return SVNRepositoryInfo(path=path, base_path=base_path, local_path=local_path, uuid=uuid, supports_parent_diffs=True) else: # Versions of git-svn before 1.5.4 don't (appear to) support # 'git svn info'. If we fail because of an older git install, # here, figure out what version of git is installed and give # the user a hint about what to do next. version = self._execute([self.git, 'svn', '--version'], ignore_errors=True) version_parts = re.search('version (\d+)\.(\d+)\.(\d+)', version) svn_remote = self._execute( [self.git, 'config', '--get', 'svn-remote.svn.url'], ignore_errors=True) if (version_parts and svn_remote and not is_valid_version((int(version_parts.group(1)), int(version_parts.group(2)), int(version_parts.group(3))), (1, 5, 4))): raise SCMError('Your installation of git-svn must be ' 'upgraded to version 1.5.4 or later.') # Okay, maybe Perforce (git-p4). git_p4_ref = os.path.join(git_dir, 'refs', 'remotes', 'p4', 'master') if os.path.exists(git_p4_ref): data = self._execute([self.git, 'config', '--get', 'git-p4.port'], ignore_errors=True) m = re.search(r'(.+)', data) if m: port = m.group(1) else: port = os.getenv('P4PORT') if port: self._type = self.TYPE_GIT_P4 return RepositoryInfo(path=port, base_path='', local_path=self._git_toplevel, supports_parent_diffs=True) # Nope, it's git then. # Check for a tracking branch and determine merge-base self._type = self.TYPE_GIT url = None if getattr(self.options, 'repository_url', None): url = self.options.repository_url else: upstream_branch = self._get_parent_branch() url = self._get_origin(upstream_branch).rstrip('/') if url.startswith('fatal:'): raise SCMError('Could not determine remote URL for upstream ' 'branch %s' % upstream_branch) # Central bare repositories don't have origin URLs. # We return git_dir instead and hope for the best. if not url: url = os.path.abspath(git_dir) if url: return RepositoryInfo(path=url, base_path='', local_path=self._git_toplevel, supports_parent_diffs=True) return None
def get_repository_info(self): """Get repository information for the current Git working tree. This function changes the directory to the top level directory of the current working tree. """ if not check_install(['git', '--help']): # CreateProcess (launched via subprocess, used by check_install) # does not automatically append .cmd for things it finds in PATH. # If we're on Windows, and this works, save it for further use. if (sys.platform.startswith('win') and check_install(['git.cmd', '--help'])): self.git = 'git.cmd' else: logging.debug('Unable to execute "git --help" or "git.cmd ' '--help": skipping Git') return None git_dir = execute([self.git, "rev-parse", "--git-dir"], ignore_errors=True).rstrip("\n") if git_dir.startswith("fatal:") or not os.path.isdir(git_dir): return None # Sometimes core.bare is not set, and generates an error, so ignore # errors. Valid values are 'true' or '1'. bare = execute([self.git, 'config', 'core.bare'], ignore_errors=True).strip() self.bare = bare in ('true', '1') # If we are not working in a bare repository, then we will change # directory to the top level working tree lose our original position. # However, we need the original working directory for file exclusion # patterns, so we save it here. if self._original_cwd is None: self._original_cwd = os.getcwd() # Running in directories other than the top level of # of a work-tree would result in broken diffs on the server if not self.bare: git_top = execute([self.git, "rev-parse", "--show-toplevel"], ignore_errors=True).rstrip("\n") # Top level might not work on old git version se we use git dir # to find it. if (git_top.startswith('fatal:') or not os.path.isdir(git_dir) or git_top.startswith('cygdrive')): git_top = git_dir os.chdir(os.path.abspath(git_top)) self.head_ref = execute([self.git, 'symbolic-ref', '-q', 'HEAD'], ignore_errors=True).strip() # We know we have something we can work with. Let's find out # what it is. We'll try SVN first, but only if there's a .git/svn # directory. Otherwise, it may attempt to create one and scan # revisions, which can be slow. Also skip SVN detection if the git # repository was specified on command line. git_svn_dir = os.path.join(git_dir, 'svn') if (not getattr(self.options, 'repository_url', None) and os.path.isdir(git_svn_dir) and len(os.listdir(git_svn_dir)) > 0): data = execute([self.git, "svn", "info"], ignore_errors=True) m = re.search(r'^Repository Root: (.+)$', data, re.M) if m: path = m.group(1) m = re.search(r'^URL: (.+)$', data, re.M) if m: base_path = m.group(1)[len(path):] or "/" m = re.search(r'^Repository UUID: (.+)$', data, re.M) if m: uuid = m.group(1) self.type = "svn" # Get SVN tracking branch if getattr(self.options, 'tracking', None): self.upstream_branch = self.options.tracking else: data = execute([self.git, "svn", "rebase", "-n"], ignore_errors=True) m = re.search(r'^Remote Branch:\s*(.+)$', data, re.M) if m: self.upstream_branch = m.group(1) else: sys.stderr.write('Failed to determine SVN ' 'tracking branch. Defaulting' 'to "master"\n') self.upstream_branch = 'master' return SVNRepositoryInfo(path=path, base_path=base_path, uuid=uuid, supports_parent_diffs=True) else: # Versions of git-svn before 1.5.4 don't (appear to) support # 'git svn info'. If we fail because of an older git install, # here, figure out what version of git is installed and give # the user a hint about what to do next. version = execute([self.git, "svn", "--version"], ignore_errors=True) version_parts = re.search('version (\d+)\.(\d+)\.(\d+)', version) svn_remote = execute( [self.git, "config", "--get", "svn-remote.svn.url"], ignore_errors=True) if (version_parts and svn_remote and not is_valid_version((int(version_parts.group(1)), int(version_parts.group(2)), int(version_parts.group(3))), (1, 5, 4))): die("Your installation of git-svn must be upgraded to " "version 1.5.4 or later") # Okay, maybe Perforce (git-p4). git_p4_ref = os.path.join(git_dir, 'refs', 'remotes', 'p4', 'master') if os.path.exists(git_p4_ref): data = execute([self.git, 'config', '--get', 'git-p4.port'], ignore_errors=True) m = re.search(r'(.+)', data) if m: port = m.group(1) else: port = os.getenv('P4PORT') if port: self.type = 'perforce' self.upstream_branch = 'remotes/p4/master' return RepositoryInfo(path=port, base_path='', supports_parent_diffs=True) # Nope, it's git then. # Check for a tracking branch and determine merge-base self.upstream_branch = '' if self.head_ref: short_head = self._strip_heads_prefix(self.head_ref) merge = execute([self.git, 'config', '--get', 'branch.%s.merge' % short_head], ignore_errors=True).strip() remote = execute([self.git, 'config', '--get', 'branch.%s.remote' % short_head], ignore_errors=True).strip() merge = self._strip_heads_prefix(merge) if remote and remote != '.' and merge: self.upstream_branch = '%s/%s' % (remote, merge) url = None if getattr(self.options, 'repository_url', None): url = self.options.repository_url self.upstream_branch = self.get_origin(self.upstream_branch, True)[0] else: self.upstream_branch, origin_url = \ self.get_origin(self.upstream_branch, True) if not origin_url or origin_url.startswith("fatal:"): self.upstream_branch, origin_url = self.get_origin() url = origin_url.rstrip('/') # Central bare repositories don't have origin URLs. # We return git_dir instead and hope for the best. if not url: url = os.path.abspath(git_dir) # There is no remote, so skip this part of upstream_branch. self.upstream_branch = self.upstream_branch.split('/')[-1] if url: self.type = "git" return RepositoryInfo(path=url, base_path='', supports_parent_diffs=True) return None
def diff(self, revisions, include_files=[], exclude_patterns=[], extra_args=[]): """ Performs a diff in a Subversion repository. If the given revision spec is empty, this will do a diff of the modified files in the working directory. If the spec is a changelist, it will do a diff of the modified files in that changelist. If the spec is a single revision, it will show the changes in that revision. If the spec is two revisions, this will do a diff between the two revisions. SVN repositories do not support branches of branches in a way that makes parent diffs possible, so we never return a parent diff. """ repository_info = self.get_repository_info() # SVN paths are always relative to the root of the repository, so we # compute the current path we are checked out at and use that as the # current working directory. We use / for the base_dir because we do # not normalize the paths to be filesystem paths, but instead use SVN # paths. exclude_patterns = normalize_patterns(exclude_patterns, '/', repository_info.base_path) # Keep track of information needed for handling empty files later. empty_files_revisions = { 'base': None, 'tip': None, } base = str(revisions['base']) tip = str(revisions['tip']) diff_cmd = ['diff', '--diff-cmd=diff', '--notice-ancestry'] changelist = None if tip == self.REVISION_WORKING_COPY: # Posting the working copy diff_cmd.extend(['-r', base]) elif tip.startswith(self.REVISION_CHANGELIST_PREFIX): # Posting a changelist changelist = tip[len(self.REVISION_CHANGELIST_PREFIX):] diff_cmd.extend(['--changelist', changelist]) else: # Diff between two separate revisions. Behavior depends on whether # or not there's a working copy if self.options.repository_url: # No working copy--create 'old' and 'new' URLs if len(include_files) == 1: # If there's a single file or directory passed in, we use # that as part of the URL instead of as a separate # filename. repository_info.set_base_path(include_files[0]) include_files = [] new_url = (repository_info.path + repository_info.base_path + '@' + tip) # When the source revision is '0', assume the user wants to # upload a diff containing all the files in 'base_path' as # new files. If the base path within the repository is added to # both the old and new URLs, `svn diff` will error out, since # the base_path didn't exist at revision 0. To avoid that # error, use the repository's root URL as the source for the # diff. if base == '0': old_url = repository_info.path + '@' + base else: old_url = (repository_info.path + repository_info.base_path + '@' + base) diff_cmd.extend([old_url, new_url]) empty_files_revisions['base'] = '(revision %s)' % base empty_files_revisions['tip'] = '(revision %s)' % tip else: # Working copy--do a normal range diff diff_cmd.extend(['-r', '%s:%s' % (base, tip)]) empty_files_revisions['base'] = '(revision %s)' % base empty_files_revisions['tip'] = '(revision %s)' % tip diff_cmd.extend(include_files) if is_valid_version(self.subversion_client_version, self.SHOW_COPIES_AS_ADDS_MIN_VERSION): svn_show_copies_as_adds = getattr(self.options, 'svn_show_copies_as_adds', None) if svn_show_copies_as_adds is None: if self.history_scheduled_with_commit(changelist, include_files, exclude_patterns): sys.stderr.write("One or more files in your changeset has " "history scheduled with commit. Please " "try again with " "'--svn-show-copies-as-adds=y/n'.\n") sys.exit(1) else: if svn_show_copies_as_adds in 'Yy': diff_cmd.append("--show-copies-as-adds") diff = self._run_svn(diff_cmd, split_lines=True, results_unicode=False, log_output_on_error=False) diff = self.handle_renames(diff) if self.supports_empty_files(): diff = self._handle_empty_files(diff, diff_cmd, empty_files_revisions) diff = self.convert_to_absolute_paths(diff, repository_info) if exclude_patterns: diff = filter_diff(diff, self.INDEX_FILE_RE, exclude_patterns) return { 'diff': b''.join(diff), }
def diff(self, revisions, include_files=[], exclude_patterns=[], extra_args=[]): """ Performs a diff in a Subversion repository. If the given revision spec is empty, this will do a diff of the modified files in the working directory. If the spec is a changelist, it will do a diff of the modified files in that changelist. If the spec is a single revision, it will show the changes in that revision. If the spec is two revisions, this will do a diff between the two revisions. SVN repositories do not support branches of branches in a way that makes parent diffs possible, so we never return a parent diff. """ repository_info = self.get_repository_info() # SVN paths are always relative to the root of the repository, so we # compute the current path we are checked out at and use that as the # current working directory. We use / for the base_dir because we do # not normalize the paths to be filesystem paths, but instead use SVN # paths. exclude_patterns = normalize_patterns(exclude_patterns, '/', repository_info.base_path) # Keep track of information needed for handling empty files later. empty_files_revisions = { 'base': None, 'tip': None, } base = str(revisions['base']) tip = str(revisions['tip']) diff_cmd = ['diff', '--diff-cmd=diff', '--notice-ancestry'] changelist = None if tip == self.REVISION_WORKING_COPY: # Posting the working copy diff_cmd.extend(['-r', base]) elif tip.startswith(self.REVISION_CHANGELIST_PREFIX): # Posting a changelist changelist = tip[len(self.REVISION_CHANGELIST_PREFIX):] diff_cmd.extend(['--changelist', changelist]) else: # Diff between two separate revisions. Behavior depends on whether # or not there's a working copy if self.options.repository_url: # No working copy--create 'old' and 'new' URLs if len(include_files) == 1: # If there's a single file or directory passed in, we use # that as part of the URL instead of as a separate # filename. repository_info.set_base_path(include_files[0]) include_files = [] new_url = (repository_info.path + repository_info.base_path + '@' + tip) # When the source revision is '0', assume the user wants to # upload a diff containing all the files in 'base_path' as # new files. If the base path within the repository is added to # both the old and new URLs, `svn diff` will error out, since # the base_path didn't exist at revision 0. To avoid that # error, use the repository's root URL as the source for the # diff. if base == '0': old_url = repository_info.path + '@' + base else: old_url = (repository_info.path + repository_info.base_path + '@' + base) diff_cmd.extend([old_url, new_url]) empty_files_revisions['base'] = '(revision %s)' % base empty_files_revisions['tip'] = '(revision %s)' % tip else: # Working copy--do a normal range diff diff_cmd.extend(['-r', '%s:%s' % (base, tip)]) empty_files_revisions['base'] = '(revision %s)' % base empty_files_revisions['tip'] = '(revision %s)' % tip diff_cmd.extend(include_files) if is_valid_version(self.subversion_client_version, self.SHOW_COPIES_AS_ADDS_MIN_VERSION): svn_show_copies_as_adds = getattr( self.options, 'svn_show_copies_as_adds', None) if svn_show_copies_as_adds is None: if self.history_scheduled_with_commit(changelist, include_files, exclude_patterns): sys.stderr.write("One or more files in your changeset has " "history scheduled with commit. Please " "try again with " "'--svn-show-copies-as-adds=y/n'.\n") sys.exit(1) else: if svn_show_copies_as_adds in 'Yy': diff_cmd.append("--show-copies-as-adds") diff = self._run_svn(diff_cmd, split_lines=True, results_unicode=False, log_output_on_error=False) diff = self.handle_renames(diff) if self.supports_empty_files(): diff = self._handle_empty_files(diff, diff_cmd, empty_files_revisions) diff = self.convert_to_absolute_paths(diff, repository_info) if exclude_patterns: diff = filter_diff(diff, self.INDEX_FILE_RE, exclude_patterns) return { 'diff': b''.join(diff), }