Skip to content

Upload only files git doesn't ignore on devpod up of a local folder - #29

Merged
renatoathaydes merged 4 commits into
mainfrom
claude/devpod-gitignore-filtering-df3747
Oct 22, 2026
Merged

renatoathaydes merged 4 commits into
mainfrom
claude/devpod-gitignore-filtering-df3747

Conversation

@renatoathaydes

@renatoathaydes renatoathaydes commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

devpod up <folder> streamed the whole folder (minus .devpodignore) into a remote machine or Kubernetes pod, including .git, node_modules, build output and nested git worktrees. On a checkout of this repository that was 8.8 GB; it is now 120 MB (35 s → 2.6 s to build the tar).

When the workspace folder is in a git work tree:

  • Files: only what git ls-files --cached --others --exclude-standard lists is uploaded: tracked files and untracked files no .gitignore, .git/info/exclude or core.excludesFile ignores. Files of checked out submodules are included; nested repositories and worktrees are not. Folders git ignores are never read. Names are matched in Unicode NFC (and case-insensitively when core.ignorecase is set), and listed files are archived with git's spelling, so macOS decomposed names and case-only renames match the index.
  • .devpodignore takes precedence (like ripgrep's .ignore over .gitignore): its patterns leave out listed files, and its negation patterns upload files git ignores (e.g. !.env). It keeps its .dockerignore syntax.
  • .git: at the top of the work tree, the repository's .git is replaced by a shallow one built with git fetch --depth from the local repository: the checked out commit, the branch, its upstream branch (fetched deep enough to reach the merge base, so ahead/behind counts are right), the remotes and info/exclude. It has no user.* config, so it doesn't override the identity Give a workspace the git identity of its repository #28 puts in the container's global config. Enough to commit and push from the workspace; staged changes show as unstaged. Each checked out submodule gets its own shallow .git (embedded in its folder), and the submodule config is copied. Tracked files .devpodignore leaves out are marked skip-worktree, so they don't show as deleted (their blobs are still in .git). The snapshot uses the local repository's object and ref formats, not the user's init.* defaults. This also fixes uploads from a git worktree, whose .git file pointed at a path missing in the container. !.git in .devpodignore uploads the whole .git, .git uploads none.
  • Without git, outside a git work tree, or when git lists nothing (e.g. a dotfiles repo that ignores everything), behaviour is unchanged. Local Docker bind-mounts the folder and is unaffected.

Changes

  • pkg/extract: ExcludeRules (tri-state verdict: unmatched / excluded / re-included), FileList, and TarOptions.Files / TarOptions.Overlays (folders replaced at any path).
  • pkg/git/worktree.go: ListFiles, IsTopLevel, IgnoresCase, Snapshot.
  • go.mod: golang.org/x/text (already vendored) is now a direct dependency.
  • pkg/agent/tunnelserver: workspaceTarOptions used by StreamWorkspace and StreamMount of the workspace mount.
  • Docs: "Which files are uploaded" in Create a Workspace; CHANGELOG entry.

Testing

  • Unit tests for the archiver (including NFD and case-only spellings), for git listing and snapshots against real repositories (nested submodules, nested repos, worktrees, detached HEAD, unborn branch, ahead/behind upstream including a merged upstream, skip-worktree, ref format, config copying, commit and push from the snapshot), and for the tunnel server streaming a git workspace, extracting it and checking git status is clean.
  • An independent review found the issues fixed in the last commit; each has a regression test.
  • go test ./pkg/... ./cmd/..., go vet, golangci-lint v1.60.1: pass.
  • Not yet tried with a real devpod up against a remote machine or Kubernetes cluster.

🤖 Generated with Claude Code

renatoathaydes and others added 4 commits October 22, 2026 14:21
Streaming a local folder into a remote machine or a Kubernetes pod
uploaded everything not excluded by .devpodignore, including .git,
node_modules, build output and nested git worktrees, which made uploads
huge (8.8 GB instead of 120 MB on a checkout of this repository).

When the folder is in a git work tree, the tunnel server now only
uploads the files `git ls-files --cached --others --exclude-standard`
lists, including the files of checked out submodules but not nested
repositories or worktrees. .devpodignore takes precedence: its patterns
leave out listed files and its negation patterns upload files git
ignores.

When the folder is the top of the work tree, the repository's .git is
replaced by a shallow one with the checked out commit, the branch, its
upstream branch (deep enough for ahead/behind counts), the remotes and
the repository's user config, so that the workspace can commit and push.
`!.git` in .devpodignore uploads the whole .git and `.git` uploads none.
Without git, or outside a git work tree, nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re-filtering-df3747

# Conflicts:
#	CHANGELOG.md
DevPod now gives a workspace the git identity of its repository, or the
one given with --git-user-name and --git-user-email, in the container's
global git config. A user copied into the snapshot's local config would
override it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Match git's file names with those on disk in Unicode normalization
  form C, and ignoring case when git does (core.ignorecase), and archive
  listed files with git's spelling. On macOS, files with decomposed names
  or renamed only in case were left out, and showed as deleted.
- Mark tracked files that .devpodignore leaves out skip-worktree in the
  shallow .git, so they don't show as deleted and get committed as such.
- Give each checked out submodule a shallow .git of its own, and copy
  the submodule config, so submodules stay repositories in the
  workspace. Overlays now replace folders at any path.
- Fetch HEAD and its upstream branch in one shallow fetch, as a second
  one cut the history of the first and miscounted commits ahead.
- Create the shallow .git with the ref format of the repository, not the
  user's init.defaultRefFormat, which older git can't read.
- Copy only the settings of the checked out branch, not of branches
  whose name starts with it, and keep boolean settings without a value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@renatoathaydes
renatoathaydes merged commit c890659 into main Oct 22, 2026
20 of 21 checks passed
@renatoathaydes
renatoathaydes deleted the claude/devpod-gitignore-filtering-df3747 branch October 22, 2026 13:43
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.

1 participant