def checkout_from_tag(self, version): package = self.name prefix = '%s-%s-' % (package, version) tagdir = self.prepare_checkout_dir(prefix) os.chdir(tagdir) cmd = self.cmd_checkout_from_tag(version, tagdir) print system(cmd)
def version_control(): """Return an object that provides the version control interface based on the detected version control system.""" curdir_contents = os.listdir('.') if '.svn' in curdir_contents: return svn.Subversion() elif '.hg' in curdir_contents: return hg.Hg() elif '.bzr' in curdir_contents: return bzr.Bzr() elif '.git' in curdir_contents: return git.Git() else: # Try finding an svn checkout *not* in the root. last_try = utils.system("svn info") if 'Repository' in last_try: return svn.Subversion() # true means that we are in the work tree, false that we are in the # .git tree. If we are not in a git repository, the answer will looks # like 'Not a git repository' or even 'git: not found' last_try = utils.system("git rev-parse --is-inside-work-tree") if last_try == 'true\n': return git.Git() logger.critical('No version control system detected.') sys.exit(1)
def get_setup_py_name(self): if os.path.exists('setup.py'): # First run egg_info, as that may get rid of some warnings # that otherwise end up in the extracted name, like # UserWarnings. system(utils.setup_py('egg_info')) return system(utils.setup_py('--name')).strip()
def _tags_name(self): """Return name for tags dir Normally the plural /tags, but some projects have the singular /tag. """ default_plural = 'tags' fallback_singular = 'tag' # svn 1.7 introduced a slightly different message and a warning code. failure_messages = ["non-existent in that revision", "W160013"] base = self._base_from_svn() tag_info = system('svn list %s%s' % (base, default_plural)) # Look for one of the failure messages: found = [1 for mess in failure_messages if mess in tag_info] if not found: return default_plural logger.debug("tags dir does not exist at %s%s", base, default_plural) tag_info = system('svn list %s%s' % (base, fallback_singular)) # Look for one of the failure messages: found = [1 for mess in failure_messages if mess in tag_info] if not found: return fallback_singular logger.debug("tags dir does not exist at %s%s, either", base, fallback_singular) return None
def available_tags(self): base = self._base_from_svn() tags_name = self._tags_name if tags_name is None: # Suggest to create a tags dir with the default plural /tags name. print "tags dir does not exist at %s" % base + 'tags' if utils.ask("Shall I create it"): cmd = 'svn mkdir %stags -m "Creating tags directory."' % (base) logger.info("Running %r", cmd) print system(cmd) tags_name = self._tags_name assert tags_name == 'tags' else: sys.exit(0) tag_info = system('svn list %s%s' % (base, tags_name)) if 'Could not resolve hostname' in tag_info or \ 'Repository moved' in tag_info: logger.error('Network problem: %s', tag_info) sys.exit() tags = [line.replace('/', '').strip() for line in tag_info.split('\n')] tags = [tag for tag in tags if tag] # filter empty ones logger.debug("Available tags: %r", tags) return tags
def available_tags(self): base = self._base_from_svn() tags_name = self._tags_name if tags_name is None: # Suggest to create a tags dir with the default plural /tags name. print "tags dir does not exist at %s" % base + 'tags' if utils.ask("Shall I create it"): cmd = 'svn mkdir %stags -m "Creating tags directory."' % (base) logger.info("Running %r", cmd) print system(cmd) tags_name = self._tags_name assert tags_name == 'tags' else: sys.exit(0) tag_info = system('svn list %s%s' % (base, tags_name)) if 'Could not resolve hostname' in tag_info or \ 'Repository moved' in tag_info or 'E670008' in tag_info: logger.error('Network problem: %s', tag_info) sys.exit() tags = [line.replace('/', '').strip() for line in tag_info.split('\n')] tags = [tag for tag in tags if tag] # filter empty ones logger.debug("Available tags: %r", tags) return tags
def _upload_distributions(self, package, sdist_options, pypiconfig): # See if creating an egg actually works. logger.info("Making an egg of a fresh tag checkout.") print system(utils.setup_py('sdist ' + sdist_options)) # First ask if we want to upload to pypi, which should always # work, also without collective.dist. use_pypi = package_in_pypi(package) if use_pypi: logger.info("This package is registered on PyPI.") else: logger.warn("This package is NOT registered on PyPI.") if pypiconfig.is_old_pypi_config(): pypi_command = 'register sdist %s upload' % sdist_options shell_command = utils.setup_py(pypi_command) if use_pypi: default = True exact = False else: # We are not yet on pypi. To avoid an 'Oops..., # sorry!' when registering and uploading an internal # package we default to False here. default = False exact = True if utils.ask("Register and upload to PyPI", default=default, exact=exact): logger.info("Running: %s", shell_command) result = system(shell_command) utils.show_first_and_last_lines(result) # If collective.dist is installed (or we are using # python2.6 or higher), the user may have defined # other servers to upload to. for server in pypiconfig.distutils_servers(): if pypi.new_distutils_available(): commands = ('register', '-r', server, 'sdist', sdist_options, 'upload', '-r', server) else: ## This would be logical, given the lines above: #commands = ('mregister', '-r', server, 'sdist', # sdist_options, 'mupload', '-r', server) ## But according to the collective.dist documentation ## it should be this (with just one '-r'): commands = ('mregister', 'sdist', sdist_options, 'mupload', '-r', server) shell_command = utils.setup_py(' '.join(commands)) default = True exact = False if server == 'pypi' and not use_pypi: # We are not yet on pypi. To avoid an 'Oops..., # sorry!' when registering and uploading an internal # package we default to False here. default = False exact = True if utils.ask("Register and upload to %s" % server, default=default, exact=exact): logger.info("Running: %s", shell_command) result = system(shell_command) utils.show_first_and_last_lines(result)
def get_setup_py_version(self): if os.path.exists('setup.py'): # First run egg_info, as that may get rid of some warnings # that otherwise end up in the extracted version, like # UserWarnings. system(utils.setup_py('egg_info')) version = system(utils.setup_py('--version')) return utils.strip_version(version)
def checkout_from_tag(self, version): package = self.name prefix = '%s-%s-' % (package, version) # Not all hg versions can do a checkout in an existing or even # just in the current directory. tagdir = tempfile.mktemp(prefix=prefix) cmd = self.cmd_checkout_from_tag(version, tagdir) print system(cmd) os.chdir(tagdir)
def prepare_checkout_dir(self, prefix): # Watch out: some git versions can't clone into an existing # directory, even when it is empty. temp = tempfile.mkdtemp(prefix=prefix) cwd = os.getcwd() os.chdir(temp) cmd = 'git clone %s %s' % (self.workingdir, 'gitclone') logger.debug(system(cmd)) clonedir = os.path.join(temp, 'gitclone') os.chdir(clonedir) cmd = 'git submodule update --init --recursive' logger.debug(system(cmd)) os.chdir(cwd) return clonedir
def _check_if_tag_already_exists(self): """Check if tag already exists and show the difference if so""" version = self.data['version'] if self.vcs.tag_exists(version): self.data['tag_already_exists'] = True q = ("There is already a tag %s, show " "if there are differences?" % version) if utils.ask(q): diff_command = self.vcs.cmd_diff_last_commit_against_tag( version) print diff_command print system(diff_command) else: self.data['tag_already_exists'] = False
def is_clean_checkout(self): """Is this a clean checkout? """ head = system('git symbolic-ref --quiet HEAD') # This returns something like 'refs/heads/maurits-warn-on-tag' # or nothing. Nothing would be bad as that indicates a # detached head: likely a tag checkout if not head: # Greetings from Nearly Headless Nick. return False if system('git status --short --untracked-files=no'): # Uncommitted changes in files that are tracked. return False return True
def get_setup_py_version(self): if os.path.exists('setup.py'): # First run egg_info, as that may get rid of some warnings # that otherwise end up in the extracted version, like # UserWarnings. system(utils.setup_py('egg_info')) version = system(utils.setup_py('--version')).splitlines()[0] if version.startswith('Traceback'): # Likely cause is for example forgetting to 'import # os' when using 'os' in setup.py. logger.critical('The setup.py of this package has an error:') print version logger.critical('No version found.') sys.exit(1) return utils.strip_version(version)
def get_setup_py_version(self): if os.path.exists("setup.py"): # First run egg_info, as that may get rid of some warnings # that otherwise end up in the extracted version, like # UserWarnings. system(utils.setup_py("egg_info")) version = system(utils.setup_py("--version")) if version.startswith("Traceback"): # Likely cause is for example forgetting to 'import # os' when using 'os' in setup.py. logger.critical("The setup.py of this package has an error:") print version logger.critical("No version found.") sys.exit(1) return utils.strip_version(version)
def available_tags(self): tag_info = system('hg tags') tags = [line[:line.find(' ')] for line in tag_info.split('\n')] tags = [tag for tag in tags if tag] tags.remove('tip') # Not functional for us logger.debug("Available tags: %r", tags) return tags
def main(): logging.basicConfig(level=utils.loglevel(), format="%(levelname)s: %(message)s") vcs = zest.releaser.choose.version_control() if len(sys.argv) > 1: found = sys.argv[-1] else: found = utils.get_last_tag(vcs) name = vcs.name full_tag = vcs.tag_url(found) logger.debug("Picked tag %r for %s (currently at %r).", full_tag, name, vcs.version) logger.info("Showing log since tag %s and the last commit.", full_tag) log_command = vcs.cmd_log_since_tag(found) print log_command print system(log_command)
def _diff_and_commit(self): diff_cmd = self.vcs.cmd_diff() diff = system(diff_cmd) if sys.version.startswith('2.6.2'): # python2.6.2 bug... http://bugs.python.org/issue5170 This is the # spot it can surface as we show a part of the changelog which can # contain every kind of character. The rest is mostly ascii. print "Diff results:" print diff else: # Common case logger.info("The '%s':\n\n%s\n" % (diff_cmd, diff)) if utils.ask("OK to commit this"): msg = self.data['commit_msg'] % self.data commit_cmd = self.vcs.cmd_commit(msg) commit = system(commit_cmd) logger.info(commit)
def is_clean_checkout(self): """Is this a clean checkout? """ # The --quiet option ignores untracked (unknown and ignored) # files, which seems reasonable. if system('hg status --quiet'): # Local changes. return False return True
def _push(self): """Offer to push changes, if needed.""" push_cmds = self.vcs.push_commands() if not push_cmds: return if utils.ask("OK to push commits to the server?"): for push_cmd in push_cmds: output = system(push_cmd) logger.info(output)
def _svn_info(self): """Return svn url""" our_info = system('svn info') if not hasattr(self, '_cached_url'): url = [line for line in our_info.split('\n') if line.startswith('URL')][0] # In English, you have 'URL:', in French 'URL :' self._cached_url = url.split(':', 1)[1].strip() return self._cached_url
def main(): vcs = choose.version_control() changelogs = Changelogs(vcs) last_tag = utils.get_last_tag(vcs) git_command = vcs.cmd_log_since_tag(last_tag) changes = GitChanges(utils.system(git_command)) print 'There are {0} commit(s) for {1} line(s) in changelog'.format( len(changes.commits), len(changelogs.last_version.logs), )
def _make_tag(self): if self.data['tag_already_exists']: return cmds = self.vcs.cmd_create_tag(self.data['version']) if not isinstance(cmds, list): cmds = [cmds] if len(cmds) == 1: print "Tag needed to proceed, you can use the following command:" for cmd in cmds: print cmd if utils.ask("Run this command"): print system(cmd) else: # all commands are needed in order to proceed normally print "Please create a tag for %s yourself and rerun." % \ (self.data['version'],) sys.exit() if not self.vcs.tag_exists(self.data['version']): print "\nFailed to create tag %s!" % (self.data['version'],) sys.exit()
def show_longdesc(): filename1 = tempfile.mktemp() filename2 = tempfile.mktemp() filename2 = filename2 + '.html' # Note: for the setup.py call we use system() from our utils module. This # makes sure the python path is set up right. # For the other calls we use os.system(), because that returns an error # code which we need. system(utils.setup_py('--long-description > %s' % filename1)) error = os.system('rst2html.py %s > %s' % (filename1, filename2)) if error: # On Linux it needs to be 'rst2html', without the '.py' error = os.system('rst2html %s > %s' % (filename1, filename2)) if error: # Alternatively, zc.rst2 provides rst2 xyz. error = os.system('rst2 html %s > %s' % (filename1, filename2)) if error: logging.error( 'Error generating html. Please install docutils (or zc.rst2).') sys.exit() url = 'file://' + filename2 logging.info("Opening %s in your webbrowser.", url) webbrowser.open(url)
def is_clean_checkout(self): """Is this a clean checkout? When you try to do commits in bazaar but you are on a tag you will get this error: "working tree is out of date, run 'bzr update'" That should be clear enough already. Well, we can run 'bzr status' and see what we get. """ # Check for changes to versioned files. if system('bzr status --versioned'): # Local changes. return False return True
def cmd_log_since_tag(self, version): """Return log since a tagged version till the last commit of the working copy. """ url = self._svn_info() tag_url = self.tag_url(version) tag_info = system('svn info %s' % tag_url) # Search for: Last Changed Rev: 42761 revision = None for line in tag_info.split('\n'): line = line.lower() if len(line.split(':')) == 2: revision = line.split(':')[-1].strip() if not revision: logger.error('Could not find revision when tag was made: %s', tag_info) sys.exit() return "svn --non-interactive log -r%s:HEAD %s" % (revision, url)
def version_control(): """Return an object that provides the version control interface based on the detected version control system.""" curdir_contents = os.listdir('.') if '.svn' in curdir_contents: return svn.Subversion() elif '.hg' in curdir_contents: return hg.Hg() elif '.bzr' in curdir_contents: return bzr.Bzr() elif '.git' in curdir_contents: return git.Git() else: # Try finding an svn checkout *not* in the root. last_try = utils.system("svn info") if 'Repository' in last_try: return svn.Subversion() logger.critical('No version control system detected.') sys.exit(1)
def _check_travis(data): # pragma: no cover """Check if Travis reports that everything is ok. (used to perform checks before releasing a new version). """ import json import sys from zest.releaser.utils import system, ask if not ask('Check with Travis before releasing?'): return try: # Python 3 from urllib.request import urlopen def get(url): return urlopen(url).read().decode('utf-8') except ImportError: # Python 2 from urllib2 import urlopen def get(url): return urlopen(url).read() url = 'https://api.github.com/repos/%s/%s/status/%s' username = '******' repo = 'pint' commit = system('git rev-parse HEAD') try: result = json.loads(get(url % (username, repo, commit)))['state'] print('Travis says: %s' % result) if result != 'success': if not ask('Do you want to continue anyway?', default=False): sys.exit(1) except Exception: print('Could not determine the commit state with Travis.') if ask('Do you want to continue anyway?', default=False): sys.exit(1)
def available_tags(self): tag_info = system('bzr tags') tags = [line[:line.find(' ')] for line in tag_info.split('\n')] tags = [tag for tag in tags if tag] logger.debug("Available tags: %r", tags) return tags
def setup(test): partstestdir = os.getcwd() # Buildout's test run in parts/test test.orig_dir = partstestdir test.tempdir = tempfile.mkdtemp(prefix='testtemp') test.orig_argv = sys.argv[1:] sys.argv[1:] = [] # Monkey patch sys.exit test.orig_exit = sys.exit def _exit(code=None): msg = "SYSTEM EXIT (code=%s)" % code raise RuntimeError(msg) sys.exit = _exit # Monkey patch urllib for pypi access mocking. test.orig_urlopen = urllib2.urlopen test.mock_pypi_available = [] def _mock_urlopen(url): #print "Mock opening", url package = url.replace('http://pypi.python.org/simple/', '') if package not in test.mock_pypi_available: raise urllib2.HTTPError( url, 404, 'HTTP Error 404: Not Found (%s does not have any releases)' % package, None, None) else: answer = ' '.join(test.mock_pypi_available) return StringIO.StringIO(buf=answer) urllib2.urlopen = _mock_urlopen # Extract example project example_tar = pkg_resources.resource_filename('zest.releaser.tests', 'example.tar') tf = tarfile.TarFile(example_tar) try: tf.extractall(path=test.tempdir) except AttributeError: # BBB for python2.4 for name in tf.getnames(): tf.extract(name, test.tempdir) sourcedir = os.path.join(test.tempdir, 'tha.example') # Init svn repo. repodir = os.path.join(test.tempdir, 'svnrepo') system('svnadmin create %s' % repodir) repo_url = 'file://' + repodir # TODO: urllib or so for windows # Import example project system('svn mkdir %s/tha.example -m "mkdir"' % repo_url) system('svn mkdir %s/tha.example/tags -m "mkdir"' % repo_url) system('svn import %s %s/tha.example/trunk -m "import"' % (sourcedir, repo_url)) # Subversion checkout svnsourcedir = os.path.join(test.tempdir, 'tha.example-svn') system('svn co %s/tha.example/trunk %s' % (repo_url, svnsourcedir)) system('svn propset svn:ignore tha.example.egg-info %s/src ' % svnsourcedir) system('svn up %s' % svnsourcedir) system('svn commit %s -m "ignoring egginfo"' % svnsourcedir) # Mercurial initialization hgsourcedir = os.path.join(test.tempdir, 'tha.example-hg') shutil.copytree(sourcedir, hgsourcedir) system("hg init %s" % hgsourcedir) open(os.path.join(hgsourcedir, '.hgignore'), 'wb').write('tha.example.egg-info\n') system("hg add %s" % hgsourcedir) system("hg commit -m 'init' %s" % hgsourcedir) # Bazaar initialization bzrsourcedir = os.path.join(test.tempdir, 'tha.example-bzr') shutil.copytree(sourcedir, bzrsourcedir) system("bzr init %s" % bzrsourcedir) open(os.path.join(bzrsourcedir, '.bzrignore'), 'w').write('tha.example.egg-info\n') system("bzr add %s" % bzrsourcedir) system("bzr commit -m 'init' %s" % bzrsourcedir) # Git initialization gitsourcedir = os.path.join(test.tempdir, 'tha.example-git') shutil.copytree(sourcedir, gitsourcedir) os.chdir(gitsourcedir) system("git init") open(os.path.join(gitsourcedir, '.gitignore'), 'w').write('tha.example.egg-info\n') system("git add .") system("git commit -a -m 'init'") os.chdir(test.orig_dir) # Git svn initialization gitsvnsourcedir = os.path.join(test.tempdir, 'tha.example-gitsvn') system('git svn clone -s %s/tha.example %s' % (repo_url, gitsvnsourcedir)) os.chdir(test.orig_dir) def svnhead(*filename_parts): filename = os.path.join(svnsourcedir, *filename_parts) lines = open(filename).readlines() for line in lines[:5]: print line, def hghead(*filename_parts): filename = os.path.join(hgsourcedir, *filename_parts) lines = open(filename).readlines() for line in lines[:5]: print line, def bzrhead(*filename_parts): filename = os.path.join(bzrsourcedir, *filename_parts) lines = open(filename).readlines() for line in lines[:5]: print line, def githead(*filename_parts): filename = os.path.join(gitsourcedir, *filename_parts) lines = open(filename).readlines() for line in lines[:5]: print line, test.globs.update({ 'tempdir': test.tempdir, 'repo_url': repo_url, 'svnsourcedir': svnsourcedir, 'hgsourcedir': hgsourcedir, 'bzrsourcedir': bzrsourcedir, 'gitsourcedir': gitsourcedir, 'gitsvnsourcedir': gitsvnsourcedir, 'svnhead': svnhead, 'hghead': hghead, 'bzrhead': bzrhead, 'githead': githead, 'mock_pypi_available': test.mock_pypi_available, })
def list_files(self): """List files in version control.""" return system('bzr ls --recursive').splitlines()
def available_tags(self): tag_info = system('git tag') tags = [line for line in tag_info.split('\n') if line] logger.debug("Available tags: %r", tags) return tags
def list_files(self): """List files in version control.""" return system('git ls-tree -r HEAD --name-only').splitlines()
def cmd_log_since_tag(self, version): current_revision = system('hg identify') current_revision = current_revision.split(' ')[0] # + at the end of the revision denotes uncommitted changes current_revision = current_revision.rstrip('+') return "hg log -r %s -r %s" % (version, current_revision)
def list_files(self): """List files in version control.""" return system('hg manifest').splitlines()
def _release(self): """Upload the release, when desired""" if utils.TESTMODE: pypirc_old = pkg_resources.resource_filename( 'zest.releaser.tests', 'pypirc_old.txt') pypiconfig = pypi.PypiConfig(pypirc_old) else: pypiconfig = pypi.PypiConfig() # Does the user normally want a real release? We are # interested in getting a sane default answer here, so you can # override it in the exceptional case but just hit Enter in # the usual case. main_files = os.listdir(self.data['workingdir']) if 'setup.py' not in main_files and 'setup.cfg' not in main_files: # Neither setup.py nor setup.cfg, so this is no python # package, so at least a pypi release is not useful. # Expected case: this is a buildout directory. default_answer = False else: default_answer = pypiconfig.want_release() if not utils.ask("Check out the tag (for tweaks or pypi/distutils " "server upload)", default=default_answer): return package = self.vcs.name version = self.data['version'] logger.info("Doing a checkout...") self.vcs.checkout_from_tag(version) self.data['tagdir'] = os.path.realpath(os.getcwd()) logger.info("Tag checkout placed in %s", self.data['tagdir']) # Possibly fix setup.cfg. if self.setup_cfg.has_bad_commands(): logger.info("This is not advisable for a release.") if utils.ask("Fix %s (and commit to tag if possible)" % self.setup_cfg.config_filename, default=True): # Fix the setup.cfg in the current working directory # so the current release works well. self.setup_cfg.fix_config() # Now we may want to commit this. Note that this is # only really useful for subversion, as for example in # git you are in a detached HEAD state, which is a # place where a commit will be lost. # # Ah, in the case of bazaar doing a commit is actually # harmful, as the commit ends up on the tip, instead # of only being done on a tag or branch. # # So the summary is: # # - svn: NEEDED, not harmful # - git: not needed, not harmful # - hg: not needed, not harmful # - bzr: not needed, HARMFUL # # So for clarity and safety we should only do this for # subversion. if self.vcs.internal_filename == '.svn': command = self.vcs.cmd_commit( "Fixed %s on tag for release" % self.setup_cfg.config_filename) print system(command) else: logger.debug("Not committing in non-svn repository as " "this is not needed or may be harmful.") sdist_options = self._sdist_options() # Run extra entry point self._run_hooks('after_checkout') if 'setup.py' in os.listdir(self.data['tagdir']): if not pypiconfig.config: logger.warn("You must have a properly configured %s file in " "your home dir to upload an egg.", pypi.DIST_CONFIG_FILE) else: self._upload_distributions(package, sdist_options, pypiconfig) # Make sure we are in the expected directory again. os.chdir(self.vcs.workingdir)
def list_files(self): """List files in version control.""" return system('svn ls --recursive').splitlines()
def cmd_diff_last_commit_against_tag(self, version): current_revision = system('hg identify') current_revision = current_revision.split(' ')[0] # + at the end of the revision denotes uncommitted changes current_revision = current_revision.rstrip('+') return "hg diff -r %s -r %s" % (version, current_revision)