# -*- coding: utf-8 -*- # Copyright © 2014-2020 Puneeth Chaganti and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated # documentation files (the "Software"), to deal in the # Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the # Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice # shall be included in all copies or substantial portions of # the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS # OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """Deploy site to GitHub Pages.""" import os import subprocess from textwrap import dedent from nikola.plugin_categories import Command from nikola.plugins.command.check import real_scan_files from nikola.utils import req_missing, clean_before_deployment from nikola.__main__ import main from nikola import __version__ def uni_check_output(*args, **kwargs): """Run command and return output as Unicode (UTf-8).""" o = subprocess.check_output(*args, **kwargs) return o.decode('utf-8') def check_ghp_import_installed(): """Check if ghp-import is installed.""" try: subprocess.check_output(['ghp-import', '-h']) except OSError: # req_missing defaults to `python=True` — and it’s meant to be like this. # `ghp-import` is installed via pip, but the only way to use it is by executing the script it installs. req_missing(['ghp-import2'], 'deploy the site to GitHub Pages') class DeployFailedException(Exception): """An internal exception for deployment errors.""" pass class CommandGitHubDeploy(Command): """Deploy site to GitHub Pages.""" name = 'github_deploy' doc_usage = '[-m COMMIT_MESSAGE]' doc_purpose = 'deploy the site to GitHub Pages' doc_description = dedent( """\ This command can be used to deploy your site to GitHub Pages. It uses ghp-import to do this task. It also optionally commits to the source branch. Configuration help: https://getnikola.com/handbook.html#deploying-to-github""" ) cmd_options = [ { 'name': 'commit_message', 'short': 'm', 'long': 'message', 'default': 'Nikola auto commit.', 'type': str, 'help': 'Commit message', }, ] def _execute(self, options, args): """Run the deployment.""" # Check if ghp-import is installed check_ghp_import_installed() # Build before deploying build = main(['build']) if build != 0: self.logger.error('Build failed, not deploying to GitHub') return build # Clean non-target files only_on_output, _ = real_scan_files(self.site) for f in only_on_output: os.unlink(f) # Remove drafts and future posts if requested (Issue #2406) undeployed_posts = clean_before_deployment(self.site) if undeployed_posts: self.logger.warning("Deleted {0} posts due to DEPLOY_* settings".format(len(undeployed_posts))) # Commit and push return self._commit_and_push(options['commit_message']) def _run_command(self, command, xfail=False): """Run a command that may or may not fail.""" self.logger.info("==> {0}".format(command)) try: subprocess.check_call(command) return 0 except subprocess.CalledProcessError as e: if xfail: return e.returncode self.logger.error( 'Failed GitHub deployment -- command {0} ' 'returned {1}'.format(e.cmd, e.returncode) ) raise DeployFailedException(e.returncode) def _commit_and_push(self, commit_first_line): """Commit all the files and push.""" source = self.site.config['GITHUB_SOURCE_BRANCH'] deploy = self.site.config['GITHUB_DEPLOY_BRANCH'] remote = self.site.config['GITHUB_REMOTE_NAME'] autocommit = self.site.config['GITHUB_COMMIT_SOURCE'] try: if autocommit: commit_message = ( '{0}\n\n' 'Nikola version: {1}'.format(commit_first_line, __version__) ) e = self._run_command(['git', 'checkout', source], True) if e != 0: self._run_command(['git', 'checkout', '-b', source]) self._run_command(['git', 'add', '.']) # Figure out if there is anything to commit e = self._run_command(['git', 'diff-index', '--quiet', 'HEAD'], True) if e != 0: self._run_command(['git', 'commit', '-am', commit_message]) else: self.logger.info('Nothing to commit to source branch.') try: source_commit = uni_check_output(['git', 'rev-parse', source]) except subprocess.CalledProcessError: try: source_commit = uni_check_output(['git', 'rev-parse', 'HEAD']) except subprocess.CalledProcessError: source_commit = '?' commit_message = ( '{0}\n\n' 'Source commit: {1}' 'Nikola version: {2}'.format(commit_first_line, source_commit, __version__) ) output_folder = self.site.config['OUTPUT_FOLDER'] command = ['ghp-import', '-n', '-m', commit_message, '-p', '-r', remote, '-b', deploy, output_folder] self._run_command(command) if autocommit: self._run_command(['git', 'push', '-u', remote, source]) except DeployFailedException as e: return e.args[0] self.logger.info("Successful deployment")