Git clone: decide where the folder lands before you run it
The command gets pasted from a repository page, the shell is wherever it was left, and a project folder appears somewhere in the home directory. A week later there are four copies of the same repository in three different places, one of them inside a folder that syncs to a cloud service. Nothing about git clone went wrong. The destination was never decided, so the command used its default, and the default is a function of the current working directory rather than of any plan.
Where the folder goes when nothing is specified
git clone takes an optional second argument, the directory to clone into. When it is omitted, Git derives a name from the source. The manual calls this the "humanish" part of the repository address: repo for /path/to/repo.git, and foo for host.xz:foo/.git. That derived name is created inside the current working directory.
Naming the directory explicitly removes the guesswork, and it is the difference between a predictable tree and an accumulation:
git clone [email protected]:owner/project.git ~/code/project
git clone https://github.com/owner/project.git ~/code/project-review
Cloning into a directory that already exists is allowed only when that directory is empty. This is the check that turns a mistyped path into an error message instead of a mess, and it is worth relying on rather than working around.
Three other things happen at clone time that are easy to forget about later. Git creates remote-tracking branches for every branch in the source, visible with git branch --remotes. It initialises remote.origin.url and remote.origin.fetch in the new repository's config. It then creates and checks out an initial branch forked from whatever the source repository's HEAD pointed at.
Both of the first two can be adjusted on the command line. -o <name> uses a different remote name instead of origin, which overrides the clone.defaultRemoteName setting and matters when a fork and an upstream will both be added. -b <name> points the new HEAD at a named branch rather than the source's default, and it accepts a tag as well, in which case the resulting repository has a detached HEAD at that commit.
Choosing the parent folder on a Mac
A repository is a working directory full of files that change constantly, plus an object database that grows. That profile interacts badly with several folders that are otherwise convenient.
The Desktop and Documents folders are the first to check, because they may not be local. Apple's support documentation describes what happens when the Desktop and Documents Folders option is turned on: both folders are stored in iCloud Drive and their contents sync to every signed-in device. Two consequences follow for a clone placed there. Deleting a file on one device deletes it on all of them, with a 30 day window in Recently Deleted to undo the mistake. And every checkout, branch switch, and build writes into a folder whose contents are being uploaded while the writes are happening.
Third-party sync folders have the same shape of problem. A branch switch can rewrite thousands of files in a few seconds, which a sync client sees as thousands of independent changes. The .git directory is where that hurts most, because a partially synced object database is a broken repository rather than a stale one.
External volumes and disk images introduce a different question, which is what filesystem they carry. A repository on a case-sensitive volume and a repository on the default case-insensitive one behave differently, and the next section covers why.
The boring answer holds up well: a single plain folder in the home directory, outside anything synced, with one level of grouping. ~/code/<project> is enough structure for a few hundred repositories, and it makes the question "where is this checked out" answerable without a search.
The filesystem details that decide whether a clone works
Two Git settings exist specifically because of how macOS stores filenames, and both are set automatically rather than by hand.
core.ignoreCase is described in the configuration documentation as an internal variable that enables workarounds for filesystems that are not case sensitive, naming APFS, HFS+, FAT, and NTFS. Its default is false, except that git clone and git init probe the filesystem and set it to true when appropriate. The documentation also warns that Git relies on this being correct for the filesystem in use, and that changing the value may produce unexpected behaviour. So it is a value to read, not to set.
The practical effect appears when a repository contains two paths differing only in case, which is legal on the Linux machine where they were created and impossible on a default macOS volume. The checkout cannot represent both files. Renaming within a repository has a milder version of the same issue, which is why git mv with an explicit intermediate name is the reliable way to change only the capitalisation of a filename.
core.precomposeUnicode is documented as being used only by the macOS implementation of Git. When true, it reverts the Unicode decomposition of filenames performed by macOS, which is what makes a repository containing accented or Japanese filenames readable on Linux and Windows. This is the setting behind filenames that look identical in two terminals but compare as different strings.
core.protectHFS defaults to true on macOS, and it refuses to check out paths that would be treated as equivalent to .git on an HFS+ filesystem. It is a security measure rather than a convenience, and it is another value to leave alone.
Cloning less than everything
Large repositories do not have to arrive whole. Four independent mechanisms reduce what is transferred, and they answer different questions.
git clone --depth 1 https://github.com/owner/project.git
git clone --filter=blob:none https://github.com/owner/project.git
git clone --sparse https://github.com/owner/project.git
git clone --single-branch -b release/2.7 https://github.com/owner/project.git
--depth <n> truncates history to the given number of commits. It implies --single-branch unless --no-single-branch is passed, and --shallow-submodules extends the same treatment to submodules. Two related forms exist: --shallow-since=<date> keeps history after a date, and --shallow-exclude=<ref> excludes history reachable from a named ref.
--filter=<filter-spec> produces a partial clone, fetching objects lazily on demand. The specification blob:none omits all blobs, and blob:limit=<n> omits blobs above a size. History remains complete, which is the difference from a shallow clone, and blob contents arrive when something asks for them.
--sparse employs a sparse-checkout in which only the files in the top level directory are present initially, and git sparse-checkout grows the working directory as needed. This changes what is on disk rather than what was downloaded.
--no-tags clones no tags and records remote.<remote>.tagOpt=--no-tags, so later fetches do not follow tags either. The documentation notes the combination with --single-branch as a way to maintain a minimal clone of a default branch, for example for search indexing. Explicit tag fetches continue to work.
Shallow and partial clones are not equivalent, and the choice depends on what will be run in the clone. A shallow clone cannot answer questions about old history, which breaks git blame on untouched lines and any build step that reads a commit count. A partial clone answers them, at the cost of needing the remote to be reachable when it does.
Cloning from a path on the same Mac is a separate case with its own defaults. When the source is given as a local path, --local is already in effect and the flag is a no-op, and files under .git/objects are hardlinked where possible to save space. --no-hardlinks forces real copies instead, which is what a backup of a repository wants. -s or --shared goes the other direction and sets up .git/objects/info/alternates so the new repository borrows objects from the source and starts out holding none of its own, which is fast and fragile in equal measure, because deleting the source breaks the clone. --reference <repo> borrows from a third local repository while still cloning from a remote, and --dissociate copies the borrowed objects in so the dependency ends. Two limits are worth knowing before relying on any of these. A local clone can race with changes being made to the source at the same time, in the same way copying a directory while it is being edited can, and a local clone of a repository owned by another user is refused for security reasons unless --no-local is passed.
Bare, mirror, and the clones that are not working trees
Some clones are not meant to be edited, and the flags for them produce structurally different results.
| Flag | What is created | Remote-tracking refs |
|---|---|---|
| default | working tree plus .git inside it |
created under refs/remotes/origin |
-n, --no-checkout |
.git populated, no files checked out |
created normally |
--bare |
the directory itself is the repository | none created |
--mirror |
bare repository carrying all refs | all refs mapped for overwrite |
--separate-git-dir=<dir> |
working tree here, repository elsewhere | created normally |
--bare makes the target directory itself the $GIT_DIR rather than placing administrative files in a .git subdirectory. It implies no checkout, because there is nowhere to check out to, and the source's branch heads are copied directly to local branch heads without being mapped into refs/remotes/origin. No remote-tracking branches and no related configuration are created.
--mirror implies --bare and goes further: it maps all refs, including remote-tracking branches and notes, and sets a refspec such that a git remote update overwrites all of them. That is the flag for a copy that should follow the original exactly, and the wrong flag for anything a person will commit to.
--separate-git-dir=<git-dir> places the repository at a given path and leaves a filesystem-agnostic link at the working tree location. It has a real use, which is keeping the object database off a slow or synced volume while the files stay where they are expected, and it has a real cost, covered next.
Moving a clone that landed in the wrong place
An ordinary clone can be moved with mv. Nothing inside it records its own absolute location: the config holds remote URLs, and everything else is relative. Renaming the parent folder or shifting the whole tree to another volume leaves a working repository.
Three cases break that rule, and all three involve a stored path.
A repository created with --separate-git-dir has a .git file containing the path to the real repository, and the repository in turn records the path back to the working tree. Moving either end requires updating both.
Linked worktrees created with git worktree add store paths in administrative files. git worktree repair exists for exactly this situation and re-establishes the links after a move.
Submodules hold their repositories under the superproject's .git/modules and reference them through the same mechanism, so a submodule directory should be moved with git mv rather than by hand.
Before moving anything, git status in the clone and git worktree list are enough to tell which case applies. After moving, git status again is the confirmation.
Where the time goes
Deciding where a clone belongs takes seconds. Finding out where one already is takes longer, and that is the part that repeats: reading a path out of a terminal, locating it in a file list, confirming which branch it has checked out, then going back.
When a file list, a terminal, and an assistant share one working directory inside a single window, the folder holding the clone and the commands acting on it are visible at once, and the question of which directory the shell is in stops being a separate lookup. What that arrangement covers is described on the Features page, and how it compares with dual pane browsers and terminal-first tools is set out on Compared with other file managers.
What to change first
Pick one parent folder outside anything that syncs, and pass the destination path to git clone explicitly from now on. If the repeated cost is finding and confirming the repositories already scattered across the disk, that is a layout problem rather than a Git one, and Atriens is one example of the shape of tool that addresses it.
Frequently asked questions
Can a repository be cloned into a folder that already has files in it?
Only if the folder is empty. Git refuses to clone into a non-empty directory, which prevents an existing project from being overwritten by a mistyped path. To combine a clone with existing files, clone to a new location and move the files in afterwards.
Is it safe to keep a git repository in iCloud Drive or Dropbox?
It works until a branch switch or a rebase rewrites many files while the sync client is uploading, and a partially synced .git directory is a broken repository rather than a stale one. Apple's documentation also notes that deleting a file in a synced Desktop or Documents folder deletes it on every signed-in device. Keeping clones in an unsynced folder avoids both problems.
What is the difference between --depth 1 and --filter=blob:none?
A shallow clone truncates history, so commands that read old commits, such as git blame on an unchanged line, have nothing to read. A partial clone keeps all history and omits file contents until something requests them, so those commands still work but need the remote reachable. Shallow suits throwaway checkouts, partial suits long-lived ones.
Can a cloned folder be renamed or moved later?
An ordinary clone can be moved with mv, because nothing in it stores its own absolute path. Repositories created with --separate-git-dir, repositories with linked worktrees, and submodule directories do store paths, and those need git worktree repair or a manual config update after the move.