From 509c73b8883474a74df3369bdb00af878f0ef43e Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 16 Jul 2010 23:01:34 +0530 Subject: [PATCH] gitweb/daemon now work for wild repos also (thanks to Kevin Fleming for the need/use case) TODO: tests TODO: proper documentation; meanwhile, just read this: - you can give gitweb and daemon read rights to wild card repos also, and it'll all just work -- when a new repo is 'C'reated, it'll pick up those rights etc - you can assign descriptions (and owners) to individual repos as before, except now you can assign them to repos that actually were created from wild card patterns. So for example, you can define rules for repo foo/..* and then assign descriptions like foo/repo1 = "repo one" foo/repo2 = "repo two" foo/dil "scott" = "scott's dilbert repo" However, this only works for repos that already exist, and only when you push the admin repo. Thumb rule: have the user create his wild repo, *then* add and push the admin config file with the description. Not the other way around. implementation notes: - wildcard support for git config revamped, refactored... it's not just git config that needs wildcard support. daemon and gitweb access also will be needing it soon, so we start by factoring out the part that finds the "pattern" given a "real" repo name. - GL_NO_DAEMON_NO_GITWEB now gates more than just those two things; see doc/big-config.mkd for details - we trawl through $GL_REPO_BASE_ABS *once* only, collecting repo names and tying them to either the same name or to a wild pattern that the repo name was created from - nice little subs to setup gitweb, daemon, and git config - god bless $GL_REPOPATT and the day I decided to set that env var whenever a user hits a wild repo in any way :-) - the code in gl-compile-conf is very simple now. Much nicer than before --- src/gitolite.pm | 119 ++++++++++++++++++++++++++++-------- src/gl-auth-command | 7 ++- src/gl-compile-conf | 146 ++++++++++++++++++++------------------------ 3 files changed, 165 insertions(+), 107 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index be59488..bf70db6 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -115,6 +115,43 @@ sub ln_sf } } +# collect repo patterns for all %repos + +# for each repo passed (actual repos only please!), use either its own name if +# it exists as is in the repos hash, or find and use the pattern that matches + +sub collect_repo_patts +{ + my $repos_p = shift; + my %repo_patts = (); + + wrap_chdir("$ENV{GL_REPO_BASE_ABS}"); + for my $repo (`find . -type d -name "*.git"`) { + chomp ($repo); + $repo =~ s(\./(.*)\.git$)($1); + # if its non-wild that's all you need + if ($repos_p->{$repo}) { + $repo_patts{$repo} = $repo; + } else { + # otherwise it gets a wee bit complicated ;-) + chomp (my $creator = `cat $repo.git/gl-creater`); + for my $key (keys %$repos_p) { + my $key2 = $key; + # subst $creator in the copy with the creator name + $key2 =~ s/\$creator/$creator/g; + # match the new key against $repo + if ($repo =~ /^$key2$/) { + # and if it matched you're done for this $repo + $repo_patts{$repo} = $key; + last; + } + } + } + } + + return %repo_patts; +} + # ---------------------------------------------------------------------------- # where is the rc file hiding? @@ -243,38 +280,17 @@ sub get_set_desc } } +# ---------------------------------------------------------------------------- +# IMPORTANT NOTE: next 3 subs (setup_*) assume $PWD is the bare repo itself +# ---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # set/unset repo configs # ---------------------------------------------------------------------------- sub setup_repo_configs { - my ($repo_config_p, $repo, $wild) = @_; - - wrap_chdir("$ENV{GL_REPO_BASE_ABS}/$repo.git"); - - # prep for wild and non-wild cases separately - my $repo_patt = ''; # actually "repo or repo_pattern" - if ($wild) { - chomp (my $creator = `cat gl-creater`); - - # loop each key of %repo_config and make a copy - for my $key (keys %$repo_config_p) { - my $key2 = $key; - # subst $creator in the copy with the creator name - $key2 =~ s/\$creator/$creator/g; - # match the new key against $repo - if ($repo =~ /^$key2$/) { - # if it matches, proceed - $repo_patt = $key; - last; - } - } - } else { - $repo_patt ||= $repo; # just use the repo itself... - # XXX TODO there is a remote possibility of errors if you have a - # normal repo that fits a wild pattern; needs some digging into... - } + my ($repo, $repo_patt, $repo_config_p) = @_; while ( my ($key, $value) = each(%{ $repo_config_p->{$repo_patt} }) ) { if ($value) { @@ -286,6 +302,57 @@ sub setup_repo_configs } } +# ---------------------------------------------------------------------------- +# set/unset daemon access +# ---------------------------------------------------------------------------- + +my $export_ok = "git-daemon-export-ok"; +sub setup_daemon_access +{ + my ($repo, $allowed) = @_; + + if ($allowed) { + system("touch $export_ok"); + } else { + unlink($export_ok); + } +} + +# ---------------------------------------------------------------------------- +# set/unset gitweb access +# ---------------------------------------------------------------------------- + +my $desc_file = "description"; +sub setup_gitweb_access +# this also sets "owner" for gitweb, by the way +{ + my ($repo, $allowed, $desc, $owner) = @_; + + if ($allowed) { + if ($desc) { + open(DESC, ">", $desc_file); + print DESC $desc . "\n"; + close DESC; + } + if ($owner) { + # set the repository owner + system("git", "config", "gitweb.owner", $owner); + } else { + # remove the repository owner setting + system("git config --unset-all gitweb.owner 2>/dev/null"); + } + } else { + unlink $desc_file; + system("git config --unset-all gitweb.owner 2>/dev/null"); + } + + # if there are no gitweb.* keys set, remove the section to keep the config file clean + my $keys = `git config --get-regexp '^gitweb\\.' 2>/dev/null`; + if (length($keys) == 0) { + system("git config --remove-section gitweb 2>/dev/null"); + } +} + # ---------------------------------------------------------------------------- # parse the compiled acl # ---------------------------------------------------------------------------- diff --git a/src/gl-auth-command b/src/gl-auth-command index aa4c5eb..bd7737a 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -23,7 +23,7 @@ use warnings; # ---------------------------------------------------------------------------- # these are set by the "rc" file -our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR, $RSYNC_BASE, $HTPASSWD_FILE, $GL_WILDREPOS, $GL_WILDREPOS_DEFPERMS, $GL_ADC_PATH, $SVNSERVE, $GL_SLAVE_MODE); +our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR, $RSYNC_BASE, $HTPASSWD_FILE, $GL_WILDREPOS, $GL_WILDREPOS_DEFPERMS, $GL_ADC_PATH, $SVNSERVE, $PROJECTS_LIST, $GL_SLAVE_MODE); # and these are set by gitolite.pm our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT, $REPOPATT_PATT); our %repos; @@ -207,7 +207,10 @@ if ($perm =~ /C/) { # it was missing, and you have create perms wrap_chdir("$ENV{GL_REPO_BASE_ABS}"); new_repo($repo, "$GL_ADMINDIR/hooks/common", $user); - &setup_repo_configs(\%repo_config, $repo, 1); + &setup_repo_configs($repo, $ENV{GL_REPOPATT}, \%repo_config); + &setup_daemon_access($repo, $repos{$ENV{GL_REPOPATT}}{'R'}{'daemon'} || ''); + &setup_gitweb_access($repo, $repos{$ENV{GL_REPOPATT}}{'R'}{'gitweb'} || '', '', ''); + system("echo $repo.git >> $PROJECTS_LIST"); wrap_chdir($ENV{HOME}); } diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 25b4a67..8926a91 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -470,94 +470,82 @@ unless ($GL_NO_CREATE_REPOS) { } # ---------------------------------------------------------------------------- -# update repo configurations +# collect repo_patt for each actual repo # ---------------------------------------------------------------------------- -# no gating required for this. If you don't have any "config" lines it won't -# run anyway. An example of a config line could be: -# config hooks.emailprefix = "[foo]" +# go through each actual repo on disk, and match it to either its own name in +# the config (non-wild) or a wild pattern that matches it. Lots of things +# later will need this correspondence so we may as well snarf it in one shot -for my $repo (keys %repo_config) { - &setup_repo_configs(\%repo_config, $repo) if $repo =~ $REPONAME_PATT; -} -# wild -if (%repo_config and $GL_WILDREPOS and $GL_GITCONFIG_WILD) { - wrap_chdir("$ENV{GL_REPO_BASE_ABS}"); - for my $repo (`find . -type d -name "*.git"`) { - chomp ($repo); - next unless -f "$ENV{GL_REPO_BASE_ABS}/$repo/gl-creater"; # all/only wild repos have this file +my %repo_patts = (); +%repo_patts = &collect_repo_patts(\%repos) unless $GL_NO_DAEMON_NO_GITWEB; - # $repo will look like "./foo/bar.git", make it "foo/bar" before - # calling the repo config setup sub - $repo =~ s(\./(.*)\.git$)($1); - &setup_repo_configs(\%repo_config, $repo, 1); +# NOTE: we're overloading GL_NO_DAEMON_NO_GITWEB to mean "no git config" also. +# In fact anything that requires trawling through the existing repos doing +# stuff to all of them is skipped if this variable is set. This is primarily +# for the Fedora folks, but it should be useful for anyone who has a huge set +# of repos and wants to manage gitweb/daemon/etc access via other means (they +# typically have the whole thing controlled by a web-app and a database +# anyway, and gitolite is only doing the access control and nothing more). + +# ---------------------------------------------------------------------------- +# various updates to all real repos +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# update repo configurations, gitweb description, daemon export-ok, etc +# ---------------------------------------------------------------------------- + +# all these require a "chdir" to the repo, so we club them for efficiency + +my %projlist = (); + +# for each real repo (and remember this will be empty, thus skipping all this, +# if $GL_NO_DAEMON_NO_GITWEB is on!) +for my $repo (keys %repo_patts) { + my $repo_patt = $repo_patts{$repo}; # if non-wild, $repo_patt will be eq $repo anyway + + wrap_chdir("$ENV{GL_REPO_BASE_ABS}/$repo.git"); + + # git config + if ($repo_patt eq $repo or $GL_GITCONFIG_WILD) { + # erm, what that means is that it's either a non-wild repo being + # config'd or a wild one but gitconfig is allowed on wilds. + # XXX do we really need GL_GITCONFIG_WILD now? It was meant to be + # only an efficiency thing, but that was before this whole revamp; + # we already trawl through $REPO_BASE exactly once now anyway! + # ...need to think about this + &setup_repo_configs($repo, $repo_patt, \%repo_config) if $repo_config{$repo_patt}; + } + + # daemon is easy + &setup_daemon_access($repo, $repos{$repo_patt}{'R'}{'daemon'} || ''); + + # gitweb is a little more complicated. Here're some notes: + # - "setup_gitweb_access" also sets "owner", despite the name + # - specifying a description also counts as enabling gitweb + # - description and owner are not specified for wildrepos; they're + # specified for *actual* repos, even if the repo was created by a + # wild card spec and "C" permissions. If you see the + # conf/example.conf file, you will see that repo owner/desc don't go + # into the "repo foo" section; they're essentialy independent. + # Anyway, I believe it doesn't make sense to have all wild repos + # (for some pattern) to have the same description and owner. + if ($repos{$repo_patt}{'R'}{'gitweb'} or $desc{"$repo.git"}) { + $projlist{"$repo.git"} = 1; + &setup_gitweb_access($repo, 1, $desc{"$repo.git"} || '', $owner{"$repo.git"} || ''); + } else { + &setup_gitweb_access($repo, 0, '', ''); } } -# ---------------------------------------------------------------------------- -# handle gitweb and daemon -# ---------------------------------------------------------------------------- - -# I just assume you'll never have any *real* users called "gitweb" or "daemon" -# :-) These are now "pseduo users" -- giving them "R" access to a repo is all -# you have to do - -wrap_chdir("$ENV{GL_REPO_BASE_ABS}"); - -unless ($GL_NO_DAEMON_NO_GITWEB) { - # daemons first... - for my $repo (sort keys %repos) { - next unless $repo =~ $REPONAME_PATT; - next if $repo =~ m(^\@|EXTCMD/); # these are not real repos - my $export_ok = "$repo.git/git-daemon-export-ok"; - if ($repos{$repo}{'R'}{'daemon'}) { - system("touch $export_ok"); - } else { - unlink($export_ok); - } - } - - my %projlist = (); - # ...then gitwebs - for my $repo (sort keys %repos) { - next unless $repo =~ $REPONAME_PATT; - next if $repo =~ m(^\@|EXTCMD/); # these are not real repos - my $desc_file = "$repo.git/description"; - # note: having a description also counts as enabling gitweb - if ($repos{$repo}{'R'}{'gitweb'} or $desc{"$repo.git"}) { - $projlist{"$repo.git"} = 1; - # add the description file; no messages to user or error checking :) - $desc{"$repo.git"} and open(DESC, ">", $desc_file) and print DESC $desc{"$repo.git"} . "\n" and close DESC; - if ($owner{"$repo.git"}) { - # set the repository owner - system("git", "--git-dir=$repo.git", "config", "gitweb.owner", $owner{"$repo.git"}); - } else { - # remove the repository owner setting - system("git --git-dir=$repo.git config --unset-all gitweb.owner 2>/dev/null"); - } - } else { - # delete the description file; no messages to user or error checking :) - unlink $desc_file; - # remove the repository owner setting - system("git --git-dir=$repo.git config --unset-all gitweb.owner 2>/dev/null"); - } - - # unless there are other gitweb.* keys set, remove the section to keep the - # config file clean - my $keys = `git --git-dir=$repo.git config --get-regexp '^gitweb\\.' 2>/dev/null`; - if (length($keys) == 0) { - system("git --git-dir=$repo.git config --remove-section gitweb 2>/dev/null"); - } - } - - # update the project list - my $projlist_fh = wrap_open( ">", $PROJECTS_LIST); - for my $proj (sort keys %projlist) { - print $projlist_fh "$proj\n"; - } - close $projlist_fh; +# write out the project list +my $projlist_fh = wrap_open( ">", $PROJECTS_LIST); +for my $proj (sort keys %projlist) { + print $projlist_fh "$proj\n"; } +close $projlist_fh; # ---------------------------------------------------------------------------- # "compile" ssh authorized_keys