How to setup a git driver to ignore a folder on merge
I've already did an extensive search, read a lot of SO questions and solutions and tried it different ways, but I've been unable to do what I want, which is fairly simple.
I have the master branch, where all the main code resides, and the design branch, where the layout of the rails application is built by the design team. They have added a folder called "photoshop" to the public folder to keep their sources for the images also under version control. But I don't want this folder to be copied on merge to the master branch because, well, it is not needed.
Apparently, the way to do this is through a merge driver. So, I've created the "ignore" driver:
[merge "ignore"]
name = always ignore during merge
driver = ignore.sh %开发者_JAVA技巧0 %A %B
And created the ignore.sh file on my $PATH:
exit 0
I've created the .gitattributes file inside public/, because the photoshop folder should be ignored in whole and it is going to appear under public/:
photoshop merge=ignore
photoshop/ merge=ignore
photoshop/* merge=ignore
photoshop/**/* merge=ignore
As you can see, I've tried several different patterns to ignoring the whole folder, but it does not work. I believe this is because there is no folder on the master branch, so there's no conflict and so git doesn't use the ignore driver. Is there a way to achieve this without having to create a public/photoshop folder on master?
Thanks!
As suggested by my other answer, here goes the extended, generalized, industrial-strength version of the solution.
(yes, I was bored at home and had nothing else better to do :P)
This script will add a new, detached commit based on your local design branch, so it won't affect neither the design repository or your design branch. The commit will have all desired files removed. Then it performs the merge.
For those too lazy to read the full code, the "core" of these steps can be simplified as:
original=$(gitbranch HEAD) # current branch name, or sha1 if not in a branch
branchsha=$(gitsha "$branch") # sha1 of a ref, to force detached commit
git checkout "$branchsha" &&
git rm -rf "${files[@]}" &&
git commit -m "$msgcommit" &&
newsha=$(gitsha HEAD) &&
git checkout "$original" &&
git merge -m "$msgmerge" "${mergeopts[@]}" "$newsha"
And here is the full script:
(a bit modified to cope with the weak and limited SO's syntax coloring, so it's better to get the pristine source from the link below)
git-strip-merge
#!/bin/bash
#
# git-strip-merge - a git-merge that delete files on branch before merging
#
# Copyright (C) 2012 Rodrigo Silva (MestreLion) <linux@rodrigosilva.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not see <http://www.gnu.org/licenses/gpl.html>
#
# Answer for "How to setup a git driver to ignore a folder on merge?"
# See http://stackoverflow.com/questions/3111515
#Defaults:
msgcommit="remove files from '<branch>' before merge"
msgmerge="Merge stripped branch '<branch>'"
verbose=0
quiet=(--quiet)
usage() {
cat <<- USAGE
Usage: $myname [git-merge options] [-M <commitmsg>] <branch> FILE...
USAGE
if [[ "$1" ]] ; then
cat >&2 <<- USAGE
Try '$myname --help' for more information.
USAGE
exit 1
fi
cat <<-USAGE
"git-merge that delete files on "foreign" <branch> before merging
Useful for ignoring a folder in <branch> before merging it with
current branch. Works by deleting FILE(S) in a detached commit based
on <branch>, and then performing the merge of this new commit in the
current branch. Note that <branch> is not changed by this procedure.
Also note that <branch> may actually be any reference, like a tag,
or a remote branch, or even a commit SHA.
For more information, see <http://stackoverflow.com/questions/3111515>
Options:
-h, --help
show this page.
-v, --verbose
do not use -q to supress normal output of internal steps from git
checkout, rm, commit. By default, only git merge output is shown.
Errors, however, are never supressed
-M <message>, --msgcommit=<message>
message for the removal commit in <branch>. Not to be confused
with the message of the merge commit, which is set by -m. Default
message is: "$msgcommit"
-m <message>, --message=<message>
message for the merge commit. Since we are not merging <branch>
directly, but rather a detached commit based on it, we forge a
message similar to git's default for a branch merge. Otherwise
git would use in message the full and ugly SHA1 of our commit.
Default message is: "$msgmerge"
For both commit messages, the token "<branch>" is replaced for the
actual <branch> name.
Additional options are passed unchecked to git merge.
All options must precede <branch> and FILE(s), except -h and --help
that may appear anywhere on the command line.
Example:
$myname design "photoshop/*"
Copyright (C) 2012 Rodrigo Silva (MestreLion) <linux@rodrigosilva.com>
License: GPLv3 or later. See <http://www.gnu.org/licenses/gpl.html>"
USAGE
exit 0
}
# Helper functions
myname="${0##*/}"
argerr() { printf "%s: %s\n" "${0##*/}" "${1:-error}" >&2 ; usage 1 ; }
invalid() { argerr "invalid option: $1" ; }
missing() { argerr "missing ${2:+$2 }operand${1:+ from $1}." ; }
# Option handling
files=()
mergeopts=()
for arg in "$@"; do case "$arg" in -h|--help) usage ;; esac; done
while (( $# )); do
case "$1" in
-v|--verbose ) verbose=1 ;;
-M ) shift ; msgcommit=$1 ;;
-m ) shift ; msgmerge=$1 ;;
--msgcommit=* ) msgcommit=${1#*=} ;;
--message=* ) msgmerge=${1#*=} ;;
-* ) mergeopts+=( "$1" ) ;;
* ) branch="$1"
shift ; break ;;
esac
shift
done
files+=( "$@" )
# Argument handling
msgcommit=${msgcommit//<branch>/$branch}
msgmerge=${msgmerge//<branch>/$branch}
[[ "$msgcommit" ]] || missing "msgcommit" "MSG"
[[ "$branch" ]] || missing "" "<branch>"
(( ${#files[@]} )) || missing "" "FILE"
((verbose)) && quiet=()
# Here the fun begins...
gitsha() { git rev-parse "$1" ; }
gitbranch() {
git symbolic-ref "$1" 2> /dev/null | sed 's/refs\/heads\///' ||
gitsha "$1"
}
original=$(gitbranch HEAD)
branchsha=$(gitsha "$branch")
trap 'git checkout --quiet "$original"' EXIT
git checkout "$branchsha" "${quiet[@]}" &&
git rm -rf "${files[@]}" "${quiet[@]}" &&
git commit -m "$msgcommit" "${quiet[@]}" &&
newsha=$(gitsha HEAD) &&
git checkout "$original" "${quiet[@]}" &&
git merge -m "$msgmerge" "${mergeopts[@]}" "$newsha"
Enjoy!
An image is worth more than a thousand words...
Before merge:
After merge:
Note that "design" branch tip wasn't affected at all, even being a local branch, thanks to the detached commit trick. Other than that, both commits (the removal and the merge) are regular commits, with suitable commit messages and parents. And "master" branch is clean of any undesired files.
Not an answer per se, but a few notes about .gitignore
: it will not help you in your scenario.
.gitignore
is for ignoring files from the working tree to be added to index (staging area). So it is only effective when you use git add <files>
, and it can still be overridden using git add --force <files>
. It is meant just as a convenience to prevent unwanted files from being added, but it has no effect on files inside the repository
In your scenario, .gitignore
is not needed, since you don't have any local ./photoshop
folder, so you will never have any photoshop files to add to your master branch. It wouldn't hurt, however, to create one just in case. And for the design team .gitignore
is not welcome, since they want photoshop files to be added to their branch.
So, since merge deals with committed data, and ./photoshop
files are already inside the repository, your approach of using a a merge driver was correct.
The problem is... by default a merge driver is only triggered in case of conflicts. And, since master
branch does not have any ./photoshop
folder or files, there are no conflicts at all, they are cleanly merged. So your merge driver also had no effect, regardless of your path patterns (by the way, your 2nd one, photoshop/
, was the correct one). I don't know if git merge can be configured to trigger a merge driver even for non-conflicting files, but it's worth googling for.
As I've said before, my answer is not a true solution to your problem. I just hoped to shed some light on the subject, explaining why your attempts using merge driver and .gitignore have failed. I suggest reading more about (configuring) merge drivers. Also submodules is worth investigating.
Hope this helps!
UPDATE
Maybe my other answer will help you:
Usage: git-strip-merge [git-merge options] [-M <commitmsg>] <branch> FILE...
git-strip-merge
Enjoy!
Did you try adding a .gitignore
file in your master
branch, to ignore any photopshop
directory content ?
Then a merge from design to master
shouldn't add that new directory in master
.
If this works, you still needs a merge driver, but this time to manage the content of the .gitignore
file.
精彩评论