git_update_version.py 5.7 KB

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