def email_commit(self, commit): """See AbstractUpdate.email_commit.""" 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") notes_contents = (None if notes.contents is None else indent( notes.contents, ' ' * 4)) # 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 = '[notes%s][%s] %s' % (subject_tag, self.email_info.project_name, 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)[1:] email_bcc = git_config('hooks.filer-email') email = Email(self.email_info, annotated_commit.email_to(self.ref_name), email_bcc, subject, body, commit.author, self.ref_name, commit.base_rev_for_display(), commit.rev, diff) email.enqueue()
def __get_lost_commits(self): """Return a list of CommitInfo objects lost after our update. RETURN VALUE A list of CommitInfo objects, or the empty list if the update did not cause any commit to be lost. """ if is_null_rev(self.old_rev): # We are creating a new reference, so we cannot possibly # be losing commits. return [] # The list of lost commits is computed by listing all commits # accessible from the old_rev, but not from any of the references. exclude = ['^%s' % self.all_refs[rev] for rev in self.all_refs.keys()] commit_list = commit_info_list(self.old_rev, *exclude) return commit_list
def __get_added_commits(self): """Return a list of CommitInfo objects added by our update. RETURN VALUE A list of CommitInfo objects, or the empty list if the update did not introduce any new commit. """ if is_null_rev(self.new_rev): return [] # Compute the list of commits that are not accessible from # any of the references. These are the commits which are # new in the repository. # # Note that we do not use the commit_info_list function for # that, because we only need the commit hashes, and a list # of commit hashes is more convenient for what we want to do # than a list of CommitInfo objects. exclude = [ '^%s' % self.all_refs[ref_name] for ref_name in self.all_refs.keys() if ref_name != self.ref_name ] if not is_null_rev(self.old_rev): exclude.append('^%s' % self.old_rev) new_repo_revs = git.rev_list(self.new_rev, *exclude, reverse=True, _split_lines=True) # If this is a reference creation (base_rev is null), try to # find a commit which can serve as base_rev. We try to find # a pre-existing commit making the base_rev..new_rev list # as short as possible. base_rev = self.old_rev if is_null_rev(base_rev): if len(new_repo_revs) > 0: # The ref update brings some new commits. The first # parent of the oldest of those commits, if it exists, # seems like a good candidate. If it does not exist, # we are pushing an entirely new headless branch, and # base_rev should remain null. parents = commit_parents(new_repo_revs[0]) if parents: base_rev = parents[0] else: # This reference update does not bring any new commits # at all. This means new_rev is already accessible # through one of the references, thus making it a good # base_rev as well. base_rev = self.new_rev # Expand base_rev..new_rev to compute the list of commits which # are new for the reference. If there is no actual base_rev # (Eg. a headless branch), then expand to all commits accessible # from that reference. if not is_null_rev(base_rev): commit_list = commit_info_list(self.new_rev, '^%s' % base_rev) base_rev = commit_rev(base_rev) else: commit_list = commit_info_list(self.new_rev) base_rev = None # Iterate over every commit, and set their pre_existing_p attribute. for commit in commit_list: commit.pre_existing_p = commit.rev not in new_repo_revs debug('update base: %s' % base_rev) return commit_list
def __get_added_commits(self): """Return a list of CommitInfo objects added by our update. RETURN VALUE A list of CommitInfo objects, or the empty list if the update did not introduce any new commit. """ if is_null_rev(self.new_rev): return [] # Compute the list of commits that are not accessible from # any of the references. These are the commits which are # new in the repository. # # Note that we do not use the commit_info_list function for # that, because we only need the commit hashes, and a list # of commit hashes is more convenient for what we want to do # than a list of CommitInfo objects. exclude = ['^%s' % self.all_refs[ref_name] for ref_name in self.all_refs.keys() if ref_name != self.ref_name] if not is_null_rev(self.old_rev): exclude.append('^%s' % self.old_rev) new_repo_revs = git.rev_list(self.new_rev, *exclude, reverse=True, _split_lines=True) # If this is a reference creation (base_rev is null), try to # find a commit which can serve as base_rev. We try to find # a pre-existing commit making the base_rev..new_rev list # as short as possible. base_rev = self.old_rev if is_null_rev(base_rev): if len(new_repo_revs) > 0: # The ref update brings some new commits. The first # parent of the oldest of those commits, if it exists, # seems like a good candidate. If it does not exist, # we are pushing an entirely new headless branch, and # base_rev should remain null. parents = commit_parents(new_repo_revs[0]) if parents: base_rev = parents[0] else: # This reference update does not bring any new commits # at all. This means new_rev is already accessible # through one of the references, thus making it a good # base_rev as well. base_rev = self.new_rev # Expand base_rev..new_rev to compute the list of commits which # are new for the reference. If there is no actual base_rev # (Eg. a headless branch), then expand to all commits accessible # from that reference. if not is_null_rev(base_rev): commit_list = commit_info_list(self.new_rev, '^%s' % base_rev) base_rev = commit_rev(base_rev) else: commit_list = commit_info_list(self.new_rev) base_rev = None # Iterate over every commit, and set their pre_existing_p attribute. for commit in commit_list: commit.pre_existing_p = commit.rev not in new_repo_revs debug('update base: %s' % base_rev) return commit_list
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, )