Commit f4a57e8e authored by Thomas de Grenier de Latour's avatar Thomas de Grenier de Latour
Browse files

feat: add --skip-visibility option (preserve existing groups/projects visibility)

parent 105fcee3
Loading
Loading
Loading
Loading
+6 −4
Original line number Diff line number Diff line
@@ -17,9 +17,9 @@ gitlab-cp --help

```bash
usage: gitlab-cp [-h] [--src-api SRC_API] [--src-token SRC_TOKEN] [--src-sync-path SRC_SYNC_PATH] [--dest-api DEST_API] [--dest-token DEST_TOKEN] [--dest-sync-path DEST_SYNC_PATH] [--max-visibility {public,internal,private}]
                 [--exclude EXCLUDE] [--exclude-from EXCLUDE_FROM] [--include INCLUDE] [--include-from INCLUDE_FROM] [--insecure] [--update-release] [--update-avatar] [--no-group-description] [--no-project-description]
                 [--new-group-options NEW_GROUP_OPTIONS] [--new-group-options-from NEW_GROUP_OPTIONS_FROM] [--new-project-options NEW_PROJECT_OPTIONS] [--new-project-options-from NEW_PROJECT_OPTIONS_FROM] [--dry-run]
                 [--halt-on-error] [--cache-dir CACHE_DIR]
                 [--skip-visibility] [--exclude EXCLUDE] [--exclude-from EXCLUDE_FROM] [--include INCLUDE] [--include-from INCLUDE_FROM] [--insecure] [--update-release] [--update-avatar] [--no-group-description]
                 [--no-project-description] [--new-group-options NEW_GROUP_OPTIONS] [--new-group-options-from NEW_GROUP_OPTIONS_FROM] [--new-project-options NEW_PROJECT_OPTIONS]
                 [--new-project-options-from NEW_PROJECT_OPTIONS_FROM] [--dry-run] [--halt-on-error] [--cache-dir CACHE_DIR]

This tool recursively copies/synchronizes a GitLab group from one GitLab server to another.

@@ -36,7 +36,8 @@ options:
  --dest-sync-path DEST_SYNC_PATH
                        GitLab destination root group path to synchronize (defaults to --src-sync-path)
  --max-visibility {public,internal,private}
                        maximum visibility of projects in destination group
                        maximum visibility of groups and projects in destination group
  --skip-visibility     skip updating the destination group or project visibility (when it exists already)
  --exclude EXCLUDE     project/group path to exclude from processing (relative to --src-sync-path)
  --exclude-from EXCLUDE_FROM
                        a file which lists paths to exclude (one per line); incompatible with --exclude
@@ -73,6 +74,7 @@ options:
| `--dest-token`               | `$DEST_TOKEN`                   | GitLab destination token with at least scopes `api,read_repository,write_repository` and `Owner` role (**mandatory**) |
| `--dest-sync-path`           | `$DEST_SYNC_PATH`               | GitLab destination root group path to synchronize (defaults to `--src-sync-path`)                                     |
| `--max-visibility`           | `$MAX_VISIBILITY`               | maximum visibility of projects in destination group (defaults to `public`)                                            |
| `--skip-visibility`          | `$SKIP_VISIBILITY`              | skip updating the destination group or project visibility (when it exists already)                                    |
| `--exclude`                  | `$EXCLUDE`                      | project/group path(s) to exclude (multiple CLI option; env. variable is a coma separated list; wins over `--include`) |
| `--exclude-from`             | `$EXCLUDE_FROM`                 | a file which lists paths to exclude (one per line); incompatible with `--exclude` / `$EXCLUDE`                        |
| `--include`                  | `$INCLUDE`                      | project/group path(s) to include (multiple CLI option; env. var. is coma separated; all paths included by default)    |
+33 −11
Original line number Diff line number Diff line
@@ -72,6 +72,7 @@ class Synchronizer:
        dest_sync_path: str,
        work_dir: Path,
        max_visibility=GlVisibility.public,
        skip_visibility=False,
        exclude: Optional[list[str]] = None,
        include: Optional[list[str]] = None,
        update_release=False,
@@ -89,6 +90,7 @@ class Synchronizer:
        self.dest_sync_path = dest_sync_path
        self.work_dir = work_dir
        self.max_visibility = max_visibility
        self.skip_visibility = skip_visibility
        self.exclude = exclude or []
        self.include = include or []
        self.force_update_latest_release = update_release
@@ -406,6 +408,11 @@ class Synchronizer:
                dest_project.name = src_project.name
                changed_attr.append("name")
            if dest_project.visibility != dest_visibility:
                if self.skip_visibility:
                    print(
                        f"    - visibility: {AnsiColors.HYELLOW}skipping update{AnsiColors.RESET} ({dest_project.visibility} -> {dest_visibility}) ({dest_project.get_id()})"
                    )
                else:
                    dest_project.visibility = dest_visibility
                    changed_attr.append("visibility")
            if (
@@ -546,6 +553,11 @@ class Synchronizer:
                dest_group.name = src_group.name
                changed_attr.append("name")
            if dest_group.visibility != dest_visibility:
                if self.skip_visibility:
                    print(
                        f"- visibility: {AnsiColors.HYELLOW}skipping update{AnsiColors.RESET} ({dest_group.visibility} -> {dest_visibility}) ({dest_group.get_id()})"
                    )
                else:
                    dest_group.visibility = dest_visibility
                    changed_attr.append("visibility")
            if (
@@ -787,7 +799,13 @@ def run() -> None:
        default=os.getenv("MAX_VISIBILITY") or GlVisibility.public,
        type=GlVisibility,
        choices=list(GlVisibility),
        help="maximum visibility of projects in destination group",
        help="maximum visibility of groups and projects in destination group",
    )
    parser.add_argument(
        "--skip-visibility",
        action="store_true",
        default=os.getenv("SKIP_VISIBILITY") is not None,
        help="skip updating the destination group or project visibility (when it exists already)",
    )
    parser.add_argument(
        "--exclude",
@@ -961,6 +979,9 @@ def run() -> None:
    print(
        f"- max visi.   (--max-visibility) : {AnsiColors.CYAN}{args.max_visibility}{AnsiColors.RESET}"
    )
    print(
        f"- skip visi  (--skip-visibility) : {AnsiColors.CYAN}{args.skip_visibility}{AnsiColors.RESET}"
    )
    print(
        f"- exclude     (--exclude(-from)) : {AnsiColors.CYAN}{', '.join(exclude_list)}{AnsiColors.RESET}"
    )
@@ -1023,12 +1044,13 @@ def run() -> None:
        ),
        dest_sync_path,
        work_dir,
        args.max_visibility,
        exclude_list,
        include_list,
        args.update_release,
        not args.no_group_description,
        not args.no_project_description,
        max_visibility=args.max_visibility,
        skip_visibility=args.skip_visibility,
        exclude=exclude_list,
        include=include_list,
        update_release=args.update_release,
        group_description=(not args.no_group_description),
        project_description=(not args.no_project_description),
        dry_run=args.dry_run,
        continue_on_error=not args.halt_on_error,
        update_avatar=args.update_avatar,