git-ssh-sync 0.4.0__tar.gz → 0.5.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: git-ssh-sync
3
- Version: 0.4.0
3
+ Version: 0.5.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "git-ssh-sync"
3
- version = "0.4.0"
3
+ version = "0.5.0"
4
4
  description = "Sync Git commits through a local machine for development environments without direct GitHub or GitLab access."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,3 +1,3 @@
1
1
  """git-ssh-sync package."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.5.0"
@@ -109,7 +109,7 @@ def default_config_path() -> Path:
109
109
  if sys.platform == "win32":
110
110
  base = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
111
111
  else:
112
- base = Path.home() / ".config"
112
+ base = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"))
113
113
  return base / "git-ssh-sync" / "config.yaml"
114
114
 
115
115
 
@@ -197,6 +197,7 @@ def remote(
197
197
  cwd: str | Path | None = None,
198
198
  env: Mapping[str, str] | None = None,
199
199
  verbose: bool = False,
200
+ check: bool = True,
200
201
  ) -> CommandResult:
201
202
  """Run `git remote`."""
202
- return run_git(["remote", *args], cwd=cwd, env=env, verbose=verbose)
203
+ return run_git(["remote", *args], cwd=cwd, env=env, verbose=verbose, check=check)
@@ -90,6 +90,31 @@ def _ensure_origin_branch(local_path: Path, branch: str) -> None:
90
90
  )
91
91
 
92
92
 
93
+ def _origin_branch_exists_on_remote(local_path: Path, branch: str) -> bool:
94
+ result = git.run_git(
95
+ ["ls-remote", "--exit-code", "--heads", "origin", branch],
96
+ cwd=local_path,
97
+ check=False,
98
+ )
99
+ if result.returncode == 0:
100
+ return True
101
+ if result.returncode == 2:
102
+ return False
103
+ raise CommandExecutionError(
104
+ environment=result.environment,
105
+ command=result.command,
106
+ returncode=result.returncode,
107
+ cwd=result.cwd,
108
+ stdout=result.stdout,
109
+ stderr=result.stderr,
110
+ )
111
+
112
+
113
+ def _ensure_origin_branch_on_remote(local_path: Path, branch: str) -> None:
114
+ if not _origin_branch_exists_on_remote(local_path, branch):
115
+ raise SyncError(f"Origin branch does not exist: {branch}")
116
+
117
+
93
118
  def _ensure_origin_branch_missing(project: str, local_path: Path, branch: str) -> None:
94
119
  if _origin_branch_exists(local_path, branch):
95
120
  raise SyncError(
@@ -99,6 +124,17 @@ def _ensure_origin_branch_missing(project: str, local_path: Path, branch: str) -
99
124
  )
100
125
 
101
126
 
127
+ def _ensure_origin_branch_missing_on_remote(
128
+ project: str, local_path: Path, branch: str
129
+ ) -> None:
130
+ if _origin_branch_exists_on_remote(local_path, branch):
131
+ raise SyncError(
132
+ f"Origin branch already exists: {branch}\n\n"
133
+ "Run checkout without --base to use the existing branch:\n\n"
134
+ f" git-ssh-sync checkout {project} {branch}"
135
+ )
136
+
137
+
102
138
  def _create_origin_branch(local_path: Path, branch: str, base_branch: str) -> None:
103
139
  git.push(
104
140
  "origin",
@@ -369,6 +405,47 @@ def _ensure_pushable(
369
405
  )
370
406
 
371
407
 
408
+ def _ensure_pushable_from_development(
409
+ project: str, project_config: ProjectConfig, local_path: Path, branch: str
410
+ ) -> None:
411
+ origin_head = git.rev_parse([f"refs/remotes/origin/{branch}"], cwd=local_path)
412
+ origin_commit = origin_head.stdout.strip()
413
+ result = ssh.run_remote_git(
414
+ project_config.dev.host,
415
+ project_config.dev.work_path,
416
+ ["merge-base", "--is-ancestor", origin_commit, f"refs/heads/{branch}"],
417
+ user=project_config.dev.user,
418
+ check=False,
419
+ remote_os=project_config.dev.os,
420
+ )
421
+ if result.returncode == 0:
422
+ return
423
+ if result.returncode == 1:
424
+ current_branch = _remote_current_branch(project_config)
425
+ current_commit = _remote_short_head(project_config)
426
+ raise SyncError(
427
+ f"Cannot push {branch}.\n\n"
428
+ f"origin/{branch} has commits that are not included in dev/{branch}.\n\n"
429
+ f"Project:\n {project}\n\n"
430
+ "Development:\n"
431
+ f" host: {project_config.dev.host}\n"
432
+ f" path: {project_config.dev.work_path}\n"
433
+ f" branch: {current_branch}\n"
434
+ f" commit: {current_commit}\n\n"
435
+ "Run:\n\n"
436
+ f" git-ssh-sync pull {project}\n\n"
437
+ "Then resolve the branch on the development environment before pushing again."
438
+ )
439
+ raise CommandExecutionError(
440
+ environment=result.environment,
441
+ command=result.command,
442
+ returncode=result.returncode,
443
+ cwd=result.cwd,
444
+ stdout=result.stdout,
445
+ stderr=result.stderr,
446
+ )
447
+
448
+
372
449
  def _load_project(project: str) -> ProjectConfig:
373
450
  return get_project(load_config(), project)
374
451
 
@@ -456,12 +533,12 @@ def checkout_project(
456
533
  operations = ["fetch origin in the gateway repository"]
457
534
  if create:
458
535
  base = base_branch or _require_remote_current_branch(project_config)
459
- _ensure_origin_branch(local_path, base)
460
- _ensure_origin_branch_missing(project, local_path, branch)
536
+ _ensure_origin_branch_on_remote(local_path, base)
537
+ _ensure_origin_branch_missing_on_remote(project, local_path, branch)
461
538
  preflight.extend(
462
539
  [
463
- f"origin/{base} exists in the gateway repository",
464
- f"origin/{branch} does not exist in the gateway repository",
540
+ f"origin/{base} exists on origin",
541
+ f"origin/{branch} does not exist on origin",
465
542
  ]
466
543
  )
467
544
  operations.extend(
@@ -470,12 +547,13 @@ def checkout_project(
470
547
  f"fetch origin/{branch} into the gateway repository",
471
548
  ]
472
549
  )
473
- _ensure_origin_branch(local_path, branch)
550
+ else:
551
+ _ensure_origin_branch_on_remote(local_path, branch)
474
552
  _ensure_dev_clean(project, project_config)
475
553
  branch_exists = _remote_branch_exists(project_config, branch)
476
554
  preflight.extend(
477
555
  [
478
- f"origin/{branch} exists in the gateway repository",
556
+ f"origin/{branch} exists on origin",
479
557
  "development work tree is clean",
480
558
  ]
481
559
  )
@@ -527,7 +605,9 @@ def push_project(project: str, *, dry_run: bool = False) -> None:
527
605
  if dry_run:
528
606
  git.run_git(["fetch", "--dry-run", "origin"], cwd=local_path)
529
607
  _ensure_origin_branch(local_path, selected_branch)
530
- _ensure_pushable(project, project_config, local_path, selected_branch)
608
+ _ensure_pushable_from_development(
609
+ project, project_config, local_path, selected_branch
610
+ )
531
611
  _print_dry_run_plan(
532
612
  project=project,
533
613
  branch=selected_branch,
File without changes