bisect-kit: add chrome local build bisector for branched version
bisector: bisect_cr_localbuild_internal.py
switcher: switch_cros_cr_localbuild_internal.py
This is partial patch of bigger work. Some tasks will be addressed by
later CLs:
- Existing bisectors will be migrated to use codechange module
- This bisector can only deal with branches before 3406 because it
cannot support gclient recursedeps yet
- Add more unit tests
BUG=chromium:776314,chromium:850443
TEST=unittest and following commands
(Stupid example, to find the commit to change branch number to 3396)
$ ./bisect_cr_localbuild_internal.py init --old 67.0.3392.0 --new 67.0.3396.17 \
--chrome_root $CHROME_ROOT \
--gclient_cache_dir $GCLIENT_CACHE_DIR \
--dut $DUT
$ ./bisect_cr_localbuild_internal.py config switch ./switch_cros_cr_localbuild_internal.py
$ ./bisect_cr_localbuild_internal.py config eval bash -c \
"ssh $DUT /opt/google/chrome/chrome --version | grep -v 3396"
$ ./bisect_cr_localbuild_internal.py run
Change-Id: I9b974a19d0c6f6fd045988fde603699282aef32f
Reviewed-on: https://chromium-review.googlesource.com/1088541
Commit-Ready: Kuang-che Wu <kcwu@chromium.org>
Tested-by: Kuang-che Wu <kcwu@chromium.org>
Reviewed-by: Chung-yih Wang <cywang@chromium.org>
diff --git a/bisect_kit/git_util.py b/bisect_kit/git_util.py
index b2d2561..98f3d4e 100644
--- a/bisect_kit/git_util.py
+++ b/bisect_kit/git_util.py
@@ -44,6 +44,11 @@
return s
+def is_git_root(path):
+ """Is given path root of git repo."""
+ return os.path.exists(os.path.join(path, '.git'))
+
+
def checkout_version(git_repo, rev):
"""git checkout.
@@ -60,12 +65,18 @@
Args:
git_repo: path of git repo.
rev: git commit revision in query.
+
+ Returns:
+ True if rev is inside given git repo. If git_repo is not a git folder,
+ returns False as well.
"""
try:
return util.check_output(
'git', 'cat-file', '-t', rev, cwd=git_repo) == 'commit\n'
except subprocess.CalledProcessError:
return False
+ except OSError:
+ return False
def is_ancestor_commit(git_repo, old, new):
@@ -138,6 +149,80 @@
return git_rev
+def get_commit_time(git_repo, rev):
+ """Get git commit timestamp.
+
+ Args:
+ git_repo: path of git repo
+ rev: git commit id
+
+ Returns:
+ timestamp (int)
+ """
+ line = util.check_output(
+ 'git', 'log', '-1', '--format=%ct', rev, cwd=git_repo)
+ return int(line)
+
+
+def get_file_from_revision(git_repo, rev, path):
+ """Get file content of given revision.
+
+ Args:
+ git_repo: path of git repo
+ rev: git commit id
+ path: file path
+
+ Returns:
+ file content (str)
+ """
+ return util.check_output(
+ 'git', 'show', '%s:%s' % (rev, path), cwd=git_repo, log_output=False)
+
+
+def get_rev_by_time(git_repo, timestamp, *args):
+ """Query commit of given time.
+
+ Args:
+ git_repo: path of git repo.
+ timestamp: timestamp
+ args: only the selected subset of history to query. If branch name is
+ specified, only parent of the said branch is queried. If omitted, only
+ queries the parent of current working directory.
+
+ Returns:
+ git commit hash
+ """
+ return util.check_output(
+ 'git',
+ 'log',
+ '-1',
+ '--before',
+ str(timestamp),
+ '--format=%H',
+ *args,
+ cwd=git_repo).strip()
+
+
+def get_history(git_repo, path, after=None):
+ """Get commit history of given path.
+
+ Args:
+ git_repo: path of git repo.
+ path: path to query, relative to git_repo
+ after: limit history after given time
+
+ Yields:
+ commit timestamp, git hash
+ """
+ cmd = ['git', 'log', '--reverse', '--format=%ct %H']
+ if after:
+ cmd += ['--after', str(after)]
+ cmd.append(path)
+ for line in util.check_output(*cmd, cwd=git_repo).splitlines():
+ commit_time, git_rev = line.split()
+ yield int(commit_time), git_rev
+
+
class Diff(object):
"""Class to describe the difference between git commits.
@@ -189,6 +274,54 @@
self.action == rhs.action and self.git_rev == rhs.git_rev)
+def list_commits_between_commits(git_repo, old, new):
+ """Get all commits between (old, new].
+
+ Args:
+ git_repo: path of git repo.
+ old: old commit hash (exclusive)
+ new: new commit hash (inclusive)
+
+ Returns:
+ list of (timestamp, rev)
+ """
+ assert old and new
+ assert old == new or is_ancestor_commit(git_repo, old, new)
+ commits = []
+ # --first-parent is necessary for Android, see following link for more
+ # discussion.
+ # https://docs.google.com/document/d/1c8qiq14_ObRRjLT62sk9r5V5cyCGHX66dLYab4MVnks/edit#heading=h.n3i6mt2n6xuu
+ for line in util.check_output(
+ 'git',
+ 'rev-list',
+ '--timestamp',
+ '--reverse',
+ '--first-parent',
+ '%s..%s' % (old, new),
+ cwd=git_repo).splitlines():
+ timestamp, git_rev = line.split()
+ commits.append([int(timestamp), git_rev])
+
+ # bisect-kit has a fundamental assumption that commit timestamps are
+ # increasing because we sort and bisect the commits by timestamp across git
+ # repos. If not increasing, we have to adjust the timestamp as workaround.
+ # This might lead to bad bisect result, however the bad probability is low in
+ # practice since most machines' clocks are good enough.
+ if commits != sorted(commits, key=lambda x: x[0]):
+ logger.warning('Commit timestamps are not increasing')
+ last_timestamp = -1
+ adjusted = 0
+ for commit in commits:
+ if commit[0] < last_timestamp:
+ commit[0] = last_timestamp
+ adjusted += 1
+
+ last_timestamp = commit[0]
+ logger.warning('%d timestamps adjusted', adjusted)
+
+ return commits
+
+
def get_difflist_between_two_commit(base_dir, path, old, new):
"""Get difflist between (old, new].
@@ -202,40 +335,9 @@
list of Diff objects
"""
git_repo = os.path.join(base_dir, path)
- assert old and new
- assert old == new or is_ancestor_commit(git_repo, old, new)
difflist = []
- # --first-parent is necessary for Android, see following link for more
- # discussion.
- # https://docs.google.com/document/d/1c8qiq14_ObRRjLT62sk9r5V5cyCGHX66dLYab4MVnks/edit#heading=h.n3i6mt2n6xuu
- for line in util.check_output(
- 'git',
- 'rev-list',
- '--timestamp',
- '--reverse',
- '--first-parent',
- '%s..%s' % (old, new),
- cwd=git_repo).splitlines():
- timestamp, git_rev = line.split()
- difflist.append(Diff(int(timestamp), path, Diff.CHECKOUT_TO, git_rev))
-
- # bisect-kit has a fundamental assumption that commit timestamps are
- # increasing because we sort and bisect the commits by timestamp across git
- # repos. If not increasing, we have to adjust the timestamp as workaround.
- # This might lead to bad bisect result, however the bad probability is low in
- # practice since most machines' clocks are good enough.
- if difflist != sorted(difflist, key=lambda x: x.timestamp):
- logger.warning('Commit timestamps are not increasing')
- last_timestamp = -1
- adjusted = 0
- for diff in difflist:
- if diff.timestamp < last_timestamp:
- diff.timestamp = last_timestamp
- adjusted += 1
-
- last_timestamp = diff.timestamp
- logger.warning('%d timestamps adjusted', adjusted)
-
+ for timestamp, git_rev in list_commits_between_commits(git_repo, old, new):
+ difflist.append(Diff(timestamp, path, Diff.CHECKOUT_TO, git_rev))
return difflist