| # Copyright 2020 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """ Provides utilities for filtered branch handling. """ |
| |
| import collections |
| import re |
| import subprocess |
| |
| import utils |
| |
| # Keyword to uniquely identify the beginning of upstream history. |
| CROS_LIBCHROME_INITIAL_COMMIT = b'CrOS-Libchrome-History-Initial-Commit' |
| # Keyword to identify original commit in Chromium browser repository. |
| CROS_LIBCHROME_ORIGINAL_COMMIT = b'CrOS-Libchrome-Original-Commit' |
| # Keyword to identify current commit for deleting files or merge add files |
| # branch. |
| CROS_LIBCHROME_CURRENT_COMMIT = b'CrOS-Libchrome-Current-Commit' |
| |
| # Stores metadata required for a git commit. |
| GitCommitMetadata = collections.namedtuple('GitCommitMetadata', [ |
| 'parents', |
| 'original_commits', |
| 'original_commit_cursor', |
| 'tree', |
| 'authorship', |
| 'title', |
| 'message', |
| 'is_root', |
| ]) |
| |
| # Stores information for a commit authorship. |
| GitCommitAuthorship = collections.namedtuple('GitCommitAuthorship', [ |
| 'name', |
| 'email', |
| 'time', |
| 'timezone', |
| ]) |
| |
| |
| def get_metadata(commit_hash): |
| """Returns the metadata of the commit specified by the commit_hash. |
| |
| This function parses the commit message of the commit specified by the |
| commit_hash, then returns its GitCommitMetadata instance. |
| The commit must be on the filtered branch, otherwise some metadata may be |
| omitted. |
| Returns metadata from the commit message about commit_hash on the filtered |
| branch. |
| |
| Args: |
| commit_hash: the commit hash on the filtered branch. |
| """ |
| |
| ret = subprocess.check_output(['git', 'cat-file', 'commit', |
| commit_hash]).split(b'\n') |
| parents = [] |
| tree_hash = None |
| authorship = None |
| author_re = re.compile(rb'^(.*) <(.*)> ([0-9]+) ([^ ])+$') |
| while ret: |
| line = ret[0] |
| ret = ret[1:] |
| if not line.strip(): |
| # End of header. break. |
| break |
| tag, reminder = line.split(b' ', 1) |
| if tag == b'tree': |
| tree_hash = reminder |
| elif tag == b'author': |
| m = author_re.match(reminder) |
| assert m, (line, commit_hash) |
| authorship = GitCommitAuthorship(m.group(1), m.group(2), m.group(3), |
| m.group(4)) |
| elif tag == b'parent': |
| parents.append(reminder) |
| |
| title = ret[0] if ret else None |
| |
| original_commits = [] |
| original_commit_cursor = None |
| is_root = False |
| for line in ret: |
| if line.startswith(CROS_LIBCHROME_ORIGINAL_COMMIT): |
| original_commits.append(line.split(b':')[1].strip()) |
| if line.startswith(CROS_LIBCHROME_ORIGINAL_COMMIT) or line.startswith( |
| CROS_LIBCHROME_CURRENT_COMMIT): |
| original_commit_cursor = line.split(b':')[1].strip() |
| if line == CROS_LIBCHROME_INITIAL_COMMIT: |
| is_root = True |
| msg = b'\n'.join(ret) |
| return GitCommitMetadata(parents, original_commits, original_commit_cursor, |
| tree_hash, authorship, title, msg, is_root) |
| |
| |
| def get_commits_map(commit_hash, progress_callback): |
| """Returns a map from original commit hashes to filtered commit hashes. |
| |
| This function traverses the filtered branch from the commit specified by |
| commit_hash to its root, then parses each commit message and constructs the |
| map of those commits. |
| |
| Args: |
| commit_hash: the commit hash on the filtered branch. |
| progress_callback: called every commit is being read. Parameters taken |
| are (idx, total_commits, current_commit) |
| """ |
| commits_map = {} |
| commits_filtered_tree = utils.git_revlist(None, commit_hash) |
| for index, commit in enumerate(commits_filtered_tree, start=1): |
| if progress_callback: |
| progress_callback(index, len(commits_filtered_tree), commit[0]) |
| meta = get_metadata(commit[0]) |
| for original_commit in meta.original_commits: |
| commits_map[original_commit] = commit[0] |
| if meta.is_root: |
| assert 'ROOT' not in commits_map |
| commits_map['ROOT'] = commit[0] |
| return commits_map |