Bix
Bix

Reputation: 928

Track folder locally with Git, Don't track folder on GitHub

This seems like an obvious question but I can't find the answer I need anywhere.

I have a folder on my local machine. I would like to track it locally with Git.

When I push to GitHub, I would like to not track/push that folder.

Is this possible/How can I accomplish this?

Upvotes: 5

Views: 2380

Answers (3)

torek
torek

Reputation: 488143

Tracked, in Git, means in the index.1 Only files can be tracked this way, not folders, because Git does not store folders.2

So: what the heck is "the index" anyway? Well, this eventually gets complicated—the index has a bunch of different roles, with one complex one used during merging—but to put it very simply, the index, in Git, holds your proposed next commit.

The index starts out holding copies3 of all of your files from some commit. That is, you pick some commit to "check out", with git checkout name or git checkout hash-id. This commit holds a bunch of files. Git copies (see footnote 3) all of those files into the index, and then also into your work-tree.

The work-tree or working tree is where you see and use files in Git. But the files in your work-tree aren't actually in Git at all!

What Git keeps are the commits. Each commit has its own unique hash ID. When you make a new commit, or anyone makes any commit, Git assigns that commit its own new unique hash ID, and from then on, that hash ID means that commit. These commits, once made, can never be changed. They're all-or-nothing: you either have that hash ID in your Git, in which case you have that commit, or you don't have that commit at all.

This makes commits great for archiving: every version of every file that you, or anyone, ever saved, is available for future inspection or re-use forever!4 To save space and for other good reasons, though, they're stored in a special, read-only, Git-only, compressed format. Only Git itself can extract them, and of course, being read-only like this, nobody can change them.

So this means commits are useless, on their own, for getting any actual work done. You have to have Git extract the commit somewhere so that you can work with and/or on it.

That somewhere is your work-tree.

Because your work-tree is yours, you can stick files into it that Git doesn't have saved in any commits. You pick some commit with git checkout and have Git extract all of its files into your work-tree, and then you add some other file that isn't in the commit. In your case, for instance, you first make a folder named drafts/ and then create a file within this drafts/ folder. Now there is some file that:

  • is not in the current commit ...
  • so Git didn't copy it into the index ...
  • but there it is, drafts/README or whatever, in your work-tree.

That is, by definition, an untracked file.

If you tell Git to track this file, by copying it into the index, that file will be in your next commit. And, as code_fodder said, it's the commits that get shared. When you connect your Git to some other Git with git push, your Git sends their Git some or all of your commits—ones that you have, that they don't, that you want them to have. You cannot pick and choose files in this process. You either give them the whole commit—with its complete snapshot of all of its files—or you don't.

This means that the short answer to your question is just "no". It is not possible to track a file—which means it goes into each new commit you make—yet have your Git deliver to the other Git some commit that you didn't make, that doesn't contain the file.

A longer answer is available though. You can make commits in which you do save drafts/* files, and other commits in which you don't save them. You can then carefully push only chains of commits in which drafts/* files have never appeared.

If you ever accidentally push one of the commits in which the drafts/* files do appear, though, it's very hard to take that back: Git treats commits as fairly precious (see footnote 4) and has a tendency to give all commits to every other Git that comes along and asks. That is, if you accidentally send a commit to GitHub, and someone else comes along and asks GitHub for any new commits that are in that repository, the someone else will get this commit, and now you must plead with that someone else to give it up. Even if you get GitHub to remove the accidental commit, this other guy has a copy now.

It's also a pretty big pain. If you have commit a123456... (some big ugly hash ID) that does have drafts/file1.txt in it, and commit b789abc... (some other different big ugly hash ID) that doesn't have drafts/file1.txt in it, then every time you switch from b789abc... to a123456..., Git will remove drafts/file1.txt from both the index and your work-tree. When switching from a123456... back to b789abc..., Git will copy the file from b789abc... back into both the index and your work-tree—as it must; your index holds the proposed next commit, and your work-tree should match, at least for all tracked files.

Usually, the right way to do this is to have a separate Git repository in which you store these things. That is, inside drafts/, just create a new Git repository. The outer Git, holding all the other files, will leave all the drafts/* files to the inner Git. You can then just not give anyone else this Git repository at all. If you want to have some hosting server keep a copy, you can do that separately, and not give people access to the separate repository there.


1Well, it means that when we're referring to files. Git also sometimes says that a branch is set up to "track" another branch.

2What happens is that when Git encounters a commit and the commit contains a file named a/b/c.txt, Git tells the computer create a file named a/b/c.txt. To do that, if the computer requires it—obviously yours does—Git will create the folder named a, then create the folder named b within a, so that it can create a file named c.txt within b within a. But to the rest of Git, this is just a file named a/b/c.txt. It never saved "folder a" nor "folder b", just the file.

3Technically, the index holds a reference to an internal Git blob object, for each file. But it works just as well at this level to just think of the index as if it held a copy of each Git-format file.

4It is possible to get rid of a commit. It's relatively easy, in fact, up until you yourself with later commits, and/or other people's commits, make things (which in Git means "more commits") that depend on that commit. Then it gets harder. So commits are completely read-only—no one, nothing, not even Git, can change them—but if you have made a bad one, you can just stop using it. If you're not using it, and have set things up so that you can't use it again, and no one else can use it, Git eventually cleans it up for you.

Upvotes: 3

karmakaze
karmakaze

Reputation: 36134

There isn't an obvious answer because git branches are not meant to work this way. When a local branch is pushed to remote, it is done exactly which is the purpose of version control.

If you want to have two different sets of files tracked (e.g. one branch excluding a folder) you could do so with two branches, one that's only kept locally and one that's pushed remotely. By doing this note that you're not getting any backup copy of the local-only branch. You can make a script that commits to the local-only branch and also to the branch excluding a folder that will be pushed.

Upvotes: 1

code_fodder
code_fodder

Reputation: 16341

The folder is either part of your repository or it is not. You push complete commits where each commit is the state of all the files in your repo at a specific time. If you add a folder to your repo and commit it then it is now a part of your repo. If you push it, it goes wherever you push'ed it to...

Here are some options (there are probably others):

  • You could have a local branch that you commit locally and don't push that branch to github
  • You can move your folder to another repo outside of your current repo - possibly add a symlink to it so that when you push your repo to github it just contains a broken symlink (not pretty but will work).
  • You can add your folder to .gitignore file, it will not be part of your repo and won't get pushed, but you can't track it

Maybe you can explain your usecase, usually a repo is a complete set of things - i.e. if you don't want to push it, then it sounds like it should not be part of your repo (this is a generalisation - so it will be interesting to hear what your usecase is)...

Upvotes: 2

Related Questions