git-ssh-sync 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/PKG-INFO +133 -3
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/README.md +132 -2
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/pyproject.toml +1 -1
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/__init__.py +1 -1
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/attach.py +1 -0
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/branch.py +1 -0
- git_ssh_sync-0.4.0/src/git_ssh_sync/clone.py +171 -0
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/config.py +43 -5
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/doctor.py +2 -0
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/git.py +1 -0
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/ssh.py +43 -7
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/status.py +4 -1
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/sync.py +2 -0
- git_ssh_sync-0.4.0/src/git_ssh_sync/windows_git_ssh.py +52 -0
- git_ssh_sync-0.3.0/src/git_ssh_sync/clone.py +0 -113
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/cli.py +0 -0
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/console.py +0 -0
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/dev.py +0 -0
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/errors.py +0 -0
- {git_ssh_sync-0.3.0 → git_ssh_sync-0.4.0}/src/git_ssh_sync/logging_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: git-ssh-sync
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Sync Git commits through a local machine for development environments without direct GitHub or GitLab access.
|
|
5
5
|
Requires-Dist: pydantic>=2.13.4
|
|
6
6
|
Requires-Dist: pyyaml>=6.0.3
|
|
@@ -102,9 +102,20 @@ git-ssh-sync init myproject `
|
|
|
102
102
|
--dev-host devserver `
|
|
103
103
|
--dev-user user `
|
|
104
104
|
--dev-os windows `
|
|
105
|
-
--dev-path C:\Users\user\work\myproject
|
|
105
|
+
--dev-path 'C:\Users\user\work\myproject'
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
When running the command from macOS or Linux shells such as `zsh` or `bash`,
|
|
109
|
+
quote Windows paths that contain backslashes. Otherwise the shell can remove the
|
|
110
|
+
backslashes before `git-ssh-sync` receives the argument. You can also use forward
|
|
111
|
+
slashes, for example `C:/Users/user/work/myproject`.
|
|
112
|
+
|
|
113
|
+
When `--dev-os windows` is used, the default cache path is
|
|
114
|
+
`C:\Users\<dev-user>\.git-ssh-sync\cache\<project>.git`. `clone` stops if either
|
|
115
|
+
the configured work path or cache path already exists on the development
|
|
116
|
+
environment, so remove stale directories or use the attach/recover workflow for
|
|
117
|
+
existing repositories.
|
|
118
|
+
|
|
108
119
|
For `--origin`, specify a remote URL that can be used with `git clone` or `git fetch`. Main formats are:
|
|
109
120
|
|
|
110
121
|
```text
|
|
@@ -128,6 +139,59 @@ git-ssh-sync init myproject \
|
|
|
128
139
|
--force
|
|
129
140
|
```
|
|
130
141
|
|
|
142
|
+
### Configuration file
|
|
143
|
+
|
|
144
|
+
Project settings are saved as YAML. The default path depends on the local
|
|
145
|
+
machine where `git-ssh-sync` runs:
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
macOS / Linux: ~/.config/git-ssh-sync/config.yaml
|
|
149
|
+
Windows: %APPDATA%\git-ssh-sync\config.yaml
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
A generated configuration looks like this:
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
version: 1
|
|
156
|
+
|
|
157
|
+
projects:
|
|
158
|
+
myproject:
|
|
159
|
+
origin: git@github.com:example/myproject.git
|
|
160
|
+
|
|
161
|
+
local:
|
|
162
|
+
repo_path: ~/.git-ssh-sync/repos/myproject
|
|
163
|
+
|
|
164
|
+
dev:
|
|
165
|
+
host: devserver
|
|
166
|
+
user: user
|
|
167
|
+
os: posix
|
|
168
|
+
work_path: /home/user/work/myproject
|
|
169
|
+
cache_path: /home/user/.git-ssh-sync/cache/myproject.git
|
|
170
|
+
|
|
171
|
+
options:
|
|
172
|
+
sync_tags: true
|
|
173
|
+
lfs: false
|
|
174
|
+
submodules: false
|
|
175
|
+
ff_only: true
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Main fields:
|
|
179
|
+
|
|
180
|
+
- `origin`: GitHub / GitLab repository URL used by the local gateway repository
|
|
181
|
+
- `local.repo_path`: Local gateway repository path managed by `git-ssh-sync`
|
|
182
|
+
- `dev.host`, `dev.user`, `dev.os`: SSH connection target and remote OS
|
|
183
|
+
- `dev.work_path`: Work repository path on the development environment
|
|
184
|
+
- `dev.cache_path`: Bare cache repository path on the development environment
|
|
185
|
+
- `options.sync_tags`: Synchronize Git tags when pulling or pushing
|
|
186
|
+
- `options.lfs`: Reserved option for Git LFS support
|
|
187
|
+
- `options.submodules`: Reserved option for submodule support
|
|
188
|
+
- `options.ff_only`: Keep synchronization fast-forward only
|
|
189
|
+
|
|
190
|
+
In normal use, manage this file with `git-ssh-sync init` and
|
|
191
|
+
`git-ssh-sync config` commands. If you edit it manually, keep the YAML
|
|
192
|
+
structure unchanged and use paths that are valid on the machine or
|
|
193
|
+
development environment where each field is used.
|
|
194
|
+
|
|
131
195
|
You can inspect and maintain registered projects without opening the config file directly.
|
|
132
196
|
|
|
133
197
|
```bash
|
|
@@ -250,6 +314,27 @@ git-ssh-sync push myproject
|
|
|
250
314
|
|
|
251
315
|
`pull` and `push` target the current branch of the work repository on the development environment. To synchronize a different branch, switch the work repository branch with `checkout` first.
|
|
252
316
|
|
|
317
|
+
If you are not sure about the current state at the beginning of work, first check synchronization status from the local machine and run `pull` when needed.
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
git-ssh-sync status myproject
|
|
321
|
+
git-ssh-sync pull myproject
|
|
322
|
+
git-ssh-sync dev status myproject
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
If `dev status` shows a dirty working tree on the development environment, uncommitted changes are not synchronized. Inspect the diff on the development environment and commit the changes you want to synchronize before `push`.
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
git-ssh-sync dev diff myproject --stat
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Before pushing, confirm that the development environment changes are committed, then run `status` and `push` from the local machine.
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
git-ssh-sync status myproject
|
|
335
|
+
git-ssh-sync push myproject
|
|
336
|
+
```
|
|
337
|
+
|
|
253
338
|
Use `--dry-run` to inspect the planned operations and preflight checks before changing refs:
|
|
254
339
|
|
|
255
340
|
```bash
|
|
@@ -257,6 +342,51 @@ git-ssh-sync pull myproject --dry-run
|
|
|
257
342
|
git-ssh-sync push myproject --dry-run
|
|
258
343
|
```
|
|
259
344
|
|
|
345
|
+
## Workflow When Push Stops
|
|
346
|
+
|
|
347
|
+
`push` executes only when the branch on the origin side is an ancestor of the branch on the development environment side. It stops when origin has commits that have not been pulled yet, or when origin and the development environment have diverged.
|
|
348
|
+
|
|
349
|
+
In that case, run `pull` from the local machine to deliver origin changes to the development environment.
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
git-ssh-sync pull myproject
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
If `pull` cannot fast-forward, `git-ssh-sync` does not automatically merge or rebase. Resolve it with normal Git operations on the development environment, using either merge or rebase, then run `push` again from the local machine.
|
|
356
|
+
|
|
357
|
+
Example using merge:
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
cd ~/work/myproject
|
|
361
|
+
git fetch gitsync
|
|
362
|
+
git merge gitsync/main
|
|
363
|
+
# If there are conflicts, edit the files
|
|
364
|
+
git status
|
|
365
|
+
git add <resolved-files>
|
|
366
|
+
git commit
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Example using rebase:
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
cd ~/work/myproject
|
|
373
|
+
git fetch gitsync
|
|
374
|
+
git rebase gitsync/main
|
|
375
|
+
# If there are conflicts, edit the files
|
|
376
|
+
git status
|
|
377
|
+
git add <resolved-files>
|
|
378
|
+
git rebase --continue
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
If the branch is not `main`, replace `gitsync/main` with the target branch. After merge or rebase completes, check status from the local machine and push.
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
git-ssh-sync status myproject
|
|
385
|
+
git-ssh-sync push myproject
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
After rebase, only rewrite commits that exist only on the development environment and have not been pushed to origin yet. If you want to avoid rewriting history on a shared branch, use merge.
|
|
389
|
+
|
|
260
390
|
## Branch Switching Workflow
|
|
261
391
|
|
|
262
392
|
To switch to an existing branch, execute `checkout` from the local machine.
|
|
@@ -343,7 +473,7 @@ Uncommitted changes are not synchronized. If there are uncommitted changes in th
|
|
|
343
473
|
|
|
344
474
|
`push` executes only when the branch on the origin side is an ancestor of the branch on the development environment side. If there are unobtained commits on origin, it stops.
|
|
345
475
|
|
|
346
|
-
When diverged, automatic resolution is not performed.
|
|
476
|
+
When diverged, automatic resolution is not performed. Follow "Workflow When Push Stops", merge or rebase in the development environment, then `push` again.
|
|
347
477
|
|
|
348
478
|
## Common Commands
|
|
349
479
|
|
|
@@ -91,9 +91,20 @@ git-ssh-sync init myproject `
|
|
|
91
91
|
--dev-host devserver `
|
|
92
92
|
--dev-user user `
|
|
93
93
|
--dev-os windows `
|
|
94
|
-
--dev-path C:\Users\user\work\myproject
|
|
94
|
+
--dev-path 'C:\Users\user\work\myproject'
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
When running the command from macOS or Linux shells such as `zsh` or `bash`,
|
|
98
|
+
quote Windows paths that contain backslashes. Otherwise the shell can remove the
|
|
99
|
+
backslashes before `git-ssh-sync` receives the argument. You can also use forward
|
|
100
|
+
slashes, for example `C:/Users/user/work/myproject`.
|
|
101
|
+
|
|
102
|
+
When `--dev-os windows` is used, the default cache path is
|
|
103
|
+
`C:\Users\<dev-user>\.git-ssh-sync\cache\<project>.git`. `clone` stops if either
|
|
104
|
+
the configured work path or cache path already exists on the development
|
|
105
|
+
environment, so remove stale directories or use the attach/recover workflow for
|
|
106
|
+
existing repositories.
|
|
107
|
+
|
|
97
108
|
For `--origin`, specify a remote URL that can be used with `git clone` or `git fetch`. Main formats are:
|
|
98
109
|
|
|
99
110
|
```text
|
|
@@ -117,6 +128,59 @@ git-ssh-sync init myproject \
|
|
|
117
128
|
--force
|
|
118
129
|
```
|
|
119
130
|
|
|
131
|
+
### Configuration file
|
|
132
|
+
|
|
133
|
+
Project settings are saved as YAML. The default path depends on the local
|
|
134
|
+
machine where `git-ssh-sync` runs:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
macOS / Linux: ~/.config/git-ssh-sync/config.yaml
|
|
138
|
+
Windows: %APPDATA%\git-ssh-sync\config.yaml
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
A generated configuration looks like this:
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
version: 1
|
|
145
|
+
|
|
146
|
+
projects:
|
|
147
|
+
myproject:
|
|
148
|
+
origin: git@github.com:example/myproject.git
|
|
149
|
+
|
|
150
|
+
local:
|
|
151
|
+
repo_path: ~/.git-ssh-sync/repos/myproject
|
|
152
|
+
|
|
153
|
+
dev:
|
|
154
|
+
host: devserver
|
|
155
|
+
user: user
|
|
156
|
+
os: posix
|
|
157
|
+
work_path: /home/user/work/myproject
|
|
158
|
+
cache_path: /home/user/.git-ssh-sync/cache/myproject.git
|
|
159
|
+
|
|
160
|
+
options:
|
|
161
|
+
sync_tags: true
|
|
162
|
+
lfs: false
|
|
163
|
+
submodules: false
|
|
164
|
+
ff_only: true
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Main fields:
|
|
168
|
+
|
|
169
|
+
- `origin`: GitHub / GitLab repository URL used by the local gateway repository
|
|
170
|
+
- `local.repo_path`: Local gateway repository path managed by `git-ssh-sync`
|
|
171
|
+
- `dev.host`, `dev.user`, `dev.os`: SSH connection target and remote OS
|
|
172
|
+
- `dev.work_path`: Work repository path on the development environment
|
|
173
|
+
- `dev.cache_path`: Bare cache repository path on the development environment
|
|
174
|
+
- `options.sync_tags`: Synchronize Git tags when pulling or pushing
|
|
175
|
+
- `options.lfs`: Reserved option for Git LFS support
|
|
176
|
+
- `options.submodules`: Reserved option for submodule support
|
|
177
|
+
- `options.ff_only`: Keep synchronization fast-forward only
|
|
178
|
+
|
|
179
|
+
In normal use, manage this file with `git-ssh-sync init` and
|
|
180
|
+
`git-ssh-sync config` commands. If you edit it manually, keep the YAML
|
|
181
|
+
structure unchanged and use paths that are valid on the machine or
|
|
182
|
+
development environment where each field is used.
|
|
183
|
+
|
|
120
184
|
You can inspect and maintain registered projects without opening the config file directly.
|
|
121
185
|
|
|
122
186
|
```bash
|
|
@@ -239,6 +303,27 @@ git-ssh-sync push myproject
|
|
|
239
303
|
|
|
240
304
|
`pull` and `push` target the current branch of the work repository on the development environment. To synchronize a different branch, switch the work repository branch with `checkout` first.
|
|
241
305
|
|
|
306
|
+
If you are not sure about the current state at the beginning of work, first check synchronization status from the local machine and run `pull` when needed.
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
git-ssh-sync status myproject
|
|
310
|
+
git-ssh-sync pull myproject
|
|
311
|
+
git-ssh-sync dev status myproject
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
If `dev status` shows a dirty working tree on the development environment, uncommitted changes are not synchronized. Inspect the diff on the development environment and commit the changes you want to synchronize before `push`.
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
git-ssh-sync dev diff myproject --stat
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Before pushing, confirm that the development environment changes are committed, then run `status` and `push` from the local machine.
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
git-ssh-sync status myproject
|
|
324
|
+
git-ssh-sync push myproject
|
|
325
|
+
```
|
|
326
|
+
|
|
242
327
|
Use `--dry-run` to inspect the planned operations and preflight checks before changing refs:
|
|
243
328
|
|
|
244
329
|
```bash
|
|
@@ -246,6 +331,51 @@ git-ssh-sync pull myproject --dry-run
|
|
|
246
331
|
git-ssh-sync push myproject --dry-run
|
|
247
332
|
```
|
|
248
333
|
|
|
334
|
+
## Workflow When Push Stops
|
|
335
|
+
|
|
336
|
+
`push` executes only when the branch on the origin side is an ancestor of the branch on the development environment side. It stops when origin has commits that have not been pulled yet, or when origin and the development environment have diverged.
|
|
337
|
+
|
|
338
|
+
In that case, run `pull` from the local machine to deliver origin changes to the development environment.
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
git-ssh-sync pull myproject
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
If `pull` cannot fast-forward, `git-ssh-sync` does not automatically merge or rebase. Resolve it with normal Git operations on the development environment, using either merge or rebase, then run `push` again from the local machine.
|
|
345
|
+
|
|
346
|
+
Example using merge:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
cd ~/work/myproject
|
|
350
|
+
git fetch gitsync
|
|
351
|
+
git merge gitsync/main
|
|
352
|
+
# If there are conflicts, edit the files
|
|
353
|
+
git status
|
|
354
|
+
git add <resolved-files>
|
|
355
|
+
git commit
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Example using rebase:
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
cd ~/work/myproject
|
|
362
|
+
git fetch gitsync
|
|
363
|
+
git rebase gitsync/main
|
|
364
|
+
# If there are conflicts, edit the files
|
|
365
|
+
git status
|
|
366
|
+
git add <resolved-files>
|
|
367
|
+
git rebase --continue
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
If the branch is not `main`, replace `gitsync/main` with the target branch. After merge or rebase completes, check status from the local machine and push.
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
git-ssh-sync status myproject
|
|
374
|
+
git-ssh-sync push myproject
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
After rebase, only rewrite commits that exist only on the development environment and have not been pushed to origin yet. If you want to avoid rewriting history on a shared branch, use merge.
|
|
378
|
+
|
|
249
379
|
## Branch Switching Workflow
|
|
250
380
|
|
|
251
381
|
To switch to an existing branch, execute `checkout` from the local machine.
|
|
@@ -332,7 +462,7 @@ Uncommitted changes are not synchronized. If there are uncommitted changes in th
|
|
|
332
462
|
|
|
333
463
|
`push` executes only when the branch on the origin side is an ancestor of the branch on the development environment side. If there are unobtained commits on origin, it stops.
|
|
334
464
|
|
|
335
|
-
When diverged, automatic resolution is not performed.
|
|
465
|
+
When diverged, automatic resolution is not performed. Follow "Workflow When Push Stops", merge or rebase in the development environment, then `push` again.
|
|
336
466
|
|
|
337
467
|
## Common Commands
|
|
338
468
|
|
|
@@ -146,6 +146,7 @@ def inspect_project_branch(project: str, project_config: ProjectConfig) -> Branc
|
|
|
146
146
|
dev_repo_url,
|
|
147
147
|
[f"refs/heads/{branch}:refs/remotes/dev/{branch}"],
|
|
148
148
|
cwd=local_path,
|
|
149
|
+
env=ssh.git_ssh_environment(project_config.dev.os),
|
|
149
150
|
)
|
|
150
151
|
|
|
151
152
|
rows: list[BranchRow] = []
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Project clone workflow."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from git_ssh_sync import git, ssh
|
|
9
|
+
from git_ssh_sync.config import get_project, load_config
|
|
10
|
+
from git_ssh_sync.errors import CommandExecutionError
|
|
11
|
+
from git_ssh_sync.logging_config import logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CloneError(RuntimeError):
|
|
15
|
+
"""Raised when the clone workflow would overwrite existing data."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _ensure_local_missing(path: Path) -> None:
|
|
19
|
+
if path.exists():
|
|
20
|
+
raise CloneError(f"[local] path already exists: {path}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _ensure_remote_missing(
|
|
24
|
+
*, host: str, user: str, path: str, remote_os: ssh.RemoteOS
|
|
25
|
+
) -> None:
|
|
26
|
+
result = ssh.remote_path_exists(
|
|
27
|
+
host, path, user=user, remote_os=remote_os, path_type="any"
|
|
28
|
+
)
|
|
29
|
+
if result.returncode == 0:
|
|
30
|
+
raise CloneError(f"[{result.environment}] path already exists: {path}")
|
|
31
|
+
if result.returncode == 1:
|
|
32
|
+
return
|
|
33
|
+
raise CommandExecutionError(
|
|
34
|
+
environment=result.environment,
|
|
35
|
+
command=result.command,
|
|
36
|
+
returncode=result.returncode,
|
|
37
|
+
cwd=result.cwd,
|
|
38
|
+
stdout=result.stdout,
|
|
39
|
+
stderr=result.stderr,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _local_current_branch(local_path: Path) -> str:
|
|
44
|
+
result = git.run_git(["branch", "--show-current"], cwd=local_path)
|
|
45
|
+
branch = result.stdout.strip()
|
|
46
|
+
if not branch:
|
|
47
|
+
raise CloneError("Could not determine the cloned repository's current branch.")
|
|
48
|
+
return branch
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _cleanup_local_path(path: Path) -> None:
|
|
52
|
+
if not path.exists():
|
|
53
|
+
return
|
|
54
|
+
try:
|
|
55
|
+
if path.is_dir() and not path.is_symlink():
|
|
56
|
+
shutil.rmtree(path)
|
|
57
|
+
return
|
|
58
|
+
path.unlink()
|
|
59
|
+
except OSError as error:
|
|
60
|
+
logger.debug("Failed to clean up local path %s: %s", path, error)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _cleanup_remote_path(
|
|
64
|
+
*, host: str, user: str, path: str, remote_os: ssh.RemoteOS
|
|
65
|
+
) -> None:
|
|
66
|
+
result = ssh.remote_remove(host, path, user=user, remote_os=remote_os)
|
|
67
|
+
if result.returncode != 0:
|
|
68
|
+
logger.debug(
|
|
69
|
+
"Failed to clean up remote path %s on %s: %s",
|
|
70
|
+
path,
|
|
71
|
+
result.environment,
|
|
72
|
+
result.stderr.strip(),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def clone_project(project: str) -> None:
|
|
77
|
+
"""Clone a configured project and initialize its development repositories."""
|
|
78
|
+
app_config = load_config()
|
|
79
|
+
project_config = get_project(app_config, project)
|
|
80
|
+
|
|
81
|
+
local_path = Path(project_config.local.repo_path)
|
|
82
|
+
dev_host = project_config.dev.host
|
|
83
|
+
dev_user = project_config.dev.user
|
|
84
|
+
dev_os = project_config.dev.os
|
|
85
|
+
cache_path = project_config.dev.cache_path
|
|
86
|
+
work_path = project_config.dev.work_path
|
|
87
|
+
|
|
88
|
+
_ensure_local_missing(local_path)
|
|
89
|
+
_ensure_remote_missing(
|
|
90
|
+
host=dev_host, user=dev_user, path=cache_path, remote_os=dev_os
|
|
91
|
+
)
|
|
92
|
+
_ensure_remote_missing(
|
|
93
|
+
host=dev_host, user=dev_user, path=work_path, remote_os=dev_os
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
local_started = False
|
|
97
|
+
cache_started = False
|
|
98
|
+
work_started = False
|
|
99
|
+
try:
|
|
100
|
+
local_path.parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
local_started = True
|
|
102
|
+
git.run_git(["clone", project_config.origin, str(local_path)])
|
|
103
|
+
git.fetch("origin", cwd=local_path)
|
|
104
|
+
branch = _local_current_branch(local_path)
|
|
105
|
+
|
|
106
|
+
ssh.remote_mkdir(
|
|
107
|
+
dev_host,
|
|
108
|
+
ssh.remote_parent(cache_path, dev_os),
|
|
109
|
+
user=dev_user,
|
|
110
|
+
remote_os=dev_os,
|
|
111
|
+
)
|
|
112
|
+
cache_started = True
|
|
113
|
+
ssh.run_remote_command(
|
|
114
|
+
dev_host,
|
|
115
|
+
["git", "init", "--bare", cache_path],
|
|
116
|
+
user=dev_user,
|
|
117
|
+
remote_os=dev_os,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
remote_cache = ssh.remote_git_url(
|
|
121
|
+
host=dev_host, user=dev_user, repo_path=cache_path, remote_os=dev_os
|
|
122
|
+
)
|
|
123
|
+
git.push(
|
|
124
|
+
remote_cache,
|
|
125
|
+
[f"refs/remotes/origin/{branch}:refs/heads/{branch}"],
|
|
126
|
+
cwd=local_path,
|
|
127
|
+
env=ssh.git_ssh_environment(dev_os),
|
|
128
|
+
)
|
|
129
|
+
if project_config.options.sync_tags:
|
|
130
|
+
git.push(
|
|
131
|
+
remote_cache,
|
|
132
|
+
["--tags"],
|
|
133
|
+
cwd=local_path,
|
|
134
|
+
env=ssh.git_ssh_environment(dev_os),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
ssh.remote_mkdir(
|
|
138
|
+
dev_host,
|
|
139
|
+
ssh.remote_parent(work_path, dev_os),
|
|
140
|
+
user=dev_user,
|
|
141
|
+
remote_os=dev_os,
|
|
142
|
+
)
|
|
143
|
+
work_started = True
|
|
144
|
+
ssh.run_remote_command(
|
|
145
|
+
dev_host,
|
|
146
|
+
["git", "clone", cache_path, work_path],
|
|
147
|
+
user=dev_user,
|
|
148
|
+
remote_os=dev_os,
|
|
149
|
+
)
|
|
150
|
+
ssh.run_remote_git(
|
|
151
|
+
dev_host,
|
|
152
|
+
work_path,
|
|
153
|
+
["remote", "rename", "origin", "gitsync"],
|
|
154
|
+
user=dev_user,
|
|
155
|
+
remote_os=dev_os,
|
|
156
|
+
)
|
|
157
|
+
ssh.run_remote_git(
|
|
158
|
+
dev_host, work_path, ["switch", branch], user=dev_user, remote_os=dev_os
|
|
159
|
+
)
|
|
160
|
+
except Exception:
|
|
161
|
+
if work_started:
|
|
162
|
+
_cleanup_remote_path(
|
|
163
|
+
host=dev_host, user=dev_user, path=work_path, remote_os=dev_os
|
|
164
|
+
)
|
|
165
|
+
if cache_started:
|
|
166
|
+
_cleanup_remote_path(
|
|
167
|
+
host=dev_host, user=dev_user, path=cache_path, remote_os=dev_os
|
|
168
|
+
)
|
|
169
|
+
if local_started:
|
|
170
|
+
_cleanup_local_path(local_path)
|
|
171
|
+
raise
|
|
@@ -3,12 +3,19 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
+
import re
|
|
6
7
|
import sys
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any, Literal
|
|
9
10
|
|
|
10
11
|
import yaml
|
|
11
|
-
from pydantic import
|
|
12
|
+
from pydantic import (
|
|
13
|
+
BaseModel,
|
|
14
|
+
ConfigDict,
|
|
15
|
+
Field,
|
|
16
|
+
ValidationError,
|
|
17
|
+
field_validator,
|
|
18
|
+
)
|
|
12
19
|
|
|
13
20
|
|
|
14
21
|
class ConfigError(Exception):
|
|
@@ -31,6 +38,29 @@ def _expand_path(value: str) -> str:
|
|
|
31
38
|
return str(Path(value).expanduser())
|
|
32
39
|
|
|
33
40
|
|
|
41
|
+
def _default_dev_cache_path(
|
|
42
|
+
project: str, dev_user: str | None, dev_os: Literal["posix", "windows"]
|
|
43
|
+
) -> str:
|
|
44
|
+
if dev_os == "windows":
|
|
45
|
+
return f"C:\\Users\\{dev_user}\\.git-ssh-sync\\cache\\{project}.git"
|
|
46
|
+
return f"/home/{dev_user}/.git-ssh-sync/cache/{project}.git"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _looks_like_unquoted_windows_path(value: str) -> bool:
|
|
50
|
+
return bool(re.match(r"^[A-Za-z]:[^\\/]", value))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _validate_windows_path_input(field_name: str, value: str | None) -> None:
|
|
54
|
+
if value is None or not _looks_like_unquoted_windows_path(value):
|
|
55
|
+
return
|
|
56
|
+
raise ConfigError(
|
|
57
|
+
f"{field_name} looks like a Windows path whose separators were removed "
|
|
58
|
+
"by the shell. Quote backslash paths, for example "
|
|
59
|
+
r"'C:\Users\user\work\repo', or use forward slashes like "
|
|
60
|
+
"C:/Users/user/work/repo."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
34
64
|
class LocalConfig(BaseModel):
|
|
35
65
|
model_config = ConfigDict(extra="forbid")
|
|
36
66
|
|
|
@@ -48,9 +78,6 @@ class DevConfig(BaseModel):
|
|
|
48
78
|
work_path: str = Field(min_length=1)
|
|
49
79
|
cache_path: str = Field(min_length=1)
|
|
50
80
|
|
|
51
|
-
_expand_work_path = field_validator("work_path")(_expand_path)
|
|
52
|
-
_expand_cache_path = field_validator("cache_path")(_expand_path)
|
|
53
|
-
|
|
54
81
|
|
|
55
82
|
class OptionsConfig(BaseModel):
|
|
56
83
|
model_config = ConfigDict(extra="forbid")
|
|
@@ -194,6 +221,13 @@ def update_project(
|
|
|
194
221
|
raw["dev"]["cache_path"] = dev_cache_path
|
|
195
222
|
updated = True
|
|
196
223
|
|
|
224
|
+
effective_dev_os = raw["dev"].get("os")
|
|
225
|
+
if effective_dev_os == "windows":
|
|
226
|
+
if dev_work_path is not None:
|
|
227
|
+
_validate_windows_path_input("dev.work_path", dev_work_path)
|
|
228
|
+
if dev_cache_path is not None:
|
|
229
|
+
_validate_windows_path_input("dev.cache_path", dev_cache_path)
|
|
230
|
+
|
|
197
231
|
for key, value in {
|
|
198
232
|
"sync_tags": sync_tags,
|
|
199
233
|
"lfs": lfs,
|
|
@@ -234,6 +268,10 @@ def build_project_config(
|
|
|
234
268
|
options: OptionsConfig | None = None,
|
|
235
269
|
) -> ProjectConfig:
|
|
236
270
|
"""Build and validate a project config, applying init defaults."""
|
|
271
|
+
if dev_os == "windows":
|
|
272
|
+
_validate_windows_path_input("dev.work_path", dev_work_path)
|
|
273
|
+
_validate_windows_path_input("dev.cache_path", dev_cache_path)
|
|
274
|
+
|
|
237
275
|
raw: dict[str, Any] = {
|
|
238
276
|
"origin": origin,
|
|
239
277
|
"local": {
|
|
@@ -245,7 +283,7 @@ def build_project_config(
|
|
|
245
283
|
"os": dev_os,
|
|
246
284
|
"work_path": dev_work_path,
|
|
247
285
|
"cache_path": dev_cache_path
|
|
248
|
-
or
|
|
286
|
+
or _default_dev_cache_path(project, dev_user, dev_os),
|
|
249
287
|
},
|
|
250
288
|
"options": (options or OptionsConfig()).model_dump(mode="json"),
|
|
251
289
|
}
|
|
@@ -621,6 +621,7 @@ def _check_repository(
|
|
|
621
621
|
cache_repo_url,
|
|
622
622
|
[f"refs/heads/{branch}:refs/remotes/dev-cache/{branch}"],
|
|
623
623
|
cwd=local_path,
|
|
624
|
+
env=ssh.git_ssh_environment(project_config.dev.os),
|
|
624
625
|
)
|
|
625
626
|
except CommandExecutionError as error:
|
|
626
627
|
checks.append(
|
|
@@ -647,6 +648,7 @@ def _check_repository(
|
|
|
647
648
|
dev_repo_url,
|
|
648
649
|
[f"refs/heads/{branch}:refs/remotes/dev/{branch}"],
|
|
649
650
|
cwd=local_path,
|
|
651
|
+
env=ssh.git_ssh_environment(project_config.dev.os),
|
|
650
652
|
)
|
|
651
653
|
except CommandExecutionError as error:
|
|
652
654
|
checks.append(
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import shlex
|
|
6
|
+
import sys
|
|
7
|
+
from base64 import b64encode
|
|
6
8
|
from collections.abc import Mapping, Sequence
|
|
7
9
|
from pathlib import Path, PurePosixPath, PureWindowsPath
|
|
8
10
|
from typing import Literal
|
|
@@ -86,9 +88,10 @@ def run_powershell(
|
|
|
86
88
|
check: bool = True,
|
|
87
89
|
) -> CommandResult:
|
|
88
90
|
"""Run a PowerShell script on an SSH host."""
|
|
91
|
+
encoded_script = b64encode(script.encode("utf-16le")).decode("ascii")
|
|
89
92
|
remote_command = (
|
|
90
|
-
"powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass
|
|
91
|
-
f"{
|
|
93
|
+
"powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass "
|
|
94
|
+
f"-EncodedCommand {encoded_script}"
|
|
92
95
|
)
|
|
93
96
|
return _run_ssh_command_string(
|
|
94
97
|
host,
|
|
@@ -183,15 +186,31 @@ def run_remote_git(
|
|
|
183
186
|
|
|
184
187
|
def remote_git_url(*, host: str, user: str, repo_path: str, remote_os: RemoteOS) -> str:
|
|
185
188
|
"""Build an SSH Git URL for a remote repository path."""
|
|
186
|
-
|
|
187
|
-
repo_path.replace("\\", "/")
|
|
188
|
-
|
|
189
|
+
if remote_os == "windows":
|
|
190
|
+
normalized_path = repo_path.replace("\\", "/")
|
|
191
|
+
return f"{user}@{host}:{normalized_path}"
|
|
192
|
+
|
|
193
|
+
normalized_path = repo_path
|
|
189
194
|
quoted_path = quote(normalized_path, safe="/~:")
|
|
190
|
-
if remote_os == "windows" and not quoted_path.startswith("/"):
|
|
191
|
-
quoted_path = f"/{quoted_path}"
|
|
192
195
|
return f"ssh://{user}@{host}{quoted_path}"
|
|
193
196
|
|
|
194
197
|
|
|
198
|
+
def git_ssh_environment(
|
|
199
|
+
remote_os: RemoteOS, env: Mapping[str, str] | None = None
|
|
200
|
+
) -> Mapping[str, str] | None:
|
|
201
|
+
"""Return environment overrides for local Git SSH transport."""
|
|
202
|
+
if remote_os != "windows":
|
|
203
|
+
return env
|
|
204
|
+
|
|
205
|
+
git_env = dict(env or {})
|
|
206
|
+
if existing_command := git_env.get("GIT_SSH_COMMAND"):
|
|
207
|
+
git_env["GIT_SSH_SYNC_BASE_SSH_COMMAND"] = existing_command
|
|
208
|
+
git_env["GIT_SSH_COMMAND"] = (
|
|
209
|
+
f"{shlex.quote(sys.executable)} -m git_ssh_sync.windows_git_ssh"
|
|
210
|
+
)
|
|
211
|
+
return git_env
|
|
212
|
+
|
|
213
|
+
|
|
195
214
|
def remote_parent(path: str, remote_os: RemoteOS) -> str:
|
|
196
215
|
"""Return the parent directory using the remote operating system's path rules."""
|
|
197
216
|
if remote_os == "windows":
|
|
@@ -237,6 +256,23 @@ def remote_mkdir(
|
|
|
237
256
|
return run_ssh(host, ["mkdir", "-p", path], user=user)
|
|
238
257
|
|
|
239
258
|
|
|
259
|
+
def remote_remove(
|
|
260
|
+
host: str,
|
|
261
|
+
path: str,
|
|
262
|
+
*,
|
|
263
|
+
user: str,
|
|
264
|
+
remote_os: RemoteOS,
|
|
265
|
+
) -> CommandResult:
|
|
266
|
+
"""Remove a remote path recursively if it exists."""
|
|
267
|
+
if remote_os == "windows":
|
|
268
|
+
script = (
|
|
269
|
+
"Remove-Item -LiteralPath "
|
|
270
|
+
f"{_powershell_quote(path)} -Recurse -Force -ErrorAction SilentlyContinue"
|
|
271
|
+
)
|
|
272
|
+
return run_powershell(host, script, user=user, check=False)
|
|
273
|
+
return run_ssh(host, ["rm", "-rf", "--", path], user=user, check=False)
|
|
274
|
+
|
|
275
|
+
|
|
240
276
|
def remote_command_exists(
|
|
241
277
|
host: str,
|
|
242
278
|
command: str,
|
|
@@ -134,7 +134,10 @@ def inspect_project_status(project: str, project_config: ProjectConfig) -> Statu
|
|
|
134
134
|
host=dev_host, user=dev_user, repo_path=dev_work_path, remote_os=dev_os
|
|
135
135
|
)
|
|
136
136
|
git.fetch(
|
|
137
|
-
dev_repo_url,
|
|
137
|
+
dev_repo_url,
|
|
138
|
+
[f"refs/heads/{branch}:refs/remotes/dev/{branch}"],
|
|
139
|
+
cwd=local_path,
|
|
140
|
+
env=ssh.git_ssh_environment(dev_os),
|
|
138
141
|
)
|
|
139
142
|
|
|
140
143
|
origin_ref = f"origin/{branch}"
|
|
@@ -123,6 +123,7 @@ def _push_origin_branch_to_cache(
|
|
|
123
123
|
remote_cache,
|
|
124
124
|
[f"refs/remotes/origin/{branch}:refs/heads/{branch}"],
|
|
125
125
|
cwd=local_path,
|
|
126
|
+
env=ssh.git_ssh_environment(project_config.dev.os),
|
|
126
127
|
)
|
|
127
128
|
|
|
128
129
|
|
|
@@ -239,6 +240,7 @@ def _fetch_dev_branch_to_local(
|
|
|
239
240
|
_work_url(project_config),
|
|
240
241
|
[f"refs/heads/{branch}:refs/remotes/dev/{branch}"],
|
|
241
242
|
cwd=local_path,
|
|
243
|
+
env=ssh.git_ssh_environment(project_config.dev.os),
|
|
242
244
|
)
|
|
243
245
|
|
|
244
246
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""SSH command wrapper for Git protocol commands targeting Windows hosts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shlex
|
|
7
|
+
import sys
|
|
8
|
+
from base64 import b64encode
|
|
9
|
+
|
|
10
|
+
GIT_PROTOCOL_COMMANDS = {"git-receive-pack", "git-upload-pack", "git-upload-archive"}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _powershell_quote(value: str) -> str:
|
|
14
|
+
return "'" + value.replace("'", "''") + "'"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _base_ssh_command() -> list[str]:
|
|
18
|
+
command = os.environ.get("GIT_SSH_SYNC_BASE_SSH_COMMAND", "ssh")
|
|
19
|
+
return shlex.split(command)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _powershell_command(remote_command: str) -> str | None:
|
|
23
|
+
try:
|
|
24
|
+
parts = shlex.split(remote_command, posix=True)
|
|
25
|
+
except ValueError:
|
|
26
|
+
return None
|
|
27
|
+
if len(parts) < 2 or parts[0] not in GIT_PROTOCOL_COMMANDS:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
script = "& " + " ".join(_powershell_quote(part) for part in parts)
|
|
31
|
+
encoded_script = b64encode(script.encode("utf-16le")).decode("ascii")
|
|
32
|
+
return (
|
|
33
|
+
"powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass "
|
|
34
|
+
f"-EncodedCommand {encoded_script}"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main() -> None:
|
|
39
|
+
args = sys.argv[1:]
|
|
40
|
+
base_command = _base_ssh_command()
|
|
41
|
+
if len(args) < 2:
|
|
42
|
+
os.execvp(base_command[0], [*base_command, *args])
|
|
43
|
+
|
|
44
|
+
remote_command = _powershell_command(args[-1])
|
|
45
|
+
if remote_command is None:
|
|
46
|
+
os.execvp(base_command[0], [*base_command, *args])
|
|
47
|
+
|
|
48
|
+
os.execvp(base_command[0], [*base_command, *args[:-1], remote_command])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
main()
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
"""Project clone workflow."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from git_ssh_sync import git, ssh
|
|
8
|
-
from git_ssh_sync.config import get_project, load_config
|
|
9
|
-
from git_ssh_sync.errors import CommandExecutionError
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class CloneError(RuntimeError):
|
|
13
|
-
"""Raised when the clone workflow would overwrite existing data."""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def _ensure_local_missing(path: Path) -> None:
|
|
17
|
-
if path.exists():
|
|
18
|
-
raise CloneError(f"[local] path already exists: {path}")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _ensure_remote_missing(
|
|
22
|
-
*, host: str, user: str, path: str, remote_os: ssh.RemoteOS
|
|
23
|
-
) -> None:
|
|
24
|
-
result = ssh.remote_path_exists(
|
|
25
|
-
host, path, user=user, remote_os=remote_os, path_type="any"
|
|
26
|
-
)
|
|
27
|
-
if result.returncode == 0:
|
|
28
|
-
raise CloneError(f"[{result.environment}] path already exists: {path}")
|
|
29
|
-
if result.returncode == 1:
|
|
30
|
-
return
|
|
31
|
-
raise CommandExecutionError(
|
|
32
|
-
environment=result.environment,
|
|
33
|
-
command=result.command,
|
|
34
|
-
returncode=result.returncode,
|
|
35
|
-
cwd=result.cwd,
|
|
36
|
-
stdout=result.stdout,
|
|
37
|
-
stderr=result.stderr,
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _local_current_branch(local_path: Path) -> str:
|
|
42
|
-
result = git.run_git(["branch", "--show-current"], cwd=local_path)
|
|
43
|
-
branch = result.stdout.strip()
|
|
44
|
-
if not branch:
|
|
45
|
-
raise CloneError("Could not determine the cloned repository's current branch.")
|
|
46
|
-
return branch
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def clone_project(project: str) -> None:
|
|
50
|
-
"""Clone a configured project and initialize its development repositories."""
|
|
51
|
-
app_config = load_config()
|
|
52
|
-
project_config = get_project(app_config, project)
|
|
53
|
-
|
|
54
|
-
local_path = Path(project_config.local.repo_path)
|
|
55
|
-
dev_host = project_config.dev.host
|
|
56
|
-
dev_user = project_config.dev.user
|
|
57
|
-
dev_os = project_config.dev.os
|
|
58
|
-
cache_path = project_config.dev.cache_path
|
|
59
|
-
work_path = project_config.dev.work_path
|
|
60
|
-
|
|
61
|
-
_ensure_local_missing(local_path)
|
|
62
|
-
_ensure_remote_missing(
|
|
63
|
-
host=dev_host, user=dev_user, path=cache_path, remote_os=dev_os
|
|
64
|
-
)
|
|
65
|
-
_ensure_remote_missing(
|
|
66
|
-
host=dev_host, user=dev_user, path=work_path, remote_os=dev_os
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
local_path.parent.mkdir(parents=True, exist_ok=True)
|
|
70
|
-
git.run_git(["clone", project_config.origin, str(local_path)])
|
|
71
|
-
git.fetch("origin", cwd=local_path)
|
|
72
|
-
branch = _local_current_branch(local_path)
|
|
73
|
-
|
|
74
|
-
ssh.remote_mkdir(
|
|
75
|
-
dev_host, ssh.remote_parent(cache_path, dev_os), user=dev_user, remote_os=dev_os
|
|
76
|
-
)
|
|
77
|
-
ssh.run_remote_command(
|
|
78
|
-
dev_host,
|
|
79
|
-
["git", "init", "--bare", cache_path],
|
|
80
|
-
user=dev_user,
|
|
81
|
-
remote_os=dev_os,
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
remote_cache = ssh.remote_git_url(
|
|
85
|
-
host=dev_host, user=dev_user, repo_path=cache_path, remote_os=dev_os
|
|
86
|
-
)
|
|
87
|
-
git.push(
|
|
88
|
-
remote_cache,
|
|
89
|
-
[f"refs/remotes/origin/{branch}:refs/heads/{branch}"],
|
|
90
|
-
cwd=local_path,
|
|
91
|
-
)
|
|
92
|
-
if project_config.options.sync_tags:
|
|
93
|
-
git.push(remote_cache, ["--tags"], cwd=local_path)
|
|
94
|
-
|
|
95
|
-
ssh.remote_mkdir(
|
|
96
|
-
dev_host, ssh.remote_parent(work_path, dev_os), user=dev_user, remote_os=dev_os
|
|
97
|
-
)
|
|
98
|
-
ssh.run_remote_command(
|
|
99
|
-
dev_host,
|
|
100
|
-
["git", "clone", cache_path, work_path],
|
|
101
|
-
user=dev_user,
|
|
102
|
-
remote_os=dev_os,
|
|
103
|
-
)
|
|
104
|
-
ssh.run_remote_git(
|
|
105
|
-
dev_host,
|
|
106
|
-
work_path,
|
|
107
|
-
["remote", "rename", "origin", "gitsync"],
|
|
108
|
-
user=dev_user,
|
|
109
|
-
remote_os=dev_os,
|
|
110
|
-
)
|
|
111
|
-
ssh.run_remote_git(
|
|
112
|
-
dev_host, work_path, ["switch", branch], user=dev_user, remote_os=dev_os
|
|
113
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|