1import fnmatch
2import os
3import re
4import shutil
5import sys
6import uuid
7
8from .base import Step, StepRunner
9from .tree import Commit
10
11here = os.path.abspath(os.path.dirname(__file__))
12
13
14def copy_wpt_tree(tree, dest, excludes=None, includes=None):
15    """Copy the working copy of a Tree to a destination directory.
16
17    :param tree: The Tree to copy.
18    :param dest: The destination directory"""
19    if os.path.exists(dest):
20        assert os.path.isdir(dest)
21
22    shutil.rmtree(dest)
23
24    os.mkdir(dest)
25
26    if excludes is None:
27        excludes = []
28
29    excludes = [re.compile(fnmatch.translate(item)) for item in excludes]
30
31    if includes is None:
32        includes = []
33
34    includes = [re.compile(fnmatch.translate(item)) for item in includes]
35
36    for tree_path in tree.paths():
37        if (any(item.match(tree_path) for item in excludes) and
38            not any(item.match(tree_path) for item in includes)):
39            continue
40
41        source_path = os.path.join(tree.root, tree_path)
42        dest_path = os.path.join(dest, tree_path)
43
44        dest_dir = os.path.dirname(dest_path)
45        if not os.path.isdir(source_path):
46            if not os.path.exists(dest_dir):
47                os.makedirs(dest_dir)
48            shutil.copy2(source_path, dest_path)
49
50    for source, destination in [("testharness_runner.html", ""),
51                                ("testdriver-vendor.js", "resources/")]:
52        source_path = os.path.join(here, os.pardir, source)
53        dest_path = os.path.join(dest, destination, os.path.basename(source))
54        shutil.copy2(source_path, dest_path)
55
56
57class UpdateCheckout(Step):
58    """Pull changes from upstream into the local sync tree."""
59
60    provides = ["local_branch"]
61
62    def create(self, state):
63        sync_tree = state.sync_tree
64        state.local_branch = uuid.uuid4().hex
65        sync_tree.update(state.sync["remote_url"],
66                         state.sync["branch"],
67                         state.local_branch)
68        sync_path = os.path.abspath(sync_tree.root)
69        if sync_path not in sys.path:
70            from .update import setup_paths
71            setup_paths(sync_path)
72
73    def restore(self, state):
74        assert os.path.abspath(state.sync_tree.root) in sys.path
75        Step.restore(self, state)
76
77
78class GetSyncTargetCommit(Step):
79    """Find the commit that we will sync to."""
80
81    provides = ["sync_commit"]
82
83    def create(self, state):
84        if state.target_rev is None:
85            #Use upstream branch HEAD as the base commit
86            state.sync_commit = state.sync_tree.get_remote_sha1(state.sync["remote_url"],
87                                                                state.sync["branch"])
88        else:
89            state.sync_commit = Commit(state.sync_tree, state.rev)
90
91        state.sync_tree.checkout(state.sync_commit.sha1, state.local_branch, force=True)
92        self.logger.debug("New base commit is %s" % state.sync_commit.sha1)
93
94
95class UpdateManifest(Step):
96    """Update the manifest to match the tests in the sync tree checkout"""
97
98    provides = ["manifest_path", "test_manifest"]
99
100    def create(self, state):
101        from manifest import manifest  # type: ignore
102        state.manifest_path = os.path.join(state.metadata_path, "MANIFEST.json")
103        state.test_manifest = manifest.load_and_update(state.sync["path"],
104                                                       state.manifest_path,
105                                                       "/",
106                                                       write_manifest=True)
107
108
109class CopyWorkTree(Step):
110    """Copy the sync tree over to the destination in the local tree"""
111
112    def create(self, state):
113        copy_wpt_tree(state.sync_tree,
114                      state.tests_path,
115                      excludes=state.path_excludes,
116                      includes=state.path_includes)
117
118
119class CreateSyncPatch(Step):
120    """Add the updated test files to a commit/patch in the local tree."""
121
122    def create(self, state):
123        if not state.patch:
124            return
125
126        local_tree = state.local_tree
127        sync_tree = state.sync_tree
128
129        local_tree.create_patch("web-platform-tests_update_%s" % sync_tree.rev,
130                                "Update %s to revision %s" % (state.suite_name, sync_tree.rev))
131        test_prefix = os.path.relpath(state.tests_path, local_tree.root)
132        local_tree.add_new(test_prefix)
133        local_tree.add_ignored(sync_tree, test_prefix)
134        updated = local_tree.update_patch(include=[state.tests_path,
135                                                   state.metadata_path])
136        local_tree.commit_patch()
137
138        if not updated:
139            self.logger.info("Nothing to sync")
140
141
142class SyncFromUpstreamRunner(StepRunner):
143    """(Sub)Runner for doing an upstream sync"""
144    steps = [UpdateCheckout,
145             GetSyncTargetCommit,
146             UpdateManifest,
147             CopyWorkTree,
148             CreateSyncPatch]
149