Replace API
The Replace API enables bulk search and replace operations across multiple repositories with automatic merge request creation.
Two-Phase Flow
Section titled “Two-Phase Flow”The replace API uses a two-phase approach for safety and efficiency:
- Preview: Search for matches and return what will be changed
- Execute: Use matches from preview to perform replacements and create MRs
This design ensures:
- Users always see exactly what will change before committing
- Execute phase is fast (no re-searching needed)
- Exact consistency between preview and execution
- All changes go through merge request review
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│ POST /preview │────▶│ Review matches │────▶│ POST /execute ││ │ │ (with repo IDs) │ │ (creates MR) │└─────────────────┘ └─────────────────┘ └─────────────────┘Preview Replacements
Section titled “Preview Replacements”Search for matches without making any changes.
POST /api/v1/replace/previewRequest Body
Section titled “Request Body”{ "search_pattern": "oldFunction", "replace_with": "newFunction", "is_regex": false, "case_sensitive": true, "file_patterns": ["*.go", "*.ts"], "repos": ["myorg/api"]}| Field | Type | Required | Description |
|---|---|---|---|
search_pattern | string | Yes | Text or regex pattern to search for |
replace_with | string | Yes | Replacement text |
is_regex | boolean | No | Treat search_pattern as regex (default: false) |
case_sensitive | boolean | No | Case-sensitive matching (default: false) |
file_patterns | string[] | No | Filter files by glob patterns |
repos | string[] | No | Filter to specific repositories |
Response
Section titled “Response”{ "results": [ { "repository_id": 1, "repo": "myorg/api", "file": "internal/service.go", "line": 42, "content": "func oldFunction() {" }, { "repository_id": 1, "repo": "myorg/api", "file": "internal/handler.go", "line": 15, "content": "result := oldFunction()" }, { "repository_id": 2, "repo": "myorg/web", "file": "src/api.ts", "line": 8, "content": "const data = oldFunction();" } ], "total": 3, "repos_count": 2, "files_count": 3}Execute Replacements
Section titled “Execute Replacements”Execute replacements using matches from preview. This creates a background job that will make the changes and create a merge request.
POST /api/v1/replace/executeRequest Body
Section titled “Request Body”{ "search_pattern": "oldFunction", "replace_with": "newFunction", "is_regex": false, "case_sensitive": true, "file_patterns": ["*.go", "*.ts"], "matches": [ { "repository_id": 1, "repository_name": "myorg/api", "file_path": "internal/service.go" }, { "repository_id": 1, "repository_name": "myorg/api", "file_path": "internal/handler.go" }, { "repository_id": 2, "repository_name": "myorg/web", "file_path": "src/api.ts" } ], "branch_name": "refactor/rename-function", "mr_title": "Rename oldFunction to newFunction", "mr_description": "Automated refactoring via code-search"}| Field | Type | Required | Description |
|---|---|---|---|
search_pattern | string | Yes | Same pattern used in preview |
replace_with | string | Yes | Replacement text |
is_regex | boolean | No | Treat search_pattern as regex |
case_sensitive | boolean | No | Case-sensitive matching |
file_patterns | string[] | No | File patterns (from preview) |
matches | object[] | Yes | Matches from preview (see below) |
branch_name | string | No | Branch name for changes (auto-generated if not specified) |
mr_title | string | No | MR/PR title (auto-generated if not specified) |
mr_description | string | No | MR/PR description (auto-generated if not specified) |
user_token | string | No | Personal access token (required in read-only mode) |
Match Object
Section titled “Match Object”| Field | Type | Required | Description |
|---|---|---|---|
repository_id | number | Yes | Repository ID from preview |
repository_name | string | Yes | Repository name from preview |
file_path | string | Yes | File path from preview |
Response
Section titled “Response”{ "job_id": "job_abc123", "status": "pending", "message": "Replace job queued"}List Replace Jobs
Section titled “List Replace Jobs”Get all replace jobs.
GET /api/v1/replace/jobsResponse
Section titled “Response”[ { "id": "job_abc123", "type": "replace", "status": "running", "payload": { "search_pattern": "oldFunction", "replace_with": "newFunction" }, "progress": { "current": 2, "total": 3, "message": "Processing repository 2/3: myorg/web" }, "created_at": "2024-01-15T10:30:00Z" }]Get Replace Job
Section titled “Get Replace Job”Get a specific replace job.
GET /api/v1/replace/jobs/{id}Response
Section titled “Response”{ "id": "job_abc123", "type": "replace", "status": "completed", "payload": { "search_pattern": "oldFunction", "replace_with": "newFunction", "matches": [ {"repository_id": 1, "repository_name": "myorg/api", "file_path": "internal/service.go"} ] }, "result": { "repositories_processed": 2, "files_modified": 3, "merge_requests_created": 2 }, "created_at": "2024-01-15T10:30:00Z", "completed_at": "2024-01-15T10:32:00Z"}Examples
Section titled “Examples”# Step 1: Preview changescurl -X POST "http://localhost:8080/api/v1/replace/preview" \ -H "Content-Type: application/json" \ -d '{ "search_pattern": "oldFunction", "replace_with": "newFunction", "file_patterns": ["*.go"] }'
# Step 2: Execute with matches from previewcurl -X POST "http://localhost:8080/api/v1/replace/execute" \ -H "Content-Type: application/json" \ -d '{ "search_pattern": "oldFunction", "replace_with": "newFunction", "file_patterns": ["*.go"], "matches": [ {"repository_id": 1, "repository_name": "myorg/api", "file_path": "internal/service.go"}, {"repository_id": 1, "repository_name": "myorg/api", "file_path": "internal/handler.go"} ], "create_mr": true, "mr_title": "Rename oldFunction to newFunction" }'
# Check job statuscurl "http://localhost:8080/api/v1/replace/jobs/job_abc123"const API = 'http://localhost:8080/api/v1';
// Step 1: Preview changesconst preview = await fetch(`${API}/replace/preview`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ search_pattern: 'oldFunction', replace_with: 'newFunction', file_patterns: ['*.go'] })}).then(r => r.json());
console.log(`Found ${preview.total} matches in ${preview.repos_count} repos`);
// Step 2: Build matches array from preview resultsconst matches = preview.results.map(r => ({ repository_id: r.repository_id, repository_name: r.repo, file_path: r.file}));
// Step 3: Execute with matchesconst job = await fetch(`${API}/replace/execute`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ search_pattern: 'oldFunction', replace_with: 'newFunction', file_patterns: ['*.go'], matches: matches, create_mr: true, mr_title: 'Rename oldFunction to newFunction' })}).then(r => r.json());
console.log(`Job queued: ${job.job_id}`);
// Poll for completionlet status = 'pending';while (status === 'pending' || status === 'running') { const result = await fetch(`${API}/replace/jobs/${job.job_id}`).then(r => r.json()); status = result.status; console.log(`Status: ${status} - ${result.progress?.message || ''}`); await new Promise(r => setTimeout(r, 2000));}import requestsimport time
API = "http://localhost:8080/api/v1"
# Step 1: Preview changespreview = requests.post(f"{API}/replace/preview", json={ "search_pattern": "oldFunction", "replace_with": "newFunction", "file_patterns": ["*.go"]}).json()
print(f"Found {preview['total']} matches in {preview['repos_count']} repos")
# Step 2: Build matches array from preview resultsmatches = [ { "repository_id": r["repository_id"], "repository_name": r["repo"], "file_path": r["file"] } for r in preview["results"]]
# Step 3: Execute with matchesjob = requests.post(f"{API}/replace/execute", json={ "search_pattern": "oldFunction", "replace_with": "newFunction", "file_patterns": ["*.go"], "matches": matches, "create_mr": True, "mr_title": "Rename oldFunction to newFunction"}).json()
print(f"Job queued: {job['job_id']}")
# Poll for completionstatus = "pending"while status in ("pending", "running"): result = requests.get(f"{API}/replace/jobs/{job['job_id']}").json() status = result["status"] message = result.get("progress", {}).get("message", "") print(f"Status: {status} - {message}") time.sleep(2)Regex Examples
Section titled “Regex Examples”Version Bump
Section titled “Version Bump”# Previewcurl -X POST "http://localhost:8080/api/v1/replace/preview" \ -H "Content-Type: application/json" \ -d '{ "search_pattern": "v([0-9]+)\\.([0-9]+)\\.([0-9]+)", "replace_with": "v$1.$2.999", "is_regex": true, "file_patterns": ["*.go", "version.txt"] }'Function Rename Pattern
Section titled “Function Rename Pattern”# Previewcurl -X POST "http://localhost:8080/api/v1/replace/preview" \ -H "Content-Type: application/json" \ -d '{ "search_pattern": "get([A-Z]\\w+)ById", "replace_with": "find$1ById", "is_regex": true }'Error Responses
Section titled “Error Responses”| Status | Description |
|---|---|
| 400 | Invalid request (missing required fields) |
| 400 | matches is required for execute |
| 500 | Internal server error |
{ "error": "'matches' is required - call preview endpoint first to get matches"}Next Steps
Section titled “Next Steps”- Jobs API - Monitor replace job progress
- CLI Replace - Command-line interface for replace