I recently integrated Git into my workflow, and I'm impressed with its capabilities. Not only is it a great VCS, it's running laps around FTP when pushing via ssh. I did run into a hitch this evening though, and I'm wondering if you fine folk can help me resolve it.
There are certain files in a project that I want to track, but don't want to push to production. Is there a way to push to a production server while excluding certain files from the repo? Two great examples:
- .less files don't need to be on a production server at all, but ABSOLUTELY should still be tracked
- As my local and production environments operate under different domains, the .htaccess files vary slightly. I still want to track these, as most of the rules are universal (and a pain to rewrite if lost) - but every time I push my local .htaccess live, it breaks my production environment and I have to fix the file by hand.
If possible, I'd like to keep everything under a single commit/push. My workflow is beautifully simple (except for this one thing), and adding extra steps or complicating factors would make me a sad Moose.
I don't know if it helps or not, but I'm using Tower to help manage everything.
I don't have a completely awesome solution for you, so I will give you a few options to consider.
First, I have addressed similar situations in the past by using separate branches for production, qa and dev. This does violate your desire for "a single push".
Secondly, if you really want to keep everything in a single branch, you could use another common technique which is to exclude the specific files by using .gitignore, but link to them in each working directory via a soft-link to the varying file. Links are supported in most operating systems, including OSX, Unix-like (Linux, FreeBSD, etc.) and Windows NTFS via the MKLINK command.
To set up using links, you would rename the .htaccess file to be .htaccess-dev and then copy and update it to .htaccess-dev, .htaccess-qa for all your environments. Then, add .htaccess to the .gitignore and finally create soft-links to the correct environment on each of your environments.
For example, if your DEV system was Windows, you would see something like:
C:\code\dev-example>ren .htaccess .htaccess-dev
C:\code\dev-example>mklink .htaccess .htaccess-dev
symbolic link created for .htaccess <<===>> .htaccess-dev
C:\code\dev-example>dir
Volume in drive C is Boot
Volume Serial Number is DC8C-D5C9
Directory of C:\code\dev-example
06/02/2014 06:12 PM <DIR> .
06/02/2014 06:12 PM <DIR> ..
06/02/2014 06:09 PM 10 .gitignore
06/02/2014 06:09 PM <SYMLINK> .htaccess [.htaccess-dev]
06/02/2014 06:08 PM 22 .htaccess-dev
06/02/2014 06:12 PM 23 .htaccess-prod
4 File(s) 55 bytes
2 Dir(s) 13,497,245,696 bytes free
C:\code\dev-example>type .htaccess
# Config file for DEV
C:\code\dev-example>type .gitignore
.htaccess
This approach works well for a few varying configuration files, but gets unwieldy if you have many such files, and it doesn't really address your desire for excluding the .less files.
Finally, if you can re-organize your project to separate the dev only code into separate folders, you could use git sparse-checkout on the production server to grab only the folders that you want in prod. In combination with using soft-linked configuration files, this might allow you to have your single-push workflow while maintaining different working folders in dev/qa/prod. See http://briancoyner.github.io/blog/2013/06/05/git-sparse-checkout/
Pushing a commit without certain files is impossible in Git. A commit is identified by the hash of it's changeset and the commit it's based on(which which is in turn a hash of changesets and previous commits). Excluding files changes at least one of the changeset, which in turn will change the hash(unless you happen to hit the same hash, but that is so rare it barely worth mentioning).
However, if might be able to exclude the files from being checked out in the production environment. They'll still be on the production machine, but compacted inside the Git index of the local repository there, where they barely take up any space and don't interfere with anything.
In order for this to work, you need to be able to set up hooks on a bare repository in the production environment you'll push to. Upon a push, you need to use this command:
git archive --remote <path-to-bare-repo> --format tar <branch-to-checkout> | tar --extract --directory <path-to-extract> --exclude <pattern-of-files-to-exclude>
This command, instead of regular checkout, will create a tar archive representation of the branch's current tree. That archive is piped through tar
to convert it to actual files on the disc. tar
accepts the --exclude
flag that lets you specify the files you want to exclude.