def clean(self): result = run([ self.executable(), 'revert', '-R', self.root_path, ], cwd=self.root_path).returncode if result: return result for line in reversed( run([self.executable(), 'status'], cwd=self.root_path, capture_output=True, encoding='utf-8').stdout.splitlines()): candidate = line.split(' ') if candidate[0] != '?': continue path = os.path.join(self.root_path, ' '.join(candidate[1:])) if os.path.isdir(path): shutil.rmtree(path, ignore_errors=True) elif os.path.exists(path): os.remove(path) return 0
def test_documentation(self): with OutputCapture(): with mocks.Subprocess( 'ls', completion=mocks.ProcessCompletion( returncode=0, stdout='file1.txt\nfile2.txt\n'), ): result = run(['ls'], capture_output=True, encoding='utf-8') assert result.returncode == 0 assert result.stdout == 'file1.txt\nfile2.txt\n' with mocks.Subprocess( 'ls', completion=mocks.ProcessCompletion( returncode=0, stdout='file1.txt\nfile2.txt\n'), ): assert subprocess.check_output(['ls' ]) == b'file1.txt\nfile2.txt\n' assert subprocess.check_call(['ls']) == 0 with mocks.Subprocess( mocks.Subprocess.CommandRoute( 'command-a', 'argument', completion=mocks.ProcessCompletion(returncode=0)), mocks.Subprocess.CommandRoute( 'command-b', completion=mocks.ProcessCompletion(returncode=-1)), ): result = run(['command-a', 'argument']) assert result.returncode == 0 result = run(['command-b']) assert result.returncode == -1
def test_implied_route(self): with mocks.Subprocess( 'command', completion=mocks.ProcessCompletion(returncode=0)): self.assertEqual(run(['command']).returncode, 0) with self.assertRaises(OSError): run(['invalid-file'])
def checkout(self, argument): if not isinstance(argument, six.string_types): raise ValueError( "Expected 'argument' to be a string, not '{}'".format( type(argument))) self._branch = None if log.level > logging.WARNING: log_arg = ['-q'] elif log.level < logging.WARNING: log_arg = ['--progress'] else: log_arg = [] parsed_commit = Commit.parse(argument, do_assert=False) if parsed_commit: commit = self.commit( hash=parsed_commit.hash, revision=parsed_commit.revision, identifier=parsed_commit.identifier, branch=parsed_commit.branch, ) return None if run( [self.executable(), 'checkout'] + [commit.hash] + log_arg, cwd=self.root_path, ).returncode else commit return None if run( [self.executable(), 'checkout'] + [argument] + log_arg, cwd=self.root_path, ).returncode else self.commit()
def pull(self): commit = self.commit() code = run([self.executable(), 'pull'], cwd=self.root_path).returncode if not code and self.is_svn: return run([ self.executable(), 'svn', 'fetch', '--log-window-size=5000', '-r', '{}:HEAD'.format(commit.revision), ], cwd=self.root_path).returncode return code
def branch(self): status = run([self.executable(), 'status'], cwd=self.root_path, capture_output=True, encoding='utf-8') if status.returncode: raise self.Exception('Failed to run `git status` for {}'.format(self.root_path)) if status.stdout.splitlines()[0].startswith('HEAD detached at'): return None result = run([self.executable(), 'rev-parse', '--abbrev-ref', 'HEAD'], cwd=self.root_path, capture_output=True, encoding='utf-8') if result.returncode: raise self.Exception('Failed to retrieve branch for {}'.format(self.root_path)) return result.stdout.rstrip()
def test_argument_priority(self): with OutputCapture(), mocks.Subprocess( mocks.Subprocess.Route( 'command', '--help', completion=mocks.ProcessCompletion(returncode=0)), mocks.Subprocess.Route( 'command', completion=mocks.ProcessCompletion(returncode=1)), ): self.assertEqual(run(['command']).returncode, 1) self.assertEqual(run(['command', '--help']).returncode, 0)
def test_cwd_priority(self): with OutputCapture(), mocks.Subprocess( mocks.Subprocess.Route( 'command', completion=mocks.ProcessCompletion(returncode=0), cwd='/example'), mocks.Subprocess.Route( 'command', completion=mocks.ProcessCompletion(returncode=1)), ): self.assertEqual(run(['command']).returncode, 1) self.assertEqual(run(['command'], cwd='/example').returncode, 0)
def test_input_priority(self): with OutputCapture(), mocks.Subprocess( mocks.Subprocess.Route( 'command', completion=mocks.ProcessCompletion(returncode=0), input='stdin'), mocks.Subprocess.Route( 'command', completion=mocks.ProcessCompletion(returncode=1)), ): self.assertEqual(run(['command']).returncode, 1) self.assertEqual(run(['command'], input='stdin').returncode, 0) self.assertEqual( run(['command'], stdin=BytesIO(b'stdin')).returncode, 0)
def test_ordered(self): with OutputCapture(), mocks.Subprocess( mocks.Subprocess.Route( 'command', completion=mocks.ProcessCompletion(returncode=0)), mocks.Subprocess.Route( 'command', completion=mocks.ProcessCompletion(returncode=1)), ordered=True, ): self.assertEqual(run(['command']).returncode, 0) self.assertEqual(run(['command']).returncode, 1) with self.assertRaises(OSError): run(['command'])
def info(self, branch=None, revision=None, tag=None): if tag and branch: raise ValueError('Cannot specify both branch and tag') if tag and revision: raise ValueError('Cannot specify both branch and tag') revision = Commit._parse_revision(revision) if branch and branch != self.default_branch and '/' not in branch: branch = 'branches/{}'.format(branch) additional_args = [ '^/{}'.format(branch) ] if branch and branch != self.default_branch else [] additional_args += ['^/tags/{}'.format(tag)] if tag else [] additional_args += ['-r', str(revision)] if revision else [] info_result = run([self.executable(), 'info'] + additional_args, cwd=self.root_path, capture_output=True, encoding='utf-8') if info_result.returncode: return {} result = {} for line in info_result.stdout.splitlines(): split = line.split(': ') result[split[0]] = ': '.join(split[1:]) return result
def test_run(self): result = run([sys.executable, '-c', 'print("message")'], capture_output=True, encoding='utf-8') self.assertEqual(0, result.returncode) self.assertEqual(result.stdout, 'message\n') self.assertEqual(result.stderr, '')
def find(self, argument, include_log=True): if not isinstance(argument, six.string_types): raise ValueError( "Expected 'argument' to be a string, not '{}'".format( type(argument))) parsed_commit = Commit.parse(argument, do_assert=False) if parsed_commit: return self.commit( hash=parsed_commit.hash, revision=parsed_commit.revision, identifier=parsed_commit.identifier, branch=parsed_commit.branch, include_log=include_log, ) output = run( [self.executable(), 'rev-parse', argument], cwd=self.root_path, capture_output=True, encoding='utf-8', ) if output.returncode: raise ValueError( "'{}' is not an argument recognized by git".format(argument)) return self.commit(hash=output.stdout.rstrip(), include_log=include_log)
def _commit_count(self, native_parameter): revision_count = run( [self.executable(), 'rev-list', '--count', '--no-merges', native_parameter], cwd=self.root_path, capture_output=True, encoding='utf-8', ) if revision_count.returncode: raise self.Exception('Failed to retrieve revision count for {}'.format(native_parameter)) return int(revision_count.stdout)
def root_path(self): result = run([self.executable(), 'rev-parse', '--show-toplevel'], cwd=self.path, capture_output=True, encoding='utf-8') if result.returncode: return None return result.stdout.rstrip()
def clean(self): return run([ self.executable(), 'reset', 'HEAD', '--hard', ], cwd=self.root_path).returncode
def remote(self, name=None): result = run([self.executable, 'remote', 'get-url', name or 'origin'], cwd=self.root_path, capture_output=True, encoding='utf-8') if result.returncode: raise self.Exception('Failed to retrieve remote for {}'.format( self.root_path)) return result.stdout.rstrip()
def test_popen(self): with OutputCapture() as captured: with mocks.Subprocess(MockSubprocess.LS): result = run(['ls']) self.assertEqual(result.returncode, 0) self.assertEqual(result.stdout, None) self.assertEqual(result.stderr, None) self.assertEqual(captured.stdout.getvalue(), 'file1.txt\nfile2.txt\n')
def tags(self): tags = run([self.executable(), 'tag'], cwd=self.root_path, capture_output=True, encoding='utf-8') if tags.returncode: raise self.Exception('Failed to retrieve tag list for {}'.format( self.root_path)) return tags.stdout.splitlines()
def list(self, category): list_result = run([self.executable, 'list', '^/{}'.format(category)], cwd=self.path, capture_output=True, encoding='utf-8') if list_result.returncode: return [] return [ element.rstrip('/') for element in list_result.stdout.splitlines() ]
def checkout(self, argument): commit = self.find(argument) if not commit: return None command = [self.executable(), 'up', '-r', str(commit.revision)] if log.level > logging.WARNING: command.append('-q') return None if run(command, cwd=self.root_path).returncode else commit
def default_branch(self): result = run([self.executable(), 'rev-parse', '--abbrev-ref', 'origin/HEAD'], cwd=self.path, capture_output=True, encoding='utf-8') if result.returncode: candidates = self.branches if 'master' in candidates: return 'master' if 'main' in candidates: return 'main' return None return '/'.join(result.stdout.rstrip().split('/')[1:])
def _branch_for(self, revision): candidates = [ branch for branch, revisions in self._metadata_cache.items() if branch != 'version' and revision in revisions ] candidate = self.prioritize_branches( candidates) if candidates else None # In the default branch case, we don't even need to ask the remote if candidate == self.default_branch: return candidate process = run( [ self.executable(), 'log', '-v', '-q', self.remote(), '-r', str(revision), '-l', '1' ], cwd=self.root_path, capture_output=True, encoding='utf-8', ) # If we didn't get a valid answer from the remote, but we found a matching candidate, we return that. # This is a bit risky because there is a chance the branch we have cached is not the canonical branch # for a revision, but this is pretty unlikely because it would require the n + 1 level branch to be cached # but not the n level branch. if process.returncode or not process.stdout: if candidate: return candidate raise self.Exception( "Failed to retrieve branch for '{}'".format(revision)) partial = None for line in process.stdout.splitlines(): if partial is None and line == 'Changed paths:': partial = '' elif partial == '': partial = line.lstrip()[2:] elif partial: line = line.lstrip() while line.startswith( ('A ', 'D ', 'M ')) and not line[2:].startswith(partial): partial = partial[:-1] if len(partial) <= 3: raise self.Exception('Malformed set of edited files') partial = partial.split(' ')[0] candidate = partial.split( '/')[2 if partial.startswith('/branches') else 1] # Tags are a unique case for SVN, because they're treated as branches in native SVN if candidate == 'tags': return partial[1:].rstrip('/') return candidate
def main(cls, args, repository, subversion=None, **kwargs): if not repository.path: sys.stderr.write('Cannot setup git-svn on remote repository\n') return 1 if not repository.is_git: sys.stderr.write('Cannot setup git-svn on Subversion repository\n') return 1 if not subversion: sys.stderr.write( 'Failed to find Subversion remote: {}\n'.format(subversion)) return 1 print('Adding svn-remote to git config') config_path = os.path.join(repository.root_path, '.git', 'config') config_data = [] with open(config_path, 'r') as config: is_in_svn_remote = False for line in config.readlines(): if is_in_svn_remote and not line[:1].isspace(): is_in_svn_remote = False if line.startswith('[svn-remote "svn"]'): is_in_svn_remote = True if not is_in_svn_remote: config_data.append(line) with open(config_path, 'w') as config: for line in config_data: config.write(line) config.write('[svn-remote "svn"]\n') config.write('\turl = {}\n'.format(subversion)) config.write('\tfetch = trunk:refs/remotes/origin/{}\n'.format( repository.default_branch)) if args.all_branches: svn_remote = remote.Svn( url=subversion, dev_branches=repository.dev_branches, prod_branches=repository.prod_branches, contributors=repository.contributors, ) git_branches = repository.branches for branch in sorted(svn_remote.branches): if branch not in git_branches: continue config.write( '\tfetch = branches/{branch}:refs/remotes/origin/{branch}\n' .format(branch=branch)) print('Populating svn commit mapping (will take a few minutes)...') return run([ repository.executable(), 'svn', 'fetch', '--log-window-size=5000', '-r', '1:HEAD' ], cwd=repository.root_path).returncode
def _branches_for(self, hash=None): branch = run( [self.executable(), 'branch', '-a'] + (['--contains', hash] if hash else []), cwd=self.root_path, capture_output=True, encoding='utf-8', ) if branch.returncode: raise self.Exception('Failed to retrieve branch list for {}'.format(self.root_path)) result = [branch.lstrip(' *') for branch in filter(lambda branch: '->' not in branch, branch.stdout.splitlines())] return sorted(set(['/'.join(branch.split('/')[2:]) if branch.startswith('remotes/origin/') else branch for branch in result]))
def is_svn(self): try: return run( [self.executable(), 'svn', 'find-rev', 'r1'], cwd=self.root_path, capture_output=True, encoding='utf-8', timeout=1, ).returncode == 0 except TimeoutExpired: return False
def branches(self): branch = run([self.executable, 'branch', '-a'], cwd=self.root_path, capture_output=True, encoding='utf-8') if branch.returncode: raise self.Exception( 'Failed to retrieve branch list for {}'.format(self.root_path)) return [ branch.lstrip(' *') for branch in filter( lambda branch: '->' not in branch, branch.stdout.splitlines()) ]
def executable(cls, program): for candidate in ['/usr/bin', '/usr/bin/local']: candidate = os.path.join(candidate, program) if os.path.exists(candidate): return candidate which = run(['/usr/bin/which', program], capture_output=True, encoding='utf-8') if not which.returncode: return os.path.realpath(which.stdout.rstrip()) raise OSError("Cannot find '{}' program".format(program))
def info(self): if not self.is_svn: raise self.Exception('Cannot run SVN info on a git checkout which is not git-svn') info_result = run([self.executable(), 'svn', 'info'], cwd=self.path, capture_output=True, encoding='utf-8') if info_result.returncode: return {} result = {} for line in info_result.stdout.splitlines(): split = line.split(': ') result[split[0]] = ': '.join(split[1:]) return result
def info(self): info_result = run([self.executable, 'info'], cwd=self.path, capture_output=True, encoding='utf-8') if info_result.returncode: return {} result = {} for line in info_result.stdout.splitlines(): split = line.split(': ') result[split[0]] = ': '.join(split[1:]) return result