(root)/
gcc-13.2.0/
contrib/
gcc-changelog/
git_update_version.py
       1  #!/usr/bin/env python3
       2  
       3  # Copyright (C) 2020-2023 Free Software Foundation, Inc.
       4  #
       5  # This file is part of GCC.
       6  #
       7  # GCC is free software; you can redistribute it and/or modify it under
       8  # the terms of the GNU General Public License as published by the Free
       9  # Software Foundation; either version 3, or (at your option) any later
      10  # version.
      11  #
      12  # GCC is distributed in the hope that it will be useful, but WITHOUT ANY
      13  # WARRANTY; without even the implied warranty of MERCHANTABILITY or
      14  # FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      15  # for more details.
      16  #
      17  # You should have received a copy of the GNU General Public License
      18  # along with GCC; see the file COPYING3.  If not see
      19  # <http://www.gnu.org/licenses/>.  */
      20  
      21  import argparse
      22  import datetime
      23  import logging
      24  import os
      25  
      26  from git import Repo
      27  
      28  from git_repository import parse_git_revisions
      29  
      30  current_timestamp = datetime.datetime.now().strftime('%Y%m%d\n')
      31  
      32  # Skip the following commits, they cannot be correctly processed
      33  IGNORED_COMMITS = (
      34          'c2be82058fb40f3ae891c68d185ff53e07f14f45',
      35          '04a040d907a83af54e0a98bdba5bfabc0ef4f700',
      36          '2e96b5f14e4025691b57d2301d71aa6092ed44bc',
      37          '3ab5c8cd03d92bf4ec41e351820349d92fbc40c4',
      38          '86d8e0c0652ef5236a460b75c25e4f7093cc0651',
      39          'e4cba49413ca429dc82f6aa2e88129ecb3fdd943',
      40          '1957bedf29a1b2cc231972aba680fe80199d5498')
      41  
      42  FORMAT = '%(asctime)s:%(levelname)s:%(name)s:%(message)s'
      43  logging.basicConfig(level=logging.INFO, format=FORMAT,
      44                      handlers=[
      45                          logging.FileHandler('/tmp/git_update_version.txt'),
      46                          logging.StreamHandler()
      47                      ])
      48  
      49  
      50  def read_timestamp(path):
      51      with open(path) as f:
      52          return f.read()
      53  
      54  
      55  def prepend_to_changelog_files(repo, folder, git_commit, add_to_git):
      56      if not git_commit.success:
      57          for error in git_commit.errors:
      58              logging.info(error)
      59          raise AssertionError()
      60      for entry, output in git_commit.to_changelog_entries(use_commit_ts=True):
      61          full_path = os.path.join(folder, entry, 'ChangeLog')
      62          logging.info('writing to %s' % full_path)
      63          if os.path.exists(full_path):
      64              with open(full_path) as f:
      65                  content = f.read()
      66          else:
      67              content = ''
      68          with open(full_path, 'w+') as f:
      69              f.write(output)
      70              if content:
      71                  f.write('\n\n')
      72                  f.write(content)
      73          if add_to_git:
      74              repo.git.add(full_path)
      75  
      76  
      77  active_refs = ['master', 'releases/gcc-10',
      78                 'releases/gcc-11', 'releases/gcc-12']
      79  
      80  parser = argparse.ArgumentParser(description='Update DATESTAMP and generate '
      81                                   'ChangeLog entries')
      82  parser.add_argument('-g', '--git-path', default='.',
      83                      help='Path to git repository')
      84  parser.add_argument('-p', '--push', action='store_true',
      85                      help='Push updated active branches')
      86  parser.add_argument('-d', '--dry-mode',
      87                      help='Generate patch for ChangeLog entries and do it'
      88                           ' even if DATESTAMP is unchanged; folder argument'
      89                           ' is expected')
      90  parser.add_argument('-c', '--current', action='store_true',
      91                      help='Modify current branch (--push argument is ignored)')
      92  args = parser.parse_args()
      93  
      94  repo = Repo(args.git_path)
      95  origin = repo.remotes['origin']
      96  
      97  
      98  def update_current_branch(ref_name):
      99      commit = repo.head.commit
     100      commit_count = 1
     101      while commit:
     102          if (commit.author.email == 'gccadmin@gcc.gnu.org'
     103                  and commit.message.strip() == 'Daily bump.'):
     104              break
     105          # We support merge commits but only with 2 parensts
     106          assert len(commit.parents) <= 2
     107          commit = commit.parents[-1]
     108          commit_count += 1
     109  
     110      logging.info('%d revisions since last Daily bump' % commit_count)
     111      datestamp_path = os.path.join(args.git_path, 'gcc/DATESTAMP')
     112      if (read_timestamp(datestamp_path) != current_timestamp
     113              or args.dry_mode or args.current):
     114          head = repo.head.commit
     115          # if HEAD is a merge commit, start with second parent
     116          # (branched that is being merged into the current one)
     117          assert len(head.parents) <= 2
     118          if len(head.parents) == 2:
     119              head = head.parents[1]
     120          commits = parse_git_revisions(args.git_path, '%s..%s'
     121                                        % (commit.hexsha, head.hexsha), ref_name)
     122          commits = [c for c in commits if c.info.hexsha not in IGNORED_COMMITS]
     123          for git_commit in reversed(commits):
     124              prepend_to_changelog_files(repo, args.git_path, git_commit,
     125                                         not args.dry_mode)
     126          if args.dry_mode:
     127              diff = repo.git.diff('HEAD')
     128              patch = os.path.join(args.dry_mode,
     129                                   branch.name.split('/')[-1] + '.patch')
     130              with open(patch, 'w+') as f:
     131                  f.write(diff)
     132              logging.info('branch diff written to %s' % patch)
     133              repo.git.checkout(force=True)
     134          else:
     135              # update timestamp
     136              logging.info('DATESTAMP will be changed:')
     137              with open(datestamp_path, 'w+') as f:
     138                  f.write(current_timestamp)
     139              repo.git.add(datestamp_path)
     140              if not args.current:
     141                  repo.index.commit('Daily bump.')
     142                  logging.info('commit is done')
     143                  if args.push:
     144                      try:
     145                          repo.git.push('origin', branch)
     146                          logging.info('branch is pushed')
     147                      except Exception:
     148                          logging.exception('git push failed')
     149      else:
     150          logging.info('DATESTAMP unchanged')
     151  
     152  
     153  if args.current:
     154      logging.info('=== Working on the current branch ===')
     155      update_current_branch()
     156  else:
     157      for ref in origin.refs:
     158          assert ref.name.startswith('origin/')
     159          name = ref.name[len('origin/'):]
     160          if name in active_refs:
     161              if name in repo.branches:
     162                  branch = repo.branches[name]
     163              else:
     164                  branch = repo.create_head(name, ref).set_tracking_branch(ref)
     165              logging.info('=== Working on: %s ===' % branch)
     166              branch.checkout()
     167              origin.pull(rebase=True)
     168              logging.info('branch pulled and checked out')
     169              update_current_branch(name)
     170              assert not repo.index.diff(None)
     171              logging.info('branch is done')
     172              logging.info('')