tags:

views:

49

answers:

3

Git has the very handy archive command which allows me to make a copy of a particular commit in a .zip archive like so:

git archive -o ../latest.zip some-commit

This will contain the entire working tree for that commit. Usually I just need the changed files since a previous release. Currently I use this to get those files into a zip:

git diff --name-only previous-commit latest-commit | zip ../changes.zip -@

This will however zip files from my working copy, which may have uncommitted changes. Is there some way to get only the changed files as they were committed directly into a zip?

A: 

why not create a git patch which you can then apply to the other codebase?

$ git format-patch HEAD^..HEAD
knittl
Not everybody knows how to work with patch files I'm afraid. I use this to supply new releases to my customers. Unzipping an archive is something even non-developers can do. Applying a patch requires skills not all my customers have mastered (or even want to master).
Marnix van Valen
A: 

If creating a patch is not an option, then how about something like this:

git stash
git checkout latest-commit
git diff --name-only previous-commit | zip ../changes.zip -@
git checkout master
git stash apply

NOTE: The git stash and git stash apply are only needed if you have working copy changes that have not been committed.

NOTE 2: git checkout latest-commit will put you on a detached head. Be sure to git checkout master when you are done or you could wind up having problems with future commits.

Tim Henigan
I was kind of hoping for a single command solution...
Marnix van Valen
@Marnix van Valen Then why not turn it into a simple script (or batch file)? The problem is that you need to translate the list of file names into specific versions. The only other way I could think of is to use `git show latest-commit:file`. However, I was not able to come up with a one liner to do this. It would look something like this: `git diff --name-only previous-commit | xargs -I file git show latest-commit:file` ... but here is the problem. This prints the file (without its name) to STDOUT. How can this be translated into the zip file?
Tim Henigan
+2  A: 

git archive will accept paths as arguments. All you should need to do is:

git archive -o ../latest.zip some-commit $(git diff --name-only earlier-commit some-commit)

or if you have files with spaces (or other special characters) in them, use xargs:

git diff --name-only earlier-commit some-commit | xargs -d'\n' git archive -o ../latest.zip some-commit

If you don't have xargs properly installed, you could cook up an alternative:

#!/bin/bash

IFS=$'\n'
files=($(git diff --name-only earlier-commit some-commit))

git archive -o ../latest.zip some-commit "${files[@]}"

Written as a shell script, but should work as a one-liner too. Alternatively, change earlier-commit, some-commit, and ../latest.zip to $1 $2 and $3 and you've got yourself a reusable script.

Jefromi
Nice one! That works great.
Marnix van Valen
Beware that extracting such an archive will not delete any files. This may cause hard to diagnose problems later on.
jilles
@jilles: Very true. This is why patches are great. @Marnix van Valen: You could perhaps just generate the patch then wrap it with a script (you could even use a heredoc to keep it all in one file) - then it'd be both usable and, well, correct.
Jefromi
What I usually do is include a list of all added and removed files with the release. The customer will then manually add or remove the files to/from source control on his end. @Jefromi Good idea. That would completely remove any human errors. I'll give it a shot.
Marnix van Valen
@Jefromi One problem with the second command; it doesn't work on windows. The xargs version that comes with the Windows git installer doesn't support the -d option. Nor does any of the other xargs builds for windows I could find. Any idea's for an alternative?
Marnix van Valen
@Marnix van Valen: Then I'd do it within bash. See the answer.
Jefromi