Edit, per submodule related question
You can nest repositories. Here is an example—my Git version is slightly different from yours—on this particular machine it's currently 2.14.1—but I get the same new git add
complaint and behavior, which requires a little initial trickery.
$ mkdir trepo
$ cd trepo
$ git init
Initialized empty Git repository in ...
$ echo testing nested dirs > README
$ git add README
$ git commit -m initial
[master (root-commit) 1eb37c9] initial
1 file changed, 1 insertion(+)
create mode 100644 README
The top level repository (path/trepo
) now exists, with one commit in it. The lone commit contains one file, README
.
$ mkdir sub
$ cd sub
$ git init
Initialized empty Git repository in .../trepo/sub/.git
$ echo this is a nested subdirectory > README
$ git add README
$ git commit -m initial-sub
[master (root-commit) 943378a] initial-sub
1 file changed, 1 insertion(+)
create mode 100644 README
The sub-repository now exists, also with one commit that contains one file named README
. If we now walk back up one level, we can convince Git to add the files inside sub
. However, as you found, a sufficiently modern Git—I'm not sure when this new behavior appeared—will, if we use git add sub
, add sub
as a submodule—well, add it as a broken submodule—rather than adding the files contained within sub
. So instead of git add sub
, we will explicitly add the files within sub
, for each such file (which is easy now as there is only one—remember, we want to skip over the .git
directory and all its contents):
$ cd ..
$ git add sub/README
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: sub/README
Let's go ahead and commit this, then make another file in sub
to see what happens:
$ git commit -m 'add sub/README'
[master a7fb248] add sub/README
1 file changed, 1 insertion(+)
create mode 100644 sub/README
$ echo another test > sub/newfile
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
sub/newfile
nothing added to commit but untracked files present (use "git add" to track)
$ (cd sub; git status)
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
newfile
nothing added to commit but untracked files present (use "git add" to track)
So now that our trepo/.git
repository is tracking files within sub
, things work the way we might expect.
As I noted in comments, you can do all of this. I have seen people try this method. They usually end up unsatisfied. Still, if it works for you, well, Git is a tool, not a solution; use it however you like!
(Note that when using submodules, the "top level" repository—Git calls this the superproject—will not contain the same content as the sub-repository. Instead, each commit within the superproject will contain, in each commit, a special Git entry called a gitlink that records the correct hash ID that Git should git checkout
in the submodule. The submodule is a completely independent Git repository, all on its own, that you can use as a standalone project. To use it with the superproject, you clone the superproject, then have Git use the superproject's .gitmodules
file to know how to clone the submodule. After that, each existing commit you extract from the superproject knows which commit to extract in the subproject, and each new commit you make in the superproject shall record the correct subproject commit. It's up to you to make sure that those new superproject commits do record the correct subproject commit.)
Original answer below
This is not going to work the way you want. Repositories don't hold files—well, not directly. Repositories hold commits. If you connect two repositories, what they will share is some set of commits.
Any given commit holds a snapshot (of files), so in that particular sense, a repository does hold files. But it holds them one commit at a time. In general, you git checkout
an entire commit. Let's call this commit C1
. This fills in your work-tree, and, very importantly, also fills in your index / staging-area.1 Your work-tree now has all the files that are in C1
.
If you then switch from that commit to some other commit C2
, Git removes any files that went with C1
that are not in C2
. Your index and work-tree now match commit C2
, not commit C1
.
Pay no attention to .gitignore
at this point, because it's largely irrelevant. The set of tracked files, in a Git work-tree, are those files that are in the index. They exist in the index because you extracted them from commit C1
or C2
, whichever it is that you checked out. So those files are tracked, and any other files in your work-tree—however they may have arrived there—are untracked.
What .gitignore
does is keep Git from complaining about the untracked files. Normally, Git would whine at you, suggesting that you add them to the next commit you will make. So if you have a bunch of build artifacts, for instance, you can list them in .gitignore
and keep Git from whining. As a pleasant side effect, this also means that git add .
or git add --all
won't add the untracked files—but their "untracked-ness" is a result of them not being in the index right now.
If you git checkout
some other commit C3
that has some other set of files in it, Git will remove from the work-tree any tracked file that isn't in C3
, and add—to both index and work-tree—any files that are in C3
. (And, of course, it changes the contents of files that are in both commits, to whatever goes with C3
.) The files from C3
are now, by definition, tracked.
Note: commits are uniquely identified by their hash IDs. A repository either has the complete commit, or has nothing of that commit. When you connect two repositories, Git's general process is to yank into itself all commits that the sending repository has, that the receiving repository lacks.2 That's whole commits, not files. You then git checkout
one of those commits to populate your index and your work-tree. Branch names like master
and develop
mainly serve to identify one particular commit, namely the commit that Git is to use as the tip of that branch.
You can get what you want by playing fancy tricks with multiple index files. But you'll need separate (unconnected) repositories with a single shared work-tree, which is tricky, and you'll need to commit everything separately to the two separate repositories, which is also tricky. You also won't be able to use .gitignore
files usefully (you'll need to use the per-repo $GIT_DIR/info/exclude
instead). I don't recommend attempting this: I don't think I'd want to try to do it myself, and I know most of the dark corners of Git. :-)
1This index thing actually has three names, reflecting its three purposes: it (1) indexes the work-tree; (2) acts as a staging area in which you build up the next commit you will make; and (3) works as a cache to make git commit
go very fast.
2This is an deliberate overstatement—the truth has to do with graph theory and reachability—but it's good enough for an initial view.