在 Git 中只导出具有文件夹结构的修改和添加的文件

我希望在一个特定的提交中获得修改和添加的文件的列表,这样我就可以导出它们并使用文件结构生成一个包。

其思想是获取包并将其提取到服务器上。由于很多原因,我不能创建一个钩子来自动拉回购和最简单的方法,我必须保持服务器更新是生成这个包。

77462 次浏览
git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT $commit_id
  1. git diff-tree -r $commit_id:

    Take a diff of the given commit to its parent(s) (including all subdirectories, not just the top directory).

  2. --no-commit-id --name-only:

    Do not output the commit SHA1. Output only the names of the affected files instead of a full diff.

  3. --diff-filter=ACMRT:

    Only show files added, copied, modified, renamed or that had their type changed (eg. file → symlink) in this commit. This leaves out deleted files.


UPDATE FROM THE COMMENT:
Base on the question context and the comments below, with the following command, you can get the ACMRT files as a .tar file with their folder structure.

git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT $commit_id | tar -czf file.tgz -T -

I have made a php script to export changed files on Windows. If you have a localhost development server with php set up then you can run it easily. It will remember your last repository and export always to the same folder. The export folder is always emptied before exporting. You will also see deleted files in red so you know what to delete on the server.

These are just two files so I will post them here. Let's assume your repositories are located under c:/www in their own folders and that http://localhost also points to c:/www and is php-enabled. Let's put these 2 files in c:/www/git-export -

index.php :

<?php
/* create directory if doesn't exist */
function createDir($dirName, $perm = 0777) {
$dirs = explode('/', $dirName);
$dir='';
foreach ($dirs as $part) {
$dir.=$part.'/';
if (!is_dir($dir) && strlen($dir)>0) {
mkdir($dir, $perm);
}
}
}


/* deletes dir recursevely, be careful! */
function deleteDirRecursive($f) {


if (strpos($f, "c:/www/export" . "/") !== 0) {
exit("deleteDirRecursive() protection disabled deleting of tree: $f  - please edit the path check in source php file!");
}


if (is_dir($f)) {
foreach(scandir($f) as $item) {
if ($item == '.' || $item == '..') {
continue;
}


deleteDirRecursive($f . "/" . $item);
}
rmdir($f);


} elseif (is_file($f)) {
unlink($f);
}
}


$lastRepoDirFile = "last_repo_dir.txt";
$repo = isset($_POST['repo']) ? $_POST['repo'] : null;




if (!$repo && is_file($lastRepoDirFile)) {
$repo = file_get_contents($lastRepoDirFile);
}


$range = isset($_POST['range']) ? $_POST['range'] : "HEAD~1 HEAD";




$ini = parse_ini_file("git-export.ini");


$exportDir = $ini['export_dir'];
?>


<html>
<head>
<title>Git export changed files</title>
</head>


<body>
<form action="." method="post">
repository: <?=$ini['base_repo_dir'] ?>/<input type="text" name="repo" value="<?=htmlspecialchars($repo) ?>" size="25"><br/><br/>


range: <input type="text" name="range" value="<?=htmlspecialchars($range) ?>" size="100"><br/><br/>


target: <strong><?=$exportDir ?></strong><br/><br/>


<input type="submit" value="EXPORT!">
</form>


<br/>




<?php
if (!empty($_POST)) {


/* ************************************************************** */
file_put_contents($lastRepoDirFile, $repo);


$repoDir = $ini['base_repo_dir'] ."/$repo";
$repoDir = rtrim($repoDir, '/\\');


echo "<hr/>source repository: <strong>$repoDir</strong><br/>";
echo "exporting to: <strong>$exportDir</strong><br/><br/>\n";




createDir($exportDir);


// empty export dir
foreach (scandir($exportDir) as $file) {
if ($file != '..' && $file != '.') {
deleteDirRecursive("$exportDir/$file");
}
}


// execute git diff
$cmd = "git --git-dir=$repoDir/.git diff $range --name-only";


exec("$cmd 2>&1", $output, $err);


if ($err) {
echo "Command error: <br/>";
echo implode("<br/>", array_map('htmlspecialchars', $output));
exit;
}




// $output contains a list of filenames with paths of changed files
foreach ($output as $file) {


$source = "$repoDir/$file";


if (is_file($source)) {
if (strpos($file, '/')) {
createDir("$exportDir/" .dirname($file));
}


copy($source, "$exportDir/$file");
echo "$file<br/>\n";


} else {
// deleted file
echo "<span style='color: red'>$file</span><br/>\n";
}
}
}
?>


</body>
</html>

git-export.ini :

; path to all your git repositories for convenience - less typing
base_repo_dir = c:/www


; if you change it you have to also change it in the php script
; in deleteDirRecursive() function - this is for security
export_dir = c:/www/export

And now load localhost/git-export/ in a browser. The script is set up to export always to c:/www/export - change all paths to suit your environment or modify the script to suit your needs.

This will work if you have Git installed so that the git command is in your PATH - this can be configured when you run the windows Git installer.

git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT $commit_id | xargs tar -rf mytarfile.tar

Just to round this out, here is the command piped to tar. This exports the files into a tar archive.

Here is a one-line command that works on Windows 7. Run it from your repository's top-level folder.

