Fix order of commits from previous imports
Build list of commits to be carried since the last upstream import in two steps to ensure any commits that were merged after the last import merge, but were based off of the previous mainline, are listed in the correct order. For git-upstream, this occurs with a graph such as: O---- / \ B---C---D---H---I---J---K master / \ / / ----G / / / B1--C1--D1 import/next / / A---E---F---L---M upstream/master Where 'G' is the import merge, and O was approved to land before it. Previously it was possible for changes started before an import is performed, but only land afterward the final result of the merge is fixed, would be listed in the wrong order. This problem is created because in graphs where merges occur and there is no ability to distinguish which commit was on the target branch, the order of listing commits from both sides simultaneously is non-deterministic. Taken from the git rev-list man page: For example, in a commit history like this: ---1----2----4----7 \ \ 3----5----6----8--- When listing in topological order, both 87426531 and 86537421 are equally valid. When asking git to list commits between F to K, ignoring D and it's parents, both O, H, B1, C1, D1, G, I, J, K and B1, C1, D1, G, O, H, I, J, K are valid orders, however while git would always choose the former, the latter is more desirable for git-upstream due to the special nature of G, the second ordering will be less likely to cause conflicts as otherwise I would not have been created. Change-Id: I4af6c7063e09d48f8598aa3305298f4246355cac
This commit is contained in:
parent
3d641bcbf9
commit
f41f3f9bc1
|
@ -17,6 +17,7 @@
|
|||
|
||||
from abc import ABCMeta
|
||||
from abc import abstractmethod
|
||||
import itertools
|
||||
import re
|
||||
|
||||
from git_upstream.lib.pygitcompat import Commit
|
||||
|
@ -78,7 +79,7 @@ class Searcher(GitMixin):
|
|||
# included as it contributes changes to the tree.
|
||||
if (self.git.rev_parse("%s^{tree}" % parent) !=
|
||||
self.git.rev_parse("%s^{tree}" % mergecommit)):
|
||||
return False, []
|
||||
return None, []
|
||||
|
||||
mergebase = self.git.merge_base(parent, self.commit,
|
||||
with_exceptions=False)
|
||||
|
@ -97,7 +98,7 @@ class Searcher(GitMixin):
|
|||
Found merge of additional branch:
|
||||
%s
|
||||
""", mergecommit)
|
||||
return False, ["^%s" % parent]
|
||||
return None, ["^%s" % parent]
|
||||
|
||||
# otherwise we have a descendant commit with the same tree that
|
||||
# requires further inspection to determine if it is really the
|
||||
|
@ -110,19 +111,19 @@ class Searcher(GitMixin):
|
|||
# the previous mainline that was replaced and should ignore
|
||||
if mergecommit == last_merge:
|
||||
# also means we've found the previous import
|
||||
return True, ["^%s" % parent]
|
||||
return mergecommit, ["^%s" % parent]
|
||||
|
||||
# otherwise this an unusual state where we are looking at the
|
||||
# merge of the previous import with a change that landed on the
|
||||
# previous target mainline but was not included in the changes
|
||||
# that where on the previous import. This can occur due to a
|
||||
# change being approved/landed after the import was performed
|
||||
return False, []
|
||||
return None, []
|
||||
|
||||
# otherwise looking at the previous import merge commit and the parent
|
||||
# from the previous import branch, so exclude all other parents.
|
||||
return True, ["^%s" % ip
|
||||
for ip in mergecommit.parents if ip != parent]
|
||||
return mergecommit, ["^%s" % ip
|
||||
for ip in mergecommit.parents if ip != parent]
|
||||
|
||||
def list(self):
|
||||
"""
|
||||
|
@ -179,29 +180,51 @@ class Searcher(GitMixin):
|
|||
# an import branch and only merging the final result of upstream +
|
||||
# local changes.
|
||||
if merge_list and not previous_import:
|
||||
for p in merge_list[-1].parents:
|
||||
previous_import = merge_list[-1]
|
||||
for p in previous_import.parents:
|
||||
if p.hexsha == self.commit.hexsha:
|
||||
ignore_args.extend(["^%s" % ip
|
||||
for ip in merge_list[-1].parents
|
||||
for ip in previous_import.parents
|
||||
if ip != p])
|
||||
|
||||
# walk the tree and find all commits that lie in the path between the
|
||||
# commit found by find() and head of the branch to provide a list of
|
||||
# commits to the caller
|
||||
self.log.info(
|
||||
"""
|
||||
Walking the changes between found commit and target, excluding
|
||||
those behind the previous import or merged as an additional branch
|
||||
during the previous import
|
||||
git rev-list --topo-order %s %s
|
||||
""", revision_spec, " ".join(ignore_args))
|
||||
# commit found by find() and head of the branch in two steps, to
|
||||
# ensure a deterministic order between what is from the previous
|
||||
# upstream to the last import, and from that import to what is on
|
||||
# the tip of the head to avoid inversion where older commits
|
||||
# started before the previous import merge and approved afterwards
|
||||
# are not sorted by 'rev-list' predictably.
|
||||
if previous_import:
|
||||
search_list = [
|
||||
(previous_import, self.branch),
|
||||
(self.commit, previous_import),
|
||||
]
|
||||
else:
|
||||
search_list = [(self.commit, self.branch)]
|
||||
|
||||
commit_list = Commit._iter_from_process_or_stream(
|
||||
self.repo, self.git.rev_list(revision_spec, *ignore_args,
|
||||
as_process=True, topo_order=True))
|
||||
commit_list = []
|
||||
for start, end in search_list:
|
||||
revision_spec = "{0}..{1}".format(start, end)
|
||||
|
||||
self.log.info(
|
||||
"""
|
||||
Walking the changes between found commit and target, excluding
|
||||
those behind the previous import or merged as an additional
|
||||
branch during the previous import
|
||||
git rev-list --topo-order %s %s
|
||||
""", revision_spec, " ".join(ignore_args))
|
||||
|
||||
commit_list.append(
|
||||
Commit._iter_from_process_or_stream(
|
||||
self.repo,
|
||||
self.git.rev_list(revision_spec,
|
||||
*ignore_args,
|
||||
as_process=True,
|
||||
topo_order=True)))
|
||||
|
||||
# chain the filters as generators so that we don't need to allocate new
|
||||
# lists for each step in the filter chain.
|
||||
commit_list = itertools.chain(*commit_list)
|
||||
for f in self.filters:
|
||||
commit_list = f.filter(commit_list)
|
||||
|
||||
|
|
|
@ -60,4 +60,4 @@
|
|||
head: [master, K]
|
||||
upstream: [upstream/master, M]
|
||||
|
||||
expected_changes: [O, H, B1, C1, D1, G, I, J, K]
|
||||
expected_changes: [B1, C1, D1, G, O, H, I, J, K]
|
||||
|
|
|
@ -55,4 +55,4 @@
|
|||
head: [master, K]
|
||||
upstream: [upstream/master, M]
|
||||
|
||||
expected_changes: [I, B1, C1, D1, G1, H, J, K]
|
||||
expected_changes: [B1, C1, D1, G1, H, I, J, K]
|
||||
|
|
|
@ -59,4 +59,4 @@
|
|||
head: [master, K]
|
||||
upstream: [upstream/master, M]
|
||||
|
||||
expected_changes: [O, B1, C1, D1, J, K]
|
||||
expected_changes: [B1, C1, D1, O, J, K]
|
||||
|
|
Loading…
Reference in New Issue