Metadata-Version: 2.4
Name: svncli
Version: 1.0.0
Summary: CLI tool for syncing files with Polarion SVN when direct SVN access is not available
License: MIT
Project-URL: Homepage, https://github.com/pupca/svncli
Project-URL: Issues, https://github.com/pupca/svncli/issues
Project-URL: Changelog, https://github.com/pupca/svncli/blob/master/CHANGELOG.md
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Version Control
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28
Requires-Dist: browser-cookie3>=0.16
Provides-Extra: interactive
Requires-Dist: playwright>=1.40; extra == "interactive"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-cov>=4; extra == "dev"
Requires-Dist: playwright>=1.40; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: pre-commit>=3; extra == "dev"
Dynamic: license-file

# Polarion SVN CLI (svncli)

[![CI](https://github.com/pupca/svncli/actions/workflows/ci.yml/badge.svg)](https://github.com/pupca/svncli/actions/workflows/ci.yml)
[![Python 3.10+](https://img.shields.io/badge/python-3.10%E2%80%933.14-blue.svg)](https://www.python.org/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

CLI tool for syncing files with Polarion's SVN repository when direct SVN access is not available — such as on PolarionX or other hosted environments.

Supports **macOS**, **Linux**, and **Windows**.

## Why?

Some Polarion deployments don't expose direct SVN access. You can browse and manage individual files through the web UI, but there's no built-in way to recursively upload folders, sync changes, or automate file management. **svncli** fills that gap.

## Installation

```bash
pip install svncli
```

For interactive browser login (optional):

```bash
pip install "svncli[interactive]"
playwright install chromium
```

### From source

```bash
git clone https://github.com/pupca/svncli.git
cd svncli
pip install -e ".[dev]"
```

## Quick start

### 1. Authenticate

Pick one of these methods:

```bash
# Auto-extract cookies from Chrome (requires being logged in via browser)
svncli login https://your-server.example.com

# Interactive login — opens a browser window (works with SSO/MFA)
svncli login -i https://your-server.example.com

# Manual cookie — paste from browser DevTools
svncli login --cookie "JSESSIONID=ABC123; X-CSRF-Token=DEF456" https://your-server.example.com
```

All methods save the session to `~/.svncli/cookies.json` — you only need to log in once per server. After that, all commands just work.

### 2. Browse

```bash
svncli ls https://your-server.example.com:MyProject/trunk
svncli ls -r https://your-server.example.com:MyProject/trunk   # recursive
```

### 3. Copy files

```bash
# Upload
svncli cp ./report.pdf https://your-server.example.com:MyProject/trunk/docs/report.pdf

# Download
svncli cp https://your-server.example.com:MyProject/trunk/docs/report.pdf ./report.pdf

# Copy a whole folder
svncli cp -r ./local-folder https://your-server.example.com:MyProject/trunk/folder
```

### 4. Sync

```bash
# Push local changes to remote
svncli sync ./src https://your-server.example.com:MyProject/trunk/src

# Pull remote changes to local
svncli sync https://your-server.example.com:MyProject/trunk/src ./src

# Preview what would change
svncli sync -n ./src https://your-server.example.com:MyProject/trunk/src

# Delete remote files not present locally
svncli sync --delete --force ./src https://your-server.example.com:MyProject/trunk/src
```

### 5. Cross-server operations

Copy or sync files directly between two Polarion servers — no manual download/upload needed:

```bash
# Copy a folder from one server to another
svncli cp -r https://server1.com:Project/src https://server2.com:Project/src

# Sync between servers (only transfers changed files)
svncli sync https://server1.com:Project/src https://server2.com:Project/src

# Preview cross-server sync
svncli sync -n https://server1.com:Project/src https://server2.com:Project/src
```

This works by downloading from the source server to a temp directory, then uploading to the destination. Each server authenticates independently — run `svncli login` for each one.

## Python API

svncli can be used as a Python library:

```python
from svncli import PolarionSVNClient

client = PolarionSVNClient(verify_ssl=False)

# Authenticate (saved to ~/.svncli/cookies.json)
client.login("https://server.com")                            # auto-extract from Chrome
client.login("https://server.com", browser="edge")            # specific browser
client.login("https://server.com", cookie="JSESSIONID=...")   # manual cookie
client.login("https://server.com", interactive=True)           # opens browser window

# List files
items = client.ls("https://server.com:Project/trunk")
for item in items:
    print(f"{item.name}  {item.size} bytes  r{item.revision}")

# Copy a single file
client.cp("./report.pdf", "https://server.com:Project/docs/report.pdf")   # upload
client.cp("https://server.com:Project/docs/report.pdf", "./report.pdf")   # download

# Copy a directory recursively
actions = client.cp_r("./src", "https://server.com:Project/trunk/src")

# Sync with options
actions = client.sync("./src", "https://server.com:Project/trunk/src",
    delete=True,                     # remove remote files not in local
    exclude=["*.log", ".git/*"],     # exclude patterns
    dry_run=True,                    # preview only, don't execute
)

# Inspect what happened
from svncli import SyncOp
uploaded = [a for a in actions if a.op == SyncOp.UPLOAD]
skipped = [a for a in actions if a.op == SyncOp.SKIP]

# Cross-server sync
client.login("https://other-server.com")
client.sync("https://server.com:Project/src", "https://other-server.com:Project/src")

# Create / delete
client.mkdir("https://server.com:Project/new-folder")   # returns True
client.rm("https://server.com:Project/old-file.txt")     # returns True
```

## Path format

Remote paths include the server URL followed by `:` and the repository path:

```
https://server.example.com:RepoName/folder/path
```

Local paths start with `/`, `./`, or `~/`:

```
./local-folder
/absolute/path
~/home-relative
```

## Commands

| Command | Description |
|---------|-------------|
| `svncli ls <remote>` | List remote directory (`-r` for recursive) |
| `svncli cp <src> <dst>` | Copy files (local↔remote or remote↔remote with `-r`) |
| `svncli sync <src> <dst>` | Sync files between local and remote |
| `svncli rm <remote>` | Delete a remote file or folder |
| `svncli mb <remote>` | Create a remote directory |
| `svncli login <server>` | Authenticate and save session cookies |
| `svncli logout [server]` | Remove saved session cookies |

## Global options

```
--no-verify-ssl    Disable SSL certificate verification
--timeout SECONDS  HTTP request timeout (default: 60)
-v, --verbose      Verbose output
--version          Show version
```

## Sync options

```
-n, --dry-run      Show what would change without doing it
--delete           Remove destination files not present in source
--force            Skip confirmation prompt for deletions
--exclude PATTERN  Exclude files matching glob pattern (repeatable)
```

## Authentication

Each server has its own saved session. svncli resolves cookies per server in this order:

1. `--cookie` flag
2. Saved cookies from `~/.svncli/cookies.json` (from a previous `svncli login`)
3. Auto-extraction from browser cookie store (Chrome, Firefox, Edge, Safari, etc.)

### Automatic cookie extraction

By default, svncli reads cookies from your Chrome cookie store. This requires that you've already logged into Polarion in Chrome. To use a different browser:

```bash
svncli login --browser firefox https://your-server.com
svncli login --browser edge https://your-server.com    # common on Windows
```

Supported browsers: `chrome`, `firefox`, `edge`, `brave`, `opera`, `chromium`, `vivaldi`, `safari`, `librewolf`, `arc`.

### Interactive login

For environments where automatic cookie extraction doesn't work (e.g., remote servers, locked-down browsers):

```bash
svncli login -i https://your-server.com
```

This opens a Chromium window. Log in normally (including SSO/MFA), then close the window. Cookies are saved automatically.

Requires the `interactive` extra: `pip install "svncli[interactive]"` and `playwright install chromium`.

### Manual cookie extraction

If neither automatic nor interactive login works, you can extract cookies manually from your browser:

1. Open your Polarion server in Chrome/Firefox
2. Log in normally
3. Open Developer Tools (F12 or Cmd+Option+I)
4. Go to the **Network** tab
5. Navigate to any page on the Polarion server (e.g. refresh the page)
6. Click on any request to the server
7. In the **Headers** section, find the `Cookie` request header
8. Copy the entire cookie value string (e.g. `JSESSIONID=ABC123; X-CSRF-Token=DEF456; ...`)

Then save it with `svncli login`:

```bash
svncli login --cookie "JSESSIONID=ABC123; X-CSRF-Token=DEF456" https://your-server.com
```

After this, all commands to that server will use the saved session automatically.

> **Tip:** In Chrome, you can also right-click a request in the Network tab → **Copy → Copy as cURL**, then extract the cookie value from the `-b` or `--cookie` flag in the copied command.

## How sync works

svncli tracks sync state in manifest files stored in `~/.svncli/manifests/`. On each sync:

1. **First sync** — compares by file size; uploads/downloads differences
2. **Subsequent syncs** — uses SVN revision numbers + local file SHA-256 hashes:
   - Local file unchanged + remote revision unchanged → **skip** (fast, no hashing needed)
   - Local file changed → **upload**
   - Remote revision changed, local unchanged → **skip** (remote is newer)
   - New local file → **upload**
   - New remote file → **download** (in download direction)

The manifest enables efficient incremental syncs without downloading remote files to compare.

## Corporate environments

For servers with self-signed or corporate CA certificates:

```bash
svncli --no-verify-ssl ls MyProject
```


## Development

```bash
git clone https://github.com/pupca/svncli.git
cd svncli
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

# Run unit tests (no server needed)
pytest tests/test_sync.py tests/test_client.py tests/test_models.py tests/test_util.py -v

# Run full suite including E2E (requires access to two Polarion servers)
export SVNCLI_SERVER_A="https://your-server.example.com"
export SVNCLI_ROOT_A="ProjectRoot"
export SVNCLI_SERVER_B="https://your-other-server.example.com"
export SVNCLI_ROOT_B="OtherProjectRoot"
pytest tests/ -v --cov=svncli --cov-report=html

# Run E2E with one server only (cross-server tests will be skipped)
export SVNCLI_SERVER_A="https://your-server.example.com"
export SVNCLI_ROOT_A="ProjectRoot"
pytest tests/ -v
```

## License

[MIT](LICENSE)
