@elixir-playground/git 0.0.0-stage → 1.0.0

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.
Files changed (43) hide show
  1. package/NOTICE.md +15 -0
  2. package/fs/bundles/0.tar +0 -0
  3. package/fs/manifest.json +1 -0
  4. package/git.wasm +0 -0
  5. package/index.d.ts +4 -0
  6. package/index.js +34 -0
  7. package/licenses/git-2.56.0/COPYING +360 -0
  8. package/licenses/git-2.56.0/LGPL-2.1 +511 -0
  9. package/licenses/index.json +51 -0
  10. package/licenses/wasi-compat-playground/LICENSE +18 -0
  11. package/licenses/wasi-libc-2e6fb9d8ee0c/LICENSE +17 -0
  12. package/licenses/wasi-libc-2e6fb9d8ee0c/LICENSE-APACHE +201 -0
  13. package/licenses/wasi-libc-2e6fb9d8ee0c/LICENSE-APACHE-LLVM +220 -0
  14. package/licenses/wasi-libc-2e6fb9d8ee0c/LICENSE-MIT +23 -0
  15. package/licenses/wasi-libc-2e6fb9d8ee0c/cloudlibc-LICENSE +22 -0
  16. package/licenses/wasi-libc-2e6fb9d8ee0c/musl-COPYRIGHT +193 -0
  17. package/licenses/wasi-libc-2e6fb9d8ee0c/musl-fts-COPYING +26 -0
  18. package/licenses/zlib-1.3.2/LICENSE +22 -0
  19. package/package.json +44 -4
  20. package/source/git/README.md +17 -0
  21. package/source/git/git-2.56.0.tar.xz +0 -0
  22. package/source/git/tools/git/build.py +168 -0
  23. package/source/git/tools/git/config.mak +42 -0
  24. package/source/git/tools/git/patches/0001-wasi-spawn.patch +76 -0
  25. package/source/git/tools/git/patches/0002-wasi-async.patch +124 -0
  26. package/source/git/tools/wasi-compat/LICENSE +18 -0
  27. package/source/git/tools/wasi-compat/autoconf.py +158 -0
  28. package/source/git/tools/wasi-compat/config.site +31 -0
  29. package/source/git/tools/wasi-compat/include/grp.h +6 -0
  30. package/source/git/tools/wasi-compat/include/mntent.h +10 -0
  31. package/source/git/tools/wasi-compat/include/netdb.h +7 -0
  32. package/source/git/tools/wasi-compat/include/paths.h +2 -0
  33. package/source/git/tools/wasi-compat/include/pwd.h +5 -0
  34. package/source/git/tools/wasi-compat/include/setjmp.h +17 -0
  35. package/source/git/tools/wasi-compat/include/sys/wait.h +8 -0
  36. package/source/git/tools/wasi-compat/include/syslog.h +7 -0
  37. package/source/git/tools/wasi-compat/include/termios.h +10 -0
  38. package/source/git/tools/wasi-compat/include/wasi-compat.h +38 -0
  39. package/source/git/tools/wasi-compat/include/wasi-posix.h +79 -0
  40. package/source/git/tools/wasi-compat/stubs.c +326 -0
  41. package/source/git/tools/wasi-compat/wrap.c +54 -0
  42. package/source/git/zlib-1.3.2.tar.gz +0 -0
  43. package/README.md +0 -3
