Skip to content

Commit c8479a6

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 edede46 + f16fc9e commit c8479a6

22 files changed

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

Documentation/config/core.adoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,3 +828,12 @@ core.WSLCompat::
828828
The default value is false. When set to true, Git will set the mode
829829
bits of the file in the way of wsl, so that the executable flag of
830830
files can be set or read correctly.
831+
832+
core.configWriteLockTimeoutMS::
833+
When processes try to write to the config concurrently, it is likely
834+
that one process "wins" and the other process(es) fail to lock the
835+
config file. By configuring a timeout larger than zero, Git can be
836+
told to try to lock the config again a couple times within the
837+
specified timeout. If the timeout is configure to zero (which is the
838+
default), Git will fail immediately when the config is already
839+
locked.

Documentation/scalar.adoc

Lines changed: 55 additions & 1 deletion
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].

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2816,6 +2816,7 @@ GIT_OBJS += git.o
28162816
.PHONY: git-objs
28172817
git-objs: $(GIT_OBJS)
28182818

2819+
SCALAR_OBJS := json-parser.o
28192820
SCALAR_OBJS += scalar.o
28202821
.PHONY: scalar-objs
28212822
scalar-objs: $(SCALAR_OBJS)
@@ -2971,7 +2972,7 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(LAZYLOAD_LIBCURL_OB
29712972
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
29722973
$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
29732974

2974-
scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)
2975+
scalar$X: $(SCALAR_OBJS) GIT-LDFLAGS $(GITLIBS)
29752976
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
29762977
$(filter %.o,$^) $(LIBS)
29772978

abspath.c

Lines changed: 3 additions & 3 deletions
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
/*

abspath.h

Lines changed: 5 additions & 0 deletions
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 "/").

config.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3271,6 +3271,7 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
32713271
const char *comment,
32723272
unsigned flags)
32733273
{
3274+
static unsigned long timeout_ms = ULONG_MAX;
32743275
int fd = -1, in_fd = -1;
32753276
int ret;
32763277
struct lock_file lock = LOCK_INIT;
@@ -3291,11 +3292,16 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
32913292
if (!config_filename)
32923293
config_filename = filename_buf = repo_git_path(r, "config");
32933294

3295+
if ((long)timeout_ms < 0 &&
3296+
git_config_get_ulong("core.configWriteLockTimeoutMS", &timeout_ms))
3297+
timeout_ms = 0;
3298+
32943299
/*
32953300
* The lock serves a purpose in addition to locking: the new
32963301
* contents of .git/config will be written into it.
32973302
*/
3298-
fd = hold_lock_file_for_update(&lock, config_filename, 0);
3303+
fd = hold_lock_file_for_update_timeout(&lock, config_filename, 0,
3304+
timeout_ms);
32993305
if (fd < 0) {
33003306
error_errno(_("could not lock config file %s"), config_filename);
33013307
ret = CONFIG_NO_LOCK;

0 commit comments

Comments
 (0)