I am working in a team environment, and there is already a .gitignore
file.
I want to add more items to the .gitignore
file, but I don\'t w
You may be interested in an update hook that Junio wrote and that Carl improved. Place the code below in $GIT_DIR/hooks/update
and don't forget to enable it with chmod +x
.
#!/bin/bash
umask 002
# If you are having trouble with this access control hook script
# you can try setting this to true. It will tell you exactly
# why a user is being allowed/denied access.
verbose=false
# Default shell globbing messes things up downstream
GLOBIGNORE=*
function grant {
$verbose && echo >&2 "-Grant- $1"
echo grant
exit 0
}
function deny {
$verbose && echo >&2 "-Deny- $1"
echo deny
exit 1
}
function info {
$verbose && echo >&2 "-Info- $1"
}
# Implement generic branch and tag policies.
# - Tags should not be updated once created.
# - Branches should only be fast-forwarded unless their pattern starts with '+'
case "$1" in
refs/tags/*)
git rev-parse --verify -q "$1" &&
deny >/dev/null "You can't overwrite an existing tag"
;;
refs/heads/*)
# No rebasing or rewinding
if expr "$2" : '0*$' >/dev/null; then
info "The branch '$1' is new..."
else
# updating -- make sure it is a fast-forward
mb=$(git-merge-base "$2" "$3")
case "$mb,$2" in
"$2,$mb") info "Update is fast-forward" ;;
*) noff=y; info "This is not a fast-forward update.";;
esac
fi
;;
*)
deny >/dev/null \
"Branch is not under refs/heads or refs/tags. What are you trying to do?"
;;
esac
# Implement per-branch controls based on username
allowed_users_file=$GIT_DIR/info/allowed-users
username=$(id -u -n)
info "The user is: '$username'"
if test -f "$allowed_users_file"
then
rc=$(cat $allowed_users_file | grep -v '^#' | grep -v '^$' |
while read heads user_patterns
do
# does this rule apply to us?
head_pattern=${heads#+}
matchlen=$(expr "$1" : "${head_pattern#+}")
test "$matchlen" = ${#1} || continue
# if non-ff, $heads must be with the '+' prefix
test -n "$noff" &&
test "$head_pattern" = "$heads" && continue
info "Found matching head pattern: '$head_pattern'"
for user_pattern in $user_patterns; do
info "Checking user: '$username' against pattern: '$user_pattern'"
matchlen=$(expr "$username" : "$user_pattern")
if test "$matchlen" = "${#username}"
then
grant "Allowing user: '$username' with pattern: '$user_pattern'"
fi
done
deny "The user is not in the access list for this branch"
done
)
case "$rc" in
grant) grant >/dev/null "Granting access based on $allowed_users_file" ;;
deny) deny >/dev/null "Denying access based on $allowed_users_file" ;;
*) ;;
esac
fi
allowed_groups_file=$GIT_DIR/info/allowed-groups
groups=$(id -G -n)
info "The user belongs to the following groups:"
info "'$groups'"
if test -f "$allowed_groups_file"
then
rc=$(cat $allowed_groups_file | grep -v '^#' | grep -v '^$' |
while read heads group_patterns
do
# does this rule apply to us?
head_pattern=${heads#+}
matchlen=$(expr "$1" : "${head_pattern#+}")
test "$matchlen" = ${#1} || continue
# if non-ff, $heads must be with the '+' prefix
test -n "$noff" &&
test "$head_pattern" = "$heads" && continue
info "Found matching head pattern: '$head_pattern'"
for group_pattern in $group_patterns; do
for groupname in $groups; do
info "Checking group: '$groupname' against pattern: '$group_pattern'"
matchlen=$(expr "$groupname" : "$group_pattern")
if test "$matchlen" = "${#groupname}"
then
grant "Allowing group: '$groupname' with pattern: '$group_pattern'"
fi
done
done
deny "None of the user's groups are in the access list for this branch"
done
)
case "$rc" in
grant) grant >/dev/null "Granting access based on $allowed_groups_file" ;;
deny) deny >/dev/null "Denying access based on $allowed_groups_file" ;;
*) ;;
esac
fi
deny >/dev/null "There are no more rules to check. Denying access"
With this hook in place, you then give particular users or groups to make changes to the repository. Anyone else who can see it has read-only access.
This uses two files,
$GIT_DIR/info/allowed-users
andallowed-groups
, to describe which heads can be pushed into by whom. The format of each file would look like this:refs/heads/master junio +refs/heads/pu junio refs/heads/cogito$ pasky refs/heads/bw/.* linus refs/heads/tmp/.* .* refs/tags/v[0-9].* junio
With this, Linus can push or create
bw/penguin
orbw/zebra
orbw/panda
branches, Pasky can do onlycogito
, and JC can domaster
andpu
branches and make versioned tags. And anybody can dotmp/blah
branches. The '+' sign at thepu
record means that JC can make non-fast-forward pushes on it.
If this person doesn't already have access to the host where your repository lives, maybe that person should have only git-shell
access rather than unrestricted access. Create a special-purpose git user and in ~git/.ssh/authorized_keys
, add the outsider's SSH key in the following form. Note that the key should be on one long line, but I've wrapped it below to aid presentation.
no-agent-forwarding,no-port-forwarding,no-pty,no-X11-forwarding, command="env myorg_git_user=joeuser /usr/local/bin/git-shell -c \"${SSH_ORIGINAL_COMMAND:-}\"" ssh-rsa AAAAB3...2iQ== joeuser@foo.invalid
Depending on your local setup, you may need to adjust the path to git-shell
. Remember that sshd
is highly paranoid about permissions of the .ssh
directory, so turn off its group-write bits and all files beneath it.
Funneling everyone through the git user means you need to be able tell people apart, and this is the purpose of the myorg_git_user
environment variable. Instead of relying on an unconditional username=$(id -u -n)
, tweak your update hook to use it:
# Implement per-branch controls based on username
allowed_users_file=$GIT_DIR/info/allowed-users
if [ -z "$myorg_git_user" ]; then
username=$(id -u -n)
else
username=$myorg_git_user
fi
info "The user is: '$username'"
With this setup, your friend with readonly access will clone with a command resembling the one below. The particular path will depend on your setup. To make the nice path work, either relocate your repository to the git user's home directory or create a symlink that points to it.
$ git clone git@blankman.com.invalid:coolproject.git
but won't be able to make updates.
$ git push origin mybranch Total 0 (delta 0), reused 0 (delta 0) remote: error: hook declined to update refs/heads/mybranch To git@blankman.com.invalid:coolproject.git ! [remote rejected] mybranch -> mybranch (hook declined) error: failed to push some refs to 'git@blankman.com.invalid:coolproject.git'
You said you're working in a team environment, so I assume your central repository was created with the --shared
option. (See core.sharedRepository
in the git config documentation and --shared
in the git init documentation.) Make sure the new git user is a member of the system group that gives all of you access to your central repository.
Like Fred Frodo said, you can put your private exclude rules in the .git/info/exclude
of the repository.
If you want to apply the same exclude rules to all the repositories on your machine, you can add the following to the .gitconfig
file in your user directory.
[core]
excludesfile = /home/<myusername>/.gitexclude
Then add your exclude patterns to ~/.gitexclude
.
I know I am a little late to the conversation but you might want to consider using
git update-index --assume-unchanged [ FILE ]
As the git help document states:
When the "assume unchanged" bit is on, git stops checking the working tree files for possible modifications, so you need to manually unset the bit to tell git when you change the working tree file...
Emphasis mine. It goes on to say
This option can be ... used as a coarse file-level mechanism to ignore uncommitted changes in tracked files (akin to what .gitignore does for untracked files). Git will fail (gracefully) in case it needs to modify this file in the index e.g. when merging in a commit; thus, in case the assumed-untracked file is changed upstream, you will need to handle the situation manually.
So just keep in mind that you will have to be aware of any upstream changes made to these files.
In the event that you want to start tracking the file again all you have to do is use
git update-index --no-assume-unchange [ FILE ]
I hope this helps any future viewers of this post.
For the ssh part, you should consider using Gitolite (a replacement for gitosis).
.git/info/exclude
. See gitignore(5).