jolt 0.9.76__py3-none-any.whl → 0.9.429__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jolt might be problematic. Click here for more details.
- jolt/__init__.py +88 -7
- jolt/__main__.py +9 -1
- jolt/bin/fstree-darwin-x86_64 +0 -0
- jolt/bin/fstree-linux-x86_64 +0 -0
- jolt/cache.py +839 -367
- jolt/chroot.py +156 -0
- jolt/cli.py +362 -143
- jolt/common_pb2.py +63 -0
- jolt/common_pb2_grpc.py +4 -0
- jolt/config.py +99 -42
- jolt/error.py +19 -4
- jolt/expires.py +2 -2
- jolt/filesystem.py +8 -6
- jolt/graph.py +705 -117
- jolt/hooks.py +63 -1
- jolt/influence.py +129 -6
- jolt/loader.py +369 -121
- jolt/log.py +225 -63
- jolt/manifest.py +28 -38
- jolt/options.py +35 -10
- jolt/pkgs/abseil.py +42 -0
- jolt/pkgs/asio.py +25 -0
- jolt/pkgs/autoconf.py +41 -0
- jolt/pkgs/automake.py +41 -0
- jolt/pkgs/b2.py +31 -0
- jolt/pkgs/boost.py +111 -0
- jolt/pkgs/boringssl.py +32 -0
- jolt/pkgs/busybox.py +39 -0
- jolt/pkgs/bzip2.py +43 -0
- jolt/pkgs/cares.py +29 -0
- jolt/pkgs/catch2.py +36 -0
- jolt/pkgs/cbindgen.py +17 -0
- jolt/pkgs/cista.py +19 -0
- jolt/pkgs/clang.py +44 -0
- jolt/pkgs/cli11.py +23 -0
- jolt/pkgs/cmake.py +48 -0
- jolt/pkgs/cpython.py +196 -0
- jolt/pkgs/crun.py +29 -0
- jolt/pkgs/curl.py +38 -0
- jolt/pkgs/dbus.py +18 -0
- jolt/pkgs/double_conversion.py +24 -0
- jolt/pkgs/fastfloat.py +21 -0
- jolt/pkgs/ffmpeg.py +28 -0
- jolt/pkgs/flatbuffers.py +29 -0
- jolt/pkgs/fmt.py +27 -0
- jolt/pkgs/fstree.py +20 -0
- jolt/pkgs/gflags.py +18 -0
- jolt/pkgs/glib.py +18 -0
- jolt/pkgs/glog.py +25 -0
- jolt/pkgs/glslang.py +21 -0
- jolt/pkgs/golang.py +16 -11
- jolt/pkgs/googlebenchmark.py +18 -0
- jolt/pkgs/googletest.py +46 -0
- jolt/pkgs/gperf.py +15 -0
- jolt/pkgs/grpc.py +73 -0
- jolt/pkgs/hdf5.py +19 -0
- jolt/pkgs/help2man.py +14 -0
- jolt/pkgs/inja.py +28 -0
- jolt/pkgs/jsoncpp.py +31 -0
- jolt/pkgs/libarchive.py +43 -0
- jolt/pkgs/libcap.py +44 -0
- jolt/pkgs/libdrm.py +44 -0
- jolt/pkgs/libedit.py +42 -0
- jolt/pkgs/libevent.py +31 -0
- jolt/pkgs/libexpat.py +27 -0
- jolt/pkgs/libfastjson.py +21 -0
- jolt/pkgs/libffi.py +16 -0
- jolt/pkgs/libglvnd.py +30 -0
- jolt/pkgs/libogg.py +28 -0
- jolt/pkgs/libpciaccess.py +18 -0
- jolt/pkgs/libseccomp.py +21 -0
- jolt/pkgs/libtirpc.py +24 -0
- jolt/pkgs/libtool.py +42 -0
- jolt/pkgs/libunwind.py +35 -0
- jolt/pkgs/libva.py +18 -0
- jolt/pkgs/libvorbis.py +33 -0
- jolt/pkgs/libxml2.py +35 -0
- jolt/pkgs/libxslt.py +17 -0
- jolt/pkgs/libyajl.py +16 -0
- jolt/pkgs/llvm.py +81 -0
- jolt/pkgs/lua.py +54 -0
- jolt/pkgs/lz4.py +26 -0
- jolt/pkgs/m4.py +14 -0
- jolt/pkgs/make.py +17 -0
- jolt/pkgs/mesa.py +81 -0
- jolt/pkgs/meson.py +17 -0
- jolt/pkgs/mstch.py +28 -0
- jolt/pkgs/mysql.py +60 -0
- jolt/pkgs/nasm.py +49 -0
- jolt/pkgs/ncurses.py +30 -0
- jolt/pkgs/ng_log.py +25 -0
- jolt/pkgs/ninja.py +45 -0
- jolt/pkgs/nlohmann_json.py +25 -0
- jolt/pkgs/nodejs.py +19 -11
- jolt/pkgs/opencv.py +24 -0
- jolt/pkgs/openjdk.py +26 -0
- jolt/pkgs/openssl.py +103 -0
- jolt/pkgs/paho.py +76 -0
- jolt/pkgs/patchelf.py +16 -0
- jolt/pkgs/perl.py +42 -0
- jolt/pkgs/pkgconfig.py +64 -0
- jolt/pkgs/poco.py +39 -0
- jolt/pkgs/protobuf.py +77 -0
- jolt/pkgs/pugixml.py +27 -0
- jolt/pkgs/python.py +19 -0
- jolt/pkgs/qt.py +35 -0
- jolt/pkgs/rapidjson.py +26 -0
- jolt/pkgs/rapidyaml.py +28 -0
- jolt/pkgs/re2.py +30 -0
- jolt/pkgs/re2c.py +17 -0
- jolt/pkgs/readline.py +15 -0
- jolt/pkgs/rust.py +41 -0
- jolt/pkgs/sdl.py +28 -0
- jolt/pkgs/simdjson.py +27 -0
- jolt/pkgs/soci.py +46 -0
- jolt/pkgs/spdlog.py +29 -0
- jolt/pkgs/spirv_llvm.py +21 -0
- jolt/pkgs/spirv_tools.py +24 -0
- jolt/pkgs/sqlite.py +83 -0
- jolt/pkgs/ssl.py +12 -0
- jolt/pkgs/texinfo.py +15 -0
- jolt/pkgs/tomlplusplus.py +22 -0
- jolt/pkgs/wayland.py +26 -0
- jolt/pkgs/x11.py +58 -0
- jolt/pkgs/xerces_c.py +20 -0
- jolt/pkgs/xorg.py +360 -0
- jolt/pkgs/xz.py +29 -0
- jolt/pkgs/yamlcpp.py +30 -0
- jolt/pkgs/zeromq.py +47 -0
- jolt/pkgs/zlib.py +69 -0
- jolt/pkgs/zstd.py +33 -0
- jolt/plugins/alias.py +3 -0
- jolt/plugins/allure.py +5 -2
- jolt/plugins/autotools.py +66 -0
- jolt/plugins/cache.py +133 -0
- jolt/plugins/cmake.py +74 -6
- jolt/plugins/conan.py +238 -0
- jolt/plugins/cxx.py +698 -0
- jolt/plugins/cxxinfo.py +7 -0
- jolt/plugins/dashboard.py +1 -1
- jolt/plugins/docker.py +91 -23
- jolt/plugins/email.py +5 -2
- jolt/plugins/email.xslt +144 -101
- jolt/plugins/environ.py +11 -0
- jolt/plugins/fetch.py +141 -0
- jolt/plugins/gdb.py +44 -21
- jolt/plugins/gerrit.py +1 -14
- jolt/plugins/git.py +316 -101
- jolt/plugins/googletest.py +522 -1
- jolt/plugins/http.py +36 -38
- jolt/plugins/libtool.py +63 -0
- jolt/plugins/linux.py +990 -0
- jolt/plugins/logstash.py +4 -4
- jolt/plugins/meson.py +61 -0
- jolt/plugins/ninja-compdb.py +107 -31
- jolt/plugins/ninja.py +929 -134
- jolt/plugins/paths.py +11 -1
- jolt/plugins/pkgconfig.py +219 -0
- jolt/plugins/podman.py +148 -91
- jolt/plugins/python.py +137 -0
- jolt/plugins/remote_execution/__init__.py +0 -0
- jolt/plugins/remote_execution/administration_pb2.py +46 -0
- jolt/plugins/remote_execution/administration_pb2_grpc.py +170 -0
- jolt/plugins/remote_execution/log_pb2.py +32 -0
- jolt/plugins/remote_execution/log_pb2_grpc.py +68 -0
- jolt/plugins/remote_execution/scheduler_pb2.py +41 -0
- jolt/plugins/remote_execution/scheduler_pb2_grpc.py +141 -0
- jolt/plugins/remote_execution/worker_pb2.py +38 -0
- jolt/plugins/remote_execution/worker_pb2_grpc.py +112 -0
- jolt/plugins/report.py +12 -2
- jolt/plugins/rust.py +25 -0
- jolt/plugins/scheduler.py +710 -0
- jolt/plugins/selfdeploy/setup.py +9 -4
- jolt/plugins/selfdeploy.py +138 -88
- jolt/plugins/strings.py +35 -22
- jolt/plugins/symlinks.py +26 -11
- jolt/plugins/telemetry.py +5 -2
- jolt/plugins/timeline.py +13 -3
- jolt/plugins/volume.py +46 -48
- jolt/scheduler.py +591 -191
- jolt/tasks.py +1783 -245
- jolt/templates/export.sh.template +12 -6
- jolt/templates/timeline.html.template +44 -47
- jolt/timer.py +22 -0
- jolt/tools.py +749 -302
- jolt/utils.py +245 -18
- jolt/version.py +1 -1
- jolt/version_utils.py +2 -2
- jolt/xmldom.py +12 -2
- {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/METADATA +98 -38
- jolt-0.9.429.dist-info/RECORD +207 -0
- {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/WHEEL +1 -1
- jolt/plugins/amqp.py +0 -834
- jolt/plugins/debian.py +0 -338
- jolt/plugins/ftp.py +0 -181
- jolt/plugins/ninja-cache.py +0 -64
- jolt/plugins/ninjacli.py +0 -271
- jolt/plugins/repo.py +0 -253
- jolt-0.9.76.dist-info/RECORD +0 -79
- {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/entry_points.txt +0 -0
- {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/top_level.txt +0 -0
jolt/plugins/git.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import pygit2
|
|
3
3
|
import re
|
|
4
|
+
from threading import RLock
|
|
5
|
+
import urllib.parse
|
|
4
6
|
|
|
5
|
-
from jolt.tasks import BooleanParameter, Export, Parameter, TaskRegistry, WorkspaceResource
|
|
7
|
+
from jolt.tasks import BooleanParameter, Export, IntParameter, Parameter, TaskRegistry, WorkspaceResource
|
|
6
8
|
from jolt.influence import FileInfluence, HashInfluenceRegistry
|
|
7
9
|
from jolt.tools import Tools
|
|
8
|
-
from jolt.loader import JoltLoader
|
|
10
|
+
from jolt.loader import JoltLoader, workspace_locked
|
|
9
11
|
from jolt import config
|
|
10
12
|
from jolt import filesystem as fs
|
|
11
13
|
from jolt import log
|
|
@@ -19,54 +21,119 @@ from jolt.error import raise_task_error_if
|
|
|
19
21
|
log.verbose("[Git] Loaded")
|
|
20
22
|
|
|
21
23
|
|
|
24
|
+
def locked(func):
|
|
25
|
+
def _f(self, *args, **kwargs):
|
|
26
|
+
with self._lock:
|
|
27
|
+
return func(self, *args, **kwargs)
|
|
28
|
+
return _f
|
|
29
|
+
|
|
30
|
+
|
|
22
31
|
class GitRepository(object):
|
|
23
32
|
def __init__(self, url, path, relpath, refspecs=None):
|
|
24
33
|
self.path = path
|
|
25
34
|
self.relpath = relpath
|
|
35
|
+
self.gitpath = None
|
|
26
36
|
self.tools = Tools()
|
|
27
37
|
self.url = url
|
|
28
38
|
self.default_refspecs = [
|
|
29
39
|
'+refs/heads/*:refs/remotes/origin/*',
|
|
30
|
-
'+refs/tags/*:refs/
|
|
40
|
+
'+refs/tags/*:refs/tags/*',
|
|
31
41
|
]
|
|
32
42
|
self.refspecs = refspecs or []
|
|
33
43
|
self._tree_hash = {}
|
|
34
44
|
self._original_head = True
|
|
45
|
+
self._last_rev = None
|
|
35
46
|
self._init_repo()
|
|
36
47
|
|
|
37
48
|
def _init_repo(self):
|
|
38
|
-
|
|
49
|
+
gitpath = os.path.join(self.path, ".git")
|
|
50
|
+
if os.path.isdir(os.path.join(self.path, ".git")):
|
|
51
|
+
self.gitpath = gitpath
|
|
52
|
+
self.repository = pygit2.Repository(self.gitpath)
|
|
53
|
+
elif os.path.exists(gitpath):
|
|
54
|
+
with self.tools.cwd(self.path):
|
|
55
|
+
path = self.tools.run("git rev-parse --git-dir", output=False)
|
|
56
|
+
self.gitpath = os.path.join(self.path, path)
|
|
57
|
+
self.repository = pygit2.Repository(self.gitpath)
|
|
58
|
+
else:
|
|
59
|
+
self.repository = None
|
|
39
60
|
|
|
40
61
|
@utils.cached.instance
|
|
41
62
|
def _git_folder(self):
|
|
42
|
-
return
|
|
63
|
+
return self.gitpath
|
|
43
64
|
|
|
44
65
|
@utils.cached.instance
|
|
45
66
|
def _git_index(self):
|
|
46
|
-
return fs.path.join(self.
|
|
67
|
+
return fs.path.join(self.gitpath, "index")
|
|
47
68
|
|
|
48
69
|
@utils.cached.instance
|
|
49
70
|
def _git_jolt_index(self):
|
|
50
|
-
return fs.path.join(self.
|
|
71
|
+
return fs.path.join(self.gitpath, "jolt-index")
|
|
51
72
|
|
|
52
73
|
def is_cloned(self):
|
|
53
|
-
return
|
|
74
|
+
return self.gitpath is not None
|
|
54
75
|
|
|
55
76
|
def is_indexed(self):
|
|
56
|
-
return fs.path.exists(self._git_index())
|
|
77
|
+
return self.is_cloned() and fs.path.exists(self._git_index())
|
|
57
78
|
|
|
58
|
-
def clone(self):
|
|
79
|
+
def clone(self, submodules=False):
|
|
59
80
|
log.info("Cloning into {0}", self.path)
|
|
81
|
+
|
|
82
|
+
# Get reference repository path
|
|
83
|
+
refroot = config.get("git", "reference", None)
|
|
84
|
+
refpath = None
|
|
85
|
+
if refroot:
|
|
86
|
+
# Append <host>/<path> to the reference root
|
|
87
|
+
url = urllib.parse.urlparse(str(self.url))
|
|
88
|
+
refpath = fs.path.join(refroot, url.hostname, url.path.lstrip("/"))
|
|
89
|
+
refpath = fs.path.abspath(refpath)
|
|
90
|
+
|
|
91
|
+
# If the directory exists, initialize the repository instead of cloning
|
|
60
92
|
if fs.path.exists(self.path):
|
|
61
93
|
with self.tools.cwd(self.path):
|
|
62
|
-
self.tools.run("git init
|
|
63
|
-
|
|
94
|
+
self.tools.run("git init", output_on_error=True)
|
|
95
|
+
|
|
96
|
+
# Set the reference repository if available
|
|
97
|
+
if refpath:
|
|
98
|
+
# Check if the reference repository is a git repository
|
|
99
|
+
objpath = os.path.join(refpath, ".git", "objects")
|
|
100
|
+
objpath_bare = os.path.join(refpath, "objects")
|
|
101
|
+
if os.path.isdir(objpath):
|
|
102
|
+
refpath = objpath
|
|
103
|
+
elif os.path.isdir(objpath_bare):
|
|
104
|
+
refpath = objpath_bare
|
|
105
|
+
else:
|
|
106
|
+
refpath = None
|
|
107
|
+
|
|
108
|
+
if refpath:
|
|
109
|
+
self.tools.mkdir(".git/objects/info")
|
|
110
|
+
self.tools.write_file(".git/objects/info/alternates", refpath)
|
|
111
|
+
|
|
112
|
+
utils.call_and_catch(self.tools.run, "git remote remove origin", output=False)
|
|
113
|
+
self.tools.run("git remote add origin {}", self.url, output_on_error=True)
|
|
114
|
+
self._fetch_origin(submodules=submodules)
|
|
115
|
+
self.tools.run("git checkout -f FETCH_HEAD", output_on_error=True)
|
|
64
116
|
else:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
117
|
+
# Get configurable extra clone options
|
|
118
|
+
extra_clone_options = config.get("git", "clone_options", "")
|
|
119
|
+
|
|
120
|
+
if refpath and os.path.isdir(refpath):
|
|
121
|
+
self.tools.run("git clone --reference-if-able {0} {1} {2} {3}", refpath, extra_clone_options, self.url, self.path, output_on_error=True)
|
|
122
|
+
else:
|
|
123
|
+
self.tools.run("git clone {0} {1} {2}", extra_clone_options, self.url, self.path, output_on_error=True)
|
|
124
|
+
|
|
69
125
|
self._init_repo()
|
|
126
|
+
raise_error_if(
|
|
127
|
+
self.repository is None,
|
|
128
|
+
"Failed to clone repository '{0}'", self.relpath)
|
|
129
|
+
|
|
130
|
+
@utils.retried.on_exception(JoltCommandError, pattern="Command failed: git fetch", count=6, backoff=[2, 5, 10, 15, 20, 30])
|
|
131
|
+
def _fetch_origin(self, submodules=False):
|
|
132
|
+
# Get configurable extra clone options
|
|
133
|
+
extra_fetch_options = config.get("git", "fetch_options", "")
|
|
134
|
+
|
|
135
|
+
with self.tools.cwd(self.path):
|
|
136
|
+
self.tools.run("git fetch {0} {1} origin", "", extra_fetch_options, output_on_error=True)
|
|
70
137
|
|
|
71
138
|
@utils.cached.instance
|
|
72
139
|
def diff_unchecked(self):
|
|
@@ -84,18 +151,18 @@ class GitRepository(object):
|
|
|
84
151
|
|
|
85
152
|
def diff(self):
|
|
86
153
|
diff = self.diff_unchecked()
|
|
87
|
-
dlim = config.getsize("git", "maxdiffsize", "
|
|
154
|
+
dlim = config.getsize("git", "maxdiffsize", "1 MiB")
|
|
88
155
|
raise_error_if(
|
|
89
156
|
len(diff) > dlim,
|
|
90
|
-
"
|
|
91
|
-
.format(self.
|
|
157
|
+
"Repository '{}' has uncommitted changes. Size of patch exceeds configured transfer limit ({} > {} bytes)."
|
|
158
|
+
.format(self.relpath, len(diff), dlim))
|
|
92
159
|
return diff
|
|
93
160
|
|
|
94
161
|
def patch(self, patch):
|
|
95
162
|
if not patch:
|
|
96
163
|
return
|
|
97
|
-
with self.tools.cwd(self.path), self.tools.tmpdir("git") as
|
|
98
|
-
patchfile = fs.path.join(
|
|
164
|
+
with self.tools.cwd(self.path), self.tools.tmpdir("git") as tmp:
|
|
165
|
+
patchfile = fs.path.join(tmp, "jolt.diff")
|
|
99
166
|
with open(patchfile, "wb") as f:
|
|
100
167
|
f.write(patch.encode())
|
|
101
168
|
log.info("Applying patch to {0}", self.path)
|
|
@@ -122,17 +189,18 @@ class GitRepository(object):
|
|
|
122
189
|
try:
|
|
123
190
|
commit = self.repository.revparse_single(rev)
|
|
124
191
|
except KeyError:
|
|
125
|
-
self.fetch()
|
|
192
|
+
self.fetch(commit=rev)
|
|
126
193
|
try:
|
|
127
194
|
commit = self.repository.revparse_single(rev)
|
|
128
195
|
except Exception:
|
|
129
|
-
raise_error("
|
|
196
|
+
raise_error("Invalid git reference: {}", rev)
|
|
130
197
|
try:
|
|
131
198
|
return str(commit.id)
|
|
132
199
|
except Exception:
|
|
133
200
|
return str(commit)
|
|
134
201
|
|
|
135
202
|
@utils.cached.instance
|
|
203
|
+
@workspace_locked
|
|
136
204
|
def write_tree(self):
|
|
137
205
|
tools = Tools()
|
|
138
206
|
with tools.cwd(self._git_folder()):
|
|
@@ -143,30 +211,30 @@ class GitRepository(object):
|
|
|
143
211
|
output_on_error=True)
|
|
144
212
|
return tree
|
|
145
213
|
|
|
146
|
-
def tree_hash(self,
|
|
147
|
-
# When
|
|
214
|
+
def tree_hash(self, rev=None, path="/"):
|
|
215
|
+
# When rev is None, the caller want the tree hash of the repository's
|
|
148
216
|
# current workspace state. If no checkout has been made, that would be the
|
|
149
217
|
# tree that was written upon initialization of the repository as it
|
|
150
218
|
# includes any uncommitted changes. If a checkout has been made since
|
|
151
219
|
# the repo was initialized, make this an explicit request for the current
|
|
152
220
|
# head - there can be no local changes.
|
|
153
|
-
if
|
|
221
|
+
if rev is None:
|
|
154
222
|
if self.is_original_head():
|
|
155
223
|
tree = self.repository.get(self.write_tree())
|
|
156
224
|
else:
|
|
157
|
-
|
|
225
|
+
rev = self.head()
|
|
158
226
|
|
|
159
227
|
path = fs.path.normpath(path)
|
|
160
228
|
full_path = fs.path.join(self.path, path) if path != "/" else self.path
|
|
161
229
|
|
|
162
230
|
# Lookup tree hash value in cache
|
|
163
|
-
value = self._tree_hash.get((full_path,
|
|
231
|
+
value = self._tree_hash.get((full_path, rev))
|
|
164
232
|
if value is not None:
|
|
165
233
|
return value
|
|
166
234
|
|
|
167
|
-
# Translate explicit
|
|
168
|
-
if
|
|
169
|
-
commit = self.rev_parse(
|
|
235
|
+
# Translate explicit rev to tree
|
|
236
|
+
if rev is not None:
|
|
237
|
+
commit = self.rev_parse(rev)
|
|
170
238
|
obj = self.repository.get(commit)
|
|
171
239
|
try:
|
|
172
240
|
tree = obj.tree
|
|
@@ -178,7 +246,7 @@ class GitRepository(object):
|
|
|
178
246
|
tree = tree[fs.as_posix(path)]
|
|
179
247
|
|
|
180
248
|
# Update tree hash cache
|
|
181
|
-
self._tree_hash[(full_path,
|
|
249
|
+
self._tree_hash[(full_path, rev)] = value = tree.id
|
|
182
250
|
|
|
183
251
|
return value
|
|
184
252
|
|
|
@@ -190,24 +258,49 @@ class GitRepository(object):
|
|
|
190
258
|
with self.tools.cwd(self.path):
|
|
191
259
|
return self.tools.run("git reset --hard", output_on_error=True)
|
|
192
260
|
|
|
193
|
-
|
|
261
|
+
@utils.retried.on_exception(JoltCommandError, pattern="Command failed: git fetch", count=6, backoff=[2, 5, 10, 15, 20, 30])
|
|
262
|
+
def fetch(self, commit=None):
|
|
263
|
+
if commit and not self.is_valid_sha(commit):
|
|
264
|
+
commit = None
|
|
265
|
+
|
|
266
|
+
# Get configurable extra clone options
|
|
267
|
+
extra_fetch_options = config.get("git", "fetch_options", "")
|
|
268
|
+
|
|
194
269
|
refspec = " ".join(self.default_refspecs + self.refspecs)
|
|
195
270
|
with self.tools.cwd(self.path):
|
|
196
|
-
log.info("Fetching {0} from {1}", refspec or 'commits', self.url)
|
|
197
|
-
self.tools.run(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
271
|
+
log.info("Fetching {0} from {1}", commit or refspec or 'commits', self.url)
|
|
272
|
+
self.tools.run(
|
|
273
|
+
"git fetch --force --prune {extra_fetch_options} {url} {what}",
|
|
274
|
+
extra_fetch_options=extra_fetch_options,
|
|
275
|
+
url=self.url,
|
|
276
|
+
what=commit or refspec or '',
|
|
277
|
+
output_on_error=True)
|
|
278
|
+
|
|
279
|
+
def checkout(self, rev, commit=None, submodules=False):
|
|
280
|
+
if rev == self._last_rev:
|
|
281
|
+
log.debug("Checkout skipped, already @ {}", rev)
|
|
282
|
+
return False
|
|
283
|
+
log.verbose("Checking out {0} in {1}", rev, self.path)
|
|
204
284
|
with self.tools.cwd(self.path):
|
|
205
285
|
try:
|
|
206
|
-
|
|
286
|
+
self.tools.run("git checkout -f {rev}", rev=rev, output=False)
|
|
287
|
+
if submodules:
|
|
288
|
+
self._update_submodules()
|
|
207
289
|
except Exception:
|
|
208
|
-
self.fetch()
|
|
209
|
-
|
|
290
|
+
self.fetch(commit=commit)
|
|
291
|
+
try:
|
|
292
|
+
self.tools.run("git checkout -f {rev}", rev=rev, output_on_error=True)
|
|
293
|
+
if submodules:
|
|
294
|
+
self._update_submodules()
|
|
295
|
+
except Exception:
|
|
296
|
+
raise_error("Commit does not exist in remote for '{}': {}", self.relpath, rev)
|
|
210
297
|
self._original_head = False
|
|
298
|
+
self._last_rev = rev
|
|
299
|
+
return True
|
|
300
|
+
|
|
301
|
+
def _update_submodules(self):
|
|
302
|
+
with self.tools.cwd(self.path):
|
|
303
|
+
self.tools.run("git submodule update --init --recursive", output_on_error=True)
|
|
211
304
|
|
|
212
305
|
|
|
213
306
|
_gits = {}
|
|
@@ -217,9 +310,9 @@ def new_git(url, path, relpath, refspecs=None):
|
|
|
217
310
|
refspecs = utils.as_list(refspecs or [])
|
|
218
311
|
try:
|
|
219
312
|
git = _gits[path]
|
|
220
|
-
raise_error_if(git.url != url, "
|
|
313
|
+
raise_error_if(git.url != url, "Multiple git repositories required at {}", relpath)
|
|
221
314
|
raise_error_if(git.refspecs != refspecs,
|
|
222
|
-
"
|
|
315
|
+
"Conflicting refspecs detected for git repository at {}", relpath)
|
|
223
316
|
return git
|
|
224
317
|
except Exception:
|
|
225
318
|
git = _gits[path] = GitRepository(url, path, relpath, refspecs)
|
|
@@ -230,7 +323,7 @@ class GitInfluenceProvider(FileInfluence):
|
|
|
230
323
|
name = "Git"
|
|
231
324
|
|
|
232
325
|
def __init__(self, path):
|
|
233
|
-
super(
|
|
326
|
+
super().__init__(path)
|
|
234
327
|
self.path = path.rstrip(fs.sep)
|
|
235
328
|
self.name = GitInfluenceProvider.name
|
|
236
329
|
|
|
@@ -241,11 +334,11 @@ class GitInfluenceProvider(FileInfluence):
|
|
|
241
334
|
def _find_dotgit(self, path):
|
|
242
335
|
ppath = None
|
|
243
336
|
while path != ppath:
|
|
244
|
-
if fs.path.
|
|
337
|
+
if fs.path.exists(fs.path.join(path, ".git")):
|
|
245
338
|
return path
|
|
246
339
|
ppath = path
|
|
247
340
|
path = fs.path.dirname(path)
|
|
248
|
-
raise_error("
|
|
341
|
+
raise_error("No git repository found at '{}'", self.path)
|
|
249
342
|
|
|
250
343
|
@utils.cached.instance
|
|
251
344
|
def get_influence(self, task):
|
|
@@ -260,8 +353,9 @@ class GitInfluenceProvider(FileInfluence):
|
|
|
260
353
|
return "{0}/{1}: N/A".format(git_rel, relpath)
|
|
261
354
|
try:
|
|
262
355
|
git = new_git(None, git_abs, git_rel)
|
|
263
|
-
|
|
264
356
|
return "{0}/{1}: {2}".format(git_rel, relpath, git.tree_hash(path=relpath or "/"))
|
|
357
|
+
except KeyError:
|
|
358
|
+
return "{0}/{1}: N/A".format(git_rel, relpath)
|
|
265
359
|
except JoltCommandError as e:
|
|
266
360
|
stderr = "\n".join(e.stderr)
|
|
267
361
|
if "exists on disk, but not in" in stderr:
|
|
@@ -292,23 +386,101 @@ def influence(path, git_cls=GitInfluenceProvider):
|
|
|
292
386
|
return _decorate
|
|
293
387
|
|
|
294
388
|
|
|
295
|
-
class
|
|
296
|
-
"""
|
|
389
|
+
class ErrorDict(dict):
|
|
390
|
+
""" A dict that raises an error if the value of a key is None. """
|
|
391
|
+
|
|
392
|
+
def __init__(self, repo):
|
|
393
|
+
self.repo = repo
|
|
394
|
+
|
|
395
|
+
def __getitem__(self, key):
|
|
396
|
+
value = super().__getitem__(key)
|
|
397
|
+
raise_task_error_if(value is None, self.repo, "Git repository '{0}' referenced in influence collection before being cloned/checked out. Assign hash=true to the git requirement.", key)
|
|
398
|
+
return value
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class Git(WorkspaceResource, FileInfluence):
|
|
402
|
+
"""
|
|
403
|
+
Resource that clones and monitors a Git repo.
|
|
404
|
+
|
|
405
|
+
By default, the repo is cloned into a build directory named after
|
|
406
|
+
the resource. The 'path' parameter can be used to specify a different
|
|
407
|
+
location relative to the workspace root.
|
|
408
|
+
|
|
409
|
+
The path of the cloned repo is made available to consuming tasks
|
|
410
|
+
through their 'git' attribute. The 'git' attribute is a dictionary
|
|
411
|
+
where the key is the name of the git repository and the value is
|
|
412
|
+
the relative path to the repository from the consuming task's
|
|
413
|
+
workspace.
|
|
414
|
+
|
|
415
|
+
The resource influences the hash of consuming tasks, causing tasks
|
|
416
|
+
to be re-executed if the cloned repo is modified.
|
|
417
|
+
|
|
418
|
+
The plugin must be loaded before it can be used. This is done by
|
|
419
|
+
importing the module, or by adding the following line to the
|
|
420
|
+
configuration file:
|
|
421
|
+
|
|
422
|
+
.. code-block:: ini
|
|
423
|
+
|
|
424
|
+
[git]
|
|
425
|
+
|
|
426
|
+
Example:
|
|
427
|
+
|
|
428
|
+
.. code-block:: python
|
|
429
|
+
|
|
430
|
+
from jolt.plugins import git
|
|
431
|
+
|
|
432
|
+
class Example(Task):
|
|
433
|
+
requires = ["git:url=https://github.com/user/repo.git"]
|
|
434
|
+
|
|
435
|
+
def run(self, deps, tools):
|
|
436
|
+
self.info("The git repo is located at: {git[repo]}")
|
|
437
|
+
with tools.cwd(self.git["repo"]):
|
|
438
|
+
tools.run("make")
|
|
439
|
+
|
|
297
440
|
"""
|
|
441
|
+
name = "git"
|
|
298
442
|
|
|
299
|
-
name = "git-src"
|
|
300
443
|
url = Parameter(help="URL to the git repo to be cloned. Required.")
|
|
301
|
-
|
|
444
|
+
""" URL to the git repo to be cloned. Required. """
|
|
445
|
+
|
|
446
|
+
rev = Parameter(required=False, help="Specific commit or tag to be checked out. Optional.")
|
|
447
|
+
""" Specific commit or tag to be checked out. Optional. """
|
|
448
|
+
|
|
449
|
+
hash = BooleanParameter(required=False, help="Let repo content influence the hash of consuming tasks.")
|
|
450
|
+
""" Let repo content influence the hash of consuming tasks. Default ``True``. Optional. """
|
|
451
|
+
|
|
302
452
|
path = Parameter(required=False, help="Local path where the repository should be cloned.")
|
|
303
|
-
|
|
453
|
+
""" Alternative path where the repository should be cloned. Relative to ``joltdir``. Optional. """
|
|
454
|
+
|
|
455
|
+
submodules = BooleanParameter(default=False, help="Initialize and update git submodules after cloning.")
|
|
456
|
+
""" Initialize and update git submodules after cloning. Default ``False``. Optional. """
|
|
457
|
+
|
|
458
|
+
clean = BooleanParameter(default=False, help="Clean repository after it has been used")
|
|
459
|
+
""" Removes untracked files from the repository. """
|
|
460
|
+
|
|
304
461
|
_revision = Export(value=lambda t: t._export_revision())
|
|
462
|
+
""" To worker exported value of the revision to be checked out. """
|
|
463
|
+
|
|
305
464
|
_diff = Export(value=lambda t: t.git.diff(), encoded=True)
|
|
465
|
+
""" To worker exported value of the diff of the repo. """
|
|
306
466
|
|
|
307
467
|
def __init__(self, *args, **kwargs):
|
|
308
|
-
super(
|
|
468
|
+
super().__init__(*args, **kwargs)
|
|
469
|
+
self._lock = RLock()
|
|
309
470
|
self.joltdir = JoltLoader.get().joltdir
|
|
310
|
-
|
|
311
|
-
|
|
471
|
+
|
|
472
|
+
# Set the path to the repo
|
|
473
|
+
if self.path.is_unset():
|
|
474
|
+
self.abspath = self.tools.builddir(utils.canonical(self.short_qualified_name), incremental="always", unique=False)
|
|
475
|
+
self.relpath = fs.path.relpath(self.abspath, self.tools.wsroot)
|
|
476
|
+
else:
|
|
477
|
+
self.abspath = fs.path.join(self.joltdir, str(self.path) or self._get_name())
|
|
478
|
+
self.relpath = fs.path.relpath(self.abspath, self.tools.wsroot)
|
|
479
|
+
|
|
480
|
+
self.abspath = fs.path.normpath(self.abspath)
|
|
481
|
+
self.relpath = fs.path.normpath(self.relpath)
|
|
482
|
+
|
|
483
|
+
# Create the git repository
|
|
312
484
|
self.refspecs = kwargs.get("refspecs", [])
|
|
313
485
|
self.git = new_git(self.url, self.abspath, self.relpath, self.refspecs)
|
|
314
486
|
|
|
@@ -319,83 +491,126 @@ class GitSrc(WorkspaceResource, FileInfluence):
|
|
|
319
491
|
return name
|
|
320
492
|
|
|
321
493
|
def _export_revision(self):
|
|
322
|
-
return self.
|
|
494
|
+
return self.rev.value or self.git.head()
|
|
323
495
|
|
|
324
496
|
def _get_revision(self):
|
|
325
497
|
if self._revision.is_imported:
|
|
326
498
|
return self._revision.value
|
|
327
|
-
if not self.
|
|
328
|
-
return self.
|
|
499
|
+
if not self.rev.is_unset():
|
|
500
|
+
return self.rev.get_value()
|
|
329
501
|
return None
|
|
330
502
|
|
|
331
|
-
def
|
|
503
|
+
def _assign_git(self, task, none=False):
|
|
504
|
+
if not hasattr(task, "git"):
|
|
505
|
+
task.git = ErrorDict(self)
|
|
506
|
+
if none:
|
|
507
|
+
# None means the git repo is not cloned or checked out
|
|
508
|
+
# and should not be included in the git dictionary
|
|
509
|
+
# of the consuming task yet. If the consuming task
|
|
510
|
+
# requires the git repo for its influence collection,
|
|
511
|
+
# the dict will raise an error. The solution is to
|
|
512
|
+
# assign hash=true to the git requirement which
|
|
513
|
+
# will cause the git repo to be cloned and checked out
|
|
514
|
+
# before the influence collection is performed.
|
|
515
|
+
task.git[self._get_name()] = None
|
|
516
|
+
else:
|
|
517
|
+
# Assign the git repo to the consuming task.
|
|
518
|
+
# The git repo is cloned and checked out before
|
|
519
|
+
# any influence collection is performed.
|
|
520
|
+
task.git[self._get_name()] = fs.path.relpath(self.abspath, task.joltdir)
|
|
521
|
+
|
|
522
|
+
def acquire(self, artifact, deps, tools, owner):
|
|
332
523
|
self._acquire_ws()
|
|
524
|
+
self._assign_git(owner)
|
|
525
|
+
artifact.worktree = fs.path.relpath(self.abspath, owner.joltdir)
|
|
526
|
+
|
|
527
|
+
def release(self, artifact, deps, tools, owner):
|
|
528
|
+
if self.clean:
|
|
529
|
+
self.git.clean()
|
|
530
|
+
self.git.reset()
|
|
531
|
+
|
|
532
|
+
def prepare_ws_for(self, task):
|
|
533
|
+
""" Prepare the workspace for the task.
|
|
534
|
+
|
|
535
|
+
:param task: The task to prepare the workspace for.
|
|
536
|
+
"""
|
|
537
|
+
if not self._must_influence():
|
|
538
|
+
# The content of the git repo is not required to influence the hash of the
|
|
539
|
+
# consumer task. The repo is therefore not cloned or checked out
|
|
540
|
+
# until the consumer is executed. Raise an error if the git repo
|
|
541
|
+
# is required for the influence collection of the consumer task.
|
|
542
|
+
self._assign_git(task, none=True)
|
|
543
|
+
return
|
|
544
|
+
# The content of the git repo is required to influence the hash of the consumer task.
|
|
545
|
+
self._assign_git(task)
|
|
333
546
|
|
|
334
|
-
def acquire_ws(self):
|
|
335
|
-
|
|
547
|
+
def acquire_ws(self, force=False):
|
|
548
|
+
""" Clone and/or checkout the git repo if required """
|
|
549
|
+
if force or self._must_influence() or self._revision.is_imported:
|
|
336
550
|
self._acquire_ws()
|
|
337
551
|
|
|
552
|
+
@locked
|
|
338
553
|
def _acquire_ws(self):
|
|
554
|
+
commit = None
|
|
339
555
|
if not self.git.is_cloned():
|
|
340
|
-
self.git.clone()
|
|
556
|
+
self.git.clone(submodules=bool(self.submodules))
|
|
341
557
|
if not self._revision.is_imported:
|
|
342
558
|
self.git.diff_unchecked()
|
|
559
|
+
else:
|
|
560
|
+
commit = self._revision.value
|
|
343
561
|
rev = self._get_revision()
|
|
344
562
|
if rev is not None:
|
|
345
563
|
raise_task_error_if(
|
|
346
|
-
not self._revision.is_imported and not self.
|
|
347
|
-
"
|
|
564
|
+
not self._revision.is_imported and not self.rev.is_unset() and self.git.diff(), self,
|
|
565
|
+
"Explicit revision requested but git repo '{0}' has local changes, refusing checkout", self.git.relpath)
|
|
348
566
|
# Should be safe to do this now
|
|
349
567
|
rev = self.git.rev_parse(rev)
|
|
350
568
|
if not self.git.is_head(rev) or self._revision.is_imported:
|
|
351
|
-
self.git.checkout(rev)
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
def get_influence(self, task):
|
|
356
|
-
return None
|
|
357
|
-
|
|
358
|
-
def is_influenced_by(self, task, path):
|
|
359
|
-
return fs.is_relative_to(path, self.abspath) and self.sha.is_set()
|
|
569
|
+
if self.git.checkout(rev, commit=commit, submodules=bool(self.submodules)):
|
|
570
|
+
self.git.clean()
|
|
571
|
+
self.git.patch(self._diff.value)
|
|
360
572
|
|
|
573
|
+
def _must_influence(self):
|
|
574
|
+
""" Check if the git repo must influence the hash of the consumer task."""
|
|
361
575
|
|
|
362
|
-
|
|
576
|
+
# If the hash parameter is set, honor it
|
|
577
|
+
if self.hash.is_set():
|
|
578
|
+
return self.hash
|
|
363
579
|
|
|
580
|
+
# If the revision parameter is not set, the git repo must influence the hash
|
|
581
|
+
if self.rev.is_unset():
|
|
582
|
+
return True
|
|
364
583
|
|
|
365
|
-
|
|
366
|
-
|
|
584
|
+
# If the revision parameter is set, no influence is needed since the
|
|
585
|
+
# revision is fixed and repository content will not change.
|
|
586
|
+
return False
|
|
367
587
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
"""
|
|
372
|
-
name = "git"
|
|
373
|
-
url = Parameter(help="URL to the git repo to be cloned. Required.")
|
|
374
|
-
sha = Parameter(required=False, help="Specific commit or tag to be checked out. Optional.")
|
|
375
|
-
path = Parameter(required=False, help="Local path where the repository should be cloned.")
|
|
376
|
-
defer = None
|
|
377
|
-
_revision = Export(value=lambda t: t._export_revision())
|
|
378
|
-
_diff = Export(value=lambda t: t.git.diff(), encoded=True)
|
|
588
|
+
def is_influenced_by(self, task, path):
|
|
589
|
+
influencing = self._must_influence() or self.rev.is_set()
|
|
590
|
+
return influencing and fs.is_relative_to(path, self.abspath)
|
|
379
591
|
|
|
380
|
-
def
|
|
381
|
-
super(
|
|
382
|
-
self.
|
|
592
|
+
def _influence(self):
|
|
593
|
+
influence = super()._influence()
|
|
594
|
+
return influence + [self] if self._must_influence() else influence
|
|
383
595
|
|
|
596
|
+
@locked
|
|
384
597
|
@utils.cached.instance
|
|
385
598
|
def get_influence(self, task):
|
|
386
599
|
if not self.git.is_cloned():
|
|
387
|
-
self.git.clone()
|
|
600
|
+
self.git.clone(submodules=bool(self.submodules))
|
|
388
601
|
if not self._revision.is_imported:
|
|
389
602
|
self.git.diff_unchecked()
|
|
390
603
|
rev = self._get_revision()
|
|
391
|
-
if rev is not None:
|
|
392
|
-
return self.git.tree_hash(rev)
|
|
393
|
-
return "{0}: {1}".format(
|
|
394
|
-
self.git.relpath,
|
|
395
|
-
self.git.tree_hash())
|
|
396
604
|
|
|
397
|
-
|
|
398
|
-
|
|
605
|
+
try:
|
|
606
|
+
if rev is not None:
|
|
607
|
+
th = self.git.tree_hash(rev)
|
|
608
|
+
else:
|
|
609
|
+
th = self.git.tree_hash()
|
|
610
|
+
except KeyError:
|
|
611
|
+
th = "N/A"
|
|
612
|
+
|
|
613
|
+
return "{0}: {1}".format(self.git.relpath, th)
|
|
399
614
|
|
|
400
615
|
|
|
401
616
|
TaskRegistry.get().add_task_class(Git)
|