def UpdateChangeLog( pkgdir, user, msg, skel_path, category, package, new=(), removed=(), changed=(), pretend=False, quiet=False): """ Write an entry to an existing ChangeLog, or create a new one. Updates copyright year on changed files, and updates the header of ChangeLog with the contents of skel.ChangeLog. """ if '<root@' in user: if not quiet: logging.critical('Please set ECHANGELOG_USER or run as non-root') return None # ChangeLog times are in UTC gmtime = time.gmtime() year = time.strftime('%Y', gmtime) date = time.strftime('%d %b %Y', gmtime) # check modified files and the ChangeLog for copyright updates # patches and diffs (identified by .patch and .diff) are excluded for fn in chain(new, changed): if fn.endswith('.diff') or fn.endswith('.patch'): continue update_copyright(os.path.join(pkgdir, fn), year, pretend=pretend) cl_path = os.path.join(pkgdir, 'ChangeLog') clold_lines = [] clnew_lines = [] old_header_lines = [] header_lines = [] clold_file = None try: clold_file = io.open(_unicode_encode( cl_path, encoding=_encodings['fs'], errors='strict'), mode='r', encoding=_encodings['repo.content'], errors='replace') except EnvironmentError: pass f, clnew_path = mkstemp() # construct correct header first try: if clold_file is not None: # retain header from old ChangeLog first_line = True for line in clold_file: line_strip = line.strip() if line_strip and line[:1] != "#": clold_lines.append(line) break # always make sure cat/pkg is up-to-date in case we are # moving packages around, or copied from another pkg, or ... if first_line: if line.startswith('# ChangeLog for'): line = '# ChangeLog for %s/%s\n' % (category, package) first_line = False old_header_lines.append(line) header_lines.append(update_copyright_year(year, line)) if not line_strip: break clskel_file = None if not header_lines: # delay opening this until we find we need a header try: clskel_file = io.open(_unicode_encode( skel_path, encoding=_encodings['fs'], errors='strict'), mode='r', encoding=_encodings['repo.content'], errors='replace') except EnvironmentError: pass if clskel_file is not None: # read skel.ChangeLog up to first empty line for line in clskel_file: line_strip = line.strip() if not line_strip: break line = line.replace('<CATEGORY>', category) line = line.replace('<PACKAGE_NAME>', package) line = update_copyright_year(year, line) header_lines.append(line) header_lines.append('\n') clskel_file.close() # write new ChangeLog entry clnew_lines.extend(header_lines) newebuild = False for fn in new: if not fn.endswith('.ebuild'): continue ebuild = fn.split(os.sep)[-1][0:-7] clnew_lines.append('*%s (%s)\n' % (ebuild, date)) newebuild = True if newebuild: clnew_lines.append('\n') trivial_files = ('ChangeLog', 'Manifest') display_new = [ '+' + elem for elem in new if elem not in trivial_files] display_removed = [ '-' + elem for elem in removed] display_changed = [ elem for elem in changed if elem not in trivial_files] if not (display_new or display_removed or display_changed): # If there's nothing else to display, show one of the # trivial files. for fn in trivial_files: if fn in new: display_new = ['+' + fn] break elif fn in changed: display_changed = [fn] break display_new.sort() display_removed.sort() display_changed.sort() mesg = '%s; %s %s:' % (date, user, ', '.join(chain( display_new, display_removed, display_changed))) for line in textwrap.wrap( mesg, 80, initial_indent=' ', subsequent_indent=' ', break_on_hyphens=False): clnew_lines.append('%s\n' % line) for line in textwrap.wrap( msg, 80, initial_indent=' ', subsequent_indent=' '): clnew_lines.append('%s\n' % line) # Don't append a trailing newline if the file is new. if clold_file is not None: clnew_lines.append('\n') f = io.open( f, mode='w', encoding=_encodings['repo.content'], errors='backslashreplace') for line in clnew_lines: f.write(line) # append stuff from old ChangeLog if clold_file is not None: if clold_lines: # clold_lines may contain a saved non-header line # that we want to write first. # Also, append this line to clnew_lines so that the # unified_diff call doesn't show it as removed. for line in clold_lines: f.write(line) clnew_lines.append(line) else: # ensure that there is no more than one blank # line after our new entry for line in clold_file: if line.strip(): f.write(line) break # Now prepend old_header_lines to clold_lines, for use # in the unified_diff call below. clold_lines = old_header_lines + clold_lines # Trim any trailing newlines. lines = clold_file.readlines() clold_file.close() while lines and lines[-1] == '\n': del lines[-1] f.writelines(lines) f.close() # show diff if not quiet: for line in difflib.unified_diff( clold_lines, clnew_lines, fromfile=cl_path, tofile=cl_path, n=0): util.writemsg_stdout(line, noiselevel=-1) util.writemsg_stdout("\n", noiselevel=-1) if pretend: # remove what we've done os.remove(clnew_path) else: # rename to ChangeLog, and set permissions try: clold_stat = os.stat(cl_path) except OSError: clold_stat = None shutil.move(clnew_path, cl_path) if clold_stat is None: util.apply_permissions(cl_path, mode=0o644) else: util.apply_stat_permissions(cl_path, clold_stat) if clold_file is None: return True else: return False except IOError as e: err = 'Repoman is unable to create/write to Changelog.new file: %s' % (e,) logging.critical(err) # try to remove if possible try: os.remove(clnew_path) except OSError: pass return None
def perform(self, qa_output): myautoadd = self._vcs_autoadd() self._vcs_deleted() changes = self.get_vcs_changed() mynew, mychanged, myremoved, no_expansion, expansion = changes # Manifests need to be regenerated after all other commits, so don't commit # them now even if they have changed. mymanifests = set() myupdates = set() for f in mychanged + mynew: if "Manifest" == os.path.basename(f): mymanifests.add(f) else: myupdates.add(f) myupdates.difference_update(myremoved) myupdates = list(myupdates) mymanifests = list(mymanifests) myheaders = [] commitmessage = self.options.commitmsg if self.options.commitmsgfile: try: f = io.open(_unicode_encode(self.options.commitmsgfile, encoding=_encodings['fs'], errors='strict'), mode='r', encoding=_encodings['content'], errors='replace') commitmessage = f.read() f.close() del f except (IOError, OSError) as e: if e.errno == errno.ENOENT: portage.writemsg("!!! File Not Found:" " --commitmsgfile='%s'\n" % self.options.commitmsgfile) else: raise if commitmessage[:9].lower() in ("cat/pkg: ", ): commitmessage = self.msg_prefix() + commitmessage[9:] if not commitmessage or not commitmessage.strip(): commitmessage = self.get_new_commit_message(qa_output) commitmessage = commitmessage.rstrip() # Update copyright for new and changed files year = time.strftime('%Y', time.gmtime()) for fn in chain(mynew, mychanged): if fn.endswith('.diff') or fn.endswith('.patch'): continue update_copyright(fn, year, pretend=self.options.pretend) myupdates, broken_changelog_manifests = self.changelogs( myupdates, mymanifests, myremoved, mychanged, myautoadd, mynew, commitmessage) lines = commitmessage.splitlines() lastline = lines[-1] if len(lines) == 1 or re.match(r'^\S+:\s', lastline) is None: commitmessage += '\n' commit_footer = self.get_commit_footer() commitmessage += commit_footer print("* %s files being committed..." % green(str(len(myupdates))), end=' ') if not self.vcs_settings.needs_keyword_expansion: # With some VCS types there's never any keyword expansion, so # there's no need to regenerate manifests and all files will be # committed in one big commit at the end. logging.debug("VCS type doesn't need keyword expansion") print() elif not self.repo_settings.repo_config.thin_manifest: logging.debug("perform: Calling thick_manifest()") self.vcs_settings.changes.thick_manifest(myupdates, myheaders, no_expansion, expansion) logging.info("myupdates: %s", myupdates) logging.info("myheaders: %s", myheaders) uq = UserQuery(self.options) if self.options.ask and uq.query('Commit changes?', True) != 'Yes': print("* aborting commit.") sys.exit(128 + signal.SIGINT) # Handle the case where committed files have keywords which # will change and need a priming commit before the Manifest # can be committed. if (myupdates or myremoved) and myheaders: self.priming_commit(myupdates, myremoved, commitmessage) # When files are removed and re-added, the cvs server will put /Attic/ # inside the $Header path. This code detects the problem and corrects it # so that the Manifest will generate correctly. See bug #169500. # Use binary mode in order to avoid potential character encoding issues. self.vcs_settings.changes.clear_attic(myheaders) if self.scanner.repolevel == 1: utilities.repoman_sez("\"You're rather crazy... " "doing the entire repository.\"\n") self.vcs_settings.changes.digest_regen(myupdates, myremoved, mymanifests, self.scanner, broken_changelog_manifests) if self.repo_settings.sign_manifests: self.sign_manifest(myupdates, myremoved, mymanifests) self.vcs_settings.changes.update_index(mymanifests, myupdates) self.add_manifest(mymanifests, myheaders, myupdates, myremoved, commitmessage) if self.options.quiet: return print() if self.vcs_settings.vcs: print("Commit complete.") else: print("repoman was too scared" " by not seeing any familiar version control file" " that he forgot to commit anything") utilities.repoman_sez( "\"If everyone were like you, I'd be out of business!\"\n") return
def perform(self, qa_output): myautoadd = self._vcs_autoadd() self._vcs_deleted() changes = self.get_vcs_changed() mynew, mychanged, myremoved, no_expansion, expansion = changes # Manifests need to be regenerated after all other commits, so don't commit # them now even if they have changed. mymanifests = set() myupdates = set() for f in mychanged + mynew: if "Manifest" == os.path.basename(f): mymanifests.add(f) else: myupdates.add(f) myupdates.difference_update(myremoved) myupdates = list(myupdates) mymanifests = list(mymanifests) myheaders = [] commitmessage = self.options.commitmsg if self.options.commitmsgfile: try: f = io.open( _unicode_encode( self.options.commitmsgfile, encoding=_encodings['fs'], errors='strict'), mode='r', encoding=_encodings['content'], errors='replace') commitmessage = f.read() f.close() del f except (IOError, OSError) as e: if e.errno == errno.ENOENT: portage.writemsg( "!!! File Not Found:" " --commitmsgfile='%s'\n" % self.options.commitmsgfile) else: raise if commitmessage[:9].lower() in ("cat/pkg: ",): commitmessage = self.msg_prefix() + commitmessage[9:] if not commitmessage or not commitmessage.strip(): commitmessage = self.get_new_commit_message(qa_output) commitmessage = commitmessage.rstrip() # Update copyright for new and changed files year = time.strftime('%Y', time.gmtime()) for fn in chain(mynew, mychanged): if fn.endswith('.diff') or fn.endswith('.patch'): continue update_copyright(fn, year, pretend=self.options.pretend) myupdates, broken_changelog_manifests = self.changelogs( myupdates, mymanifests, myremoved, mychanged, myautoadd, mynew, commitmessage) lines = commitmessage.splitlines() lastline = lines[-1] if len(lines) == 1 or re.match(r'^\S+:\s', lastline) is None: commitmessage += '\n' commit_footer = self.get_commit_footer() commitmessage += commit_footer print("* %s files being committed..." % green(str(len(myupdates))), end=' ') if not self.vcs_settings.needs_keyword_expansion: # With some VCS types there's never any keyword expansion, so # there's no need to regenerate manifests and all files will be # committed in one big commit at the end. logging.debug("VCS type doesn't need keyword expansion") print() elif not self.repo_settings.repo_config.thin_manifest: logging.debug("perform: Calling thick_manifest()") self.vcs_settings.changes.thick_manifest(myupdates, myheaders, no_expansion, expansion) logging.info("myupdates: %s", myupdates) logging.info("myheaders: %s", myheaders) uq = UserQuery(self.options) if self.options.ask and uq.query('Commit changes?', True) != 'Yes': print("* aborting commit.") sys.exit(128 + signal.SIGINT) # Handle the case where committed files have keywords which # will change and need a priming commit before the Manifest # can be committed. if (myupdates or myremoved) and myheaders: self.priming_commit(myupdates, myremoved, commitmessage) # When files are removed and re-added, the cvs server will put /Attic/ # inside the $Header path. This code detects the problem and corrects it # so that the Manifest will generate correctly. See bug #169500. # Use binary mode in order to avoid potential character encoding issues. self.vcs_settings.changes.clear_attic(myheaders) if self.scanner.repolevel == 1: utilities.repoman_sez( "\"You're rather crazy... " "doing the entire repository.\"\n") self.vcs_settings.changes.digest_regen(myupdates, myremoved, mymanifests, self.scanner, broken_changelog_manifests) if self.repo_settings.sign_manifests: self.sign_manifest(myupdates, myremoved, mymanifests) self.vcs_settings.changes.update_index(mymanifests, myupdates) self.add_manifest(mymanifests, myheaders, myupdates, myremoved, commitmessage) if self.options.quiet: return print() if self.vcs_settings.vcs: print("Commit complete.") else: print( "repoman was too scared" " by not seeing any familiar version control file" " that he forgot to commit anything") utilities.repoman_sez( "\"If everyone were like you, I'd be out of business!\"\n") return
def perform(self, qa_output): myautoadd = self._vcs_autoadd() self._vcs_deleted() changes = self.get_vcs_changed() mynew, mychanged, myremoved, no_expansion, expansion = changes # Manifests need to be regenerated after all other commits, so don't commit # them now even if they have changed. mymanifests = set() myupdates = set() for f in mychanged + mynew: if "Manifest" == os.path.basename(f): mymanifests.add(f) else: myupdates.add(f) myupdates.difference_update(myremoved) myupdates = list(myupdates) mymanifests = list(mymanifests) myheaders = [] commitmessage = self.options.commitmsg if self.options.commitmsgfile: try: f = io.open( _unicode_encode( self.options.commitmsgfile, encoding=_encodings["fs"], errors="strict", ), mode="r", encoding=_encodings["content"], errors="replace", ) commitmessage = f.read() f.close() del f except (IOError, OSError) as e: if e.errno == errno.ENOENT: portage.writemsg("!!! File Not Found:" " --commitmsgfile='%s'\n" % self.options.commitmsgfile) else: raise if commitmessage[:9].lower() in ("cat/pkg: ", ): commitmessage = self.msg_prefix() + commitmessage[9:] if commitmessage is not None and commitmessage.strip(): res, expl = self.verify_commit_message(commitmessage) if not res: print(bad("RepoMan does not like your commit message:")) print(expl) if self.options.force: print("(but proceeding due to --force)") else: sys.exit(1) else: commitmessage = None msg_qa_output = qa_output initial_message = None while True: commitmessage = self.get_new_commit_message( msg_qa_output, commitmessage) res, expl = self.verify_commit_message(commitmessage) if res: break else: full_expl = ( """Issues with the commit message were found. Please fix it or remove the whole commit message to abort. """ + expl) msg_qa_output = [ " %s\n" % x for x in full_expl.splitlines() ] + qa_output commitmessage = commitmessage.rstrip() # Update copyright for new and changed files year = time.strftime("%Y", time.gmtime()) updated_copyright = [] for fn in chain(mynew, mychanged): if fn.endswith(".diff") or fn.endswith(".patch"): continue if update_copyright(fn, year, pretend=self.options.pretend): updated_copyright.append(fn) if updated_copyright and not ( self.options.pretend or self.repo_settings.repo_config.thin_manifest): for cp in sorted(self._vcs_files_to_cps(iter(updated_copyright))): self._manifest_gen(cp) myupdates, broken_changelog_manifests = self.changelogs( myupdates, mymanifests, myremoved, mychanged, myautoadd, mynew, commitmessage, ) lines = commitmessage.splitlines() lastline = lines[-1] if len(lines) == 1 or re.match(r"^\S+:\s", lastline) is None: commitmessage += "\n" commit_footer = self.get_commit_footer() commitmessage += commit_footer print("* %s files being committed..." % green(str(len(myupdates))), end=" ") if not self.vcs_settings.needs_keyword_expansion: # With some VCS types there's never any keyword expansion, so # there's no need to regenerate manifests and all files will be # committed in one big commit at the end. logging.debug("VCS type doesn't need keyword expansion") print() elif not self.repo_settings.repo_config.thin_manifest: logging.debug("perform: Calling thick_manifest()") self.vcs_settings.changes.thick_manifest(myupdates, myheaders, no_expansion, expansion) logging.info("myupdates: %s", myupdates) logging.info("myheaders: %s", myheaders) uq = UserQuery(self.options) if self.options.ask and uq.query("Commit changes?", True) != "Yes": print("* aborting commit.") sys.exit(128 + signal.SIGINT) # Handle the case where committed files have keywords which # will change and need a priming commit before the Manifest # can be committed. if (myupdates or myremoved) and myheaders: self.priming_commit(myupdates, myremoved, commitmessage) # When files are removed and re-added, the cvs server will put /Attic/ # inside the $Header path. This code detects the problem and corrects it # so that the Manifest will generate correctly. See bug #169500. # Use binary mode in order to avoid potential character encoding issues. self.vcs_settings.changes.clear_attic(myheaders) if self.scanner.repolevel == 1: utilities.repoman_sez("\"You're rather crazy... " 'doing the entire repository."\n') self.vcs_settings.changes.digest_regen(myupdates, myremoved, mymanifests, self.scanner, broken_changelog_manifests) if self.repo_settings.sign_manifests: self.sign_manifest(myupdates, myremoved, mymanifests) self.vcs_settings.changes.update_index(mymanifests, myupdates) self.add_manifest(mymanifests, myheaders, myupdates, myremoved, commitmessage) if self.options.quiet: return print() if self.vcs_settings.vcs: print("Commit complete.") else: print("repoman was too scared" " by not seeing any familiar version control file" " that he forgot to commit anything") utilities.repoman_sez( '"If everyone were like you, I\'d be out of business!"\n') return