Skip to content

feat: Git Sync Folder Implementation#1629

Merged
kmendell merged 9 commits intogetarcaneapp:mainfrom
sasha-id:feat/git-sync-folder
Mar 26, 2026
Merged

feat: Git Sync Folder Implementation#1629
kmendell merged 9 commits intogetarcaneapp:mainfrom
sasha-id:feat/git-sync-folder

Conversation

@sasha-id
Copy link
Copy Markdown
Contributor

@sasha-id sasha-id commented Feb 2, 2026

Fixes: #1619

I've implemented the directory sync feature for Git Sync. Here's what was built:
Directory sync capability that allows Git Sync to pull entire directories instead of just single compose files, supporting Docker Compose extends, include, and relative file references.

  • Recursive sync: Syncs compose file directory and all subdirectories
  • Safety limits: 500 files max, 50MB total, skip binaries >10MB
  • Default enabled: syncDirectory=true for all syncs (backward compatible)
  • File tracking: Synced files stored in DB for cleanup on updates

Disclaimer Greptiles Reviews use AI, make sure to check over its work.

To better help train Greptile on our codebase, if the comment is useful and valid Like the comment, if its not helpful or invalid Dislike

To have Greptile Re-Review the changes, mention greptileai.

Greptile Summary

This PR implements directory sync for Git Sync, allowing Arcane to pull an entire compose-file directory (including subdirectories) rather than just a single compose file. This enables support for Docker Compose extends, include, and relative file references that span multiple files. The feature is backward-compatible (syncDirectory=true is the new default), adds configurable safety limits (file count, total size, binary-file size) enforced at both the per-sync and environment levels, and tracks synced file paths in the database for accurate cleanup on subsequent syncs.

Key changes:

  • WalkDirectory in backend/pkg/gitutil/git.go: recursive directory traversal with binary detection via net/http.DetectContentType and null-byte fallback.
  • WriteSyncedDirectory / CleanupRemovedFiles in backend/pkg/projects/fs_writer.go: secure multi-file writes with IsSafeSubdirectory path-traversal guards and best-effort empty-directory cleanup.
  • GitOpsSyncService: two-tier limit system (per-sync values capped by environment-wide settings) via effectiveIntLimit.
  • enrichWithDirectoryFiles in project_service.go: displays non-compose, non-env files from the project directory in the UI using os.OpenRoot for sandboxed access.
  • Migrations (Postgres + SQLite): adds sync_directory, synced_files, and three max_* columns with correct defaults.
  • Frontend: new "Sync Files" toggle in the GitOps sync dialog, read-only directory-file viewer in the project page tree, and environment-level limit controls in GeneralTab.

Issues found:

  • appendSyncFile in git.go reads the full file content into memory before applying the binary-size skip — large binary files cause a transient memory spike before being discarded.
  • effectiveIntLimit/effectiveInt64Limit treat a sync-level 0 as "defer to environment cap", but all API documentation and model comments state "0 means unlimited" — this semantic mismatch can surprise API consumers who set maxSyncFiles=0 expecting to remove the per-sync cap.
  • The compose-file lookup in walkAndParseSyncDirectory relies on an implicit invariant (the walk root is always the direct parent of the compose file) that should be documented to prevent future regressions.

Confidence Score: 3/5

Core logic is well-structured and tested, but two concrete issues — unexpected memory pressure on binary-heavy repos and a doc/behaviour mismatch that silently caps 'unlimited' syncs — should be resolved before merging.

The implementation is feature-complete with solid path-traversal guards, proper migration files for both databases, and good test coverage. However, the memory issue in appendSyncFile is a real reliability concern for users with large binaries in their repos, and the '0 means unlimited' semantic contradiction across multiple API types and model comments is a correctness/trust issue that will be hard to fix later without a breaking change.

backend/pkg/gitutil/git.go (appendSyncFile memory), backend/internal/services/gitops_sync_service.go and types/gitops/gitops.go (effectiveIntLimit semantics + documentation)

Important Files Changed

