Skip to content

Commit e7cae0b

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 ebf3a9a commit e7cae0b

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.AUTOHOTKEY_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
@@ -40,6 +40,93 @@ Sleep 150
4040
; Wait for the `^C` tell-tale that is the PowerShell prompt to appear
4141
WaitForRegExInWindowsTerminal('>[ `n`r]*$', 'Timed out waiting for interrupt', 'Sleep was interrupted as desired')
4242

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

0 commit comments

Comments
 (0)