ravmem-cli 0.1.0__py3-none-any.whl
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.
- ravmem/cli/__init__.py +0 -0
- ravmem/cli/analyze.py +484 -0
- ravmem/cli/auth.py +675 -0
- ravmem/cli/browse.py +144 -0
- ravmem/cli/config_cmd.py +69 -0
- ravmem/cli/diff.py +122 -0
- ravmem/cli/git_local.py +131 -0
- ravmem/cli/impact.py +106 -0
- ravmem/cli/info.py +82 -0
- ravmem/cli/kg.py +787 -0
- ravmem/cli/main.py +96 -0
- ravmem/cli/remote.py +549 -0
- ravmem/cli/search.py +91 -0
- ravmem/cli/serve.py +41 -0
- ravmem/client/__init__.py +3 -0
- ravmem/client/api.py +279 -0
- ravmem/client/config.py +52 -0
- ravmem/config.py +103 -0
- ravmem/config_loader.py +247 -0
- ravmem_cli-0.1.0.dist-info/METADATA +62 -0
- ravmem_cli-0.1.0.dist-info/RECORD +25 -0
- ravmem_cli-0.1.0.dist-info/WHEEL +5 -0
- ravmem_cli-0.1.0.dist-info/entry_points.txt +2 -0
- ravmem_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- ravmem_cli-0.1.0.dist-info/top_level.txt +1 -0
ravmem/cli/__init__.py
ADDED
|
File without changes
|
ravmem/cli/analyze.py
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
"""ravmem analyze — index a repository via the remote server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from ravmem.config import DEFAULT_LANGS
|
|
12
|
+
|
|
13
|
+
# Extensions whose content is never useful for the code-graph pipeline.
|
|
14
|
+
# Oversized text files (> _CLI_MAX_FILE_BYTES) are also skipped.
|
|
15
|
+
_BINARY_SUFFIXES = {
|
|
16
|
+
# Images
|
|
17
|
+
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".webp", ".tiff",
|
|
18
|
+
# Documents
|
|
19
|
+
".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
|
|
20
|
+
# Archives
|
|
21
|
+
".zip", ".tar", ".gz", ".bz2", ".xz", ".rar", ".7z",
|
|
22
|
+
# Compiled / binary
|
|
23
|
+
".exe", ".dll", ".so", ".dylib", ".bin", ".o", ".a", ".lib",
|
|
24
|
+
".pyc", ".pyo", ".pyd", ".class", ".jar", ".wasm",
|
|
25
|
+
# Media
|
|
26
|
+
".mp3", ".mp4", ".avi", ".mov", ".wav", ".flac", ".ogg",
|
|
27
|
+
# Fonts
|
|
28
|
+
".ttf", ".otf", ".woff", ".woff2",
|
|
29
|
+
# Data / model files
|
|
30
|
+
".pkl", ".joblib", ".npy", ".npz", ".h5", ".hdf5", ".onnx", ".pt", ".pth",
|
|
31
|
+
# Database
|
|
32
|
+
".db", ".sqlite", ".sqlite3",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Match server default RAVMEM_MAX_FILE_SIZE_MB=5 to avoid uploading files the
|
|
36
|
+
# server would skip anyway.
|
|
37
|
+
_CLI_MAX_FILE_BYTES = 5 * 1024 * 1024
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_client(remote: str | None):
|
|
41
|
+
from ravmem.client.api import RavMemClient
|
|
42
|
+
from ravmem.client.config import load_config
|
|
43
|
+
|
|
44
|
+
url = remote
|
|
45
|
+
api_key = None
|
|
46
|
+
if not url:
|
|
47
|
+
cfg = load_config()
|
|
48
|
+
url = cfg.get("server_url")
|
|
49
|
+
api_key = cfg.get("api_key")
|
|
50
|
+
if not url:
|
|
51
|
+
typer.echo(
|
|
52
|
+
"Error: No server configured. "
|
|
53
|
+
"Run `ravmem remote config --url <URL>` or pass --remote <URL>.",
|
|
54
|
+
err=True,
|
|
55
|
+
)
|
|
56
|
+
raise typer.Exit(1)
|
|
57
|
+
return RavMemClient(base_url=url, api_key=api_key or None)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _print_job_result(job: dict) -> None:
|
|
61
|
+
status = job.get("status", "unknown")
|
|
62
|
+
if status == "done":
|
|
63
|
+
typer.echo(f"✓ Indexing complete (job {job.get('job_id', '?')}).")
|
|
64
|
+
elif status == "failed":
|
|
65
|
+
typer.echo(
|
|
66
|
+
f"✗ Indexing failed (job {job.get('job_id', '?')}): "
|
|
67
|
+
f"{job.get('failure_reason', 'unknown')}",
|
|
68
|
+
err=True,
|
|
69
|
+
)
|
|
70
|
+
raise typer.Exit(1)
|
|
71
|
+
else:
|
|
72
|
+
typer.echo(f"Job {job.get('job_id', '?')} ended with status: {status}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _analyze_upload(
|
|
76
|
+
client,
|
|
77
|
+
project_id: str,
|
|
78
|
+
repo_path: Path,
|
|
79
|
+
branch_name: str,
|
|
80
|
+
parent_branch: str | None,
|
|
81
|
+
batch_size: int,
|
|
82
|
+
lang_list: list[str],
|
|
83
|
+
wait: bool,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Upload committed files to the server in batches and trigger indexing."""
|
|
86
|
+
from ravmem.cli.git_local import ls_files, rev_parse, LocalGitError
|
|
87
|
+
|
|
88
|
+
typer.echo(
|
|
89
|
+
" ℹ ravmem indexes committed files only. "
|
|
90
|
+
"Uncommitted changes will not appear.\n"
|
|
91
|
+
" Commit your work before running analyze if you want it included."
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Collect committed files
|
|
95
|
+
try:
|
|
96
|
+
all_rel_paths = ls_files(repo_path)
|
|
97
|
+
except LocalGitError as exc:
|
|
98
|
+
typer.echo(f"✗ git ls-files failed: {exc}", err=True)
|
|
99
|
+
raise typer.Exit(1)
|
|
100
|
+
|
|
101
|
+
# Get HEAD commit SHA to record as last_indexed_commit
|
|
102
|
+
try:
|
|
103
|
+
to_commit = rev_parse(repo_path)
|
|
104
|
+
except LocalGitError:
|
|
105
|
+
to_commit = ""
|
|
106
|
+
|
|
107
|
+
# Filter binaries and oversized files
|
|
108
|
+
to_upload: list[str] = []
|
|
109
|
+
skipped = 0
|
|
110
|
+
for rel in all_rel_paths:
|
|
111
|
+
p = repo_path / rel
|
|
112
|
+
if p.suffix.lower() in _BINARY_SUFFIXES:
|
|
113
|
+
skipped += 1
|
|
114
|
+
continue
|
|
115
|
+
try:
|
|
116
|
+
if p.stat().st_size > _CLI_MAX_FILE_BYTES:
|
|
117
|
+
skipped += 1
|
|
118
|
+
continue
|
|
119
|
+
except OSError:
|
|
120
|
+
skipped += 1
|
|
121
|
+
continue
|
|
122
|
+
to_upload.append(rel)
|
|
123
|
+
|
|
124
|
+
if not to_upload:
|
|
125
|
+
typer.echo("No source files found to upload.")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
total_files = len(to_upload)
|
|
129
|
+
batches = [to_upload[i : i + batch_size] for i in range(0, total_files, batch_size)]
|
|
130
|
+
batch_total = len(batches)
|
|
131
|
+
|
|
132
|
+
typer.echo(
|
|
133
|
+
f" Uploading {total_files} files"
|
|
134
|
+
+ (f" ({skipped} skipped)" if skipped else "")
|
|
135
|
+
+ f" in {batch_total} batch(es)…"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
job_id: int | None = None
|
|
139
|
+
for batch_index, batch_paths in enumerate(batches, 1):
|
|
140
|
+
files: list[dict] = []
|
|
141
|
+
for rel in batch_paths:
|
|
142
|
+
p = repo_path / rel
|
|
143
|
+
try:
|
|
144
|
+
content = base64.b64encode(p.read_bytes()).decode()
|
|
145
|
+
files.append({"path": rel, "content": content})
|
|
146
|
+
except OSError:
|
|
147
|
+
pass # unreadable — skip silently
|
|
148
|
+
|
|
149
|
+
result = client.upload_batch(
|
|
150
|
+
project_id=project_id,
|
|
151
|
+
branch=branch_name,
|
|
152
|
+
batch_index=batch_index,
|
|
153
|
+
batch_total=batch_total,
|
|
154
|
+
files=files,
|
|
155
|
+
parent_branch=parent_branch,
|
|
156
|
+
to_commit=to_commit,
|
|
157
|
+
langs=lang_list if lang_list else None,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if batch_total > 1:
|
|
161
|
+
typer.echo(f" Batch {batch_index}/{batch_total} sent ({len(files)} files).")
|
|
162
|
+
|
|
163
|
+
if "job_id" in result:
|
|
164
|
+
job_id = result["job_id"]
|
|
165
|
+
|
|
166
|
+
if job_id is None:
|
|
167
|
+
typer.echo("✗ Server did not return a job_id.", err=True)
|
|
168
|
+
raise typer.Exit(1)
|
|
169
|
+
|
|
170
|
+
typer.echo(f"Indexing started on branch '{branch_name}': job_id={job_id}")
|
|
171
|
+
|
|
172
|
+
if wait:
|
|
173
|
+
typer.echo("Waiting for completion…")
|
|
174
|
+
job = client.poll_job(job_id)
|
|
175
|
+
_print_job_result(job)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _analyze_incremental_upload(
|
|
179
|
+
client,
|
|
180
|
+
project_id: str,
|
|
181
|
+
repo_path: Path,
|
|
182
|
+
branch_name: str,
|
|
183
|
+
parent_branch: str | None,
|
|
184
|
+
batch_size: int,
|
|
185
|
+
lang_list: list[str],
|
|
186
|
+
wait: bool,
|
|
187
|
+
) -> None:
|
|
188
|
+
"""Upload only the files changed since the last index, plus a deletion list."""
|
|
189
|
+
from ravmem.cli.git_local import diff_name_status, rev_parse, LocalGitError
|
|
190
|
+
|
|
191
|
+
# 1. Find out where the server's index is anchored
|
|
192
|
+
try:
|
|
193
|
+
status = client.get_branch_status(project_id, branch_name)
|
|
194
|
+
last_commit = status.get("last_indexed_commit") or ""
|
|
195
|
+
except Exception:
|
|
196
|
+
last_commit = ""
|
|
197
|
+
|
|
198
|
+
if not last_commit:
|
|
199
|
+
typer.echo(
|
|
200
|
+
" ℹ No previous index found for this branch — running full upload instead."
|
|
201
|
+
)
|
|
202
|
+
_analyze_upload(
|
|
203
|
+
client=client,
|
|
204
|
+
project_id=project_id,
|
|
205
|
+
repo_path=repo_path,
|
|
206
|
+
branch_name=branch_name,
|
|
207
|
+
parent_branch=parent_branch,
|
|
208
|
+
batch_size=batch_size,
|
|
209
|
+
lang_list=lang_list,
|
|
210
|
+
wait=wait,
|
|
211
|
+
)
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
# 2. Diff from the last indexed commit to HEAD
|
|
215
|
+
try:
|
|
216
|
+
modified, deleted = diff_name_status(repo_path, last_commit)
|
|
217
|
+
to_commit = rev_parse(repo_path)
|
|
218
|
+
except LocalGitError as exc:
|
|
219
|
+
typer.echo(f"✗ git diff failed: {exc}", err=True)
|
|
220
|
+
raise typer.Exit(1)
|
|
221
|
+
|
|
222
|
+
if not modified and not deleted:
|
|
223
|
+
typer.echo(
|
|
224
|
+
f" ℹ No changes since last index ({last_commit[:7]}). Nothing to do."
|
|
225
|
+
)
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
# 3. Filter modified files (same binary/size rules as full upload)
|
|
229
|
+
to_upload: list[str] = []
|
|
230
|
+
skipped = 0
|
|
231
|
+
for rel in modified:
|
|
232
|
+
p = repo_path / rel
|
|
233
|
+
if p.suffix.lower() in _BINARY_SUFFIXES:
|
|
234
|
+
skipped += 1
|
|
235
|
+
continue
|
|
236
|
+
try:
|
|
237
|
+
if p.stat().st_size > _CLI_MAX_FILE_BYTES:
|
|
238
|
+
skipped += 1
|
|
239
|
+
continue
|
|
240
|
+
except OSError:
|
|
241
|
+
skipped += 1
|
|
242
|
+
continue
|
|
243
|
+
to_upload.append(rel)
|
|
244
|
+
|
|
245
|
+
typer.echo(
|
|
246
|
+
f" Incremental: {len(to_upload)} modified"
|
|
247
|
+
+ (f" ({skipped} skipped)" if skipped else "")
|
|
248
|
+
+ f", {len(deleted)} deleted, base {last_commit[:7]}…"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# 4. Build batches — at least one batch even if no modified files (deletions only)
|
|
252
|
+
batches: list[list[str]] = (
|
|
253
|
+
[to_upload[i : i + batch_size] for i in range(0, len(to_upload), batch_size)]
|
|
254
|
+
if to_upload
|
|
255
|
+
else [[]]
|
|
256
|
+
)
|
|
257
|
+
batch_total = len(batches)
|
|
258
|
+
|
|
259
|
+
job_id: int | None = None
|
|
260
|
+
for batch_index, batch_paths in enumerate(batches, 1):
|
|
261
|
+
files: list[dict] = []
|
|
262
|
+
for rel in batch_paths:
|
|
263
|
+
p = repo_path / rel
|
|
264
|
+
try:
|
|
265
|
+
content = base64.b64encode(p.read_bytes()).decode()
|
|
266
|
+
files.append({"path": rel, "content": content})
|
|
267
|
+
except OSError:
|
|
268
|
+
pass # unreadable — skip silently
|
|
269
|
+
|
|
270
|
+
result = client.upload_batch(
|
|
271
|
+
project_id=project_id,
|
|
272
|
+
branch=branch_name,
|
|
273
|
+
batch_index=batch_index,
|
|
274
|
+
batch_total=batch_total,
|
|
275
|
+
files=files,
|
|
276
|
+
parent_branch=parent_branch,
|
|
277
|
+
to_commit=to_commit,
|
|
278
|
+
from_commit=last_commit,
|
|
279
|
+
mode="incremental",
|
|
280
|
+
deleted_paths=deleted if batch_index == 1 else [],
|
|
281
|
+
langs=lang_list if lang_list else None,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
if batch_total > 1:
|
|
285
|
+
typer.echo(f" Batch {batch_index}/{batch_total} sent ({len(files)} files).")
|
|
286
|
+
|
|
287
|
+
if "job_id" in result:
|
|
288
|
+
job_id = result["job_id"]
|
|
289
|
+
|
|
290
|
+
if job_id is None:
|
|
291
|
+
typer.echo("✗ Server did not return a job_id.", err=True)
|
|
292
|
+
raise typer.Exit(1)
|
|
293
|
+
|
|
294
|
+
typer.echo(f"Incremental indexing started on branch '{branch_name}': job_id={job_id}")
|
|
295
|
+
|
|
296
|
+
if wait:
|
|
297
|
+
typer.echo("Waiting for completion…")
|
|
298
|
+
job = client.poll_job(job_id)
|
|
299
|
+
_print_job_result(job)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _analyze_server_side(
|
|
303
|
+
client,
|
|
304
|
+
project_id: str,
|
|
305
|
+
branch_name: str,
|
|
306
|
+
mode: str,
|
|
307
|
+
lang_list: list[str],
|
|
308
|
+
wait: bool,
|
|
309
|
+
yes: bool,
|
|
310
|
+
no_auto_sync: bool = False,
|
|
311
|
+
) -> None:
|
|
312
|
+
"""Trigger server-side indexing (mount / clone projects)."""
|
|
313
|
+
# Pre-flight divergence check
|
|
314
|
+
try:
|
|
315
|
+
div = client.get_divergence(project_id, branch_name)
|
|
316
|
+
ratio = div.get("ratio", 0.0)
|
|
317
|
+
recommend = div.get("recommend", "full")
|
|
318
|
+
parent_branch_name = div.get("parent_branch") or ""
|
|
319
|
+
parent_behind = div.get("parent_behind", False)
|
|
320
|
+
|
|
321
|
+
# Cross-branch guard: warn when the parent index is stale (Phase 12).
|
|
322
|
+
# An incremental index built on a stale baseline may silently miss changes
|
|
323
|
+
# that landed on the parent after its last index run.
|
|
324
|
+
if parent_behind and parent_branch_name and not yes:
|
|
325
|
+
typer.echo(
|
|
326
|
+
f"\nWarning: parent branch '{parent_branch_name}' index is not current.\n"
|
|
327
|
+
f" Re-indexing '{parent_branch_name}' first will give more accurate incremental results."
|
|
328
|
+
)
|
|
329
|
+
if not typer.confirm("Proceed anyway?", default=False):
|
|
330
|
+
raise typer.Exit(0)
|
|
331
|
+
|
|
332
|
+
if ratio > 0.60 and recommend == "full" and mode == "auto" and not yes:
|
|
333
|
+
pct = int(ratio * 100)
|
|
334
|
+
typer.echo(
|
|
335
|
+
f"\nWarning: {div.get('changed_files', '?')}/{div.get('total_files', '?')} "
|
|
336
|
+
f"files changed since last index ({pct}%).\n"
|
|
337
|
+
"This will trigger a full re-index."
|
|
338
|
+
)
|
|
339
|
+
if not typer.confirm("Proceed?", default=False):
|
|
340
|
+
raise typer.Exit(0)
|
|
341
|
+
if mode == "auto":
|
|
342
|
+
mode = recommend
|
|
343
|
+
except typer.Exit:
|
|
344
|
+
raise
|
|
345
|
+
except Exception:
|
|
346
|
+
pass # divergence check is best-effort
|
|
347
|
+
|
|
348
|
+
result = client.index_branch(
|
|
349
|
+
project_id, branch_name, mode=mode, langs=lang_list or None,
|
|
350
|
+
no_auto_sync=no_auto_sync,
|
|
351
|
+
)
|
|
352
|
+
job_id = result.get("job_id")
|
|
353
|
+
typer.echo(f"Indexing started on branch '{branch_name}': job_id={job_id}")
|
|
354
|
+
|
|
355
|
+
if wait and job_id is not None:
|
|
356
|
+
typer.echo("Waiting for completion…")
|
|
357
|
+
job = client.poll_job(job_id)
|
|
358
|
+
_print_job_result(job)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def analyze_cmd(
|
|
362
|
+
repo: Path = typer.Argument(Path("."), help="Path to the repository to index"),
|
|
363
|
+
langs: str = typer.Option(
|
|
364
|
+
",".join(DEFAULT_LANGS),
|
|
365
|
+
"--langs",
|
|
366
|
+
help="Comma-separated list of languages to index",
|
|
367
|
+
),
|
|
368
|
+
incremental: bool = typer.Option(
|
|
369
|
+
False, "--incremental", "-i",
|
|
370
|
+
help="Run incremental update from last indexed commit (upload mode: patches; clone/mount: git-diff)",
|
|
371
|
+
),
|
|
372
|
+
remote: Optional[str] = typer.Option(None, "--remote", help="Server URL (overrides config)"),
|
|
373
|
+
project: Optional[str] = typer.Option(None, "--project", help="Project ID on the server"),
|
|
374
|
+
branch: Optional[str] = typer.Option(
|
|
375
|
+
None, "--branch", help="Branch name (auto-detected from git if omitted)"
|
|
376
|
+
),
|
|
377
|
+
parent_branch: Optional[str] = typer.Option(
|
|
378
|
+
None, "--parent-branch",
|
|
379
|
+
help="Parent branch for copy-from-base optimisation (defaults to project default branch)",
|
|
380
|
+
),
|
|
381
|
+
batch_size: int = typer.Option(
|
|
382
|
+
50, "--batch-size",
|
|
383
|
+
help="Files per upload batch (upload mode only; default 50)",
|
|
384
|
+
min=1, max=200,
|
|
385
|
+
),
|
|
386
|
+
default_branch: Optional[str] = typer.Option(
|
|
387
|
+
None, "--default-branch",
|
|
388
|
+
help="Set/update the project's default/parent branch",
|
|
389
|
+
),
|
|
390
|
+
wait: bool = typer.Option(False, "--wait", "-w", help="Wait for indexing to complete"),
|
|
391
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompts (CI/scripts)"),
|
|
392
|
+
no_auto_sync: bool = typer.Option(
|
|
393
|
+
False, "--no-auto-sync",
|
|
394
|
+
help="Skip automatic git fetch before indexing (clone-mode only). "
|
|
395
|
+
"Use when you know the workspace is already up to date.",
|
|
396
|
+
),
|
|
397
|
+
) -> None:
|
|
398
|
+
"""Index a repository — detects upload/clone/mount mode from the project registration."""
|
|
399
|
+
|
|
400
|
+
repo_path = repo.resolve()
|
|
401
|
+
|
|
402
|
+
client = _get_client(remote)
|
|
403
|
+
try:
|
|
404
|
+
if not project:
|
|
405
|
+
typer.echo(
|
|
406
|
+
"✗ --project is required. "
|
|
407
|
+
"Register first with `ravmem remote add`.",
|
|
408
|
+
err=True,
|
|
409
|
+
)
|
|
410
|
+
raise typer.Exit(1)
|
|
411
|
+
|
|
412
|
+
# Fetch project info to determine index_mode and default_branch
|
|
413
|
+
try:
|
|
414
|
+
project_info = client.get_project(project)
|
|
415
|
+
except Exception as exc:
|
|
416
|
+
typer.echo(f"✗ Could not fetch project '{project}': {exc}", err=True)
|
|
417
|
+
raise typer.Exit(1)
|
|
418
|
+
|
|
419
|
+
index_mode = project_info.get("index_mode", "mount")
|
|
420
|
+
project_default_branch = project_info.get("default_branch", "main")
|
|
421
|
+
|
|
422
|
+
# Update default branch if requested
|
|
423
|
+
if default_branch:
|
|
424
|
+
client.update_project(project, default_branch=default_branch)
|
|
425
|
+
typer.echo(f"Default branch updated to '{default_branch}'.")
|
|
426
|
+
project_default_branch = default_branch
|
|
427
|
+
|
|
428
|
+
# Detect branch
|
|
429
|
+
if branch:
|
|
430
|
+
branch_name = branch
|
|
431
|
+
elif index_mode == "upload":
|
|
432
|
+
from ravmem.cli.git_local import current_branch
|
|
433
|
+
branch_name = current_branch(repo_path) or project_default_branch
|
|
434
|
+
else:
|
|
435
|
+
from ravmem.cli.git_local import current_branch
|
|
436
|
+
branch_name = current_branch(repo_path) or project_default_branch
|
|
437
|
+
|
|
438
|
+
effective_parent = parent_branch or project_default_branch
|
|
439
|
+
lang_list = [ln.strip() for ln in langs.split(",") if ln.strip()]
|
|
440
|
+
|
|
441
|
+
typer.echo(
|
|
442
|
+
f"Analyzing '{project}' branch '{branch_name}' "
|
|
443
|
+
f"[mode: {index_mode}]…"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
if index_mode == "upload":
|
|
447
|
+
if incremental:
|
|
448
|
+
_analyze_incremental_upload(
|
|
449
|
+
client=client,
|
|
450
|
+
project_id=project,
|
|
451
|
+
repo_path=repo_path,
|
|
452
|
+
branch_name=branch_name,
|
|
453
|
+
parent_branch=effective_parent,
|
|
454
|
+
batch_size=batch_size,
|
|
455
|
+
lang_list=lang_list,
|
|
456
|
+
wait=wait,
|
|
457
|
+
)
|
|
458
|
+
else:
|
|
459
|
+
_analyze_upload(
|
|
460
|
+
client=client,
|
|
461
|
+
project_id=project,
|
|
462
|
+
repo_path=repo_path,
|
|
463
|
+
branch_name=branch_name,
|
|
464
|
+
parent_branch=effective_parent,
|
|
465
|
+
batch_size=batch_size,
|
|
466
|
+
lang_list=lang_list,
|
|
467
|
+
wait=wait,
|
|
468
|
+
)
|
|
469
|
+
else:
|
|
470
|
+
# mount or clone — server-side indexing
|
|
471
|
+
mode = "incremental" if incremental else "auto"
|
|
472
|
+
_analyze_server_side(
|
|
473
|
+
client=client,
|
|
474
|
+
project_id=project,
|
|
475
|
+
branch_name=branch_name,
|
|
476
|
+
mode=mode,
|
|
477
|
+
lang_list=lang_list,
|
|
478
|
+
wait=wait,
|
|
479
|
+
yes=yes,
|
|
480
|
+
no_auto_sync=no_auto_sync,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
finally:
|
|
484
|
+
client.close()
|