Filename Overview
backend/pkg/gitutil/git.go Adds WalkDirectory + helper functions for recursive repo traversal; binary detection via net/http is solid, but full file read before binary-size skip wastes memory for large repos.
backend/internal/services/gitops_sync_service.go Core directory-sync orchestration; limit-enforcement logic is solid but "0 = unlimited" semantics in effectiveIntLimit contradict API documentation, and compose-file lookup relies on an undocumented invariant.
backend/pkg/projects/fs_writer.go Adds WriteSyncedDirectory and CleanupRemovedFiles with proper path-traversal guards (IsSafeSubdirectory) and best-effort cleanup logic; well structured.
backend/internal/services/project_service.go enrichWithDirectoryFiles uses os.OpenRoot for sandboxed access, skips symlinks, limits files to 1 MB, and detects binary via null-byte check — good security hygiene.
frontend/src/routes/(app)/projects/[projectId]/+page.svelte Adds directoryFiles tree rendering in both tablet and desktop layouts; dir: prefix scheme for selectedFile is straightforward, and all directory files are shown as read-only.
types/gitops/gitops.go New fields are well-documented but the "0 means unlimited" comment is misleading given that effectiveIntLimit defers to the environment cap when sync value is 0.
backend/internal/models/gitops_sync.go Model expanded with SyncDirectory, SyncedFiles, and three max-* limit columns; GORM tags and defaults align with migration SQL.
backend/internal/services/settings_service.go Three new Git Sync limit defaults added consistently with other setting variables; defaults match the constants in gitops_sync_service.go.
backend/resources/migrations/postgres/041_add_directory_sync.up.sql Adds sync_directory, synced_files, and three max_* columns with correct defaults; index on sync_directory is appropriate.
backend/resources/migrations/sqlite/041_add_directory_sync.down.sql SQLite down migration reconstructs the table to drop columns; approach is correct for SQLite's lack of DROP COLUMN support in older versions.
backend/internal/services/gitops_sync_service_test.go Good tests covering environment limit inheritance, tighter sync limits, and the zero-means-defer-to-env case.
frontend/src/routes/(app)/environments/[id]/components/GeneralTab.svelte New Git Sync File Limits section renders three numeric inputs with correct i18n keys and error bindings.

Sequence Diagram

sequenceDiagram
    participant S as GitOpsSyncService
    participant G as gitutil.Client
    participant FS as fs_writer
    participant DB as Database

    S->>S: getEffectiveSyncLimits(ctx, sync)
    note over S: min(sync limits, env limits)

    S->>G: WalkDirectory(repoPath, composePath, maxFiles, maxTotalSize, maxBinarySize)
    loop fs.WalkDir
        G->>G: walkSyncEntry(path, entry)
        G->>G: appendSyncFile(path)
        note over G: ReadFile → detect binary → check size limits
    end
    G-->>S: DirectoryWalkResult{Files, TotalFiles, TotalSize, SkippedBinaries}

    S->>S: find compose file in walkResult (by base name)
    S->>S: getOrCreateProjectInternal(composeContent, nil)

    S->>FS: CleanupRemovedFiles(projectsRoot, projectPath, oldFiles, newFiles)
    FS->>FS: delete removed files, cleanupEmptyDirs

    S->>FS: WriteSyncedDirectory(projectsRoot, projectPath, syncFiles)
    FS->>FS: validate paths (IsSafeSubdirectory)
    FS-->>S: writtenPaths

    S->>DB: updateSyncStatusWithFiles(id, success, commitHash, syncedFiles)
    DB-->>S: ok
Loading

