def all_refs(split=False, git=git): """Return a tuple of (local branches, remote branches, tags).""" local_branches = [] remote_branches = [] tags = [] triple = lambda x, y: (x, len(x) + 1, y) query = (triple('refs/tags', tags), triple('refs/heads', local_branches), triple('refs/remotes', remote_branches)) cmdout = core.decode(git.for_each_ref(format='%(refname)')) for ref in cmdout.splitlines(): for prefix, prefix_len, dst in query: if ref.startswith(prefix) and not ref.endswith('/HEAD'): dst.append(ref[prefix_len:]) continue if split: return local_branches, remote_branches, tags else: return local_branches + remote_branches + tags
def for_each_ref_basename(refs, git=git): """Return refs starting with 'refs'.""" git_output = git.for_each_ref(refs, format='%(refname)') output = core.decode(git_output).splitlines() non_heads = filter(lambda x: not x.endswith('/HEAD'), output) return map(lambda x: x[len(refs) + 1:], non_heads)
def get_standard_commit_email(self, commit): """See AbstractUpdate.get_standard_commit_email.""" notes = GitNotes(commit.rev) # Get commit info for the annotated commit annotated_commit = commit_info_list("-1", notes.annotated_rev)[0] # Get a description of the annotated commit (a la "git show"), # except that we do not want the diff. # # Also, we have to handle the notes manually, as the commands # get the notes from the HEAD of the notes/commits branch, # whereas what we needs is the contents at the commit.rev. # This makes a difference when a single push updates the notes # of the same commit multiple times. annotated_rev_log = git.log(annotated_commit.rev, no_notes=True, max_count="1", _decode=True) notes_contents = (None if notes.contents is None else indent( notes.contents, " " * 4)) # Get the list of references the annotated commit is contained in. annotated_commit_ref_names = git.for_each_ref( contains=annotated_commit.rev, format="%(refname)", _decode=True, _split_lines=True, ) # Strip from that list all the references which are to be ignored # (typically, those are internal references). annotated_commit_ref_names = [ ref_name for ref_name in annotated_commit_ref_names if search_config_option_list("hooks.ignore-refs", ref_name) is None ] subject_prefix = commit_email_subject_prefix( project_name=self.email_info.project_name, ref_names=annotated_commit_ref_names, ) # Determine subject tag based on ref name: # * remove "refs/notes" prefix # * remove entire tag if remaining component is "commits" # (case of the default refs/notes/commits ref) notes_ref = self.ref_name.split("/", 2)[2] if notes_ref == "commits": subject_tag = "" else: subject_tag = "(%s)" % notes_ref subject = f"[notes{subject_tag}]{subject_prefix} {annotated_commit.subject}" body_template = (DELETED_NOTES_COMMIT_EMAIL_BODY_TEMPLATE if notes_contents is None else UPDATED_NOTES_COMMIT_EMAIL_BODY_TEMPLATE) body = body_template % { "annotated_rev_log": annotated_rev_log, "notes_contents": notes_contents, } # Git commands calls strip on the output, which is usually # a good thing, but not in the case of the diff output. # Prevent this from happening by putting an artificial # character at the start of the format string, and then # by stripping it from the output. diff = git.show(commit.rev, pretty="format:|", p=True, _decode=True)[1:] refs_containing_annotated_commit_section = ( REFS_CONTAINING_ANNOTATED_COMMIT_TEMPLATE.format( annotated_commit_references="\n".join([ f" {ref_name}" for ref_name in annotated_commit_ref_names ]))) email_bcc = git_config("hooks.filer-email") return Email( self.email_info, annotated_commit.email_to(self.ref_name), email_bcc, subject, body, commit.full_author_email, self.ref_name, commit.base_rev_for_display(), commit.rev, # Place the refs_containing_annotated_commit_section inside # the "Diff:" section to avoid having that section trigger # some unexpected filing. refs_containing_annotated_commit_section + diff, )