Skip to content

Commit 13f6383

Browse files
committed
ui-tests: verify that interrupting clones via SSH works
This was the actual use case that was broken and necessitated the fix in 7674c51 (Cygwin: console: Set ENABLE_PROCESSED_INPUT when disable_master_thread, 2025-07-01). It does require an SSH server, which Git for Windows no longer ships. Therefore, this test uses the `sshd.exe` of OpenSSH for Windows (https://github.com/powershell/Win32-OpenSSH) in conjunction with Git for Windows' `ssh.exe` (because using OpenSSH for Windows' variant of `ssh.exe` would not exercise the MSYS2 runtime and therefore not demonstrate a regression, should it surface in the future). To avoid failing the test because OpenSSH for Windows is not available, the test case is guarded by the environment variable `OPENSSH_FOR_WINDOWS_DIRECTORY` which needs to point to a directory that contains a working `sshd.exe`. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent b7e8626 commit 13f6383

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed

.github/workflows/ui-tests.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ on:
1010
env:
1111
AUTOHOTKEY_VERSION: 2.0.19
1212
WT_VERSION: 1.22.11141.0
13+
WIN32_OPENSSH_VERSION: 9.8.3.0p2-Preview
1314

1415
jobs:
1516
ui-tests:
@@ -76,6 +77,27 @@ jobs:
7677
"$WINDIR/system32/tar.exe" -C "$RUNNER_TEMP/ahk" -xf "$RUNNER_TEMP/ahk.zip" &&
7778
cygpath -aw "$RUNNER_TEMP/ahk" >>$GITHUB_PATH
7879
- uses: actions/setup-node@v4 # the hook uses node for the background process
80+
- uses: actions/cache/restore@v4
81+
id: restore-win32-openssh
82+
with:
83+
key: win32-openssh-${{ env.WIN32_OPENSSH_VERSION }}
84+
path: ${{ runner.temp }}/win32-openssh.zip
85+
- name: Download Win32-OpenSSH
86+
if: steps.restore-win32-openssh.outputs.cache-hit != 'true'
87+
shell: bash
88+
run: |
89+
curl -fLo "$RUNNER_TEMP/win32-openssh.zip" \
90+
https://github.com/PowerShell/Win32-OpenSSH/releases/download/v$WIN32_OPENSSH_VERSION/OpenSSH-Win64.zip
91+
- uses: actions/cache/save@v4
92+
if: steps.restore-win32-openssh.outputs.cache-hit != 'true'
93+
with:
94+
key: win32-openssh-${{ env.WIN32_OPENSSH_VERSION }}
95+
path: ${{ runner.temp }}/win32-openssh.zip
96+
- name: Unpack Win32-OpenSSH
97+
shell: bash
98+
run: |
99+
"$WINDIR/system32/tar.exe" -C "$RUNNER_TEMP" -xvf "$RUNNER_TEMP/win32-openssh.zip" &&
100+
echo "OPENSSH_FOR_WINDOWS_DIRECTORY=$(cygpath -aw "$RUNNER_TEMP/OpenSSH-Win64")" >>$GITHUB_ENV
79101
80102
- uses: actions/checkout@v4
81103
with:

ui-tests/ctrl-c.ahk

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,93 @@ Sleep 150
4141
; Wait for the `^C` tell-tale that is the PowerShell prompt to appear
4242
WaitForRegExInWindowsTerminal('>[ `n`r]*$', 'Timed out waiting for interrupt', 'Sleep was interrupted as desired')
4343

44+
; Clone via SSH test; Requires an OpenSSH for Windows `sshd.exe` whose directory needs to be specified via
45+
; the environment variable `OPENSSH_FOR_WINDOWS_DIRECTORY`. The clone will still be performed via Git's
46+
; included `ssh.exe`, to exercise the MSYS2 runtime (which these UI tests are all about).
47+
48+
openSSHPath := EnvGet('OPENSSH_FOR_WINDOWS_DIRECTORY')
49+
if (openSSHPath != '' and FileExist(openSSHPath . '\sshd.exe')) {
50+
Info('Generate 26M of data')
51+
RunWait('git init --bare large.git', '', 'Hide')
52+
RunWait('git --git-dir=large.git -c alias.c="!(' .
53+
'printf \"reset refs/heads/main\\n\"; ' .
54+
'seq 100000 | ' .
55+
'sed \"s|.*|blob\\nmark :&\\ndata <<E\\n&\\nE\\ncommit refs/heads/main\\n' .
56+
'committer a <[email protected]> 1234& +0000\\ndata <<E\\n&\\nE\\nM 100644 :& file|\"' .
57+
') | git fast-import" c', '', 'Hide')
58+
Info('Done generating 26M of data')
59+
60+
; When running as administrator, `ssh-keygen` will generate files with
61+
; too-open permissions by default; Let's adjust them.
62+
AdjustPermissions(path) {
63+
if not A_IsAdmin
64+
return
65+
RunWait('icacls ' . path . ' /inheritance:r')
66+
if A_LastError
67+
ExitWithError 'Could not adjust ACL inheritance of ' . path . ': ' A_LastError
68+
RunWait('icacls ' . path . ' /remove "NT AUTHORITY\Authenticated Users"')
69+
if A_LastError
70+
ExitWithError 'Could not remove authenticated user permission from ' . path . ': ' A_LastError
71+
RunWait('icacls ' . path . ' /grant "Administrators:(R)"')
72+
if A_LastError
73+
ExitWithError 'Could not add admin read permission from ' . path . ': ' A_LastError
74+
}
75+
76+
; Set up SSH server
77+
Info('Generating host key')
78+
RunWait('git -c alias.c="!ssh-keygen -b 4096 -f ssh_host_rsa_key -N \"\"" c', '', 'Hide')
79+
if A_LastError
80+
ExitWithError 'Error generating host key: ' A_LastError
81+
AdjustPermissions('ssh_host_rsa_key')
82+
AdjustPermissions('ssh_host_rsa_key.pub')
83+
Info('Generating client key')
84+
RunWait('git -c alias.c="!ssh-keygen -f id_rsa -N \"\"" c', '', 'Hide')
85+
if A_LastError
86+
ExitWithError 'Error generating client key: ' A_LastError
87+
AdjustPermissions('id_rsa')
88+
AdjustPermissions('id_rsa.pub')
89+
FileAppend('Port 2322`n' .
90+
'HostKey "' . workTree . '\ssh_host_rsa_key"`n' .
91+
'AuthorizedKeysFile "' . workTree . '\id_rsa.pub"`n',
92+
'sshd_config')
93+
sshdOptions := '-f "' . workTree . '\sshd_config" -D -d -d -d -E sshd.log'
94+
95+
; Start SSH server
96+
Info('Starting SSH server')
97+
Run(openSSHPath . '\sshd.exe ' . sshdOptions, '', 'Hide', &sshdPID)
98+
if A_LastError
99+
ExitWithError 'Error starting SSH server: ' A_LastError
100+
Info('Started SSH server: ' sshdPID)
101+
102+
Info('Starting clone')
103+
workTreeMSYS := RunWaitOne('git -c alias.cygpath="!cygpath" cygpath -u "' . workTree . '"')
104+
sshOptions := '-i ' . workTreeMSYS . '/id_rsa -p 2322 -T ' .
105+
'-o UserKnownHostsFile=' . workTreeMSYS . '/known_hosts ' .
106+
'-o StrictHostKeyChecking=accept-new '
107+
; The `--upload-pack` option is needed because OpenSSH for Windows' default shell
108+
; is `cmd.exe`, which does not handle single-quoted strings as Git expects.
109+
; An heavy-handed alternative would be to require PowerShell to be configured via
110+
; HKLM:\SOFTWARE\OpenSSH's DefaultShell property, for full details see
111+
; https://github.com/PowerShell/Win32-OpenSSH/wiki/Setting-up-a-Git-server-on-Windows-using-Git-for-Windows-and-Win32_OpenSSH
112+
;
113+
; The username is needed because by default, on domain-joined machines MSYS2's
114+
; `ssh.exe` prefixes the username with the domain name.
115+
cloneOptions := '--upload-pack="powershell git upload-pack" ' .
116+
EnvGet('USERNAME') . '@localhost:' . workTree . '\large.git large-clone'
117+
Send('git -c core.sshCommand="ssh ' . sshOptions . '" clone ' . cloneOptions . '{Enter}')
118+
Sleep 50
119+
Info('Waiting for clone to start')
120+
WinActivate('ahk_id ' . hwnd)
121+
WaitForRegExInWindowsTerminal('remote: ', 'Timed out waiting for clone to start', 'Clone started', 5000, 'ahk_id ' . hwnd)
122+
Info('Trying to interrupt clone')
123+
Send('^C') ; interrupt clone
124+
Sleep 150
125+
WaitForRegExInWindowsTerminal('`nfatal: (.*`r?`n){1,3}PS .*>[ `n`r]*$', 'Timed out waiting for clone to be interrupted', 'clone was interrupted as desired')
126+
127+
if DirExist(workTree . '\large-clone')
128+
ExitWithError('`large-clone` was unexpectedly not deleted on interrupt')
129+
}
130+
44131
Send('exit{Enter}')
45132
Sleep 50
46133
CleanUpWorkTree()

0 commit comments

Comments
 (0)