There are many threads in SO about tracking different files in different branches. Since git eases deployment too, people try to add deployment specific secret files to an in-house branch and the problem discussed in those threads will arise. One possible suggested solution is using submodule
. I prefer to avoid it. So by extending this method made a script that let me merge a private branch without considering changes in secret files listed in .branchownfiles
.
It seems work well, but raise a question about file removal in git: git merge
complains about deleted files when merging from private
to master
, even if this file never had participated in a commit related to master
: "file deleted in HEAD and modified in private"
This is the procedure you can see:
i@my % git init
Initialized empty Git repository in /here/there/.git/
i@my % echo 'hello' > 'start.txt'
i@my % git add start.txt
i@my % git commit -m 'started'
[master (root-commit) 32d41f5] started
1 file changed, 1 insertion(+)
create mode 100644 start.txt
i@my % git checkout -b private
Switched to a new branch 'private'
i@my % echo 'this is secret' > a_secret.txt
i@my % echo a_secret.txt > .branchownfiles
i@my % echo .branchownfiles >> .branchownfiles
i@my % git add a_secret.txt
i@my % git add .branchownfiles
i@my % git commit -m 'a secret file added'
[private c2e174f] a secret file added
2 files changed, 3 insertions(+)
create mode 100644 .branchownfiles
create mode 100644 a_secret.txt
i@my % git checkout master
Switched to branch 'master'
i@my % git-merge-with-care.sh private
Automatic merge went well; stopped before committing as requested
rm '.branchownfiles'
rm 'a_secret.txt'
Success
# this is basically a `git merge` followed by two `git rm` for two files.
# so i suppose .branchownfiles and a_secret.txt never should leave anything in `master`
# but my assumption is not true..
i@my % git commit
[master 916c14b] Merge branch 'private'
i@my % git checkout private
Switched to branch 'private'
i@my % echo 'changed in private, should change in public too' > start.txt
i@my % echo 'secret changed too' > a_secret.txt
i@my % git add -u
i@my % git commit -m 'secret changed'
[private dc8938a] secret changed
2 files changed, 2 insertions(+), 2 deletions(-)
i@my % git checkout master
Switched to branch 'master'
i@my % git-merge-with-care.sh private
# now my question arise.
CONFLICT (modify/delete): a_secret.txt deleted in HEAD and modified in private. Version private of a_secret.txt left in tree.
# git merge complains about a_secret.txt an knows it deleted in HEAD. Why?!
Automatic merge failed; fix conflicts and then commit the result.
Trying to fix conflicts for branch limited files
a_secret.txt: [b]
Remerge with ours strategy
CONFLICT (modify/delete): a_secret.txt deleted in HEAD and modified in private. Version private of a_secret.txt left in tree.
Automatic merge failed; fix conflicts and then commit the result.
a_secret.txt: needs merge
rm 'a_secret.txt'
Success
My script still works, because it delete these files before merge. But i want to know why git recalls a_secret.txt
though it never was in a commit for this branch? Is there a way to accomplish effect of git merge --no-commit
followed by git rm
in some way git never recall these deleted files was here before? I mean git merge
but with the ability to add just some files to index directory.
And is my approach secure? If i git rm
some files and git later recalls these files were here before, git still have some information about these files. What kind of data and where is it? Can someone find content of a_secret.txt
by hers access restricted to master
branch?
And this is my git-merge-with-care.sh
:
#!/bin/env zsh
ls -d .git 2>/dev/null 1>&2
if [ $? != 0 ]; then
echo "error: run this from root of project"
exit 1
fi
if [ $# != 1 ]; then
echo "usage: $0 <private branch>"
exit 1
fi
current_branch=$(git branch -q | sed -n -e 's/^* //p')
from_branch=$1 #private branch
into_branch=master #public branch
if [ $current_branch != $into_branch ]; then
echo "error: merge while out of $into_branch branch"
exit 1
fi
new_files=$(git diff -z --name-only --diff-filter=A $into_branch $from_branch)
changed_files=$(git diff -z --name-only --diff-filter='M|T' $into_branch $from_branch)
branch_files=$(git show $from_branch:.branchownfiles)
git merge --no-commit --no-ff $from_branch
# fix conflict with strategy
if [ $? != 0 ]; then
echo "Trying to fix conflicts for branch limited files"
failing=false
git diff -z --name-only --diff-filter=U | while IFS= read -r -d $'\0' f; do
{ grep -x -q "$f" <<<"$branch_files" && echo "$f: [b]"; } || { [ $f != ".branchownfiles" ] && echo "$f: [!]" && failing=true; }
done
git merge --abort
if $failing; then
echo "Merge aborted because of merge conflicts"
exit 2
else
echo "Remerge with ours strategy"
git merge --no-commit $from_branch -s recursive -X ours --no-ff
if [ $? != 0 ]; then
# conflits for deleted files in merger but added again from mergee
conflicting_files=$(git diff -z --name-only --diff-filter=U)
# echo "$conflicting_files" | sort
# echo "$new_files" | sort
if grep -q "$(echo \"$conflicting_files\" | sort -z)" <<<"$(echo \"$new_files\" | sort -z)"; then
xargs -0 <<<"$conflicting_files" -I'{}' git rm --ignore-unmatch '{}'
[[ x`git diff --diff-filter=U` == 'x' ]] || exit 5
fi
fi
fi
fi
# remove files added from private branch but are restricted to private branch
xargs -0 -I'{}' <<<$new_files sh -c "grep -x -q '{}' <<<\"$branch_files\" && [ -e '{}' ] && git rm -rf '{}'"
# bring back public files overwritten bye private branch data
xargs -0 -I'{}' <<<"$changed_files" sh -c "grep -x -q '{}' <<<\"$branch_files\" && git checkout HEAD '{}'"
echo "Success"
exit 0