77

I want to put my home directory (~) under source control (git, in this case), as I have many setting files (.gitconfig, .gitignore, .emacs, etc.) in there I would like to carry across machines, and having them in Git would make it nice for retrieving them.

My main machine is my MacBook, and the way that OS X is set up, there are many folders I want to ignore (Documents, Downloads, .ssh). There are also folders which are already using Git (.emacs.d).

My thought was to just add all these directories to my .gitignore file, but that seems kind of tiresome, and could potentially lead to some unforeseen consequences. My next thought was to periodically copy the files I want to store into some folder in home, then commit that folder. The problem with that will be that I have to remember to move them before committing.

Is there a clean way to do this?

  • You could also make a script doing the commits of the folders you want and calling it through cronjobs. – Shadok Sep 6 '11 at 9:27
  • Note that the default HFS+ file system on Mac OS X is case-insensitive (but case-preserving) and that file paths are encoded in canonically decomposed UTF-8 in user space! For more information see: <a href="stackoverflow.com/questions/5581857/… and the Umlaut problem on Mac OS X</a> – user10523 Sep 6 '11 at 15:25
60

I have $HOME under git. The first line of my .gitignore file is

/*

The rest are patterns to not ignore using the ! modifier. This first line means the default is to ignore all files in my home directory. Those files that I want to version control go into .gitignore like this:

!/.gitignore
!/.profile
[...]

A trickier pattern I have is:

!/.ssh
/.ssh/*
!/.ssh/config

That is, I only want to version .ssh/config - I don't want my keys and other files in .ssh to go into git. The above is how I achieve that.

Edit: Added slashes to start of all paths. This makes the ignore patterns match from the top of the repository ($HOME) instead of anywhere. For example, if !lib/ was a pattern (don't ignore everything in the lib directory) and you add a file .gitignore, previously the pattern (!.gitignore) was matching that. With the leading slash (!/.gitignore), it will only match .gitignore in my home directory and not in any subdirectories.

I haven't seen a case where this makes a practical difference with my ignore list, but it appears to me to be more technically accurate.

24

What I do (with the same objectives) is to put my configuration files in a subdirectory ~/lib and have symbolic links in my home directory, e.g., .emacs -> lib/emacs/dot.emacs. I only keep configuration files that I wrote explicitly under version control; my home directory contains plently of automatically-created dot files that are not under version control. Thus ~/lib is under version control, and my home directory is not.

I have a script that creates the symbolic links from the files under ~/lib. When I create an account on a new machine, I populate it by checking out ~/lib and running that script.

My experience is with CVS, not git, so it's not 100% transferable. One of the reasons I didn't put my home directory directly under CVS is that ~/.cvsignore would apply to all my CVS checkouts and not just my home directory; git doesn't have this problem. The downside of that approach compared with having the home directory under version control is that you can't use git status to distinguish between a file that you've explicitly decided to ignore (which would be listed in the ignore file, so not displayed) and a file that you have no opinion about (which would be displayed with a ?).

Some files need to be different on different machines. I put them in a directory called ~/Local/SITENAME/lib and either create symbolic links for them as well or (for file formats that support it) have an include directive in the file under ~/lib. I also have a symbolic link ~/Here -> ~/Local/SITENAME. Since git, unlike CVS, is designed to support mostly-similar-but-not-identical repositories, there may be a better way to manage machine-specific files. A few of my dot files are in fact not symbolic links, but automatically generated from content under ~/lib and ~/Here.

  • Maybe you have core.excludesfile = ~/.gitignore. Without such a configuration, the file is not applied to any repository except one stored in ~/.git (even then it would not apply to sub-repositories). I use core.excludesfile = ~/.git-user-excludes to avoid the conflict between the excludes I want applied to all my Git repositories (regardless of location) and the excludes I want applied to the repository that holds (parts of) my home directory. – Chris Johnsen Sep 11 '10 at 20:23
  • @Chris: I know extremely little about git. I may have misunderstood this sentence in the gitignore man page: “Patterns read from a .gitignore file in the same directory as the path, or in any parent directory (…)” Does it in fact stop at the root of the checkout (i.e. where the .git directory is)? – Gilles Sep 11 '10 at 20:57
  • 1
    Yes, the upwards search for .gitignore files is bounded by the root of the working tree. The sentence you quoted continues: “(up to the toplevel of the work tree)”. – Chris Johnsen Sep 11 '10 at 22:15
  • @Chris: my version of the man page doesn't have these words — it looks like the wording has been clarified. I've corrected my answer. Thanks! – Gilles Sep 12 '10 at 0:28
  • this was discussed in chat recently – strugee Dec 1 '13 at 22:22
10

We can use Git's ability to continue tracking files even if they're listed in .gitignore. So, this is enough for .gitignore:

$ cat .gitignore
/*

For each file that you want to track, run add -f (the -f parameter overrides ignoring both in .gitignore and .git/info/exclude):

git add -f .gitignore
git add -f .profile
git add -f .zshrc
git add -f .ssh/config

Once having indexed a file, Git will track all changes despite the fact that the file is ignored. The same works for a directory, but only for the files that are actually present:

git add -f somedirname

If you want to track a whole directory with all new files that appear in it, it can be excluded from .gitignore in a way, described in the answer by camh:

!/somedirname

If you ever want to stop tracking a file, this commands removes a file from Git's index but leaves it untouched on the hard drive:

git rm --cached .ssh/config
  • 1
    I'd give you my points if I could. If should be noted that this method can only be used to track files, not directories. If you want to have git track all files in a directory, you'll still need to un-ignore that directory in .gitignore. Otherwise new files in that directory will not appear as new files. – camh Sep 15 '15 at 11:31
5

I use the old rcs for that.

Have a look at manpages for ci, co, and rcs. Those sites should be helpful as well:

I use that for version controlling my dotfiles for instance:

ci -u .*vimrc

And if I want to edit them:

co -l .*vimrc

I recommend making a directory named RCS in your ~, you can then easily backup that directory somewhere.

  • 2
    rcs is pretty dated now, and git does everything rcs does and a lot more besides. I used to use rcs for anything local where I didn't want the overhead of setting up a repository on a server, but I've completely switched over to git now for that type of usage. I even wrote a script that wraps cvs2git to convert an existing directory hierarchy with rcs files in it to git. – Neil Mayhew Sep 11 '10 at 4:09
  • 1
    Yes, I know it is dated. The question was for a tip on how to do that. I've just told this guy how I've been doing it for many, many years ("dated", hint, hint). It wasn't meant in the way that it's the only reasonable way to do it. – polemon Jun 19 '13 at 3:18
5

I check out my config files to $HOME/.conf/ from a BitBucket Mercurial repository. A GitHub repo would work just as well.

The ~/.conf checkout contains config files and a shell script to populate symlinks in $HOME to each file in ~/.conf. For config formats that support inclusion (.bashrc, .inputrc, .vimrc, etc.) I include the ~/.conf file rather than link to it, so that I can do local overrides.

For some config files I symlink to a file in my Dropbox folder and share via dropbox.

For some months I tried keeping $HOME itself in version control, but I tired of managing the massive ignore lists, I tired of checking in config changes made by running apps, and the result was not even something I would want to check out on another computer. Can you imagine fixing conflicts over ~/.gconf or ~/.config/monitors.xml, or catering fro differing versions of desktop apps?

I find it easier to symlink to or include a limited list of config files that I have personally customised and want to share across machines as global defaults.

3

I've just started using the following Python Dotfiles script, which is a handy tool that auto-links the files for you: https://pypi.python.org/pypi/dotfiles

1

I think your second hunch to have non-related folder under source control is good.

Just add 2 shell scripts there. One to copy files under your control to ~ and the other to collect files from ~ and copy it back to the source-controlled folder and commit.

0

Here is a small ruby script which I use to setup a new machine

#!/usr/bin/env ruby
# setup.rb

#list of dirs which you don't want to symlink
ignored = %w(.gitignore .gitmodules misc setup.rb readme)

current_dir = File.expand_path(Dir.pwd)
home_dir = File.expand_path("~")

links = `git ls-tree --name-only HEAD`.lines.map(&:strip).select {|x| !ignored.include?(x)  }

links.each do |link|
  link = File.join(current_dir, link)
  symlink = File.join(home_dir, File.basename(link))
  `ln -ns #{link} #{symlink}`
end

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.