Skip to content

Commit 2aa3e49

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 5dc7c03 + 02fe032 commit 2aa3e49

22 files changed

+1831
-44
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-2022, 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.426'
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
@@ -833,3 +833,12 @@ core.WSLCompat::
833833
The default value is false. When set to true, Git will set the mode
834834
bits of the file in the way of wsl, so that the executable flag of
835835
files can be set or read correctly.
836+
837+
core.configWriteLockTimeoutMS::
838+
When processes try to write to the config concurrently, it is likely
839+
that one process "wins" and the other process(es) fail to lock the
840+
config file. By configuring a timeout larger than zero, Git can be
841+
told to try to lock the config again a couple times within the
842+
specified timeout. If the timeout is configure to zero (which is the
843+
default), Git will fail immediately when the config is already
844+
locked.

Documentation/scalar.adoc

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@ SYNOPSIS
99
--------
1010
[verse]
1111
scalar clone [--single-branch] [--branch <main-branch>] [--full-clone]
12-
[--[no-]src] [--[no-]tags] [--[no-]maintenance] <url> [<enlistment>]
12+
[--[no-]src] [--[no-]tags] [--[no-]maintenance]
13+
[--[no-]src] [--local-cache-path <path>] [--cache-server-url <url>]
14+
<url> [<enlistment>]
1315
scalar list
1416
scalar register [--[no-]maintenance] [<enlistment>]
1517
scalar unregister [<enlistment>]
1618
scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>]
1719
scalar reconfigure [--maintenance=(enable|disable|keep)] [ --all | <enlistment> ]
1820
scalar diagnose [<enlistment>]
1921
scalar delete <enlistment>
22+
scalar cache-server ( --get | --set <url> | --list [<remote>] ) [<enlistment>]
2023

2124
DESCRIPTION
2225
-----------
@@ -102,6 +105,37 @@ cloning. If the HEAD at the remote did not point at any branch when
102105
background maintenance feature. Use the `--no-maintenance` to skip
103106
this configuration.
104107

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

@@ -191,6 +225,27 @@ delete <enlistment>::
191225
This subcommand lets you delete an existing Scalar enlistment from your
192226
local file system, unregistering the repository.
193227

228+
Cache-server
229+
~~~~~~~~~~~~
230+
231+
cache-server ( --get | --set <url> | --list [<remote>] ) [<enlistment>]::
232+
This command lets you query or set the GVFS-enabled cache server used
233+
to fetch missing objects.
234+
235+
--get::
236+
This is the default command mode: query the currently-configured cache
237+
server URL, if any.
238+
239+
--list::
240+
Access the `gvfs/info` endpoint of the specified remote (default:
241+
`origin`) to figure out which cache servers are available, if any.
242+
+
243+
In contrast to the `--get` command mode (which only accesses the local
244+
repository), this command mode triggers a request via the network that
245+
potentially requires authentication. If authentication is required, the
246+
configured credential helper is employed (see linkgit:git-credential[1]
247+
for details).
248+
194249
SEE ALSO
195250
--------
196251
linkgit:git-clone[1], linkgit:git-maintenance[1].

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2831,6 +2831,7 @@ GIT_OBJS += git.o
28312831
.PHONY: git-objs
28322832
git-objs: $(GIT_OBJS)
28332833

2834+
SCALAR_OBJS := json-parser.o
28342835
SCALAR_OBJS += scalar.o
28352836
.PHONY: scalar-objs
28362837
scalar-objs: $(SCALAR_OBJS)
@@ -2986,7 +2987,7 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(LAZYLOAD_LIBCURL_OB
29862987
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
29872988
$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
29882989

2989-
scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)
2990+
scalar$X: $(SCALAR_OBJS) GIT-LDFLAGS $(GITLIBS)
29902991
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
29912992
$(filter %.o,$^) $(LIBS)
29922993

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
@@ -3270,6 +3270,7 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
32703270
const char *comment,
32713271
unsigned flags)
32723272
{
3273+
static unsigned long timeout_ms = ULONG_MAX;
32733274
int fd = -1, in_fd = -1;
32743275
int ret;
32753276
struct lock_file lock = LOCK_INIT;
@@ -3290,11 +3291,16 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
32903291
if (!config_filename)
32913292
config_filename = filename_buf = repo_git_path(r, "config");
32923293

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

0 commit comments

Comments
 (0)