def publish_tag(version, ref, clone_dir): ctx_obj = click.get_current_context().obj ctx_obj.version = version ref = try_context(ctx_obj, ref, "ref", "rc_ref") clone_dir = try_context(ctx_obj, clone_dir, "clone_dir", "clone_dir") repo = git.Repo(clone_dir) repo.create_tag(version, ref, message=version) repo.remotes.origin.push(version) logger.info( "Tag '{tag}' successfully pushed to repository.".format(tag=version))
def create_release(repo, version, bodyfile): ctx_obj = click.get_current_context().obj # Attempt to read release_notes from context # They may have been set by release.generate_release_notes try: with open(bodyfile, "r") as bodyfile_open: release_notes = bodyfile_open.read() except IOError as e: logger.error("Failed to open release notes file: {f} {e}".format( f=bodyfile, e=e)) click.get_current_context().exit(-1) version = try_context(ctx_obj, version, "version", "version") # Store version in context for use in notifications ctx_obj.version = version try: release = repo.create_release( tag_name=version, name=version, body=release_notes, ) logger.info("Release {} created.".format(version)) except github3.models.GitHubError as e: logger.error("Error creating release: {}".format(e)) if e.code == 422: logger.error("Failed to create release, tag already exists?") raise SystemExit(5) if e.code == 404: logger.error("Failed to create release, Jenkins lacks repo perms?") raise SystemExit(6) else: raise e else: ctx_obj.release_url = release.html_url
def clone(url, ref, refspec): ctx_obj = click.get_current_context().obj url = try_context(ctx_obj, url, "url", "ssh_url") ctx_obj.rc_ref = ref clone_dir = "{o}/{r}".format( o=ctx_obj.owner.login, r=ctx_obj.name ) ctx_obj.clone_dir = clone_dir logging.debug("Cloning {url}@{ref} to {dir}".format( url=url, ref=ref, dir=clone_dir)) repo = git.Repo.init(clone_dir) try: origin = repo.remotes.origin origin.set_url(url) except Exception as e: origin = repo.create_remote('origin', url) repo.git.fetch(["-u", "-v", "-f", url] + refspec.split()) try: getattr(origin.refs, ref).checkout() except AttributeError as e: logging.error("Ref {ref} not found in {url}".format( ref=ref, url=url )) raise e logging.debug("Clone complete, current ref: {sha}/{message}".format( sha=repo.head.commit.hexsha, message=repo.head.commit.message ))
def publish_tag(version, ref, clone_dir): ctx_obj = click.get_current_context().obj ctx_obj.version = version ref = try_context(ctx_obj, ref, "ref", "rc_ref") clone_dir = try_context(ctx_obj, clone_dir, "clone_dir", "clone_dir") repo = git.Repo(clone_dir) refsha = repo.rev_parse(ref).hexsha tagsha = None try: tagsha = repo.tags[version].commit.hexsha except IndexError: pass if tagsha is not None: # tag exists if tagsha == refsha: # Existing tag SHA matches ref logging.info("Tag '{tag}' already exists and the ref matches," "nothing to do.".format(tag=version)) else: # Existing tag SHA doesn't not match ref if ctx_obj.re_release: # Overwrite flag is set so we remove the existing tag # and recreate it pointing to ref repo.delete_tag(version) repo.remote().push(":{tag}".format(tag=version)) logging.info("Tag '{tag}@{tagsha}' removed.".format( tag=version, tagsha=tagsha)) _publish_tag(repo, version, ref) else: # Tag exists, doesn't match and overwrite flag not set, bail. raise ReleaseFailureException( "Trying to create tag {tag}@{refsha}" " but {tag}@{tagsha} already exists." "Failing because re release was not specified.".format( tag=version, refsha=refsha, tagsha=tagsha)) else: # Tag doesn't exist, create it. _publish_tag(repo, version, ref)
def create_release(repo, version, ref, bodyfile): ctx_obj = click.get_current_context().obj # Attempt to read release_notes from context # They may have been set by release.generate_release_notes try: with open(bodyfile, "r") as bodyfile_open: release_notes = bodyfile_open.read() except IOError as e: logger.error("Failed to open release notes file: {f} {e}".format( f=bodyfile, e=e )) click.get_current_context().exit(-1) ref = try_context(ctx_obj, ref, "ref", "rc_ref") # Store version in context for use in notifications ctx_obj.version = version # Create a subject for use by notifications ctx_obj.release_subject = "Version {v} of {o}/{r} released".format( v=version, o=repo.owner.login, r=repo.name ) try: repo.create_release( version, # tag name ref, # tag reference version, # release name release_notes # release body ) logger.info("Release {} created.".format(version)) except github3.models.GitHubError as e: logger.error("Error creating release: {}".format(e)) if e.code == 422: logger.error("Failed to create release, tag already exists?") raise SystemExit(5) if e.code == 404: logger.error("Failed to create release, Jenkins lacks repo perms?") raise SystemExit(6) else: raise e
def create_release(repo, version, bodyfile): # Attempt to read release_notes from context # They may have been set by release.generate_release_notes try: with open(bodyfile, "r") as bodyfile_open: release_notes = bodyfile_open.read() except IOError as e: logging.error("Failed to open release notes file: {f} {e}".format( f=bodyfile, e=e )) click.get_current_context().exit(-1) version = try_context(repo, version, "version", "version") # Store version in context for use in notifications repo.version = version try: release = [r for r in repo.iter_releases() if r.name == version][0] repo.release_url = release.html_url except IndexError: release = _create_release(repo, version, release_notes) else: if release.body != release_notes: if repo.re_release: release.delete() # doesn't remove tag # (but tag should have been recreated if necessary by now) logging.info("Release {} removed." .format(version)) release = _create_release(repo, version, release_notes) else: raise ReleaseFailureException( "Release failed. Github release {version}" " already exists but its release notes" " don't match the release notes" " currently being supplied." .format(version=version) ) else: logging.info("Release {} already exists, not creating." .format(version))
def update_rc_branch(ctx, mainline, rc): """Update rc branch. 1. Store branch protection data 2. Delete rc branch 3. Create rc branch from head of mainline 4. Enable branch protection with skeleton or previously stored settings. """ repo = ctx.obj rc = try_context(repo, rc, "rc", "rc_ref") if mainline == rc: raise ValueError("Specifying the same branch for mainline and rc" " will result in dataloss. The mainline branch" " will be deleted, then the rc branch will be" " created from the now non-existent mainline branch") branch_protection_enabled = False # check if branch exists if rc in (b.name for b in repo.iter_branches()): logger.debug("Branch {} exists".format(rc)) # rc branch exists branch_protection_response = branch_api_request(repo, rc, 'GET') if branch_protection_response.status_code == 200: # rc branch exists and protection enabled logger.debug("Branch {branch} has protection enabled," " config: {bp_config}".format( branch=rc, bp_config=branch_protection_response.json())) branch_protection_enabled = True # disable branch protection r = branch_api_request(repo, rc, 'DELETE') r.raise_for_status() logger.debug("Branch protection disabled") elif branch_protection_response.status_code == 404: # rc branch exists without protection, so it doesn't need # to be disabled # TODO: create jira issue about unprotected branch? pass else: # failure retrieving branch protection status branch_protection_response.raise_for_status() # Delete branch r = repo._session.request( 'DELETE', repo.git_refs_urlt.expand(sha="heads/{}".format(rc))) r.raise_for_status() logger.debug("Branch {} deleted".format(rc)) mainline_sha = repo.branch(mainline).commit.sha logger.debug("Mainline SHA: {}".format(mainline_sha)) # create rc branch pointing at head of mainline repo.create_ref("refs/heads/{}".format(rc), mainline_sha) logger.debug("Branch {} created".format(rc)) # Skeleton branch protection data, used to protect a new branch. protection_data = { "required_status_checks": None, "enforce_admins": True, "required_pull_request_reviews": { "dismissal_restrictions": {}, "dismiss_stale_reviews": False, "require_code_owner_reviews": False }, "restrictions": None } # Incorporate previous branch protection data if the branch was # protected perviously if branch_protection_enabled: stored_bpd = branch_protection_response.json() protection_data.update(stored_bpd) # The github api returns enforce_admins as dict, but requires it to # be sent as a bool. protection_data['enforce_admins'] \ = stored_bpd['enforce_admins']['enabled'] # Enable branch protection r = branch_api_request(repo, rc, 'PUT', data=json.dumps(protection_data)) r.raise_for_status() logger.debug("Branch Protection enabled for branch {}".format(rc)) # Ensure the rc branch was not updated to anything else while it was # unprotected. Stored mainline_sha is used incase mainline has # moved on since the SHA was acquired. assert mainline_sha == repo.branch(rc).commit.sha logger.debug("rc branch update complete")
def generate_release_notes(scripts, rnfile, text, version, prev_version, clone_dir, dst_file): ctx_obj = click.get_current_context().obj clone_dir = try_context(ctx_obj, clone_dir, "clone_dir", "clone_dir") os.system("mkdir -p {}".format(os.path.dirname(dst_file))) if scripts: version = try_context(ctx_obj, version, "version", "version") logging.debug( "Generating release notes from scripts: {}".format(scripts)) sub_env = os.environ.copy() sub_env["RE_HOOK_PREVIOUS_VERSION"] = prev_version.encode('ascii') sub_env["RE_HOOK_VERSION"] = version.encode('ascii') sub_env["RE_HOOK_RELEASE_NOTES"] = dst_file.encode('ascii') sub_env["RE_HOOK_REPO_HTTP_URL"] = ctx_obj.clone_url.encode('ascii') script_work_dir = "{cwd}/{clone_dir}".format(cwd=os.getcwd(), clone_dir=clone_dir) logging.debug("script work dir: {}".format(script_work_dir)) logging.debug("script env: {}".format(sub_env)) for script in scripts: try: optional = False if script.startswith("optional:"): script = script.replace("optional:", "") optional = True script_path = "{script_work_dir}/{script}".format( script_work_dir=script_work_dir, script=script) if os.path.exists(script_path): logging.debug("Executing script: {}".format(script_path)) proc = subprocess.Popen(script_path, env=sub_env, cwd=script_work_dir) proc.communicate() logging.debug( "Script Execution Complete: {}".format(script_path)) if proc.returncode != 0: logging.error( "Script {s} failed. (Return Code: {rc})".format( s=script_path, rc=proc.returncode)) click.get_current_context().exit(-1) else: if not optional: logging.error( "Aborting as required hook script not found: {}". format(script_path)) click.get_current_context().exit(-1) else: logging.info( "Optional hook script not found: {}".format( script_path)) except Exception as e: logging.error(traceback.format_exc()) logging.error("Failed to generate release notes: {}".format(e)) click.get_current_context().exit(-1) if not os.path.exists(dst_file): logging.error("Scripts did not generate release notes or did not" " place them in" " $WORKSPACE/{df}".format(df=dst_file)) click.get_current_context().exit(-1) elif rnfile: # Release notes are already in a file, just copy them to where # they need to be logging.debug("Reading release notes from file: {}".format(rnfile)) return_code = subprocess.check_call( ["cp", pipes.quote(rnfile), dst_file]) if return_code != 0: logging.error( "Failed to read release notes file: {}".format(rnfile)) click.get_current_context().exit(-1) elif text: logging.debug("Release notes supplied inline: {}".format(text)) with open(dst_file, "w") as out: out.write(text) else: logging.error("One of script, text or file must be specified. " "No release notes generated") click.get_current_context().exit(-1)