for /f "usebackq tokens=*" %A in (`git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT HEAD~1 HEAD`) do echo FA|xcopy "%~fA" "C:\git_changed_files\%A"

  • echo FA answers the inevitable xcopy question about whether you're copying a file or a directory (file), and the possible question about overwriting a file (overwrite All)
  • usebackq allows us to use the output from our git command as the input to our do clause
  • HEAD~1 HEAD gets all differences between the previous commit and the current HEAD
  • %~fA transforms the git output into fully qualified paths (needed to change forward slashes to backslashes)
  • C:\git_changed_files\ is where you will find all the files that are different

if your commit hash is for example a9359f9, this command :

git archive -o patch.zip a9359f9 $(git diff --name-only a9359f9^..a9359f9)

will extract the files modified in the commit and place them in patch.zip while keeping the project directory structure intact.

a bit verbose, the commit hash is mentioned three times, but it seems to work for me.

got it here : http://tosbourn.com/2011/05/git/using-git-to-create-an-archive-of-changed-files/

I needed to update my test server and add files that changed since version 2.1.
For me worked similar solution as James Ehly posted, but in my case i wanted to export to archive package of difference between two older tags - tag_ver_2.1 and tag_ver_2.2 not the only one commit.

For example:

tag_ver_2.1 = 1f72b38ad
tag_ver_2.2 = c1a546782

Here is modified example:

git diff-tree -r --no-commit-id --name-only c1a546782 1f72b38ad | xargs tar -rf test.tar

You can export diff using Tortoise Git to MS Windows:

I right click and select TortoiseGit > Show Log and Log Messages will be open.

Select two revisions and compare it. Difference between will be open.

Select the files and Export selection to ... to a folder!

enter image description here

To export modified files starting with a date:

  diff --stat @{2016-11-01} --diff-filter=ACRMRT --name-only | xargs tar -cf 11.tar

Shortcut (use alias)

  git exportmdf 2016-11-01 11.tar

Alias in .gitconfig

  [alias]
exportmdf = "!f() { \
git diff --stat @{$1} --diff-filter=ACRMRT --name-only | xargs tar -cf $2; \
}; f"

Here is a small bash(Unix) script that I wrote that will copy files for a given commit hash with the folder structure:

ARRAY=($(git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT $1))
PWD=$(pwd)


if [ -d "$2" ]; then


for i in "${ARRAY[@]}"
do
:
cp --parents "$PWD/$i" $2
done
else
echo "Chosen destination folder does not exist."
fi

Create a file named '~/Scripts/copy-commit.sh' then give it execution privileges:

chmod a+x ~/Scripts/copy-commit.sh

Then, from the root of the git repository:

~/Scripts/copy-commit.sh COMMIT_KEY ~/Existing/Destination/Folder/

Below commands worked for me.

If you want difference of the files changed by the last commit:

git archive -o update.zip HEAD $(git diff --name-only HEAD HEAD^)

or if you want difference between two specific commits:

git archive -o update.zip sha1 $(git diff --name-only sha1 sha2)

or if you have uncommitted files, remember git way is to commit everything, branches are cheap:

git stash
git checkout -b feature/new-feature
git stash apply
git add --all
git commit -m 'commit message here'
git archive -o update.zip HEAD $(git diff --name-only HEAD HEAD^)

I also faced a similar problem before. I wrote a simple Shell script.

$git log --reverse commit_HashX^..commit_HashY --pretty=format:'%h'

The above command will display Commit Hash(Revision) from commit_HashX to commit_HashY in reverse order.

 456d517 (second_hash)
9362d03
5362d03
226x47a
478bf6b (six_hash)

Now the main Shell Script using above command.

commitHashList=$(git log --reverse $1^..$2 --pretty=format:'%h')
for hash in $commitHashList
do
echo "$hash"
git archive -o \Path_Where_you_want_store\ChangesMade.zip $hash
done

Add this code to export_changes.sh. Now pass file and commit hashes to the script.

Make sure that initial commit_hash must be the first argument then last commit_hash up to which you want to export changes.

Example:

$sh export_changes.sh hashX hashY

Put this script into git local directory or set git local directory path in the script. Hope it Helps..!

This works for me for both Unix and Windows:

git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT __1__.. | xargs cp --parents -t __2__

There are two placeholders in the command. You need to replace them for your purpose:

  • __1__: replace with commit id of commit right before all of the commits you want to export (e.g. 997cc7b6 - don't forget to keep that doubledot after commit id - this means "involve all commits newer than this commit")

  • __2__: replace with existing path where you want to export your files (e.g. ../export_path/)

As a result you will get your files in a folder structure (no zips/tars...) as someone might be used to using tortoise svn exports in svn repositories.

This is for example pretty useful when you want to perform manual deploy of added/modified files of few last commits. So you can then just copy these files through ftp client.

I also had the same problem. The solution of @NicolasDermine didn't work since too many files have changed between the compared commits. I got an error that the shell arguments are too long.

As a consequence I added a python implementation. This can be installed via pip install gitzip and then executed with

python -m gitzip export.zip <commit id> <commit id>

in the repositories root creating an export.zip file containing all changed files retaining the directory structure.

Maybe someone needs it too so I thought to share it here.

Disclaimer: I am the author of the gitzip module.

git archive -o package.zip HEAD $(git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT <comit_id>)