@@ -0,0 +1,26 @@
1
+ Copyright (c) 1989, 1993
2
+ The Regents of the University of California. All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions
6
+ are met:
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright
10
+ notice, this list of conditions and the following disclaimer in the
11
+ documentation and/or other materials provided with the distribution.
12
+ 3. Neither the name of the University nor the names of its contributors
13
+ may be used to endorse or promote products derived from this software
14
+ without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
17
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
20
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26
+ SUCH DAMAGE.
@@ -0,0 +1,22 @@
1
+ Copyright notice:
2
+
3
+ (C) 1995-2026 Jean-loup Gailly and Mark Adler
4
+
5
+ This software is provided 'as-is', without any express or implied
6
+ warranty. In no event will the authors be held liable for any damages
7
+ arising from the use of this software.
8
+
9
+ Permission is granted to anyone to use this software for any purpose,
10
+ including commercial applications, and to alter it and redistribute it
11
+ freely, subject to the following restrictions:
12
+
13
+ 1. The origin of this software must not be misrepresented; you must not
14
+ claim that you wrote the original software. If you use this software
15
+ in a product, an acknowledgment in the product documentation would be
16
+ appreciated but is not required.
17
+ 2. Altered source versions must be plainly marked as such, and must not be
18
+ misrepresented as being the original software.
19
+ 3. This notice may not be removed or altered from any source distribution.
20
+
21
+ Jean-loup Gailly Mark Adler
22
+ jloup@gzip.org madler@alumni.caltech.edu
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "@elixir-playground/git",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "license": "(Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND BSD-2-Clause AND BSD-3-Clause AND GPL-2.0-only AND MIT AND Zlib",
5
+ "files": [
6
+ "NOTICE.md",
7
+ "fs",
8
+ "git.wasm",
9
+ "index.d.ts",
10
+ "index.js",
11
+ "licenses",
12
+ "source"
13
+ ],
14
+ "type": "module",
15
+ "types": "./index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./index.d.ts",
19
+ "default": "./index.js"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "peerDependencies": {
24
+ "@elixir-playground/runtime": "^1.0.0"
25
+ },
26
+ "playground": {
27
+ "abi": {
28
+ "wasi": 1,
29
+ "processes": 1
30
+ },
31
+ "programs": {
32
+ "git": {
33
+ "module": "git.wasm"
34
+ }
35
+ },
36
+ "fs": {
37
+ "format": 3,
38
+ "root": "/usr/libexec/git-core",
39
+ "manifest": "fs/manifest.json",
40
+ "bundles": "fs/bundles/",
41
+ "count": 1,
42
+ "version": "14ca27ec67928487"
43
+ }
44
+ },
45
+ "playgroundDigest": "a1d07507e15e"
46
+ }
@@ -0,0 +1,17 @@
1
+ # git 2.56.0: corresponding source
2
+
3
+ The source of the WebAssembly build of git 2.56.0 in this package,
4
+ under GPL-2.0-only (its COPYING): what the build is made from, as the
5
+ playground's repository has it.
6
+
7
+ - `git-2.56.0.tar.xz`
8
+ - `tools/git/build.py`
9
+ - `tools/git/config.mak`
10
+ - `tools/git/patches`
11
+ - `tools/wasi-compat`
12
+ - `zlib-1.3.2.tar.gz`
13
+
14
+ The build applies `patches/` to the release, links `wasi-compat/` (MIT)
15
+ and wasi-libc, and compiles with the wasi-sdk Docker image its script
16
+ names by digest. From a checkout of the playground's repository, with
17
+ these files in their places there, `python3 -I tools/git/build.py` builds it.
Binary file
@@ -0,0 +1,168 @@
1
+ """git built to WebAssembly (wasm32-wasip1): the git CLI itself, for the
2
+ shell and the nodes (docs/git.md).
3
+
4
+ python3 -I tools/git/build.py # → target/git/git.wasm
5
+
6
+ git's release with patches/ applied, and zlib, built with wasi-sdk in
7
+ Docker (backend/Dockerfile's wasi-sdk). WASI preview 1 lacks processes, users,
8
+ modes, terminals and sockets: tools/wasi-compat/ (every C tool's) declares what
9
+ git asks of them and stubs it (stubs.c), its processes the playground's
10
+ host's (the `processes` imports, app/src/runtime/processes.ts:
11
+ run-command.c spawns instead of forking), and config.mak turns off the
12
+ rest.
13
+ """
14
+
15
+ import hashlib
16
+ import importlib.util
17
+ import os
18
+ import shutil
19
+ import subprocess
20
+ import tarfile
21
+ import urllib.request
22
+
23
+ # The repository (this file is tools/NAME/build.py).
24
+ ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
25
+ HERE = os.path.dirname(os.path.abspath(__file__))
26
+ # What WASI lacks, for every C tool (wasi-compat.h, stubs.c).
27
+ COMPAT = os.path.join(ROOT, "tools", "wasi-compat")
28
+ OUT = os.path.join(ROOT, "target", "git")
29
+ DOCKER = os.environ.get("DOCKER", "sudo -n docker").split()
30
+ # wasi-sdk 33 (clang 23), as the backend's NIF toolchain (backend/Dockerfile).
31
+ IMAGE = "ghcr.io/webassembly/wasi-sdk@sha256:52595085a0ffbaf574d8d678a9aa81305f0b3af81c639291f15bc7713e42848e"
32
+ GIT = ("2.56.0", "https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.56.0.tar.xz",
33
+ "26c56c296b38c0695b26fa95f475f1d01704d2d38e73465ca30b0b2f5dc789d3")
34
+ ZLIB = ("1.3.2", "https://github.com/madler/zlib/releases/download/v1.3.2/zlib-1.3.2.tar.gz",
35
+ "bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16")
36
+ # git's Makefile reads `uname` before config.mak: on the command line, so
37
+ # that the host's (Linux) settings stay out.
38
+ UNAME = "uname_S=WASI uname_M=wasm32 uname_O=WASI uname_R=1 uname_V=1"
39
+
40
+
41
+ def fetch(source, sha256, path):
42
+ if not os.path.exists(path):
43
+ urllib.request.urlretrieve(source, path + ".part")
44
+ with open(path + ".part", "rb") as f:
45
+ got = hashlib.sha256(f.read()).hexdigest()
46
+ if got != sha256:
47
+ raise SystemExit(f"{source}: sha256 {got}, not {sha256}")
48
+ os.rename(path + ".part", path)
49
+
50
+
51
+ def stamp():
52
+ """What the build is made from: the image, the sources, our files."""
53
+ hashed = hashlib.sha256(f"{IMAGE}\0{GIT[2]}\0{ZLIB[2]}\0".encode())
54
+ for top in (HERE, COMPAT):
55
+ for directory, _, names in sorted(os.walk(top)):
56
+ for name in sorted(names):
57
+ if name.endswith(".py"):
58
+ continue
59
+ path = os.path.join(directory, name)
60
+ with open(path, "rb") as f:
61
+ hashed.update(os.path.relpath(path, ROOT).encode() + b"\0" + f.read())
62
+ return hashed.hexdigest()
63
+
64
+
65
+ def licensed(git_archive, zlib_archive):
66
+ """target/git/licenses (tools/licenses.py) and target/git/source: git is
67
+ GPL-2.0, so its source goes with it: the releases, the patches and the
68
+ build's files, zlib's too (git links it)."""
69
+ spec = importlib.util.spec_from_file_location("licenses", os.path.join(ROOT, "tools", "licenses.py"))
70
+ licenses = importlib.util.module_from_spec(spec)
71
+ spec.loader.exec_module(licenses)
72
+ if licenses.current(OUT, stamp()):
73
+ return
74
+ fetch(GIT[1], GIT[2], git_archive)
75
+ fetch(ZLIB[1], ZLIB[2], zlib_archive)
76
+ zlib = licenses.Inventory().add("zlib", ZLIB[0], "Zlib", "https://zlib.net",
77
+ licenses.archive_texts(zlib_archive, f"zlib-{ZLIB[0]}", ["LICENSE"]))
78
+ sources = {os.path.basename(git_archive): git_archive, os.path.basename(zlib_archive): zlib_archive,
79
+ "tools/git/build.py": os.path.join(HERE, "build.py"),
80
+ "tools/git/config.mak": os.path.join(HERE, "config.mak"),
81
+ "tools/git/patches": os.path.join(HERE, "patches"), "tools/wasi-compat": COMPAT}
82
+ inventory = licenses.c_tool(OUT, "git", GIT[0], "GPL-2.0-only", "https://git-scm.com", git_archive,
83
+ ["COPYING", "LGPL-2.1"], sources, "tools/git/build.py", [zlib])
84
+ licenses.finish(inventory, OUT, stamp())
85
+
86
+
87
+ def unpack(archive, name, into):
88
+ shutil.rmtree(into, ignore_errors=True)
89
+ with tarfile.open(archive) as tar:
90
+ tar.extractall(OUT, filter="data")
91
+ os.rename(os.path.join(OUT, name), into)
92
+
93
+
94
+ # git's commands that are shell scripts (its SCRIPT_SH and SCRIPT_LIB), which
95
+ # the playground's sh runs from git's exec path.
96
+ SCRIPTS = [
97
+ "git-difftool--helper", "git-filter-branch", "git-merge-octopus", "git-merge-one-file",
98
+ "git-merge-resolve", "git-mergetool", "git-quiltimport", "git-request-pull", "git-submodule",
99
+ "git-web--browse", "git-mergetool--lib", "git-sh-i18n", "git-sh-setup",
100
+ ]
101
+
102
+
103
+ def main():
104
+ os.makedirs(OUT, exist_ok=True)
105
+ built = os.path.join(OUT, "stamp")
106
+ git_archive = os.path.join(OUT, f"git-{GIT[0]}.tar.xz")
107
+ zlib_archive = os.path.join(OUT, f"zlib-{ZLIB[0]}.tar.gz")
108
+ if os.path.exists(os.path.join(OUT, "git.wasm")) and os.path.exists(built):
109
+ with open(built) as f:
110
+ if f.read() == stamp():
111
+ return licensed(git_archive, zlib_archive)
112
+ fetch(GIT[1], GIT[2], git_archive)
113
+ fetch(ZLIB[1], ZLIB[2], zlib_archive)
114
+ src = os.path.join(OUT, "src")
115
+ zlib = os.path.join(OUT, "zlib-src")
116
+ unpack(git_archive, f"git-{GIT[0]}", src)
117
+ for name in sorted(os.listdir(os.path.join(HERE, "patches"))):
118
+ subprocess.run(["patch", "-p1", "-s", "-d", src, "-i", os.path.join(HERE, "patches", name)],
119
+ check=True)
120
+ unpack(zlib_archive, f"zlib-{ZLIB[0]}", zlib)
121
+ shutil.copy(os.path.join(HERE, "config.mak"), os.path.join(src, "config.mak"))
122
+ wasi = os.path.join(OUT, "wasi")
123
+ shutil.rmtree(wasi, ignore_errors=True)
124
+ shutil.copytree(COMPAT, wasi)
125
+ cc = "/opt/wasi-sdk/bin/clang --target=wasm32-wasip1 --sysroot=/opt/wasi-sdk/share/wasi-sysroot"
126
+ script = (
127
+ # zlib's configure runs what it builds: its files by hand instead.
128
+ "cd /zlib-src\n"
129
+ "mkdir -p /zlib/include /zlib/lib\n"
130
+ f"for c in adler32 compress crc32 deflate gzclose gzlib gzread gzwrite infback inffast "
131
+ f"inflate inftrees trees uncompr zutil; do {cc} -Oz -DHAVE_UNISTD_H -DHAVE_STDARG_H -c $c.c -o $c.o; done\n"
132
+ "/opt/wasi-sdk/bin/llvm-ar rcs /zlib/lib/libz.a *.o\n"
133
+ "cp zlib.h zconf.h /zlib/include/\n"
134
+ "cd /src\n"
135
+ # test-tool, GIT-BUILD-OPTIONS and templates/blt: for git's own tests
136
+ # (app/test/git).
137
+ f"make -s -j$(nproc) {UNAME} git {' '.join(SCRIPTS)} t/helper/test-tool GIT-BUILD-OPTIONS >/dev/null\n"
138
+ "make -s -C templates >/dev/null\n"
139
+ f"chown -R {os.getuid()}:{os.getgid()} /src /zlib-src /zlib /wasi\n"
140
+ )
141
+ zlib_out = os.path.join(OUT, "zlib")
142
+ os.makedirs(zlib_out, exist_ok=True)
143
+ subprocess.run(DOCKER + [
144
+ "run", "--rm", "-v", f"{src}:/src", "-v", f"{zlib}:/zlib-src", "-v", f"{zlib_out}:/zlib",
145
+ "-v", f"{wasi}:/wasi", IMAGE, "sh", "-ec", script,
146
+ ], check=True)
147
+ shutil.copy(os.path.join(src, "git"), os.path.join(OUT, "git.wasm"))
148
+ shutil.copy(os.path.join(src, "t", "helper", "test-tool"), os.path.join(OUT, "test-tool.wasm"))
149
+ # The scripts, for the site's /usr/libexec/git-core (runtime/build.py).
150
+ libexec = os.path.join(OUT, "libexec")
151
+ shutil.rmtree(libexec, ignore_errors=True)
152
+ os.makedirs(libexec)
153
+ for name in SCRIPTS:
154
+ shutil.copy(os.path.join(src, name), os.path.join(libexec, name))
155
+ # The build's paths, as the tests on the host see them (not Docker's /src).
156
+ options = os.path.join(src, "GIT-BUILD-OPTIONS")
157
+ with open(options) as f:
158
+ text = f.read().replace("='/src", f"='{src}")
159
+ with open(options, "w") as f:
160
+ f.write(text)
161
+ with open(built, "w") as f:
162
+ f.write(stamp())
163
+ licensed(git_archive, zlib_archive)
164
+ print(f"{OUT}/git.wasm: {os.path.getsize(os.path.join(OUT, 'git.wasm')) / 1e6:.1f} MB")
165
+
166
+
167
+ if __name__ == "__main__":
168
+ main()
@@ -0,0 +1,42 @@
1
+ # git's build for wasm32-wasip1 (tools/git/build.py copies this into the source
2
+ # as config.mak). WASI preview 1 has no processes, threads, users, modes,
3
+ # terminals or sockets: what git would ask of them, tools/wasi-compat/ declares and
4
+ # stubs; what has a compat fallback in git (regex, mmap, poll) uses it.
5
+ CC = /opt/wasi-sdk/bin/clang --target=wasm32-wasip1 --sysroot=/opt/wasi-sdk/share/wasi-sysroot
6
+ AR = /opt/wasi-sdk/bin/llvm-ar
7
+ CFLAGS = -Oz -Wno-macro-redefined -I/zlib/include -I/wasi/include -include /wasi/include/wasi-compat.h \
8
+ -Dchmod=wasi_chmod -Dfchmod=wasi_fchmod \
9
+ -D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_PROCESS_CLOCKS -D_WASI_EMULATED_GETPID -D_WASI_EMULATED_MMAN
10
+ # A stack of 1 MiB (wasm-ld's default is 64 KiB): history walks recurse.
11
+ # The stack pointer exported: the host switches it between git and an
12
+ # async's coroutine (stubs.c wasi_async_start).
13
+ LDFLAGS = -L/zlib/lib -Wl,-z,stack-size=1048576 -Wl,--export=__stack_pointer \
14
+ -lwasi-emulated-signal -lwasi-emulated-process-clocks -lwasi-emulated-getpid -lwasi-emulated-mman
15
+ LIB_OBJS += /wasi/stubs.o
16
+
17
+ prefix = /usr
18
+ NO_RUST = YesPlease
19
+ NO_PTHREADS = YesPlease
20
+ NO_CURL = YesPlease
21
+ NO_EXPAT = YesPlease
22
+ NO_GETTEXT = YesPlease
23
+ NO_ICONV = YesPlease
24
+ NO_OPENSSL = YesPlease
25
+ NO_PERL = YesPlease
26
+ NO_PYTHON = YesPlease
27
+ NO_TCLTK = YesPlease
28
+ NO_REGEX = YesPlease
29
+ NO_MMAP = YesPlease
30
+ NO_UNIX_SOCKETS = YesPlease
31
+ NO_IPV6 = YesPlease
32
+ NO_SETITIMER = YesPlease
33
+ NO_STRUCT_ITIMERVAL = YesPlease
34
+ NO_INITGROUPS = YesPlease
35
+ NO_GECOS_IN_PWENT = YesPlease
36
+ NO_TRUSTABLE_FILEMODE = YesPlease
37
+ NO_SYMLINK_HEAD = YesPlease
38
+ NO_INSTALL_HARDLINKS = YesPlease
39
+ NO_POSIX_GOODIES = UnfortunatelyYes
40
+ SKIP_DASHED_BUILT_INS = YesPlease
41
+ CSPRNG_METHOD = getentropy
42
+ HAVE_DEV_TTY =
@@ -0,0 +1,76 @@
1
+ --- a/run-command.c
2
+ +++ b/run-command.c
3
+ @@ -745,7 +745,7 @@
4
+ if (cmd->odb_to_close)
5
+ odb_close(cmd->odb_to_close);
6
+
7
+ -#ifndef GIT_WINDOWS_NATIVE
8
+ +#if !defined(GIT_WINDOWS_NATIVE) && !defined(__wasi__)
9
+ {
10
+ int notify_pipe[2];
11
+ int null_fd = -1;
12
+ @@ -908,6 +908,64 @@
13
+ }
14
+ end_of_spawn:
15
+
16
+ +#elif defined(__wasi__)
17
+ +/*
18
+ + * WASI has no fork: the host starts the child with three of our
19
+ + * descriptors as its standard streams (wasi_spawn), as Windows does.
20
+ + */
21
+ +{
22
+ + int fhin = 0, fhout = 1, fherr = 2;
23
+ + const char **sargv = cmd->args.v;
24
+ + struct strvec nargv = STRVEC_INIT;
25
+ + char **childenv;
26
+ +
27
+ + if (cmd->no_stdin)
28
+ + fhin = open("/dev/null", O_RDWR);
29
+ + else if (need_in)
30
+ + fhin = dup(fdin[0]);
31
+ + else if (cmd->in)
32
+ + fhin = dup(cmd->in);
33
+ +
34
+ + if (cmd->no_stderr)
35
+ + fherr = open("/dev/null", O_RDWR);
36
+ + else if (need_err)
37
+ + fherr = dup(fderr[1]);
38
+ + else if (cmd->err > 2)
39
+ + fherr = dup(cmd->err);
40
+ +
41
+ + if (cmd->no_stdout)
42
+ + fhout = open("/dev/null", O_RDWR);
43
+ + else if (cmd->stdout_to_stderr)
44
+ + fhout = dup(fherr);
45
+ + else if (need_out)
46
+ + fhout = dup(fdout[1]);
47
+ + else if (cmd->out > 1)
48
+ + fhout = dup(cmd->out);
49
+ +
50
+ + if (cmd->git_cmd)
51
+ + cmd->args.v = prepare_git_cmd(&nargv, sargv);
52
+ + else if (cmd->use_shell)
53
+ + cmd->args.v = prepare_shell_cmd(&nargv, sargv);
54
+ +
55
+ + trace_argv_printf(cmd->args.v, "trace: start_command:");
56
+ + childenv = prep_childenv(cmd->env.v);
57
+ + cmd->pid = wasi_spawn(cmd->args.v, childenv, cmd->dir, fhin, fhout, fherr);
58
+ + failed_errno = errno;
59
+ + if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))
60
+ + error_errno("cannot run %s", cmd->args.v[0]);
61
+ + if (cmd->clean_on_exit && cmd->pid >= 0)
62
+ + mark_child_for_cleanup(cmd->pid, cmd);
63
+ +
64
+ + free(childenv);
65
+ + strvec_clear(&nargv);
66
+ + cmd->args.v = sargv;
67
+ + if (fhin != 0)
68
+ + close(fhin);
69
+ + if (fhout != 1)
70
+ + close(fhout);
71
+ + if (fherr != 2)
72
+ + close(fherr);
73
+ +}
74
+ #else
75
+ {
76
+ int fhin = 0, fhout = 1, fherr = 2;
@@ -0,0 +1,124 @@
1
+ diff --git a/run-command.c b/run-command.c
2
+ index 99e18d2..37d909b 100644
3
+ --- a/run-command.c
4
+ +++ b/run-command.c
5
+ @@ -1192,6 +1192,48 @@ int git_atexit(void (*handler)(void))
6
+ }
7
+ #define atexit git_atexit
8
+
9
+ +#ifdef __wasi__
10
+ +/*
11
+ + * WASI: an async function runs beside git as a coroutine of the host's,
12
+ + * on a stack of its own (wasi_async_start, tools/wasi-compat/stubs.c). It shares
13
+ + * git's descriptors and memory as a thread would; the host says which
14
+ + * runs, and ends one that dies.
15
+ + */
16
+ +int in_async(void)
17
+ +{
18
+ + return wasi_async_current() != NULL;
19
+ +}
20
+ +
21
+ +static void NORETURN async_exit(int code)
22
+ +{
23
+ + wasi_async_exit(code);
24
+ +}
25
+ +
26
+ +static NORETURN void die_async(const char *err, va_list params)
27
+ +{
28
+ + report_fn die_message_fn = get_die_message_routine();
29
+ +
30
+ + die_message_fn(err, params);
31
+ +
32
+ + if (in_async()) {
33
+ + struct async *async = wasi_async_current();
34
+ + if (async->proc_in >= 0)
35
+ + close(async->proc_in);
36
+ + if (async->proc_out >= 0)
37
+ + close(async->proc_out);
38
+ + async_exit(128);
39
+ + }
40
+ +
41
+ + exit(128);
42
+ +}
43
+ +
44
+ +/* What the host's coroutine runs: the exit status, as a forked one's. */
45
+ +__attribute__((export_name("wasi_run_async")))
46
+ +int wasi_run_async(struct async *async)
47
+ +{
48
+ + return !!async->proc(async->proc_in, async->proc_out, async->data);
49
+ +}
50
+ +#else
51
+ static int process_is_async;
52
+ int in_async(void)
53
+ {
54
+ @@ -1202,6 +1244,7 @@ static void NORETURN async_exit(int code)
55
+ {
56
+ exit(code);
57
+ }
58
+ +#endif
59
+
60
+ #endif
61
+
62
+ @@ -1260,7 +1303,22 @@ int start_async(struct async *async)
63
+ else
64
+ proc_out = -1;
65
+
66
+ -#ifdef NO_PTHREADS
67
+ +#if defined(__wasi__)
68
+ + {
69
+ + static int started;
70
+ + if (!started) {
71
+ + started = 1;
72
+ + set_die_routine(die_async);
73
+ + }
74
+ + }
75
+ + async->proc_in = proc_in;
76
+ + async->proc_out = proc_out;
77
+ + async->pid = wasi_async_start(async, &async->stack);
78
+ + if (async->pid < 0) {
79
+ + error_errno("cannot start async");
80
+ + goto error;
81
+ + }
82
+ +#elif defined(NO_PTHREADS)
83
+ /* Flush stdio before fork() to avoid cloning buffers */
84
+ fflush(NULL);
85
+
86
+ @@ -1339,6 +1397,10 @@ int finish_async(struct async *async)
87
+ int ret = wait_or_whine(async->pid, "child process", 0);
88
+
89
+ invalidate_lstat_cache();
90
+ +#ifdef __wasi__
91
+ + free(async->stack);
92
+ + async->stack = NULL;
93
+ +#endif
94
+
95
+ return ret;
96
+ #else
97
+ @@ -1354,7 +1416,7 @@ int finish_async(struct async *async)
98
+
99
+ int async_with_fork(void)
100
+ {
101
+ -#ifdef NO_PTHREADS
102
+ +#if defined(NO_PTHREADS) && !defined(__wasi__)
103
+ return 1;
104
+ #else
105
+ return 0;
106
+ diff --git a/run-command.h b/run-command.h
107
+ index c2fad4f..754109c 100644
108
+ --- a/run-command.h
109
+ +++ b/run-command.h
110
+ @@ -363,7 +363,13 @@ struct async {
111
+ */
112
+ int in; /* caller writes here and closes it */
113
+ int out; /* caller reads from here and closes it */
114
+ -#ifdef NO_PTHREADS
115
+ +#if defined(__wasi__)
116
+ + /* A coroutine of the host's beside git's (wasi_async_start). */
117
+ + pid_t pid;
118
+ + int proc_in;
119
+ + int proc_out;
120
+ + void *stack;
121
+ +#elif defined(NO_PTHREADS)
122
+ pid_t pid;
123
+ #else
124
+ pthread_t tid;
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The Elixir Playground Authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.