Skip to content

Commit 6c4bdc1

Browse files
committed
Merge pull request #371 from dscho/run-scalar-functional-tests-and-fix-built-in-fsmonitor
Fix the built-in FSMonitor, and run Scalar's Functional Tests as part of the automated builds
2 parents 59c47e1 + 5f1b33c commit 6c4bdc1

22 files changed

+1768
-45
lines changed

Diff for: .github/workflows/scalar-functional-tests.yml

+220
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
name: Scalar Functional Tests
2+
3+
env:
4+
SCALAR_REPOSITORY: microsoft/scalar
5+
SCALAR_REF: main
6+
DEBUG_WITH_TMATE: false
7+
SCALAR_TEST_SKIP_VSTS_INFO: true
8+
9+
on:
10+
push:
11+
branches: [ vfs-*, tentative/vfs-* ]
12+
pull_request:
13+
branches: [ vfs-*, features/* ]
14+
15+
jobs:
16+
scalar:
17+
name: "Scalar Functional Tests"
18+
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
# Order by runtime (in descending order)
23+
os: [windows-2019, macos-13, ubuntu-20.04, ubuntu-22.04]
24+
# Scalar.NET used to be tested using `features: [false, experimental]`
25+
# But currently, Scalar/C ignores `feature.scalar` altogether, so let's
26+
# save some electrons and run only one of them...
27+
features: [ignored]
28+
exclude:
29+
# The built-in FSMonitor is not (yet) supported on Linux
30+
- os: ubuntu-20.04
31+
features: experimental
32+
- os: ubuntu-22.04
33+
features: experimental
34+
runs-on: ${{ matrix.os }}
35+
36+
env:
37+
BUILD_FRAGMENT: bin/Release/netcoreapp3.1
38+
GIT_FORCE_UNTRACKED_CACHE: 1
39+
40+
steps:
41+
- name: Check out Git's source code
42+
uses: actions/checkout@v4
43+
44+
- name: Setup build tools on Windows
45+
if: runner.os == 'Windows'
46+
uses: git-for-windows/setup-git-for-windows-sdk@v1
47+
48+
- name: Provide a minimal `install` on Windows
49+
if: runner.os == 'Windows'
50+
shell: bash
51+
run: |
52+
test -x /usr/bin/install ||
53+
tr % '\t' >/usr/bin/install <<-\EOF
54+
#!/bin/sh
55+
56+
cmd=cp
57+
while test $# != 0
58+
do
59+
%case "$1" in
60+
%-d) cmd="mkdir -p";;
61+
%-m) shift;; # ignore mode
62+
%*) break;;
63+
%esac
64+
%shift
65+
done
66+
67+
exec $cmd "$@"
68+
EOF
69+
70+
- name: Install build dependencies for Git (Linux)
71+
if: runner.os == 'Linux'
72+
run: |
73+
sudo apt-get update
74+
sudo apt-get -q -y install libssl-dev libcurl4-openssl-dev gettext
75+
76+
- name: Build and install Git
77+
shell: bash
78+
env:
79+
NO_TCLTK: Yup
80+
run: |
81+
# We do require a VFS version
82+
def_ver="$(sed -n 's/DEF_VER=\(.*vfs.*\)/\1/p' GIT-VERSION-GEN)"
83+
test -n "$def_ver"
84+
85+
# Ensure that `git version` reflects DEF_VER
86+
case "$(git describe --match "v[0-9]*vfs*" HEAD)" in
87+
${def_ver%%.vfs.*}.vfs.*) ;; # okay, we can use this
88+
*) git -c user.name=ci -c user.email=ci@github tag -m for-testing ${def_ver}.NNN.g$(git rev-parse --short HEAD);;
89+
esac
90+
91+
SUDO=
92+
extra=
93+
case "${{ runner.os }}" in
94+
Windows)
95+
extra=DESTDIR=/c/Progra~1/Git
96+
cygpath -aw "/c/Program Files/Git/cmd" >>$GITHUB_PATH
97+
;;
98+
Linux)
99+
SUDO=sudo
100+
extra=prefix=/usr
101+
;;
102+
macOS)
103+
SUDO=sudo
104+
extra=prefix=/usr/local
105+
;;
106+
esac
107+
108+
$SUDO make -j5 $extra install
109+
110+
- name: Ensure that we use the built Git and Scalar
111+
shell: bash
112+
run: |
113+
type -p git
114+
git version
115+
case "$(git version)" in *.vfs.*) echo Good;; *) exit 1;; esac
116+
type -p scalar
117+
scalar version
118+
case "$(scalar version 2>&1)" in *.vfs.*) echo Good;; *) exit 1;; esac
119+
120+
- name: Check out Scalar's source code
121+
uses: actions/checkout@v4
122+
with:
123+
fetch-depth: 0 # Indicate full history so Nerdbank.GitVersioning works.
124+
path: scalar
125+
repository: ${{ env.SCALAR_REPOSITORY }}
126+
ref: ${{ env.SCALAR_REF }}
127+
128+
- name: Setup .NET Core
129+
uses: actions/setup-dotnet@v4
130+
with:
131+
dotnet-version: '3.1.x'
132+
133+
- name: Install dependencies
134+
run: dotnet restore
135+
working-directory: scalar
136+
env:
137+
DOTNET_NOLOGO: 1
138+
139+
- name: Build
140+
working-directory: scalar
141+
run: dotnet build --configuration Release --no-restore -p:UseAppHost=true # Force generation of executable on macOS.
142+
143+
- name: Setup platform (Linux)
144+
if: runner.os == 'Linux'
145+
run: |
146+
echo "BUILD_PLATFORM=${{ runner.os }}" >>$GITHUB_ENV
147+
echo "TRACE2_BASENAME=Trace2.${{ github.run_id }}__${{ github.run_number }}__${{ matrix.os }}__${{ matrix.features }}" >>$GITHUB_ENV
148+
149+
- name: Setup platform (Mac)
150+
if: runner.os == 'macOS'
151+
run: |
152+
echo 'BUILD_PLATFORM=Mac' >>$GITHUB_ENV
153+
echo "TRACE2_BASENAME=Trace2.${{ github.run_id }}__${{ github.run_number }}__${{ matrix.os }}__${{ matrix.features }}" >>$GITHUB_ENV
154+
155+
- name: Setup platform (Windows)
156+
if: runner.os == 'Windows'
157+
run: |
158+
echo "BUILD_PLATFORM=${{ runner.os }}" >>$env:GITHUB_ENV
159+
echo 'BUILD_FILE_EXT=.exe' >>$env:GITHUB_ENV
160+
echo "TRACE2_BASENAME=Trace2.${{ github.run_id }}__${{ github.run_number }}__${{ matrix.os }}__${{ matrix.features }}" >>$env:GITHUB_ENV
161+
162+
- name: Configure feature.scalar
163+
run: git config --global feature.scalar ${{ matrix.features }}
164+
165+
- id: functional_test
166+
name: Functional test
167+
timeout-minutes: 60
168+
working-directory: scalar
169+
shell: bash
170+
run: |
171+
export GIT_TRACE2_EVENT="$PWD/$TRACE2_BASENAME/Event"
172+
export GIT_TRACE2_PERF="$PWD/$TRACE2_BASENAME/Perf"
173+
export GIT_TRACE2_EVENT_BRIEF=true
174+
export GIT_TRACE2_PERF_BRIEF=true
175+
mkdir -p "$TRACE2_BASENAME"
176+
mkdir -p "$TRACE2_BASENAME/Event"
177+
mkdir -p "$TRACE2_BASENAME/Perf"
178+
git version --build-options
179+
cd ../out
180+
Scalar.FunctionalTests/$BUILD_FRAGMENT/Scalar.FunctionalTests$BUILD_FILE_EXT --test-scalar-on-path --test-git-on-path --timeout=300000 --full-suite
181+
182+
- name: Force-stop FSMonitor daemons and Git processes (Windows)
183+
if: runner.os == 'Windows' && (success() || failure())
184+
shell: bash
185+
run: |
186+
set -x
187+
wmic process get CommandLine,ExecutablePath,HandleCount,Name,ParentProcessID,ProcessID
188+
wmic process where "CommandLine Like '%fsmonitor--daemon %run'" delete
189+
wmic process where "ExecutablePath Like '%git.exe'" delete
190+
191+
- id: trace2_zip_unix
192+
if: runner.os != 'Windows' && ( success() || failure() ) && ( steps.functional_test.conclusion == 'success' || steps.functional_test.conclusion == 'failure' )
193+
name: Zip Trace2 Logs (Unix)
194+
shell: bash
195+
working-directory: scalar
196+
run: zip -q -r $TRACE2_BASENAME.zip $TRACE2_BASENAME/
197+
198+
- id: trace2_zip_windows
199+
if: runner.os == 'Windows' && ( success() || failure() ) && ( steps.functional_test.conclusion == 'success' || steps.functional_test.conclusion == 'failure' )
200+
name: Zip Trace2 Logs (Windows)
201+
working-directory: scalar
202+
run: Compress-Archive -DestinationPath ${{ env.TRACE2_BASENAME }}.zip -Path ${{ env.TRACE2_BASENAME }}
203+
204+
- name: Archive Trace2 Logs
205+
if: ( success() || failure() ) && ( steps.trace2_zip_unix.conclusion == 'success' || steps.trace2_zip_windows.conclusion == 'success' )
206+
uses: actions/upload-artifact@v4
207+
with:
208+
name: ${{ env.TRACE2_BASENAME }}.zip
209+
path: scalar/${{ env.TRACE2_BASENAME }}.zip
210+
retention-days: 3
211+
212+
# The GitHub Action `action-tmate` allows developers to connect to the running agent
213+
# using SSH (it will be a `tmux` session; on Windows agents it will be inside the MSYS2
214+
# environment in `C:\msys64`, therefore it can be slightly tricky to interact with
215+
# Git for Windows, which runs a slightly incompatible MSYS2 runtime).
216+
- name: action-tmate
217+
if: env.DEBUG_WITH_TMATE == 'true' && failure()
218+
uses: mxschmitt/action-tmate@v3
219+
with:
220+
limit-access-to-actor: true

Diff for: Documentation/config/core.txt

+9
Original file line numberDiff line numberDiff line change
@@ -834,3 +834,12 @@ core.WSLCompat::
834834
The default value is false. When set to true, Git will set the mode
835835
bits of the file in the way of wsl, so that the executable flag of
836836
files can be set or read correctly.
837+
838+
core.configWriteLockTimeoutMS::
839+
When processes try to write to the config concurrently, it is likely
840+
that one process "wins" and the other process(es) fail to lock the
841+
config file. By configuring a timeout larger than zero, Git can be
842+
told to try to lock the config again a couple times within the
843+
specified timeout. If the timeout is configure to zero (which is the
844+
default), Git will fail immediately when the config is already
845+
locked.

Diff for: Documentation/scalar.txt

+55-1
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ SYNOPSIS
99
--------
1010
[verse]
1111
scalar clone [--single-branch] [--branch <main-branch>] [--full-clone]
12-
[--[no-]src] <url> [<enlistment>]
12+
[--[no-]src] [--local-cache-path <path>] [--cache-server-url <url>]
13+
<url> [<enlistment>]
1314
scalar list
1415
scalar register [<enlistment>]
1516
scalar unregister [<enlistment>]
1617
scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>]
1718
scalar reconfigure [ --all | <enlistment> ]
1819
scalar diagnose [<enlistment>]
1920
scalar delete <enlistment>
21+
scalar cache-server ( --get | --set <url> | --list [<remote>] ) [<enlistment>]
2022

2123
DESCRIPTION
2224
-----------
@@ -97,6 +99,37 @@ cloning. If the HEAD at the remote did not point at any branch when
9799
A sparse-checkout is initialized by default. This behavior can be
98100
turned off via `--full-clone`.
99101

102+
--local-cache-path <path>::
103+
Override the path to the local cache root directory; Pre-fetched objects
104+
are stored into a repository-dependent subdirectory of that path.
105+
+
106+
The default is `<drive>:\.scalarCache` on Windows (on the same drive as the
107+
clone), and `~/.scalarCache` on macOS.
108+
109+
--cache-server-url <url>::
110+
Retrieve missing objects from the specified remote, which is expected to
111+
understand the GVFS protocol.
112+
113+
--[no-]gvfs-protocol::
114+
When cloning from a `<url>` with either `dev.azure.com` or
115+
`visualstudio.com` in the name, `scalar clone` will attempt to use the GVFS
116+
Protocol to access Git objects, specifically from a cache server when
117+
available, and will fail to clone if there is an error over that protocol.
118+
119+
To enable the GVFS Protocol regardless of the origin `<url>`, use
120+
`--gvfs-protocol`. This will cause `scalar clone` to fail when the origin
121+
server fails to provide a valid response to the `gvfs/config` endpoint.
122+
123+
To disable the GVFS Protocol, use `--no-gvfs-protocol` and `scalar clone`
124+
will only use the Git protocol, starting with a partial clone. This can be
125+
helpful if your `<url>` points to Azure Repos but the repository does not
126+
have GVFS cache servers enabled. It is likely more efficient to use its
127+
partial clone functionality through the Git protocol.
128+
129+
Previous versions of `scalar clone` could fall back to a partial clone over
130+
the Git protocol if there is any issue gathering GVFS configuration
131+
information from the origin server.
132+
100133
List
101134
~~~~
102135

@@ -170,6 +203,27 @@ delete <enlistment>::
170203
This subcommand lets you delete an existing Scalar enlistment from your
171204
local file system, unregistering the repository.
172205

206+
Cache-server
207+
~~~~~~~~~~~~
208+
209+
cache-server ( --get | --set <url> | --list [<remote>] ) [<enlistment>]::
210+
This command lets you query or set the GVFS-enabled cache server used
211+
to fetch missing objects.
212+
213+
--get::
214+
This is the default command mode: query the currently-configured cache
215+
server URL, if any.
216+
217+
--list::
218+
Access the `gvfs/info` endpoint of the specified remote (default:
219+
`origin`) to figure out which cache servers are available, if any.
220+
+
221+
In contrast to the `--get` command mode (which only accesses the local
222+
repository), this command mode triggers a request via the network that
223+
potentially requires authentication. If authentication is required, the
224+
configured credential helper is employed (see linkgit:git-credential[1]
225+
for details).
226+
173227
SEE ALSO
174228
--------
175229
linkgit:git-clone[1], linkgit:git-maintenance[1].

Diff for: Makefile

+2-1
Original file line numberDiff line numberDiff line change
@@ -2787,6 +2787,7 @@ GIT_OBJS += git.o
27872787
.PHONY: git-objs
27882788
git-objs: $(GIT_OBJS)
27892789

2790+
SCALAR_OBJS := json-parser.o
27902791
SCALAR_OBJS += scalar.o
27912792
.PHONY: scalar-objs
27922793
scalar-objs: $(SCALAR_OBJS)
@@ -2938,7 +2939,7 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(LAZYLOAD_LIBCURL_OB
29382939
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
29392940
$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
29402941

2941-
scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)
2942+
scalar$X: $(SCALAR_OBJS) GIT-LDFLAGS $(GITLIBS)
29422943
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
29432944
$(filter %.o,$^) $(LIBS)
29442945

Diff for: abspath.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ int is_directory(const char *path)
1414
}
1515

1616
/* removes the last path component from 'path' except if 'path' is root */
17-
static void strip_last_component(struct strbuf *path)
17+
void strip_last_path_component(struct strbuf *path)
1818
{
1919
size_t offset = offset_1st_component(path->buf);
2020
size_t len = path->len;
@@ -119,7 +119,7 @@ static char *strbuf_realpath_1(struct strbuf *resolved, const char *path,
119119
continue; /* '.' component */
120120
} else if (next.len == 2 && !strcmp(next.buf, "..")) {
121121
/* '..' component; strip the last path component */
122-
strip_last_component(resolved);
122+
strip_last_path_component(resolved);
123123
continue;
124124
}
125125

@@ -171,7 +171,7 @@ static char *strbuf_realpath_1(struct strbuf *resolved, const char *path,
171171
* strip off the last component since it will
172172
* be replaced with the contents of the symlink
173173
*/
174-
strip_last_component(resolved);
174+
strip_last_path_component(resolved);
175175
}
176176

177177
/*

Diff for: abspath.h

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ char *real_pathdup(const char *path, int die_on_error);
1010
const char *absolute_path(const char *path);
1111
char *absolute_pathdup(const char *path);
1212

13+
/**
14+
* Remove the last path component from 'path' except if 'path' is root.
15+
*/
16+
void strip_last_path_component(struct strbuf *path);
17+
1318
/*
1419
* Concatenate "prefix" (if len is non-zero) and "path", with no
1520
* connecting characters (so "prefix" should end with a "/").

Diff for: config.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -3275,6 +3275,7 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
32753275
const char *comment,
32763276
unsigned flags)
32773277
{
3278+
static unsigned long timeout_ms = ULONG_MAX;
32783279
int fd = -1, in_fd = -1;
32793280
int ret;
32803281
struct lock_file lock = LOCK_INIT;
@@ -3295,11 +3296,16 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
32953296
if (!config_filename)
32963297
config_filename = filename_buf = repo_git_path(r, "config");
32973298

3299+
if ((long)timeout_ms < 0 &&
3300+
git_config_get_ulong("core.configWriteLockTimeoutMS", &timeout_ms))
3301+
timeout_ms = 0;
3302+
32983303
/*
32993304
* The lock serves a purpose in addition to locking: the new
33003305
* contents of .git/config will be written into it.
33013306
*/
3302-
fd = hold_lock_file_for_update(&lock, config_filename, 0);
3307+
fd = hold_lock_file_for_update_timeout(&lock, config_filename, 0,
3308+
timeout_ms);
33033309
if (fd < 0) {
33043310
error_errno(_("could not lock config file %s"), config_filename);
33053311
ret = CONFIG_NO_LOCK;

0 commit comments

Comments
 (0)