Comments Outside Diff (2)

  1. frontend/src/routes/(app)/projects/[projectId]/+page.svelte, line 715-721 (link)

    P1 Validation and diff bindings stripped from tablet compose panel

    The tablet layout's compose CodePanel lost bind:hasErrors, bind:validationReady, fileId, originalValue, enableDiff, and editorContext. Without bind:hasErrors={composeHasErrors} and bind:validationReady={composeValidationReady}, the save-button guard that prevents submitting invalid YAML will stop working on tablet viewports — users could persist broken compose files without seeing any validation error. The diff view is also silently disabled.

    These same bindings are correctly kept in the non-tablet code path (around line 857). They should be restored in the tablet branch as well.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: frontend/src/routes/(app)/projects/[projectId]/+page.svelte
    Line: 715-721
    
    Comment:
    **Validation and diff bindings stripped from tablet compose panel**
    
    The tablet layout's compose `CodePanel` lost `bind:hasErrors`, `bind:validationReady`, `fileId`, `originalValue`, `enableDiff`, and `editorContext`. Without `bind:hasErrors={composeHasErrors}` and `bind:validationReady={composeValidationReady}`, the save-button guard that prevents submitting invalid YAML will stop working on tablet viewports — users could persist broken compose files without seeing any validation error. The diff view is also silently disabled.
    
    These same bindings are correctly kept in the non-tablet code path (around line 857). They should be restored in the tablet branch as well.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. frontend/src/routes/(app)/projects/[projectId]/+page.svelte, line 734-743 (link)

    P2 Hardcoded language="yaml" for all directory files

    The directory sync feature can pull any file type from the repository (shell scripts, JSON, Makefile, .env, Nginx configs, etc.). Using language="yaml" for every directory file will produce incorrect syntax highlighting for non-YAML files.

    Consider deriving the language from the file extension:

    {@const dirLang = dirFile.relativePath.endsWith('.json') ? 'json' :
       dirFile.relativePath.endsWith('.sh') ? 'shell' :
       dirFile.relativePath.endsWith('.env') ? 'env' : 'yaml'}
    <CodePanel
        open={true}
        title={dirFile.relativePath}
        language={dirLang}
        value={dirFile.content}
        readOnly={true}
    />

    This pattern is repeated in two places (tablet and desktop views).

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: frontend/src/routes/(app)/projects/[projectId]/+page.svelte
    Line: 734-743
    
    Comment:
    **Hardcoded `language="yaml"` for all directory files**
    
    The directory sync feature can pull any file type from the repository (shell scripts, JSON, Makefile, `.env`, Nginx configs, etc.). Using `language="yaml"` for every directory file will produce incorrect syntax highlighting for non-YAML files.
    
    Consider deriving the language from the file extension:
    
    ```svelte
    {@const dirLang = dirFile.relativePath.endsWith('.json') ? 'json' :
       dirFile.relativePath.endsWith('.sh') ? 'shell' :
       dirFile.relativePath.endsWith('.env') ? 'env' : 'yaml'}
    <CodePanel
        open={true}
        title={dirFile.relativePath}
        language={dirLang}
        value={dirFile.content}
        readOnly={true}
    />
    ```
    
    This pattern is repeated in two places (tablet and desktop views).
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: backend/pkg/gitutil/git.go
Line: 612-621

Comment:
**Large binary files fully read before size check**

`root.ReadFile(path)` loads the complete file into memory before the binary-size guard on line 620 runs. A 100 MB image that will ultimately be skipped because it exceeds `maxBinarySize` still gets entirely buffered, causing a transient memory spike per such file.

The fix is to stat the file first using the `fs.DirEntry` available in `walkSyncEntry` and skip early if the file is already known to be larger than `maxBinarySize`:

```go
// In walkSyncEntry, before calling appendSyncFile:
if limits.maxBinarySize > 0 {
    if info, err := d.Info(); err == nil && info.Size() > limits.maxBinarySize {
        result.SkippedBinaries++
        return nil
    }
}
```

Alternatively, read only the first 512 bytes for binary detection and then use `d.Info().Size()` for the size check, avoiding a full read of large files that will be discarded.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: backend/internal/services/gitops_sync_service.go
Line: 66-88

Comment:
**"0 means unlimited" documentation contradicts actual behaviour**

The `GitOpsSync` model, `CreateSyncRequest`, `UpdateSyncRequest`, and `types/gitops/gitops.go` all document `MaxSyncFiles`, `MaxSyncTotalSize`, and `MaxSyncBinarySize` with "0 means unlimited". But `effectiveIntLimit`/`effectiveInt64Limit` treat a sync-level 0 as "defer to the environment cap" — not as truly unlimited:

```go
case syncLimit == 0:
    return environmentLimit   // returns env cap, *not* 0
```

A caller who reads the API docs and sets `maxSyncFiles: 0` to remove the per-sync cap will silently still be bounded by the environment setting. True "unlimited" only happens when both the sync value **and** the environment setting are 0.

This mismatch exists in every file that documents these fields:
- `types/gitops/gitops.go` lines 128, 133, 138, 337, 343, 349, 400, 405, 410, 666
- `backend/internal/models/gitops_sync.go` (the `// 0 = unlimited` inline comments)

The documentation should clarify that 0 at the sync level means "inherit the environment cap", and that a sync cannot opt out of the environment limit. Alternatively, the semantics should be inverted so that a sync-level 0 truly bypasses the environment cap (which may be the more natural expectation for an API user).

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: backend/internal/services/gitops_sync_service.go
Line: 929-930

Comment:
**Compose file lookup relies on an undocumented invariant**

`composeFileName := filepath.Base(sync.ComposePath)` strips the directory prefix entirely. This works correctly today because `WalkDirectory` always roots the walk at the *direct parent* of the compose file — so the compose file appears at the root of the walked tree with just its base name as its relative path.

