1# Copyright 2020 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4""" Provides utilities for filtered branch handling. """ 5 6import collections 7import re 8import subprocess 9 10import utils 11 12# Keyword to uniquely identify the beginning of upstream history. 13CROS_LIBCHROME_INITIAL_COMMIT = b'CrOS-Libchrome-History-Initial-Commit' 14# Keyword to identify original commit in Chromium browser repository. 15CROS_LIBCHROME_ORIGINAL_COMMIT = b'CrOS-Libchrome-Original-Commit' 16 17 18# Stores metadata required for a git commit. 19GitCommitMetadata = collections.namedtuple( 20 'GitCommitMetadata', 21 ['parents', 'original_commits', 'tree', 'authorship', 'title', 'message', 'is_root',] 22) 23 24 25# Stores information for a commit authorship. 26GitCommitAuthorship = collections.namedtuple( 27 'GitCommitAuthorship', 28 ['name', 'email', 'time', 'timezone',] 29) 30 31 32def get_metadata(commit_hash): 33 """Returns the metadata of the commit specified by the commit_hash. 34 35 This function parses the commit message of the commit specified by the 36 commit_hash, then returns its GitCommitMetadata instance. 37 The commit must be on the filtered branch, otherwise some metadata may be 38 omitted. 39 Returns metadata from the commit message about commit_hash on the filtered 40 branch. 41 42 Args: 43 commit_hash: the commit hash on the filtered branch. 44 """ 45 46 ret = subprocess.check_output(['git', 'cat-file', 'commit', 47 commit_hash]).split(b'\n') 48 parents = [] 49 tree_hash = None 50 authorship = None 51 author_re = re.compile(rb'^(.*) <(.*)> ([0-9]+) ([^ ])+$') 52 while ret: 53 line = ret[0] 54 ret = ret[1:] 55 if not line.strip(): 56 # End of header. break. 57 break 58 tag, reminder = line.split(b' ', 1) 59 if tag == b'tree': 60 tree_hash = reminder 61 elif tag == b'author': 62 m = author_re.match(reminder) 63 assert m, (line, commit_hash) 64 authorship = GitCommitAuthorship(m.group(1), 65 m.group(2), 66 m.group(3), 67 m.group(4)) 68 elif tag == b'parent': 69 parents.append(reminder) 70 71 title = ret[0] if ret else None 72 73 original_commits = [] 74 is_root = False 75 for line in ret: 76 if line.startswith(CROS_LIBCHROME_ORIGINAL_COMMIT): 77 original_commits.append(line.split(b':')[1].strip()) 78 if line == CROS_LIBCHROME_INITIAL_COMMIT: 79 is_root = True 80 msg = b'\n'.join(ret) 81 return GitCommitMetadata(parents, original_commits, tree_hash, authorship, 82 title, msg, is_root) 83 84 85def get_commits_map(commit_hash, progress_callback): 86 """Returns a map from original commit hashes to filtered commit hashes. 87 88 This function traverses the filtered branch from the commit specified by 89 commit_hash to its root, then parses each commit message and constructs the 90 map of those commits. 91 92 Args: 93 commit_hash: the commit hash on the filtered branch. 94 progress_callback: called every commit is being read. Parameters taken 95 are (idx, total_commits, current_commit) 96 """ 97 commits_map = {} 98 commits_filtered_tree = utils.git_revlist(None, commit_hash) 99 for index, commit in enumerate(commits_filtered_tree, start=1): 100 if progress_callback: 101 progress_callback(index, len(commits_filtered_tree), commit[0]) 102 meta = get_metadata(commit[0]) 103 for original_commit in meta.original_commits: 104 commits_map[original_commit] = commit[0] 105 if meta.is_root: 106 assert 'ROOT' not in commits_map 107 commits_map['ROOT'] = commit[0] 108 return commits_map 109