However, this invariant is nowhere documented. A future refactor that walks a wider directory (e.g., the repo root instead of the compose file's parent) would cause `f.RelativePath == composeFileName` to silently match a wrong file in a subdirectory, or miss the compose file entirely.

Add a comment explaining why the base name comparison is sufficient:

```go
// composeFileName is the base name of the compose file. WalkDirectory roots
// the walk at filepath.Dir(composePath), so the compose file always appears
// at the top level of the walk with RelativePath == filepath.Base(composePath).
composeFileName := filepath.Base(sync.ComposePath)
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (3): Last reviewed commit: "fixes" | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Context used:

  • Rule used - # Golang Pro

Senior Go developer with deep expert... (source)

  • Rule used - # Go Development Patterns

What: Code should p... (source)

@sasha-id sasha-id requested a review from a team February 2, 2026 00:51
@sasha-id sasha-id changed the title Git Sync Folder Implementation feat: Git Sync Folder Implementation Feb 2, 2026
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 17 comments

Edit Code Review Agent Settings | Greptile

Comment thread backend/internal/services/gitops_sync_service.go
Comment thread backend/internal/services/gitops_sync_service.go
Comment thread backend/pkg/projects/fs_writer.go
Comment thread backend/pkg/gitutil/git.go
Comment thread backend/pkg/gitutil/git.go
Comment thread backend/internal/services/gitops_sync_service.go
Comment thread backend/internal/services/gitops_sync_service.go
Comment thread backend/pkg/projects/fs_writer.go
Comment thread backend/internal/utils/git/git.go Outdated
Comment thread backend/pkg/gitutil/git.go
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Feb 2, 2026

Additional Comments (7)

backend/internal/utils/fs/fs_writer.go
unexported function missing "Internal" suffix

func detectExistingComposeFileInternal(dir string) string {

Context Used: Rule from dashboard - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/internal/utils/fs/fs_writer.go
Line: 19:19

Comment:
unexported function missing "Internal" suffix

```suggestion
func detectExistingComposeFileInternal(dir string) string {
```

**Context Used:** Rule from `dashboard` - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... ([source](https://app.greptile.com/review/custom-context?memory=306fc233-4d2f-4ac4-bdf7-8059588e8a43))

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

backend/internal/utils/git/git.go
unexported function missing "Internal" suffix

func getKnownHostsPathInternal() string {

Context Used: Rule from dashboard - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/internal/utils/git/git.go
Line: 165:165

Comment:
unexported function missing "Internal" suffix

```suggestion
func getKnownHostsPathInternal() string {
```

**Context Used:** Rule from `dashboard` - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... ([source](https://app.greptile.com/review/custom-context?memory=306fc233-4d2f-4ac4-bdf7-8059588e8a43))

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

backend/internal/utils/git/git.go
unexported function missing "Internal" suffix

func addHostKeyInternal(knownHostsPath, hostname string, key gossh.PublicKey) (err error) {

Context Used: Rule from dashboard - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/internal/utils/git/git.go
Line: 181:181

Comment:
unexported function missing "Internal" suffix

```suggestion
func addHostKeyInternal(knownHostsPath, hostname string, key gossh.PublicKey) (err error) {
```

**Context Used:** Rule from `dashboard` - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... ([source](https://app.greptile.com/review/custom-context?memory=306fc233-4d2f-4ac4-bdf7-8059588e8a43))

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

backend/internal/utils/fs/fs_writer.go
update function call to use new "Internal" suffix

	if existingFile := detectExistingComposeFileInternal(dirPath); existingFile != "" {

Context Used: Rule from dashboard - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/internal/utils/fs/fs_writer.go
Line: 57:57

Comment:
update function call to use new "Internal" suffix

```suggestion
	if existingFile := detectExistingComposeFileInternal(dirPath); existingFile != "" {
```

**Context Used:** Rule from `dashboard` - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... ([source](https://app.greptile.com/review/custom-context?memory=306fc233-4d2f-4ac4-bdf7-8059588e8a43))

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

backend/internal/utils/git/git.go
update function call to use new "Internal" suffix

		return knownhosts.New(getKnownHostsPathInternal())

Context Used: Rule from dashboard - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/internal/utils/git/git.go
Line: 97:97

Comment:
update function call to use new "Internal" suffix

```suggestion
		return knownhosts.New(getKnownHostsPathInternal())
```

**Context Used:** Rule from `dashboard` - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... ([source](https://app.greptile.com/review/custom-context?memory=306fc233-4d2f-4ac4-bdf7-8059588e8a43))

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

backend/internal/utils/git/git.go
update function call to use new "Internal" suffix

	knownHostsPath := getKnownHostsPathInternal()

Context Used: Rule from dashboard - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/internal/utils/git/git.go
Line: 112:112

Comment:
update function call to use new "Internal" suffix

```suggestion
	knownHostsPath := getKnownHostsPathInternal()
```

**Context Used:** Rule from `dashboard` - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... ([source](https://app.greptile.com/review/custom-context?memory=306fc233-4d2f-4ac4-bdf7-8059588e8a43))

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

backend/internal/utils/git/git.go
update function call to use new "Internal" suffix

		if err := addHostKeyInternal(knownHostsPath, hostname, key); err != nil {

Context Used: Rule from dashboard - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/internal/utils/git/git.go
Line: 154:154

Comment:
update function call to use new "Internal" suffix

```suggestion
		if err := addHostKeyInternal(knownHostsPath, hostname, key); err != nil {
```

**Context Used:** Rule from `dashboard` - What: All unexported functions must have the "Internal" suffix.

Why: Clearly distinguishes private ... ([source](https://app.greptile.com/review/custom-context?memory=306fc233-4d2f-4ac4-bdf7-8059588e8a43))

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment thread backend/internal/services/gitops_sync_service.go Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 3, 2026

This pull request has merge conflicts. Please resolve the conflicts so the PR can stay up-to-date and reviewed.

@kmendell
Copy link
Copy Markdown
Member

kmendell commented Feb 5, 2026

@sasha-id As anoteghr PR was merged to support .env files, do you wnat to restructure yours to allow this stuff including with the existsing support?

@sasha-id
Copy link
Copy Markdown
Contributor Author

sasha-id commented Feb 5, 2026

@sasha-id As anoteghr PR was merged to support .env files, do you wnat to restructure yours to allow this stuff including with the existsing support?

Go isn't really my strongest skill — I was happy to make the initial contribution, but I think it would be better if someone more experienced with Go took this on to make sure it's done properly.

Copy link
Copy Markdown
Member

kmendell commented Feb 5, 2026

@sasha-id As anoteghr PR was merged to support .env files, do you wnat to restructure yours to allow this stuff including with the existsing support?

Go isn't really my strongest skill — I was happy to make the initial contribution, but I think it would be better if someone more experienced with Go took this on to make sure it's done properly.

Understood, Ill try to implement this based off your initial implementation. Thank you :)

@kmendell kmendell marked this pull request as draft February 8, 2026 00:01
@sasha-id sasha-id force-pushed the feat/git-sync-folder branch 2 times, most recently from c4a54b6 to 64e9d14 Compare March 25, 2026 08:13
@sasha-id sasha-id marked this pull request as ready for review March 25, 2026 08:14
Comment thread frontend/src/routes/(app)/projects/[projectId]/+page.svelte
…ility

- Directory sync mode: sync entire git repo directories to projects, not just compose files
- Configurable per-sync limits: maxSyncFiles, maxSyncTotalSize, maxSyncBinarySize (0 = unlimited)
- File cleanup: removes files deleted from source repo on subsequent syncs
- Project files UI: shows all project directory files in tree view, not just compose/env
- Security: path traversal prevention, symlink skipping, binary detection
- Fix: resolve http import collision in git client
@sasha-id sasha-id force-pushed the feat/git-sync-folder branch from 64e9d14 to 5e24ede Compare March 25, 2026 08:24
@sasha-id
Copy link
Copy Markdown
Contributor Author

Conflict resolved + few improvements, please review and merge

Comment thread backend/pkg/gitutil/git.go
Comment thread backend/internal/services/gitops_sync_service.go
Comment thread backend/internal/services/gitops_sync_service.go
@kmendell
Copy link
Copy Markdown
Member

Ill fix the rest of it tomorrow. Thanks for updating it :)

@github-actions
Copy link
Copy Markdown

This pull request has merge conflicts. Please resolve the conflicts so the PR can stay up-to-date and reviewed.

@kmendell
Copy link
Copy Markdown
Member

Thanks!

@kmendell kmendell merged commit 67b6937 into getarcaneapp:main Mar 26, 2026
16 checks passed
@lordraiden
Copy link
Copy Markdown

Is this already deployed? I dont see the way to configure it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

⚡️ Feature: Git Sync should pull entire project directory, not just a single compose file

3 participants