From 901a5f722049c6316d137688756613f06a57d423 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 23 Aug 2009 07:09:59 +0530 Subject: [PATCH 001/637] gl-add-auth-keys; first version, pretty much done --- gl-add-auth-keys | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100755 gl-add-auth-keys diff --git a/gl-add-auth-keys b/gl-add-auth-keys new file mode 100755 index 0000000..dfadc1a --- /dev/null +++ b/gl-add-auth-keys @@ -0,0 +1,71 @@ +#!/bin/bash + +# === add-auth-keys === +# refreshes ~/.ssh/authorized_keys from the list of pub-keys + +# part of the gitosis-lite (GL) suite + +# how run: manual, by GL admin +# when: anytime a pubkey is added/deleted +# (i.e., contents of ~/.gitosis-lite/pubkeys change) +# input: ~/.gitosis-lite/pubkeys +# output: ~/.ssh/authorized_keys +# security: +# - touches a very critical system file that manages the restrictions on +# incoming users. Be sure to audit AUTH_COMMAND and AUTH_OPTIONS (see +# below) on any change to this script +# - no security checks within program. The GL admin runs this manually + +# robustness: +# - if the "start" line exists, but the "end" line does not, you lose the +# rest of the existing authkey file. In general, "don't do that (TM)", +# but we do have a "vim -d" popping up so you can see the changes being +# made, just in case... + +# other notes: +# - you do NOT need to run this for permission changes within +# gitosis-lite.conf, (like giving an *existing* user new rights) +# - keys are added/deleted from the keystore **manually**, and all keys +# are named "name.pub" + +# command and options for authorized_keys +AUTH_COMMAND=~/.gitosis-lite/myecho +AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty" + +# save existing authkeys minus the GL-added stuff +sed -e '/^# gitosis-lite start/,/^# gitosis-lite end/d' \ + < ~/.ssh/authorized_keys \ + > ~/.ssh/new_authkeys + +# add our "start" line, each key on its own line (prefixed by command and +# options, in the standard ssh authorized_keys format), then the "end" line. +echo "# gitosis-lite start" >> ~/.ssh/new_authkeys +cd ~/.gitosis-lite/pubkeys +for i in *.pub +do + j=${i%.pub} + echo -n "command=\"$AUTH_COMMAND $j\",$AUTH_OPTIONS " + cat $i +done >> ~/.ssh/new_authkeys +echo "# gitosis-lite end" >> ~/.ssh/new_authkeys + +# just so you can see what changes are being made +vim -d ~/.ssh/authorized_keys ~/.ssh/new_authkeys + +# all done; overwrite the file (use cat to avoid perm changes) +cat ~/.ssh/new_authkeys > ~/.ssh/authorized_keys +rm ~/.ssh/new_authkeys + +# if the gl admin directory (~/.gitosis-lite) is itself a git repo, do an +# autocheckin. nothing fancy; this is a "just in case" type of thing. +cd ~/.gitosis-lite +if [[ -d .git ]] +then + git add -A pubkeys # stage all changes in pubkeys + if ! git diff --cached --quiet # and if there are any + then + echo pubkeys changed # create a commit message + echo + git diff --cached --name-status + fi | git commit -F - # and commit +fi From 40dbada4863c2ed6cbda4a3bdf260cf9221d0d52 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 23 Aug 2009 11:05:14 +0530 Subject: [PATCH 002/637] shell? perl? schizophrenia? fix it NOW dammit :) --- gl-add-auth-keys | 71 --------------------------------------- gl-compile-conf | 86 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 71 deletions(-) delete mode 100755 gl-add-auth-keys create mode 100755 gl-compile-conf diff --git a/gl-add-auth-keys b/gl-add-auth-keys deleted file mode 100755 index dfadc1a..0000000 --- a/gl-add-auth-keys +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/bash - -# === add-auth-keys === -# refreshes ~/.ssh/authorized_keys from the list of pub-keys - -# part of the gitosis-lite (GL) suite - -# how run: manual, by GL admin -# when: anytime a pubkey is added/deleted -# (i.e., contents of ~/.gitosis-lite/pubkeys change) -# input: ~/.gitosis-lite/pubkeys -# output: ~/.ssh/authorized_keys -# security: -# - touches a very critical system file that manages the restrictions on -# incoming users. Be sure to audit AUTH_COMMAND and AUTH_OPTIONS (see -# below) on any change to this script -# - no security checks within program. The GL admin runs this manually - -# robustness: -# - if the "start" line exists, but the "end" line does not, you lose the -# rest of the existing authkey file. In general, "don't do that (TM)", -# but we do have a "vim -d" popping up so you can see the changes being -# made, just in case... - -# other notes: -# - you do NOT need to run this for permission changes within -# gitosis-lite.conf, (like giving an *existing* user new rights) -# - keys are added/deleted from the keystore **manually**, and all keys -# are named "name.pub" - -# command and options for authorized_keys -AUTH_COMMAND=~/.gitosis-lite/myecho -AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty" - -# save existing authkeys minus the GL-added stuff -sed -e '/^# gitosis-lite start/,/^# gitosis-lite end/d' \ - < ~/.ssh/authorized_keys \ - > ~/.ssh/new_authkeys - -# add our "start" line, each key on its own line (prefixed by command and -# options, in the standard ssh authorized_keys format), then the "end" line. -echo "# gitosis-lite start" >> ~/.ssh/new_authkeys -cd ~/.gitosis-lite/pubkeys -for i in *.pub -do - j=${i%.pub} - echo -n "command=\"$AUTH_COMMAND $j\",$AUTH_OPTIONS " - cat $i -done >> ~/.ssh/new_authkeys -echo "# gitosis-lite end" >> ~/.ssh/new_authkeys - -# just so you can see what changes are being made -vim -d ~/.ssh/authorized_keys ~/.ssh/new_authkeys - -# all done; overwrite the file (use cat to avoid perm changes) -cat ~/.ssh/new_authkeys > ~/.ssh/authorized_keys -rm ~/.ssh/new_authkeys - -# if the gl admin directory (~/.gitosis-lite) is itself a git repo, do an -# autocheckin. nothing fancy; this is a "just in case" type of thing. -cd ~/.gitosis-lite -if [[ -d .git ]] -then - git add -A pubkeys # stage all changes in pubkeys - if ! git diff --cached --quiet # and if there are any - then - echo pubkeys changed # create a commit message - echo - git diff --cached --name-status - fi | git commit -F - # and commit -fi diff --git a/gl-compile-conf b/gl-compile-conf new file mode 100755 index 0000000..a954744 --- /dev/null +++ b/gl-compile-conf @@ -0,0 +1,86 @@ +#!/usr/bin/perl -w + +use strict; + +# === add-auth-keys === +# refreshes ~/.ssh/authorized_keys from the list of pub-keys + +# part of the gitosis-lite (GL) suite + +# how run: manual, by GL admin +# when: anytime a pubkey is added/deleted +# (i.e., contents of ~/.gitosis-lite/keydir change) +# input: ~/.gitosis-lite/keydir +# output: ~/.ssh/authorized_keys +# security: +# - touches a very critical system file that manages the restrictions on +# incoming users. Be sure to audit AUTH_COMMAND and AUTH_OPTIONS (see +# below) on any change to this script +# - no security checks within program. The GL admin runs this manually + +# robustness: +# - if the "start" line exists, but the "end" line does not, you lose the +# rest of the existing authkey file. In general, "don't do that (TM)", +# but we do have a "vim -d" popping up so you can see the changes being +# made, just in case... + +# other notes: +# - you do NOT need to run this for permission changes within +# gitosis-lite.conf, (like giving an *existing* user new rights) +# - keys are added/deleted from the keystore **manually**, and all keys +# are named "name.pub" + +# command and options for authorized_keys +our $AUTH_COMMAND=$ENV{HOME} . "/.gitosis-lite/gl-auth-command"; +our $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; + +# quick subroutines +sub my_chdir +{ + chdir($_[0]) or die "chdir $_[0] failed: $!"; +} + +open(INF, "<", $ENV{HOME} . "/.ssh/authorized_keys") or die "open old authkeys failed: $!"; +open(OUT, ">", $ENV{HOME} . "/.ssh/new_authkeys") or die "open new authkeys failed: $!"; +# save existing authkeys minus the GL-added stuff +while () +{ + print OUT unless (/^# gitosis-lite start/../^# gitosis-lite end/); +} + +# add our "start" line, each key on its own line (prefixed by command and +# options, in the standard ssh authorized_keys format), then the "end" line. +print OUT "# gitosis-lite start\n"; +my_chdir($ENV{HOME} . "/.gitosis-lite/keydir"); +for my $pubkey (glob("*.pub")) +{ + my $user = $pubkey; $user =~ s/\.pub$//; + print OUT "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS "; + print OUT `cat $pubkey`; +} +print OUT "# gitosis-lite end\n"; +close(OUT); + +# check what changes are being made; just a comfort factor +system("vim -d ~/.ssh/authorized_keys ~/.ssh/new_authkeys"); + +# all done; overwrite the file (use cat to avoid perm changes) +system("cat ~/.ssh/new_authkeys > ~/.ssh/authorized_keys"); +system("rm ~/.ssh/new_authkeys"); + +# if the gl admin directory (~/.gitosis-lite) is itself a git repo, do an +# autocheckin. nothing fancy; this is a "just in case" type of thing. +my_chdir($ENV{HOME} . "/.gitosis-lite"); +if (-d ".git") +{ + system("git add -A keydir"); # stage all changes in keydir + if (! system("git diff --cached --quiet") ) + # and if there are any + { + open(COMMIT, "|-", "git commit -F -") + or die "pipe commit failed: $!"; + print COMMIT "keydir changed\n\n"; + print COMMIT `git diff --cached --name-status`; + close(COMMIT) or die "close commit failed: $!"; + } +} From d45a4e3972a2abc449567534b38f13bb529886ee Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 23 Aug 2009 11:29:07 +0530 Subject: [PATCH 003/637] added example.conf --- example.conf | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 example.conf diff --git a/example.conf b/example.conf new file mode 100644 index 0000000..f72f470 --- /dev/null +++ b/example.conf @@ -0,0 +1,61 @@ +# example conf file for gitosis-lite + +# objectives, over and above gitosis: +# - simpler syntax +# - no gitweb/daemon control +# - allows ff/non-ff control +# - allows branch level control + +# ---------------------------------------------------------------------------- +# user groups; syntax: +# @groupname = username [...] +# too many users? just add more such lines +# (they accumulate, like squid ACLs) + +@customers = cust1 cust2 +@customers = cust99 +@staff = me alice +@staff = bob +@secret_staff = bruce whitfield martin + +# ---------------------------------------------------------------------------- +# each section pertains to one or more repo(s); syntax: +# repo reponame [...] +# (R|RW|RW+) [list of ref names] = [list of users] + +# - RW+ means non-ff push is allowed +# - if no ref name exists, the rule applies to all refs +# - ref names are perl regex patterns +# - suffixed by "$" +# - prefixed by "refs/heads/" if it doesn't start with "refs/" +# (i.e., tags have to be explicitly named as refs/tags/pattern) + +# - the order of entries matters: the first ref+action line (for the repo) +# that matches is what counts. If the user is listed, the action is +# allowed, otherwise it is rejected. + +# - list of users can use any group name defined earlier +# - "@all" is a special, predefined, groupname + +# anyone can play in the sandbox +repo sandbox + RW+ = @all + +# my repo and alice's repo have the same memberships and access, so we just +# club them +repo myrepo alicerepo + RW+ = me, alice + R = bob, eve + +repo custrepo + RW = @customers + R = @staff + +# idea for the tags syntax shamelessly copied from git.git +# Documentation/howto/update-hook-example.txt :) +repo secret + RW+ pu = bruce + RW master next = bruce + RW refs/tags/v[0-9].* = bruce + RW tmp/.* = @secret_staff + R = @secret_staff From 930fbafed7532744b20fdb2459e6e5aa30537b20 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 23 Aug 2009 13:44:46 +0530 Subject: [PATCH 004/637] example conf expanded a bit --- example.conf | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/example.conf b/example.conf index f72f470..b4134f6 100644 --- a/example.conf +++ b/example.conf @@ -1,5 +1,7 @@ # example conf file for gitosis-lite +# comments in the normal shell-ish style; no surprises there + # objectives, over and above gitosis: # - simpler syntax # - no gitweb/daemon control @@ -14,8 +16,10 @@ @customers = cust1 cust2 @customers = cust99 +@interns = indy james @staff = me alice -@staff = bob +# you can nest groups; they'll be fully expanded in actual use +@staff = bob @interns @secret_staff = bruce whitfield martin # ---------------------------------------------------------------------------- @@ -44,8 +48,8 @@ repo sandbox # my repo and alice's repo have the same memberships and access, so we just # club them repo myrepo alicerepo - RW+ = me, alice - R = bob, eve + RW+ = me alice + R = bob eve repo custrepo RW = @customers From dc4193e633a2e781e3d9a63ec664e175681cd660 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 23 Aug 2009 13:46:45 +0530 Subject: [PATCH 005/637] gl-compile-conf now has "compilation" of the conf file also --- gl-compile-conf | 132 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 122 insertions(+), 10 deletions(-) diff --git a/gl-compile-conf b/gl-compile-conf index a954744..0d2283b 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -1,45 +1,85 @@ #!/usr/bin/perl -w use strict; +use Data::Dumper; # === add-auth-keys === -# refreshes ~/.ssh/authorized_keys from the list of pub-keys # part of the gitosis-lite (GL) suite +# (1) - "compiles" ~/.ssh/authorized_keys from the list of pub-keys +# (2) - also "compiles" the user-friendly GL conf file into something easier +# to parse. We're doing this because both the gl-auth-command and the +# (gl-)update hook need this, and it seems easier to do this than +# replicate the parsing code in both those places. As a bonus, it's +# probably more efficient. + # how run: manual, by GL admin -# when: anytime a pubkey is added/deleted -# (i.e., contents of ~/.gitosis-lite/keydir change) -# input: ~/.gitosis-lite/keydir -# output: ~/.ssh/authorized_keys +# when: +# - anytime a pubkey is added/deleted (i.e., contents of +# ~/.gitosis-lite/keydir change) +# - anytime gitosis-lite.conf is changed +# input: +# - ~/.gitosis-lite/gitosis-lite.conf +# - ~/.gitosis-lite/keydir +# output: +# - ~/.ssh/authorized_keys +# - ~/.ssh/gitosis-lite.conf-compiled.pm # security: # - touches a very critical system file that manages the restrictions on # incoming users. Be sure to audit AUTH_COMMAND and AUTH_OPTIONS (see # below) on any change to this script # - no security checks within program. The GL admin runs this manually -# robustness: +# warnings: # - if the "start" line exists, but the "end" line does not, you lose the # rest of the existing authkey file. In general, "don't do that (TM)", # but we do have a "vim -d" popping up so you can see the changes being # made, just in case... # other notes: -# - you do NOT need to run this for permission changes within -# gitosis-lite.conf, (like giving an *existing* user new rights) # - keys are added/deleted from the keystore **manually**, and all keys -# are named "name.pub" +# are named "name.pub". Keep the names simple. # command and options for authorized_keys our $AUTH_COMMAND=$ENV{HOME} . "/.gitosis-lite/gl-auth-command"; our $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; +our %groups = (); +our %repos = (); + # quick subroutines sub my_chdir { chdir($_[0]) or die "chdir $_[0] failed: $!"; } +sub expand_userlist +{ + my @list = @_; + my @new_list = (); + + for my $item (@list) + { + if ($item =~ /^@/) # nested group + { + die "undefined group $item" unless $groups{$item}; + # add those names to the list + push @new_list, @{ $groups{$item} }; + } + else + { + push @new_list, $item; + } + } + + return @new_list; +} + +# ---------------------------------------------------------------------------- +# "compile" ssh authorized_keys +# ---------------------------------------------------------------------------- + open(INF, "<", $ENV{HOME} . "/.ssh/authorized_keys") or die "open old authkeys failed: $!"; open(OUT, ">", $ENV{HOME} . "/.ssh/new_authkeys") or die "open new authkeys failed: $!"; # save existing authkeys minus the GL-added stuff @@ -62,7 +102,7 @@ print OUT "# gitosis-lite end\n"; close(OUT); # check what changes are being made; just a comfort factor -system("vim -d ~/.ssh/authorized_keys ~/.ssh/new_authkeys"); +# system("vim -d ~/.ssh/authorized_keys ~/.ssh/new_authkeys"); # all done; overwrite the file (use cat to avoid perm changes) system("cat ~/.ssh/new_authkeys > ~/.ssh/authorized_keys"); @@ -84,3 +124,75 @@ if (-d ".git") close(COMMIT) or die "close commit failed: $!"; } } + +# ---------------------------------------------------------------------------- +# "compile" GL conf +# ---------------------------------------------------------------------------- + +open(INF, "<", $ENV{HOME} . "/.gitosis-lite/gitosis-lite.conf") + or die "open GL conf failed: $!"; +open(OUT, ">", $ENV{HOME} . "/.ssh/gitosis-lite.conf-compiled.pm") + or die "open GL conf compiled failed: $!"; + +# the syntax is fairly simple, so we parse it inline + +my @repos; +while () +{ + # normalise whitespace; keeps later regexes very simple + s/=/ = /; + s/\s+/ /g; + s/^ //; + s/ $//; + # kill comments + s/#.*//; + # and blank lines + next unless /\S/; + + + # user groups + if (/^(@\S+) = (.*)/) + { + push @{ $groups{$1} }, expand_userlist( split(' ', $2) ); + } + # repo(s) + elsif (/^repo (.*)/) + { + @repos = split(' ', $1); + } + # actual permission line + elsif (/^(R|RW|RW\+) (.* )?= (.+)/) + { + my @perms = split //, $1; + my @refs = split ' ', $2 if $2; + my @users = split ' ', $3; + + # if no ref is given, this PERM applies to all refs + @refs = qw(refs/.*) unless @refs; + # fully qualify refs that dont start with "refs/"; prefix them with + # "refs/heads/" + @refs = map { m(^refs/) or s(^)(refs/heads/) } @refs; + + # expand the user list, unless it is just "@all" + @users = expand_userlist ( @users ) + unless (@users == 1 and $users[0] eq '@all'); + + # ok, we can finally populate the %repos hash + for my $repo (@repos) # each repo in the current stanza + { + for my $perm (@perms) + { + for my $ref (@refs) + { + for my $user (@users) + { + $repos{$repo}{$perm}{$ref}{$user} = 1; + } + } + } + } + } +} + +print OUT Data::Dumper->Dump([\%repos], [qw(*repos)]); +close(OUT); From 7d016908bd0a4ed08fcd6928411636a27071c463 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 23 Aug 2009 14:55:50 +0530 Subject: [PATCH 006/637] gl-compile-conf changed (see below) and "rc" file added - factored out all the pathnames etc to an rc - taught it to create repos that dont exist but are mentioned - promoted user up one level (moving ref down) because gl-auth needs it - REPO_BASE no longer contains $HOME so that has to be added in manually - little bugs here and there, like in @refs --- example.gitosis-lite.rc | 14 +++++++ gl-compile-conf | 82 ++++++++++++++++++++++++++++++----------- 2 files changed, 75 insertions(+), 21 deletions(-) create mode 100644 example.gitosis-lite.rc diff --git a/example.gitosis-lite.rc b/example.gitosis-lite.rc new file mode 100644 index 0000000..2e9db31 --- /dev/null +++ b/example.gitosis-lite.rc @@ -0,0 +1,14 @@ +# this is meant to be pulled into a perl program using "do" + +# gitosis-lite admin directory, files, etc +$GL_ADMINDIR=$ENV{HOME} . "/.gitosis-lite"; +$GL_CONF="$GL_ADMINDIR/gitosis-lite.conf"; +$GL_KEYDIR="$GL_ADMINDIR/keydir"; + # this one has to agree with the other programs, watch out: +$GL_CONF_COMPILED=$ENV{HOME} . "/.ssh/gitosis-lite.conf-compiled.pm"; + +# base directory for all the repos +$REPO_BASE="repositories"; + +# this should be the last line in this file +1; diff --git a/gl-compile-conf b/gl-compile-conf index 0d2283b..bcce972 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -16,15 +16,14 @@ use Data::Dumper; # how run: manual, by GL admin # when: -# - anytime a pubkey is added/deleted (i.e., contents of -# ~/.gitosis-lite/keydir change) +# - anytime a pubkey is added/deleted # - anytime gitosis-lite.conf is changed # input: -# - ~/.gitosis-lite/gitosis-lite.conf -# - ~/.gitosis-lite/keydir +# - GL_CONF (default: ~/.gitosis-lite/gitosis-lite.conf) +# - GL_KEYDIR (default: ~/.gitosis-lite/keydir) # output: -# - ~/.ssh/authorized_keys -# - ~/.ssh/gitosis-lite.conf-compiled.pm +# - ~/.ssh/authorized_keys (dictated by sshd) +# - GL_CONF_COMPILED (default: ~/.gitosis-lite/gitosis-lite.conf-compiled.pm) # security: # - touches a very critical system file that manages the restrictions on # incoming users. Be sure to audit AUTH_COMMAND and AUTH_OPTIONS (see @@ -37,18 +36,39 @@ use Data::Dumper; # but we do have a "vim -d" popping up so you can see the changes being # made, just in case... -# other notes: -# - keys are added/deleted from the keystore **manually**, and all keys -# are named "name.pub". Keep the names simple. +# ---------------------------------------------------------------------------- +# common definitions +# ---------------------------------------------------------------------------- + +our $GL_ADMINDIR; +our $GL_CONF; +our $GL_KEYDIR; +our $GL_CONF_COMPILED; +our $REPO_BASE; + +my $glrc = $ENV{HOME} . "/.gitosis-lite.rc"; +unless (my $ret = do $glrc) +{ + die "parse $glrc failed: $@" if $@; + die "couldn't do $glrc: $!" unless defined $ret; + die "couldn't run $glrc" unless $ret; +} + +# ---------------------------------------------------------------------------- +# definitions specific to this program +# ---------------------------------------------------------------------------- # command and options for authorized_keys -our $AUTH_COMMAND=$ENV{HOME} . "/.gitosis-lite/gl-auth-command"; +our $AUTH_COMMAND="$GL_ADMINDIR/gl-auth-command"; our $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; our %groups = (); our %repos = (); -# quick subroutines +# ---------------------------------------------------------------------------- +# subroutines +# ---------------------------------------------------------------------------- + sub my_chdir { chdir($_[0]) or die "chdir $_[0] failed: $!"; @@ -91,7 +111,7 @@ while () # add our "start" line, each key on its own line (prefixed by command and # options, in the standard ssh authorized_keys format), then the "end" line. print OUT "# gitosis-lite start\n"; -my_chdir($ENV{HOME} . "/.gitosis-lite/keydir"); +my_chdir($GL_KEYDIR); for my $pubkey (glob("*.pub")) { my $user = $pubkey; $user =~ s/\.pub$//; @@ -110,12 +130,12 @@ system("rm ~/.ssh/new_authkeys"); # if the gl admin directory (~/.gitosis-lite) is itself a git repo, do an # autocheckin. nothing fancy; this is a "just in case" type of thing. -my_chdir($ENV{HOME} . "/.gitosis-lite"); +my_chdir($GL_ADMINDIR); if (-d ".git") { system("git add -A keydir"); # stage all changes in keydir - if (! system("git diff --cached --quiet") ) # and if there are any + if (system("git diff --cached --quiet") ) { open(COMMIT, "|-", "git commit -F -") or die "pipe commit failed: $!"; @@ -129,9 +149,9 @@ if (-d ".git") # "compile" GL conf # ---------------------------------------------------------------------------- -open(INF, "<", $ENV{HOME} . "/.gitosis-lite/gitosis-lite.conf") +open(INF, "<", $GL_CONF) or die "open GL conf failed: $!"; -open(OUT, ">", $ENV{HOME} . "/.ssh/gitosis-lite.conf-compiled.pm") +open(OUT, ">", $GL_CONF_COMPILED) or die "open GL conf compiled failed: $!"; # the syntax is fairly simple, so we parse it inline @@ -164,14 +184,14 @@ while () elsif (/^(R|RW|RW\+) (.* )?= (.+)/) { my @perms = split //, $1; - my @refs = split ' ', $2 if $2; + my @refs; @refs = split(' ', $2) if $2; my @users = split ' ', $3; # if no ref is given, this PERM applies to all refs @refs = qw(refs/.*) unless @refs; # fully qualify refs that dont start with "refs/"; prefix them with # "refs/heads/" - @refs = map { m(^refs/) or s(^)(refs/heads/) } @refs; + @refs = map { m(^refs/) or s(^)(refs/heads/); $_ } @refs; # expand the user list, unless it is just "@all" @users = expand_userlist ( @users ) @@ -182,11 +202,11 @@ while () { for my $perm (@perms) { - for my $ref (@refs) + for my $user (@users) { - for my $user (@users) + for my $ref (@refs) { - $repos{$repo}{$perm}{$ref}{$user} = 1; + $repos{$repo}{$perm}{$user}{$ref} = 1; } } } @@ -196,3 +216,23 @@ while () print OUT Data::Dumper->Dump([\%repos], [qw(*repos)]); close(OUT); + +# ---------------------------------------------------------------------------- +# any new repos created? +# ---------------------------------------------------------------------------- + +# modern gits allow cloning from an empty repo, so we just create it. Gitosis +# did not have that luxury, so it was forced to detect the first push and +# create it then + +my_chdir("$ENV{HOME}/$REPO_BASE"); +for my $repo (keys %repos) +{ + unless (-d "$repo.git") + { + mkdir("$repo.git") or die "mkdir $repo.git failed: $!"; + my_chdir("$repo.git"); + system("git init --bare"); + my_chdir("$ENV{HOME}/$REPO_BASE"); + } +} From 4a4b6e9d97794fed3675f317248bb465fa31681b Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 23 Aug 2009 18:24:37 +0530 Subject: [PATCH 007/637] auth: finally! --- gl-auth-command | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100755 gl-auth-command diff --git a/gl-auth-command b/gl-auth-command new file mode 100755 index 0000000..e1be7d7 --- /dev/null +++ b/gl-auth-command @@ -0,0 +1,106 @@ +#!/usr/bin/perl -w + +use strict; + +# === auth-command === +# the command that GL users actually run + +# part of the gitosis-lite (GL) suite + +# how run: via sshd, being listed in "command=" in ssh authkeys +# when: every login by a GL user +# input: $1 is GL username, plus $SSH_ORIGINAL_COMMAND +# output: +# security: +# - currently, we just make some basic checks, copied from gitosis + +# robustness: + +# other notes: + +# ---------------------------------------------------------------------------- +# common definitions +# ---------------------------------------------------------------------------- + +our $GL_ADMINDIR; +our $GL_CONF; +our $GL_KEYDIR; +our $GL_CONF_COMPILED; +our $REPO_BASE; +our %repos; + +my $glrc = $ENV{HOME} . "/.gitosis-lite.rc"; +unless (my $ret = do $glrc) +{ + die "parse $glrc failed: $@" if $@; + die "couldn't do $glrc: $!" unless defined $ret; + die "couldn't run $glrc" unless $ret; +} + +die "couldnt do perms file" unless (my $ret = do $GL_CONF_COMPILED); + +# ---------------------------------------------------------------------------- +# definitions specific to this program +# ---------------------------------------------------------------------------- + +our $R_COMMANDS=qr/git[ -]upload-pack/; +our $W_COMMANDS=qr/git[ -]receive-pack/; +our $REPONAME_PATT=qr(^[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern + +# ---------------------------------------------------------------------------- +# start... +# ---------------------------------------------------------------------------- + +# first, fix the biggest gripe I have with gitosis, a 1-line change +my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere! + +# ---------------------------------------------------------------------------- +# sanity checks on SSH_ORIGINAL_COMMAND +# ---------------------------------------------------------------------------- + +# SSH_ORIGINAL_COMMAND must exist. Since we also captured $user, we print +# that in the message so people saying "ssh git@server" can see which gitosis +# user he is being recognised as +my $cmd = $ENV{SSH_ORIGINAL_COMMAND} + or die "no SSH_ORIGINAL_COMMAND? I'm not a shell, $user!"; + +# we don't like newlines or semicolons in SSH_ORIGINAL_COMMAND +die "$cmd??? you're a funny guy..." + if $cmd =~ /[;\n]/; + +# split into command and arguments; the pattern allows old style as well as +# new style: "git-subcommand arg" or "git subcommand arg", just like gitosis +# does, although I'm not sure how necessary that is +# +# keep in mind this is how git sends across the command: +# git-receive-pack 'reponame.git' +# including the single quotes + +my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'(.*).git'/); +die "$verb? I don't do odd jobs, sorry..." + unless $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS; + +die "I don't like the look of $repo, sorry!" + unless $repo =~ $REPONAME_PATT; + +# ---------------------------------------------------------------------------- +# first level permissions check +# ---------------------------------------------------------------------------- + +# now, knowing the user and repo (which is repo path), we try perms +my $perm = 'W'; $perm = 'R' if $verb =~ $R_COMMANDS; +die "access denied" unless $repos{$repo}{$perm}{$user}; + +# ---------------------------------------------------------------------------- +# over to git now +# ---------------------------------------------------------------------------- + +# ( but first save the reponame; we can save some time later in the hook ) +$ENV{GL_REPO}=$repo; + +open(LOG, ">>", "$GL_ADMINDIR/log"); +print LOG "\n", scalar(localtime), " $ENV{SSH_ORIGINAL_COMMAND} $user\n"; +close(LOG); + +$repo = "'$REPO_BASE/$repo.git'"; +exec("git", "shell", "-c", "$verb $repo"); From 2e38867b59ac68b927f1671ff9e8c2bc2fd3a2be Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 23 Aug 2009 21:40:03 +0530 Subject: [PATCH 008/637] add hook, the last piece --- gl-compile-conf | 3 ++ update-hook.pl | 82 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100755 update-hook.pl diff --git a/gl-compile-conf b/gl-compile-conf index bcce972..6aefe0c 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -225,6 +225,7 @@ close(OUT); # did not have that luxury, so it was forced to detect the first push and # create it then +umask(0077); my_chdir("$ENV{HOME}/$REPO_BASE"); for my $repo (keys %repos) { @@ -233,6 +234,8 @@ for my $repo (keys %repos) mkdir("$repo.git") or die "mkdir $repo.git failed: $!"; my_chdir("$repo.git"); system("git init --bare"); + system("cp $GL_ADMINDIR/update-hook.pl hooks/update"); + system("chmod 755 hooks/update"); my_chdir("$ENV{HOME}/$REPO_BASE"); } } diff --git a/update-hook.pl b/update-hook.pl new file mode 100755 index 0000000..53540e9 --- /dev/null +++ b/update-hook.pl @@ -0,0 +1,82 @@ +#!/usr/bin/perl -w + +use strict; + +# === update === +# this is gitosis-lite's update hook + +# part of the gitosis-lite (GL) suite + +# how run: via git, being copied as .git/hooks/update in every repo +# when: every push +# input: +# - see man githooks for STDIN +# - uses the compiled config file to get permissions info +# output: based on permissions etc., exit 0 or 1 +# security: +# - none + +# robustness: + +# other notes: + +# ---------------------------------------------------------------------------- +# common definitions +# ---------------------------------------------------------------------------- + +our $GL_ADMINDIR; +our $GL_CONF; +our $GL_KEYDIR; +our $GL_CONF_COMPILED; +our $REPO_BASE; +our %repos; + +my $glrc = $ENV{HOME} . "/.gitosis-lite.rc"; +unless (my $ret = do $glrc) +{ + die "parse $glrc failed: $@" if $@; + die "couldn't do $glrc: $!" unless defined $ret; + die "couldn't run $glrc" unless $ret; +} + +die "couldnt do perms file" unless (my $ret = do $GL_CONF_COMPILED); + +# ---------------------------------------------------------------------------- +# definitions specific to this program +# ---------------------------------------------------------------------------- + +open(LOG, ">>", "$GL_ADMINDIR/log"); + +# ---------------------------------------------------------------------------- +# start... +# ---------------------------------------------------------------------------- + +my $ref = shift; +my $oldsha = shift; +my $newsha = shift; +my $merge_base = '0' x 40; +chomp($merge_base = `git merge-base $oldsha $newsha`) + unless $oldsha eq '0' x 40; + +# some of this is from an example hook in Documentation/howto of git.git, with +# some variations + +# what are you trying to do? (is it 'W' or '+'?) +my $perm = 'W'; +# rewriting a tag +$perm = '+' if $ref =~ m(refs/tags/) and $oldsha ne ('0' x 40); +# non-ff push to branch +$perm = '+' if $ref =~ m(refs/heads/) and $oldsha ne $merge_base; + +my $allowed_refs = $repos{$ENV{GL_REPO}}{$perm}{$ENV{GL_USER}}; +for my $refex (keys %$allowed_refs) +# refex? sure -- a regex to match a ref against :) +{ + if ($ref =~ /$refex/) + { + print LOG "$perm: $ENV{GL_USER} $ENV{GL_REPO} $ref $oldsha $newsha\n"; + close (LOG); + exit 0; + } +} +exit 1; From e8e7bda41c9547d0f074d92090699b3847325ca7 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 24 Aug 2009 07:29:25 +0530 Subject: [PATCH 009/637] added README and TODO --- README.markdown | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ TODO | 9 +++++++ 2 files changed, 80 insertions(+) create mode 100644 README.markdown create mode 100644 TODO diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..d63b62d --- /dev/null +++ b/README.markdown @@ -0,0 +1,71 @@ +# gitosis-lite + +In this document: + + * "lite"? + * what's extra + * whats missing/TODO + * workflow + * conf file example + +---- + +### "lite"? + +I have been gitosis for a while, and have learnt a lot from it. But in a +typical $DAYJOB setting, there are some issues. It's not always Linux, so you +can't just "urpmi gitosis" and be done. "python-setuptools" isn't often +installed (and on a Solaris 9 I was trying to help remotely, we never did +manage it). And the most requested feature (see next section) had to be +written anyway. + +While I was pondering having to finally learn python (I hate whitespace based +flow logic except for plain text; this is a *personal* opinion so pythonistas +can back off :-), I also realised that: + + * no one in $DAYJOB settings will use or approve access methods that work + without any authentication, so I didn't need gitweb/daemon support in the + tool + * the idea that you admin it by pushing to a special repo is cute and + convenient, but not really necessary because of how rarely these changes + are made. + +All of this pointed to a rewrite. In perl, naturally. + +I also gained (and used) an unfair advantage: gits newer than 1.6.2 can clone +an empty repo, so I don't need complex logic in the permissions checking part +to *create* the repo initially -- I just create an empty bare repo when I +"compile" the config file (see "workflow" below). + +### what's extra? + +A lot of people in my $DAYJOB type world want per-branch permissions, so I +copied the basic idea from +git.git:Documentation/howto/update-hook-example.txt. I think this is the most +significant extra I have. This includes not just who can push to what branch, +but also whether they are allowed to rewind it or not (non-ff push). + +### what's missing/TODO + +See TODO file + +### workflow + +I took the opportunity to change the workflow significantly. + + * all admin happens *on the server*, in a special directory + * after making any changes, one "compiles" the configuration. This + refreshes `~/.ssh/authorized_keys`, as well as puts a parsed form of the + access list in a file for the other two pieces to use. + +Why pre-parse? Because access control decisions are taken at two separate +stages now: + + * the program that is run via `~/.ssh/authorized_keys` (called + `gl-auth-command`, equivalent to `gitosis-serve`) decides whether even git + should be allowed to run (basic R/W/no access) + * the update-hook on each repo, which decides the per-branch permissions. + +But the user specifies only one access file, and he doesn't have to know these +distinctions. So I avoid having to parse the access file in two completely +different programs by pre-compiling it and storing it as a perl "variable". diff --git a/TODO b/TODO new file mode 100644 index 0000000..c3bfd57 --- /dev/null +++ b/TODO @@ -0,0 +1,9 @@ + * a proper INSTALL file with clear instructions for non-experts + + * make a proper test suite + + * `use Git` instead of run git commands: I don't run too many git commands + in this and Git.pm just runs the same commands (per the documentation), so + it's sorta moot right now, but it's worth trying + + * change the "rc" file to use "gitconfig" instead... From 14f82ffc462793018894b265f8403f1ee5473175 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 24 Aug 2009 10:07:06 +0530 Subject: [PATCH 010/637] INSTALL started (w-i-p for now); minor changes to README, TODO, and the rc file --- INSTALL | 25 +++++++++++++++++++++++++ README.markdown | 6 ------ TODO | 2 ++ example.gitosis-lite.rc | 22 ++++++++++++++-------- 4 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 INSTALL diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..2a0184a --- /dev/null +++ b/INSTALL @@ -0,0 +1,25 @@ +There are 5 files you need to touch/copy + + example.conf + example.gitosis-lite.rc + gl-auth-command + gl-compile-conf + update-hook.pl + +1. copy `example.gitosis-lite.rc` as `~/.gitosis-lite.rc`. This location is + fixed for now (maybe later I'll change it to a "git config" variable). + +2. edit `~/.gitosis-lite.rc` and change all the paths however you want. Be + sure to keep the perl syntax -- you *don't* have to know perl to do so, + it's fairly easy to guess in this limited case. + +3. copy `example.conf` to whatever path you specified for `GL_CONF` in the rc + file in step 2. By default it is `~/.gitosis-lite/gitosis-lite.conf`. + + Edit the file as you wish. The comments in the file ought to be clear + enough but let me know if not + +4. create directories for whatever you named in `GL_KEYDIR` and `REPO_BASE` + (default `~/.gitosis-lite/keydir` and `~/repositories`) + +5. copy `update-hook.pl` to `$GL_ADMINDIR` (default `~/.gitosis-lite`) diff --git a/README.markdown b/README.markdown index d63b62d..53413f6 100644 --- a/README.markdown +++ b/README.markdown @@ -4,9 +4,7 @@ In this document: * "lite"? * what's extra - * whats missing/TODO * workflow - * conf file example ---- @@ -45,10 +43,6 @@ git.git:Documentation/howto/update-hook-example.txt. I think this is the most significant extra I have. This includes not just who can push to what branch, but also whether they are allowed to rewind it or not (non-ff push). -### what's missing/TODO - -See TODO file - ### workflow I took the opportunity to change the workflow significantly. diff --git a/TODO b/TODO index c3bfd57..7f3a611 100644 --- a/TODO +++ b/TODO @@ -7,3 +7,5 @@ it's sorta moot right now, but it's worth trying * change the "rc" file to use "gitconfig" instead... + + * check user/group names with some suitable regex diff --git a/example.gitosis-lite.rc b/example.gitosis-lite.rc index 2e9db31..ddbcca8 100644 --- a/example.gitosis-lite.rc +++ b/example.gitosis-lite.rc @@ -1,14 +1,20 @@ # this is meant to be pulled into a perl program using "do" -# gitosis-lite admin directory, files, etc -$GL_ADMINDIR=$ENV{HOME} . "/.gitosis-lite"; -$GL_CONF="$GL_ADMINDIR/gitosis-lite.conf"; -$GL_KEYDIR="$GL_ADMINDIR/keydir"; - # this one has to agree with the other programs, watch out: -$GL_CONF_COMPILED=$ENV{HOME} . "/.ssh/gitosis-lite.conf-compiled.pm"; - # base directory for all the repos $REPO_BASE="repositories"; -# this should be the last line in this file +# gitosis-lite admin directory, files, etc +$GL_ADMINDIR=$ENV{HOME} . "/.gitosis-lite"; + +# -------------------------------------- + +# the ones below can be left as they are, unless for some reason you want them +# elsewhere + +$GL_CONF="$GL_ADMINDIR/gitosis-lite.conf"; +$GL_KEYDIR="$GL_ADMINDIR/keydir"; +$GL_CONF_COMPILED="$GL_ADMINDIR/gitosis-lite.conf-compiled.pm"; + +# -------------------------------------- +# this should be the last line in this file, per perl rules 1; From 6feffc9b165a724bf264763ce3d3099c1287c1e1 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 24 Aug 2009 10:07:33 +0530 Subject: [PATCH 011/637] example.conf got lots more comments (aka documentation!) --- example.conf | 56 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/example.conf b/example.conf index b4134f6..006ea7c 100644 --- a/example.conf +++ b/example.conf @@ -1,6 +1,9 @@ # example conf file for gitosis-lite -# comments in the normal shell-ish style; no surprises there +# overall syntax: +# - everything in this is space-separated; no commas, semicolons, etc +# - comments in the normal shell-ish style; no surprises there +# - there are no continuation lines of any kind # objectives, over and above gitosis: # - simpler syntax @@ -9,51 +12,61 @@ # - allows branch level control # ---------------------------------------------------------------------------- -# user groups; syntax: +# USERS and GROUPS + +# syntax: # @groupname = username [...] -# too many users? just add more such lines + +# usernames and groupnames should be as simple as possible; there's no +# explicit list of allowed characters for now but that's a TODO item. + +# too many users in one group? just add more such lines # (they accumulate, like squid ACLs) -@customers = cust1 cust2 -@customers = cust99 +@cust_A = cust1 cust2 +@cust_A = cust99 @interns = indy james @staff = me alice -# you can nest groups; they'll be fully expanded in actual use + +# you can nest groups, but not recursively of course! @staff = bob @interns @secret_staff = bruce whitfield martin # ---------------------------------------------------------------------------- -# each section pertains to one or more repo(s); syntax: -# repo reponame [...] -# (R|RW|RW+) [list of ref names] = [list of users] +# REPOS, REFS, and PERMISSIONS + +# syntax: +# repo [one or more reponames] +# (R|RW|RW+) [zero or more refnames] = [one or more users] + +# notes: # - RW+ means non-ff push is allowed -# - if no ref name exists, the rule applies to all refs +# - you can't write just "W" or "+"; it has to be R, or RW, or RW+ + +# - if no ref name appears, the rule applies to all refs in that repo # - ref names are perl regex patterns -# - suffixed by "$" # - prefixed by "refs/heads/" if it doesn't start with "refs/" # (i.e., tags have to be explicitly named as refs/tags/pattern) -# - the order of entries matters: the first ref+action line (for the repo) -# that matches is what counts. If the user is listed, the action is -# allowed, otherwise it is rejected. - -# - list of users can use any group name defined earlier +# - the list of users can inlude any group name defined earlier # - "@all" is a special, predefined, groupname -# anyone can play in the sandbox +# anyone can play in the sandbox, including making non-fastforward commits +# (that's what the "+" means) repo sandbox RW+ = @all # my repo and alice's repo have the same memberships and access, so we just -# club them +# put them both in the same stanza repo myrepo alicerepo RW+ = me alice R = bob eve -repo custrepo - RW = @customers - R = @staff +# this repo is visible to customers from company A but they can't write to it +repo cust_A_repo + R = @cust_A + RW = @staff # idea for the tags syntax shamelessly copied from git.git # Documentation/howto/update-hook-example.txt :) @@ -61,5 +74,6 @@ repo secret RW+ pu = bruce RW master next = bruce RW refs/tags/v[0-9].* = bruce + RW refs/tags/ = @secret_staff RW tmp/.* = @secret_staff R = @secret_staff From 3cddc4ca354cdd027d6eadecfde93f045418d980 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 24 Aug 2009 10:16:25 +0530 Subject: [PATCH 012/637] compile/update-hook: preserve the order of refs ...by turning the last piece (list of allowed refs) into an array rather than a hash --- gl-compile-conf | 5 +---- update-hook.pl | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/gl-compile-conf b/gl-compile-conf index 6aefe0c..8379fa5 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -204,10 +204,7 @@ while () { for my $user (@users) { - for my $ref (@refs) - { - $repos{$repo}{$perm}{$user}{$ref} = 1; - } + push @{ $repos{$repo}{$perm}{$user} }, @refs; } } } diff --git a/update-hook.pl b/update-hook.pl index 53540e9..b881f5a 100755 --- a/update-hook.pl +++ b/update-hook.pl @@ -69,7 +69,7 @@ $perm = '+' if $ref =~ m(refs/tags/) and $oldsha ne ('0' x 40); $perm = '+' if $ref =~ m(refs/heads/) and $oldsha ne $merge_base; my $allowed_refs = $repos{$ENV{GL_REPO}}{$perm}{$ENV{GL_USER}}; -for my $refex (keys %$allowed_refs) +for my $refex (@$allowed_refs) # refex? sure -- a regex to match a ref against :) { if ($ref =~ /$refex/) From d33c408dc3f1d54db81aa495e22bb0736001f342 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 24 Aug 2009 13:29:33 +0530 Subject: [PATCH 013/637] INSTALL and README pretty much done --- INSTALL | 106 ++++++++++++++++++++++++++++++++++++++------- README.markdown | 112 ++++++++++++++++++++++++++++++------------------ 2 files changed, 160 insertions(+), 58 deletions(-) diff --git a/INSTALL b/INSTALL index 2a0184a..ce0d1d0 100644 --- a/INSTALL +++ b/INSTALL @@ -1,25 +1,99 @@ -There are 5 files you need to touch/copy +### quickinstall - example.conf - example.gitosis-lite.rc - gl-auth-command - gl-compile-conf - update-hook.pl +I assume all the files pertaining to this software are untarred and available +in the current directory. -1. copy `example.gitosis-lite.rc` as `~/.gitosis-lite.rc`. This location is - fixed for now (maybe later I'll change it to a "git config" variable). +A quick install, taking all the defaults, can be done with the following +commands; just copy and paste them into your shell: -2. edit `~/.gitosis-lite.rc` and change all the paths however you want. Be - sure to keep the perl syntax -- you *don't* have to know perl to do so, - it's fairly easy to guess in this limited case. + # this one is fixed to the location shown + cp example.gitosis-lite.rc ~/.gitosis-lite.rc -3. copy `example.conf` to whatever path you specified for `GL_CONF` in the rc - file in step 2. By default it is `~/.gitosis-lite/gitosis-lite.conf`. + # the destinations below are defaults; if you change the paths in the "rc" + # file above, these destinations also must change accordingly + # mkdir $REPO_BASE, $GL_ADMINDIR, and $GL_KEYDIR + mkdir ~/repositories + mkdir ~/.gitosis-lite + mkdir ~/.gitosis-lite/keydir + + # copy sample conf to $GL_CONF + cp example.conf ~/.gitosis-lite/gitosis-lite.conf + + # copy the 3 programs to $GL_ADMINDIR + cp update-hook.pl ~/.gitosis-lite + cp gl-auth-command ~/.gitosis-lite + cp gl-compile-conf ~/.gitosis-lite + + # optional; copy the documents also (if you untarred the package into a + # temporary directory and need to get rid of it) + cp INSTALL README.markdown ~/.gitosis-lite + +### install notes + + * At present the location of `~/.gitosis-lite.rc` is fixed (maybe later I'll + change it to a "git config" variable). + + If you edit it and change any paths, be sure to keep the perl syntax -- + you *don't* have to know perl to do so, it's fairly easy to guess in this + limited case. And of course, make sure you adjust the commands shown + above to suit the new locations + + * the config file is (by default) at `~/.gitosis-lite/gitosis-lite.conf`. Edit the file as you wish. The comments in the file ought to be clear enough but let me know if not -4. create directories for whatever you named in `GL_KEYDIR` and `REPO_BASE` - (default `~/.gitosis-lite/keydir` and `~/repositories`) + * if you want to bring in existing (bare, server) repos into gitosis-lite, + this should work: + * backup the repo, then move it to `$BASE_REPO` + * copy `$GL_ADMINDIR/update-hook.pl` to `[reponame].git/hooks/update` -- + if you don't do this, per branch restrictions will not work + * then update the keys and the config file and "compile" -5. copy `update-hook.pl` to `$GL_ADMINDIR` (default `~/.gitosis-lite`) +### administer + + * ask each user who will get access to send you a public key. See other + sources (for example + [here](http://sitaramc.github.com/0-installing/2-access-gitosis.html#generating_a_public_key)) + for how to do this + * for each "user" in `$GL_CONF`, copy their public key to a file called + "user.pub" in `$GL_KEYDIR` + * edit the config file (`$GL_CONF`) to add the new users in whatever way you + like + * backup your `~/.ssh/authorized_keys` file if you feel nervous :-) + * cd to `$GL_ADMINDIR` and run `./gl-compile-conf` + +#### optional -- if you want to be doubly sure + +It should all work, but the first couple of times you may want to check these + + * check the outputs + + * `~/.ssh/authorized_keys` should contain one line for each "user" pub + key added, between two "marker" lines (which you should please please + not remove!). The line should contain a "command=" pointing to a + `$GL_ADMINDIR/gl-auth-command` file, then some sshd restrictions, the + key, etc. + * `$GL_CONF_COMPILED` (default + `~/.gitosis-lite/gitosis-lite.conf-compiled.pm`) should contain an + expanded list of the access control rules. It may look a little long, + but it's fairly intuitive! + + * if the run threw up any "initialising empty repo" messages, check the + individual repos (inside `$REPO_BASE`) if you wish. Especially make sure + the `$REPO_BASE/[reponame].git/hooks/update` got copied OK and is + executable + +### run + +Just use it as normal. Every new repo mentioned has been created already, so +(as long as your clients are using git > 1.6.2), you can just clone it. + +And once in a while, if you're feeling particularly BOFH-ish, take a look at +`$GL_ADMINDIR/log` :-) + +### errors, warnings, etc + + * when you clone an empty repo, git seems to complain about the remote + hanging up or something. I have no idea what that is, but it doesn't seem + to hurt anything. This happens even in normal git, not just gitosis-lite. diff --git a/README.markdown b/README.markdown index 53413f6..363378e 100644 --- a/README.markdown +++ b/README.markdown @@ -1,65 +1,93 @@ # gitosis-lite +gitosis-lite is the bare essentials of gitosis, with a completely different +config file that allows (at last!) access control down to the branch level, +including specifying who can and cannot *rewind* a given branch. + In this document: - * "lite"? - * what's extra - * workflow + * why + * what's gone + * what's new + * the workflow ---- -### "lite"? +### why -I have been gitosis for a while, and have learnt a lot from it. But in a -typical $DAYJOB setting, there are some issues. It's not always Linux, so you -can't just "urpmi gitosis" and be done. "python-setuptools" isn't often -installed (and on a Solaris 9 I was trying to help remotely, we never did -manage it). And the most requested feature (see next section) had to be -written anyway. +I have been using gitosis for a while, and have learnt a lot from it. But in +a typical $DAYJOB setting, there are some issues: -While I was pondering having to finally learn python (I hate whitespace based -flow logic except for plain text; this is a *personal* opinion so pythonistas -can back off :-), I also realised that: + * it's not always Linux; you can't just "urpmi gitosis" (or yum or apt-get) + and be done + * often, "python-setuptools" isn't installed (and on a Solaris9 I was trying + to help remotely, we never did manage to install it eventually) + * the most requested feature (see "what's extra?") had to be written anyway - * no one in $DAYJOB settings will use or approve access methods that work - without any authentication, so I didn't need gitweb/daemon support in the - tool - * the idea that you admin it by pushing to a special repo is cute and - convenient, but not really necessary because of how rarely these changes - are made. +### what's gone -All of this pointed to a rewrite. In perl, naturally. +While I was pondering the need to finally learn python[1] , I also realised +that: -I also gained (and used) an unfair advantage: gits newer than 1.6.2 can clone -an empty repo, so I don't need complex logic in the permissions checking part -to *create* the repo initially -- I just create an empty bare repo when I -"compile" the config file (see "workflow" below). + * no one in $DAYJOB type environments will use or approve access methods + that work without any authentication, so I didn't need gitweb/daemon + support in the tool or in the config file + * the idea that you admin it by pushing to a special repo is nice, but not + really necessary because of how rarely these changes are made, especially + considering how much code is involved in that piece + +All of this pointed to a rewrite. In perl, naturally :-) ### what's extra? -A lot of people in my $DAYJOB type world want per-branch permissions, so I -copied the basic idea from -git.git:Documentation/howto/update-hook-example.txt. I think this is the most -significant extra I have. This includes not just who can push to what branch, -but also whether they are allowed to rewind it or not (non-ff push). +Per-branch permissions. You will not believe how often I am asked this at +$DAYJOB. This is almost the single reason I started *thinking* about rolling +my own gitosis in the first place. -### workflow +Take a look at the example config file in the repo to see how I do this. I +copied the basic idea from `update-hook-example.txt` (it's one of the "howto"s +that come with the git source tree). This includes not just who can push to +what branch, but also whether they are allowed to rewind it or not (non-ff +push). -I took the opportunity to change the workflow significantly. +However, please note the difference in the size and complexity of the +*operational code* between the update hook in that example, and in mine :-) +The reason is in the next section. - * all admin happens *on the server*, in a special directory - * after making any changes, one "compiles" the configuration. This +### the workflow + +In order to get per-branch access, you *must* use an update hook. However, +that only gets invoked on a push; "read" access still has to be controlled +right at the beginning, before git even enters the scene (just the way gitosis +currently works). + +So: either split the access control into two config files, or have two +completely different programs *both* parse the same one and pick what they +want. Crap... I definitely don't want the hook doing any parsing, (and it +would be nice if the auth-control program didn't have to either). + +So I changed the workflow completely: + + * all admin changes happen *on the server*, in a special directory that + contains the config and the users' pubkeys. But there's no commit and + push afterward. Nothing prevents you from version-controlling that + directory if you wish to, but it's not *required* + * instead, after making changes, you "compile" the configuration. This refreshes `~/.ssh/authorized_keys`, as well as puts a parsed form of the access list in a file for the other two pieces to use. -Why pre-parse? Because access control decisions are taken at two separate -stages now: +The pre-parsed form is basically a huge perl variable. It's human readable +too (never mind what the python guys say!) - * the program that is run via `~/.ssh/authorized_keys` (called - `gl-auth-command`, equivalent to `gitosis-serve`) decides whether even git - should be allowed to run (basic R/W/no access) - * the update-hook on each repo, which decides the per-branch permissions. +Advantages: all the complexity of parsing and error checking the parse is done +away from the two places where the actual access control happens, which are: -But the user specifies only one access file, and he doesn't have to know these -distinctions. So I avoid having to parse the access file in two completely -different programs by pre-compiling it and storing it as a perl "variable". + * the program that is run via `~/.ssh/authorized_keys` (I call it + `gl-auth-command`, equivalent to `gitosis-serve`); this decides whether + git should even be allowed to run (basic R/W/no access) + * the update-hook on each repo, which decides the per-branch permissions + +---- + +[1] I hate whitespace to mean anything significant except for text; this is a +personal opinion *only*, so pythonistas please back off :-) From cd01bb52975f12a589c5c61f52a4fe804d364767 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 24 Aug 2009 13:30:58 +0530 Subject: [PATCH 014/637] compile: fail/error checks: - don't update authkeys if parse fails (done by moving that code so it runs *after* the parse) - check group/usernames for sanity --- example.conf | 9 +-- gl-compile-conf | 191 +++++++++++++++++++++++++----------------------- 2 files changed, 103 insertions(+), 97 deletions(-) diff --git a/example.conf b/example.conf index 006ea7c..89f3bd0 100644 --- a/example.conf +++ b/example.conf @@ -17,19 +17,18 @@ # syntax: # @groupname = username [...] -# usernames and groupnames should be as simple as possible; there's no -# explicit list of allowed characters for now but that's a TODO item. +# usernames and groupnames should be as simple as possible # too many users in one group? just add more such lines # (they accumulate, like squid ACLs) - @cust_A = cust1 cust2 @cust_A = cust99 -@interns = indy james -@staff = me alice # you can nest groups, but not recursively of course! +@interns = indy james @staff = bob @interns + +@staff = me alice @secret_staff = bruce whitfield martin # ---------------------------------------------------------------------------- diff --git a/gl-compile-conf b/gl-compile-conf index 8379fa5..ac4134d 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -61,6 +61,7 @@ unless (my $ret = do $glrc) # command and options for authorized_keys our $AUTH_COMMAND="$GL_ADMINDIR/gl-auth-command"; our $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; +our $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern our %groups = (); our %repos = (); @@ -81,6 +82,7 @@ sub expand_userlist for my $item (@list) { + die "bad user $item\n" unless $item =~ $USERNAME_PATT; if ($item =~ /^@/) # nested group { die "undefined group $item" unless $groups{$item}; @@ -96,6 +98,103 @@ sub expand_userlist return @new_list; } +# ---------------------------------------------------------------------------- +# "compile" GL conf +# ---------------------------------------------------------------------------- + +open(INF, "<", $GL_CONF) + or die "open GL conf failed: $!"; + +# the syntax is fairly simple, so we parse it inline + +my @repos; +while () +{ + # normalise whitespace; keeps later regexes very simple + s/=/ = /; + s/\s+/ /g; + s/^ //; + s/ $//; + # kill comments + s/#.*//; + # and blank lines + next unless /\S/; + + # user groups + if (/^(@\S+) = (.*)/) + { + push @{ $groups{$1} }, expand_userlist( split(' ', $2) ); + die "bad group $1\n" unless $1 =~ $USERNAME_PATT; + } + # repo(s) + elsif (/^repo (.*)/) + { + @repos = split(' ', $1); + } + # actual permission line + elsif (/^(R|RW|RW\+) (.* )?= (.+)/) + { + # split perms to separate out R, W, and + + my @perms = split //, $1; + my @refs; @refs = split(' ', $2) if $2; + my @users = split ' ', $3; + + # if no ref is given, this PERM applies to all refs + @refs = qw(refs/.*) unless @refs; + # fully qualify refs that dont start with "refs/"; prefix them with + # "refs/heads/" + @refs = map { m(^refs/) or s(^)(refs/heads/); $_ } @refs; + + # expand the user list, unless it is just "@all" + @users = expand_userlist ( @users ) + unless (@users == 1 and $users[0] eq '@all'); + + # ok, we can finally populate the %repos hash + for my $repo (@repos) # each repo in the current stanza + { + for my $perm (@perms) + { + for my $user (@users) + { + push @{ $repos{$repo}{$perm}{$user} }, @refs; + } + } + } + } + else + { + die "can't make head or tail of '$_'\n"; + } +} + +open(OUT, ">", $GL_CONF_COMPILED) + or die "open GL conf compiled failed: $!"; +print OUT Data::Dumper->Dump([\%repos], [qw(*repos)]); +close(OUT); + +# ---------------------------------------------------------------------------- +# any new repos created? +# ---------------------------------------------------------------------------- + +# modern gits allow cloning from an empty repo, so we just create it. Gitosis +# did not have that luxury, so it was forced to detect the first push and +# create it then + +umask(0077); +my_chdir("$ENV{HOME}/$REPO_BASE"); +for my $repo (keys %repos) +{ + unless (-d "$repo.git") + { + mkdir("$repo.git") or die "mkdir $repo.git failed: $!"; + my_chdir("$repo.git"); + system("git init --bare"); + system("cp $GL_ADMINDIR/update-hook.pl hooks/update"); + system("chmod 755 hooks/update"); + my_chdir("$ENV{HOME}/$REPO_BASE"); + } +} + # ---------------------------------------------------------------------------- # "compile" ssh authorized_keys # ---------------------------------------------------------------------------- @@ -144,95 +243,3 @@ if (-d ".git") close(COMMIT) or die "close commit failed: $!"; } } - -# ---------------------------------------------------------------------------- -# "compile" GL conf -# ---------------------------------------------------------------------------- - -open(INF, "<", $GL_CONF) - or die "open GL conf failed: $!"; -open(OUT, ">", $GL_CONF_COMPILED) - or die "open GL conf compiled failed: $!"; - -# the syntax is fairly simple, so we parse it inline - -my @repos; -while () -{ - # normalise whitespace; keeps later regexes very simple - s/=/ = /; - s/\s+/ /g; - s/^ //; - s/ $//; - # kill comments - s/#.*//; - # and blank lines - next unless /\S/; - - - # user groups - if (/^(@\S+) = (.*)/) - { - push @{ $groups{$1} }, expand_userlist( split(' ', $2) ); - } - # repo(s) - elsif (/^repo (.*)/) - { - @repos = split(' ', $1); - } - # actual permission line - elsif (/^(R|RW|RW\+) (.* )?= (.+)/) - { - my @perms = split //, $1; - my @refs; @refs = split(' ', $2) if $2; - my @users = split ' ', $3; - - # if no ref is given, this PERM applies to all refs - @refs = qw(refs/.*) unless @refs; - # fully qualify refs that dont start with "refs/"; prefix them with - # "refs/heads/" - @refs = map { m(^refs/) or s(^)(refs/heads/); $_ } @refs; - - # expand the user list, unless it is just "@all" - @users = expand_userlist ( @users ) - unless (@users == 1 and $users[0] eq '@all'); - - # ok, we can finally populate the %repos hash - for my $repo (@repos) # each repo in the current stanza - { - for my $perm (@perms) - { - for my $user (@users) - { - push @{ $repos{$repo}{$perm}{$user} }, @refs; - } - } - } - } -} - -print OUT Data::Dumper->Dump([\%repos], [qw(*repos)]); -close(OUT); - -# ---------------------------------------------------------------------------- -# any new repos created? -# ---------------------------------------------------------------------------- - -# modern gits allow cloning from an empty repo, so we just create it. Gitosis -# did not have that luxury, so it was forced to detect the first push and -# create it then - -umask(0077); -my_chdir("$ENV{HOME}/$REPO_BASE"); -for my $repo (keys %repos) -{ - unless (-d "$repo.git") - { - mkdir("$repo.git") or die "mkdir $repo.git failed: $!"; - my_chdir("$repo.git"); - system("git init --bare"); - system("cp $GL_ADMINDIR/update-hook.pl hooks/update"); - system("chmod 755 hooks/update"); - my_chdir("$ENV{HOME}/$REPO_BASE"); - } -} From 0f726bea9a8fe9d1548968398a4b108bd5835304 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 24 Aug 2009 20:30:26 +0530 Subject: [PATCH 015/637] added license info --- COPYING | 339 ++++++++++++++++++++++++++++++++++++++++++++++++ INSTALL | 4 + README.markdown | 3 +- 3 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 COPYING diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/INSTALL b/INSTALL index ce0d1d0..e4350e3 100644 --- a/INSTALL +++ b/INSTALL @@ -97,3 +97,7 @@ And once in a while, if you're feeling particularly BOFH-ish, take a look at * when you clone an empty repo, git seems to complain about the remote hanging up or something. I have no idea what that is, but it doesn't seem to hurt anything. This happens even in normal git, not just gitosis-lite. + +---- + +gitosis-lite is released under the GPL v2 license. See COPYING for details diff --git a/README.markdown b/README.markdown index 363378e..ac4effe 100644 --- a/README.markdown +++ b/README.markdown @@ -2,7 +2,8 @@ gitosis-lite is the bare essentials of gitosis, with a completely different config file that allows (at last!) access control down to the branch level, -including specifying who can and cannot *rewind* a given branch. +including specifying who can and cannot *rewind* a given branch. It is +released under GPL v2. See COPYING for details. In this document: From 92cf77d9c2007c3354ae5c4e7d7fdefd7409cc32 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 25 Aug 2009 06:38:05 +0530 Subject: [PATCH 016/637] update hook: squelch message for branch deletion Ilari pointed out that in case of branch deletion the *new* SHA could be 0, which causes an ugly fatal: Not a valid commit name 0000000000000000000000000000000000000000 Since we consider deletion an extreme form of rewind, the end result does not change ($merge_base will be unequal to $oldsha anyway), but we do need to squelch the ugly message. --- update-hook.pl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/update-hook.pl b/update-hook.pl index b881f5a..8c6d917 100755 --- a/update-hook.pl +++ b/update-hook.pl @@ -55,17 +55,21 @@ my $ref = shift; my $oldsha = shift; my $newsha = shift; my $merge_base = '0' x 40; +# compute a merge-base if both SHAs are non-0, else leave it as '0'x40 +# (i.e., for branch create or delete, merge_base == '0'x40) chomp($merge_base = `git merge-base $oldsha $newsha`) - unless $oldsha eq '0' x 40; + unless $oldsha eq '0' x 40 + or $newsha eq '0' x 40; # some of this is from an example hook in Documentation/howto of git.git, with # some variations # what are you trying to do? (is it 'W' or '+'?) my $perm = 'W'; -# rewriting a tag +# rewriting a tag is considered a rewind, in terms of permissions $perm = '+' if $ref =~ m(refs/tags/) and $oldsha ne ('0' x 40); -# non-ff push to branch +# non-ff push to branch. Notice that branch delete looks like a rewind, as it +# should $perm = '+' if $ref =~ m(refs/heads/) and $oldsha ne $merge_base; my $allowed_refs = $repos{$ENV{GL_REPO}}{$perm}{$ENV{GL_USER}}; From 1f1b95f4c63879a1ebb921fe556ccc141333bc39 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 25 Aug 2009 07:06:36 +0530 Subject: [PATCH 017/637] compile: move umask up to cover other outputs also --- gl-compile-conf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gl-compile-conf b/gl-compile-conf index ac4134d..da3163a 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -66,6 +66,9 @@ our $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple patter our %groups = (); our %repos = (); +# set a restrictive umask, just in case +umask(0077); + # ---------------------------------------------------------------------------- # subroutines # ---------------------------------------------------------------------------- @@ -180,7 +183,6 @@ close(OUT); # did not have that luxury, so it was forced to detect the first push and # create it then -umask(0077); my_chdir("$ENV{HOME}/$REPO_BASE"); for my $repo (keys %repos) { From ebf6300d01eee439d5d4708503d08b4159232aee Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 25 Aug 2009 09:08:11 +0530 Subject: [PATCH 018/637] all: some "our"s changed to "my" --- gl-auth-command | 6 +++--- gl-compile-conf | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gl-auth-command b/gl-auth-command index e1be7d7..c05f53c 100755 --- a/gl-auth-command +++ b/gl-auth-command @@ -43,9 +43,9 @@ die "couldnt do perms file" unless (my $ret = do $GL_CONF_COMPILED); # definitions specific to this program # ---------------------------------------------------------------------------- -our $R_COMMANDS=qr/git[ -]upload-pack/; -our $W_COMMANDS=qr/git[ -]receive-pack/; -our $REPONAME_PATT=qr(^[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern +my $R_COMMANDS=qr/git[ -]upload-pack/; +my $W_COMMANDS=qr/git[ -]receive-pack/; +my $REPONAME_PATT=qr(^[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern # ---------------------------------------------------------------------------- # start... diff --git a/gl-compile-conf b/gl-compile-conf index da3163a..b668228 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -59,12 +59,12 @@ unless (my $ret = do $glrc) # ---------------------------------------------------------------------------- # command and options for authorized_keys -our $AUTH_COMMAND="$GL_ADMINDIR/gl-auth-command"; -our $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; -our $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern +my $AUTH_COMMAND="$GL_ADMINDIR/gl-auth-command"; +my $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; +my $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern -our %groups = (); -our %repos = (); +my %groups = (); +my %repos = (); # set a restrictive umask, just in case umask(0077); From 0b0d95a1ff88b0613996ca15745098e85453c6bd Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 25 Aug 2009 09:21:07 +0530 Subject: [PATCH 019/637] auth: tighten up 2 regexes; one minor code clarity fix --- gl-auth-command | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gl-auth-command b/gl-auth-command index c05f53c..8d02d3d 100755 --- a/gl-auth-command +++ b/gl-auth-command @@ -43,8 +43,8 @@ die "couldnt do perms file" unless (my $ret = do $GL_CONF_COMPILED); # definitions specific to this program # ---------------------------------------------------------------------------- -my $R_COMMANDS=qr/git[ -]upload-pack/; -my $W_COMMANDS=qr/git[ -]receive-pack/; +my $R_COMMANDS=qr/^git[ -]upload-pack$/; +my $W_COMMANDS=qr/^git[ -]receive-pack$/; my $REPONAME_PATT=qr(^[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern # ---------------------------------------------------------------------------- @@ -87,8 +87,9 @@ die "I don't like the look of $repo, sorry!" # first level permissions check # ---------------------------------------------------------------------------- -# now, knowing the user and repo (which is repo path), we try perms -my $perm = 'W'; $perm = 'R' if $verb =~ $R_COMMANDS; +# we know the user and repo; we just need to know what perm he's trying +my $perm = ($verb =~ $R_COMMANDS ? 'R' : 'W'); + die "access denied" unless $repos{$repo}{$perm}{$user}; # ---------------------------------------------------------------------------- From 66bf4a20f9f171895ab7c12c6b2fec04e9e9371c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 25 Aug 2009 09:57:19 +0530 Subject: [PATCH 020/637] all: lexical file handles instead of bare --- gl-auth-command | 9 ++++++--- gl-compile-conf | 44 +++++++++++++++++++++++--------------------- update-hook.pl | 14 ++++++-------- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/gl-auth-command b/gl-auth-command index 8d02d3d..eb809ee 100755 --- a/gl-auth-command +++ b/gl-auth-command @@ -99,9 +99,12 @@ die "access denied" unless $repos{$repo}{$perm}{$user}; # ( but first save the reponame; we can save some time later in the hook ) $ENV{GL_REPO}=$repo; -open(LOG, ">>", "$GL_ADMINDIR/log"); -print LOG "\n", scalar(localtime), " $ENV{SSH_ORIGINAL_COMMAND} $user\n"; -close(LOG); +# if log failure isn't important enough to block access, get rid of all the +# error checking +open my $log_fh, ">>", "$GL_ADMINDIR/log" + or die "open log failed: $!"; +print $log_fh "\n", scalar(localtime), " $ENV{SSH_ORIGINAL_COMMAND} $user\n"; +close $log_fh or die "close log failed: $!"; $repo = "'$REPO_BASE/$repo.git'"; exec("git", "shell", "-c", "$verb $repo"); diff --git a/gl-compile-conf b/gl-compile-conf index b668228..6568c0f 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -105,13 +105,13 @@ sub expand_userlist # "compile" GL conf # ---------------------------------------------------------------------------- -open(INF, "<", $GL_CONF) - or die "open GL conf failed: $!"; +open my $conf_fh, "<", $GL_CONF + or die "open conf failed: $!"; # the syntax is fairly simple, so we parse it inline my @repos; -while () +while (<$conf_fh>) { # normalise whitespace; keeps later regexes very simple s/=/ = /; @@ -170,10 +170,10 @@ while () } } -open(OUT, ">", $GL_CONF_COMPILED) - or die "open GL conf compiled failed: $!"; -print OUT Data::Dumper->Dump([\%repos], [qw(*repos)]); -close(OUT); +open my $compiled_fh, ">", $GL_CONF_COMPILED + or die "open compiled-conf failed: $!"; +print $compiled_fh Data::Dumper->Dump([\%repos], [qw(*repos)]); +close $compiled_fh or die "close compiled-conf failed: $!"; # ---------------------------------------------------------------------------- # any new repos created? @@ -201,26 +201,28 @@ for my $repo (keys %repos) # "compile" ssh authorized_keys # ---------------------------------------------------------------------------- -open(INF, "<", $ENV{HOME} . "/.ssh/authorized_keys") or die "open old authkeys failed: $!"; -open(OUT, ">", $ENV{HOME} . "/.ssh/new_authkeys") or die "open new authkeys failed: $!"; +open my $authkeys_fh, "<", $ENV{HOME} . "/.ssh/authorized_keys" + or die "open authkeys failed: $!"; +open my $newkeys_fh, ">", $ENV{HOME} . "/.ssh/new_authkeys" + or die "open newkeys failed: $!"; # save existing authkeys minus the GL-added stuff -while () +while (<$authkeys_fh>) { - print OUT unless (/^# gitosis-lite start/../^# gitosis-lite end/); + print $newkeys_fh unless (/^# gitosis-lite start/../^# gitosis-lite end/); } # add our "start" line, each key on its own line (prefixed by command and # options, in the standard ssh authorized_keys format), then the "end" line. -print OUT "# gitosis-lite start\n"; +print $newkeys_fh "# gitosis-lite start\n"; my_chdir($GL_KEYDIR); for my $pubkey (glob("*.pub")) { my $user = $pubkey; $user =~ s/\.pub$//; - print OUT "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS "; - print OUT `cat $pubkey`; + print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS "; + print $newkeys_fh `cat $pubkey`; } -print OUT "# gitosis-lite end\n"; -close(OUT); +print $newkeys_fh "# gitosis-lite end\n"; +close $newkeys_fh or die "close newkeys failed: $!"; # check what changes are being made; just a comfort factor # system("vim -d ~/.ssh/authorized_keys ~/.ssh/new_authkeys"); @@ -238,10 +240,10 @@ if (-d ".git") # and if there are any if (system("git diff --cached --quiet") ) { - open(COMMIT, "|-", "git commit -F -") - or die "pipe commit failed: $!"; - print COMMIT "keydir changed\n\n"; - print COMMIT `git diff --cached --name-status`; - close(COMMIT) or die "close commit failed: $!"; + open my $commit_ph, "|-", "git commit -F -" + or die "open commit failed: $!"; + print $commit_ph "keydir changed\n\n"; + print $commit_ph `git diff --cached --name-status`; + close $commit_ph or die "close commit failed: $!"; } } diff --git a/update-hook.pl b/update-hook.pl index 8c6d917..babd005 100755 --- a/update-hook.pl +++ b/update-hook.pl @@ -41,12 +41,6 @@ unless (my $ret = do $glrc) die "couldnt do perms file" unless (my $ret = do $GL_CONF_COMPILED); -# ---------------------------------------------------------------------------- -# definitions specific to this program -# ---------------------------------------------------------------------------- - -open(LOG, ">>", "$GL_ADMINDIR/log"); - # ---------------------------------------------------------------------------- # start... # ---------------------------------------------------------------------------- @@ -78,8 +72,12 @@ for my $refex (@$allowed_refs) { if ($ref =~ /$refex/) { - print LOG "$perm: $ENV{GL_USER} $ENV{GL_REPO} $ref $oldsha $newsha\n"; - close (LOG); + # if log failure isn't important enough to block pushes, get rid of + # all the error checking + open my $log_fh, ">>", "$GL_ADMINDIR/log" + or die "open log failed: $!"; + print $log_fh "$perm: $ENV{GL_USER} $ENV{GL_REPO} $ref $oldsha $newsha\n"; + close $log_fh or die "close log failed: $!"; exit 0; } } From 33963391a5bd7c3e5e3751922c264c34b94918a0 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 25 Aug 2009 20:08:47 +0530 Subject: [PATCH 021/637] INSTALL: pre-requisites, and lots of stuff on ssh the duplicate pubkey problem and the need to document it was highlighted by Ilari ...damn I spend more time on ssh than on git it seems to me :( --- INSTALL | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/INSTALL b/INSTALL index e4350e3..bfbea1e 100644 --- a/INSTALL +++ b/INSTALL @@ -1,3 +1,17 @@ +### pre-requisites + +One of the big needs I'm trying to fill here is people who do not have root +access, permissions to create other userids, etc. This could be a typical +hosting provider type of thing, or -- in a corporate setting -- a very tightly +controlled server. + +Gitosis-lite requires these: + + * git itself, the more recent the better + * perl, typically installed with git, since git sort of needs it; any + version that includes `Data::Dumper`[1] will do. + * one user account on the server, with password access [2] + ### quickinstall I assume all the files pertaining to this software are untarred and available @@ -100,4 +114,41 @@ And once in a while, if you're feeling particularly BOFH-ish, take a look at ---- +Footnotes: + +[1] Actually, due to the way gitosis-lite is architected, you can manage +without `Data::Dumper` on the server if you have no choice. Only +`gl-compile-conf` needs it, so just run that on some other machine and copy +the two output files across. Cumbersome but doable... the advantage of +separating all the hard work into a manually-run piece :) + +[2] If you have *only* pubkey access, and **no** password access, then your +pubkey is already in the server's `~/.ssh/authorized_keys`. If you also need +to access git as a developer (clone, push, etc), do *not* submit this same +pubkey to gitosis-lite -- it won't work. + +Instead, create a different keypair for your "developer" role (by, e.g., +`ssh-keygen -t rsa -f ~/.ssh/gitdev`), then give `~/.ssh/gitdev.pub` to +gitosis-lite as "yourname.pub", just like you would do for any other user. + +Then you create a suitable `~/.ssh/config` to use the correct key +automatically, something like this: + + host gitadm + hostname my.server + user my_userid_on_server + + host gitdev + hostname my.server + user my_userid_on_server + identityfile ~/.ssh/gitdev + +From now on, `ssh gitadm` will get you a command line on the server, to do +gitosis-lite admin and other work. And your repository URLs would look like +`gitdev:reponame.git`. Very, very, simple... + +And as with gitosis, there's more "ssh" magic than "git" magic here :-) + +---- + gitosis-lite is released under the GPL v2 license. See COPYING for details From 7a9bf9c2960e557cea031e7adc306c2131cfeea2 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 25 Aug 2009 20:11:44 +0530 Subject: [PATCH 022/637] conf-convert: quick and dirty hack, works fine :) --- conf-convert.pl | 101 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100755 conf-convert.pl diff --git a/conf-convert.pl b/conf-convert.pl new file mode 100755 index 0000000..4f8e913 --- /dev/null +++ b/conf-convert.pl @@ -0,0 +1,101 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +# migrate gitosis.conf to gitosis-lite.conf format + +# not very smart, but there shouldn't be any errors for simple configurations. +# the biggest thing you'll find is probably some comments rearranged or +# something, due to the "flush" thing below + +# for stuff it can't handle, it'll ignore the trivial ones (like gitweb and +# daemon), and put in an obviously syntax error-ed line for "repositories" and +# "map" statements. + +my @repos; +my @RO_repos; +my @comments; +my @users; +my $groupname; + +# a gitosis.conf stanza ends when a new "[group name]" line shows up, so you +# can't write as you go; you have to accumulate and flush +sub flush { + die "repos but no users?" if (not @users and (@repos or @RO_repos)); + # just a groupname + if (@users and not (@repos or @RO_repos)) { + print "\@$groupname = ", join(" ", @users), "\n"; + } + # RW repos + if (@repos) + { + print "repo ", join(" ", @repos), "\n"; + print " RW = ", join(" ", @users), "\n"; + } + # RO repos + if (@RO_repos) + { + print "repo ", join(" ", @RO_repos), "\n"; + print " R = ", join(" ", @users), "\n"; + } + # comments; yes there'll be some reordering, sorry! + print @comments if @comments; + + # empty out for next round + @users = (); + @repos = (); + @RO_repos = (); + @comments = (); +} + +while (<>) +{ + # pure comment lines or blank lines + if (/^\s*#/ or /^\s*$/) { + push @comments, $_; + next; + } + + # not supported + if (/^repositories *=/ or /^map /) { + print STDERR "not supported: $_"; + s/^/NOT SUPPORTED: /; + print; + next; + } + + chomp; + + # normalise whitespace to help later regexes + s/\s+/ /g; + s/ ?= ?/ = /; + s/^ //; + s/ $//; + + # the chaff... + next if /^\[(gitosis|repo)\]$/ + or /^(gitweb|daemon|loglevel|description|owner) =/; + + # the wheat... + if (/^members = (.*)/) { + push @users, split(' ', $1); + next; + } + if (/^write?able = (.*)/) { + push @repos, split(' ', $1); + next; + } + if (/^readonly = (.*)/) { + push @RO_repos, split(' ', $1); + next; + } + + # new group starts + if (/^\[group (.*?) ?\]/) { + flush(); + $groupname = $1; + } +} + +flush(); From cb5a802d3e858672fe700574e9b1c02793d85cd1 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 25 Aug 2009 21:47:12 +0530 Subject: [PATCH 023/637] DAMN DAMN DAMN those lexical filehandles print FH unless () # was working fine but print $fh unless () # doesn't instead of printing $_ to $fh, it prints $fh to STDOUT. DAMN DAMN DAMN --- gl-compile-conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gl-compile-conf b/gl-compile-conf index 6568c0f..19cdbb5 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -208,7 +208,7 @@ open my $newkeys_fh, ">", $ENV{HOME} . "/.ssh/new_authkeys" # save existing authkeys minus the GL-added stuff while (<$authkeys_fh>) { - print $newkeys_fh unless (/^# gitosis-lite start/../^# gitosis-lite end/); + print $newkeys_fh $_ unless (/^# gitosis-lite start/../^# gitosis-lite end/); } # add our "start" line, each key on its own line (prefixed by command and From 09aeb311984202745e7b2fc8138b2b30a05d947d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 26 Aug 2009 06:17:27 +0530 Subject: [PATCH 024/637] project renamed to gitolite --- INSTALL | 38 +++++++++---------- README.markdown | 4 +- conf-convert.pl | 2 +- example.conf | 2 +- ...ple.gitosis-lite.rc => example.gitolite.rc | 8 ++-- gl-auth-command | 4 +- gl-compile-conf | 20 +++++----- update-hook.pl | 6 +-- 8 files changed, 42 insertions(+), 42 deletions(-) rename example.gitosis-lite.rc => example.gitolite.rc (67%) diff --git a/INSTALL b/INSTALL index bfbea1e..c815405 100644 --- a/INSTALL +++ b/INSTALL @@ -5,7 +5,7 @@ access, permissions to create other userids, etc. This could be a typical hosting provider type of thing, or -- in a corporate setting -- a very tightly controlled server. -Gitosis-lite requires these: +Gitolite requires these: * git itself, the more recent the better * perl, typically installed with git, since git sort of needs it; any @@ -21,31 +21,31 @@ A quick install, taking all the defaults, can be done with the following commands; just copy and paste them into your shell: # this one is fixed to the location shown - cp example.gitosis-lite.rc ~/.gitosis-lite.rc + cp example.gitolite.rc ~/.gitolite.rc # the destinations below are defaults; if you change the paths in the "rc" # file above, these destinations also must change accordingly # mkdir $REPO_BASE, $GL_ADMINDIR, and $GL_KEYDIR mkdir ~/repositories - mkdir ~/.gitosis-lite - mkdir ~/.gitosis-lite/keydir + mkdir ~/.gitolite + mkdir ~/.gitolite/keydir # copy sample conf to $GL_CONF - cp example.conf ~/.gitosis-lite/gitosis-lite.conf + cp example.conf ~/.gitolite/gitolite.conf # copy the 3 programs to $GL_ADMINDIR - cp update-hook.pl ~/.gitosis-lite - cp gl-auth-command ~/.gitosis-lite - cp gl-compile-conf ~/.gitosis-lite + cp update-hook.pl ~/.gitolite + cp gl-auth-command ~/.gitolite + cp gl-compile-conf ~/.gitolite # optional; copy the documents also (if you untarred the package into a # temporary directory and need to get rid of it) - cp INSTALL README.markdown ~/.gitosis-lite + cp INSTALL README.markdown ~/.gitolite ### install notes - * At present the location of `~/.gitosis-lite.rc` is fixed (maybe later I'll + * At present the location of `~/.gitolite.rc` is fixed (maybe later I'll change it to a "git config" variable). If you edit it and change any paths, be sure to keep the perl syntax -- @@ -53,11 +53,11 @@ commands; just copy and paste them into your shell: limited case. And of course, make sure you adjust the commands shown above to suit the new locations - * the config file is (by default) at `~/.gitosis-lite/gitosis-lite.conf`. + * the config file is (by default) at `~/.gitolite/gitolite.conf`. Edit the file as you wish. The comments in the file ought to be clear enough but let me know if not - * if you want to bring in existing (bare, server) repos into gitosis-lite, + * if you want to bring in existing (bare, server) repos into gitolite, this should work: * backup the repo, then move it to `$BASE_REPO` * copy `$GL_ADMINDIR/update-hook.pl` to `[reponame].git/hooks/update` -- @@ -89,7 +89,7 @@ It should all work, but the first couple of times you may want to check these `$GL_ADMINDIR/gl-auth-command` file, then some sshd restrictions, the key, etc. * `$GL_CONF_COMPILED` (default - `~/.gitosis-lite/gitosis-lite.conf-compiled.pm`) should contain an + `~/.gitolite/gitolite.conf-compiled.pm`) should contain an expanded list of the access control rules. It may look a little long, but it's fairly intuitive! @@ -110,13 +110,13 @@ And once in a while, if you're feeling particularly BOFH-ish, take a look at * when you clone an empty repo, git seems to complain about the remote hanging up or something. I have no idea what that is, but it doesn't seem - to hurt anything. This happens even in normal git, not just gitosis-lite. + to hurt anything. This happens even in normal git, not just gitolite. ---- Footnotes: -[1] Actually, due to the way gitosis-lite is architected, you can manage +[1] Actually, due to the way gitolite is architected, you can manage without `Data::Dumper` on the server if you have no choice. Only `gl-compile-conf` needs it, so just run that on some other machine and copy the two output files across. Cumbersome but doable... the advantage of @@ -125,11 +125,11 @@ separating all the hard work into a manually-run piece :) [2] If you have *only* pubkey access, and **no** password access, then your pubkey is already in the server's `~/.ssh/authorized_keys`. If you also need to access git as a developer (clone, push, etc), do *not* submit this same -pubkey to gitosis-lite -- it won't work. +pubkey to gitolite -- it won't work. Instead, create a different keypair for your "developer" role (by, e.g., `ssh-keygen -t rsa -f ~/.ssh/gitdev`), then give `~/.ssh/gitdev.pub` to -gitosis-lite as "yourname.pub", just like you would do for any other user. +gitolite as "yourname.pub", just like you would do for any other user. Then you create a suitable `~/.ssh/config` to use the correct key automatically, something like this: @@ -144,11 +144,11 @@ automatically, something like this: identityfile ~/.ssh/gitdev From now on, `ssh gitadm` will get you a command line on the server, to do -gitosis-lite admin and other work. And your repository URLs would look like +gitolite admin and other work. And your repository URLs would look like `gitdev:reponame.git`. Very, very, simple... And as with gitosis, there's more "ssh" magic than "git" magic here :-) ---- -gitosis-lite is released under the GPL v2 license. See COPYING for details +gitolite is released under the GPL v2 license. See COPYING for details diff --git a/README.markdown b/README.markdown index ac4effe..e8349ed 100644 --- a/README.markdown +++ b/README.markdown @@ -1,6 +1,6 @@ -# gitosis-lite +# gitolite -gitosis-lite is the bare essentials of gitosis, with a completely different +Gitolite is the bare essentials of gitosis, with a completely different config file that allows (at last!) access control down to the branch level, including specifying who can and cannot *rewind* a given branch. It is released under GPL v2. See COPYING for details. diff --git a/conf-convert.pl b/conf-convert.pl index 4f8e913..f9775cb 100755 --- a/conf-convert.pl +++ b/conf-convert.pl @@ -3,7 +3,7 @@ use strict; use warnings; -# migrate gitosis.conf to gitosis-lite.conf format +# migrate gitosis.conf to gitolite.conf format # not very smart, but there shouldn't be any errors for simple configurations. # the biggest thing you'll find is probably some comments rearranged or diff --git a/example.conf b/example.conf index 89f3bd0..f19a95a 100644 --- a/example.conf +++ b/example.conf @@ -1,4 +1,4 @@ -# example conf file for gitosis-lite +# example conf file for gitolite # overall syntax: # - everything in this is space-separated; no commas, semicolons, etc diff --git a/example.gitosis-lite.rc b/example.gitolite.rc similarity index 67% rename from example.gitosis-lite.rc rename to example.gitolite.rc index ddbcca8..72ad3dc 100644 --- a/example.gitosis-lite.rc +++ b/example.gitolite.rc @@ -3,17 +3,17 @@ # base directory for all the repos $REPO_BASE="repositories"; -# gitosis-lite admin directory, files, etc -$GL_ADMINDIR=$ENV{HOME} . "/.gitosis-lite"; +# gitolite admin directory, files, etc +$GL_ADMINDIR=$ENV{HOME} . "/.gitolite"; # -------------------------------------- # the ones below can be left as they are, unless for some reason you want them # elsewhere -$GL_CONF="$GL_ADMINDIR/gitosis-lite.conf"; +$GL_CONF="$GL_ADMINDIR/gitolite.conf"; $GL_KEYDIR="$GL_ADMINDIR/keydir"; -$GL_CONF_COMPILED="$GL_ADMINDIR/gitosis-lite.conf-compiled.pm"; +$GL_CONF_COMPILED="$GL_ADMINDIR/gitolite.conf-compiled.pm"; # -------------------------------------- # this should be the last line in this file, per perl rules diff --git a/gl-auth-command b/gl-auth-command index eb809ee..ffb4060 100755 --- a/gl-auth-command +++ b/gl-auth-command @@ -5,7 +5,7 @@ use strict; # === auth-command === # the command that GL users actually run -# part of the gitosis-lite (GL) suite +# part of the gitolite (GL) suite # how run: via sshd, being listed in "command=" in ssh authkeys # when: every login by a GL user @@ -29,7 +29,7 @@ our $GL_CONF_COMPILED; our $REPO_BASE; our %repos; -my $glrc = $ENV{HOME} . "/.gitosis-lite.rc"; +my $glrc = $ENV{HOME} . "/.gitolite.rc"; unless (my $ret = do $glrc) { die "parse $glrc failed: $@" if $@; diff --git a/gl-compile-conf b/gl-compile-conf index 19cdbb5..a3ae0d0 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -5,7 +5,7 @@ use Data::Dumper; # === add-auth-keys === -# part of the gitosis-lite (GL) suite +# part of the gitolite (GL) suite # (1) - "compiles" ~/.ssh/authorized_keys from the list of pub-keys # (2) - also "compiles" the user-friendly GL conf file into something easier @@ -17,13 +17,13 @@ use Data::Dumper; # how run: manual, by GL admin # when: # - anytime a pubkey is added/deleted -# - anytime gitosis-lite.conf is changed +# - anytime gitolite.conf is changed # input: -# - GL_CONF (default: ~/.gitosis-lite/gitosis-lite.conf) -# - GL_KEYDIR (default: ~/.gitosis-lite/keydir) +# - GL_CONF (default: ~/.gitolite/gitolite.conf) +# - GL_KEYDIR (default: ~/.gitolite/keydir) # output: # - ~/.ssh/authorized_keys (dictated by sshd) -# - GL_CONF_COMPILED (default: ~/.gitosis-lite/gitosis-lite.conf-compiled.pm) +# - GL_CONF_COMPILED (default: ~/.gitolite/gitolite.conf-compiled.pm) # security: # - touches a very critical system file that manages the restrictions on # incoming users. Be sure to audit AUTH_COMMAND and AUTH_OPTIONS (see @@ -46,7 +46,7 @@ our $GL_KEYDIR; our $GL_CONF_COMPILED; our $REPO_BASE; -my $glrc = $ENV{HOME} . "/.gitosis-lite.rc"; +my $glrc = $ENV{HOME} . "/.gitolite.rc"; unless (my $ret = do $glrc) { die "parse $glrc failed: $@" if $@; @@ -208,12 +208,12 @@ open my $newkeys_fh, ">", $ENV{HOME} . "/.ssh/new_authkeys" # save existing authkeys minus the GL-added stuff while (<$authkeys_fh>) { - print $newkeys_fh $_ unless (/^# gitosis-lite start/../^# gitosis-lite end/); + print $newkeys_fh $_ unless (/^# gitolite start/../^# gitolite end/); } # add our "start" line, each key on its own line (prefixed by command and # options, in the standard ssh authorized_keys format), then the "end" line. -print $newkeys_fh "# gitosis-lite start\n"; +print $newkeys_fh "# gitolite start\n"; my_chdir($GL_KEYDIR); for my $pubkey (glob("*.pub")) { @@ -221,7 +221,7 @@ for my $pubkey (glob("*.pub")) print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS "; print $newkeys_fh `cat $pubkey`; } -print $newkeys_fh "# gitosis-lite end\n"; +print $newkeys_fh "# gitolite end\n"; close $newkeys_fh or die "close newkeys failed: $!"; # check what changes are being made; just a comfort factor @@ -231,7 +231,7 @@ close $newkeys_fh or die "close newkeys failed: $!"; system("cat ~/.ssh/new_authkeys > ~/.ssh/authorized_keys"); system("rm ~/.ssh/new_authkeys"); -# if the gl admin directory (~/.gitosis-lite) is itself a git repo, do an +# if the gl admin directory (~/.gitolite) is itself a git repo, do an # autocheckin. nothing fancy; this is a "just in case" type of thing. my_chdir($GL_ADMINDIR); if (-d ".git") diff --git a/update-hook.pl b/update-hook.pl index babd005..2883a67 100755 --- a/update-hook.pl +++ b/update-hook.pl @@ -3,9 +3,9 @@ use strict; # === update === -# this is gitosis-lite's update hook +# this is gitolite's update hook -# part of the gitosis-lite (GL) suite +# part of the gitolite (GL) suite # how run: via git, being copied as .git/hooks/update in every repo # when: every push @@ -31,7 +31,7 @@ our $GL_CONF_COMPILED; our $REPO_BASE; our %repos; -my $glrc = $ENV{HOME} . "/.gitosis-lite.rc"; +my $glrc = $ENV{HOME} . "/.gitolite.rc"; unless (my $ret = do $glrc) { die "parse $glrc failed: $@" if $@; From dc4f32b14e94bc8a03d6f552c345ba93370ca48a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 26 Aug 2009 07:05:04 +0530 Subject: [PATCH 025/637] backward compat for marker lines in authkeys --- gl-compile-conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gl-compile-conf b/gl-compile-conf index a3ae0d0..76c4239 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -208,7 +208,7 @@ open my $newkeys_fh, ">", $ENV{HOME} . "/.ssh/new_authkeys" # save existing authkeys minus the GL-added stuff while (<$authkeys_fh>) { - print $newkeys_fh $_ unless (/^# gitolite start/../^# gitolite end/); + print $newkeys_fh $_ unless (/^# gito(sis-)?lite start/../^# gito(sis-)?lite end/); } # add our "start" line, each key on its own line (prefixed by command and From 0a32d46f5951ba9b2d6efbdbda0eea6c788086e3 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 25 Aug 2009 08:44:46 +0530 Subject: [PATCH 026/637] use-warnings --- gl-auth-command | 3 ++- gl-compile-conf | 3 ++- update-hook.pl | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/gl-auth-command b/gl-auth-command index ffb4060..cd3f450 100755 --- a/gl-auth-command +++ b/gl-auth-command @@ -1,6 +1,7 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl use strict; +use warnings; # === auth-command === # the command that GL users actually run diff --git a/gl-compile-conf b/gl-compile-conf index 76c4239..40d8088 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -1,6 +1,7 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl use strict; +use warnings; use Data::Dumper; # === add-auth-keys === diff --git a/update-hook.pl b/update-hook.pl index 2883a67..2e98911 100755 --- a/update-hook.pl +++ b/update-hook.pl @@ -1,6 +1,7 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl use strict; +use warnings; # === update === # this is gitolite's update hook From 43b658660db344ab85aee3c9d25503ae3dfafd86 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 26 Aug 2009 21:14:25 +0530 Subject: [PATCH 027/637] add ".mkd" to docs so gh will render them nicely, plus a couple of minor fixes --- INSTALL => INSTALL.mkd | 4 +--- README.markdown => README.mkd | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) rename INSTALL => INSTALL.mkd (99%) rename README.markdown => README.mkd (99%) diff --git a/INSTALL b/INSTALL.mkd similarity index 99% rename from INSTALL rename to INSTALL.mkd index c815405..402cc89 100644 --- a/INSTALL +++ b/INSTALL.mkd @@ -112,9 +112,7 @@ And once in a while, if you're feeling particularly BOFH-ish, take a look at hanging up or something. I have no idea what that is, but it doesn't seem to hurt anything. This happens even in normal git, not just gitolite. ----- - -Footnotes: +### Footnotes: [1] Actually, due to the way gitolite is architected, you can manage without `Data::Dumper` on the server if you have no choice. Only diff --git a/README.markdown b/README.mkd similarity index 99% rename from README.markdown rename to README.mkd index e8349ed..e7d557e 100644 --- a/README.markdown +++ b/README.mkd @@ -39,7 +39,7 @@ that: All of this pointed to a rewrite. In perl, naturally :-) -### what's extra? +### what's new Per-branch permissions. You will not believe how often I am asked this at $DAYJOB. This is almost the single reason I started *thinking* about rolling From 522b35434e2482dd6c7a0671bd9c4d848a464efb Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 27 Aug 2009 05:45:48 +0530 Subject: [PATCH 028/637] compile/INSTALL: multi-key feature code+doc --- INSTALL.mkd | 39 ++++++++++++++++++++++++++++++++++++++- gl-compile-conf | 2 +- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/INSTALL.mkd b/INSTALL.mkd index 402cc89..0df9894 100644 --- a/INSTALL.mkd +++ b/INSTALL.mkd @@ -1,3 +1,15 @@ +In this document: + + * pre-requisites + * quickinstall + * install notes + * administer + * run + * special cases + * errors, warnings, etc + +---- + ### pre-requisites One of the big needs I'm trying to fill here is people who do not have root @@ -71,7 +83,8 @@ commands; just copy and paste them into your shell: [here](http://sitaramc.github.com/0-installing/2-access-gitosis.html#generating_a_public_key)) for how to do this * for each "user" in `$GL_CONF`, copy their public key to a file called - "user.pub" in `$GL_KEYDIR` + "user.pub" in `$GL_KEYDIR`. For example, mine would be called + "sitaram.pub" * edit the config file (`$GL_CONF`) to add the new users in whatever way you like * backup your `~/.ssh/authorized_keys` file if you feel nervous :-) @@ -106,6 +119,30 @@ Just use it as normal. Every new repo mentioned has been created already, so And once in a while, if you're feeling particularly BOFH-ish, take a look at `$GL_ADMINDIR/log` :-) +### special cases + +#### one user, many keys + +Sometimes the same user needs to access the server from differnt machines +(like a desktop and a laptop, for instance). Gitolite needs to be given all +these public keys, but associate *all* of them with the same user. + +Recall from the "administer" section above that each "user" has one public key +file called "user.pub", which seems to imply a one-to-one match. + +But this is not strictly true -- gitolite allows a *filename* to have a small +"location" piece attached to it. So you can have "sitaram@laptop.pub" and +"sitaram@desktop.pub", for instance, and they'll all be treated as keys for +"sitaram". Just add both the files to "keydir/", and use the username +"sitaram" (*without* the "@location" part) in your `gitolite.conf` file. + +Advantages: if a user reports *one of his keys* is lost or needs replacing, +it's easy to remove or replace just that. + +(Gitosis keeps multiple entries in the same "user.pub", which means to delete +or change one of the keys you have to edit the file and figure out which of +the 2 or more long lines should be removed). + ### errors, warnings, etc * when you clone an empty repo, git seems to complain about the remote diff --git a/gl-compile-conf b/gl-compile-conf index 40d8088..78cc671 100755 --- a/gl-compile-conf +++ b/gl-compile-conf @@ -218,7 +218,7 @@ print $newkeys_fh "# gitolite start\n"; my_chdir($GL_KEYDIR); for my $pubkey (glob("*.pub")) { - my $user = $pubkey; $user =~ s/\.pub$//; + my $user = $pubkey; $user =~ s/(\@.+)?\.pub$//; print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS "; print $newkeys_fh `cat $pubkey`; } From 3ddc9087d3ead1561a6012bac309cbd84d8a48d6 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 27 Aug 2009 13:14:47 +0530 Subject: [PATCH 029/637] first production use: @all, leading slash I had to make two minor fixes while migrating my work repos: 1. I forgot to honor '@all'; oops! While I was about it, I also fixed the "access denied" message to show what rights were being tried when it failed. 2. I forgot that URLs can have leading slashes (I myself only use URLs like gs:reponame.git, where gs is an ssh stanza that describes the git server in question). --- gl-auth-command | 11 +++++++---- update-hook.pl | 6 ++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/gl-auth-command b/gl-auth-command index cd3f450..1fd294c 100755 --- a/gl-auth-command +++ b/gl-auth-command @@ -65,9 +65,10 @@ my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere! my $cmd = $ENV{SSH_ORIGINAL_COMMAND} or die "no SSH_ORIGINAL_COMMAND? I'm not a shell, $user!"; -# we don't like newlines or semicolons in SSH_ORIGINAL_COMMAND +# this check is largely for comic value if someone tries something outrageous; +# $cmd gets split and the pieces examined more thoroughly later anyway die "$cmd??? you're a funny guy..." - if $cmd =~ /[;\n]/; + if $cmd =~ /[<>&|;\n]/; # split into command and arguments; the pattern allows old style as well as # new style: "git-subcommand arg" or "git subcommand arg", just like gitosis @@ -77,7 +78,7 @@ die "$cmd??? you're a funny guy..." # git-receive-pack 'reponame.git' # including the single quotes -my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'(.*).git'/); +my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*).git'/); die "$verb? I don't do odd jobs, sorry..." unless $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS; @@ -91,7 +92,9 @@ die "I don't like the look of $repo, sorry!" # we know the user and repo; we just need to know what perm he's trying my $perm = ($verb =~ $R_COMMANDS ? 'R' : 'W'); -die "access denied" unless $repos{$repo}{$perm}{$user}; +die "$perm access for $repo denied to $user" + unless $repos{$repo}{$perm}{$user} + or $repos{$repo}{$perm}{'@all'}; # ---------------------------------------------------------------------------- # over to git now diff --git a/update-hook.pl b/update-hook.pl index 2e98911..709266e 100755 --- a/update-hook.pl +++ b/update-hook.pl @@ -67,8 +67,10 @@ $perm = '+' if $ref =~ m(refs/tags/) and $oldsha ne ('0' x 40); # should $perm = '+' if $ref =~ m(refs/heads/) and $oldsha ne $merge_base; -my $allowed_refs = $repos{$ENV{GL_REPO}}{$perm}{$ENV{GL_USER}}; -for my $refex (@$allowed_refs) +my @allowed_refs; +push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{$ENV{GL_USER}} }; +push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{'@all'} }; +for my $refex (@allowed_refs) # refex? sure -- a regex to match a ref against :) { if ($ref =~ /$refex/) From f0099a125eafefc551b1caba07df7e393eabd757 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 27 Aug 2009 14:00:00 +0530 Subject: [PATCH 030/637] reduce clutter by making src, doc, conf subdirectories --- example.conf => conf/example.conf | 0 example.gitolite.rc => conf/example.gitolite.rc | 0 COPYING => doc/COPYING | 0 INSTALL.mkd => doc/INSTALL.mkd | 0 TODO => doc/TODO | 0 conf-convert.pl => src/conf-convert.pl | 0 gl-auth-command => src/gl-auth-command | 0 gl-compile-conf => src/gl-compile-conf | 0 update-hook.pl => src/update-hook.pl | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename example.conf => conf/example.conf (100%) rename example.gitolite.rc => conf/example.gitolite.rc (100%) rename COPYING => doc/COPYING (100%) rename INSTALL.mkd => doc/INSTALL.mkd (100%) rename TODO => doc/TODO (100%) rename conf-convert.pl => src/conf-convert.pl (100%) rename gl-auth-command => src/gl-auth-command (100%) rename gl-compile-conf => src/gl-compile-conf (100%) rename update-hook.pl => src/update-hook.pl (100%) diff --git a/example.conf b/conf/example.conf similarity index 100% rename from example.conf rename to conf/example.conf diff --git a/example.gitolite.rc b/conf/example.gitolite.rc similarity index 100% rename from example.gitolite.rc rename to conf/example.gitolite.rc diff --git a/COPYING b/doc/COPYING similarity index 100% rename from COPYING rename to doc/COPYING diff --git a/INSTALL.mkd b/doc/INSTALL.mkd similarity index 100% rename from INSTALL.mkd rename to doc/INSTALL.mkd diff --git a/TODO b/doc/TODO similarity index 100% rename from TODO rename to doc/TODO diff --git a/conf-convert.pl b/src/conf-convert.pl similarity index 100% rename from conf-convert.pl rename to src/conf-convert.pl diff --git a/gl-auth-command b/src/gl-auth-command similarity index 100% rename from gl-auth-command rename to src/gl-auth-command diff --git a/gl-compile-conf b/src/gl-compile-conf similarity index 100% rename from gl-compile-conf rename to src/gl-compile-conf diff --git a/update-hook.pl b/src/update-hook.pl similarity index 100% rename from update-hook.pl rename to src/update-hook.pl From 4e74652b38fa4569c5533b4dbe4693c95a82f6cd Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 27 Aug 2009 15:24:08 +0530 Subject: [PATCH 031/637] source code changes after splitting into src/doc/conf --- conf/example.gitolite.rc | 4 ++-- src/gl-compile-conf | 6 +++--- src/install.sh | 25 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) create mode 100755 src/install.sh diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index 72ad3dc..7a5068d 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -11,9 +11,9 @@ $GL_ADMINDIR=$ENV{HOME} . "/.gitolite"; # the ones below can be left as they are, unless for some reason you want them # elsewhere -$GL_CONF="$GL_ADMINDIR/gitolite.conf"; +$GL_CONF="$GL_ADMINDIR/conf/gitolite.conf"; $GL_KEYDIR="$GL_ADMINDIR/keydir"; -$GL_CONF_COMPILED="$GL_ADMINDIR/gitolite.conf-compiled.pm"; +$GL_CONF_COMPILED="$GL_ADMINDIR/conf/gitolite.conf-compiled.pm"; # -------------------------------------- # this should be the last line in this file, per perl rules diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 78cc671..3388979 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -60,7 +60,7 @@ unless (my $ret = do $glrc) # ---------------------------------------------------------------------------- # command and options for authorized_keys -my $AUTH_COMMAND="$GL_ADMINDIR/gl-auth-command"; +my $AUTH_COMMAND="$GL_ADMINDIR/src/gl-auth-command"; my $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; my $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern @@ -192,7 +192,7 @@ for my $repo (keys %repos) mkdir("$repo.git") or die "mkdir $repo.git failed: $!"; my_chdir("$repo.git"); system("git init --bare"); - system("cp $GL_ADMINDIR/update-hook.pl hooks/update"); + system("cp $GL_ADMINDIR/src/update-hook.pl hooks/update"); system("chmod 755 hooks/update"); my_chdir("$ENV{HOME}/$REPO_BASE"); } @@ -237,7 +237,7 @@ system("rm ~/.ssh/new_authkeys"); my_chdir($GL_ADMINDIR); if (-d ".git") { - system("git add -A keydir"); # stage all changes in keydir + system("git add -A conf keydir"); # stage all operational data # and if there are any if (system("git diff --cached --quiet") ) { diff --git a/src/install.sh b/src/install.sh new file mode 100755 index 0000000..c2156da --- /dev/null +++ b/src/install.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# install gitolite + +# quick safety check: do not run if ~/.gitolite.rc is present + +[[ -f ~/.gitolite.rc ]] && { + echo sorry\; \'~/.gitolite.rc\' already exists + exit 1 +} + +# this one is fixed to the location shown +cp conf/example.gitolite.rc ~/.gitolite.rc + +# the destinations below are defaults; if you change the paths in the "rc" +# file above, these destinations also must change accordingly + +# mkdir $REPO_BASE, $GL_ADMINDIR, it's subdirs, and $GL_KEYDIR +mkdir ~/repositories +mkdir ~/.gitolite +mkdir ~/.gitolite/{src,conf,doc,keydir} + +# copy conf, src, doc +cp -a src doc conf ~/.gitolite +cp conf/example.conf ~/.gitolite/conf/gitolite.conf From 00b4baa435eb61f55f7d460b1a34a237a795ba97 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 27 Aug 2009 15:24:23 +0530 Subject: [PATCH 032/637] doc changes after split --- doc/0-INSTALL.mkd | 84 ++++++++++++++++++ doc/1-migrate.mkd | 3 + doc/2-admin.mkd | 35 ++++++++ doc/3-faq-tips-etc.mkd | 38 +++++++++ doc/INSTALL.mkd | 189 ----------------------------------------- doc/TODO | 8 -- 6 files changed, 160 insertions(+), 197 deletions(-) create mode 100644 doc/0-INSTALL.mkd create mode 100644 doc/1-migrate.mkd create mode 100644 doc/2-admin.mkd create mode 100644 doc/3-faq-tips-etc.mkd delete mode 100644 doc/INSTALL.mkd diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd new file mode 100644 index 0000000..9a8f2fc --- /dev/null +++ b/doc/0-INSTALL.mkd @@ -0,0 +1,84 @@ +# installing gitolite + +### pre-requisites + +One of the big needs I'm trying to fill here is people who do not have root +access, permissions to create other userids, etc. This could be a typical +hosting provider type of thing, or -- in a corporate setting -- a very tightly +controlled server. + +Gitolite requires these: + + * git itself, the more recent the better + * perl, typically installed with git, since git sort of needs it; any + version that includes `Data::Dumper`[1] will do. + * one user account on the server, with password access [2] + +### quickinstall + +I assume all the files pertaining to this software are untarred and available +in the current directory. + +A quick install, taking all the defaults, can be done with the `install.sh` +script in the `src` directory. + +Note: + + * At present the location of `~/.gitolite.rc` is fixed (maybe later I'll + change it to a "git config" variable). + + If you edit it and change any paths, be sure to keep the perl syntax -- + you *don't* have to know perl to do so, it's fairly easy to guess in this + limited case. And of course, make sure you adjust the commands shown + above to suit the new locations + + * the config file is (by default) at `~/.gitolite/conf/gitolite.conf`. + Edit the file as you wish. The comments in the file ought to be clear + enough but let me know if not + + * if you want to bring in existing (bare, server) repos into gitolite, this + should work: + * backup the repo, then move it to `$BASE_REPO` + * copy `$GL_ADMINDIR/src/update-hook.pl` to + `[reponame].git/hooks/update` -- if you don't do this, per branch + restrictions will not work + * then update the keys and the config file and "compile" + +### Footnotes: + +[1] Actually, due to the way gitolite is architected, you can manage +without `Data::Dumper` on the server if you have no choice. Only +`gl-compile-conf` needs it, so just run that on some other machine and copy +the two output files across. Cumbersome but doable... the advantage of +separating all the hard work into a manually-run piece :) + +[2] If you have *only* pubkey access, and **no** password access, then your +pubkey is already in the server's `~/.ssh/authorized_keys`. If you also need +to access git as a developer (clone, push, etc), do *not* submit this same +pubkey to gitolite -- it won't work. + +Instead, create a different keypair for your "developer" role (by, e.g., +`ssh-keygen -t rsa -f ~/.ssh/gitdev`), then give `~/.ssh/gitdev.pub` to +gitolite as "yourname.pub", just like you would do for any other user. + +Then you create a suitable `~/.ssh/config` to use the correct key +automatically, something like this: + + host gitadm + hostname my.server + user my_userid_on_server + + host gitdev + hostname my.server + user my_userid_on_server + identityfile ~/.ssh/gitdev + +From now on, `ssh gitadm` will get you a command line on the server, to do +gitolite admin and other work. And your repository URLs would look like +`gitdev:reponame.git`. Very, very, simple... + +And as with gitosis, there's more "ssh" magic than "git" magic here :-) + +---- + +gitolite is released under the GPL v2 license. See COPYING for details diff --git a/doc/1-migrate.mkd b/doc/1-migrate.mkd new file mode 100644 index 0000000..c1061cb --- /dev/null +++ b/doc/1-migrate.mkd @@ -0,0 +1,3 @@ +# migrating from gitosis to gitolite + + diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd new file mode 100644 index 0000000..8c9ae56 --- /dev/null +++ b/doc/2-admin.mkd @@ -0,0 +1,35 @@ +# administering and running gitolite + +### administer + + * ask each user who will get access to send you a public key. See other + sources (for example + [here](http: /sitaramc.github.com/0-installing/2-access-gitolite.html#generating_a_public_key)) + for how to do this + * for each "user" in `$GL_CONF`, copy their public key to a file called + "user.pub" in `$GL_KEYDIR`. For example, mine would be called + "sitaram.pub" + * edit the config file (`$GL_CONF`) to add the new users in whatever way you + like + * backup your `~/.ssh/authorized_keys` file if you feel nervous :-) + * cd to `$GL_ADMINDIR` and run `src/gl-compile-conf` + +That should be it, really. However, if you want to be doubly sure, or maybe +the first couple of times you use it, you may want to check these: + + * check the outputs + + * `~/.ssh/authorized_keys` should contain one line for each "user" pub + key added, between two "marker" lines (which you should please please + not remove!). The line should contain a "command=" pointing to a + `$GL_ADMINDIR/src/gl-auth-command` file, then some sshd restrictions, the + key, etc. + * `$GL_CONF_COMPILED` (default + `~/.gitolite/conf/gitolite.conf-compiled.pm`) should contain an + expanded list of the access control rules. It may look a little long, + but it's fairly intuitive! + + * if the run threw up any "initialising empty repo" messages, check the + individual repos (inside `$REPO_BASE`) if you wish. Especially make sure + the `$REPO_BASE/[reponame].git/hooks/update` got copied OK and is + executable diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd new file mode 100644 index 0000000..ed3754e --- /dev/null +++ b/doc/3-faq-tips-etc.mkd @@ -0,0 +1,38 @@ +# assorted faqs, tips, and notes on gitolite + +### errors, warnings, notes... + + * cloning an empty repo is only possible with clients greater than 1.6.2. + So at least one of your clients needs to have a recent git. Once at least + one commit has been made, older clients can also use it. + + * when you clone an empty repo, git seems to complain about the remote + hanging up or something. I have no idea what that is, but it doesn't seem + to hurt anything. This happens even in normal git, not just gitolite. + + * once in a while, if you're feeling particularly BOFH-ish, take a look at + `$GL_ADMINDIR/log` :-) + +### special cases + +#### one user, many keys + +Sometimes the same user needs to access the server from differnt machines +(like a desktop and a laptop, for instance). Gitolite needs to be given all +these public keys, but associate *all* of them with the same user. + +Recall from the "admin" document that each "user" has one public key file +called "user.pub", which seems to imply a one-to-one match. + +But this is not strictly true -- gitolite allows a *filename* to have a small +"location" piece attached to it. So you can have "sitaram@laptop.pub" and +"sitaram@desktop.pub", for instance, and they'll all be treated as keys for +"sitaram". Just add both the files to "keydir/", and use the username +"sitaram" (*without* the "@location" part) in your `gitolite.conf` file. + +Advantages: if a user reports *one of his keys* is lost or needs replacing, +it's easy to remove or replace just that. + +(Contrast with gitosis, which keeps multiple entries in the same "user.pub" +file. Deleting or changing one of the keys involves editing the file and +figuring out which key is the right one!) diff --git a/doc/INSTALL.mkd b/doc/INSTALL.mkd deleted file mode 100644 index 0df9894..0000000 --- a/doc/INSTALL.mkd +++ /dev/null @@ -1,189 +0,0 @@ -In this document: - - * pre-requisites - * quickinstall - * install notes - * administer - * run - * special cases - * errors, warnings, etc - ----- - -### pre-requisites - -One of the big needs I'm trying to fill here is people who do not have root -access, permissions to create other userids, etc. This could be a typical -hosting provider type of thing, or -- in a corporate setting -- a very tightly -controlled server. - -Gitolite requires these: - - * git itself, the more recent the better - * perl, typically installed with git, since git sort of needs it; any - version that includes `Data::Dumper`[1] will do. - * one user account on the server, with password access [2] - -### quickinstall - -I assume all the files pertaining to this software are untarred and available -in the current directory. - -A quick install, taking all the defaults, can be done with the following -commands; just copy and paste them into your shell: - - # this one is fixed to the location shown - cp example.gitolite.rc ~/.gitolite.rc - - # the destinations below are defaults; if you change the paths in the "rc" - # file above, these destinations also must change accordingly - - # mkdir $REPO_BASE, $GL_ADMINDIR, and $GL_KEYDIR - mkdir ~/repositories - mkdir ~/.gitolite - mkdir ~/.gitolite/keydir - - # copy sample conf to $GL_CONF - cp example.conf ~/.gitolite/gitolite.conf - - # copy the 3 programs to $GL_ADMINDIR - cp update-hook.pl ~/.gitolite - cp gl-auth-command ~/.gitolite - cp gl-compile-conf ~/.gitolite - - # optional; copy the documents also (if you untarred the package into a - # temporary directory and need to get rid of it) - cp INSTALL README.markdown ~/.gitolite - -### install notes - - * At present the location of `~/.gitolite.rc` is fixed (maybe later I'll - change it to a "git config" variable). - - If you edit it and change any paths, be sure to keep the perl syntax -- - you *don't* have to know perl to do so, it's fairly easy to guess in this - limited case. And of course, make sure you adjust the commands shown - above to suit the new locations - - * the config file is (by default) at `~/.gitolite/gitolite.conf`. - Edit the file as you wish. The comments in the file ought to be clear - enough but let me know if not - - * if you want to bring in existing (bare, server) repos into gitolite, - this should work: - * backup the repo, then move it to `$BASE_REPO` - * copy `$GL_ADMINDIR/update-hook.pl` to `[reponame].git/hooks/update` -- - if you don't do this, per branch restrictions will not work - * then update the keys and the config file and "compile" - -### administer - - * ask each user who will get access to send you a public key. See other - sources (for example - [here](http://sitaramc.github.com/0-installing/2-access-gitosis.html#generating_a_public_key)) - for how to do this - * for each "user" in `$GL_CONF`, copy their public key to a file called - "user.pub" in `$GL_KEYDIR`. For example, mine would be called - "sitaram.pub" - * edit the config file (`$GL_CONF`) to add the new users in whatever way you - like - * backup your `~/.ssh/authorized_keys` file if you feel nervous :-) - * cd to `$GL_ADMINDIR` and run `./gl-compile-conf` - -#### optional -- if you want to be doubly sure - -It should all work, but the first couple of times you may want to check these - - * check the outputs - - * `~/.ssh/authorized_keys` should contain one line for each "user" pub - key added, between two "marker" lines (which you should please please - not remove!). The line should contain a "command=" pointing to a - `$GL_ADMINDIR/gl-auth-command` file, then some sshd restrictions, the - key, etc. - * `$GL_CONF_COMPILED` (default - `~/.gitolite/gitolite.conf-compiled.pm`) should contain an - expanded list of the access control rules. It may look a little long, - but it's fairly intuitive! - - * if the run threw up any "initialising empty repo" messages, check the - individual repos (inside `$REPO_BASE`) if you wish. Especially make sure - the `$REPO_BASE/[reponame].git/hooks/update` got copied OK and is - executable - -### run - -Just use it as normal. Every new repo mentioned has been created already, so -(as long as your clients are using git > 1.6.2), you can just clone it. - -And once in a while, if you're feeling particularly BOFH-ish, take a look at -`$GL_ADMINDIR/log` :-) - -### special cases - -#### one user, many keys - -Sometimes the same user needs to access the server from differnt machines -(like a desktop and a laptop, for instance). Gitolite needs to be given all -these public keys, but associate *all* of them with the same user. - -Recall from the "administer" section above that each "user" has one public key -file called "user.pub", which seems to imply a one-to-one match. - -But this is not strictly true -- gitolite allows a *filename* to have a small -"location" piece attached to it. So you can have "sitaram@laptop.pub" and -"sitaram@desktop.pub", for instance, and they'll all be treated as keys for -"sitaram". Just add both the files to "keydir/", and use the username -"sitaram" (*without* the "@location" part) in your `gitolite.conf` file. - -Advantages: if a user reports *one of his keys* is lost or needs replacing, -it's easy to remove or replace just that. - -(Gitosis keeps multiple entries in the same "user.pub", which means to delete -or change one of the keys you have to edit the file and figure out which of -the 2 or more long lines should be removed). - -### errors, warnings, etc - - * when you clone an empty repo, git seems to complain about the remote - hanging up or something. I have no idea what that is, but it doesn't seem - to hurt anything. This happens even in normal git, not just gitolite. - -### Footnotes: - -[1] Actually, due to the way gitolite is architected, you can manage -without `Data::Dumper` on the server if you have no choice. Only -`gl-compile-conf` needs it, so just run that on some other machine and copy -the two output files across. Cumbersome but doable... the advantage of -separating all the hard work into a manually-run piece :) - -[2] If you have *only* pubkey access, and **no** password access, then your -pubkey is already in the server's `~/.ssh/authorized_keys`. If you also need -to access git as a developer (clone, push, etc), do *not* submit this same -pubkey to gitolite -- it won't work. - -Instead, create a different keypair for your "developer" role (by, e.g., -`ssh-keygen -t rsa -f ~/.ssh/gitdev`), then give `~/.ssh/gitdev.pub` to -gitolite as "yourname.pub", just like you would do for any other user. - -Then you create a suitable `~/.ssh/config` to use the correct key -automatically, something like this: - - host gitadm - hostname my.server - user my_userid_on_server - - host gitdev - hostname my.server - user my_userid_on_server - identityfile ~/.ssh/gitdev - -From now on, `ssh gitadm` will get you a command line on the server, to do -gitolite admin and other work. And your repository URLs would look like -`gitdev:reponame.git`. Very, very, simple... - -And as with gitosis, there's more "ssh" magic than "git" magic here :-) - ----- - -gitolite is released under the GPL v2 license. See COPYING for details diff --git a/doc/TODO b/doc/TODO index 7f3a611..9c6faef 100644 --- a/doc/TODO +++ b/doc/TODO @@ -1,11 +1,3 @@ - * a proper INSTALL file with clear instructions for non-experts - * make a proper test suite - * `use Git` instead of run git commands: I don't run too many git commands - in this and Git.pm just runs the same commands (per the documentation), so - it's sorta moot right now, but it's worth trying - * change the "rc" file to use "gitconfig" instead... - - * check user/group names with some suitable regex From 3161b0ac86d1de03499ea2122ed3f957d0b96acc Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 28 Aug 2009 06:26:23 +0530 Subject: [PATCH 033/637] migration document added --- doc/1-migrate.mkd | 88 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/doc/1-migrate.mkd b/doc/1-migrate.mkd index c1061cb..045946c 100644 --- a/doc/1-migrate.mkd +++ b/doc/1-migrate.mkd @@ -1,3 +1,91 @@ # migrating from gitosis to gitolite +Migrating from gitosis to gitolite is pretty easy, because the basic design is +the same. The differences are: + * gitolite does not use a special repo for the configuration, pubkeys, etc. + You can choose to version that directory but it is not required that you + do so + +Here's how we migrated my work repos: + +1. login as the `git` user on the server, and get a bash shell prompt + +2. **disable gitosis** by renaming `/usr/bin/gitosis-serve` to something + else. This will prevent users from pushing anything while you do the + backup, migration, etc. + +3. For added safety, **delete** the post-update hook that gitosis-admin + installed + + rm ~/repositories/gitosis-admin.git/hooks/post-update + + or at least rename it to `.sample` like all the other hooks hanging + around, or edit it and comment out the line that calls `gitosis-run-hook + post-update`. + + If you do not do this, an accidental push to the gitosis-admin repo will + mess up your `~/.ssh/authorized_keys` file + +4. take a **backup** of the `~/repositories` directory + +5. untar gitolite to some temporary directory and follow the instructions to + **install** it. Some of the steps (like `mkdir ~/repositories`) will + fail, but this is expected. Once this is done, cd to the `$GL_ADMINDIR` + (by default `~/.gitolite`) + + cd ~/.gitolite + +6. **convert** your gitosis config file: + + src/conf-convert.pl < ~/.gitosis.conf > conf/gitolite.conf + + be sure to check the file to make sure it converted correctly + +7. **copy** the update hook to each of the existing repos + + for i in ~/repositories/*.git + do + cp src/update-hook.pl $i/hooks/update + done + +8. **copy** the keys from gitosis's keydir + + cp ~/repositories/gitosis-admin.git/gitosis-export/keydir/* keydir + +9. **Important: expand** any multi-key filess you may have. See the "faq, + tips, etc" document in the doc directory for an explanation of what + multi-keys are, how gitosis does them and how gitolite does it + differently. + + You can split the keys manually, or use the following code (just + copy-paste it into you xterm): + + wc -l keydir/*.pub | grep -v total | grep -v -w 1 | while read a b + do + i=1 + cat $b|while read l + do + echo "$l" > ${b%.pub}@$i.pub + (( i++ )) + done + v $b $b.done + done + + This will split each multi-key file (say "sitaram.pub") into individual + files called "sitaram@1.pub", "sitaram@2.pub", etc., and rename the + original to "sitaram.pub.done" so gitolite won't pick it up. + + At this point you can rename the split parts more appropriately, like + "sitaram@laptop.pub" and "sitaram@desktop.pub" or whatever. *Please check + the files to make sure this worked properly* + +10. **edit** `~/.ssh/authorized_keys` and **carefully** remove all the lines + containing "gitosis-serve", as well as the marker line that says + "auto-generated by gitosis, DO NOT REMOVE", then save the file. If the + file did not have any other keys and is now empty, don't worry -- save it + anyway because gitolite expects the file to be present (even if it is + empty). + +At this point you're ready to "compile" the configuration. See the "admin" +document for what to do, and how to check the outputs, etc. From 491b3fac36137d33456dfcaa6684f1ef5376009f Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 28 Aug 2009 07:16:40 +0530 Subject: [PATCH 034/637] you can't array-deref an undefined value! --- src/update-hook.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/update-hook.pl b/src/update-hook.pl index 709266e..470cf8f 100755 --- a/src/update-hook.pl +++ b/src/update-hook.pl @@ -68,8 +68,8 @@ $perm = '+' if $ref =~ m(refs/tags/) and $oldsha ne ('0' x 40); $perm = '+' if $ref =~ m(refs/heads/) and $oldsha ne $merge_base; my @allowed_refs; -push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{$ENV{GL_USER}} }; -push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{'@all'} }; +push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{$ENV{GL_USER}} || [] }; +push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{'@all'} || [] }; for my $refex (@allowed_refs) # refex? sure -- a regex to match a ref against :) { From 4bea8a9ae7c58d0e5cd27082ae3b25136379c68d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 28 Aug 2009 09:41:52 +0530 Subject: [PATCH 035/637] cp -a changed to cp -R for solaris compat --- src/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/install.sh b/src/install.sh index c2156da..5b9e537 100755 --- a/src/install.sh +++ b/src/install.sh @@ -21,5 +21,5 @@ mkdir ~/.gitolite mkdir ~/.gitolite/{src,conf,doc,keydir} # copy conf, src, doc -cp -a src doc conf ~/.gitolite +cp -R src doc conf ~/.gitolite cp conf/example.conf ~/.gitolite/conf/gitolite.conf From 23e4c209eb671e50a7b391f7593de5a999e1a100 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 28 Aug 2009 18:29:05 +0530 Subject: [PATCH 036/637] README: added para about selective rewind, plus some minor fixes --- README.mkd | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/README.mkd b/README.mkd index e7d557e..aa97140 100644 --- a/README.mkd +++ b/README.mkd @@ -23,7 +23,7 @@ a typical $DAYJOB setting, there are some issues: and be done * often, "python-setuptools" isn't installed (and on a Solaris9 I was trying to help remotely, we never did manage to install it eventually) - * the most requested feature (see "what's extra?") had to be written anyway + * the most requested feature (see "what's new?") had to be written anyway ### what's gone @@ -45,15 +45,17 @@ Per-branch permissions. You will not believe how often I am asked this at $DAYJOB. This is almost the single reason I started *thinking* about rolling my own gitosis in the first place. +It's not just "read-only" versus "read-write". Rewinding a branch (aka "non +fast forward push") is potentially dangerous, but sometimes needed. So is +deleting a branch (which is really just an extreme form of rewind). I needed +something in between allowing anyone to do it (the default) and disabling it +completely (`receive.denyNonFastForwards` or `receive.denyDeletes`). + Take a look at the example config file in the repo to see how I do this. I copied the basic idea from `update-hook-example.txt` (it's one of the "howto"s -that come with the git source tree). This includes not just who can push to -what branch, but also whether they are allowed to rewind it or not (non-ff -push). - -However, please note the difference in the size and complexity of the -*operational code* between the update hook in that example, and in mine :-) -The reason is in the next section. +that come with the git source tree). However, please note the difference in +the size and complexity of the *operational code* between the update hook in +that example, and in mine :-) The reason is in the next section. ### the workflow @@ -71,8 +73,7 @@ So I changed the workflow completely: * all admin changes happen *on the server*, in a special directory that contains the config and the users' pubkeys. But there's no commit and - push afterward. Nothing prevents you from version-controlling that - directory if you wish to, but it's not *required* + push afterward * instead, after making changes, you "compile" the configuration. This refreshes `~/.ssh/authorized_keys`, as well as puts a parsed form of the access list in a file for the other two pieces to use. @@ -80,15 +81,20 @@ So I changed the workflow completely: The pre-parsed form is basically a huge perl variable. It's human readable too (never mind what the python guys say!) -Advantages: all the complexity of parsing and error checking the parse is done -away from the two places where the actual access control happens, which are: +So the admin knows immediately if the config file had any problems, which is +good. Also, the relatively complex parse code is not part of the actual +access control points, which are: * the program that is run via `~/.ssh/authorized_keys` (I call it `gl-auth-command`, equivalent to `gitosis-serve`); this decides whether git should even be allowed to run (basic R/W/no access) * the update-hook on each repo, which decides the per-branch permissions ----- +### footnotes [1] I hate whitespace to mean anything significant except for text; this is a personal opinion *only*, so pythonistas please back off :-) + +### contact + +sitaramc@gmail.com From 352208759189bfa5edc8743f5cbb9e5a0d60ba75 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 29 Aug 2009 11:36:26 +0530 Subject: [PATCH 037/637] compile: REPO_BASE need not be under $HOME In the "create new repos" loop, we need an absolute value for REPO_BASE, in order to be able to chdir back and forth. But (taking the "normal user with no privileges" assumption too far!) we assumed REPO_BASE would be within $HOME, and relative to it. So it fails when someone wants the repo_base elsewhere. Now we don't prefix $HOME if REPO_BASE is already absolute (begins with a "/") bug reported by evocallaghan --- conf/example.gitolite.rc | 2 +- src/gl-compile-conf | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index 7a5068d..44413a0 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -1,6 +1,6 @@ # this is meant to be pulled into a perl program using "do" -# base directory for all the repos +# base directory for all the repos (absolute, or relative to $HOME) $REPO_BASE="repositories"; # gitolite admin directory, files, etc diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 3388979..811c325 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -184,7 +184,10 @@ close $compiled_fh or die "close compiled-conf failed: $!"; # did not have that luxury, so it was forced to detect the first push and # create it then -my_chdir("$ENV{HOME}/$REPO_BASE"); +# repo-base needs to be an absolute path for this loop to work right +# so if it was not already absolute, prefix $HOME. +my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); +my_chdir("$repo_base_abs"); for my $repo (keys %repos) { unless (-d "$repo.git") @@ -194,7 +197,7 @@ for my $repo (keys %repos) system("git init --bare"); system("cp $GL_ADMINDIR/src/update-hook.pl hooks/update"); system("chmod 755 hooks/update"); - my_chdir("$ENV{HOME}/$REPO_BASE"); + my_chdir("$repo_base_abs"); } } From d27af37d21bcfd9ccc212743d3d3f1a004a25787 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 28 Aug 2009 19:09:31 +0530 Subject: [PATCH 038/637] faq-tips-etc: completely revamped; big "differences from gitosis" section, etc --- doc/3-faq-tips-etc.mkd | 113 +++++++++++++++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 16 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index ed3754e..bd66101 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -1,5 +1,14 @@ # assorted faqs, tips, and notes on gitolite +In this document: + + * errors, warnings, notes... + * differences from gitosis + * one user, many keys + * who am I? + * cool ideas I want feedback on + * developer specific branches + ### errors, warnings, notes... * cloning an empty repo is only possible with clients greater than 1.6.2. @@ -13,26 +22,98 @@ * once in a while, if you're feeling particularly BOFH-ish, take a look at `$GL_ADMINDIR/log` :-) -### special cases +### differences from gitosis + +Apart from the big ones listed in the top level README, and subjective ones +like "better config file format", there are some small, but significant and +concrete, differences from gitosis. + +#### built-in logging + +...just in case of emergency :-) + +Let's say you gave a dev the right to rewind a branch and he went and rewound +it all the way, or pushed something drastically different on it. Now you need +to recover the commit that got wiped out. + +If you'd remembered to `git config core.logAllRefUpdates` for that repo, or +globally, you'd be fine -- the reflog will tell you. Otherwise you'd be left +grubbing around in `git fsck --unreachable` a bit :-( + +And even if you recover the correct commit, you'll never know *who* did it -- +not unless you add a one-line patch to gitosis, plus a `post-receive` hook to +every repository. + +With gitolite, there's a log file in `$GL_ADMINDIR` that contains lines like +this [I have abbreviated the SHAs for brevity in this document; the actual log +file will have all 40 characters]: + + +: username reponame refs/heads/branchname d0188d1 c5c00b6 + +The "+" at the start indicates a non-fast forward update, in this case from +d0188d1 to c5c00b6 So d0188d1 is the one to restore! Can it get easier? #### one user, many keys -Sometimes the same user needs to access the server from differnt machines -(like a desktop and a laptop, for instance). Gitolite needs to be given all -these public keys, but associate *all* of them with the same user. +I have a laptop and a desktop I need to access the server from. I have +different private keys on them, but as far as gitolite is concerned both of +them should be treated as "sitaram". How does this work? -Recall from the "admin" document that each "user" has one public key file -called "user.pub", which seems to imply a one-to-one match. +In gitosis, the admin creates a single "sitaram.pub" containing one line for +each of my pubkeys. In gitolite, we keep them separate: "sitaram@laptop.pub" +and "sitaram@desktop.pub". The part before the "@" is the username, so +gitolite knows these two keys belong to the same person. -But this is not strictly true -- gitolite allows a *filename* to have a small -"location" piece attached to it. So you can have "sitaram@laptop.pub" and -"sitaram@desktop.pub", for instance, and they'll all be treated as keys for -"sitaram". Just add both the files to "keydir/", and use the username -"sitaram" (*without* the "@location" part) in your `gitolite.conf` file. +I think this is easier to maintain if you have to delete or change one of +those keys. -Advantages: if a user reports *one of his keys* is lost or needs replacing, -it's easy to remove or replace just that. +#### who am I? -(Contrast with gitosis, which keeps multiple entries in the same "user.pub" -file. Deleting or changing one of the keys involves editing the file and -figuring out which key is the right one!) +As a developer, I send a file called "id_rsa.pub" to the gitolite admin. He +would rename it to "sitaram.pub" and put it in the key directory. Then he'd +add "sitaram" to the config file for the repos which I have access to. + +But he could have called me "foobar" instead of "sitaram" -- as long as he +uses it consistently, it'll all work the same and look the same to me, because +the public key is all that matters. + +So do I have no reason to know what the admin named me? Well -- maybe (see +next section for one possible use). Anyway how do I find out? + +In gitolite, it's simple: just ask nicely :-) + + $ ssh git@my.gitolite.server + PTY allocation request failed on channel 0 + no SSH_ORIGINAL_COMMAND? I'm not a shell, sitaram! + +### cool ideas + +#### developer specific branches + +So I know what gitolite calls me. Big deal... who cares? + +Here is a cool idea: allow me to create, rewind, or delete any branch whose +full name matches this pattern: + + $PERSONAL_BRANCH_PREFIX/sitaram-.* + +The admin could set `$PERSONAL_BRANCH_PREFIX` in the rc file and communicate +this to all users. It could be something like `refs/heads/personal`, which +means all such branches will show up in `git branch` lookups and `git clone` +will fetch them. Or he could use, say, `refs/personal`, which means it won't +show up in any normal "branch-y" commands and stuff, and generally be much +less noisy. + +Yes, I know git is all about allowing private branches, but in a corporate +environment it's not always possible to pull from a co-worker, for the same +reasons you don't have anonymous access (like the git:// protocol). A normal +developer workstation cannot do authentication, so how would they know who's +pulling? This is a perfect way to share code *without* cluttering the global +namespace, and each developer controls his/her own set of branches! + +The amount of code needed? *One line!* I'll spend about 3x more on declaring +and initialising the new variable, and 30x more on documenting it :-) + +**That** is how neatly gitolite is designed... + +/me pats himself on the back ;-) From 4c2c55f2d17ef4dcc0934e7bd3c1588de4c9c498 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 29 Aug 2009 10:51:48 +0530 Subject: [PATCH 039/637] admin doc: clarified the instructions a little more ...it seems some admins are, well, not quite ready to be admins :) (also some minor typo fixes slipped in) --- doc/1-migrate.mkd | 6 +++--- doc/2-admin.mkd | 20 +++++++++++++------- doc/3-faq-tips-etc.mkd | 2 +- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/doc/1-migrate.mkd b/doc/1-migrate.mkd index 045946c..ca520c6 100644 --- a/doc/1-migrate.mkd +++ b/doc/1-migrate.mkd @@ -53,13 +53,13 @@ Here's how we migrated my work repos: cp ~/repositories/gitosis-admin.git/gitosis-export/keydir/* keydir -9. **Important: expand** any multi-key filess you may have. See the "faq, +9. **Important: expand** any multi-key files you may have. See the "faq, tips, etc" document in the doc directory for an explanation of what multi-keys are, how gitosis does them and how gitolite does it differently. You can split the keys manually, or use the following code (just - copy-paste it into you xterm): + copy-paste it into your xterm): wc -l keydir/*.pub | grep -v total | grep -v -w 1 | while read a b do @@ -69,7 +69,7 @@ Here's how we migrated my work repos: echo "$l" > ${b%.pub}@$i.pub (( i++ )) done - v $b $b.done + mv $b $b.done done This will split each multi-key file (say "sitaram.pub") into individual diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index 8c9ae56..7f1c507 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -1,16 +1,22 @@ # administering and running gitolite +Note: some of the paths in this document use variable names. Just refer to +`~/.gitolite.rc` for the correct values, assuming you followed the +instructions in the "INSTALL" document. + ### administer * ask each user who will get access to send you a public key. See other sources (for example - [here](http: /sitaramc.github.com/0-installing/2-access-gitolite.html#generating_a_public_key)) + [here](http://sitaramc.github.com/0-installing/2-access-gitolite.html#generating_a_public_key)) for how to do this - * for each "user" in `$GL_CONF`, copy their public key to a file called - "user.pub" in `$GL_KEYDIR`. For example, mine would be called - "sitaram.pub" - * edit the config file (`$GL_CONF`) to add the new users in whatever way you - like + * rename each public key according to the user's name, with a `.pub` + extension, like `sitaram.pub` or `john-smith.pub`. You can also use + periods and underscores + * copy all these `*.pub` files to `$GL_KEYDIR` + * edit the config file (`$GL_CONF`) and give the new users permissions as + required. The users names should be exactly the same as their keyfile + names, but without the `.pub` extension * backup your `~/.ssh/authorized_keys` file if you feel nervous :-) * cd to `$GL_ADMINDIR` and run `src/gl-compile-conf` @@ -18,7 +24,7 @@ That should be it, really. However, if you want to be doubly sure, or maybe the first couple of times you use it, you may want to check these: * check the outputs - + * `~/.ssh/authorized_keys` should contain one line for each "user" pub key added, between two "marker" lines (which you should please please not remove!). The line should contain a "command=" pointing to a diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index bd66101..d7b3994 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -69,7 +69,7 @@ those keys. #### who am I? -As a developer, I send a file called "id_rsa.pub" to the gitolite admin. He +As a developer, I send a file called `id_rsa.pub` to the gitolite admin. He would rename it to "sitaram.pub" and put it in the key directory. Then he'd add "sitaram" to the config file for the repos which I have access to. From b1c329dbb64bc71e00b57e31bf255903479217be Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 30 Aug 2009 12:08:54 +0530 Subject: [PATCH 040/637] doc fixes: - install is even clearer now (I hope!), esp to people with root access who seem to expect something else :) - used path vars (from ~/.gitolite.rc) more consistently, and - added refeerences to ~/.gitolite.rc for resolving them --- doc/0-INSTALL.mkd | 43 +++++++++++++++++++++++++------------------ doc/1-migrate.mkd | 22 +++++++++++----------- doc/2-admin.mkd | 11 ++++------- 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index 9a8f2fc..970237e 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -2,47 +2,54 @@ ### pre-requisites -One of the big needs I'm trying to fill here is people who do not have root -access, permissions to create other userids, etc. This could be a typical -hosting provider type of thing, or -- in a corporate setting -- a very tightly -controlled server. - -Gitolite requires these: +If you managed to install git, you might already have what gitolite needs: * git itself, the more recent the better * perl, typically installed with git, since git sort of needs it; any version that includes `Data::Dumper`[1] will do. * one user account on the server, with password access [2] -### quickinstall +A major objective is to allow use by people without root access, permissions +to create other userids, etc. If you have root, congratulations and all that, +but go add a user just for gitolite and do all this from that user. Really. +I see no earthly reason for this to run as root. -I assume all the files pertaining to this software are untarred and available -in the current directory. +And don't bug me about wanting to install it in `/opt` or whatever if you +haven't the time to read the docs or change a path in a config file. -A quick install, taking all the defaults, can be done with the `install.sh` -script in the `src` directory. +### quick install -Note: + * cd to the directory where you unpacked the source + * run `src/install.pl` and follow the prompts + +**When you are told to edit some file, please read the comments in the file**. +And if you can make some time to read the documentation, please do. +Especially if you have problems. + +Notes: * At present the location of `~/.gitolite.rc` is fixed (maybe later I'll - change it to a "git config" variable). + change it to a "git config" variable but I don't see much need right now) If you edit it and change any paths, be sure to keep the perl syntax -- you *don't* have to know perl to do so, it's fairly easy to guess in this limited case. And of course, make sure you adjust the commands shown above to suit the new locations - * the config file is (by default) at `~/.gitolite/conf/gitolite.conf`. - Edit the file as you wish. The comments in the file ought to be clear - enough but let me know if not + * the config file is (by default) at `~/.gitolite/conf/gitolite.conf`, + though you can change its location in the "rc" file. Edit the file as you + wish. The comments in the file ought to be clear enough but let me know + if not * if you want to bring in existing (bare, server) repos into gitolite, this - should work: + should work (refer to `~/.gitolite.rc` for *your* values of the pathnames + below): * backup the repo, then move it to `$BASE_REPO` * copy `$GL_ADMINDIR/src/update-hook.pl` to `[reponame].git/hooks/update` -- if you don't do this, per branch restrictions will not work - * then update the keys and the config file and "compile" + * then update the keys and the config file and "compile" (see "admin" + document) ### Footnotes: diff --git a/doc/1-migrate.mkd b/doc/1-migrate.mkd index ca520c6..6ef0c90 100644 --- a/doc/1-migrate.mkd +++ b/doc/1-migrate.mkd @@ -7,7 +7,8 @@ the same. The differences are: You can choose to version that directory but it is not required that you do so -Here's how we migrated my work repos: +Here's how we migrated my work repos (note: substitute real paths, from your +`~/.gitolite.rc`, for `$REPO_BASE` and `$GL_ADMINDIR` below): 1. login as the `git` user on the server, and get a bash shell prompt @@ -18,7 +19,7 @@ Here's how we migrated my work repos: 3. For added safety, **delete** the post-update hook that gitosis-admin installed - rm ~/repositories/gitosis-admin.git/hooks/post-update + rm $REPO_BASE/gitosis-admin.git/hooks/post-update or at least rename it to `.sample` like all the other hooks hanging around, or edit it and comment out the line that calls `gitosis-run-hook @@ -27,31 +28,29 @@ Here's how we migrated my work repos: If you do not do this, an accidental push to the gitosis-admin repo will mess up your `~/.ssh/authorized_keys` file -4. take a **backup** of the `~/repositories` directory +4. take a **backup** of the `$REPO_BASE` directory 5. untar gitolite to some temporary directory and follow the instructions to - **install** it. Some of the steps (like `mkdir ~/repositories`) will - fail, but this is expected. Once this is done, cd to the `$GL_ADMINDIR` - (by default `~/.gitolite`) - - cd ~/.gitolite + **install** it using `src/install.pl` 6. **convert** your gitosis config file: + cd $GL_ADMINDIR src/conf-convert.pl < ~/.gitosis.conf > conf/gitolite.conf be sure to check the file to make sure it converted correctly -7. **copy** the update hook to each of the existing repos +7. **copy** the update hook to each of the existing repos (if you have repos + in subdirectories, this won't work as is; adapt it): - for i in ~/repositories/*.git + for i in $REPO_BASE/*.git do cp src/update-hook.pl $i/hooks/update done 8. **copy** the keys from gitosis's keydir - cp ~/repositories/gitosis-admin.git/gitosis-export/keydir/* keydir + cp $REPO_BASE/gitosis-admin.git/gitosis-export/keydir/* keydir 9. **Important: expand** any multi-key files you may have. See the "faq, tips, etc" document in the doc directory for an explanation of what @@ -61,6 +60,7 @@ Here's how we migrated my work repos: You can split the keys manually, or use the following code (just copy-paste it into your xterm): + cd $GL_ADMINDIR wc -l keydir/*.pub | grep -v total | grep -v -w 1 | while read a b do i=1 diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index 7f1c507..ea8d06f 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -1,8 +1,7 @@ # administering and running gitolite -Note: some of the paths in this document use variable names. Just refer to -`~/.gitolite.rc` for the correct values, assuming you followed the -instructions in the "INSTALL" document. +*Note*: some of the paths in this document use variable names. Just refer to +`~/.gitolite.rc` for the correct values for *your* installation. ### administer @@ -30,10 +29,8 @@ the first couple of times you use it, you may want to check these: not remove!). The line should contain a "command=" pointing to a `$GL_ADMINDIR/src/gl-auth-command` file, then some sshd restrictions, the key, etc. - * `$GL_CONF_COMPILED` (default - `~/.gitolite/conf/gitolite.conf-compiled.pm`) should contain an - expanded list of the access control rules. It may look a little long, - but it's fairly intuitive! + * `$GL_CONF_COMPILED` should contain an expanded list of the access + control rules. It may look a little long, but it's fairly intuitive! * if the run threw up any "initialising empty repo" messages, check the individual repos (inside `$REPO_BASE`) if you wish. Especially make sure From 08305aa482abe5bb9d1219961dde421771fd4ce6 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 30 Aug 2009 12:11:55 +0530 Subject: [PATCH 041/637] install streamlining: - install.sh is now install.pl (had to happen sooner or later!) - now handles updates more gracefully, doesn't overwrite important stuff :) - makes the install sequence much easier to understand (just run it and follow the prompts!) - made ~/.gitolite.rc much clearer to edit --- conf/example.gitolite.rc | 28 +++++++++++++++---- src/install.pl | 58 ++++++++++++++++++++++++++++++++++++++++ src/install.sh | 25 ----------------- 3 files changed, 81 insertions(+), 30 deletions(-) create mode 100755 src/install.pl delete mode 100755 src/install.sh diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index 44413a0..72acc79 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -1,20 +1,38 @@ -# this is meant to be pulled into a perl program using "do" +# default paths for gitolite + +# please read comments before editing + +# this file is meant to be pulled into a perl program using "do" or "require". + +# You do NOT need to know perl to edit the paths; it should be fairly +# self-explanatory + +# -------------------------------------- + +# this is where the repos go. If you provide a relative path (not starting +# with "/"), it's relative to your $HOME. You may want to put in something +# like "/bigdisk" or whatever if your $HOME is too small for the repos, for +# example -# base directory for all the repos (absolute, or relative to $HOME) $REPO_BASE="repositories"; +# -------------------------------------- + +# I see no reason anyone may want to change the gitolite admin directory, but +# feel free to do so + # gitolite admin directory, files, etc $GL_ADMINDIR=$ENV{HOME} . "/.gitolite"; # -------------------------------------- -# the ones below can be left as they are, unless for some reason you want them -# elsewhere +# I see even less reason to change these, since they're all relative to the +# gitolite admin directory above, but hey it's *your* system... $GL_CONF="$GL_ADMINDIR/conf/gitolite.conf"; $GL_KEYDIR="$GL_ADMINDIR/keydir"; $GL_CONF_COMPILED="$GL_ADMINDIR/conf/gitolite.conf-compiled.pm"; # -------------------------------------- -# this should be the last line in this file, per perl rules +# per perl rules, this should be the last line in such a file: 1; diff --git a/src/install.pl b/src/install.pl new file mode 100755 index 0000000..73fb118 --- /dev/null +++ b/src/install.pl @@ -0,0 +1,58 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +our $REPO_BASE; +our $GL_ADMINDIR; +our $GL_CONF; + +# wrapper around mkdir; it's not an error if the directory exists, but it is +# an error if it doesn't exist and we can't create it +sub wrap_mkdir +{ + my $dir = shift; + -d $dir or mkdir($dir) or die "mkdir $dir failed: $!\n"; +} + +# the only path that is *fixed* (can't be changed without changing all 3 +# programs) is ~/.gitolite.rc + +my $glrc = $ENV{HOME} . "/.gitolite.rc"; +unless (-f $glrc) { + # doesn't exist. Copy it across, tell user to edit it and come back + system("cp conf/example.gitolite.rc $glrc"); + print STDERR "created $glrc\n"; + print STDERR "please edit it, set the paths as you like, and rerun this script\n"; + exit; +} + +# ok now $glrc exists; read it to get the other paths +unless (my $ret = do $glrc) +{ + die "parse $glrc failed: $@" if $@; + die "couldn't do $glrc: $!" unless defined $ret; + die "couldn't run $glrc" unless $ret; +} + +# mkdir $REPO_BASE, $GL_ADMINDIR if they don't already exist +wrap_mkdir( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); +wrap_mkdir($GL_ADMINDIR); +# mkdir $GL_ADMINDIR's subdirs +for my $dir qw(conf doc keydir src) { + wrap_mkdir("$GL_ADMINDIR/$dir"); +} + +# "src" and "doc" will be overwritten on each install, but not conf +system("cp -R src doc $GL_ADMINDIR"); + +unless (-f $GL_CONF) { + system("cp conf/example.conf $GL_CONF"); + print STDERR < Date: Sun, 30 Aug 2009 13:27:18 +0530 Subject: [PATCH 042/637] minor: remove needless "our"s --- src/gl-auth-command | 2 -- src/update-hook.pl | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index 1fd294c..8344435 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -24,8 +24,6 @@ use warnings; # ---------------------------------------------------------------------------- our $GL_ADMINDIR; -our $GL_CONF; -our $GL_KEYDIR; our $GL_CONF_COMPILED; our $REPO_BASE; our %repos; diff --git a/src/update-hook.pl b/src/update-hook.pl index 470cf8f..b4c4580 100755 --- a/src/update-hook.pl +++ b/src/update-hook.pl @@ -26,10 +26,7 @@ use warnings; # ---------------------------------------------------------------------------- our $GL_ADMINDIR; -our $GL_CONF; -our $GL_KEYDIR; our $GL_CONF_COMPILED; -our $REPO_BASE; our %repos; my $glrc = $ENV{HOME} . "/.gitolite.rc"; From b916a07d281c463db5554f8238e2ad3858b1744c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 28 Aug 2009 20:41:21 +0530 Subject: [PATCH 043/637] update hook: using non-std branches revealed an unnecessary check for refs/heads/; removed --- doc/0-INSTALL.mkd | 8 ++------ src/update-hook.pl | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index 970237e..10450a2 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -10,12 +10,8 @@ If you managed to install git, you might already have what gitolite needs: * one user account on the server, with password access [2] A major objective is to allow use by people without root access, permissions -to create other userids, etc. If you have root, congratulations and all that, -but go add a user just for gitolite and do all this from that user. Really. -I see no earthly reason for this to run as root. - -And don't bug me about wanting to install it in `/opt` or whatever if you -haven't the time to read the docs or change a path in a config file. +to create other userids, etc. Even if you have root, please add a user just +for gitolite and do all this from that user. ### quick install diff --git a/src/update-hook.pl b/src/update-hook.pl index b4c4580..834a32f 100755 --- a/src/update-hook.pl +++ b/src/update-hook.pl @@ -60,9 +60,9 @@ chomp($merge_base = `git merge-base $oldsha $newsha`) my $perm = 'W'; # rewriting a tag is considered a rewind, in terms of permissions $perm = '+' if $ref =~ m(refs/tags/) and $oldsha ne ('0' x 40); -# non-ff push to branch. Notice that branch delete looks like a rewind, as it -# should -$perm = '+' if $ref =~ m(refs/heads/) and $oldsha ne $merge_base; +# non-ff push to ref +# notice that ref delete looks like a rewind, as it should +$perm = '+' if $oldsha ne $merge_base; my @allowed_refs; push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{$ENV{GL_USER}} || [] }; From 78a10a1ee180201c9c9fc75f3f1a0459fa5edd83 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 30 Aug 2009 21:14:15 +0530 Subject: [PATCH 044/637] compile: another solaris compat fix, to do with "~" system("...") run from perl on sol does not seem to like "~" (regardless of what $SHELL is set to), so use $ENV{HOME} instead thanks again to evocallaghan --- src/gl-compile-conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 811c325..9affc31 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -232,8 +232,8 @@ close $newkeys_fh or die "close newkeys failed: $!"; # system("vim -d ~/.ssh/authorized_keys ~/.ssh/new_authkeys"); # all done; overwrite the file (use cat to avoid perm changes) -system("cat ~/.ssh/new_authkeys > ~/.ssh/authorized_keys"); -system("rm ~/.ssh/new_authkeys"); +system("cat $ENV{HOME}/.ssh/new_authkeys > $ENV{HOME}/.ssh/authorized_keys"); +system("rm $ENV{HOME}/.ssh/new_authkeys"); # if the gl admin directory (~/.gitolite) is itself a git repo, do an # autocheckin. nothing fancy; this is a "just in case" type of thing. From 53f1a77f7fe69ba08aaa14b549879b942dc62aca Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 30 Aug 2009 21:19:36 +0530 Subject: [PATCH 045/637] admin doc: clarify why authkeys is needed and what it does I was very insistently told by a user that I should just create the file if it does not exist, but this is as far as I am willing to go --- doc/2-admin.mkd | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index ea8d06f..ae65b72 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -9,14 +9,28 @@ sources (for example [here](http://sitaramc.github.com/0-installing/2-access-gitolite.html#generating_a_public_key)) for how to do this + * rename each public key according to the user's name, with a `.pub` extension, like `sitaram.pub` or `john-smith.pub`. You can also use periods and underscores + * copy all these `*.pub` files to `$GL_KEYDIR` + * edit the config file (`$GL_CONF`) and give the new users permissions as required. The users names should be exactly the same as their keyfile names, but without the `.pub` extension + * backup your `~/.ssh/authorized_keys` file if you feel nervous :-) + * that's "backup" as in "copy", not "move". The next step won't work if + the file doesn't exist. Even an empty one is fine but it must be + present + * if you don't have an `~/.ssh/authorized_keys` file at all, you may + have logged in with a password, which in turn might mean you are not + familiar with ssh and authkeys etc. If so, please read up at least + [this](http://sitaramc.github.com/0-installing/9-gitolite-basics.html#IMPORTANT_overview_of_ssh), + and preferably also the man pages for sshd and sshd\_config, to make + sure you understand the security implications of what you are doing + * cd to `$GL_ADMINDIR` and run `src/gl-compile-conf` That should be it, really. However, if you want to be doubly sure, or maybe From abb4580d85c2979f6c7f2da198664fae3de07675 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 31 Aug 2009 07:58:08 +0530 Subject: [PATCH 046/637] compile: wrap the open call as well, plus better messages from both wrappers --- src/gl-compile-conf | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 9affc31..e702a1d 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -74,9 +74,13 @@ umask(0077); # subroutines # ---------------------------------------------------------------------------- -sub my_chdir -{ - chdir($_[0]) or die "chdir $_[0] failed: $!"; +sub wrap_chdir { + chdir($_[0]) or die "chdir $_[0] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; +} + +sub wrap_open { + open (my $fh, $_[0], $_[1]) or die "open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; + return $fh; } sub expand_userlist @@ -106,8 +110,7 @@ sub expand_userlist # "compile" GL conf # ---------------------------------------------------------------------------- -open my $conf_fh, "<", $GL_CONF - or die "open conf failed: $!"; +my $conf_fh = wrap_open( "<", $GL_CONF ); # the syntax is fairly simple, so we parse it inline @@ -171,8 +174,7 @@ while (<$conf_fh>) } } -open my $compiled_fh, ">", $GL_CONF_COMPILED - or die "open compiled-conf failed: $!"; +my $compiled_fh = wrap_open( ">", $GL_CONF_COMPILED ); print $compiled_fh Data::Dumper->Dump([\%repos], [qw(*repos)]); close $compiled_fh or die "close compiled-conf failed: $!"; @@ -187,17 +189,17 @@ close $compiled_fh or die "close compiled-conf failed: $!"; # repo-base needs to be an absolute path for this loop to work right # so if it was not already absolute, prefix $HOME. my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); -my_chdir("$repo_base_abs"); +wrap_chdir("$repo_base_abs"); for my $repo (keys %repos) { unless (-d "$repo.git") { mkdir("$repo.git") or die "mkdir $repo.git failed: $!"; - my_chdir("$repo.git"); + wrap_chdir("$repo.git"); system("git init --bare"); system("cp $GL_ADMINDIR/src/update-hook.pl hooks/update"); system("chmod 755 hooks/update"); - my_chdir("$repo_base_abs"); + wrap_chdir("$repo_base_abs"); } } @@ -205,10 +207,8 @@ for my $repo (keys %repos) # "compile" ssh authorized_keys # ---------------------------------------------------------------------------- -open my $authkeys_fh, "<", $ENV{HOME} . "/.ssh/authorized_keys" - or die "open authkeys failed: $!"; -open my $newkeys_fh, ">", $ENV{HOME} . "/.ssh/new_authkeys" - or die "open newkeys failed: $!"; +my $authkeys_fh = wrap_open( "<", $ENV{HOME} . "/.ssh/authorized_keys" ); +my $newkeys_fh = wrap_open( ">", $ENV{HOME} . "/.ssh/new_authkeys" ); # save existing authkeys minus the GL-added stuff while (<$authkeys_fh>) { @@ -218,7 +218,7 @@ while (<$authkeys_fh>) # add our "start" line, each key on its own line (prefixed by command and # options, in the standard ssh authorized_keys format), then the "end" line. print $newkeys_fh "# gitolite start\n"; -my_chdir($GL_KEYDIR); +wrap_chdir($GL_KEYDIR); for my $pubkey (glob("*.pub")) { my $user = $pubkey; $user =~ s/(\@.+)?\.pub$//; @@ -237,7 +237,7 @@ system("rm $ENV{HOME}/.ssh/new_authkeys"); # if the gl admin directory (~/.gitolite) is itself a git repo, do an # autocheckin. nothing fancy; this is a "just in case" type of thing. -my_chdir($GL_ADMINDIR); +wrap_chdir($GL_ADMINDIR); if (-d ".git") { system("git add -A conf keydir"); # stage all operational data From dd13de2d14bb11a51cc9424cd5eef8e7a3f4f6f3 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 30 Aug 2009 13:42:53 +0530 Subject: [PATCH 047/637] update hook/conf: $PERSONAL branch prefix wicked cool to get it in one line of code! --- conf/example.gitolite.rc | 10 ++++++++++ src/update-hook.pl | 2 ++ 2 files changed, 12 insertions(+) diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index 72acc79..f63a411 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -33,6 +33,16 @@ $GL_CONF="$GL_ADMINDIR/conf/gitolite.conf"; $GL_KEYDIR="$GL_ADMINDIR/keydir"; $GL_CONF_COMPILED="$GL_ADMINDIR/conf/gitolite.conf-compiled.pm"; +# -------------------------------------- +# personal branch prefix; leave it as is (empty) if you don't want to use the +# feature (see the "developer-specific branches" section in the "faq, tips, +# etc" document) +$PERSONAL=""; +# uncomment one of these if you do want it. I recommend this: +# $PERSONAL="refs/personal"; +# but if you want something more visible/noisy, use this: +# $PERSONAL="refs/heads/personal"; + # -------------------------------------- # per perl rules, this should be the last line in such a file: 1; diff --git a/src/update-hook.pl b/src/update-hook.pl index 834a32f..4a51d76 100755 --- a/src/update-hook.pl +++ b/src/update-hook.pl @@ -27,6 +27,7 @@ use warnings; our $GL_ADMINDIR; our $GL_CONF_COMPILED; +our $PERSONAL; our %repos; my $glrc = $ENV{HOME} . "/.gitolite.rc"; @@ -67,6 +68,7 @@ $perm = '+' if $oldsha ne $merge_base; my @allowed_refs; push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{$ENV{GL_USER}} || [] }; push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{'@all'} || [] }; +push @allowed_refs, "$PERSONAL/$ENV{GL_USER}-" if $PERSONAL; for my $refex (@allowed_refs) # refex? sure -- a regex to match a ref against :) { From 62d89bf8f8f817fa148fa2b0188a1ea8dc0553ed Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 31 Aug 2009 20:50:47 +0530 Subject: [PATCH 048/637] gl-auth: R_COMMANDS adds "git-upload-archive" --- src/gl-auth-command | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index 8344435..012398f 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -42,7 +42,7 @@ die "couldnt do perms file" unless (my $ret = do $GL_CONF_COMPILED); # definitions specific to this program # ---------------------------------------------------------------------------- -my $R_COMMANDS=qr/^git[ -]upload-pack$/; +my $R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/; my $W_COMMANDS=qr/^git[ -]receive-pack$/; my $REPONAME_PATT=qr(^[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern From 5d4d5184b4c095ddce41e9c7fe779e6d44649aff Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 1 Sep 2009 19:36:00 +0530 Subject: [PATCH 049/637] sources: 1-line all the "do"s for brevity and clarity and yes, brevity and clarity "do" go together in perl :) --- src/gl-auth-command | 10 ++-------- src/gl-compile-conf | 7 +------ src/install.pl | 7 +------ src/update-hook.pl | 10 ++-------- 4 files changed, 6 insertions(+), 28 deletions(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index 012398f..2ea2dbb 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -29,14 +29,8 @@ our $REPO_BASE; our %repos; my $glrc = $ENV{HOME} . "/.gitolite.rc"; -unless (my $ret = do $glrc) -{ - die "parse $glrc failed: $@" if $@; - die "couldn't do $glrc: $!" unless defined $ret; - die "couldn't run $glrc" unless $ret; -} - -die "couldnt do perms file" unless (my $ret = do $GL_CONF_COMPILED); +die "parse $glrc failed: " . ($! or $@) unless do $glrc; +die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; # ---------------------------------------------------------------------------- # definitions specific to this program diff --git a/src/gl-compile-conf b/src/gl-compile-conf index e702a1d..8454eb4 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -48,12 +48,7 @@ our $GL_CONF_COMPILED; our $REPO_BASE; my $glrc = $ENV{HOME} . "/.gitolite.rc"; -unless (my $ret = do $glrc) -{ - die "parse $glrc failed: $@" if $@; - die "couldn't do $glrc: $!" unless defined $ret; - die "couldn't run $glrc" unless $ret; -} +die "parse $glrc failed: " . ($! or $@) unless do $glrc; # ---------------------------------------------------------------------------- # definitions specific to this program diff --git a/src/install.pl b/src/install.pl index 73fb118..6dec899 100755 --- a/src/install.pl +++ b/src/install.pl @@ -28,12 +28,7 @@ unless (-f $glrc) { } # ok now $glrc exists; read it to get the other paths -unless (my $ret = do $glrc) -{ - die "parse $glrc failed: $@" if $@; - die "couldn't do $glrc: $!" unless defined $ret; - die "couldn't run $glrc" unless $ret; -} +die "parse $glrc failed: " . ($! or $@) unless do $glrc; # mkdir $REPO_BASE, $GL_ADMINDIR if they don't already exist wrap_mkdir( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); diff --git a/src/update-hook.pl b/src/update-hook.pl index 4a51d76..526cf1d 100755 --- a/src/update-hook.pl +++ b/src/update-hook.pl @@ -31,14 +31,8 @@ our $PERSONAL; our %repos; my $glrc = $ENV{HOME} . "/.gitolite.rc"; -unless (my $ret = do $glrc) -{ - die "parse $glrc failed: $@" if $@; - die "couldn't do $glrc: $!" unless defined $ret; - die "couldn't run $glrc" unless $ret; -} - -die "couldnt do perms file" unless (my $ret = do $GL_CONF_COMPILED); +die "parse $glrc failed: " . ($! or $@) unless do $glrc; +die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; # ---------------------------------------------------------------------------- # start... From 208c401858b638ad07f736b43d7e96ae5bd0985e Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 1 Sep 2009 19:40:42 +0530 Subject: [PATCH 050/637] compile: chmod internal, and save "old" authkeys --- src/gl-compile-conf | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 8454eb4..0907655 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -193,7 +193,7 @@ for my $repo (keys %repos) wrap_chdir("$repo.git"); system("git init --bare"); system("cp $GL_ADMINDIR/src/update-hook.pl hooks/update"); - system("chmod 755 hooks/update"); + chmod 0755, "hooks/update"; wrap_chdir("$repo_base_abs"); } } @@ -223,10 +223,8 @@ for my $pubkey (glob("*.pub")) print $newkeys_fh "# gitolite end\n"; close $newkeys_fh or die "close newkeys failed: $!"; -# check what changes are being made; just a comfort factor -# system("vim -d ~/.ssh/authorized_keys ~/.ssh/new_authkeys"); - # all done; overwrite the file (use cat to avoid perm changes) +system("cat $ENV{HOME}/.ssh/authorized_keys > $ENV{HOME}/.ssh/old_authkeys"); system("cat $ENV{HOME}/.ssh/new_authkeys > $ENV{HOME}/.ssh/authorized_keys"); system("rm $ENV{HOME}/.ssh/new_authkeys"); From 401c2f46d7f3cbbeeec807329fec502c9db6818d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 1 Sep 2009 20:33:19 +0530 Subject: [PATCH 051/637] install: update hooks in all repos on upgrade --- src/install.pl | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/install.pl b/src/install.pl index 6dec899..4624a74 100755 --- a/src/install.pl +++ b/src/install.pl @@ -51,3 +51,16 @@ unless (-f $GL_CONF) { (the "admin" document should help here...) EOF } + +# finally, any potential changes to src/update-hook.pl must be propagated to +# all the repos' hook directories +my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); +# err, no need to get all worked up if you can't CD there -- this may be the +# very first run and it hasn't been created yet +if (chdir("$repo_base_abs")) { + for my $repo (`find . -type d -name "*.git"`) { + chomp ($repo); + system("cp $GL_ADMINDIR/src/update-hook.pl $repo/hooks/update"); + chmod 0755, "$repo/hooks/update"; + } +} From 4fa1ca665267f6d25c6339fe8823fab06ec70883 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 1 Sep 2009 19:52:06 +0530 Subject: [PATCH 052/637] minor doc updates re directories etc --- conf/example.gitolite.rc | 3 ++- doc/3-faq-tips-etc.mkd | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index f63a411..0eb7ce6 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -19,7 +19,8 @@ $REPO_BASE="repositories"; # -------------------------------------- # I see no reason anyone may want to change the gitolite admin directory, but -# feel free to do so +# feel free to do so. However, please note that it *must* be an *absolute* +# path (i.e., starting with a "/" character) # gitolite admin directory, files, etc $GL_ADMINDIR=$ENV{HOME} . "/.gitolite"; diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index d7b3994..19ead5e 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -22,6 +22,12 @@ In this document: * once in a while, if you're feeling particularly BOFH-ish, take a look at `$GL_ADMINDIR/log` :-) + * if you specify a repo that is not at the top level `$REPO_BASE`, be sure + to manually create the intermediate directories first. For instance if + you specify a new repo called "a/b/c" to the config file and "compile", + the "compile" script will just `mkdir a/b/c.git`, assuming "a/b" has + already been created + ### differences from gitosis Apart from the big ones listed in the top level README, and subjective ones From 455ebe1bc99706efa7c1e6a6ba01793c0d8554c0 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 2 Sep 2009 06:49:04 +0530 Subject: [PATCH 053/637] update hook: personal branches pattern, "-" becomes "/" --- doc/3-faq-tips-etc.mkd | 7 ++++--- src/update-hook.pl | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 19ead5e..ea3c3a7 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -98,10 +98,11 @@ In gitolite, it's simple: just ask nicely :-) So I know what gitolite calls me. Big deal... who cares? -Here is a cool idea: allow me to create, rewind, or delete any branch whose -full name matches this pattern: +Here is an idea: give every developer a personal "scratch" namespace within +which she can create, rewind, or delete any branch. For example, I would own +anything under - $PERSONAL_BRANCH_PREFIX/sitaram-.* + $PERSONAL_BRANCH_PREFIX/sitaram/ The admin could set `$PERSONAL_BRANCH_PREFIX` in the rc file and communicate this to all users. It could be something like `refs/heads/personal`, which diff --git a/src/update-hook.pl b/src/update-hook.pl index 526cf1d..41d6f76 100755 --- a/src/update-hook.pl +++ b/src/update-hook.pl @@ -62,7 +62,7 @@ $perm = '+' if $oldsha ne $merge_base; my @allowed_refs; push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{$ENV{GL_USER}} || [] }; push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{'@all'} || [] }; -push @allowed_refs, "$PERSONAL/$ENV{GL_USER}-" if $PERSONAL; +push @allowed_refs, "$PERSONAL/$ENV{GL_USER}/" if $PERSONAL; for my $refex (@allowed_refs) # refex? sure -- a regex to match a ref against :) { From 804c70f570bafdbd1098e45b8de2f651edcae0cc Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 6 Sep 2009 13:34:41 +0530 Subject: [PATCH 054/637] almost all src/conf: logging totally redone, upgrade doc added - logs go into $GL_ADMINDIR/logs by default, named by year-month - logfile name template (including dir prefix) now in $GL_LOGT - two new env vars passed down: GL_TS and GL_LOG (timestamp, logfilename) - log messages timestamps more compact, fields tab-delimited - old and new SHAs cut to 14 characters --- README.mkd | 5 ++++ conf/example.gitolite.rc | 28 +++++++++++++++++++++- doc/0-UPGRADE.mkd | 52 ++++++++++++++++++++++++++++++++++++++++ src/gl-auth-command | 31 ++++++++++++++++++------ src/gl-compile-conf | 6 +---- src/install.pl | 23 +++++++----------- src/update-hook.pl | 10 ++++---- 7 files changed, 123 insertions(+), 32 deletions(-) create mode 100644 doc/0-UPGRADE.mkd diff --git a/README.mkd b/README.mkd index aa97140..a49eb67 100644 --- a/README.mkd +++ b/README.mkd @@ -1,5 +1,10 @@ # gitolite +> [IMPORTANT: There is now an "upgrade" document in the "doc" directory; +> please read if upgrading gitolite] + +---- + Gitolite is the bare essentials of gitosis, with a completely different config file that allows (at last!) access control down to the branch level, including specifying who can and cannot *rewind* a given branch. It is diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index 0eb7ce6..f5e64a1 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -1,4 +1,4 @@ -# default paths for gitolite +# paths and configuration variables for gitolite # please read comments before editing @@ -23,10 +23,27 @@ $REPO_BASE="repositories"; # path (i.e., starting with a "/" character) # gitolite admin directory, files, etc + $GL_ADMINDIR=$ENV{HOME} . "/.gitolite"; # -------------------------------------- +# templates for location of the log files and format of their names + +# I prefer this template (note the %y and %m placeholders) +# it produces files like `~/.gitolite/logs/gitolite-2009-09.log` + +$GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y-%m.log"; + +# other choices are below, or you can make your own -- but PLEASE MAKE SURE +# the directory exists and is writable; gitolite won't do that for you (unless +# it is the default, which is "$GL_ADMINDIR/logs") + +# $GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y-%m-%d.log"; +# $GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y.log"; + +# -------------------------------------- + # I see even less reason to change these, since they're all relative to the # gitolite admin directory above, but hey it's *your* system... @@ -35,15 +52,24 @@ $GL_KEYDIR="$GL_ADMINDIR/keydir"; $GL_CONF_COMPILED="$GL_ADMINDIR/conf/gitolite.conf-compiled.pm"; # -------------------------------------- + # personal branch prefix; leave it as is (empty) if you don't want to use the # feature (see the "developer-specific branches" section in the "faq, tips, # etc" document) + $PERSONAL=""; + # uncomment one of these if you do want it. I recommend this: # $PERSONAL="refs/personal"; + # but if you want something more visible/noisy, use this: # $PERSONAL="refs/heads/personal"; +# NOTE: whatever value you choose, for security reasons it is better to make +# it fully qualified -- that is, starting with "refs/" + # -------------------------------------- # per perl rules, this should be the last line in such a file: 1; + +# vim: set syn=perl: diff --git a/doc/0-UPGRADE.mkd b/doc/0-UPGRADE.mkd new file mode 100644 index 0000000..4d55ac0 --- /dev/null +++ b/doc/0-UPGRADE.mkd @@ -0,0 +1,52 @@ +# upgrading gitolite atomically + +### general upgrade notes + +If you follow the steps below, you can make the upgrade "atomic", so you don't +have to do it at a "quiet" time or something. + +1. untar the new version to some temp directory and `cd` to it + +2. *prepare* the new version of `~/.gitolite.rc`. It **must** have **all** + the variables defined in `conf/example.gitolite.rc` (the "new" rc file), + because the new versions of the programs will be depending on seeing these + variables. + + However, it must also retain any customisations you made to the **old** + variables. + + So this is what you do: + + * make a copy of `conf/example.gitolite.rc` as `~/glrc.new` + * if your current `~/.gitolite.rc` had any customisations (where you + changed the defaults in some way), edit `~/glrc.new` and make those + same changes there + +3. upgrade the rc file first + + cp ~/glrc.new ~/.gitolite.rc + +4. upgrade the software + + src/install.pl + +And you're done. + +### upgrade notes for specific versions + +If any extra steps beyond the generic ones above are needed, they will be +listed here, newest first. + +#### upgrading from abb4580 + +Two new features (personal branches, and customisable logfile names/locations) +have been added between abb4580 and this version. + + * if you want to enable the personal branches feature, choose one of the + alternative values given for `$PERSONAL` or change it to something you + like; by default it is empty, which disables the feature + + * if you want the log files named or grouped differently, choose one of the + alternative values for `$GL_LOGT`. **Note** that if you choose to put + them in some other directory than the default, you **must** create that + directory (`mkdir`) yourself; gitolite will not do that for you diff --git a/src/gl-auth-command b/src/gl-auth-command index 2ea2dbb..6a8632c 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -23,9 +23,8 @@ use warnings; # common definitions # ---------------------------------------------------------------------------- -our $GL_ADMINDIR; -our $GL_CONF_COMPILED; -our $REPO_BASE; + +our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE); our %repos; my $glrc = $ENV{HOME} . "/.gitolite.rc"; @@ -89,18 +88,36 @@ die "$perm access for $repo denied to $user" or $repos{$repo}{$perm}{'@all'}; # ---------------------------------------------------------------------------- -# over to git now +# logging, timestamp. also setup env vars for later # ---------------------------------------------------------------------------- -# ( but first save the reponame; we can save some time later in the hook ) +# reponame $ENV{GL_REPO}=$repo; +# timestamp +my ($s, $min, $h, $d, $m, $y) = (localtime)[0..5]; +$y += 1900; $m++; # usual adjustments +for ($s, $min, $h, $d, $m) { + $_ = "0$_" if $_ < 10; +} +$ENV{GL_TS} = "$y-$m-$d.$h:$min:$s"; + +# substitute template parameters and set the logfile name +$GL_LOGT =~ s/%y/$y/g; +$GL_LOGT =~ s/%m/$m/g; +$GL_LOGT =~ s/%d/$d/g; +$ENV{GL_LOG} = $GL_LOGT; + # if log failure isn't important enough to block access, get rid of all the # error checking -open my $log_fh, ">>", "$GL_ADMINDIR/log" +open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!"; -print $log_fh "\n", scalar(localtime), " $ENV{SSH_ORIGINAL_COMMAND} $user\n"; +print $log_fh "$ENV{GL_TS}\t$ENV{SSH_ORIGINAL_COMMAND}\t$user\n"; close $log_fh or die "close log failed: $!"; +# ---------------------------------------------------------------------------- +# over to git now +# ---------------------------------------------------------------------------- + $repo = "'$REPO_BASE/$repo.git'"; exec("git", "shell", "-c", "$verb $repo"); diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 0907655..388d933 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -41,11 +41,7 @@ use Data::Dumper; # common definitions # ---------------------------------------------------------------------------- -our $GL_ADMINDIR; -our $GL_CONF; -our $GL_KEYDIR; -our $GL_CONF_COMPILED; -our $REPO_BASE; +our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE); my $glrc = $ENV{HOME} . "/.gitolite.rc"; die "parse $glrc failed: " . ($! or $@) unless do $glrc; diff --git a/src/install.pl b/src/install.pl index 4624a74..6586ef2 100755 --- a/src/install.pl +++ b/src/install.pl @@ -3,9 +3,7 @@ use strict; use warnings; -our $REPO_BASE; -our $GL_ADMINDIR; -our $GL_CONF; +our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF); # wrapper around mkdir; it's not an error if the directory exists, but it is # an error if it doesn't exist and we can't create it @@ -31,10 +29,11 @@ unless (-f $glrc) { die "parse $glrc failed: " . ($! or $@) unless do $glrc; # mkdir $REPO_BASE, $GL_ADMINDIR if they don't already exist -wrap_mkdir( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); +my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); +wrap_mkdir($repo_base_abs); wrap_mkdir($GL_ADMINDIR); # mkdir $GL_ADMINDIR's subdirs -for my $dir qw(conf doc keydir src) { +for my $dir qw(conf doc keydir logs src) { wrap_mkdir("$GL_ADMINDIR/$dir"); } @@ -54,13 +53,9 @@ EOF # finally, any potential changes to src/update-hook.pl must be propagated to # all the repos' hook directories -my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); -# err, no need to get all worked up if you can't CD there -- this may be the -# very first run and it hasn't been created yet -if (chdir("$repo_base_abs")) { - for my $repo (`find . -type d -name "*.git"`) { - chomp ($repo); - system("cp $GL_ADMINDIR/src/update-hook.pl $repo/hooks/update"); - chmod 0755, "$repo/hooks/update"; - } +chdir("$repo_base_abs") or die "chdir $repo_base_abs failed: $!\n"; +for my $repo (`find . -type d -name "*.git"`) { + chomp ($repo); + system("cp $GL_ADMINDIR/src/update-hook.pl $repo/hooks/update"); + chmod 0755, "$repo/hooks/update"; } diff --git a/src/update-hook.pl b/src/update-hook.pl index 41d6f76..fe92ec7 100755 --- a/src/update-hook.pl +++ b/src/update-hook.pl @@ -25,9 +25,7 @@ use warnings; # common definitions # ---------------------------------------------------------------------------- -our $GL_ADMINDIR; -our $GL_CONF_COMPILED; -our $PERSONAL; +our ($GL_CONF_COMPILED, $PERSONAL); our %repos; my $glrc = $ENV{HOME} . "/.gitolite.rc"; @@ -70,9 +68,11 @@ for my $refex (@allowed_refs) { # if log failure isn't important enough to block pushes, get rid of # all the error checking - open my $log_fh, ">>", "$GL_ADMINDIR/log" + open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!"; - print $log_fh "$perm: $ENV{GL_USER} $ENV{GL_REPO} $ref $oldsha $newsha\n"; + print $log_fh "$ENV{GL_TS} $perm\t" . + substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . + "\t$ENV{GL_REPO}\t$ref\t$ENV{GL_USER}\n"; close $log_fh or die "close log failed: $!"; exit 0; } From 7abc629d51a21886c93221083868e8c2bdc7042d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 10 Sep 2009 15:57:52 +0530 Subject: [PATCH 055/637] faq-tips doc: "compile" as a separate step vindicated :-) it seems gitosis silently ignores config errors. It can't do anything else, considering *when* the config file is parsed (on every access!) --- doc/3-faq-tips-etc.mkd | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index ea3c3a7..4fc44b1 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -4,6 +4,7 @@ In this document: * errors, warnings, notes... * differences from gitosis + * error checking the config file * one user, many keys * who am I? * cool ideas I want feedback on @@ -34,6 +35,16 @@ Apart from the big ones listed in the top level README, and subjective ones like "better config file format", there are some small, but significant and concrete, differences from gitosis. +#### error checking the config file + +gitosis does not do any. I just found out that if you mis-spell `members` as +`member`, gitosis will silently ignore it, and leave you wondering why access +was denied. + +In gitolite, you have to "compile" the config file first (this step takes the +place of the commit+push in gitosis), and keyword typos *are* caught so you +know right away. + #### built-in logging ...just in case of emergency :-) From 3d44b003c844e48f58e84c08d13fd998e50b7e59 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 10 Sep 2009 19:05:07 +0530 Subject: [PATCH 056/637] clarifications in various messages etc (thanks to SethX for feedback) - install: a little more verbosity in the mkdir - install and example conf: some of the help text made more clear - auth: error message on bad $cmd is now clearer, plus no perl-warnings to confuse people --- conf/example.conf | 3 +++ src/gl-auth-command | 8 +++----- src/install.pl | 9 +++++++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index f19a95a..8f018ee 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -40,6 +40,9 @@ # notes: +# - the reponame is a simple name. Do not add the ".git" extension -- +# that will be added by the program when the actual repo is created + # - RW+ means non-ff push is allowed # - you can't write just "W" or "+"; it has to be R, or RW, or RW+ diff --git a/src/gl-auth-command b/src/gl-auth-command index 6a8632c..4fc757d 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -70,11 +70,9 @@ die "$cmd??? you're a funny guy..." # including the single quotes my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*).git'/); -die "$verb? I don't do odd jobs, sorry..." - unless $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS; - -die "I don't like the look of $repo, sorry!" - unless $repo =~ $REPONAME_PATT; +die "Sorry, I don't like the command you gave me: $cmd\n" + unless ( ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) + and $repo =~ $REPONAME_PATT ); # ---------------------------------------------------------------------------- # first level permissions check diff --git a/src/install.pl b/src/install.pl index 6586ef2..85b8607 100755 --- a/src/install.pl +++ b/src/install.pl @@ -10,7 +10,12 @@ our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF); sub wrap_mkdir { my $dir = shift; - -d $dir or mkdir($dir) or die "mkdir $dir failed: $!\n"; + if ( -d $dir ) { + print STDERR "$dir already exists\n"; + return; + } + mkdir($dir) or die "mkdir $dir failed: $!\n"; + print STDERR "created $dir\n"; } # the only path that is *fixed* (can't be changed without changing all 3 @@ -21,7 +26,7 @@ unless (-f $glrc) { # doesn't exist. Copy it across, tell user to edit it and come back system("cp conf/example.gitolite.rc $glrc"); print STDERR "created $glrc\n"; - print STDERR "please edit it, set the paths as you like, and rerun this script\n"; + print STDERR "please edit it, change the paths if you wish to, and RERUN THIS SCRIPT\n"; exit; } From 694050d6c4daef1c176d6361d1b02b63c5c9c16a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 10 Sep 2009 21:24:58 +0530 Subject: [PATCH 057/637] all src: suffixed a \n to all die's; error output looks cleaner now --- src/conf-convert.pl | 2 +- src/gl-auth-command | 10 +++++----- src/gl-compile-conf | 12 ++++++------ src/update-hook.pl | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/conf-convert.pl b/src/conf-convert.pl index f9775cb..c5b1ac1 100755 --- a/src/conf-convert.pl +++ b/src/conf-convert.pl @@ -22,7 +22,7 @@ my $groupname; # a gitosis.conf stanza ends when a new "[group name]" line shows up, so you # can't write as you go; you have to accumulate and flush sub flush { - die "repos but no users?" if (not @users and (@repos or @RO_repos)); + die "repos but no users?\n" if (not @users and (@repos or @RO_repos)); # just a groupname if (@users and not (@repos or @RO_repos)) { print "\@$groupname = ", join(" ", @users), "\n"; diff --git a/src/gl-auth-command b/src/gl-auth-command index 4fc757d..c301415 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -54,11 +54,11 @@ my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere! # that in the message so people saying "ssh git@server" can see which gitosis # user he is being recognised as my $cmd = $ENV{SSH_ORIGINAL_COMMAND} - or die "no SSH_ORIGINAL_COMMAND? I'm not a shell, $user!"; + or die "no SSH_ORIGINAL_COMMAND? I'm not a shell, $user!\n"; # this check is largely for comic value if someone tries something outrageous; # $cmd gets split and the pieces examined more thoroughly later anyway -die "$cmd??? you're a funny guy..." +die "$cmd??? you're a funny guy...\n" if $cmd =~ /[<>&|;\n]/; # split into command and arguments; the pattern allows old style as well as @@ -81,7 +81,7 @@ die "Sorry, I don't like the command you gave me: $cmd\n" # we know the user and repo; we just need to know what perm he's trying my $perm = ($verb =~ $R_COMMANDS ? 'R' : 'W'); -die "$perm access for $repo denied to $user" +die "$perm access for $repo denied to $user\n" unless $repos{$repo}{$perm}{$user} or $repos{$repo}{$perm}{'@all'}; @@ -109,9 +109,9 @@ $ENV{GL_LOG} = $GL_LOGT; # if log failure isn't important enough to block access, get rid of all the # error checking open my $log_fh, ">>", $ENV{GL_LOG} - or die "open log failed: $!"; + or die "open log failed: $!\n"; print $log_fh "$ENV{GL_TS}\t$ENV{SSH_ORIGINAL_COMMAND}\t$user\n"; -close $log_fh or die "close log failed: $!"; +close $log_fh or die "close log failed: $!\n"; # ---------------------------------------------------------------------------- # over to git now diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 388d933..ce8a0b9 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -84,7 +84,7 @@ sub expand_userlist die "bad user $item\n" unless $item =~ $USERNAME_PATT; if ($item =~ /^@/) # nested group { - die "undefined group $item" unless $groups{$item}; + die "undefined group $item\n" unless $groups{$item}; # add those names to the list push @new_list, @{ $groups{$item} }; } @@ -167,7 +167,7 @@ while (<$conf_fh>) my $compiled_fh = wrap_open( ">", $GL_CONF_COMPILED ); print $compiled_fh Data::Dumper->Dump([\%repos], [qw(*repos)]); -close $compiled_fh or die "close compiled-conf failed: $!"; +close $compiled_fh or die "close compiled-conf failed: $!\n"; # ---------------------------------------------------------------------------- # any new repos created? @@ -185,7 +185,7 @@ for my $repo (keys %repos) { unless (-d "$repo.git") { - mkdir("$repo.git") or die "mkdir $repo.git failed: $!"; + mkdir("$repo.git") or die "mkdir $repo.git failed: $!\n"; wrap_chdir("$repo.git"); system("git init --bare"); system("cp $GL_ADMINDIR/src/update-hook.pl hooks/update"); @@ -217,7 +217,7 @@ for my $pubkey (glob("*.pub")) print $newkeys_fh `cat $pubkey`; } print $newkeys_fh "# gitolite end\n"; -close $newkeys_fh or die "close newkeys failed: $!"; +close $newkeys_fh or die "close newkeys failed: $!\n"; # all done; overwrite the file (use cat to avoid perm changes) system("cat $ENV{HOME}/.ssh/authorized_keys > $ENV{HOME}/.ssh/old_authkeys"); @@ -234,9 +234,9 @@ if (-d ".git") if (system("git diff --cached --quiet") ) { open my $commit_ph, "|-", "git commit -F -" - or die "open commit failed: $!"; + or die "open commit failed: $!\n"; print $commit_ph "keydir changed\n\n"; print $commit_ph `git diff --cached --name-status`; - close $commit_ph or die "close commit failed: $!"; + close $commit_ph or die "close commit failed: $!\n"; } } diff --git a/src/update-hook.pl b/src/update-hook.pl index fe92ec7..c703723 100755 --- a/src/update-hook.pl +++ b/src/update-hook.pl @@ -69,11 +69,11 @@ for my $refex (@allowed_refs) # if log failure isn't important enough to block pushes, get rid of # all the error checking open my $log_fh, ">>", $ENV{GL_LOG} - or die "open log failed: $!"; + or die "open log failed: $!\n"; print $log_fh "$ENV{GL_TS} $perm\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . "\t$ENV{GL_REPO}\t$ref\t$ENV{GL_USER}\n"; - close $log_fh or die "close log failed: $!"; + close $log_fh or die "close log failed: $!\n"; exit 0; } } From d9d432a483ffc3c8e7228713bef7cc8de9c74829 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 11 Sep 2009 23:03:41 +0530 Subject: [PATCH 058/637] faq/tips: added "common errors..." section with 2 examples --- doc/3-faq-tips-etc.mkd | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 4fc44b1..2f65fa2 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -2,7 +2,8 @@ In this document: - * errors, warnings, notes... + * common errors and mistakes + * other errors, warnings, notes... * differences from gitosis * error checking the config file * one user, many keys @@ -10,7 +11,20 @@ In this document: * cool ideas I want feedback on * developer specific branches -### errors, warnings, notes... +### common errors and mistakes + + * forgetting to suffix `.git` to the end of the reponame in the `git clone`. + This suffix is *not* used in the gitolite config file for the sake of + clarity and cleaner syntax, but don't let that fool you. It's a + convention in the git world that **bare repos** end with `.git`. + + * adding `repositories/` at the start of the repo name in the `git clone`. + This error is typically made by the *admin* himself -- because he knows + what `$REPO_BASE` is set to and thinks he has to provide that prefix on + the client side also :-) In fact gitolite prepends `$REPO_BASE` when it + is required anyway, so you shouldn't do the same thing! + +### other errors, warnings, notes... * cloning an empty repo is only possible with clients greater than 1.6.2. So at least one of your clients needs to have a recent git. Once at least From 7f9c2e6510c396007baf03dce0b809cd097bfbf3 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 12 Sep 2009 19:38:11 +0530 Subject: [PATCH 059/637] minor doc updates - README: re not needing root access - doc/3: "empty clone error" vis-a-vis git 1.6.4.3 --- README.mkd | 1 + doc/3-faq-tips-etc.mkd | 1 + 2 files changed, 2 insertions(+) diff --git a/README.mkd b/README.mkd index a49eb67..fa8e2f4 100644 --- a/README.mkd +++ b/README.mkd @@ -28,6 +28,7 @@ a typical $DAYJOB setting, there are some issues: and be done * often, "python-setuptools" isn't installed (and on a Solaris9 I was trying to help remotely, we never did manage to install it eventually) + * or you don't have root access, or the ability to add users * the most requested feature (see "what's new?") had to be written anyway ### what's gone diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 2f65fa2..9623149 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -33,6 +33,7 @@ In this document: * when you clone an empty repo, git seems to complain about the remote hanging up or something. I have no idea what that is, but it doesn't seem to hurt anything. This happens even in normal git, not just gitolite. + [Update 2009-09-14; this has been fixed in git 1.6.4.3] * once in a while, if you're feeling particularly BOFH-ish, take a look at `$GL_ADMINDIR/log` :-) From 2ca491662151fd486395871284431298a7dedfe3 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 14 Sep 2009 12:31:19 +0530 Subject: [PATCH 060/637] doc/3: explain how 2-level access checks affect personal branch rights --- doc/3-faq-tips-etc.mkd | 58 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 9623149..1a5e426 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -5,6 +5,7 @@ In this document: * common errors and mistakes * other errors, warnings, notes... * differences from gitosis + * two levels of access rights checking * error checking the config file * one user, many keys * who am I? @@ -50,6 +51,34 @@ Apart from the big ones listed in the top level README, and subjective ones like "better config file format", there are some small, but significant and concrete, differences from gitosis. +#### two levels of access rights checking + +Gitolite has two levels of access checks. The **first check** is what I will +call the **pre-git** level (this is the only check that gitosis has). At this +stage, the `gl-auth-command` has been invoked by `sshd`, and it knows just +three things: + + * who, + * what repository, and + * what type of access (R or W) + +Note that at this point no git program has entered the picture, and we have no +way of knowing what **ref** (branch, tag, etc) he is trying to update, even if +it is a "write" operation. + +For a "read" operation to pass this check, the username (or `@all`) must be +mentioned on some line in the config for this repo. + +For a "write" operation, there is an additional restriction: lines specifying +only `R` (read access) don't count. *The user must have write access to +**some** ref in the repo in order to pass this stage!* + +The **second check** is via a git `update hook`. This check only happens for +write operations. By this time we know what "ref" he is trying to update, as +well as the old and the new SHAs of that ref (by which we can also deduce +whether it's a fast forward or not). This is where the "per-branch" +permissions come into play. + #### error checking the config file gitosis does not do any. I just found out that if you mis-spell `members` as @@ -147,6 +176,31 @@ namespace, and each developer controls his/her own set of branches! The amount of code needed? *One line!* I'll spend about 3x more on declaring and initialising the new variable, and 30x more on documenting it :-) -**That** is how neatly gitolite is designed... +**Note that a user who has NO write access cannot have personal branches**; if +you read the section (above) on "two levels of access rights checking" you'll +understand why. -/me pats himself on the back ;-) +For instance, in the following example, `user3` cannot push to any +`refs/heads/personal/user3/*` branches because the first level check stops him +cold: + + # assume $PERSONAL = 'refs/heads/personal' in ~/.gitolite.rc + repo myrepo + RW+ master = sitaram + RW+ release = qa_guy + RW = user1 user2 + R = user3 + +If we relax that check, *any* access becomes *write* access. Yes it will be +caught later, by the hook, but it's good practice to catch things in multiple +places. + +If you want `user3` to have his own personal branch, but without write access +to any of the "real" branches (like "master", "release", etc.), just use a +dummy branch. Choose a name that will never exist in practice, or even if +someone creates it, we don't care. For example, this will get him past the +first check: + + RW dummy = user3 + +Just don't *show* the user this config file; it might sound insulting :-) From 5758f69a4326644af1830d71ca3cd8f53a950cb4 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 15 Sep 2009 12:04:15 +0530 Subject: [PATCH 061/637] doc: added 4-push-to-admin --- doc/3-faq-tips-etc.mkd | 8 +++++ doc/4-push-to-admin.mkd | 79 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 doc/4-push-to-admin.mkd diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 1a5e426..4347f3f 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -45,6 +45,14 @@ In this document: the "compile" script will just `mkdir a/b/c.git`, assuming "a/b" has already been created + * if you run `git init` inside `$GL_ADMINDIR` (that is, make it a normal, + non-bare, repo), then, everytime you "compile" (run + `src/gl-compile-conf`), any changes to `conf` and `keydir` will + automatically be committed. This is a simple safety net in case you + accidentally delete the whole config or something. Also see + [4-push-to-admin.mkd](http://github.com/sitaramc/gitolite/blob/pu/doc/4-push-to-admin.mkd) + if you really know what you're doing and want "push to admin". + ### differences from gitosis Apart from the big ones listed in the top level README, and subjective ones diff --git a/doc/4-push-to-admin.mkd b/doc/4-push-to-admin.mkd new file mode 100644 index 0000000..ef5125b --- /dev/null +++ b/doc/4-push-to-admin.mkd @@ -0,0 +1,79 @@ +# "push to admin" in gitolite + +---- + +Gitosis's default mode of admin is by cloning and pushing the `gitosis-admin` +repo. I call this "push to admin". It's a very cool/cute feature, and I +loved it at first. + +But it's a ***support nightmare***. Half the gitosis angst on `#git` is +because of this feature. Gitolite does not use or endorse this method for +people new to git, or ssh or (worse) both. + +However, ***if*** you know git and ssh really, *really*, well and you know +what you're doing, this is a pretty nice thing to have -- does make life +easier, I admit. + +So, here is how to make PTA (hey nice acronym, just missing an "I") work on +gitolite as well. But remember, there is NO SUPPORT! Go away. Leave me +alone... Anything else on gitolite I will help but not this, ok? :-) + +The instructions are presented as shell commands; they should be fairly +obvious. All paths are from the default `~/.gitolite.rc`; if you changed any, +make the same changes below. + +---- + +First, on the server, log on to the `git` userid, add a new repo called +`gitolite-admin` to the config file, give yourself `RW` or `RW+` rights to it, +and "compile": + + cd ~/.gitolite + vim conf/gitolite.conf # add gitolite-admin repo, etc + src/gl-compile-conf + +Now, if you look at the "compile" script, it has an *automatic* local commit +inside, just for safety, which kicks in every time you compile. This only +works if it finds a ".git" directory, and it was designed as an "automatic +backup/safety net" type of thing, in case I accidentally deleted the whole +config file or something. + +We need to disable this, because now we have a *better* repo, one that is +manually pushed, and presumably has proper commit messages! + + mv .git .disable.git # yeah it's a hack, sue me + +Now the compile command created an empty, bare, "gitolite-admin" repo, so we +seed it with the current contents of the config and keys. (A note on the +`GIT_WORK_TREE` variable: I avoid setting these variables in the normal way +because I always forget to unset them later, and then when I `cd` to other +repos they play havoc with my git commands, so this is how I do it) + + cd ~/repositories/gitolite-admin.git + GIT_WORK_TREE=/home/git/.gitolite git add conf/gitolite.conf keydir + GIT_WORK_TREE=/home/git/.gitolite git commit -am start + +Now we have to setup the post-update hook for push-to-admin to work. The +hook should (1) make a forced checkout in the "live" config directory (which +is `~/.gitolite`), and (2) run the compile script. So we create a hook with +the appropriate code in it, and then make it executable + + cat < hooks/post-update + #!/bin/sh + + GIT_WORK_TREE=/home/git/.gitolite git checkout -f + + cd /home/git/.gitolite + src/gl-compile-conf + EOFPU + + chmod +x hooks/post-update + +---- + +Now get to your workstation, and + + git clone git@server:gitolite-admin.git + +That's it, we're done. You're in gitosis land as far as this is concerned +now. So knock yourself out. Or lock yourself out... :-) From f54c6c7a525937b3087742c593a9bab43af51406 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 15 Sep 2009 21:02:23 +0530 Subject: [PATCH 062/637] compile: make error messages grab the admin's attention required if you do "push to admin" --- src/gl-compile-conf | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index ce8a0b9..fd18f20 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -43,8 +43,13 @@ use Data::Dumper; our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE); +# now that this thing *may* be run via "push to admin", any errors have to +# grab the admin's ATTENTION so he won't miss them among the other messages a +# typical push generates +my $ATTN = "\n\t\t***** ERROR ***** "; + my $glrc = $ENV{HOME} . "/.gitolite.rc"; -die "parse $glrc failed: " . ($! or $@) unless do $glrc; +die "$ATTN parse $glrc failed: " . ($! or $@) unless do $glrc; # ---------------------------------------------------------------------------- # definitions specific to this program @@ -66,11 +71,11 @@ umask(0077); # ---------------------------------------------------------------------------- sub wrap_chdir { - chdir($_[0]) or die "chdir $_[0] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; + chdir($_[0]) or die "$ATTN chdir $_[0] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; } sub wrap_open { - open (my $fh, $_[0], $_[1]) or die "open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; + open (my $fh, $_[0], $_[1]) or die "$ATTN open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; return $fh; } @@ -81,10 +86,10 @@ sub expand_userlist for my $item (@list) { - die "bad user $item\n" unless $item =~ $USERNAME_PATT; + die "$ATTN bad user $item\n" unless $item =~ $USERNAME_PATT; if ($item =~ /^@/) # nested group { - die "undefined group $item\n" unless $groups{$item}; + die "$ATTN undefined group $item\n" unless $groups{$item}; # add those names to the list push @new_list, @{ $groups{$item} }; } @@ -122,7 +127,7 @@ while (<$conf_fh>) if (/^(@\S+) = (.*)/) { push @{ $groups{$1} }, expand_userlist( split(' ', $2) ); - die "bad group $1\n" unless $1 =~ $USERNAME_PATT; + die "$ATTN bad group $1\n" unless $1 =~ $USERNAME_PATT; } # repo(s) elsif (/^repo (.*)/) @@ -161,13 +166,13 @@ while (<$conf_fh>) } else { - die "can't make head or tail of '$_'\n"; + die "$ATTN can't make head or tail of '$_'\n"; } } my $compiled_fh = wrap_open( ">", $GL_CONF_COMPILED ); print $compiled_fh Data::Dumper->Dump([\%repos], [qw(*repos)]); -close $compiled_fh or die "close compiled-conf failed: $!\n"; +close $compiled_fh or die "$ATTN close compiled-conf failed: $!\n"; # ---------------------------------------------------------------------------- # any new repos created? @@ -185,7 +190,7 @@ for my $repo (keys %repos) { unless (-d "$repo.git") { - mkdir("$repo.git") or die "mkdir $repo.git failed: $!\n"; + mkdir("$repo.git") or die "$ATTN mkdir $repo.git failed: $!\n"; wrap_chdir("$repo.git"); system("git init --bare"); system("cp $GL_ADMINDIR/src/update-hook.pl hooks/update"); @@ -217,7 +222,7 @@ for my $pubkey (glob("*.pub")) print $newkeys_fh `cat $pubkey`; } print $newkeys_fh "# gitolite end\n"; -close $newkeys_fh or die "close newkeys failed: $!\n"; +close $newkeys_fh or die "$ATTN close newkeys failed: $!\n"; # all done; overwrite the file (use cat to avoid perm changes) system("cat $ENV{HOME}/.ssh/authorized_keys > $ENV{HOME}/.ssh/old_authkeys"); @@ -234,9 +239,9 @@ if (-d ".git") if (system("git diff --cached --quiet") ) { open my $commit_ph, "|-", "git commit -F -" - or die "open commit failed: $!\n"; + or die "$ATTN open commit failed: $!\n"; print $commit_ph "keydir changed\n\n"; print $commit_ph `git diff --cached --name-status`; - close $commit_ph or die "close commit failed: $!\n"; + close $commit_ph or die "$ATTN close commit failed: $!\n"; } } From fde9708cbfbdfa04c17ed923f7e00db1a20c12ed Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 17 Sep 2009 10:39:13 +0530 Subject: [PATCH 063/637] compile: better message when authkeys absent for security reasons, we refuse to create ~/.ssh/authorized_keys if it doesn't exist. Explain this better and point to the documentation --- doc/2-admin.mkd | 4 +++- src/gl-compile-conf | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index ae65b72..028b23a 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -29,7 +29,9 @@ familiar with ssh and authkeys etc. If so, please read up at least [this](http://sitaramc.github.com/0-installing/9-gitolite-basics.html#IMPORTANT_overview_of_ssh), and preferably also the man pages for sshd and sshd\_config, to make - sure you understand the security implications of what you are doing + sure you understand the security implications of what you are doing. + Once you have understood that, create at least an empty + `~/.ssh/authorized_keys` file before proceeding to the next step * cd to `$GL_ADMINDIR` and run `src/gl-compile-conf` diff --git a/src/gl-compile-conf b/src/gl-compile-conf index fd18f20..69bbb75 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -46,7 +46,7 @@ our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE); # now that this thing *may* be run via "push to admin", any errors have to # grab the admin's ATTENTION so he won't miss them among the other messages a # typical push generates -my $ATTN = "\n\t\t***** ERROR ***** "; +my $ATTN = "\n\t\t***** ERROR *****\n "; my $glrc = $ENV{HOME} . "/.gitolite.rc"; die "$ATTN parse $glrc failed: " . ($! or $@) unless do $glrc; @@ -75,7 +75,8 @@ sub wrap_chdir { } sub wrap_open { - open (my $fh, $_[0], $_[1]) or die "$ATTN open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; + open (my $fh, $_[0], $_[1]) or die "$ATTN open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n" . + ( $_[2] || '' ); # suffix custom error message if given return $fh; } @@ -203,7 +204,9 @@ for my $repo (keys %repos) # "compile" ssh authorized_keys # ---------------------------------------------------------------------------- -my $authkeys_fh = wrap_open( "<", $ENV{HOME} . "/.ssh/authorized_keys" ); +my $authkeys_fh = wrap_open( "<", $ENV{HOME} . "/.ssh/authorized_keys", + "\tFor security reasons, gitolite will not *create* this file if it does\n" . + "\tnot already exist. Please see the \"admin\" document for details\n"); my $newkeys_fh = wrap_open( ">", $ENV{HOME} . "/.ssh/new_authkeys" ); # save existing authkeys minus the GL-added stuff while (<$authkeys_fh>) From 86faae4d4cf93d050490eaee6379c2eb0385410b Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 15 Sep 2009 21:07:00 +0530 Subject: [PATCH 064/637] compile+conf: allow lists (@listname) for reponames too why should just usernames have all the fun :) The "expand_userlist" function is now "expand_list" and serves generically. The example conf has also been updated correspondingly --- conf/example.conf | 28 ++++++++++++++++++---------- doc/0-UPGRADE.mkd | 7 +++++++ src/gl-compile-conf | 25 +++++++++++++++++-------- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index 8f018ee..86fdeca 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -4,6 +4,7 @@ # - everything in this is space-separated; no commas, semicolons, etc # - comments in the normal shell-ish style; no surprises there # - there are no continuation lines of any kind +# - user/repo names as simple as possible # objectives, over and above gitosis: # - simpler syntax @@ -12,17 +13,19 @@ # - allows branch level control # ---------------------------------------------------------------------------- -# USERS and GROUPS +# LISTS # syntax: -# @groupname = username [...] +# @listname = name [...] +# lists can be used as shorthand for usernames as well as reponames -# usernames and groupnames should be as simple as possible - -# too many users in one group? just add more such lines -# (they accumulate, like squid ACLs) +# a list is equivalent to typing out all the right hand side names, so why do +# we need lists at all? (1) to be able to reuse the same set of usernames in +# the paras for different repos, (2) to keep the lines short, because lists +# accumulate, like squid ACLs, so you can say: @cust_A = cust1 cust2 @cust_A = cust99 +# and this is the same as listing all three on the same line # you can nest groups, but not recursively of course! @interns = indy james @@ -31,11 +34,15 @@ @staff = me alice @secret_staff = bruce whitfield martin +@pubrepos = linux git + +@privrepos = supersecretrepo anothersecretrepo + # ---------------------------------------------------------------------------- # REPOS, REFS, and PERMISSIONS # syntax: -# repo [one or more reponames] +# repo [one or more repos] # (R|RW|RW+) [zero or more refnames] = [one or more users] # notes: @@ -51,8 +58,9 @@ # - prefixed by "refs/heads/" if it doesn't start with "refs/" # (i.e., tags have to be explicitly named as refs/tags/pattern) -# - the list of users can inlude any group name defined earlier -# - "@all" is a special, predefined, groupname +# - the list of users or repos can inlude any group name defined earlier +# - "@all" is a special, predefined, groupname that means "all users" +# (there is no corresponding shortcut for all repos) # anyone can play in the sandbox, including making non-fastforward commits # (that's what the "+" means) @@ -72,7 +80,7 @@ repo cust_A_repo # idea for the tags syntax shamelessly copied from git.git # Documentation/howto/update-hook-example.txt :) -repo secret +repo @privrepos thirdsecretrepo RW+ pu = bruce RW master next = bruce RW refs/tags/v[0-9].* = bruce diff --git a/doc/0-UPGRADE.mkd b/doc/0-UPGRADE.mkd index 4d55ac0..9b0d9a5 100644 --- a/doc/0-UPGRADE.mkd +++ b/doc/0-UPGRADE.mkd @@ -37,6 +37,13 @@ And you're done. If any extra steps beyond the generic ones above are needed, they will be listed here, newest first. +#### upgrading from 5758f69 + +Between 5758f69 and this version, gitolite learnt to allow "groupnames" for +repos as well. The `conf/example.conf` has been recommented to explain the +syntax but it's really a no-brainer: what you could previously do only for +usernames, you can now do for reponames also. + #### upgrading from abb4580 Two new features (personal branches, and customisable logfile names/locations) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 69bbb75..d8a583b 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -58,8 +58,11 @@ die "$ATTN parse $glrc failed: " . ($! or $@) unless do $glrc; # command and options for authorized_keys my $AUTH_COMMAND="$GL_ADMINDIR/src/gl-auth-command"; my $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; -my $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern +# note that REPONAME_PATT allows a "/" also, which USERNAME_PATT doesn't +my $REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern +my $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern +# groups can now represent user groups or repo groups my %groups = (); my %repos = (); @@ -80,14 +83,16 @@ sub wrap_open { return $fh; } -sub expand_userlist +sub expand_list { my @list = @_; my @new_list = (); for my $item (@list) { - die "$ATTN bad user $item\n" unless $item =~ $USERNAME_PATT; + # we test with the slightly more relaxed pattern here; we'll catch the + # "/" in user name thing later; it doesn't affect security anyway + die "$ATTN bad user or repo name $item\n" unless $item =~ $REPONAME_PATT; if ($item =~ /^@/) # nested group { die "$ATTN undefined group $item\n" unless $groups{$item}; @@ -124,16 +129,19 @@ while (<$conf_fh>) # and blank lines next unless /\S/; - # user groups + # user or repo groups if (/^(@\S+) = (.*)/) { - push @{ $groups{$1} }, expand_userlist( split(' ', $2) ); - die "$ATTN bad group $1\n" unless $1 =~ $USERNAME_PATT; + push @{ $groups{$1} }, expand_list( split(' ', $2) ); + # again, we take the more "relaxed" pattern + die "$ATTN bad group $1\n" unless $1 =~ $REPONAME_PATT; } # repo(s) elsif (/^repo (.*)/) { - @repos = split(' ', $1); + # grab the list and expand any @stuff in it + @repos = split ' ', $1; + @repos = expand_list ( @repos ); } # actual permission line elsif (/^(R|RW|RW\+) (.* )?= (.+)/) @@ -150,8 +158,9 @@ while (<$conf_fh>) @refs = map { m(^refs/) or s(^)(refs/heads/); $_ } @refs; # expand the user list, unless it is just "@all" - @users = expand_userlist ( @users ) + @users = expand_list ( @users ) unless (@users == 1 and $users[0] eq '@all'); + do { die "$ATTN bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users; # ok, we can finally populate the %repos hash for my $repo (@repos) # each repo in the current stanza From 838dd65d5fafbff5b2fcd5c447830f5ec098ed88 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 21 Sep 2009 07:48:30 +0530 Subject: [PATCH 065/637] compile+doc/3: deal with older gits - detect/warn git version < 1.6.2 - create documentation with details on client-side workaround - change the "git init --bare" to (older) "git --bare init", since the old syntax still works anyway --- doc/3-faq-tips-etc.mkd | 30 ++++++++++++++++++++++++++++++ src/gl-compile-conf | 15 ++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 4347f3f..c31f4da 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -3,6 +3,7 @@ In this document: * common errors and mistakes + * git version dependency * other errors, warnings, notes... * differences from gitosis * two levels of access rights checking @@ -25,6 +26,35 @@ In this document: the client side also :-) In fact gitolite prepends `$REPO_BASE` when it is required anyway, so you shouldn't do the same thing! +### git version dependency + +Here's a workaround for a version dependency that the normal flow of gitolite +has. + +When you edit your config file to create a new repo, and run +`src/gl-compile-conf`, gitolite creates an empty, bare repo for you. +Normally, you're expected to clone this on the client side, and start working +-- make your first commit(s), then push, etc. + +However, cloning an empty repo requires a server side git version that is at +least 1.6.2. Gitolite detects this when creating a repo, and warns you. + +The workaround is to use the older (gitosis-style) method on the client: +create an empty repo locally, make a commit or two, set an "origin" remote, +and then push. Something like: + + mkdir my-new-project + cd my-new-project + git init + git commit --allow-empty -m 'Initial repository' + # or, if your client side git is too old for --allow-empty, just make some + # files, "git add" them, then "git commit" + git remote add origin git@gitolite-server:my-new-project.git + git push origin master:master + +Once this is done, the repo is available for cloning by anyone else in the +normal way, since it's not empty anymore. + ### other errors, warnings, notes... * cloning an empty repo is only possible with clients greater than 1.6.2. diff --git a/src/gl-compile-conf b/src/gl-compile-conf index d8a583b..06917da 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -192,6 +192,13 @@ close $compiled_fh or die "$ATTN close compiled-conf failed: $!\n"; # did not have that luxury, so it was forced to detect the first push and # create it then +# but it turns out not everyone has "modern" gits :) +my $git_version = `git --version`; +my ($gv_maj, $gv_min, $gv_patchrel) = ($git_version =~ m/git version (\d+)\.(\d+)\.(\d+)/); +die "$ATTN I can't understand $git_version\n" unless ($gv_maj >= 1); +$git_version = $gv_maj*10000 + $gv_min*100 + $gv_patchrel; # now it's "normalised" +my $git_too_old = 0; + # repo-base needs to be an absolute path for this loop to work right # so if it was not already absolute, prefix $HOME. my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); @@ -202,12 +209,18 @@ for my $repo (keys %repos) { mkdir("$repo.git") or die "$ATTN mkdir $repo.git failed: $!\n"; wrap_chdir("$repo.git"); - system("git init --bare"); + system("git --bare init"); system("cp $GL_ADMINDIR/src/update-hook.pl hooks/update"); chmod 0755, "hooks/update"; wrap_chdir("$repo_base_abs"); + $git_too_old++ if $git_version < 10602; # that's 1.6.2 to you } } +warn "\n\t\t***** WARNING *****\n" . + "\tyour git version is older than 1.6.2\n" . + "\tgitolite will work but you MUST read the section on\n" . + "\t\"git version dependency\" in doc/3-faq-tips-etc.mkd\n" + if $git_too_old; # ---------------------------------------------------------------------------- # "compile" ssh authorized_keys From df3dd0de488ef8cfca0160dd82e4d86a8d1e41f2 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 21 Sep 2009 14:41:37 +0530 Subject: [PATCH 066/637] compile, rc, doc/3: allow custom umask --- conf/example.gitolite.rc | 8 ++++++++ doc/3-faq-tips-etc.mkd | 7 +++++-- src/gl-compile-conf | 6 +++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index f5e64a1..d2adbfd 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -16,6 +16,14 @@ $REPO_BASE="repositories"; +# the default umask for repositories is 0077; change this if you run stuff +# like gitweb and find it can't read the repos. Please note the syntax; the +# leading 0 is required + +$REPO_UMASK = 0077; # gets you 'rwx------' +# $REPO_UMASK = 0027; # gets you 'rwxr-x---' +# $REPO_UMASK = 0022; # gets you 'rwxr-xr-x' + # -------------------------------------- # I see no reason anyone may want to change the gitolite admin directory, but diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index c31f4da..595d938 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -59,7 +59,7 @@ normal way, since it's not empty anymore. * cloning an empty repo is only possible with clients greater than 1.6.2. So at least one of your clients needs to have a recent git. Once at least - one commit has been made, older clients can also use it. + one commit has been made, older clients can also use it * when you clone an empty repo, git seems to complain about the remote hanging up or something. I have no idea what that is, but it doesn't seem @@ -81,7 +81,10 @@ normal way, since it's not empty anymore. automatically be committed. This is a simple safety net in case you accidentally delete the whole config or something. Also see [4-push-to-admin.mkd](http://github.com/sitaramc/gitolite/blob/pu/doc/4-push-to-admin.mkd) - if you really know what you're doing and want "push to admin". + if you really know what you're doing and want "push to admin" + + * gitweb not able to read your repos? You can change the umask for newly + created repos to something more relaxed -- see the `~/.gitolite.rc` file ### differences from gitosis diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 06917da..3154dc1 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -41,7 +41,7 @@ use Data::Dumper; # common definitions # ---------------------------------------------------------------------------- -our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE); +our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK); # now that this thing *may* be run via "push to admin", any errors have to # grab the admin's ATTENTION so he won't miss them among the other messages a @@ -66,8 +66,8 @@ my $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple patter my %groups = (); my %repos = (); -# set a restrictive umask, just in case -umask(0077); +# set the umask before creating any files +umask($REPO_UMASK); # ---------------------------------------------------------------------------- # subroutines From 4879a03c60353a65257add190fdbd2fddd7dd69c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 21 Sep 2009 19:01:47 +0530 Subject: [PATCH 067/637] Makefile wraps "git archive" to record "git describe" output in tar --- .gitignore | 4 ++++ Makefile | 16 ++++++++++++++++ doc/0-INSTALL.mkd | 14 ++++++++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f60da1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.tar +*.tgz +*.tar.gz +*.tar.bz2 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..730fa72 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +# this is a simple wrapper around "git archive" using make + +# "make [refname].tar" produces a tar of refname, then adds a file containing +# the "git describe" output for that refname to the tar. This lets you say +# "cat .GITOLITE-VERSION" to find out which ref produced this tar + +# Note: I'm not sure if that "-r" is a GNU tar extension... + +.GITOLITE-VERSION: + @touch .GITOLITE-VERSION + +%.tar: .GITOLITE-VERSION + git describe --all --long $* > .GITOLITE-VERSION + git archive $* > $@ + tar -r -f $@ .GITOLITE-VERSION + rm .GITOLITE-VERSION diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index 10450a2..9d92ce9 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -13,9 +13,19 @@ A major objective is to allow use by people without root access, permissions to create other userids, etc. Even if you have root, please add a user just for gitolite and do all this from that user. -### quick install +### getting a tar file from a clone - * cd to the directory where you unpacked the source +You can clone the repo from github, then execute a make command to extract a +tar file of the branch you want. Please use the make command, not a plain +"git archive". The comments in the `Makefile` will explain why. + + git clone git://github.com/sitaramc/gitolite.git + make master.tar + # or maybe "make rebel.tar" or "make pu.tar" + +### quick install from tar file + + * make a temp directory somewhere, cd to it, and unpack the tar file * run `src/install.pl` and follow the prompts **When you are told to edit some file, please read the comments in the file**. From 780f636c0aba76d0f3a105c500a7a8ba7831bb40 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 16 Sep 2009 10:16:11 +0530 Subject: [PATCH 068/637] doc warnings: doc/admin: add warning about creating repos manually! doc/4: add warning on compile errors when using p-t-a --- doc/2-admin.mkd | 20 +++++++++++++++++--- doc/4-push-to-admin.mkd | 10 ++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index 028b23a..0585e06 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -5,6 +5,14 @@ ### administer +First of all, ***do NOT add new repos manually***, unless you know how to add +the required hook as well. Without the hook, branch-level access control will +not work for that repo, which sorta defeats the idea of using gitolite :-) + +Please read on to see how to do this correctly. + +#### adding users and repos + * ask each user who will get access to send you a public key. See other sources (for example [here](http://sitaramc.github.com/0-installing/2-access-gitolite.html#generating_a_public_key)) @@ -16,9 +24,15 @@ * copy all these `*.pub` files to `$GL_KEYDIR` - * edit the config file (`$GL_CONF`) and give the new users permissions as - required. The users names should be exactly the same as their keyfile - names, but without the `.pub` extension + * the config file (`$GL_CONF`) is very well commented, please take a couple + of minutes to read it. Then edit it and + + * add new repos as needed + * add new users and give them permissions as required. The users names + should be exactly the same as their keyfile names, but without the + `.pub` extension + +#### compiling * backup your `~/.ssh/authorized_keys` file if you feel nervous :-) * that's "backup" as in "copy", not "move". The next step won't work if diff --git a/doc/4-push-to-admin.mkd b/doc/4-push-to-admin.mkd index ef5125b..12a6bc3 100644 --- a/doc/4-push-to-admin.mkd +++ b/doc/4-push-to-admin.mkd @@ -22,6 +22,16 @@ The instructions are presented as shell commands; they should be fairly obvious. All paths are from the default `~/.gitolite.rc`; if you changed any, make the same changes below. +> ---- + +> **WARNING**: the "compilation" runs via a `post-update` hook. Which, by +> definition, runs *after* the push has successfully completed. As a +> result, a *compilation error* will be visible to the admin doing the `git +> push` but will not otherwise look like an error to client-side git (in +> terms of return codes, scripting, etc., or even the "git gui" if you +> happen to use that for pushing). So be sure to watch out for compile +> error messages on push when you do this. + ---- First, on the server, log on to the `git` userid, add a new repo called From 5415b425e765896971bafb66eae67ab928ac0868 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 16 Sep 2009 19:52:03 +0530 Subject: [PATCH 069/637] example conf, doc/3: explain refexes --- conf/example.conf | 20 +++++++++++++++----- doc/3-faq-tips-etc.mkd | 12 ++++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index 86fdeca..14c6817 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -43,7 +43,7 @@ # syntax: # repo [one or more repos] -# (R|RW|RW+) [zero or more refnames] = [one or more users] +# (R|RW|RW+) [zero or more refexes] = [one or more users] # notes: @@ -53,15 +53,25 @@ # - RW+ means non-ff push is allowed # - you can't write just "W" or "+"; it has to be R, or RW, or RW+ -# - if no ref name appears, the rule applies to all refs in that repo -# - ref names are perl regex patterns -# - prefixed by "refs/heads/" if it doesn't start with "refs/" -# (i.e., tags have to be explicitly named as refs/tags/pattern) +# - a refex is a regex that matches a ref :-) If you see the examples +# below you'll get it easy enough + +# - refexes are specified in perl regex syntax +# - if no refex appears, the rule applies to all refs in that repo +# - a refex is automatically prefixed by "refs/heads/" if it doesn't start +# with "refs/" (so tags have to be explicitly named as +# refs/tags/pattern) # - the list of users or repos can inlude any group name defined earlier # - "@all" is a special, predefined, groupname that means "all users" # (there is no corresponding shortcut for all repos) +# matching: + +# - user, repo, and access (W or +) are known. For that combination, if +# any of the refexes match the refname being updated, the push succeeds. +# If none of them match, it fails + # anyone can play in the sandbox, including making non-fastforward commits # (that's what the "+" means) repo sandbox diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 595d938..465959c 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -10,7 +10,7 @@ In this document: * error checking the config file * one user, many keys * who am I? - * cool ideas I want feedback on + * other cool things * developer specific branches ### common errors and mistakes @@ -117,8 +117,12 @@ only `R` (read access) don't count. *The user must have write access to The **second check** is via a git `update hook`. This check only happens for write operations. By this time we know what "ref" he is trying to update, as well as the old and the new SHAs of that ref (by which we can also deduce -whether it's a fast forward or not). This is where the "per-branch" -permissions come into play. +whether it's a rewind or not). This is where the "per-branch" permissions +come into play. + +Each refex that allows `W` access (or `+` if this is a rewind) for *this* +user, on *this* repo, is matched against the actual refname being updated. If +any of the refexes match, the push succeeds. If none of them match, it fails. #### error checking the config file @@ -188,7 +192,7 @@ In gitolite, it's simple: just ask nicely :-) PTY allocation request failed on channel 0 no SSH_ORIGINAL_COMMAND? I'm not a shell, sitaram! -### cool ideas +### other cool things #### developer specific branches From 344723b9741239efc23757b82596c3b08549db7a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 16 Sep 2009 22:25:32 +0530 Subject: [PATCH 070/637] conf+doc/3: explain why we don't like "exclude rules" in refexes --- conf/example.conf | 2 +- doc/3-faq-tips-etc.mkd | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/conf/example.conf b/conf/example.conf index 14c6817..7170140 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -94,6 +94,6 @@ repo @privrepos thirdsecretrepo RW+ pu = bruce RW master next = bruce RW refs/tags/v[0-9].* = bruce - RW refs/tags/ = @secret_staff + RW refs/tags/ss/ = @secret_staff RW tmp/.* = @secret_staff R = @secret_staff diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 465959c..6fcc649 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -8,10 +8,13 @@ In this document: * differences from gitosis * two levels of access rights checking * error checking the config file + * built-in logging * one user, many keys * who am I? * other cool things * developer specific branches + * design choices + * why we don't do "excludes" ### common errors and mistakes @@ -249,3 +252,52 @@ first check: RW dummy = user3 Just don't *show* the user this config file; it might sound insulting :-) + +### design choices + +#### why we don't do "excludes" + +I found an error in the example conf file. This snippet *seems* to say that +"bruce" can write versioned tags (`refs/tags/v[0-9].*`), but the other +staffers can't: + + @staff = bruce whitfield martin + [... and later ...] + RW refs/tags/v[0-9].* = bruce + RW refs/tags = @staff + +But that's not how the matching works. As long as any refex matches the +refname being updated, it's a "yes". So the second refex lets anyone on +`@staff` create versioned tags, not just Bruce. + +One way to fix this is to allow "excludes" -- some changes in syntax, combined +with a rigorous, ordered, interpretation would do it. + +But if you're ever played with squid ACLs, or the include/exclude rules for +rsync, or rdiff-backup, or even git's own ignore mechanism, you'll see why I +won't do this. It bloats the code and the docs, and, despite all the docs, +*still* confuses people, which may then *reduce* security! + +Squid, rsync, gitignore, and all *need* the feature and so tolerate all this; +but we don't need it. All we need to do is make the refexes *disjoint* in +what they match (i.e., ensure that no refname can be matched by more than one +refex): + + RW refs/tags/v[0-9].* = bruce + RW refs/tags/staff/ = @staff + +In general, you probably want to control the refnames writable by devs anyway, +if at least to maintain some sanity, so being forced to make the refexes +disjoint is not a big problem. Here's an example: only the `project_lead` can +make arbitrarily named refs, while the rest have to stay within their assigned +namespaces: + + RW+ = project_lead + RW refs/tags/qa/ = @qa_team + RW bugID/ = @dev_team + RW trac/ = @dev_team + +The lack of overlap between refexes ensures ***no confusion*** in specifying, +understanding, and ***auditing***, what is allowed and what is not. + +And in security, "no confusion" is a good thing :-) From 09fd745255d5c2d7e37f9d888065d0de05ee484c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 18 Sep 2009 09:10:35 +0530 Subject: [PATCH 071/637] upgrade doc: added step to compile --- doc/0-UPGRADE.mkd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/0-UPGRADE.mkd b/doc/0-UPGRADE.mkd index 9b0d9a5..9086661 100644 --- a/doc/0-UPGRADE.mkd +++ b/doc/0-UPGRADE.mkd @@ -30,6 +30,11 @@ have to do it at a "quiet" time or something. src/install.pl +5. compile the config once again, in case the *internal* format of the + compiled config file (`$GL_CONF_COMPILED`) has changed + + src/gl-compile-conf + And you're done. ### upgrade notes for specific versions From 2285e75c222df1768490c4a2c58d4c4226528ee5 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 18 Sep 2009 17:04:43 +0530 Subject: [PATCH 072/637] example rc: say that $PERSONAL must start with "refs/" --- conf/example.gitolite.rc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index d2adbfd..ee5a3e3 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -67,10 +67,13 @@ $GL_CONF_COMPILED="$GL_ADMINDIR/conf/gitolite.conf-compiled.pm"; $PERSONAL=""; -# uncomment one of these if you do want it. I recommend this: +# uncomment one of these if you do want it. If you change it, remember it +# MUST start with "refs/" + +# I recommend this: # $PERSONAL="refs/personal"; -# but if you want something more visible/noisy, use this: +# if you want something more visible/noisy, use this: # $PERSONAL="refs/heads/personal"; # NOTE: whatever value you choose, for security reasons it is better to make From 978046acb9d7adb9d352eb275f20f958cbe46551 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 18 Sep 2009 18:00:14 +0530 Subject: [PATCH 073/637] compile/update hook: COMPILED FILE CHANGE -- PLEASE READ BELOW Summary: DONT forget to run src/gl-compile-conf as the last step in the upgrade Details: The compiled file format has changed quite a bit, to make it easier for the rebel edition coming up :-) compile: - we don't split RW/RW+ into individual perms anymore - we store the info required for the first level check separately now: (repo, R/W, user) - the order for second level check is now: repo, user, [{ref=>perms}...] (list of hashes) update hook logic: the first refex that: - matches the incoming ref, AND - contains the perm you're trying to use, causes the match loop to exit with success. Fallthrough is failure --- doc/0-UPGRADE.mkd | 6 ++++++ src/gl-compile-conf | 15 ++++++++++----- src/update-hook.pl | 20 ++++++++++++-------- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/doc/0-UPGRADE.mkd b/doc/0-UPGRADE.mkd index 9086661..a374c05 100644 --- a/doc/0-UPGRADE.mkd +++ b/doc/0-UPGRADE.mkd @@ -42,6 +42,12 @@ And you're done. If any extra steps beyond the generic ones above are needed, they will be listed here, newest first. +#### upgrading from 86faae4 + +Between 86faae4 and this version, gitolite had a *major* change in the +*internal* format of the compiled config file. Please do not omit step 5 in +the generic instructions above. + #### upgrading from 5758f69 Between 5758f69 and this version, gitolite learnt to allow "groupnames" for diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 3154dc1..33cc057 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -3,6 +3,7 @@ use strict; use warnings; use Data::Dumper; +$Data::Dumper::Indent = 1; # === add-auth-keys === @@ -146,8 +147,7 @@ while (<$conf_fh>) # actual permission line elsif (/^(R|RW|RW\+) (.* )?= (.+)/) { - # split perms to separate out R, W, and + - my @perms = split //, $1; + my $perms = $1; my @refs; @refs = split(' ', $2) if $2; my @users = split ' ', $3; @@ -165,11 +165,16 @@ while (<$conf_fh>) # ok, we can finally populate the %repos hash for my $repo (@repos) # each repo in the current stanza { - for my $perm (@perms) + for my $user (@users) { - for my $user (@users) + # for 1st level check (see faq/tips doc) + $repos{$repo}{R}{$user} = 1 if $perms =~ /R/; + $repos{$repo}{W}{$user} = 1 if $perms =~ /W/; + + # for 2nd level check, store each "ref, perms" pair in order + for my $ref (@refs) { - push @{ $repos{$repo}{$perm}{$user} }, @refs; + push @{ $repos{$repo}{$user} }, { $ref => $perms }; } } } diff --git a/src/update-hook.pl b/src/update-hook.pl index c703723..f65641e 100755 --- a/src/update-hook.pl +++ b/src/update-hook.pl @@ -58,13 +58,17 @@ $perm = '+' if $ref =~ m(refs/tags/) and $oldsha ne ('0' x 40); $perm = '+' if $oldsha ne $merge_base; my @allowed_refs; -push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{$ENV{GL_USER}} || [] }; -push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$perm}{'@all'} || [] }; -push @allowed_refs, "$PERSONAL/$ENV{GL_USER}/" if $PERSONAL; -for my $refex (@allowed_refs) -# refex? sure -- a regex to match a ref against :) +# personal stuff -- right at the start in the new regime, I guess! +push @allowed_refs, { "$PERSONAL/$ENV{GL_USER}/" => "RW+" } if $PERSONAL; +# we want specific perms to override @all, so they come first +push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$ENV{GL_USER}} || [] }; +push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{'@all'} || [] }; +for my $ar (@allowed_refs) { - if ($ref =~ /$refex/) + my $refex = (keys %$ar)[0]; + # refex? sure -- a regex to match a ref against :) + next unless $ref =~ /$refex/; + if ($ar->{$refex} =~ /\Q$perm/) { # if log failure isn't important enough to block pushes, get rid of # all the error checking @@ -72,9 +76,9 @@ for my $refex (@allowed_refs) or die "open log failed: $!\n"; print $log_fh "$ENV{GL_TS} $perm\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . - "\t$ENV{GL_REPO}\t$ref\t$ENV{GL_USER}\n"; + "\t$ENV{GL_REPO}\t$ref\t$ENV{GL_USER}\t$refex\n"; close $log_fh or die "close log failed: $!\n"; exit 0; } } -exit 1; +die "$perm $ref $ENV{GL_USER} DENIED by fallthru\n"; From 2a763dfdb11f09ed05fda569e64566f69e7db8d4 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 20 Sep 2009 06:37:15 +0530 Subject: [PATCH 074/637] doc/3: updated the log line description --- doc/3-faq-tips-etc.mkd | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 6fcc649..d919087 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -69,9 +69,6 @@ normal way, since it's not empty anymore. to hurt anything. This happens even in normal git, not just gitolite. [Update 2009-09-14; this has been fixed in git 1.6.4.3] - * once in a while, if you're feeling particularly BOFH-ish, take a look at - `$GL_ADMINDIR/log` :-) - * if you specify a repo that is not at the top level `$REPO_BASE`, be sure to manually create the intermediate directories first. For instance if you specify a new repo called "a/b/c" to the config file and "compile", @@ -154,13 +151,17 @@ not unless you add a one-line patch to gitosis, plus a `post-receive` hook to every repository. With gitolite, there's a log file in `$GL_ADMINDIR` that contains lines like -this [I have abbreviated the SHAs for brevity in this document; the actual log -file will have all 40 characters]: +this: - +: username reponame refs/heads/branchname d0188d1 c5c00b6 + 2009-09-19.10:24:37 + b4e76569659939 4fb16f2a88d8b5 myrepo refs/heads/master user2 refs/heads/master The "+" at the start indicates a non-fast forward update, in this case from -d0188d1 to c5c00b6 So d0188d1 is the one to restore! Can it get easier? +b4e76569659939 to 4fb16f2a88d8b5. So b4e76569659939 is the one to restore! +Can it get easier? + +The other parts of the log line are the name of the repo, the refname being +updated, the user updating it, and the refex pattern (from the config file) +that matched, in case you need to debug the config file itself. #### one user, many keys From 8217ef9d5bb0d14e96f16e00fdd16d8608e44b5c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 20 Sep 2009 21:25:16 +0530 Subject: [PATCH 075/637] P-T-A doc: add note about switching back and forth --- doc/4-push-to-admin.mkd | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/4-push-to-admin.mkd b/doc/4-push-to-admin.mkd index 12a6bc3..3fc83bf 100644 --- a/doc/4-push-to-admin.mkd +++ b/doc/4-push-to-admin.mkd @@ -12,17 +12,24 @@ people new to git, or ssh or (worse) both. However, ***if*** you know git and ssh really, *really*, well and you know what you're doing, this is a pretty nice thing to have -- does make life -easier, I admit. +easier, I admit. So, here is how to make PTA (hey nice acronym, just missing +an "I") work on gitolite as well. -So, here is how to make PTA (hey nice acronym, just missing an "I") work on -gitolite as well. But remember, there is NO SUPPORT! Go away. Leave me -alone... Anything else on gitolite I will help but not this, ok? :-) +Note: + + * unlike the rest of gitolite, I can't help you with this unless you + convince me very quickly it's not a layer 8 problem :-) + + * here's a test to see if you should use this feature: after reading this + document, think about how you would switch back and forth between the + normal method and push-to-admin. If you can't immediately see what you + would need to do, please don't use it :-) The instructions are presented as shell commands; they should be fairly obvious. All paths are from the default `~/.gitolite.rc`; if you changed any, make the same changes below. -> ---- +---- > **WARNING**: the "compilation" runs via a `post-update` hook. Which, by > definition, runs *after* the push has successfully completed. As a From 70d26d810b4089f2f66577fc68adc4b1c6e53f0e Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 25 Sep 2009 12:17:33 +0530 Subject: [PATCH 076/637] compile, all docs/confs: specify gitweb/daemon access + bonus bonus: documented the "bits and pieces" thing properly; should have done this long ago, but it came to the forefront now thanks to this item --- README.mkd | 6 +++- conf/example.conf | 21 +++++++++-- conf/example.gitolite.rc | 5 +++ doc/0-UPGRADE.mkd | 6 ++++ doc/1-migrate.mkd | 2 ++ doc/2-admin.mkd | 23 ++++++++++++ doc/3-faq-tips-etc.mkd | 77 ++++++++++++++++++++++++++++++++++++++++ src/gl-compile-conf | 74 +++++++++++++++++++++++++++++++++++++- 8 files changed, 209 insertions(+), 5 deletions(-) diff --git a/README.mkd b/README.mkd index fa8e2f4..5a59bd1 100644 --- a/README.mkd +++ b/README.mkd @@ -38,7 +38,11 @@ that: * no one in $DAYJOB type environments will use or approve access methods that work without any authentication, so I didn't need gitweb/daemon - support in the tool or in the config file + support in the tool or in the config file. + + Update 2009-09-24: I don't use this feature but someone wanted it, so I + added it... see the "faq, tips, etc" document for more + * the idea that you admin it by pushing to a special repo is nice, but not really necessary because of how rarely these changes are made, especially considering how much code is involved in that piece diff --git a/conf/example.conf b/conf/example.conf index 7170140..6ea6234 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -8,9 +8,13 @@ # objectives, over and above gitosis: # - simpler syntax -# - no gitweb/daemon control -# - allows ff/non-ff control -# - allows branch level control +# - easier gitweb/daemon control +# - specify who can push a branch/tag +# - specify who can rewind a branch/rewrite a tag + +# convenience: allow specifying the access control in bits and pieces, even if +# they overlap. Keeps the config file smaller and saner. See the example in +# the "faq, tips, etc" document # ---------------------------------------------------------------------------- # LISTS @@ -97,3 +101,14 @@ repo @privrepos thirdsecretrepo RW refs/tags/ss/ = @secret_staff RW tmp/.* = @secret_staff R = @secret_staff + +# ---------------------------------------------------------------------------- +# GITWEB AND DAEMON CONTROL + +# there is no special syntax for this. If a repo gives read permissions to +# the special user "gitweb" or "daemon", the corresponding changes are made +# when you compile; see "faq, tips, etc" document for details. + +# this means you cannot have a real user called "gitweb" or "daemon" but I +# don't think that is a problem :-) + diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index ee5a3e3..3725110 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -24,6 +24,11 @@ $REPO_UMASK = 0077; # gets you 'rwx------' # $REPO_UMASK = 0027; # gets you 'rwxr-x---' # $REPO_UMASK = 0022; # gets you 'rwxr-xr-x' +# part of the setup of gitweb is a variable called $projects_list (please see +# gitweb documentation for more on this). Set this to the same value: + +$PROJECTS_LIST = "/home/git/projects.list"; + # -------------------------------------- # I see no reason anyone may want to change the gitolite admin directory, but diff --git a/doc/0-UPGRADE.mkd b/doc/0-UPGRADE.mkd index a374c05..8c80bb7 100644 --- a/doc/0-UPGRADE.mkd +++ b/doc/0-UPGRADE.mkd @@ -42,6 +42,12 @@ And you're done. If any extra steps beyond the generic ones above are needed, they will be listed here, newest first. +#### upgrading from 8217ef9 + +Between 8217ef9 and this version, gitolite learnt to handle gitweb/daemon +access. As a result, the rc file acquired a new variable, `$PROJECTS_LIST`, +which you have to set to whatever your gitweb installation requires. + #### upgrading from 86faae4 Between 86faae4 and this version, gitolite had a *major* change in the diff --git a/doc/1-migrate.mkd b/doc/1-migrate.mkd index 6ef0c90..fe0b4cf 100644 --- a/doc/1-migrate.mkd +++ b/doc/1-migrate.mkd @@ -1,5 +1,7 @@ # migrating from gitosis to gitolite +[TODO: make the migration tool fix up gitweb and daemon control also...] + Migrating from gitosis to gitolite is pretty easy, because the basic design is the same. The differences are: diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index 0585e06..8cd2216 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -32,6 +32,29 @@ Please read on to see how to do this correctly. should be exactly the same as their keyfile names, but without the `.pub` extension +#### specifying gitweb and daemon access + +This is a feature that I personally do not use (corporate environments don't +like unauthenticated access of any kind to any repo!), but someone wanted it, +so here goes. + +There's **no special syntax** for this -- just give read permission to a user +called `gitweb` or `daemon`! (This also means you can't have a normal user +with either of those two names, but I doubt that's a problem!). See the "faq, +tips, etc" document for easy ways to specify access for multiple repositories. + +Note that this does **not** install or configure gitweb/daemon -- that is a +one-time setup you must do separately. All this does is: + + * for gitweb, add the repo to the list of projects to be served by gitweb + (see the config file variable `$PROJECTS_LIST`, which should have the same + value you specified for `$projects_list` when setting up gitweb) + * for daemon, create the file `git-daemon-export-ok` in the repository + +`src/gl-compile-conf` will keep these files consistent with the config +settings -- this includes removing such settings if you remove "read" +permissions for the special usernames. + #### compiling * backup your `~/.ssh/authorized_keys` file if you feel nervous :-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index d919087..b28c548 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -6,8 +6,10 @@ In this document: * git version dependency * other errors, warnings, notes... * differences from gitosis + * simpler syntax * two levels of access rights checking * error checking the config file + * easier to specify gitweb/daemon access * built-in logging * one user, many keys * who am I? @@ -92,6 +94,49 @@ Apart from the big ones listed in the top level README, and subjective ones like "better config file format", there are some small, but significant and concrete, differences from gitosis. +#### simpler syntax + +The basic syntax is simpler and cleaner but it goes beyond that: **you can +specify access in bits and pieces**, even if they overlap. + +Some access needs are best grouped by repo, some by username, and some by +both. So just do all of them, and gitolite will combine all the access lists! +Here's an example: + + # define groups of people + @bosses = phb1 phb2 phb3 + @devs = dev1 dev2 dev3 + @interns = int1 int2 int3 + + # define groups of projects + @open = git gitolite linux rakudo + @closed = c1 c2 c3 + @topsecret = ts1 ts2 ts3 + + # all bosses have read access to all projects + repo @open @closed @topsecret + R = @bosses + + # everyone has read access to "open" projects + repo @open + R = @bosses @devs @interns + + [...or any other combination you want...] + + # later in the file: + + # specify access for individual repos (like RW, RW+, etc) + repo c1 + [...] + + [...etc...] + +If you notice that `@bosses` are given read access to `@open` via both rules, +do not worry that this causes some duplication or inefficiency. It doesn't +:-) + +See the "specify gitweb/daemon access" section below for one more example. + #### two levels of access rights checking Gitolite has two levels of access checks. The **first check** is what I will @@ -134,6 +179,38 @@ In gitolite, you have to "compile" the config file first (this step takes the place of the commit+push in gitosis), and keyword typos *are* caught so you know right away. +#### easier to specify gitweb/daemon access + +Specifying gitweb and/or daemon access for a repo is simple: give "read" +permissions to two special usernames: `gitweb` and `daemon`. + +You can also keep these pieces separate from the detailed, branch level access +for each repo, if you like, since you can write the access control specs in +bits and pieces. Here's an example, using short repo names for convenience: + + # maybe near the top of the file, for ease of access: + + @only_web = r1 r2 r3 + @only_daemon = r4 r5 r6 + @web_and_daemon = r7 r8 r9 + + repo @only_web + R = gitweb + repo @only_daemon + R = daemon + repo @web_and_daemon + R = gitweb + R = daemon + + # ...maybe much later in the file: + + repo r1 + # normal developer access lists for r1 and its branches/tags in the + # usual way + + repo r2 + # ...and so on... + #### built-in logging ...just in case of emergency :-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 33cc057..2422bc2 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -15,6 +15,11 @@ $Data::Dumper::Indent = 1; # (gl-)update hook need this, and it seems easier to do this than # replicate the parsing code in both those places. As a bonus, it's # probably more efficient. +# (3) - finally does what I have resisted doing all along -- handle gitweb and +# git-daemon access. It won't *setup* gitweb/daemon for you -- you have +# to that yourself. What this does is make sure that "repo.git" +# contains the file "git-daemon-export-ok" (for daemon case) and the +# line "repo.git" exists in the "projects.list" file (for gitweb case). # how run: manual, by GL admin # when: @@ -42,7 +47,7 @@ $Data::Dumper::Indent = 1; # common definitions # ---------------------------------------------------------------------------- -our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK); +our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST); # now that this thing *may* be run via "push to admin", any errors have to # grab the admin's ATTENTION so he won't miss them among the other messages a @@ -227,6 +232,73 @@ warn "\n\t\t***** WARNING *****\n" . "\t\"git version dependency\" in doc/3-faq-tips-etc.mkd\n" if $git_too_old; +# ---------------------------------------------------------------------------- +# handle gitweb and daemon +# ---------------------------------------------------------------------------- + +# How you specify gitweb and daemon access is quite different from gitosis. 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("$repo_base_abs"); + +# get the current project list; note that the file may not yet exist if no +# gitweb access has been specified so far +my %projlist = (); +if (-f $PROJECTS_LIST) { + my $projlist_fh = wrap_open( "<", $PROJECTS_LIST); + while(<$projlist_fh>) { + chomp; + $projlist{$_} = 1; + } + close $projlist_fh; +} +my $projlist_changed = 0; + +# daemons first... +for my $repo (sort keys %repos) { + my $export_ok = "$repo.git/git-daemon-export-ok"; + if ($repos{$repo}{'R'}{'daemon'}) { + unless (-f $export_ok) { + system("touch $export_ok"); + print STDERR "daemon add $repo.git\n"; + } + } else { + if (-f $export_ok) { + unlink($export_ok); + print STDERR "daemon del $repo.git\n"; + } + } +} + +# ...then gitwebs +for my $repo (sort keys %repos) { + if ($repos{$repo}{'R'}{'gitweb'}) { + unless ($projlist{"$repo.git"}) { + # not in the old list; add it to the new one + $projlist{"$repo.git"} = 1; + $projlist_changed = 1; + print STDERR "gitweb add $repo.git\n"; + } + } else { + if ($projlist{"$repo.git"}) { + # delete it from new list + delete $projlist{"$repo.git"}; + $projlist_changed = 1; + print STDERR "gitweb del $repo.git\n"; + } + } +} + +# has there been a change? +if ($projlist_changed) { + print STDERR "updating gitweb project list $PROJECTS_LIST\n"; + my $projlist_fh = wrap_open( ">", $PROJECTS_LIST); + print $projlist_fh join("\n", sort keys %projlist), "\n" if %projlist; + close $projlist_fh; +} + # ---------------------------------------------------------------------------- # "compile" ssh authorized_keys # ---------------------------------------------------------------------------- From c66e1ad73286e1a193373234142daab44e5f7dd6 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 27 Sep 2009 08:02:36 +0530 Subject: [PATCH 077/637] compile: pubkey related linting added - warn about files in keydir/ that dont end with ".pub" - warn about pubkey files for which the user is not mentioned in config - warn more sternly about the opposite (user in config, no pubkey!) update hook: add reponame to message on deny auth: minor typo --- src/gl-auth-command | 2 +- src/gl-compile-conf | 26 +++++++++++++++++++++++--- src/update-hook.pl | 2 +- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index c301415..3822760 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -51,7 +51,7 @@ my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere! # ---------------------------------------------------------------------------- # SSH_ORIGINAL_COMMAND must exist. Since we also captured $user, we print -# that in the message so people saying "ssh git@server" can see which gitosis +# that in the message so people saying "ssh git@server" can see which gitolite # user he is being recognised as my $cmd = $ENV{SSH_ORIGINAL_COMMAND} or die "no SSH_ORIGINAL_COMMAND? I'm not a shell, $user!\n"; diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 2422bc2..ae1ea24 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -71,6 +71,7 @@ my $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple patter # groups can now represent user groups or repo groups my %groups = (); my %repos = (); +my %user_list = (); # only to catch lint; search for "lint" below # set the umask before creating any files umask($REPO_UMASK); @@ -172,6 +173,8 @@ while (<$conf_fh>) { for my $user (@users) { + $user_list{$user}++; # only to catch lint, see later + # for 1st level check (see faq/tips doc) $repos{$repo}{R}{$user} = 1 if $perms =~ /R/; $repos{$repo}{W}{$user} = 1 if $perms =~ /W/; @@ -195,7 +198,7 @@ print $compiled_fh Data::Dumper->Dump([\%repos], [qw(*repos)]); close $compiled_fh or die "$ATTN close compiled-conf failed: $!\n"; # ---------------------------------------------------------------------------- -# any new repos created? +# any new repos to be created? # ---------------------------------------------------------------------------- # modern gits allow cloning from an empty repo, so we just create it. Gitosis @@ -291,7 +294,7 @@ for my $repo (sort keys %repos) { } } -# has there been a change? +# has there been a change in the gitweb projects list? if ($projlist_changed) { print STDERR "updating gitweb project list $PROJECTS_LIST\n"; my $projlist_fh = wrap_open( ">", $PROJECTS_LIST); @@ -317,12 +320,29 @@ while (<$authkeys_fh>) # options, in the standard ssh authorized_keys format), then the "end" line. print $newkeys_fh "# gitolite start\n"; wrap_chdir($GL_KEYDIR); -for my $pubkey (glob("*.pub")) +for my $pubkey (glob("*")) { + # lint check 1 + unless ($pubkey =~ /\.pub$/) + { + print STDERR "WARNING: pubkey files should end with \".pub\", ignoring $pubkey\n"; + next; + } my $user = $pubkey; $user =~ s/(\@.+)?\.pub$//; + # lint check 2 + print STDERR "WARNING: pubkey $pubkey exists but user $user not in config\n" + unless $user_list{$user}; + $user_list{$user} = 'has pubkey'; print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS "; print $newkeys_fh `cat $pubkey`; } +# lint check 3; a little more severe than the first two I guess... +for my $user (sort keys %user_list) +{ + next if $user eq '@all' or $user_list{$user} eq 'has pubkey'; + print STDERR "$ATTN user $user in config, but has no pubkey!\n"; +} + print $newkeys_fh "# gitolite end\n"; close $newkeys_fh or die "$ATTN close newkeys failed: $!\n"; diff --git a/src/update-hook.pl b/src/update-hook.pl index f65641e..5f1c3bb 100755 --- a/src/update-hook.pl +++ b/src/update-hook.pl @@ -81,4 +81,4 @@ for my $ar (@allowed_refs) exit 0; } } -die "$perm $ref $ENV{GL_USER} DENIED by fallthru\n"; +die "$perm $ref $ENV{GL_REPO} $ENV{GL_USER} DENIED by fallthru\n"; From 1b9969f3d6adb14559f939e082e170ee7f6f4fdc Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 27 Sep 2009 23:52:04 +0530 Subject: [PATCH 078/637] auth: better message, remove unsightly perl warning on bad command --- conf/example.conf | 2 ++ src/gl-auth-command | 12 ++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index 6ea6234..5e92b0a 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -5,6 +5,8 @@ # - comments in the normal shell-ish style; no surprises there # - there are no continuation lines of any kind # - user/repo names as simple as possible +# (usernames: only alphanumerics, ".", "_", "-"; +# reponames: same, plus "/", but not at the start) # objectives, over and above gitosis: # - simpler syntax diff --git a/src/gl-auth-command b/src/gl-auth-command index 3822760..62573d4 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -56,11 +56,6 @@ my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere! my $cmd = $ENV{SSH_ORIGINAL_COMMAND} or die "no SSH_ORIGINAL_COMMAND? I'm not a shell, $user!\n"; -# this check is largely for comic value if someone tries something outrageous; -# $cmd gets split and the pieces examined more thoroughly later anyway -die "$cmd??? you're a funny guy...\n" - if $cmd =~ /[<>&|;\n]/; - # split into command and arguments; the pattern allows old style as well as # new style: "git-subcommand arg" or "git subcommand arg", just like gitosis # does, although I'm not sure how necessary that is @@ -70,9 +65,10 @@ die "$cmd??? you're a funny guy...\n" # including the single quotes my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*).git'/); -die "Sorry, I don't like the command you gave me: $cmd\n" - unless ( ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) - and $repo =~ $REPONAME_PATT ); +die "bad command: $cmd. Make sure the repo name is exactly\n" . + "as in your config (no extra stuff before the name), plus a \".git\" at the end\n" + unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) + and $repo and $repo =~ $REPONAME_PATT ); # ---------------------------------------------------------------------------- # first level permissions check From c15c75749bb0b2b4ae8752dd7cc4559aa687b2af Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 3 Oct 2009 10:55:30 +0530 Subject: [PATCH 079/637] compile: special-case 'gitweb' and 'daemon' from the linting not a big deal since there's a very simple and obvious workaround -- create a new keypair, throw away the private key, and use the pubkey --- src/gl-compile-conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index ae1ea24..9b13dd7 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -339,7 +339,7 @@ for my $pubkey (glob("*")) # lint check 3; a little more severe than the first two I guess... for my $user (sort keys %user_list) { - next if $user eq '@all' or $user_list{$user} eq 'has pubkey'; + next if $user =~ /^(gitweb|daemon|\@all)$/ or $user_list{$user} eq 'has pubkey'; print STDERR "$ATTN user $user in config, but has no pubkey!\n"; } From 3267c3f4be80d707e49287c4181f14d17f0e047d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 2 Oct 2009 21:54:23 +0530 Subject: [PATCH 080/637] compile: change %groups from hash of lists to hash of hashes This makes it easier to test if a repo is a member of a group, which is required for the delegation feature coming up --- src/gl-compile-conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 9b13dd7..e257c69 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -104,7 +104,7 @@ sub expand_list { die "$ATTN undefined group $item\n" unless $groups{$item}; # add those names to the list - push @new_list, @{ $groups{$item} }; + push @new_list, sort keys %{ $groups{$item} }; } else { @@ -139,7 +139,7 @@ while (<$conf_fh>) # user or repo groups if (/^(@\S+) = (.*)/) { - push @{ $groups{$1} }, expand_list( split(' ', $2) ); + do { $groups{$1}{$_} = 1 } for ( expand_list( split(' ', $2) ) ); # again, we take the more "relaxed" pattern die "$ATTN bad group $1\n" unless $1 =~ $REPONAME_PATT; } From 34a6f89c265fce7c3ce35ac130d85e7861209157 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 2 Oct 2009 22:17:51 +0530 Subject: [PATCH 081/637] compile: make the parse a function instead of inline Again, prep for delegation, when we'll be reading fragments of config rules from various files and tacking them onto the %repos hash. note: this patch best viewed with "git diff -w", clicking "Ignore space change" in gitk, or eqvt :-) --- src/gl-compile-conf | 128 +++++++++++++++++++++++--------------------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index e257c69..281eafd 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -119,80 +119,86 @@ sub expand_list # "compile" GL conf # ---------------------------------------------------------------------------- -my $conf_fh = wrap_open( "<", $GL_CONF ); - -# the syntax is fairly simple, so we parse it inline - -my @repos; -while (<$conf_fh>) +sub parse_conf_file { - # normalise whitespace; keeps later regexes very simple - s/=/ = /; - s/\s+/ /g; - s/^ //; - s/ $//; - # kill comments - s/#.*//; - # and blank lines - next unless /\S/; + my ($conffile) = @_; + my $conf_fh = wrap_open( "<", $conffile ); - # user or repo groups - if (/^(@\S+) = (.*)/) + # the syntax is fairly simple, so we parse it inline + + my @repos; + while (<$conf_fh>) { - do { $groups{$1}{$_} = 1 } for ( expand_list( split(' ', $2) ) ); - # again, we take the more "relaxed" pattern - die "$ATTN bad group $1\n" unless $1 =~ $REPONAME_PATT; - } - # repo(s) - elsif (/^repo (.*)/) - { - # grab the list and expand any @stuff in it - @repos = split ' ', $1; - @repos = expand_list ( @repos ); - } - # actual permission line - elsif (/^(R|RW|RW\+) (.* )?= (.+)/) - { - my $perms = $1; - my @refs; @refs = split(' ', $2) if $2; - my @users = split ' ', $3; + # normalise whitespace; keeps later regexes very simple + s/=/ = /; + s/\s+/ /g; + s/^ //; + s/ $//; + # kill comments + s/#.*//; + # and blank lines + next unless /\S/; - # if no ref is given, this PERM applies to all refs - @refs = qw(refs/.*) unless @refs; - # fully qualify refs that dont start with "refs/"; prefix them with - # "refs/heads/" - @refs = map { m(^refs/) or s(^)(refs/heads/); $_ } @refs; - - # expand the user list, unless it is just "@all" - @users = expand_list ( @users ) - unless (@users == 1 and $users[0] eq '@all'); - do { die "$ATTN bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users; - - # ok, we can finally populate the %repos hash - for my $repo (@repos) # each repo in the current stanza + # user or repo groups + if (/^(@\S+) = (.*)/) { - for my $user (@users) + do { $groups{$1}{$_} = 1 } for ( expand_list( split(' ', $2) ) ); + # again, we take the more "relaxed" pattern + die "$ATTN bad group $1\n" unless $1 =~ $REPONAME_PATT; + } + # repo(s) + elsif (/^repo (.*)/) + { + # grab the list and expand any @stuff in it + @repos = split ' ', $1; + @repos = expand_list ( @repos ); + } + # actual permission line + elsif (/^(R|RW|RW\+) (.* )?= (.+)/) + { + my $perms = $1; + my @refs; @refs = split(' ', $2) if $2; + my @users = split ' ', $3; + + # if no ref is given, this PERM applies to all refs + @refs = qw(refs/.*) unless @refs; + # fully qualify refs that dont start with "refs/"; prefix them with + # "refs/heads/" + @refs = map { m(^refs/) or s(^)(refs/heads/); $_ } @refs; + + # expand the user list, unless it is just "@all" + @users = expand_list ( @users ) + unless (@users == 1 and $users[0] eq '@all'); + do { die "$ATTN bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users; + + # ok, we can finally populate the %repos hash + for my $repo (@repos) # each repo in the current stanza { - $user_list{$user}++; # only to catch lint, see later - - # for 1st level check (see faq/tips doc) - $repos{$repo}{R}{$user} = 1 if $perms =~ /R/; - $repos{$repo}{W}{$user} = 1 if $perms =~ /W/; - - # for 2nd level check, store each "ref, perms" pair in order - for my $ref (@refs) + for my $user (@users) { - push @{ $repos{$repo}{$user} }, { $ref => $perms }; + $user_list{$user}++; # only to catch lint, see later + + # for 1st level check (see faq/tips doc) + $repos{$repo}{R}{$user} = 1 if $perms =~ /R/; + $repos{$repo}{W}{$user} = 1 if $perms =~ /W/; + + # for 2nd level check, store each "ref, perms" pair in order + for my $ref (@refs) + { + push @{ $repos{$repo}{$user} }, { $ref => $perms }; + } } } } - } - else - { - die "$ATTN can't make head or tail of '$_'\n"; + else + { + die "$ATTN can't make head or tail of '$_'\n"; + } } } +parse_conf_file($GL_CONF); + my $compiled_fh = wrap_open( ">", $GL_CONF_COMPILED ); print $compiled_fh Data::Dumper->Dump([\%repos], [qw(*repos)]); close $compiled_fh or die "$ATTN close compiled-conf failed: $!\n"; From 5bb0850c5c628f279c8bd16fdd5583c89402093d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 2 Oct 2009 23:23:52 +0530 Subject: [PATCH 082/637] p-t-a: make the post-update hook a separate file... ...and just refer to it in the doc. This hook will acquire more code soon, when we do delegations :) --- doc/4-push-to-admin.mkd | 19 +++++++------------ src/pta-hook.sh | 6 ++++++ 2 files changed, 13 insertions(+), 12 deletions(-) create mode 100755 src/pta-hook.sh diff --git a/doc/4-push-to-admin.mkd b/doc/4-push-to-admin.mkd index 3fc83bf..4b208a9 100644 --- a/doc/4-push-to-admin.mkd +++ b/doc/4-push-to-admin.mkd @@ -70,20 +70,15 @@ repos they play havoc with my git commands, so this is how I do it) GIT_WORK_TREE=/home/git/.gitolite git add conf/gitolite.conf keydir GIT_WORK_TREE=/home/git/.gitolite git commit -am start -Now we have to setup the post-update hook for push-to-admin to work. The -hook should (1) make a forced checkout in the "live" config directory (which -is `~/.gitolite`), and (2) run the compile script. So we create a hook with -the appropriate code in it, and then make it executable +Now we have to setup the post-update hook for push-to-admin to work. The hook +should (1) make a forced checkout in the "live" config directory (which is +`~/.gitolite`), and (2) run the compile script. - cat < hooks/post-update - #!/bin/sh - - GIT_WORK_TREE=/home/git/.gitolite git checkout -f - - cd /home/git/.gitolite - src/gl-compile-conf - EOFPU +`src/pta-hook.sh` has the code you need; just copy it to the right place with +the right name and make sure it is executable: + # (assuming pwd is still ~/repositories/gitolite-admin.git) + cp ~/.gitolite/src/pta-hook.sh hooks/post-update chmod +x hooks/post-update ---- diff --git a/src/pta-hook.sh b/src/pta-hook.sh new file mode 100755 index 0000000..283cc9f --- /dev/null +++ b/src/pta-hook.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +GIT_WORK_TREE=/home/git/.gitolite git checkout -f + +cd /home/git/.gitolite +src/gl-compile-conf From 2f2af033f53954e919ca0b9fae94e521fbaf47ba Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 3 Oct 2009 13:17:02 +0530 Subject: [PATCH 083/637] pta-hook.sh: collect delegated config fragments collect the delegated config fragments from correspondingly named branches of the gitolite-admin repo, and put them all in conf/fragments/ also deprecate changes to conf and keydir locations from now on --- conf/example.gitolite.rc | 8 ++++++-- src/pta-hook.sh | 27 +++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index 3725110..ea80bec 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -57,8 +57,12 @@ $GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y-%m.log"; # -------------------------------------- -# I see even less reason to change these, since they're all relative to the -# gitolite admin directory above, but hey it's *your* system... +# Please DO NOT change the following paths unless you really know what you're +# doing. It'll work for now but it's officially deprecated to have them +# elsewhere from now on, and may break some future features. + +# Anyway, the conf files and keydirs don't grow constantly, (like the logs and +# the repositories do), so I don't think this is a major problem for anyone. $GL_CONF="$GL_ADMINDIR/conf/gitolite.conf"; $GL_KEYDIR="$GL_ADMINDIR/keydir"; diff --git a/src/pta-hook.sh b/src/pta-hook.sh index 283cc9f..cbc0d2f 100755 --- a/src/pta-hook.sh +++ b/src/pta-hook.sh @@ -1,6 +1,29 @@ #!/bin/sh -GIT_WORK_TREE=/home/git/.gitolite git checkout -f +# get this from your .gitolite.conf; and don't forget this is shell, while +# that is perl :-) +export GL_ADMINDIR=/home/git/.gitolite -cd /home/git/.gitolite +# checkout the master branch to $GL_ADMINDIR +GIT_WORK_TREE=$GL_ADMINDIR git checkout -f master + +# collect all the delegated fragments +mkdir -p $GL_ADMINDIR/conf/fragments +for br in $(git for-each-ref --format='%(refname:short)') +do + # skip master (duh!) + [[ $br == master ]] && continue + # all other branches *should* contain a file called .conf + # inside conf/fragments; if so copy it + if git show $br:conf/fragments/$br.conf > /dev/null 2>&1 + then + git show $br:conf/fragments/$br.conf > $GL_ADMINDIR/conf/fragments/$br.conf + echo "(extracted $br conf; `wc -l < $GL_ADMINDIR/conf/fragments/$br.conf` lines)" + else + echo " ***** ERROR *****" + echo " branch $br does not contain conf/fragments/$br.conf" + fi +done + +cd $GL_ADMINDIR src/gl-compile-conf From fa5567f22cba52ff8d98a0010c508f18bf073c4f Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 4 Oct 2009 09:25:20 +0530 Subject: [PATCH 084/637] doc/5-delegation added, doc/4 (PTA) enhanced This is complete user documentation for delegation --- doc/3-faq-tips-etc.mkd | 8 +++ doc/4-push-to-admin.mkd | 77 +++++++++++++---------- doc/5-delegation.mkd | 135 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 34 deletions(-) create mode 100644 doc/5-delegation.mkd diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index b28c548..66ab956 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -9,6 +9,7 @@ In this document: * simpler syntax * two levels of access rights checking * error checking the config file + * delegating parts of the config file * easier to specify gitweb/daemon access * built-in logging * one user, many keys @@ -179,6 +180,13 @@ In gitolite, you have to "compile" the config file first (this step takes the place of the commit+push in gitosis), and keyword typos *are* caught so you know right away. +#### delegating parts of the config file + +You can now split up the config file and delegate the authority to specify +access control for their own pieces. See +[doc/5-delegation.mkd](http://github.com/sitaramc/gitolite/blob/pu/doc/5-delegation.mkd) +for details. + #### easier to specify gitweb/daemon access Specifying gitweb and/or daemon access for a repo is simple: give "read" diff --git a/doc/4-push-to-admin.mkd b/doc/4-push-to-admin.mkd index 4b208a9..48d1baa 100644 --- a/doc/4-push-to-admin.mkd +++ b/doc/4-push-to-admin.mkd @@ -41,51 +41,60 @@ make the same changes below. ---- -First, on the server, log on to the `git` userid, add a new repo called -`gitolite-admin` to the config file, give yourself `RW` or `RW+` rights to it, -and "compile": +#### server side setup - cd ~/.gitolite - vim conf/gitolite.conf # add gitolite-admin repo, etc - src/gl-compile-conf +1. First, on the server, log on to the `git` userid, add a new repo called + `gitolite-admin` to the config file, give yourself `RW` or `RW+` rights to it, + and "compile": -Now, if you look at the "compile" script, it has an *automatic* local commit -inside, just for safety, which kicks in every time you compile. This only -works if it finds a ".git" directory, and it was designed as an "automatic -backup/safety net" type of thing, in case I accidentally deleted the whole -config file or something. + cd ~/.gitolite + vim conf/gitolite.conf # add gitolite-admin repo, etc + src/gl-compile-conf -We need to disable this, because now we have a *better* repo, one that is -manually pushed, and presumably has proper commit messages! +2. Now, if you look at the "compile" script, it has an *automatic* local commit + inside, just for safety, which kicks in every time you compile. This only + works if it finds a ".git" directory, and it was designed as an "automatic + backup/safety net" type of thing, in case I accidentally deleted the whole + config file or something. - mv .git .disable.git # yeah it's a hack, sue me + We need to disable this, because now we have a *better* repo, one that is + manually pushed, and presumably has proper commit messages! -Now the compile command created an empty, bare, "gitolite-admin" repo, so we -seed it with the current contents of the config and keys. (A note on the -`GIT_WORK_TREE` variable: I avoid setting these variables in the normal way -because I always forget to unset them later, and then when I `cd` to other -repos they play havoc with my git commands, so this is how I do it) + mv .git .disable.git # yeah it's a hack, sue me - cd ~/repositories/gitolite-admin.git - GIT_WORK_TREE=/home/git/.gitolite git add conf/gitolite.conf keydir - GIT_WORK_TREE=/home/git/.gitolite git commit -am start +3. Now the compile command created an empty, bare, "gitolite-admin" repo, so we + seed it with the current contents of the config and keys. (A note on the + `GIT_WORK_TREE` variable: I avoid setting these variables in the normal way + because I always forget to unset them later, and then when I `cd` to other + repos they play havoc with my git commands, so this is how I do it) -Now we have to setup the post-update hook for push-to-admin to work. The hook -should (1) make a forced checkout in the "live" config directory (which is -`~/.gitolite`), and (2) run the compile script. + cd ~/repositories/gitolite-admin.git + GIT_WORK_TREE=/home/git/.gitolite git add conf/gitolite.conf keydir + GIT_WORK_TREE=/home/git/.gitolite git commit -am start -`src/pta-hook.sh` has the code you need; just copy it to the right place with -the right name and make sure it is executable: +4. Now we have to setup the post-update hook for push-to-admin to work. The hook + should (1) make a forced checkout in the "live" config directory (which is + `~/.gitolite`), and (2) run the compile script. - # (assuming pwd is still ~/repositories/gitolite-admin.git) - cp ~/.gitolite/src/pta-hook.sh hooks/post-update - chmod +x hooks/post-update + `src/pta-hook.sh` has the code you need; just copy it to the right place with + the right name, change the first line if needed, and make it executable: + + # (assuming pwd is still ~/repositories/gitolite-admin.git) + cp ~/.gitolite/src/pta-hook.sh hooks/post-update + + # if you changed $GL_ADMINDIR in ~/.gitolite.conf, then edit the hooks + # and change the first line: + vim hooks/post-update + + chmod +x hooks/post-update ---- -Now get to your workstation, and +#### client side setup - git clone git@server:gitolite-admin.git +1. Now get to your workstation, and -That's it, we're done. You're in gitosis land as far as this is concerned -now. So knock yourself out. Or lock yourself out... :-) + git clone git@server:gitolite-admin.git + + That's it, we're done. You're in gitosis land as far as this is concerned + now. So knock yourself out. Or lock yourself out... :-) diff --git a/doc/5-delegation.mkd b/doc/5-delegation.mkd new file mode 100644 index 0000000..7592e83 --- /dev/null +++ b/doc/5-delegation.mkd @@ -0,0 +1,135 @@ +# delegating access control responsibilities + +[Thanks to jeromeag for forcing me to think through this...] + +### lots of repos, lots of users + +Gitolite tries to make it easy to manage access to lots of users and repos, +exploiting commonalities wherever possible. (The example under "simpler, more +powerful syntax" [here][ml] should give you an idea). As you can see, it lets +you specify bits and pieces of the access control separately -- i.e., *all* +the access specs for a certain repo need not be together; they can be +scattered, which makes it easier to manage the sort of slice and dice needed +in that example. + +[ml]: http://github.com/sitaramc/gitolite/blob/ml/update.mkd + +But eventually the config file will become too big. If you let only one +person have control, he could become a bottleneck. If you give it to multiple +people, they might make mistakes or stomp on each others' work accidentally. + +The best way is to divide up the config file and give parts of it to different +people. Ideally, we would delegate authority for *groups* of repos, not +individual repos, otherwise it doesn't scale. + +It would also be nice if we could specify what repos can be delegated to a +particular admin, and prevent him/her from specifying access control for any +other repos. This would be a nice "security" feature. + +### splitting up the config file into fragments + +To start with, recall that gitolite allows you to specify **groups** (of users +or repos, same syntax). So the basic idea is that the main config file +(`~/.gitolite/conf/gitolite.conf` by default) will specify some repo groups: + + # group your projects/repos however you want + @webbrowser_repos = firefox lynx + @webserver_repos = apache nginx + @malware_repos = conficker storm + + # any other config as usual, including access control lines for any of the + # above projects or groups + +Now just create these files (assuming default `$GL_ADMINDIR` location): + + ~/.gitolite/conf/fragments/webbrowser_repos.conf + ~/.gitolite/conf/fragments/webserver_repos.conf + ~/.gitolite/conf/fragments/malware_repos.conf + +Within each of those files put in whatever access control rules you want for +the repos that are members of that group. Notice that the basenames of the +files must be exactly the same as the name of the corresponding repo group in +the main config file. + +For instance, `~/.gitolite/conf/fragments/webbrowser_repos.conf` would only +contain access control for firefox and lynx. If it referenced any other repo +(say "storm") those lines would be ignored (and a warning message generated). + +When you run the compile script (`src/gl-compile-conf`), the **net effect is +as if you appended the contents of all the "fragment" files, in alphabetical +order, to the bottom of the main file**. + +(Except of course, while processing a fragment, it will ignore attempts to set +permissions for repos that are not members of the same-named "repo group"). + +And that's basically it, in the simplest usage. + +["But WAIT, there's MORE!"][bwtm] + +[bwtm]: http://en.wikipedia.org/wiki/Ed_Valenti#But_Wait.21_There.27s_More + +### delegating ownership of fragments + +Splitting up the file does help, but there's also that little security issue +-- anyone can make any change to any "fragment", unless you (once again) go +back to Unix permissions to keep them separate. + +Fixing that requires using "push-to-admin". + +The page on [push-to-admin][ptd] explains clearly how to set it up. Unlike +gitosis, I refuse to make it the default because it's a support nightmare. +Don't get me wrong -- it's a great feature, and I use it myself, but the +learning curve is too steep to make it *required*. + +[ptd]: http://github.com/sitaramc/gitolite/blob/pu/doc/4-push-to-admin.mkd + +So, having setup push-to-admin, you add these lines to the main config file, +assuming Alice is in charge of all web browser development projects, Bob takes +care of web servers, and Mallory, as [tradition][abe] dictates, is in charge +of malware ;-) + +[abe]: http://en.wikipedia.org/wiki/Alice_and_Bob#List_of_characters + + # you probably added these two lines while setting up push-to-admin + repo gitolite-admin + RW+ = sitaram + # now add these 3 lines + RW webbrowser_repos = alice + RW webserver_repos = bob + RW malware_repos = mallory + +As you can see, for each repo group you want to delegate authority over, +there's a **branch** in the `gitolite-admin` repo with the same name. Whoever +has write access to that branch, is allowed to define rules for repos in the +corresponding "repo group". + +In other words, **we use gitolite's per-branch permissions to "enforce" the +separation between the delegated configs!** + +Here's how to use this in practice: + + * Alice clones the `gitolite-admin` repo, creates (if not already created) and + checks out a new branch called `webbrowser_repos`, and adds a file called + `conf/fragments/webbrowser_repos.conf` in that branch + + * (the rest of the contents of that branch do not matter; she can keep + all the other files or delete all of them -- it doesn't make any + difference. Only that one specific file is used). + + * she writes in this file any access control rules for the "firefox" and + "lynx" repos. She should not write access rules for any other project -- + they will be ignored + + * Alice then commits and pushes this branch to the `gitolite-admin` repo + +Naturally, a successful push invokes the post-update hook that you installed +(while setting up [push-to-admin][ptd]). Here's what it does: + + * for each branch, say `br`, of the `gitolite-admin` repo, it checks if + there is a file called `conf/fragments/br.conf` + + * if there is, it extracts it and copies it with the exact same name and + path, into the `$GL_ADMINDIR` directory (`~/.gitolite` by default) + +After that, it runs the compile script, and things work the same as described +in the previous section. From 616d8a5f7d2c8c08c7cc1f52dc89bd5846695d19 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 4 Oct 2009 09:56:40 +0530 Subject: [PATCH 085/637] compile: (large changes) parse delegated fragments if any [Note: this is a fairly involved commit, compared to most of the others. See doc/5-delegation.mkd for a user-level feature description.] parse delegated config fragments (found as conf/fragments/*.conf). Any repos being referenced within a fragment config *must* belong to the "@group" with the same name as the fragment. That is, a fragment called conf/fragments/abc.conf can only refer to repos that are members of the "@abc" repo group. It cannot specify access control for any other repos. If it does, those settings are ignored, and a warning message is produced. since the delegated config must have the flexibility of (re-)defining group names for internal convenience, and since all such definitions go into the same "groups" hash, it is quite easy for conf/fragments/abc.conf to write in its own (re-)definition of "@abc"! That would be a neat little security hole :) The way to close it is to consider only members of the "@abc" groupset defined in the main ("master") config file for this purpose. --- src/gl-compile-conf | 47 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 281eafd..8f97bf0 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -121,7 +121,14 @@ sub expand_list sub parse_conf_file { - my ($conffile) = @_; + my ($conffile, $fragment) = @_; + # the second arg, $fragment, is passed in as "master" when parsing the + # main config, and the fragment name when parsing a fragment. In the + # latter case, the parser uses that information to ignore (and warn about) + # any repos in the fragment that are not members of the "repo group" of + # the same name. + my %ignored = (); + my $conf_fh = wrap_open( "<", $conffile ); # the syntax is fairly simple, so we parse it inline @@ -142,7 +149,9 @@ sub parse_conf_file # user or repo groups if (/^(@\S+) = (.*)/) { - do { $groups{$1}{$_} = 1 } for ( expand_list( split(' ', $2) ) ); + # store the members of each group as hash key. Keep track of when + # the group was *first* created by using $fragment as the *value* + do { $groups{$1}{$_} ||= $fragment } for ( expand_list( split(' ', $2) ) ); # again, we take the more "relaxed" pattern die "$ATTN bad group $1\n" unless $1 =~ $REPONAME_PATT; } @@ -174,6 +183,22 @@ sub parse_conf_file # ok, we can finally populate the %repos hash for my $repo (@repos) # each repo in the current stanza { + # if we're processing a delegated config file (not the master + # config), and if that fragment name is not the same as the + # current repo + if ($fragment ne 'master' and $fragment ne $repo) + { + # then the fragment must be a group name and the repo + # being processed must be a member of that "@group". + # Also, the value of the hash for that combination must be + # "master", signifying a group created in the master + # config file and not in one of the delegates + unless ( ($groups{"\@$fragment"}{$repo} || '') eq 'master') + { + $ignored{$fragment}{$repo} = 1; + next; + } + } for my $user (@users) { $user_list{$user}++; # only to catch lint, see later @@ -195,9 +220,25 @@ sub parse_conf_file die "$ATTN can't make head or tail of '$_'\n"; } } + for my $ig (sort keys %ignored) + { + warn "\n\t\t***** WARNING *****\n" . + "\t$ig.conf attempting to set access for " . + join (", ", sort keys %{ $ignored{$ig} }) . "\n"; + } } -parse_conf_file($GL_CONF); +# parse the main config file +parse_conf_file($GL_CONF, 'master'); + +# parse any delegated fragments +wrap_chdir($GL_ADMINDIR); +for my $fragment_file (glob("conf/fragments/*.conf")) +{ + my $fragment = $fragment_file; + $fragment =~ s/^conf\/fragments\/(.*).conf$/$1/; + parse_conf_file($fragment_file, $fragment); +} my $compiled_fh = wrap_open( ">", $GL_CONF_COMPILED ); print $compiled_fh Data::Dumper->Dump([\%repos], [qw(*repos)]); From 3c960aa5e1a6a692b811b4c76b5c414b810dcab3 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 4 Oct 2009 15:51:32 +0530 Subject: [PATCH 086/637] pta hook: avoid spurious error messages on old fragments --- src/pta-hook.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pta-hook.sh b/src/pta-hook.sh index cbc0d2f..23a2f23 100755 --- a/src/pta-hook.sh +++ b/src/pta-hook.sh @@ -7,8 +7,12 @@ export GL_ADMINDIR=/home/git/.gitolite # checkout the master branch to $GL_ADMINDIR GIT_WORK_TREE=$GL_ADMINDIR git checkout -f master +# remove all fragments. otherwise, you get spurious error messages when you +# take away someone's delegation in the main config but the fragment is still +# hanging around. The ones that are valid will get re-created anyway +rm -rf $GL_ADMINDIR/conf/fragments # collect all the delegated fragments -mkdir -p $GL_ADMINDIR/conf/fragments +mkdir $GL_ADMINDIR/conf/fragments for br in $(git for-each-ref --format='%(refname:short)') do # skip master (duh!) From 8096cc8e9c9ecdfb511c4e1c859def54878f44ee Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 5 Oct 2009 16:08:10 +0530 Subject: [PATCH 087/637] install.pl, pta hook, upgrade doc: - install the post-update hook also - fix bashism in pta-hook Also, since delegation works best with PTA, reflect that in the upgrade doc --- doc/0-UPGRADE.mkd | 3 +++ src/install.pl | 6 ++++++ src/pta-hook.sh | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/0-UPGRADE.mkd b/doc/0-UPGRADE.mkd index 8c80bb7..c106bc7 100644 --- a/doc/0-UPGRADE.mkd +++ b/doc/0-UPGRADE.mkd @@ -35,6 +35,9 @@ have to do it at a "quiet" time or something. src/gl-compile-conf + (if you've already setup "push-to-admin", this step should be replaced by + a "git push". Make a dummy commit if needed, to make the push happen). + And you're done. ### upgrade notes for specific versions diff --git a/src/install.pl b/src/install.pl index 85b8607..017854f 100755 --- a/src/install.pl +++ b/src/install.pl @@ -64,3 +64,9 @@ for my $repo (`find . -type d -name "*.git"`) { system("cp $GL_ADMINDIR/src/update-hook.pl $repo/hooks/update"); chmod 0755, "$repo/hooks/update"; } + +# oh and one of those repos is a bit more special and has an extra hook :) +system("cp $GL_ADMINDIR/src/pta-hook.sh gitolite-admin.git/hooks/post-update"); +system("perl", "-i", "-p", "-e", "s(export GL_ADMINDIR=.*)(export GL_ADMINDIR=$GL_ADMINDIR)", + "gitolite-admin.git/hooks/post-update"); +chmod 0755, "gitolite-admin.git/hooks/post-update"; diff --git a/src/pta-hook.sh b/src/pta-hook.sh index 23a2f23..5009313 100755 --- a/src/pta-hook.sh +++ b/src/pta-hook.sh @@ -16,7 +16,8 @@ mkdir $GL_ADMINDIR/conf/fragments for br in $(git for-each-ref --format='%(refname:short)') do # skip master (duh!) - [[ $br == master ]] && continue + [ "$br" = "master" ] && continue + # all other branches *should* contain a file called .conf # inside conf/fragments; if so copy it if git show $br:conf/fragments/$br.conf > /dev/null 2>&1 From ec2ad64b384aaeac6a57e8a06fa686eac1a1b5d5 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 5 Oct 2009 17:52:33 +0530 Subject: [PATCH 088/637] doc/delegation: never ending quest to write well :) --- doc/5-delegation.mkd | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/doc/5-delegation.mkd b/doc/5-delegation.mkd index 7592e83..9924dc3 100644 --- a/doc/5-delegation.mkd +++ b/doc/5-delegation.mkd @@ -19,15 +19,22 @@ person have control, he could become a bottleneck. If you give it to multiple people, they might make mistakes or stomp on each others' work accidentally. The best way is to divide up the config file and give parts of it to different -people. Ideally, we would delegate authority for *groups* of repos, not -individual repos, otherwise it doesn't scale. +people. -It would also be nice if we could specify what repos can be delegated to a -particular admin, and prevent him/her from specifying access control for any -other repos. This would be a nice "security" feature. +Ideally, we would delegate authority for *groups* of repos, not individual +repos, otherwise it doesn't scale. It would also be nice if we could prevent +an admin from creating access rules for *any* repo in the system -- i.e., set +limits on what repos he can control. This would be a nice "security" feature. + +Delegation offers a way to do all that. Note that delegated admins cannot +create or remove users, not can they define new repos. They can only define +access control rules for a set of repos they have been given authority for. ### splitting up the config file into fragments +It's easier to show how it all works with an example instead of long +descriptions. + To start with, recall that gitolite allows you to specify **groups** (of users or repos, same syntax). So the basic idea is that the main config file (`~/.gitolite/conf/gitolite.conf` by default) will specify some repo groups: @@ -98,13 +105,19 @@ of malware ;-) RW webserver_repos = bob RW malware_repos = mallory -As you can see, for each repo group you want to delegate authority over, -there's a **branch** in the `gitolite-admin` repo with the same name. Whoever -has write access to that branch, is allowed to define rules for repos in the -corresponding "repo group". + # you need these lines too -- they define what repos alice/bob/mallory are + # allowed to control + @webbrowser_repos = firefox lynx + @webserver_repos = apache nginx + @malware_repos = conficker storm -In other words, **we use gitolite's per-branch permissions to "enforce" the -separation between the delegated configs!** +**As you can see, for each repo group you want to delegate authority over, +there's a *branch* in the `gitolite-admin` repo with the same name. If you +have write access to that branch, you are allowed to define rules for repos in +that repo group.** + +In other words, we use gitolite's per-branch permissions to "enforce" the +separation between the delegated configs! Here's how to use this in practice: From f883fe7d717bb8184501aa3c310cbf8e81ba0ee1 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 5 Oct 2009 20:21:33 +0530 Subject: [PATCH 089/637] compile: comments+efficiency - add better comments on the 2 main hashes - work around an inefficiency caused by the exclude prep code needing a list instead of a hash at a certain place --- src/gl-compile-conf | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 8f97bf0..ad50c4a 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -68,10 +68,30 @@ my $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pt my $REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern my $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern -# groups can now represent user groups or repo groups +# groups can now represent user groups or repo groups. + +# $groups{group}{member} = "master" (or name of fragment file in which the +# group is defined). my %groups = (); + +# %repos has two functions. + +# $repos{repo}{R|W}{user} = 1 if user has R (or W) permissions for at least +# one branch in repo. This is used by the "level 1 check" (see faq) + +# $repos{repo}{user} is a list of {ref, perms} pairs. This is used by the +# level 2 check. In order to allow "exclude" rules, the order of rules now +# matters, so what used to be entirely "hash of hash of hash" now has a list +# in between :) my %repos = (); -my %user_list = (); # only to catch lint; search for "lint" below + +# ... having been forced to use a list as described above, we lose some +# efficiency due to the possibility of the same {ref, perms} pair showing up +# multiple times for the same repo+user. So... +my %rurp_seen = (); + +# catch usernames<->pubkeys mismatches; search for "lint" below +my %user_list = (); # set the umask before creating any files umask($REPO_UMASK); @@ -210,7 +230,8 @@ sub parse_conf_file # for 2nd level check, store each "ref, perms" pair in order for my $ref (@refs) { - push @{ $repos{$repo}{$user} }, { $ref => $perms }; + push @{ $repos{$repo}{$user} }, { $ref => $perms } + unless $rurp_seen{$repo}{$user}{$ref}{$perms}++; } } } From 410c9ba46c5fa00e88579c9f9494c12ea7a67d0f Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 7 Oct 2009 12:33:49 +0530 Subject: [PATCH 090/637] doc/install: add missing "cd" --- doc/0-INSTALL.mkd | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index 9d92ce9..c54c7eb 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -20,6 +20,7 @@ tar file of the branch you want. Please use the make command, not a plain "git archive". The comments in the `Makefile` will explain why. git clone git://github.com/sitaramc/gitolite.git + cd gitolite make master.tar # or maybe "make rebel.tar" or "make pu.tar" From 9d2c9662a21044576d7ef4361d8bc22bc85deea9 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 10 Oct 2009 12:52:40 +0530 Subject: [PATCH 091/637] install: can't assume p-t-a is setup! make installing the p-u hook conditional to avoid ugly error --- src/install.pl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/install.pl b/src/install.pl index 017854f..3e1f655 100755 --- a/src/install.pl +++ b/src/install.pl @@ -66,7 +66,10 @@ for my $repo (`find . -type d -name "*.git"`) { } # oh and one of those repos is a bit more special and has an extra hook :) -system("cp $GL_ADMINDIR/src/pta-hook.sh gitolite-admin.git/hooks/post-update"); -system("perl", "-i", "-p", "-e", "s(export GL_ADMINDIR=.*)(export GL_ADMINDIR=$GL_ADMINDIR)", - "gitolite-admin.git/hooks/post-update"); -chmod 0755, "gitolite-admin.git/hooks/post-update"; +if ( -d "gitolite-admin.git/hooks" ) { + print STDERR "copying post-update hook to gitolite-admin repo...\n"; + system("cp -v $GL_ADMINDIR/src/pta-hook.sh gitolite-admin.git/hooks/post-update"); + system("perl", "-i", "-p", "-e", "s(export GL_ADMINDIR=.*)(export GL_ADMINDIR=$GL_ADMINDIR)", + "gitolite-admin.git/hooks/post-update"); + chmod 0755, "gitolite-admin.git/hooks/post-update"; +} From ccd8372bb30641cb63efbd5d2db495af92bcf266 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 10 Oct 2009 12:38:22 +0530 Subject: [PATCH 092/637] aa ha! easy install script! src/00-easy-install.sh does *everything* needed, and it's mostly self-documented --- README.mkd | 4 + doc/0-INSTALL.mkd | 52 ++++++++- src/00-easy-install.sh | 245 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 298 insertions(+), 3 deletions(-) create mode 100755 src/00-easy-install.sh diff --git a/README.mkd b/README.mkd index 5a59bd1..fe85675 100644 --- a/README.mkd +++ b/README.mkd @@ -3,6 +3,10 @@ > [IMPORTANT: There is now an "upgrade" document in the "doc" directory; > please read if upgrading gitolite] +> [Update 2009-10-10: apart from all the nifty new features, there's now an +> "easy install" script in the src directory. Please see the INSTALL +> document in the doc directory for details] + ---- Gitolite is the bare essentials of gitosis, with a completely different diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index c54c7eb..d4f3b44 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -1,6 +1,52 @@ # installing gitolite -### pre-requisites +### easy install + +There is now an easy install script that makes installing very easy for the +common case. **This script is meant to be run on your workstation, not on the +server!** It will take care of all the server side work, *and* get you +"push-to-admin" too :-) In short, it does **everything**! + +Assumptions: + + * you have a server to host gitolite + * git is installed on that server (and so is perl) + * you have a userid on that server + * you have ssh-pubkey (password-less) login to that userid + * (if you have only password access, run `ssh-keygen -t rsa` to create a + new keypair if needed, then run `ssh-copy-id user@host`) + * you have a clone or an archive of gitolite somewhere on your workstation + +If so, just `cd` to that clone and run `src/00-easy-install.sh` and follow the +prompts! (Running it without any arguments shows you usage plus other useful +info). + +#### advantages over the older install methods + + * all ssh problems reduced to **just one pre-requisite**: enable ssh pubkey + (password-less) access to the server from your workstation first + * the script takes care of all the server side work + * when done: + * you get two different pubkeys (the original one for command line + access as before, plus a new one, created by the script, for gitolite + access) + * you can admin gitolite by commit+push a "gitolite-admin" repo, just + like gitosis (i.e., full "push to admin" power!) + +#### disadvantages + + * has been tested only with Linux. However, the script now makes a much + better "document" on what actually needs to be done, so people installing + on non-Linux machines can probably follow the steps in the script and + install if they wish. Sort of "simulate" it... :) + +### manual install + +If for some reason you cannot use the easy-install method, (for example, +you're on a non-Linux machine) read on. Unlike the easy install, all the +below stuff is meant to be run on the server. + +#### pre-requisites on the server If you managed to install git, you might already have what gitolite needs: @@ -13,7 +59,7 @@ A major objective is to allow use by people without root access, permissions to create other userids, etc. Even if you have root, please add a user just for gitolite and do all this from that user. -### getting a tar file from a clone +#### getting a tar file from a clone You can clone the repo from github, then execute a make command to extract a tar file of the branch you want. Please use the make command, not a plain @@ -24,7 +70,7 @@ tar file of the branch you want. Please use the make command, not a plain make master.tar # or maybe "make rebel.tar" or "make pu.tar" -### quick install from tar file +#### install from tar file * make a temp directory somewhere, cd to it, and unpack the tar file * run `src/install.pl` and follow the prompts diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh new file mode 100755 index 0000000..54cb5a0 --- /dev/null +++ b/src/00-easy-install.sh @@ -0,0 +1,245 @@ +#!/bin/bash + +# easy install for gitolite + +# this is the client side piece. This gets run *before* the server side piece + +# run without any arguments for "usage" info + +# important setting: bail on any errors (else we have to check every single +# command!) +set -e + +die() { echo "$@"; echo "run $0 again without any arguments for help and tips"; exit 1; } +prompt() { + echo + echo + echo ------------------------------------------------------------------------ + echo "$1" + echo + read -p '...press enter to continue or Ctrl-C to bail out' +} +usage() { + cat </dev/null || + die "cant find at least some files in gitolite sources/config; aborting" + +# do we have pubkey auth on the server +ssh -o PasswordAuthentication=no $user@$host pwd >/dev/null || + die "pubkey access didn't work; please set it up using 'ssh-copy-id' or something" + +# can the "gitolite-admin" repo be safely created in $HOME +[[ -d $HOME/gitolite-admin ]] && + die "please delete or move aside the \$HOME/gitolite-admin directory" + +# cool; now let's create a new key for you as a "gitolite user" (as opposed to +# a gitolite admin who needs to login to the server and get a command line) + +[[ -f $HOME/.ssh/$admin_name.pub ]] && die "pubkey $HOME/.ssh/$admin_name.pub exists; can't proceed" +prompt "the next command will create a new keypair for your gitolite access + + The pubkey will be $HOME/.ssh/$admin_name.pub. You will have to + choose a passphrase or hit enter for none. I recommend not having a + passphrase for now, and adding one with 'ssh-keygen -p' *as soon as* + all the setup is done and you've successfully cloned and pushed the + gitolite-admin repo. + + After that, I suggest you (1) install 'keychain' or something + similar, and (2) add the following command to your bashrc (since + this is a non-default key) + + ssh-add \$HOME/.ssh/$admin_name + + This makes using passphrases very convenient." + +ssh-keygen -t rsa -f $HOME/.ssh/$admin_name || die "ssh-keygen failed for some reason..." + +if [[ -n $SSH_AGENT_PID ]] +then + prompt "you're running ssh-agent. We'll try and do an ssh-add of the + private key we just created, otherwise this key won't get picked up. If + you specified a passphrase in the previous step, you'll get asked for one + now -- type in the same one." + + ssh-add $HOME/.ssh/$admin_name +fi + +# ok the gitolite key is done; create a stanza for it in ~/.ssh/config +echo " +host gitolite + hostname $host + user $user + identityfile ~/.ssh/$admin_name" > $HOME/.ssh/.gl-stanza + +if grep 'host *gitolite' $HOME/.ssh/config &>/dev/null +then + prompt "your \$HOME/.ssh/config already has settings for gitolite. I will + assume they're correct, but if they're not, please edit that file, delete + that paragraph (that line and the following few lines), and rerun. + + In case you want to check right now (from another terminal) if they're + correct, here's what they are *supposed* to look like: +$(cat ~/.ssh/.gl-stanza)" + +else + prompt "creating settings for your gitolite access in $HOME/.ssh/config; + these are the lines that will be appended to your ~/.ssh/config: +$(cat ~/.ssh/.gl-stanza)" + + cat $HOME/.ssh/.gl-stanza >> $HOME/.ssh/config + # if the file didn't exist at all, it might have the wrong permissions + chmod 644 $HOME/.ssh/config +fi +rm $HOME/.ssh/.gl-stanza + +# ---------------------------------------------------------------------- +# client side stuff almost done; server side now +# ---------------------------------------------------------------------- + +# setup the gitolite sources and conf on the server +ssh $user@$host mkdir -p gitolite-install +rsync -a src conf doc $user@$host:gitolite-install/ + +# give the user an opportunity to change the rc +cp conf/example.gitolite.rc .gitolite.rc + # hey here it means "release candidate" ;-) + +prompt "the gitolite rc file needs to be edited by hand. The defaults +are sensible, so if you wish, you can just exit the editor. + +Otherwise, make any changes you wish and save it. Read the comments to +understand what is what -- the rc file's documentation is inline. + +Please remember this file will actually be copied to the server, and +that all the paths etc. represent paths on the server!" + +${VISUAL:-${EDITOR:-vi}} .gitolite.rc + +# copy the rc across +scp .gitolite.rc $user@$host: + +prompt "ignore any 'please edit this file' or 'run this command' type +lines in the next set of command outputs coming up. They're only +relevant for a manual install, not this one..." + +# extract the GL_ADMINDIR and REPO_BASE locations +GL_ADMINDIR=$(ssh $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'") +REPO_BASE=$( ssh $user@$host "perl -e 'do \".gitolite.rc\"; print \$REPO_BASE'") + +# run the install script on the server +ssh $user@$host "cd gitolite-install; src/install.pl" + +# setup the initial config file +echo "#gitolite conf +#please see conf/example.conf for details on syntax and features + +repo gitolite-admin + RW+ = $admin_name + +repo testing + RW+ = @all + +" > gitolite.conf + +# send the config and the key to the remote +scp gitolite.conf $user@$host:$GL_ADMINDIR/conf/ + +scp $HOME/.ssh/$admin_name.pub $user@$host:$GL_ADMINDIR/keydir + +# run the compile script on the server +ssh $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf" + +# ---------------------------------------------------------------------- +# hey lets go the whole hog on this; setup push-to-admin! +# ---------------------------------------------------------------------- + +# setup the initial commit for the admin repo +echo "cd $REPO_BASE/gitolite-admin.git +GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir +GIT_WORK_TREE=$GL_ADMINDIR git commit -am start +" | ssh $user@$host + +ssh $user@$host "cd gitolite-install; src/install.pl" + +prompt "now we will clone the gitolite-admin repo to your workstation +and see if it all hangs together. We'll do this in your \$HOME for now, +and you can move it elsewhere later if you wish to." + +cd $HOME +git clone gitolite:gitolite-admin.git + +echo +echo +echo ------------------------------------------------------------------------ +echo "Cool -- we're done. Now you can edit the config file (currently +in ~/gitolite-admin/conf/gitolite.conf) to add more repos, users, etc. +When done, 'git add' the changed files, 'git commit' and 'git push'. + +Read the comments in conf/example.conf for information about the config +file format -- like the rc file, this also has inline documentation. + +Your URL for cloning any repo on this server will be + + gitolite:reponame.git + +However, any other users you set up will have to use + + $user@$host:reponame.git + +unless they also create similar settings in their '.ssh/config' file." From d0d9cbe3afebc490f230fb6fa9ea0420eb1fcd1a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Oct 2009 05:51:19 +0530 Subject: [PATCH 093/637] easy install comment about clientside/serverside was wrong --- src/00-easy-install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index 54cb5a0..d19c1a5 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -2,7 +2,8 @@ # easy install for gitolite -# this is the client side piece. This gets run *before* the server side piece +# this runs on the client side, and itself takes care of all the server side +# work. You don't have to do anything on the server side directly # run without any arguments for "usage" info From d78bbe8c3eecc9b3592c1b1387b784571286e5f1 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Oct 2009 08:31:59 +0530 Subject: [PATCH 094/637] lots of doc changes reflecting "push to admin" is default now :) - added comments to easy install to help do it manually - README: some stuff moved to tips doc, brief summary of extras (over gitosis) added - INSTALL: major revamp, easy install and manual install, much shorter and much more readable! plus other docs changed as needed, and updated the tips doc to roll in some details from "update.mkd" in the "ml" branch --- README.mkd | 104 ++++++++++++------------------- conf/example.gitolite.rc | 2 +- doc/0-INSTALL.mkd | 127 +++++++++----------------------------- doc/0-UPGRADE.mkd | 40 ++++++++++-- doc/1-migrate.mkd | 63 ++++++++----------- doc/2-admin.mkd | 55 +++-------------- doc/3-faq-tips-etc.mkd | 130 +++++++++++++++++++++++---------------- doc/4-push-to-admin.mkd | 5 ++ src/00-easy-install.sh | 99 +++++++++++++++++++++++++---- 9 files changed, 309 insertions(+), 316 deletions(-) diff --git a/README.mkd b/README.mkd index fe85675..7cbbaf3 100644 --- a/README.mkd +++ b/README.mkd @@ -9,17 +9,16 @@ ---- -Gitolite is the bare essentials of gitosis, with a completely different -config file that allows (at last!) access control down to the branch level, -including specifying who can and cannot *rewind* a given branch. It is -released under GPL v2. See COPYING for details. +Gitolite is a rewrite of gitosis, with a completely different config file that +allows (at last!) access control down to the branch level, including +specifying who can and cannot *rewind* a given branch. In this document: * why - * what's gone - * what's new - * the workflow + * what's extra + * security + * contact and license ---- @@ -32,30 +31,15 @@ a typical $DAYJOB setting, there are some issues: and be done * often, "python-setuptools" isn't installed (and on a Solaris9 I was trying to help remotely, we never did manage to install it eventually) - * or you don't have root access, or the ability to add users + * you don't have root access, or the ability to add users (this is also true + for people who have just one userid on a hosting provider) * the most requested feature (see "what's new?") had to be written anyway -### what's gone - -While I was pondering the need to finally learn python[1] , I also realised -that: - - * no one in $DAYJOB type environments will use or approve access methods - that work without any authentication, so I didn't need gitweb/daemon - support in the tool or in the config file. - - Update 2009-09-24: I don't use this feature but someone wanted it, so I - added it... see the "faq, tips, etc" document for more - - * the idea that you admin it by pushing to a special repo is nice, but not - really necessary because of how rarely these changes are made, especially - considering how much code is involved in that piece - All of this pointed to a rewrite. In perl, naturally :-) -### what's new +### what's extra -Per-branch permissions. You will not believe how often I am asked this at +**Per-branch permissions**. You will not believe how often I am asked this at $DAYJOB. This is almost the single reason I started *thinking* about rolling my own gitosis in the first place. @@ -65,50 +49,42 @@ deleting a branch (which is really just an extreme form of rewind). I needed something in between allowing anyone to do it (the default) and disabling it completely (`receive.denyNonFastForwards` or `receive.denyDeletes`). -Take a look at the example config file in the repo to see how I do this. I -copied the basic idea from `update-hook-example.txt` (it's one of the "howto"s -that come with the git source tree). However, please note the difference in -the size and complexity of the *operational code* between the update hook in -that example, and in mine :-) The reason is in the next section. +Here're **some more features**. All of them are documented in detail +somewhere in the `doc/` subdirectory. -### the workflow + * simpler, yet far more powerful, config file syntax, including specifying + gitweb/daemon access. You'll need this power if you manage lots of users + + repos + combinations of access + * config file syntax gets checked upfront, and much more thoroughly + * if your requirements are still too complex, you can split up the config + file and delegate authority over parts of it + * more comprehensive logging [aka: management does not think "blame" is just + a synonym for "annotate" :-)] + * "personal namespace" prefix for each dev + * migration guide and simple converter for gitosis conf file + * "exclude" (or "deny" rights in the config file) -- this is the "rebel" + branch in the repository, and always will be ;-) -In order to get per-branch access, you *must* use an update hook. However, -that only gets invoked on a push; "read" access still has to be controlled -right at the beginning, before git even enters the scene (just the way gitosis -currently works). +### security -So: either split the access control into two config files, or have two -completely different programs *both* parse the same one and pick what they -want. Crap... I definitely don't want the hook doing any parsing, (and it -would be nice if the auth-control program didn't have to either). +Due to the environment in which this was created and the need it fills, I +consider this a "security" program, albeit a very modest one. The code is +very small and easily reviewable -- the 2 programs that actually control +access when a user logs in total about 200 lines of code (about +80 lines according to "sloccount"). -So I changed the workflow completely: +For the first person to find a security hole in it, defined as allowing a +normal user (not the gitolite admin) to read a repo, or write/rewind a ref, +that the config file says he shouldn't, and caused by a bug in *code* that is +in the "master" branch, (not in the other branches, or the configuration file +or in Unix, perl, shell, etc.)... well I can't afford 1000 USD rewards like +djb, so you'll have to settle for 1000 INR (Indian Rupees) as a "token" prize +:-) - * all admin changes happen *on the server*, in a special directory that - contains the config and the users' pubkeys. But there's no commit and - push afterward - * instead, after making changes, you "compile" the configuration. This - refreshes `~/.ssh/authorized_keys`, as well as puts a parsed form of the - access list in a file for the other two pieces to use. +---- -The pre-parsed form is basically a huge perl variable. It's human readable -too (never mind what the python guys say!) +### contact and license -So the admin knows immediately if the config file had any problems, which is -good. Also, the relatively complex parse code is not part of the actual -access control points, which are: - - * the program that is run via `~/.ssh/authorized_keys` (I call it - `gl-auth-command`, equivalent to `gitosis-serve`); this decides whether - git should even be allowed to run (basic R/W/no access) - * the update-hook on each repo, which decides the per-branch permissions - -### footnotes - -[1] I hate whitespace to mean anything significant except for text; this is a -personal opinion *only*, so pythonistas please back off :-) - -### contact +Gitolite is released under GPL v2. See COPYING for details. sitaramc@gmail.com diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index ea80bec..700fc0e 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -5,7 +5,7 @@ # this file is meant to be pulled into a perl program using "do" or "require". # You do NOT need to know perl to edit the paths; it should be fairly -# self-explanatory +# self-explanatory and easy to maintain perl syntax :-) # -------------------------------------- diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index d4f3b44..5ef38cf 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -1,9 +1,24 @@ # installing gitolite +This document tells you how to install gitolite. After the install is done, +you may want to see the "admin" document for adding users, repos, etc. + +There's an easy install script for Linux, and for other Unixes there's a +slightly more manual process. Both are explained here. + +In this document: + + * easy install + * manual install + * other notes + * next steps + +---- + ### easy install -There is now an easy install script that makes installing very easy for the -common case. **This script is meant to be run on your workstation, not on the +There is an easy install script that makes installing very easy for the common +case. **This script is meant to be run on your workstation, not on the server!** It will take care of all the server side work, *and* get you "push-to-admin" too :-) In short, it does **everything**! @@ -35,110 +50,26 @@ info). #### disadvantages - * has been tested only with Linux. However, the script now makes a much - better "document" on what actually needs to be done, so people installing - on non-Linux machines can probably follow the steps in the script and - install if they wish. Sort of "simulate" it... :) + * has been tested only with Linux ### manual install If for some reason you cannot use the easy-install method, (for example, -you're on a non-Linux machine) read on. Unlike the easy install, all the -below stuff is meant to be run on the server. +you're on a non-Linux machine), it's not very complicated. Just open the file +`src/00-easy-install.sh` in a nice, syntax coloring, text editor, and follow +the instructions marked "MANUAL" :-) -#### pre-requisites on the server +### other notes -If you managed to install git, you might already have what gitolite needs: - - * git itself, the more recent the better - * perl, typically installed with git, since git sort of needs it; any - version that includes `Data::Dumper`[1] will do. - * one user account on the server, with password access [2] - -A major objective is to allow use by people without root access, permissions -to create other userids, etc. Even if you have root, please add a user just -for gitolite and do all this from that user. - -#### getting a tar file from a clone - -You can clone the repo from github, then execute a make command to extract a -tar file of the branch you want. Please use the make command, not a plain -"git archive". The comments in the `Makefile` will explain why. - - git clone git://github.com/sitaramc/gitolite.git - cd gitolite - make master.tar - # or maybe "make rebel.tar" or "make pu.tar" - -#### install from tar file - - * make a temp directory somewhere, cd to it, and unpack the tar file - * run `src/install.pl` and follow the prompts - -**When you are told to edit some file, please read the comments in the file**. -And if you can make some time to read the documentation, please do. -Especially if you have problems. - -Notes: - - * At present the location of `~/.gitolite.rc` is fixed (maybe later I'll - change it to a "git config" variable but I don't see much need right now) - - If you edit it and change any paths, be sure to keep the perl syntax -- - you *don't* have to know perl to do so, it's fairly easy to guess in this - limited case. And of course, make sure you adjust the commands shown - above to suit the new locations + * If you edit `~/.gitolite.rc` and change any paths, be sure to keep the + perl syntax -- you *don't* have to know perl to do so, it's fairly easy to + guess in this limited case * the config file is (by default) at `~/.gitolite/conf/gitolite.conf`, though you can change its location in the "rc" file. Edit the file as you - wish. The comments in the file ought to be clear enough but let me know - if not + wish. The comments in the example file (`conf/example.conf`) ought to be + clear enough but let me know if not - * if you want to bring in existing (bare, server) repos into gitolite, this - should work (refer to `~/.gitolite.rc` for *your* values of the pathnames - below): - * backup the repo, then move it to `$BASE_REPO` - * copy `$GL_ADMINDIR/src/update-hook.pl` to - `[reponame].git/hooks/update` -- if you don't do this, per branch - restrictions will not work - * then update the keys and the config file and "compile" (see "admin" - document) +### next steps -### Footnotes: - -[1] Actually, due to the way gitolite is architected, you can manage -without `Data::Dumper` on the server if you have no choice. Only -`gl-compile-conf` needs it, so just run that on some other machine and copy -the two output files across. Cumbersome but doable... the advantage of -separating all the hard work into a manually-run piece :) - -[2] If you have *only* pubkey access, and **no** password access, then your -pubkey is already in the server's `~/.ssh/authorized_keys`. If you also need -to access git as a developer (clone, push, etc), do *not* submit this same -pubkey to gitolite -- it won't work. - -Instead, create a different keypair for your "developer" role (by, e.g., -`ssh-keygen -t rsa -f ~/.ssh/gitdev`), then give `~/.ssh/gitdev.pub` to -gitolite as "yourname.pub", just like you would do for any other user. - -Then you create a suitable `~/.ssh/config` to use the correct key -automatically, something like this: - - host gitadm - hostname my.server - user my_userid_on_server - - host gitdev - hostname my.server - user my_userid_on_server - identityfile ~/.ssh/gitdev - -From now on, `ssh gitadm` will get you a command line on the server, to do -gitolite admin and other work. And your repository URLs would look like -`gitdev:reponame.git`. Very, very, simple... - -And as with gitosis, there's more "ssh" magic than "git" magic here :-) - ----- - -gitolite is released under the GPL v2 license. See COPYING for details +See the "admin" document for how to add users, etc. diff --git a/doc/0-UPGRADE.mkd b/doc/0-UPGRADE.mkd index c106bc7..5e242f6 100644 --- a/doc/0-UPGRADE.mkd +++ b/doc/0-UPGRADE.mkd @@ -1,11 +1,19 @@ # upgrading gitolite atomically +Upgrading is done **manually, on the server** (except the last step, which is +on your admin repo clone), even if you installed it using the easy install +script on the client. First, it's not as difficult as an install so you don't +really need a script. Second, you may have customised the "rc" file +(`~/.gitolite.rc` on the server) and I'm reluctant to mess with that in an +automated way. + ### general upgrade notes If you follow the steps below, you can make the upgrade "atomic", so you don't have to do it at a "quiet" time or something. -1. untar the new version to some temp directory and `cd` to it +1. copy a tar file containing the new version to the server, untar it to some + temp directory and `cd` to it 2. *prepare* the new version of `~/.gitolite.rc`. It **must** have **all** the variables defined in `conf/example.gitolite.rc` (the "new" rc file), @@ -31,12 +39,11 @@ have to do it at a "quiet" time or something. src/install.pl 5. compile the config once again, in case the *internal* format of the - compiled config file (`$GL_CONF_COMPILED`) has changed + compiled config file (`$GL_CONF_COMPILED`) has changed. - src/gl-compile-conf - - (if you've already setup "push-to-admin", this step should be replaced by - a "git push". Make a dummy commit if needed, to make the push happen). + To do this, you have to do a "git push" on the client side. That might + require a dummy change (maybe add a blank line somewhere) because + otherwise the push will not happen. And you're done. @@ -45,6 +52,27 @@ And you're done. If any extra steps beyond the generic ones above are needed, they will be listed here, newest first. +#### upgrading from 410c9ba + +Between 410c9ba and this version, gitolite managed to make "push to admin" the +default for new installs, but in a much more painless way. If you're +upgrading, you're not forced to use "push to admin", but I'd suggest you: + + * make sure you have password-less (pubkey) login to a command line on your + server + * save your `~/.gitolite.rc`, `keydir/*.pub` and your `conf/gitolite.conf` + files from the server, bring them to your workstation + * then run `src/00-easy-install.sh` on the workstation, as if it were a + fresh install + * when the editor pops up to edit the rc file, delete all the lines in + it and copy them from the saved `~/.gitolite.rc` + * at the end of the script, after the gitolite-admin repo has been + cloned successfully, copy the saved `conf/gitolite.conf` and + `keydir/*.pub` to the clone, then add, commit, and push + +Gitolite also learnt to delegate parts of the config to other users. See +`doc/5-delegation.mkd` for details. + #### upgrading from 8217ef9 Between 8217ef9 and this version, gitolite learnt to handle gitweb/daemon diff --git a/doc/1-migrate.mkd b/doc/1-migrate.mkd index fe0b4cf..fb0491f 100644 --- a/doc/1-migrate.mkd +++ b/doc/1-migrate.mkd @@ -3,14 +3,9 @@ [TODO: make the migration tool fix up gitweb and daemon control also...] Migrating from gitosis to gitolite is pretty easy, because the basic design is -the same. The differences are: +the same. - * gitolite does not use a special repo for the configuration, pubkeys, etc. - You can choose to version that directory but it is not required that you - do so - -Here's how we migrated my work repos (note: substitute real paths, from your -`~/.gitolite.rc`, for `$REPO_BASE` and `$GL_ADMINDIR` below): +Here's how we migrated my work repos: 1. login as the `git` user on the server, and get a bash shell prompt @@ -18,10 +13,17 @@ Here's how we migrated my work repos (note: substitute real paths, from your else. This will prevent users from pushing anything while you do the backup, migration, etc. -3. For added safety, **delete** the post-update hook that gitosis-admin +3. **edit** `~/.ssh/authorized_keys` and **carefully** remove all the lines + containing "gitosis-serve", as well as the marker line that says + "auto-generated by gitosis, DO NOT REMOVE", then save the file. If the + file did not have any other keys and is now empty, don't worry -- save it + anyway because gitolite expects the file to be present (even if it is + empty). + +4. For added safety, **delete** the post-update hook that gitosis-admin installed - rm $REPO_BASE/gitosis-admin.git/hooks/post-update + rm ~/repositories/gitosis-admin.git/hooks/post-update or at least rename it to `.sample` like all the other hooks hanging around, or edit it and comment out the line that calls `gitosis-run-hook @@ -30,39 +32,34 @@ Here's how we migrated my work repos (note: substitute real paths, from your If you do not do this, an accidental push to the gitosis-admin repo will mess up your `~/.ssh/authorized_keys` file -4. take a **backup** of the `$REPO_BASE` directory +5. take a **backup** of the `~/repositories` directory -5. untar gitolite to some temporary directory and follow the instructions to - **install** it using `src/install.pl` +Now, log off the server and get back to the client: -6. **convert** your gitosis config file: +1. follow instructions to install gitolite; see install document. Make sure + that you **don't** change the default path for `$REPO_BASE`! - cd $GL_ADMINDIR - src/conf-convert.pl < ~/.gitosis.conf > conf/gitolite.conf +2. **convert** your gitosis config file. Substitute the path for your + gitosis-admin clone in `$GSAC` below, and similarly the path for your + gito**lite**-admin clone in `$GLAC` - be sure to check the file to make sure it converted correctly + src/conf-convert.pl < $GSAC/gitosis.conf > $GLAC/gitolite.conf -7. **copy** the update hook to each of the existing repos (if you have repos - in subdirectories, this won't work as is; adapt it): + Be sure to check the file to make sure it converted correctly - for i in $REPO_BASE/*.git - do - cp src/update-hook.pl $i/hooks/update - done +3. **copy** the keys from gitosis's keydir (same meanings for GSAC and GLAC) -8. **copy** the keys from gitosis's keydir + cp $GSAC/keydir/* $GLAC/keydir - cp $REPO_BASE/gitosis-admin.git/gitosis-export/keydir/* keydir - -9. **Important: expand** any multi-key files you may have. See the "faq, +4. **Important: expand any multi-key files you may have**. See the "faq, tips, etc" document in the doc directory for an explanation of what multi-keys are, how gitosis does them and how gitolite does it differently. You can split the keys manually, or use the following code (just - copy-paste it into your xterm): + copy-paste it into your xterm after "cd"-ing to your gitolite-admin repo + clone): - cd $GL_ADMINDIR wc -l keydir/*.pub | grep -v total | grep -v -w 1 | while read a b do i=1 @@ -82,12 +79,4 @@ Here's how we migrated my work repos (note: substitute real paths, from your "sitaram@laptop.pub" and "sitaram@desktop.pub" or whatever. *Please check the files to make sure this worked properly* -10. **edit** `~/.ssh/authorized_keys` and **carefully** remove all the lines - containing "gitosis-serve", as well as the marker line that says - "auto-generated by gitosis, DO NOT REMOVE", then save the file. If the - file did not have any other keys and is now empty, don't worry -- save it - anyway because gitolite expects the file to be present (even if it is - empty). - -At this point you're ready to "compile" the configuration. See the "admin" -document for what to do, and how to check the outputs, etc. +5. Check all your changes to your gitolite-admin clone, commit, and push diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index 8cd2216..133455a 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -22,15 +22,15 @@ Please read on to see how to do this correctly. extension, like `sitaram.pub` or `john-smith.pub`. You can also use periods and underscores - * copy all these `*.pub` files to `$GL_KEYDIR` + * copy all these `*.pub` files to `keydir` in your gitolite-admin repo clone - * the config file (`$GL_CONF`) is very well commented, please take a couple - of minutes to read it. Then edit it and + * edit the config file (`conf/gitolite.conf` in your admin repo clone). See + `conf/example.conf` in the gitolite source for details on what goes in + that file, syntax, etc. Just add new repos as needed, and add new users + and give them permissions as required. The users names should be exactly + the same as their keyfile names, but without the `.pub` extension - * add new repos as needed - * add new users and give them permissions as required. The users names - should be exactly the same as their keyfile names, but without the - `.pub` extension + * when done, commit your changes and push #### specifying gitweb and daemon access @@ -51,41 +51,6 @@ one-time setup you must do separately. All this does is: value you specified for `$projects_list` when setting up gitweb) * for daemon, create the file `git-daemon-export-ok` in the repository -`src/gl-compile-conf` will keep these files consistent with the config -settings -- this includes removing such settings if you remove "read" -permissions for the special usernames. - -#### compiling - - * backup your `~/.ssh/authorized_keys` file if you feel nervous :-) - * that's "backup" as in "copy", not "move". The next step won't work if - the file doesn't exist. Even an empty one is fine but it must be - present - * if you don't have an `~/.ssh/authorized_keys` file at all, you may - have logged in with a password, which in turn might mean you are not - familiar with ssh and authkeys etc. If so, please read up at least - [this](http://sitaramc.github.com/0-installing/9-gitolite-basics.html#IMPORTANT_overview_of_ssh), - and preferably also the man pages for sshd and sshd\_config, to make - sure you understand the security implications of what you are doing. - Once you have understood that, create at least an empty - `~/.ssh/authorized_keys` file before proceeding to the next step - - * cd to `$GL_ADMINDIR` and run `src/gl-compile-conf` - -That should be it, really. However, if you want to be doubly sure, or maybe -the first couple of times you use it, you may want to check these: - - * check the outputs - - * `~/.ssh/authorized_keys` should contain one line for each "user" pub - key added, between two "marker" lines (which you should please please - not remove!). The line should contain a "command=" pointing to a - `$GL_ADMINDIR/src/gl-auth-command` file, then some sshd restrictions, the - key, etc. - * `$GL_CONF_COMPILED` should contain an expanded list of the access - control rules. It may look a little long, but it's fairly intuitive! - - * if the run threw up any "initialising empty repo" messages, check the - individual repos (inside `$REPO_BASE`) if you wish. Especially make sure - the `$REPO_BASE/[reponame].git/hooks/update` got copied OK and is - executable +The "compile" script will keep these files consistent with the config settings +-- this includes removing such settings if you remove "read" permissions for +the special usernames. diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 66ab956..f24d30c 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -5,19 +5,21 @@ In this document: * common errors and mistakes * git version dependency * other errors, warnings, notes... + * getting a tar file from a clone * differences from gitosis * simpler syntax * two levels of access rights checking * error checking the config file * delegating parts of the config file * easier to specify gitweb/daemon access - * built-in logging + * better logging * one user, many keys * who am I? * other cool things - * developer specific branches + * "personal" branches * design choices * why we don't do "excludes" + * keeping the parser and the access control separate ### common errors and mistakes @@ -37,10 +39,10 @@ In this document: Here's a workaround for a version dependency that the normal flow of gitolite has. -When you edit your config file to create a new repo, and run -`src/gl-compile-conf`, gitolite creates an empty, bare repo for you. -Normally, you're expected to clone this on the client side, and start working --- make your first commit(s), then push, etc. +When you edit your config file to create a new repo, and push the changes to +the server, gitolite creates an empty, bare repo for you. Normally, you're +expected to clone this on the client side, and start working -- make your +first commit(s), then push, etc. However, cloning an empty repo requires a server side git version that is at least 1.6.2. Gitolite detects this when creating a repo, and warns you. @@ -74,21 +76,27 @@ normal way, since it's not empty anymore. * if you specify a repo that is not at the top level `$REPO_BASE`, be sure to manually create the intermediate directories first. For instance if - you specify a new repo called "a/b/c" to the config file and "compile", - the "compile" script will just `mkdir a/b/c.git`, assuming "a/b" has - already been created - - * if you run `git init` inside `$GL_ADMINDIR` (that is, make it a normal, - non-bare, repo), then, everytime you "compile" (run - `src/gl-compile-conf`), any changes to `conf` and `keydir` will - automatically be committed. This is a simple safety net in case you - accidentally delete the whole config or something. Also see - [4-push-to-admin.mkd](http://github.com/sitaramc/gitolite/blob/pu/doc/4-push-to-admin.mkd) - if you really know what you're doing and want "push to admin" + you specify a new repo called "a/b/c" to the config file and push, the + "compile" script will just `mkdir a/b/c.git`, assuming "a/b" has already + been created * gitweb not able to read your repos? You can change the umask for newly created repos to something more relaxed -- see the `~/.gitolite.rc` file +### getting a tar file from a clone + +You can clone the repo from github or indefero, then execute a make command to +extract a tar file of the branch you want. Please use the make command, not a +plain "git archive", because the Makefile adds a file called +`.GITOLITE-VERSION` that will help you identify which version you are using. + + git clone git://github.com/sitaramc/gitolite.git + # (OR) + git clone git://sitaramc.indefero.net/sitaramc/gitolite.git + cd gitolite + make master.tar + # or maybe "make rebel.tar" or "make pu.tar" + ### differences from gitosis Apart from the big ones listed in the top level README, and subjective ones @@ -176,9 +184,8 @@ gitosis does not do any. I just found out that if you mis-spell `members` as `member`, gitosis will silently ignore it, and leave you wondering why access was denied. -In gitolite, you have to "compile" the config file first (this step takes the -place of the commit+push in gitosis), and keyword typos *are* caught so you -know right away. +Gitolite "compiles" the config file first and keyword typos *are* caught so +you know right away. #### delegating parts of the config file @@ -219,24 +226,21 @@ bits and pieces. Here's an example, using short repo names for convenience: repo r2 # ...and so on... -#### built-in logging +### better logging -...just in case of emergency :-) +If you have been too liberal with the permission to rewind, it has built-in +logging as an emergency fallback if someone goes too far, or for audit +purposes [`*`]. The logfile names and location are configurable, and can +include the year/month/day etc in the filename for easy archival or further +processing. The log file even tells you which pattern in the config file +matched to allow that specific access to proceed. -Let's say you gave a dev the right to rewind a branch and he went and rewound -it all the way, or pushed something drastically different on it. Now you need -to recover the commit that got wiped out. +> [`*`] setting `core.logAllRefUpdates true` does provide a safety net +> against over-zealous rewinds, but it does not tell you "who". And +> strangely, management does not seem to share the view that "blame" is just +> a synonym for "annotate" ;-)] -If you'd remembered to `git config core.logAllRefUpdates` for that repo, or -globally, you'd be fine -- the reflog will tell you. Otherwise you'd be left -grubbing around in `git fsck --unreachable` a bit :-( - -And even if you recover the correct commit, you'll never know *who* did it -- -not unless you add a one-line patch to gitosis, plus a `post-receive` hook to -every repository. - -With gitolite, there's a log file in `$GL_ADMINDIR` that contains lines like -this: +The log lines look like this: 2009-09-19.10:24:37 + b4e76569659939 4fb16f2a88d8b5 myrepo refs/heads/master user2 refs/heads/master @@ -283,33 +287,31 @@ In gitolite, it's simple: just ask nicely :-) ### other cool things -#### developer specific branches +### "personal" branches -So I know what gitolite calls me. Big deal... who cares? +"personal" branches are great for corporate environments, where +unauthenticated pull/clone is a no-no. Since a dev workstation cannot do +authentication, even work shared just between 2 devs has to go *via* the +server. This causes the same branch name clutter as in a centralised VCS, +plus setting up permissions for this becomes a chore for the admin. -Here is an idea: give every developer a personal "scratch" namespace within -which she can create, rewind, or delete any branch. For example, I would own -anything under +gitolite lets you define a "personal" or "scratch" namespace prefix for +each developer (e.g., `refs/personal//*`), with full +permissions for that dev and read-only for everyone else. And you get +this without adding a single line to the access config file -- pretty +much fire and forget as far as the admin is concerned, even if there is +constant churn in the project teams. - $PERSONAL_BRANCH_PREFIX/sitaram/ +Not bad for something that took just *one* line of code to implement. +And that's one clean, readable, line, by the way ;-) -The admin could set `$PERSONAL_BRANCH_PREFIX` in the rc file and communicate +The admin would set `$PERSONAL_BRANCH_PREFIX` in the rc file and communicate this to all users. It could be something like `refs/heads/personal`, which means all such branches will show up in `git branch` lookups and `git clone` will fetch them. Or he could use, say, `refs/personal`, which means it won't show up in any normal "branch-y" commands and stuff, and generally be much less noisy. -Yes, I know git is all about allowing private branches, but in a corporate -environment it's not always possible to pull from a co-worker, for the same -reasons you don't have anonymous access (like the git:// protocol). A normal -developer workstation cannot do authentication, so how would they know who's -pulling? This is a perfect way to share code *without* cluttering the global -namespace, and each developer controls his/her own set of branches! - -The amount of code needed? *One line!* I'll spend about 3x more on declaring -and initialising the new variable, and 30x more on documenting it :-) - **Note that a user who has NO write access cannot have personal branches**; if you read the section (above) on "two levels of access rights checking" you'll understand why. @@ -343,6 +345,9 @@ Just don't *show* the user this config file; it might sound insulting :-) #### why we don't do "excludes" +[umm... having said all this, I implemented it anyway; see the "rebel" +branch!] + I found an error in the example conf file. This snippet *seems* to say that "bruce" can write versioned tags (`refs/tags/v[0-9].*`), but the other staffers can't: @@ -387,3 +392,24 @@ The lack of overlap between refexes ensures ***no confusion*** in specifying, understanding, and ***auditing***, what is allowed and what is not. And in security, "no confusion" is a good thing :-) + +#### keeping the parser and the access control separate + +There are two programs concerned with access control: + + * `gl-auth-command`, the program that is run via `~/.ssh/authorized_keys`; + this decides whether git should even be allowed to run (basic R/W/no + access). (This one cannot decide on the branch-level access; it is not + known at this point what branch is being accessed) + * the update-hook on each repo, which decides the per-branch permissions + +I have chosen to keep the relatively complex task of parsing the config file +out of them to keep them simpler (and faster). So any changes to the config +have to be first "compiled", and the access control programs use this +"compiled" version of the config. (The compile step also refreshes +`~/.ssh/authorized_keys`). + +If you choose the "easy install" method, all this is quite transparent to you +anyway. If you cannot use the easy install and must install manually, I have +clear instructions on how to set it up. + diff --git a/doc/4-push-to-admin.mkd b/doc/4-push-to-admin.mkd index 48d1baa..8791480 100644 --- a/doc/4-push-to-admin.mkd +++ b/doc/4-push-to-admin.mkd @@ -1,5 +1,10 @@ # "push to admin" in gitolite +**WARNING: THIS DOCUMENT IS OBSOLETE. DO NOT USE. IT IS RETAINED ONLY FOR +HISTORICAL PURPOSES**. Gitolite now does "push-to-admin" by default, and does +it very easily and simply by front-loading the ssh problem. See the install +doc for details. + ---- Gitosis's default mode of admin is by cloning and pushing the `gitosis-admin` diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index d19c1a5..2ca8bf1 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -2,9 +2,13 @@ # easy install for gitolite -# this runs on the client side, and itself takes care of all the server side +# you run this on the client side, and it takes care of all the server side # work. You don't have to do anything on the server side directly +# to do a manual install (since I have tested this only on Linux), open this +# script in a nice, syntax coloring, text editor and follow the instructions +# prefixed by the word "MANUAL" in the comments below :-) + # run without any arguments for "usage" info # important setting: bail on any errors (else we have to check every single @@ -63,6 +67,9 @@ EOFU [[ "$1" =~ [^a-zA-Z0-9._-] ]] && die "user '$1' invalid" [[ "$3" =~ [^a-zA-Z0-9._-] ]] && die "admin_name '$3' invalid" +# MANUAL: (info) we'll use "git" as the user, "server" as the host, and +# "sitaram" as the admin_name in example commands shown below, if any + user=$1 host=$2 admin_name=$3 @@ -71,8 +78,9 @@ admin_name=$3 # basic sanity checks # ---------------------------------------------------------------------- -# are we in the right directory? We should have all the gitolite sources -# here... +# MANUAL: make sure you're in the gitolite directory, at the top level. +# The following files should all be visible: + ls src/gl-auth-command \ src/gl-compile-conf \ src/install.pl \ @@ -81,16 +89,23 @@ ls src/gl-auth-command \ conf/example.gitolite.rc >/dev/null || die "cant find at least some files in gitolite sources/config; aborting" -# do we have pubkey auth on the server +# MANUAL: make sure you have password-less (pubkey) auth on the server. That +# is, running "ssh git@server" should log in straight away, without asking for +# a password + ssh -o PasswordAuthentication=no $user@$host pwd >/dev/null || die "pubkey access didn't work; please set it up using 'ssh-copy-id' or something" -# can the "gitolite-admin" repo be safely created in $HOME +# MANUAL: make sure there's no "gitolite-admin" directory in $HOME (actually +# for the manual flow this doesn't matter so much!) + [[ -d $HOME/gitolite-admin ]] && die "please delete or move aside the \$HOME/gitolite-admin directory" -# cool; now let's create a new key for you as a "gitolite user" (as opposed to -# a gitolite admin who needs to login to the server and get a command line) +# MANUAL: create a new key for you as a "gitolite user" (as opposed to you as +# the "gitolite admin" who needs to login to the server and get a command +# line). For example, "ssh-keygen -t rsa ~/.ssh/sitaram"; this would create +# two files in ~/.ssh (sitaram and sitaram.pub) [[ -f $HOME/.ssh/$admin_name.pub ]] && die "pubkey $HOME/.ssh/$admin_name.pub exists; can't proceed" prompt "the next command will create a new keypair for your gitolite access @@ -111,6 +126,15 @@ prompt "the next command will create a new keypair for your gitolite access ssh-keygen -t rsa -f $HOME/.ssh/$admin_name || die "ssh-keygen failed for some reason..." +# MANUAL: copy the pubkey created to the server, say to /tmp. This would be +# "scp ~/.ssh/sitaram.pub git@server:/tmp" (the script does this at a later +# stage, you do it now for convenience). Note: only the pubkey (sitaram.pub). +# Do NOT copy the ~/.ssh/sitaram file -- that is a private key! + +# MANUAL: if you're running ssh-agent (see if you have an environment variable +# called SSH_AGENT_PID in your "env"), you should add this new key. The +# command is "ssh-add ~/.ssh/sitaram" + if [[ -n $SSH_AGENT_PID ]] then prompt "you're running ssh-agent. We'll try and do an ssh-add of the @@ -121,7 +145,17 @@ then ssh-add $HOME/.ssh/$admin_name fi -# ok the gitolite key is done; create a stanza for it in ~/.ssh/config +# MANUAL: you now need to add some lines to the end of your ~/.ssh/config +# file. If the file doesn't exist, create it. Make sure the file is "chmod +# 644". + +# The lines to be included look like this: + +# host gitolite +# hostname server +# user git +# identityfile ~/.ssh/sitaram + echo " host gitolite hostname $host @@ -153,10 +187,22 @@ rm $HOME/.ssh/.gl-stanza # client side stuff almost done; server side now # ---------------------------------------------------------------------- -# setup the gitolite sources and conf on the server +# MANUAL: copy the gitolite directories "src", "conf", and "doc" to the +# server, to a directory called (for example) "gitolite-install". You may +# have to create the directory first. + ssh $user@$host mkdir -p gitolite-install rsync -a src conf doc $user@$host:gitolite-install/ +# MANUAL: now log on to the server (ssh git@server) and get a command line. +# This step is for your convenience; the script does it all from the client +# side but that may be too much typing for manual use ;-) + +# MANUAL: cd to the "gitolite-install" directory where the sources are. Then +# copy conf/example.gitolite.rc as ~/.gitolite.rc and edit it if you wish to +# change any paths. Make a note of the GL_ADMINDIR and REPO_BASE paths; you +# will need them later + # give the user an opportunity to change the rc cp conf/example.gitolite.rc .gitolite.rc # hey here it means "release candidate" ;-) @@ -183,10 +229,17 @@ relevant for a manual install, not this one..." GL_ADMINDIR=$(ssh $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'") REPO_BASE=$( ssh $user@$host "perl -e 'do \".gitolite.rc\"; print \$REPO_BASE'") -# run the install script on the server +# MANUAL: still in the "gitolite-install" directory? Good. Run +# "src/install.pl" + ssh $user@$host "cd gitolite-install; src/install.pl" -# setup the initial config file +# MANUAL: setup the initial config file. Edit $GL_ADMINDIR/conf/gitolite.conf +# and add at least the following lines to it: + +# repo gitolite-admin +# RW+ = sitaram + echo "#gitolite conf #please see conf/example.conf for details on syntax and features @@ -203,28 +256,48 @@ scp gitolite.conf $user@$host:$GL_ADMINDIR/conf/ scp $HOME/.ssh/$admin_name.pub $user@$host:$GL_ADMINDIR/keydir -# run the compile script on the server +# MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" + ssh $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf" # ---------------------------------------------------------------------- # hey lets go the whole hog on this; setup push-to-admin! # ---------------------------------------------------------------------- -# setup the initial commit for the admin repo +# MANUAL: make the first commit in the admin repo. This is a little more +# complex, so read carefully and substitute the correct paths. What you have +# to do is: + +# cd $REPO_BASE/gitolite-admin.git +# GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir +# GIT_WORK_TREE=$GL_ADMINDIR git commit -am start + +# Substitute $GL_ADMINDIR and $REPO_BASE appropriately. Note there is no +# space around the "=" in the second and third lines. + echo "cd $REPO_BASE/gitolite-admin.git GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir GIT_WORK_TREE=$GL_ADMINDIR git commit -am start " | ssh $user@$host +# MANUAL: now that the admin repo is created, you have to set the hooks +# properly. The install program does this. So cd back to the +# "gitolite-install" directory and run "src/install.pl" + ssh $user@$host "cd gitolite-install; src/install.pl" prompt "now we will clone the gitolite-admin repo to your workstation and see if it all hangs together. We'll do this in your \$HOME for now, and you can move it elsewhere later if you wish to." +# MANUAL: you're done! Log out of the server, come back to your workstation, +# and clone the admin repo using "git clone gitolite:gitolite-admin.git"! + cd $HOME git clone gitolite:gitolite-admin.git +# MANUAL: be sure to read the message below; this applies to you too... + echo echo echo ------------------------------------------------------------------------ From 0b81bfd6ec798adcd889d41087bdd1822f0f68d4 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Oct 2009 14:08:14 +0530 Subject: [PATCH 095/637] easy install: allow ports other than 22 for ssh to server --- src/00-easy-install.sh | 55 ++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index 2ca8bf1..43e84a8 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -26,9 +26,10 @@ prompt() { } usage() { cat </dev/null || +ssh -p $port -o PasswordAuthentication=no $user@$host pwd >/dev/null || die "pubkey access didn't work; please set it up using 'ssh-copy-id' or something" # MANUAL: make sure there's no "gitolite-admin" directory in $HOME (actually @@ -152,14 +159,16 @@ fi # The lines to be included look like this: # host gitolite -# hostname server # user git +# hostname server +# port 22 # identityfile ~/.ssh/sitaram echo " host gitolite - hostname $host user $user + hostname $host + port $port identityfile ~/.ssh/$admin_name" > $HOME/.ssh/.gl-stanza if grep 'host *gitolite' $HOME/.ssh/config &>/dev/null @@ -191,8 +200,8 @@ rm $HOME/.ssh/.gl-stanza # server, to a directory called (for example) "gitolite-install". You may # have to create the directory first. -ssh $user@$host mkdir -p gitolite-install -rsync -a src conf doc $user@$host:gitolite-install/ +ssh -p $port $user@$host mkdir -p gitolite-install +rsync -e "ssh -p $port" -a src conf doc $user@$host:gitolite-install/ # MANUAL: now log on to the server (ssh git@server) and get a command line. # This step is for your convenience; the script does it all from the client @@ -219,20 +228,20 @@ that all the paths etc. represent paths on the server!" ${VISUAL:-${EDITOR:-vi}} .gitolite.rc # copy the rc across -scp .gitolite.rc $user@$host: +scp -P $port .gitolite.rc $user@$host: prompt "ignore any 'please edit this file' or 'run this command' type lines in the next set of command outputs coming up. They're only relevant for a manual install, not this one..." # extract the GL_ADMINDIR and REPO_BASE locations -GL_ADMINDIR=$(ssh $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'") -REPO_BASE=$( ssh $user@$host "perl -e 'do \".gitolite.rc\"; print \$REPO_BASE'") +GL_ADMINDIR=$(ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'") +REPO_BASE=$( ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$REPO_BASE'") # MANUAL: still in the "gitolite-install" directory? Good. Run # "src/install.pl" -ssh $user@$host "cd gitolite-install; src/install.pl" +ssh -p $port $user@$host "cd gitolite-install; src/install.pl" # MANUAL: setup the initial config file. Edit $GL_ADMINDIR/conf/gitolite.conf # and add at least the following lines to it: @@ -252,13 +261,13 @@ repo testing " > gitolite.conf # send the config and the key to the remote -scp gitolite.conf $user@$host:$GL_ADMINDIR/conf/ +scp -P $port gitolite.conf $user@$host:$GL_ADMINDIR/conf/ -scp $HOME/.ssh/$admin_name.pub $user@$host:$GL_ADMINDIR/keydir +scp -P $port $HOME/.ssh/$admin_name.pub $user@$host:$GL_ADMINDIR/keydir # MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" -ssh $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf" +ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf" # ---------------------------------------------------------------------- # hey lets go the whole hog on this; setup push-to-admin! @@ -278,13 +287,13 @@ ssh $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf" echo "cd $REPO_BASE/gitolite-admin.git GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir GIT_WORK_TREE=$GL_ADMINDIR git commit -am start -" | ssh $user@$host +" | ssh -p $port $user@$host # MANUAL: now that the admin repo is created, you have to set the hooks # properly. The install program does this. So cd back to the # "gitolite-install" directory and run "src/install.pl" -ssh $user@$host "cd gitolite-install; src/install.pl" +ssh -p $port $user@$host "cd gitolite-install; src/install.pl" prompt "now we will clone the gitolite-admin repo to your workstation and see if it all hangs together. We'll do this in your \$HOME for now, From 48e18e1d2d67baaa8169d6a4c29de923ea47e863 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 12 Oct 2009 09:53:30 +0530 Subject: [PATCH 096/637] easy install: some minor fixes - fix typo in introduction - detect if you're not running strictly as src/00-easy-install.sh --- src/00-easy-install.sh | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index 43e84a8..7f21179 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -15,7 +15,7 @@ # command!) set -e -die() { echo "$@"; echo "run $0 again without any arguments for help and tips"; exit 1; } +die() { echo "$@"; echo; echo "run $0 again without any arguments for help and tips"; exit 1; } prompt() { echo echo @@ -47,7 +47,7 @@ Notes: Pre-requisites: - you must run this from the gitolite working tree top level directory. - This means you run this as "src/00-easy-install-clientside.sh" + This means you run this as "src/00-easy-install.sh" - you must already have pubkey based access to user@host. If you currently only have password access, use "ssh-copy-id" or something. Somehow get to the point where you can type "ssh user@host" and get a command line. Run @@ -62,6 +62,23 @@ EOFU exit 1; } +# ---------------------------------------------------------------------- +# basic sanity / argument checks +# ---------------------------------------------------------------------- + +# MANUAL: this *must* be run as "src/00-easy-install.sh", not by cd-ing to src +# and then running "./00-easy-install.sh" + +[[ $0 =~ ^src/00-easy-install.sh$ ]] || +{ + echo "please cd to the gitolite repo top level directory and run this as + 'src/00-easy-install.sh'" + exit 1; +} + +# MANUAL: (info) we'll use "git" as the user, "server" as the host, and +# "sitaram" as the admin_name in example commands shown below, if any + [[ -z $3 ]] && usage user=$1 host=$2 @@ -78,13 +95,6 @@ port=22 [[ "$user" =~ [^a-zA-Z0-9._-] ]] && die "user '$user' invalid" [[ "$admin_name" =~ [^a-zA-Z0-9._-] ]] && die "admin_name '$admin_name' invalid" -# MANUAL: (info) we'll use "git" as the user, "server" as the host, and -# "sitaram" as the admin_name in example commands shown below, if any - -# ---------------------------------------------------------------------- -# basic sanity checks -# ---------------------------------------------------------------------- - # MANUAL: make sure you're in the gitolite directory, at the top level. # The following files should all be visible: From 9e46920fe3544e6edb785595c3248e8d01f6ea5a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 12 Oct 2009 20:02:38 +0530 Subject: [PATCH 097/637] faq: explain one user many keys a bit better --- doc/3-faq-tips-etc.mkd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index f24d30c..366c066 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -263,6 +263,11 @@ each of my pubkeys. In gitolite, we keep them separate: "sitaram@laptop.pub" and "sitaram@desktop.pub". The part before the "@" is the username, so gitolite knows these two keys belong to the same person. +Note that you don't say "sitaram@laptop" and so on in the **config** file -- +as far as the config file is concerned there's just **one** user called +"sitaram" -- so you only say "sitaram" there. Only the **pubkey files** have +the extra "@" stuff. + I think this is easier to maintain if you have to delete or change one of those keys. From fc36050972ce406e28024709e2deb98d32296675 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 12 Oct 2009 20:39:34 +0530 Subject: [PATCH 098/637] easy install: one step toward idempotency... --- src/00-easy-install.sh | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index 7f21179..aca409f 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -124,7 +124,6 @@ ssh -p $port -o PasswordAuthentication=no $user@$host pwd >/dev/null || # line). For example, "ssh-keygen -t rsa ~/.ssh/sitaram"; this would create # two files in ~/.ssh (sitaram and sitaram.pub) -[[ -f $HOME/.ssh/$admin_name.pub ]] && die "pubkey $HOME/.ssh/$admin_name.pub exists; can't proceed" prompt "the next command will create a new keypair for your gitolite access The pubkey will be $HOME/.ssh/$admin_name.pub. You will have to @@ -141,7 +140,13 @@ prompt "the next command will create a new keypair for your gitolite access This makes using passphrases very convenient." -ssh-keygen -t rsa -f $HOME/.ssh/$admin_name || die "ssh-keygen failed for some reason..." +if [[ -f $HOME/.ssh/$admin_name.pub ]] +then + prompt "Hmmm... pubkey $HOME/.ssh/$admin_name.pub exists; should I just re-use it? + Be sure you remember the passphrase, if you gave one when you created it!" +else + ssh-keygen -t rsa -f $HOME/.ssh/$admin_name || die "ssh-keygen failed for some reason..." +fi # MANUAL: copy the pubkey created to the server, say to /tmp. This would be # "scp ~/.ssh/sitaram.pub git@server:/tmp" (the script does this at a later @@ -185,7 +190,7 @@ if grep 'host *gitolite' $HOME/.ssh/config &>/dev/null then prompt "your \$HOME/.ssh/config already has settings for gitolite. I will assume they're correct, but if they're not, please edit that file, delete - that paragraph (that line and the following few lines), and rerun. + that paragraph (that line and the following few lines), Ctrl-C, and rerun. In case you want to check right now (from another terminal) if they're correct, here's what they are *supposed* to look like: @@ -222,10 +227,6 @@ rsync -e "ssh -p $port" -a src conf doc $user@$host:gitolite-install/ # change any paths. Make a note of the GL_ADMINDIR and REPO_BASE paths; you # will need them later -# give the user an opportunity to change the rc -cp conf/example.gitolite.rc .gitolite.rc - # hey here it means "release candidate" ;-) - prompt "the gitolite rc file needs to be edited by hand. The defaults are sensible, so if you wish, you can just exit the editor. @@ -235,6 +236,15 @@ understand what is what -- the rc file's documentation is inline. Please remember this file will actually be copied to the server, and that all the paths etc. represent paths on the server!" +# lets try and get the file from there first +if scp -P $port $user@$host:.gitolite.rc . +then + prompt "Oh hey... you already had a '.gitolite.rc' file on the server. I'll use + that instead of the default one..." +else + cp conf/example.gitolite.rc .gitolite.rc +fi + ${VISUAL:-${EDITOR:-vi}} .gitolite.rc # copy the rc across @@ -296,7 +306,7 @@ ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf" echo "cd $REPO_BASE/gitolite-admin.git GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir -GIT_WORK_TREE=$GL_ADMINDIR git commit -am start +GIT_WORK_TREE=$GL_ADMINDIR git commit -am start --allow-empty " | ssh -p $port $user@$host # MANUAL: now that the admin repo is created, you have to set the hooks From e0e9d389203730a0693fee9ff91b99bcec366982 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 12 Oct 2009 21:21:29 +0530 Subject: [PATCH 099/637] easy install: minor formatting stuff --- src/00-easy-install.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index aca409f..ee5d943 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -20,7 +20,7 @@ prompt() { echo echo echo ------------------------------------------------------------------------ - echo "$1" + echo " $1" echo read -p '...press enter to continue or Ctrl-C to bail out' } @@ -228,13 +228,13 @@ rsync -e "ssh -p $port" -a src conf doc $user@$host:gitolite-install/ # will need them later prompt "the gitolite rc file needs to be edited by hand. The defaults -are sensible, so if you wish, you can just exit the editor. + are sensible, so if you wish, you can just exit the editor. -Otherwise, make any changes you wish and save it. Read the comments to -understand what is what -- the rc file's documentation is inline. + Otherwise, make any changes you wish and save it. Read the comments to + understand what is what -- the rc file's documentation is inline. -Please remember this file will actually be copied to the server, and -that all the paths etc. represent paths on the server!" + Please remember this file will actually be copied to the server, and that + all the paths etc. represent paths on the server!" # lets try and get the file from there first if scp -P $port $user@$host:.gitolite.rc . @@ -251,8 +251,8 @@ ${VISUAL:-${EDITOR:-vi}} .gitolite.rc scp -P $port .gitolite.rc $user@$host: prompt "ignore any 'please edit this file' or 'run this command' type -lines in the next set of command outputs coming up. They're only -relevant for a manual install, not this one..." + lines in the next set of command outputs coming up. They're only relevant + for a manual install, not this one..." # extract the GL_ADMINDIR and REPO_BASE locations GL_ADMINDIR=$(ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'") @@ -316,8 +316,8 @@ GIT_WORK_TREE=$GL_ADMINDIR git commit -am start --allow-empty ssh -p $port $user@$host "cd gitolite-install; src/install.pl" prompt "now we will clone the gitolite-admin repo to your workstation -and see if it all hangs together. We'll do this in your \$HOME for now, -and you can move it elsewhere later if you wish to." + and see if it all hangs together. We'll do this in your \$HOME for now, + and you can move it elsewhere later if you wish to." # MANUAL: you're done! Log out of the server, come back to your workstation, # and clone the admin repo using "git clone gitolite:gitolite-admin.git"! From d125488107485d274c522946291e1cb3a9e6e05f Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 13 Oct 2009 10:02:58 +0530 Subject: [PATCH 100/637] doc/3 minor re-arrangement --- doc/3-faq-tips-etc.mkd | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 366c066..917590a 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -348,6 +348,26 @@ Just don't *show* the user this config file; it might sound insulting :-) ### design choices +#### keeping the parser and the access control separate + +There are two programs concerned with access control: + + * `gl-auth-command`, the program that is run via `~/.ssh/authorized_keys`; + this decides whether git should even be allowed to run (basic R/W/no + access). (This one cannot decide on the branch-level access; it is not + known at this point what branch is being accessed) + * the update-hook on each repo, which decides the per-branch permissions + +I have chosen to keep the relatively complex task of parsing the config file +out of them to keep them simpler (and faster). So any changes to the config +have to be first "compiled", and the access control programs use this +"compiled" version of the config. (The compile step also refreshes +`~/.ssh/authorized_keys`). + +If you choose the "easy install" method, all this is quite transparent to you +anyway. If you cannot use the easy install and must install manually, I have +clear instructions on how to set it up. + #### why we don't do "excludes" [umm... having said all this, I implemented it anyway; see the "rebel" @@ -398,23 +418,4 @@ understanding, and ***auditing***, what is allowed and what is not. And in security, "no confusion" is a good thing :-) -#### keeping the parser and the access control separate - -There are two programs concerned with access control: - - * `gl-auth-command`, the program that is run via `~/.ssh/authorized_keys`; - this decides whether git should even be allowed to run (basic R/W/no - access). (This one cannot decide on the branch-level access; it is not - known at this point what branch is being accessed) - * the update-hook on each repo, which decides the per-branch permissions - -I have chosen to keep the relatively complex task of parsing the config file -out of them to keep them simpler (and faster). So any changes to the config -have to be first "compiled", and the access control programs use this -"compiled" version of the config. (The compile step also refreshes -`~/.ssh/authorized_keys`). - -If you choose the "easy install" method, all this is quite transparent to you -anyway. If you cannot use the easy install and must install manually, I have -clear instructions on how to set it up. From 55ccb8291b36e7a03e3856d4c4c686815268c039 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 13 Oct 2009 08:44:59 +0530 Subject: [PATCH 101/637] easy install: change ssh-agent detection use ssh-add -l instead of $SSH_AGENT_PID to decide if agent is running --- src/00-easy-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index ee5d943..fa148c7 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -157,7 +157,7 @@ fi # called SSH_AGENT_PID in your "env"), you should add this new key. The # command is "ssh-add ~/.ssh/sitaram" -if [[ -n $SSH_AGENT_PID ]] +if ssh-add -l &>/dev/null then prompt "you're running ssh-agent. We'll try and do an ssh-add of the private key we just created, otherwise this key won't get picked up. If From 030b3f29ef2f8f44d3a402e91315bc48c3461cb4 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 13 Oct 2009 09:55:58 +0530 Subject: [PATCH 102/637] easy install: minor improvement in detection of password-less auth --- src/00-easy-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index fa148c7..c6201f1 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -110,7 +110,7 @@ ls src/gl-auth-command \ # is, running "ssh git@server" should log in straight away, without asking for # a password -ssh -p $port -o PasswordAuthentication=no $user@$host pwd >/dev/null || +ssh -p $port -o PasswordAuthentication=no $user@$host true || die "pubkey access didn't work; please set it up using 'ssh-copy-id' or something" # MANUAL: make sure there's no "gitolite-admin" directory in $HOME (actually From 59e15e62a1e95cb635f35991d9884aa7ef9db08d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 13 Oct 2009 10:02:45 +0530 Subject: [PATCH 103/637] support git installed outside default $PATH (also some minor fixes to doc/3) --- conf/example.gitolite.rc | 10 ++++++++++ doc/3-faq-tips-etc.mkd | 26 ++++++++++++++++++++++++-- src/00-easy-install.sh | 22 ++++++++++++++++++---- src/gl-auth-command | 5 ++++- src/gl-compile-conf | 5 ++++- src/install.pl | 5 ++++- 6 files changed, 64 insertions(+), 9 deletions(-) diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index 700fc0e..78df455 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -88,6 +88,16 @@ $PERSONAL=""; # NOTE: whatever value you choose, for security reasons it is better to make # it fully qualified -- that is, starting with "refs/" +# -------------------------------------- + +# if git on your server is on a standard path (that is +# ssh git@server git --version +# works), leave this setting as is. Otherwise, choose one of the +# alternatives, or write your own + +$GIT_PATH="" +# $GIT_PATH="/opt/bin/" + # -------------------------------------- # per perl rules, this should be the last line in such a file: 1; diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 917590a..cda6ab0 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -14,6 +14,7 @@ In this document: * easier to specify gitweb/daemon access * better logging * one user, many keys + * support for git installed outside default PATH * who am I? * other cool things * "personal" branches @@ -226,7 +227,7 @@ bits and pieces. Here's an example, using short repo names for convenience: repo r2 # ...and so on... -### better logging +#### better logging If you have been too liberal with the permission to rewind, it has built-in logging as an emergency fallback if someone goes too far, or for audit @@ -271,6 +272,27 @@ the extra "@" stuff. I think this is easier to maintain if you have to delete or change one of those keys. +#### support for git installed outside default PATH + +The normal solution is to add to the system default PATH somehow, either by +munging `/etc/profile` or by enabling `PermitUserEnvironment` in +`/etc/ssh/sshd_config` and then setting the PATH in `~/.ssh/.environment`. +All these are security risks because they allow a lot more than just you and +your git install :-) + +And if you don't have root, you can't do this anyway. + +The only solution till now has been to ask every client to set the config +parameters `remote..receivepack` and `remote..uploadpack`. But +telling *every* client to do so is a pain... + +Gitolite lets you specify the directory in which git binaries are to be found, +via a new variable (`$GIT_PATH`) in the "rc" file. If this variable is +non-empty, it will be appended to the PATH environment variable before +attempting to run git stuff. + +Very easy, very simple, and completely transparent to the users :-) + #### who am I? As a developer, I send a file called `id_rsa.pub` to the gitolite admin. He @@ -292,7 +314,7 @@ In gitolite, it's simple: just ask nicely :-) ### other cool things -### "personal" branches +#### "personal" branches "personal" branches are great for corporate environments, where unauthenticated pull/clone is a no-no. Since a dev workstation cannot do diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index c6201f1..a9f5ecf 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -239,14 +239,28 @@ prompt "the gitolite rc file needs to be edited by hand. The defaults # lets try and get the file from there first if scp -P $port $user@$host:.gitolite.rc . then - prompt "Oh hey... you already had a '.gitolite.rc' file on the server. I'll use - that instead of the default one..." + prompt "Oh hey... you already had a '.gitolite.rc' file on the server. + Let's see if we can use that instead of the default one..." + sort < .gitolite.rc | perl -ne 'print "$1\n" if /^(\$\w+) *=/' > glrc.old + sort < conf/example.gitolite.rc | perl -ne 'print "$1\n" if /^(\$\w+) *=/' > glrc.new + if diff -u glrc.old glrc.new + then + prompt " looks like you're upgrading! I'm going to run your editor + with *both* the old and the new files (in that order), so you can add + in the lines pertaining to the variables shown with a '+' sign in the + above diff. This is necessary; please dont skip this + + [It's upto you to figure out how your editor handles 2 filename + arguments, switch between them, copy lines, etc ;-)]" + ${VISUAL:-${EDITOR:-vi}} .gitolite.rc conf/example.gitolite.rc + else + ${VISUAL:-${EDITOR:-vi}} .gitolite.rc + fi else cp conf/example.gitolite.rc .gitolite.rc + ${VISUAL:-${EDITOR:-vi}} .gitolite.rc fi -${VISUAL:-${EDITOR:-vi}} .gitolite.rc - # copy the rc across scp -P $port .gitolite.rc $user@$host: diff --git a/src/gl-auth-command b/src/gl-auth-command index 62573d4..8201403 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -24,13 +24,16 @@ use warnings; # ---------------------------------------------------------------------------- -our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE); +our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH); our %repos; my $glrc = $ENV{HOME} . "/.gitolite.rc"; die "parse $glrc failed: " . ($! or $@) unless do $glrc; die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; +# add a custom path for git binaries, if specified +$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; + # ---------------------------------------------------------------------------- # definitions specific to this program # ---------------------------------------------------------------------------- diff --git a/src/gl-compile-conf b/src/gl-compile-conf index ad50c4a..f51f97a 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -47,7 +47,7 @@ $Data::Dumper::Indent = 1; # common definitions # ---------------------------------------------------------------------------- -our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST); +our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH); # now that this thing *may* be run via "push to admin", any errors have to # grab the admin's ATTENTION so he won't miss them among the other messages a @@ -57,6 +57,9 @@ my $ATTN = "\n\t\t***** ERROR *****\n "; my $glrc = $ENV{HOME} . "/.gitolite.rc"; die "$ATTN parse $glrc failed: " . ($! or $@) unless do $glrc; +# add a custom path for git binaries, if specified +$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; + # ---------------------------------------------------------------------------- # definitions specific to this program # ---------------------------------------------------------------------------- diff --git a/src/install.pl b/src/install.pl index 3e1f655..f6e4142 100755 --- a/src/install.pl +++ b/src/install.pl @@ -3,7 +3,7 @@ use strict; use warnings; -our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF); +our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF, $GIT_PATH); # wrapper around mkdir; it's not an error if the directory exists, but it is # an error if it doesn't exist and we can't create it @@ -33,6 +33,9 @@ unless (-f $glrc) { # ok now $glrc exists; read it to get the other paths die "parse $glrc failed: " . ($! or $@) unless do $glrc; +# add a custom path for git binaries, if specified +$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; + # mkdir $REPO_BASE, $GL_ADMINDIR if they don't already exist my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); wrap_mkdir($repo_base_abs); From b3cab456d5882aa3f88af4fa33d756a7c4bdb547 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 13 Oct 2009 10:16:23 +0530 Subject: [PATCH 104/637] easy-install: committed before testing? tsk tsk tsk! --- conf/example.gitolite.rc | 4 ++-- src/00-easy-install.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index 78df455..b3b3361 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -95,8 +95,8 @@ $PERSONAL=""; # works), leave this setting as is. Otherwise, choose one of the # alternatives, or write your own -$GIT_PATH="" -# $GIT_PATH="/opt/bin/" +$GIT_PATH=""; +# $GIT_PATH="/opt/bin/"; # -------------------------------------- # per perl rules, this should be the last line in such a file: diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index a9f5ecf..a9d8700 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -245,6 +245,8 @@ then sort < conf/example.gitolite.rc | perl -ne 'print "$1\n" if /^(\$\w+) *=/' > glrc.new if diff -u glrc.old glrc.new then + ${VISUAL:-${EDITOR:-vi}} .gitolite.rc + else prompt " looks like you're upgrading! I'm going to run your editor with *both* the old and the new files (in that order), so you can add in the lines pertaining to the variables shown with a '+' sign in the @@ -253,8 +255,6 @@ then [It's upto you to figure out how your editor handles 2 filename arguments, switch between them, copy lines, etc ;-)]" ${VISUAL:-${EDITOR:-vi}} .gitolite.rc conf/example.gitolite.rc - else - ${VISUAL:-${EDITOR:-vi}} .gitolite.rc fi else cp conf/example.gitolite.rc .gitolite.rc From 481242f6cb973db3b45225b1e572d233f0e190a8 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 13 Oct 2009 11:46:04 +0530 Subject: [PATCH 105/637] doc/3: minor fix to an already minor change :) --- doc/3-faq-tips-etc.mkd | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index cda6ab0..3d97a3a 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -19,8 +19,8 @@ In this document: * other cool things * "personal" branches * design choices - * why we don't do "excludes" * keeping the parser and the access control separate + * why we don't do "excludes" ### common errors and mistakes @@ -439,5 +439,3 @@ The lack of overlap between refexes ensures ***no confusion*** in specifying, understanding, and ***auditing***, what is allowed and what is not. And in security, "no confusion" is a good thing :-) - - From 2a63026954955d78f20c361a6ef1d8faaef01746 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 14 Oct 2009 10:09:05 +0530 Subject: [PATCH 106/637] easy install: emphasise advice re passphrases on the new key --- src/00-easy-install.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index a9d8700..b47535b 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -126,15 +126,15 @@ ssh -p $port -o PasswordAuthentication=no $user@$host true || prompt "the next command will create a new keypair for your gitolite access - The pubkey will be $HOME/.ssh/$admin_name.pub. You will have to - choose a passphrase or hit enter for none. I recommend not having a - passphrase for now, and adding one with 'ssh-keygen -p' *as soon as* - all the setup is done and you've successfully cloned and pushed the - gitolite-admin repo. + The pubkey will be $HOME/.ssh/$admin_name.pub. You will have to choose a + passphrase or hit enter for none. I recommend not having a passphrase for + now, *especially* if you do not have a passphrase for the key which you + are already using to get server access! - After that, I suggest you (1) install 'keychain' or something - similar, and (2) add the following command to your bashrc (since - this is a non-default key) + Add one using 'ssh-keygen -p' after all the setup is done and you've + successfully cloned and pushed the gitolite-admin repo. After that, + install 'keychain' or something similar, and add the following command to + your bashrc (since this is a non-default key) ssh-add \$HOME/.ssh/$admin_name From 8e47e0117a5a824f4fe55822f53b45bb220d09df Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 14 Oct 2009 11:10:06 +0530 Subject: [PATCH 107/637] easy install: much more idempotent... - example config file is now all comments (should have been that way anyway) - we detect if it is an upgrade and act accordingly (see below) IMPORTANT: we assume that $admin_name remains the same in an upgrade -- that's how we detect it is an upgrade! Change that name or his pubkey, and you're toast! --- conf/example.conf | 53 ++++++++++++++++------------- src/00-easy-install.sh | 76 +++++++++++++++++++++++++++++------------- 2 files changed, 82 insertions(+), 47 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index 5e92b0a..81b6148 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -29,20 +29,23 @@ # we need lists at all? (1) to be able to reuse the same set of usernames in # the paras for different repos, (2) to keep the lines short, because lists # accumulate, like squid ACLs, so you can say: -@cust_A = cust1 cust2 -@cust_A = cust99 + +# @cust_A = cust1 cust2 +# @cust_A = cust99 + # and this is the same as listing all three on the same line # you can nest groups, but not recursively of course! -@interns = indy james -@staff = bob @interns -@staff = me alice -@secret_staff = bruce whitfield martin +# @interns = indy james +# @staff = bob @interns -@pubrepos = linux git +# @staff = me alice +# @secret_staff = bruce whitfield martin -@privrepos = supersecretrepo anothersecretrepo +# @pubrepos = linux git + +# @privrepos = supersecretrepo anothersecretrepo # ---------------------------------------------------------------------------- # REPOS, REFS, and PERMISSIONS @@ -80,29 +83,33 @@ # anyone can play in the sandbox, including making non-fastforward commits # (that's what the "+" means) -repo sandbox - RW+ = @all + +# repo sandbox +# RW+ = @all # my repo and alice's repo have the same memberships and access, so we just # put them both in the same stanza -repo myrepo alicerepo - RW+ = me alice - R = bob eve + +# repo myrepo alicerepo +# RW+ = me alice +# R = bob eve # this repo is visible to customers from company A but they can't write to it -repo cust_A_repo - R = @cust_A - RW = @staff + +# repo cust_A_repo +# R = @cust_A +# RW = @staff # idea for the tags syntax shamelessly copied from git.git # Documentation/howto/update-hook-example.txt :) -repo @privrepos thirdsecretrepo - RW+ pu = bruce - RW master next = bruce - RW refs/tags/v[0-9].* = bruce - RW refs/tags/ss/ = @secret_staff - RW tmp/.* = @secret_staff - R = @secret_staff + +# repo @privrepos thirdsecretrepo +# RW+ pu = bruce +# RW master next = bruce +# RW refs/tags/v[0-9].* = bruce +# RW refs/tags/ss/ = @secret_staff +# RW tmp/.* = @secret_staff +# R = @secret_staff # ---------------------------------------------------------------------------- # GITWEB AND DAEMON CONTROL diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index b47535b..66c0e6c 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -113,12 +113,6 @@ ls src/gl-auth-command \ ssh -p $port -o PasswordAuthentication=no $user@$host true || die "pubkey access didn't work; please set it up using 'ssh-copy-id' or something" -# MANUAL: make sure there's no "gitolite-admin" directory in $HOME (actually -# for the manual flow this doesn't matter so much!) - -[[ -d $HOME/gitolite-admin ]] && - die "please delete or move aside the \$HOME/gitolite-admin directory" - # MANUAL: create a new key for you as a "gitolite user" (as opposed to you as # the "gitolite admin" who needs to login to the server and get a command # line). For example, "ssh-keygen -t rsa ~/.ssh/sitaram"; this would create @@ -241,20 +235,30 @@ if scp -P $port $user@$host:.gitolite.rc . then prompt "Oh hey... you already had a '.gitolite.rc' file on the server. Let's see if we can use that instead of the default one..." - sort < .gitolite.rc | perl -ne 'print "$1\n" if /^(\$\w+) *=/' > glrc.old - sort < conf/example.gitolite.rc | perl -ne 'print "$1\n" if /^(\$\w+) *=/' > glrc.new + sort < .gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > glrc.old + sort < conf/example.gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > glrc.new if diff -u glrc.old glrc.new then ${VISUAL:-${EDITOR:-vi}} .gitolite.rc else - prompt " looks like you're upgrading! I'm going to run your editor - with *both* the old and the new files (in that order), so you can add - in the lines pertaining to the variables shown with a '+' sign in the - above diff. This is necessary; please dont skip this + prompt " looks like you're upgrading, and there are some new rc + variables that this version is expecting that your old rc file doesn't + have. + + I'm going to run your editor with two filenames. The first is the + example file from this gitolite version. It will have a block (code + and comments) for each of the variables shown above with a '+' sign. + + The second is your current rc file, the destination. Copy those lines + into this file, preferably *with* the surrounding comments (for + clarity) and save it. + + This is necessary; please dont skip this! [It's upto you to figure out how your editor handles 2 filename arguments, switch between them, copy lines, etc ;-)]" - ${VISUAL:-${EDITOR:-vi}} .gitolite.rc conf/example.gitolite.rc + + ${VISUAL:-${EDITOR:-vi}} conf/example.gitolite.rc .gitolite.rc fi else cp conf/example.gitolite.rc .gitolite.rc @@ -277,6 +281,31 @@ REPO_BASE=$( ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$RE ssh -p $port $user@$host "cd gitolite-install; src/install.pl" +# MANUAL: if you're upgrading, just go to your clone of the admin repo, make a +# dummy change, and push. (This assumes that you didn't change the +# admin_name, pubkeys, userids, ports, or whatever, and you ran easy install +# only to upgrade the software). And then you are **done** -- ignore the rest +# of this file for the purposes of an upgrade + +# determine if this is an upgrade; we decide based on whether a pubkey called +# $admin_name.pub exists in $GL_ADMINDIR/keydir on the remote side +upgrade=0 +if ssh -p $port $user@$host cat $GL_ADMINDIR/keydir/$admin_name.pub &> /dev/null +then + prompt "this looks like an upgrade, based on the fact that a file called + $admin_name.pub already exists in $GL_ADMINDIR/keydir on the server. + + Please go to your clone of the admin repo, make a dummy change (like maybe + add a blank line to something), commit, and push. You're done! + + (This assumes that you didn't change the admin_name, pubkeys, userids, + ports, or whatever, and you ran easy install only to upgrade the + software)." + + exit 0 + +fi + # MANUAL: setup the initial config file. Edit $GL_ADMINDIR/conf/gitolite.conf # and add at least the following lines to it: @@ -284,7 +313,7 @@ ssh -p $port $user@$host "cd gitolite-install; src/install.pl" # RW+ = sitaram echo "#gitolite conf -#please see conf/example.conf for details on syntax and features +# please see conf/example.conf for details on syntax and features repo gitolite-admin RW+ = $admin_name @@ -296,20 +325,18 @@ repo testing # send the config and the key to the remote scp -P $port gitolite.conf $user@$host:$GL_ADMINDIR/conf/ - scp -P $port $HOME/.ssh/$admin_name.pub $user@$host:$GL_ADMINDIR/keydir # MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" - ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf" # ---------------------------------------------------------------------- # hey lets go the whole hog on this; setup push-to-admin! # ---------------------------------------------------------------------- -# MANUAL: make the first commit in the admin repo. This is a little more -# complex, so read carefully and substitute the correct paths. What you have -# to do is: +# MANUAL: you have to now make the first commit in the admin repo. This is +# a little more complex, so read carefully and substitute the correct paths. +# What you have to do is: # cd $REPO_BASE/gitolite-admin.git # GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir @@ -329,12 +356,13 @@ GIT_WORK_TREE=$GL_ADMINDIR git commit -am start --allow-empty ssh -p $port $user@$host "cd gitolite-install; src/install.pl" -prompt "now we will clone the gitolite-admin repo to your workstation - and see if it all hangs together. We'll do this in your \$HOME for now, - and you can move it elsewhere later if you wish to." - # MANUAL: you're done! Log out of the server, come back to your workstation, -# and clone the admin repo using "git clone gitolite:gitolite-admin.git"! +# and clone the admin repo using "git clone gitolite:gitolite-admin.git", or +# pull once again if you already have a clone + +prompt "now we will clone the gitolite-admin repo to your workstation +and see if it all hangs together. We'll do this in your \$HOME for now, +and you can move it elsewhere later if you wish to." cd $HOME git clone gitolite:gitolite-admin.git From 536e3198bf6a127a41aafdb79c5419da1d18b401 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 14 Oct 2009 14:09:34 +0530 Subject: [PATCH 108/637] doc fixes... - README: add a "what" section first, plus a few minor fixes - doc/5: - remove reference to obsolete ml branch URL; point it to the right place with the right section name - change text to reflect the fact that p-t-a is now the default! --- README.mkd | 33 ++++++++++++++++++++++++--------- doc/5-delegation.mkd | 39 ++++++++++++++++++++------------------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/README.mkd b/README.mkd index 7cbbaf3..81075cb 100644 --- a/README.mkd +++ b/README.mkd @@ -15,13 +15,28 @@ specifying who can and cannot *rewind* a given branch. In this document: + * what * why - * what's extra + * extra features * security * contact and license ---- +### what + +Gitolite allows a server to host many git repositories and provide access to +many developers, without having to give them real userids on the server. The +essential magic in doing this is ssh's pubkey access and the `authorized_keys` +file, and the inspiration was an older program called gitosis. + +Gitolite can restrict who can read from (clone/fetch) or write to (push) a +repository. It can also restrict who can push to what branch or tag, which is +very important in a corporate environment. Gitolite can be installed without +requiring root permissions, and with no additional software than git itself +and perl. It also has several other neat features described below and +elsewhere in the `doc/` directory. + ### why I have been using gitosis for a while, and have learnt a lot from it. But in @@ -33,15 +48,15 @@ a typical $DAYJOB setting, there are some issues: to help remotely, we never did manage to install it eventually) * you don't have root access, or the ability to add users (this is also true for people who have just one userid on a hosting provider) - * the most requested feature (see "what's new?") had to be written anyway + * the most requested feature (see below) had to be written anyway All of this pointed to a rewrite. In perl, naturally :-) -### what's extra +### extra features -**Per-branch permissions**. You will not believe how often I am asked this at -$DAYJOB. This is almost the single reason I started *thinking* about rolling -my own gitosis in the first place. +The most important feature I needed was **per-branch permissions**. This is +pretty much mandatory in a corporate environment, and is almost the single +reason I started *thinking* about rolling my own gitosis in the first place. It's not just "read-only" versus "read-write". Rewinding a branch (aka "non fast forward push") is potentially dangerous, but sometimes needed. So is @@ -53,8 +68,8 @@ Here're **some more features**. All of them are documented in detail somewhere in the `doc/` subdirectory. * simpler, yet far more powerful, config file syntax, including specifying - gitweb/daemon access. You'll need this power if you manage lots of users - + repos + combinations of access + gitweb/daemon access. You'll need this power if you manage lots of + users+repos+combinations of access * config file syntax gets checked upfront, and much more thoroughly * if your requirements are still too complex, you can split up the config file and delegate authority over parts of it @@ -62,7 +77,7 @@ somewhere in the `doc/` subdirectory. a synonym for "annotate" :-)] * "personal namespace" prefix for each dev * migration guide and simple converter for gitosis conf file - * "exclude" (or "deny" rights in the config file) -- this is the "rebel" + * "exclude" (or "deny") rights in the config file -- this is the "rebel" branch in the repository, and always will be ;-) ### security diff --git a/doc/5-delegation.mkd b/doc/5-delegation.mkd index 9924dc3..863268f 100644 --- a/doc/5-delegation.mkd +++ b/doc/5-delegation.mkd @@ -5,14 +5,14 @@ ### lots of repos, lots of users Gitolite tries to make it easy to manage access to lots of users and repos, -exploiting commonalities wherever possible. (The example under "simpler, more -powerful syntax" [here][ml] should give you an idea). As you can see, it lets -you specify bits and pieces of the access control separately -- i.e., *all* -the access specs for a certain repo need not be together; they can be +exploiting commonalities wherever possible. (The example in the section +"simpler syntax" in [this page][ml] should give you an idea). As you can see, +it lets you specify bits and pieces of the access control separately -- i.e., +*all* the access specs for a certain repo need not be together; they can be scattered, which makes it easier to manage the sort of slice and dice needed in that example. -[ml]: http://github.com/sitaramc/gitolite/blob/ml/update.mkd +[ml]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd But eventually the config file will become too big. If you let only one person have control, he could become a bottleneck. If you give it to multiple @@ -81,23 +81,24 @@ Splitting up the file does help, but there's also that little security issue -- anyone can make any change to any "fragment", unless you (once again) go back to Unix permissions to keep them separate. -Fixing that requires using "push-to-admin". +Fixing that requires using "push-to-admin", which is the default (and only) +method to make config changes in gitosis. -The page on [push-to-admin][ptd] explains clearly how to set it up. Unlike -gitosis, I refuse to make it the default because it's a support nightmare. -Don't get me wrong -- it's a great feature, and I use it myself, but the -learning curve is too steep to make it *required*. +For a long time, I refused to make "push to admin" the default because it's a +support nightmare. Don't get me wrong -- it's a great feature, and I use it +myself, but the learning curve was too steep to make it *required*. -[ptd]: http://github.com/sitaramc/gitolite/blob/pu/doc/4-push-to-admin.mkd +But eventually I figured out a way to front-load the darn ssh problem that +everyone on #git seemed to run up against, created a nice "easy install" +script, and made "push to admin" the default. -So, having setup push-to-admin, you add these lines to the main config file, -assuming Alice is in charge of all web browser development projects, Bob takes -care of web servers, and Mallory, as [tradition][abe] dictates, is in charge -of malware ;-) +So now, you just add these lines to the main config file, assuming Alice is in +charge of all web browser development projects, Bob takes care of web servers, +and Mallory, as [tradition][abe] dictates, is in charge of malware ;-) [abe]: http://en.wikipedia.org/wiki/Alice_and_Bob#List_of_characters - # you probably added these two lines while setting up push-to-admin + # these 2 lines were probably present already repo gitolite-admin RW+ = sitaram # now add these 3 lines @@ -111,10 +112,10 @@ of malware ;-) @webserver_repos = apache nginx @malware_repos = conficker storm -**As you can see, for each repo group you want to delegate authority over, -there's a *branch* in the `gitolite-admin` repo with the same name. If you +As you can see, **for each repo group** you want to delegate authority over, +there's a **branch with the same name** in the `gitolite-admin` repo. If you have write access to that branch, you are allowed to define rules for repos in -that repo group.** +that repo group. In other words, we use gitolite's per-branch permissions to "enforce" the separation between the delegated configs! From a91d5692913d9fd9412eb61424c3a904c2d99907 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 21 Oct 2009 19:19:00 +0530 Subject: [PATCH 109/637] ...because writing in crayon wasn't possible :) [long story...!] --- src/00-easy-install.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index 66c0e6c..195da08 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -49,9 +49,11 @@ Pre-requisites: - you must run this from the gitolite working tree top level directory. This means you run this as "src/00-easy-install.sh" - you must already have pubkey based access to user@host. If you currently - only have password access, use "ssh-copy-id" or something. Somehow get to - the point where you can type "ssh user@host" and get a command line. Run - this program only after that is done + only have password access, use "ssh-copy-id" or something equivalent (or + copy the key manually). Somehow (doesn't matter how), get to the point + where you can type "ssh user@host" and get a command line. + + **DO NOT RUN THIS PROGRAM UNTIL THAT WORKS** Errors: - if you get a "pubkey [...filename...] exists" error, it is either leftover From d6dc1c0856e03cce1685bd7d40af212a9796b4c4 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 21 Oct 2009 19:19:20 +0530 Subject: [PATCH 110/637] added doc/6: more complex ssh setups --- doc/6-complex-ssh-setups.mkd | 135 +++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 doc/6-complex-ssh-setups.mkd diff --git a/doc/6-complex-ssh-setups.mkd b/doc/6-complex-ssh-setups.mkd new file mode 100644 index 0000000..c274a73 --- /dev/null +++ b/doc/6-complex-ssh-setups.mkd @@ -0,0 +1,135 @@ +# more complex ssh setups + +What do you need to know in order to create more complex ssh setups (for +instance if you have *two* gitolite servers you are administering)? Once more +unto the breach, here's more ssh magic! + +In this document: + + * files on client + * files on the server + * sanity checks + * two gitolite servers to manage? + +### files on client + + * default keypair; used to get shell access to servers. You would have + copied this pubkey to the gitolite server in order to log in without a + password. (On Linux systems you may have used `ssh-copy-id` to do that). + You would have done this *before* you ran the easy install script, because + otherwise easy install won't run! + + ~/.ssh/id_rsa + ~/.ssh/id_rsa.pub + + * gitolite keypair; the "sitaram" in this is the 3rd argument to the + `src/00-easy-install.sh` command you ran; the easy install script does the + rest + + ~/.ssh/sitaram + ~/.ssh/sitaram.pub + +### files on the server + + * the authkeys file; this contains one line containing the pubkey of each + user who is permitted to login without a password. + + Pubkey lines that give shell access look like this: + + ssh-rsa AAAAB3NzaC[snip]uPjrUiAUew== /home/sitaram/.ssh/id_rsa + + On a typical server there will be only one or two of these lines. + + Note that the last bit (`/home/sitaram/.ssh/id_rsa`) is purely a *comment* + field and can be anything. Also, the actual lines are much longer, about + 400 characters; I snipped 'em in the middle, as you can see. + + In contrast, pubkey lines that give access to git repos hosted by gitolite + looks like this: + + command="[some path]src/gl-auth-command sitaram",[some restrictions] ssh-rsa AAAAB3NzaC[snip]s18OnB42oQ== sitaram@sita-lt + + You will have many more of these lines -- one for every pubkey file in + `keydir/` of your gitolite-admin repo, with the corresponding username in + place of "sitaram" in the example above. + + The "command=" at the beginning ensures that when someone with the + corresponding private key logs in, they don't get a shell. Instead, the + `gl-auth-command` program is run, and (in this example) is given the + argument `sitaram`. This is how gitolite is invoked, (and is told the + user logging in is "sitaram"). + + * config file; this file has an entry for gitolite access: + + ~/.ssh/config + + Let's step back a bit. Normally, you might expect to access gitolite + repos like this: + + ssh://git@server/reponame.git + + But this won't work, because this ends up using the *default* keypair + (normally), which gives you a command line. Which means it won't invoke + the `gl-auth-command` program at all, and so none of gitolite's access + control will work. + + You need to force ssh to use the *other* keypair when performing a git + operation. With just ssh, that would be + + ssh -i ~/.ssh/sitaram git@server + + but git does not support putting an alternate keypair in the URL. + + Luckily, ssh has a very convenient way of capturing all the mundane + information (username, hostname, port number (if it's not the default 22), + and keypair to be used) in one "paragraph". This is what the para looks + like for us (the easy install script puts it there the first time): + + host gitolite + user git + hostname server + identityfile ~/.ssh/sitaram + + (The "gitolite" can be anything you want of course; it's like an alias for + all the stuff below it). This ensures that typing + + ssh gitolite + + is equivalent to + + ssh -i ~/.ssh/sitaram git@server + + and therefore this: + + git clone gitolite:reponame.git + + now works as expected, invoking the special keypair instead of the default + one. + +### sanity checks + + * `ssh gitolite` should get you the `SSH_ORIGINAL_COMMAND` error. If you + get a command line, something is wrong + + * conversely, `ssh git@server` should get you a command line + + * the "origin" URL in any clones should look like `gitolite:reponame.git` + instead of something more complex + +### two gitolite servers to manage? + + * they can have the same key; no harm there (example, sitaram.pub) + + * instead of just one ssh/config para, you now have two (assuming that the + remote user on both machines is called "git"): + + host gitolite + user git + hostname server + identityfile ~/.ssh/sitaram + + host gitolite2 + user git + hostname server2 + identityfile ~/.ssh/sitaram + From 96fa0da946cca5063f57902e2088126d3ae452f6 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 23 Oct 2009 10:14:41 +0530 Subject: [PATCH 111/637] allow a/b/c type repos to be created --- doc/3-faq-tips-etc.mkd | 6 ------ src/gl-compile-conf | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 3d97a3a..43678e2 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -75,12 +75,6 @@ normal way, since it's not empty anymore. to hurt anything. This happens even in normal git, not just gitolite. [Update 2009-09-14; this has been fixed in git 1.6.4.3] - * if you specify a repo that is not at the top level `$REPO_BASE`, be sure - to manually create the intermediate directories first. For instance if - you specify a new repo called "a/b/c" to the config file and push, the - "compile" script will just `mkdir a/b/c.git`, assuming "a/b" has already - been created - * gitweb not able to read your repos? You can change the umask for newly created repos to something more relaxed -- see the `~/.gitolite.rc` file diff --git a/src/gl-compile-conf b/src/gl-compile-conf index f51f97a..d99976e 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -291,7 +291,8 @@ for my $repo (keys %repos) { unless (-d "$repo.git") { - mkdir("$repo.git") or die "$ATTN mkdir $repo.git failed: $!\n"; + system("mkdir", "-p", "$repo.git") and die "$ATTN mkdir $repo.git failed: $!\n"; + # erm, note that's "and die" not "or die" as is normal in perl wrap_chdir("$repo.git"); system("git --bare init"); system("cp $GL_ADMINDIR/src/update-hook.pl hooks/update"); From 8eefc036e023aea86ded1a355ccf7272cf7d01ba Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 23 Oct 2009 10:23:06 +0530 Subject: [PATCH 112/637] rc, pta-hook/doc: don't assume $HOME of 'git' user is /home/git (Thanks to Jerome Arbez-Gindre) --- conf/example.gitolite.rc | 2 +- doc/4-push-to-admin.mkd | 4 ++-- src/pta-hook.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index b3b3361..16bab4d 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -27,7 +27,7 @@ $REPO_UMASK = 0077; # gets you 'rwx------' # part of the setup of gitweb is a variable called $projects_list (please see # gitweb documentation for more on this). Set this to the same value: -$PROJECTS_LIST = "/home/git/projects.list"; +$PROJECTS_LIST = $ENV{HOME} . "/projects.list"; # -------------------------------------- diff --git a/doc/4-push-to-admin.mkd b/doc/4-push-to-admin.mkd index 8791480..7e5fc77 100644 --- a/doc/4-push-to-admin.mkd +++ b/doc/4-push-to-admin.mkd @@ -74,8 +74,8 @@ make the same changes below. repos they play havoc with my git commands, so this is how I do it) cd ~/repositories/gitolite-admin.git - GIT_WORK_TREE=/home/git/.gitolite git add conf/gitolite.conf keydir - GIT_WORK_TREE=/home/git/.gitolite git commit -am start + GIT_WORK_TREE=$HOME/.gitolite git add conf/gitolite.conf keydir + GIT_WORK_TREE=$HOME/.gitolite git commit -am start 4. Now we have to setup the post-update hook for push-to-admin to work. The hook should (1) make a forced checkout in the "live" config directory (which is diff --git a/src/pta-hook.sh b/src/pta-hook.sh index 5009313..2613340 100755 --- a/src/pta-hook.sh +++ b/src/pta-hook.sh @@ -2,7 +2,7 @@ # get this from your .gitolite.conf; and don't forget this is shell, while # that is perl :-) -export GL_ADMINDIR=/home/git/.gitolite +export GL_ADMINDIR=$HOME/.gitolite # checkout the master branch to $GL_ADMINDIR GIT_WORK_TREE=$GL_ADMINDIR git checkout -f master From 78d02e143760a0df8022dae601d809a4d452cab3 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 25 Oct 2009 08:29:52 +0530 Subject: [PATCH 113/637] the rc file can now be in one of 2 places... Packaging gitolite for debian requires the rc file to be in /etc/gitolite. But non-root installs must still be supported, and they need it in $HOME. This means the rc file is no longer in a fixed place, which needs code to find the rc file first. See comments inside new file 'gitolite.pm' for details. The rest of the changes are in the other programs, to replace the hard-coded rc filename with a call to this new code. --- src/gitolite.pm | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/gl-auth-command | 11 +++++++++-- src/gl-compile-conf | 10 ++++++++-- src/install.pl | 16 ++++++++++------ src/update-hook.pl | 5 +++-- 5 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 src/gitolite.pm diff --git a/src/gitolite.pm b/src/gitolite.pm new file mode 100644 index 0000000..2c325e2 --- /dev/null +++ b/src/gitolite.pm @@ -0,0 +1,45 @@ +# this file is commonly used using "require". It is not required to use "use" +# (because it doesn't live in a different package) + +# warning: preceding para requires 4th attribute of a programmer after +# laziness, impatience, and hubris: sense of humour :-) + +# WARNING +# ------- +# the name of this file will change as soon as its function/feature set +# stabilises enough ;-) + +# right now all it does is define a function that tells you where to find the +# rc file + +# ---------------------------------------------------------------------------- +# where is the rc file hiding? +# ---------------------------------------------------------------------------- + +sub where_is_rc +{ + # till now, the rc file was in one fixed place: .gitolite.rc in $HOME of + # the user hosting the gitolite repos. This was fine, because gitolite is + # all about empowering non-root users :-) + + # then we wanted to make a debian package out of it (thank you, Rhonda!) + # which means (a) it's going to be installed by root anyway and (b) any + # config files have to be in /etc/ + + # the only way to resolve this in a backward compat way is to look for the + # $HOME one, and if you don't find it look for the /etc one + + # this common routine does that, setting an env var for the first one it + # finds + + return if $ENV{GL_RC}; + + for my $glrc ( $ENV{HOME} . "/.gitolite.rc", "/etc/gitolite/gitolite.rc" ) { + if (-f $glrc) { + $ENV{GL_RC} = $glrc; + return; + } + } +} + +1; diff --git a/src/gl-auth-command b/src/gl-auth-command index 8201403..62e5add 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -27,8 +27,15 @@ use warnings; our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH); our %repos; -my $glrc = $ENV{HOME} . "/.gitolite.rc"; -die "parse $glrc failed: " . ($! or $@) unless do $glrc; +# the common setup module is in the same directory as this running program is +my $bindir = $0; +$bindir =~ s/\/[^\/]+$//; +require "$bindir/gitolite.pm"; + +# ask where the rc file is, get it, and "do" it +&where_is_rc(); +die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; +# then "do" the compiled config file, whose name we now know die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; # add a custom path for git binaries, if specified diff --git a/src/gl-compile-conf b/src/gl-compile-conf index d99976e..5f8b3ae 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -54,8 +54,14 @@ our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UM # typical push generates my $ATTN = "\n\t\t***** ERROR *****\n "; -my $glrc = $ENV{HOME} . "/.gitolite.rc"; -die "$ATTN parse $glrc failed: " . ($! or $@) unless do $glrc; +# the common setup module is in the same directory as this running program is +my $bindir = $0; +$bindir =~ s/\/[^\/]+$//; +require "$bindir/gitolite.pm"; + +# ask where the rc file is, get it, and "do" it +&where_is_rc(); +die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; # add a custom path for git binaries, if specified $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; diff --git a/src/install.pl b/src/install.pl index f6e4142..1eb4a70 100755 --- a/src/install.pl +++ b/src/install.pl @@ -18,20 +18,24 @@ sub wrap_mkdir print STDERR "created $dir\n"; } -# the only path that is *fixed* (can't be changed without changing all 3 -# programs) is ~/.gitolite.rc +# the common setup module is in the same directory as this running program is +my $bindir = $0; +$bindir =~ s/\/[^\/]+$//; +require "$bindir/gitolite.pm"; -my $glrc = $ENV{HOME} . "/.gitolite.rc"; -unless (-f $glrc) { +# ask where the rc file is, get it, and "do" it +&where_is_rc(); +unless ($ENV{GL_RC}) { # doesn't exist. Copy it across, tell user to edit it and come back + my $glrc = $ENV{HOME} . "/.gitolite.rc"; system("cp conf/example.gitolite.rc $glrc"); print STDERR "created $glrc\n"; print STDERR "please edit it, change the paths if you wish to, and RERUN THIS SCRIPT\n"; exit; } -# ok now $glrc exists; read it to get the other paths -die "parse $glrc failed: " . ($! or $@) unless do $glrc; +# ok now the rc file exists; read it to get the other paths +die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; # add a custom path for git binaries, if specified $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; diff --git a/src/update-hook.pl b/src/update-hook.pl index 5f1c3bb..cdf7402 100755 --- a/src/update-hook.pl +++ b/src/update-hook.pl @@ -28,8 +28,9 @@ use warnings; our ($GL_CONF_COMPILED, $PERSONAL); our %repos; -my $glrc = $ENV{HOME} . "/.gitolite.rc"; -die "parse $glrc failed: " . ($! or $@) unless do $glrc; +# we should already have the GL_RC env var set when we enter this hook +die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; +# then "do" the compiled config file, whose name we now know die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; # ---------------------------------------------------------------------------- From 2f6ed42fcdf99bc4c2ab3c5757d3adf979b931fd Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 25 Oct 2009 17:33:06 +0530 Subject: [PATCH 114/637] install and compile: learnt a '-q' flag (not for manual use!) ...only for easy install to use in "quiet" mode --- src/gl-compile-conf | 13 ++++++++----- src/install.pl | 15 +++++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 5f8b3ae..cbdaf99 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -47,6 +47,9 @@ $Data::Dumper::Indent = 1; # common definitions # ---------------------------------------------------------------------------- +# setup quiet mode if asked; please do not use this when running manually +open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); + our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH); # now that this thing *may* be run via "push to admin", any errors have to @@ -343,12 +346,12 @@ for my $repo (sort keys %repos) { if ($repos{$repo}{'R'}{'daemon'}) { unless (-f $export_ok) { system("touch $export_ok"); - print STDERR "daemon add $repo.git\n"; + print "daemon add $repo.git\n"; } } else { if (-f $export_ok) { unlink($export_ok); - print STDERR "daemon del $repo.git\n"; + print "daemon del $repo.git\n"; } } } @@ -360,21 +363,21 @@ for my $repo (sort keys %repos) { # not in the old list; add it to the new one $projlist{"$repo.git"} = 1; $projlist_changed = 1; - print STDERR "gitweb add $repo.git\n"; + print "gitweb add $repo.git\n"; } } else { if ($projlist{"$repo.git"}) { # delete it from new list delete $projlist{"$repo.git"}; $projlist_changed = 1; - print STDERR "gitweb del $repo.git\n"; + print "gitweb del $repo.git\n"; } } } # has there been a change in the gitweb projects list? if ($projlist_changed) { - print STDERR "updating gitweb project list $PROJECTS_LIST\n"; + print "updating gitweb project list $PROJECTS_LIST\n"; my $projlist_fh = wrap_open( ">", $PROJECTS_LIST); print $projlist_fh join("\n", sort keys %projlist), "\n" if %projlist; close $projlist_fh; diff --git a/src/install.pl b/src/install.pl index 1eb4a70..09a8345 100755 --- a/src/install.pl +++ b/src/install.pl @@ -5,17 +5,20 @@ use warnings; our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF, $GIT_PATH); +# setup quiet mode if asked; please do not use this when running manually +open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); + # wrapper around mkdir; it's not an error if the directory exists, but it is # an error if it doesn't exist and we can't create it sub wrap_mkdir { my $dir = shift; if ( -d $dir ) { - print STDERR "$dir already exists\n"; + print "$dir already exists\n"; return; } mkdir($dir) or die "mkdir $dir failed: $!\n"; - print STDERR "created $dir\n"; + print "created $dir\n"; } # the common setup module is in the same directory as this running program is @@ -29,8 +32,8 @@ unless ($ENV{GL_RC}) { # doesn't exist. Copy it across, tell user to edit it and come back my $glrc = $ENV{HOME} . "/.gitolite.rc"; system("cp conf/example.gitolite.rc $glrc"); - print STDERR "created $glrc\n"; - print STDERR "please edit it, change the paths if you wish to, and RERUN THIS SCRIPT\n"; + print "created $glrc\n"; + print "please edit it, change the paths if you wish to, and RERUN THIS SCRIPT\n"; exit; } @@ -54,7 +57,7 @@ system("cp -R src doc $GL_ADMINDIR"); unless (-f $GL_CONF) { system("cp conf/example.conf $GL_CONF"); - print STDERR < Date: Sun, 25 Oct 2009 14:02:04 +0530 Subject: [PATCH 115/637] easy install: add "-q" option for experts; see usage message --- src/00-easy-install.sh | 88 ++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index 195da08..ec76bc4 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -17,6 +17,14 @@ set -e die() { echo "$@"; echo; echo "run $0 again without any arguments for help and tips"; exit 1; } prompt() { + # receives two arguments. A short piece of text to be displayed, without + # pausing, in "quiet" mode, and a much longer one to be displayed, *with* + # a pause, in normal (verbose) mode + [[ $quiet == -q ]] && [[ -n $1 ]] && { + echo "$1" + return + } + shift echo echo echo ------------------------------------------------------------------------ @@ -26,7 +34,10 @@ prompt() { } usage() { cat </dev/null then - prompt "you're running ssh-agent. We'll try and do an ssh-add of the + prompt " ...adding key to agent..." \ + "you're running ssh-agent. We'll try and do an ssh-add of the private key we just created, otherwise this key won't get picked up. If you specified a passphrase in the previous step, you'll get asked for one now -- type in the same one." @@ -184,7 +197,8 @@ host gitolite if grep 'host *gitolite' $HOME/.ssh/config &>/dev/null then - prompt "your \$HOME/.ssh/config already has settings for gitolite. I will + prompt "found gitolite para in ~/.ssh/config; assuming it is correct..." \ + "your \$HOME/.ssh/config already has settings for gitolite. I will assume they're correct, but if they're not, please edit that file, delete that paragraph (that line and the following few lines), Ctrl-C, and rerun. @@ -193,7 +207,8 @@ then $(cat ~/.ssh/.gl-stanza)" else - prompt "creating settings for your gitolite access in $HOME/.ssh/config; + prompt "creating gitolite para in ~/.ssh/config..." \ + "creating settings for your gitolite access in $HOME/.ssh/config; these are the lines that will be appended to your ~/.ssh/config: $(cat ~/.ssh/.gl-stanza)" @@ -212,7 +227,7 @@ rm $HOME/.ssh/.gl-stanza # have to create the directory first. ssh -p $port $user@$host mkdir -p gitolite-install -rsync -e "ssh -p $port" -a src conf doc $user@$host:gitolite-install/ +rsync $quiet -e "ssh -p $port" -a src conf doc $user@$host:gitolite-install/ # MANUAL: now log on to the server (ssh git@server) and get a command line. # This step is for your convenience; the script does it all from the client @@ -223,7 +238,8 @@ rsync -e "ssh -p $port" -a src conf doc $user@$host:gitolite-install/ # change any paths. Make a note of the GL_ADMINDIR and REPO_BASE paths; you # will need them later -prompt "the gitolite rc file needs to be edited by hand. The defaults +prompt "finding/creating gitolite rc..." \ + "the gitolite rc file needs to be edited by hand. The defaults are sensible, so if you wish, you can just exit the editor. Otherwise, make any changes you wish and save it. Read the comments to @@ -233,19 +249,20 @@ prompt "the gitolite rc file needs to be edited by hand. The defaults all the paths etc. represent paths on the server!" # lets try and get the file from there first -if scp -P $port $user@$host:.gitolite.rc . +if scp -P $port $user@$host:.gitolite.rc . &>/dev/null then - prompt "Oh hey... you already had a '.gitolite.rc' file on the server. + prompt " ...trying to reuse existing rc" \ + "Oh hey... you already had a '.gitolite.rc' file on the server. Let's see if we can use that instead of the default one..." sort < .gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > glrc.old sort < conf/example.gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > glrc.new if diff -u glrc.old glrc.new then - ${VISUAL:-${EDITOR:-vi}} .gitolite.rc + [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} .gitolite.rc else - prompt " looks like you're upgrading, and there are some new rc - variables that this version is expecting that your old rc file doesn't - have. + prompt "" \ + " looks like you're upgrading, and there are some new rc variables + that this version is expecting that your old rc file doesn't have. I'm going to run your editor with two filenames. The first is the example file from this gitolite version. It will have a block (code @@ -264,13 +281,14 @@ then fi else cp conf/example.gitolite.rc .gitolite.rc - ${VISUAL:-${EDITOR:-vi}} .gitolite.rc + [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} .gitolite.rc fi # copy the rc across -scp -P $port .gitolite.rc $user@$host: +scp $quiet -P $port .gitolite.rc $user@$host: -prompt "ignore any 'please edit this file' or 'run this command' type +prompt "installing/upgrading..." \ + "ignore any 'please edit this file' or 'run this command' type lines in the next set of command outputs coming up. They're only relevant for a manual install, not this one..." @@ -281,7 +299,7 @@ REPO_BASE=$( ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$RE # MANUAL: still in the "gitolite-install" directory? Good. Run # "src/install.pl" -ssh -p $port $user@$host "cd gitolite-install; src/install.pl" +ssh -p $port $user@$host "cd gitolite-install; src/install.pl $quiet" # MANUAL: if you're upgrading, just go to your clone of the admin repo, make a # dummy change, and push. (This assumes that you didn't change the @@ -294,7 +312,8 @@ ssh -p $port $user@$host "cd gitolite-install; src/install.pl" upgrade=0 if ssh -p $port $user@$host cat $GL_ADMINDIR/keydir/$admin_name.pub &> /dev/null then - prompt "this looks like an upgrade, based on the fact that a file called + prompt "upgrade done!" \ + "this looks like an upgrade, based on the fact that a file called $admin_name.pub already exists in $GL_ADMINDIR/keydir on the server. Please go to your clone of the admin repo, make a dummy change (like maybe @@ -326,11 +345,11 @@ repo testing " > gitolite.conf # send the config and the key to the remote -scp -P $port gitolite.conf $user@$host:$GL_ADMINDIR/conf/ -scp -P $port $HOME/.ssh/$admin_name.pub $user@$host:$GL_ADMINDIR/keydir +scp $quiet -P $port gitolite.conf $user@$host:$GL_ADMINDIR/conf/ +scp $quiet -P $port $HOME/.ssh/$admin_name.pub $user@$host:$GL_ADMINDIR/keydir # MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" -ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf" +ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf $quiet" # ---------------------------------------------------------------------- # hey lets go the whole hog on this; setup push-to-admin! @@ -356,15 +375,16 @@ GIT_WORK_TREE=$GL_ADMINDIR git commit -am start --allow-empty # properly. The install program does this. So cd back to the # "gitolite-install" directory and run "src/install.pl" -ssh -p $port $user@$host "cd gitolite-install; src/install.pl" +ssh -p $port $user@$host "cd gitolite-install; src/install.pl $quiet" # MANUAL: you're done! Log out of the server, come back to your workstation, # and clone the admin repo using "git clone gitolite:gitolite-admin.git", or # pull once again if you already have a clone -prompt "now we will clone the gitolite-admin repo to your workstation -and see if it all hangs together. We'll do this in your \$HOME for now, -and you can move it elsewhere later if you wish to." +prompt "cloning gitolite-admin repo..." \ +"now we will clone the gitolite-admin repo to your workstation + and see if it all hangs together. We'll do this in your \$HOME for now, + and you can move it elsewhere later if you wish to." cd $HOME git clone gitolite:gitolite-admin.git From 999513c1d9fcce63341b64310aae8e0ef98739cf Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 25 Oct 2009 19:23:15 +0530 Subject: [PATCH 116/637] doc/install: document the new -q flag --- doc/0-INSTALL.mkd | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index 5ef38cf..e6bfff5 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -9,6 +9,9 @@ slightly more manual process. Both are explained here. In this document: * easy install + * typical example run + * advantages over the older install methods + * disadvantages * manual install * other notes * next steps @@ -36,6 +39,17 @@ If so, just `cd` to that clone and run `src/00-easy-install.sh` and follow the prompts! (Running it without any arguments shows you usage plus other useful info). +#### typical example run + +A typical run for me is: + + src/00-easy-install.sh -q git my.git.server sitaram + +`-q` stands for "quiet" mode -- very minimal output, no verbose descriptions +of what it is going to do, and no pauses unless absolutely needed. However, +if you're doing this for the first time or you appreciate knowing what it is +actually doing, I suggest you skip the `-q`. + #### advantages over the older install methods * all ssh problems reduced to **just one pre-requisite**: enable ssh pubkey From 98124596ed387403a46c272d19b49620db715b38 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 25 Oct 2009 21:32:32 +0530 Subject: [PATCH 117/637] doc/6: no idea how but this ended up with CRLF line endings (ugh!) --- doc/6-complex-ssh-setups.mkd | 270 +++++++++++++++++------------------ 1 file changed, 135 insertions(+), 135 deletions(-) diff --git a/doc/6-complex-ssh-setups.mkd b/doc/6-complex-ssh-setups.mkd index c274a73..fa69abf 100644 --- a/doc/6-complex-ssh-setups.mkd +++ b/doc/6-complex-ssh-setups.mkd @@ -1,135 +1,135 @@ -# more complex ssh setups - -What do you need to know in order to create more complex ssh setups (for -instance if you have *two* gitolite servers you are administering)? Once more -unto the breach, here's more ssh magic! - -In this document: - - * files on client - * files on the server - * sanity checks - * two gitolite servers to manage? - -### files on client - - * default keypair; used to get shell access to servers. You would have - copied this pubkey to the gitolite server in order to log in without a - password. (On Linux systems you may have used `ssh-copy-id` to do that). - You would have done this *before* you ran the easy install script, because - otherwise easy install won't run! - - ~/.ssh/id_rsa - ~/.ssh/id_rsa.pub - - * gitolite keypair; the "sitaram" in this is the 3rd argument to the - `src/00-easy-install.sh` command you ran; the easy install script does the - rest - - ~/.ssh/sitaram - ~/.ssh/sitaram.pub - -### files on the server - - * the authkeys file; this contains one line containing the pubkey of each - user who is permitted to login without a password. - - Pubkey lines that give shell access look like this: - - ssh-rsa AAAAB3NzaC[snip]uPjrUiAUew== /home/sitaram/.ssh/id_rsa - - On a typical server there will be only one or two of these lines. - - Note that the last bit (`/home/sitaram/.ssh/id_rsa`) is purely a *comment* - field and can be anything. Also, the actual lines are much longer, about - 400 characters; I snipped 'em in the middle, as you can see. - - In contrast, pubkey lines that give access to git repos hosted by gitolite - looks like this: - - command="[some path]src/gl-auth-command sitaram",[some restrictions] ssh-rsa AAAAB3NzaC[snip]s18OnB42oQ== sitaram@sita-lt - - You will have many more of these lines -- one for every pubkey file in - `keydir/` of your gitolite-admin repo, with the corresponding username in - place of "sitaram" in the example above. - - The "command=" at the beginning ensures that when someone with the - corresponding private key logs in, they don't get a shell. Instead, the - `gl-auth-command` program is run, and (in this example) is given the - argument `sitaram`. This is how gitolite is invoked, (and is told the - user logging in is "sitaram"). - - * config file; this file has an entry for gitolite access: - - ~/.ssh/config - - Let's step back a bit. Normally, you might expect to access gitolite - repos like this: - - ssh://git@server/reponame.git - - But this won't work, because this ends up using the *default* keypair - (normally), which gives you a command line. Which means it won't invoke - the `gl-auth-command` program at all, and so none of gitolite's access - control will work. - - You need to force ssh to use the *other* keypair when performing a git - operation. With just ssh, that would be - - ssh -i ~/.ssh/sitaram git@server - - but git does not support putting an alternate keypair in the URL. - - Luckily, ssh has a very convenient way of capturing all the mundane - information (username, hostname, port number (if it's not the default 22), - and keypair to be used) in one "paragraph". This is what the para looks - like for us (the easy install script puts it there the first time): - - host gitolite - user git - hostname server - identityfile ~/.ssh/sitaram - - (The "gitolite" can be anything you want of course; it's like an alias for - all the stuff below it). This ensures that typing - - ssh gitolite - - is equivalent to - - ssh -i ~/.ssh/sitaram git@server - - and therefore this: - - git clone gitolite:reponame.git - - now works as expected, invoking the special keypair instead of the default - one. - -### sanity checks - - * `ssh gitolite` should get you the `SSH_ORIGINAL_COMMAND` error. If you - get a command line, something is wrong - - * conversely, `ssh git@server` should get you a command line - - * the "origin" URL in any clones should look like `gitolite:reponame.git` - instead of something more complex - -### two gitolite servers to manage? - - * they can have the same key; no harm there (example, sitaram.pub) - - * instead of just one ssh/config para, you now have two (assuming that the - remote user on both machines is called "git"): - - host gitolite - user git - hostname server - identityfile ~/.ssh/sitaram - - host gitolite2 - user git - hostname server2 - identityfile ~/.ssh/sitaram - +# more complex ssh setups + +What do you need to know in order to create more complex ssh setups (for +instance if you have *two* gitolite servers you are administering)? Once more +unto the breach, here's more ssh magic! + +In this document: + + * files on client + * files on the server + * sanity checks + * two gitolite servers to manage? + +### files on client + + * default keypair; used to get shell access to servers. You would have + copied this pubkey to the gitolite server in order to log in without a + password. (On Linux systems you may have used `ssh-copy-id` to do that). + You would have done this *before* you ran the easy install script, because + otherwise easy install won't run! + + ~/.ssh/id_rsa + ~/.ssh/id_rsa.pub + + * gitolite keypair; the "sitaram" in this is the 3rd argument to the + `src/00-easy-install.sh` command you ran; the easy install script does the + rest + + ~/.ssh/sitaram + ~/.ssh/sitaram.pub + +### files on the server + + * the authkeys file; this contains one line containing the pubkey of each + user who is permitted to login without a password. + + Pubkey lines that give shell access look like this: + + ssh-rsa AAAAB3NzaC[snip]uPjrUiAUew== /home/sitaram/.ssh/id_rsa + + On a typical server there will be only one or two of these lines. + + Note that the last bit (`/home/sitaram/.ssh/id_rsa`) is purely a *comment* + field and can be anything. Also, the actual lines are much longer, about + 400 characters; I snipped 'em in the middle, as you can see. + + In contrast, pubkey lines that give access to git repos hosted by gitolite + looks like this: + + command="[some path]src/gl-auth-command sitaram",[some restrictions] ssh-rsa AAAAB3NzaC[snip]s18OnB42oQ== sitaram@sita-lt + + You will have many more of these lines -- one for every pubkey file in + `keydir/` of your gitolite-admin repo, with the corresponding username in + place of "sitaram" in the example above. + + The "command=" at the beginning ensures that when someone with the + corresponding private key logs in, they don't get a shell. Instead, the + `gl-auth-command` program is run, and (in this example) is given the + argument `sitaram`. This is how gitolite is invoked, (and is told the + user logging in is "sitaram"). + + * config file; this file has an entry for gitolite access: + + ~/.ssh/config + + Let's step back a bit. Normally, you might expect to access gitolite + repos like this: + + ssh://git@server/reponame.git + + But this won't work, because this ends up using the *default* keypair + (normally), which gives you a command line. Which means it won't invoke + the `gl-auth-command` program at all, and so none of gitolite's access + control will work. + + You need to force ssh to use the *other* keypair when performing a git + operation. With just ssh, that would be + + ssh -i ~/.ssh/sitaram git@server + + but git does not support putting an alternate keypair in the URL. + + Luckily, ssh has a very convenient way of capturing all the mundane + information (username, hostname, port number (if it's not the default 22), + and keypair to be used) in one "paragraph". This is what the para looks + like for us (the easy install script puts it there the first time): + + host gitolite + user git + hostname server + identityfile ~/.ssh/sitaram + + (The "gitolite" can be anything you want of course; it's like an alias for + all the stuff below it). This ensures that typing + + ssh gitolite + + is equivalent to + + ssh -i ~/.ssh/sitaram git@server + + and therefore this: + + git clone gitolite:reponame.git + + now works as expected, invoking the special keypair instead of the default + one. + +### sanity checks + + * `ssh gitolite` should get you the `SSH_ORIGINAL_COMMAND` error. If you + get a command line, something is wrong + + * conversely, `ssh git@server` should get you a command line + + * the "origin" URL in any clones should look like `gitolite:reponame.git` + instead of something more complex + +### two gitolite servers to manage? + + * they can have the same key; no harm there (example, sitaram.pub) + + * instead of just one ssh/config para, you now have two (assuming that the + remote user on both machines is called "git"): + + host gitolite + user git + hostname server + identityfile ~/.ssh/sitaram + + host gitolite2 + user git + hostname server2 + identityfile ~/.ssh/sitaram + From a23622a31bd2998fe61cc0fb61afbc6bb4885854 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 27 Oct 2009 09:47:06 +0530 Subject: [PATCH 118/637] doc/3: add section on unexpected gitwebauth good-ness! --- doc/3-faq-tips-etc.mkd | 61 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 43678e2..3fced01 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -12,6 +12,7 @@ In this document: * error checking the config file * delegating parts of the config file * easier to specify gitweb/daemon access + * easier to link gitweb authorisation with gitolite * better logging * one user, many keys * support for git installed outside default PATH @@ -92,6 +93,8 @@ plain "git archive", because the Makefile adds a file called make master.tar # or maybe "make rebel.tar" or "make pu.tar" + + ### differences from gitosis Apart from the big ones listed in the top level README, and subjective ones @@ -191,6 +194,9 @@ for details. #### easier to specify gitweb/daemon access +Which of your repos should be accessible via plain HTTP or the `git://` +protocols (gitweb and git daemon, respectively)? + Specifying gitweb and/or daemon access for a repo is simple: give "read" permissions to two special usernames: `gitweb` and `daemon`. @@ -221,6 +227,61 @@ bits and pieces. Here's an example, using short repo names for convenience: repo r2 # ...and so on... + + +#### easier to link gitweb authorisation with gitolite + +Over and above whether a repo is even *shown* by gitweb, you may want to +further restrict people, allowing them to view *only* those repos for which +they have been given read access by gitolite. + +This requires that: + + * you have to have some sort of HTTP auth on your web server (out of my + scope, sorry!) + * the HTTP auth should use the same username (like "sitaram") as used in the + gitolite config (for the corresponding user) + +Once that is done, it's easy. Gitweb allows you to specify a subroutine to +decide on access. We use that feature and tie it to gitolite. Sample code +(untested, munged from something I saw [here][leho]) is given below. + +Note the **utter simplicity** of the actual check (just 1 line!). This is an +unexpected piece of luck coming from the decision to keep the config parse +separate from the actual access control. The config parser puts a pure perl +hash in that file named below as `$gl_conf_compiled`, so all the parsing is +already done and we just use it! + + # completely untested... but the basic idea should work fine + + # change these as needed + $repo_base = '/home/git/repositories/'; + $gl_conf_compiled = '/home/git/.gitolite/conf/gitolite.conf-compiled.pm'; + + # I assume this gives us the HTTP auth username + $username = $cgi->remote_user; + + # ---------- + + # parse the config file; updates %repos hash + our %repos; + die "parse $gl_conf_compiled failed: " . ($! or $@) unless do $gl_conf_compiled; + + # this is gitweb's mechanism; it calls whatever sub is pointed at by this + # variable to decide access yes/no + $export_auth_hook = sub { + my $reponame = shift; + # gitweb passes us the full repo path; so we strip the beginning... + $reponame =~ s/\Q$repo_base//; + # ...and the end, to get the repo name as it is specified in gitolite conf + $reponame =~ s/\.git$//; + + return exists $repos{$reponame}{R}{$username}; + } + + +[leho]: http://leho.kraav.com/news/2009/10/27/using-apache-authentication-with-gitweb-gitosis-repository-access-control/ + #### better logging If you have been too liberal with the permission to rewind, it has built-in From fd6fb9e9e1fe2ef5d74bb3d3d41730fc2555118d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 28 Oct 2009 10:57:38 +0530 Subject: [PATCH 119/637] easy install: save version info, print upgrading message --- src/00-easy-install.sh | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index ec76bc4..4b889ce 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -125,6 +125,25 @@ ls src/gl-auth-command \ ssh -p $port -o PasswordAuthentication=no $user@$host true || die "pubkey access didn't work; please set it up using 'ssh-copy-id' or something" +# MANUAL: if needed, make a note of the version you are upgrading from, and to + +# record which version is being sent across; we assume it's HEAD +git describe --tags --long HEAD 2>/dev/null > src/VERSION || echo '(unknown)' > src/VERSION + +# what was the old version there? +export upgrade_details="you are upgrading from \ +$(ssh -p $port $user@$host cat gitolite-install/src/VERSION 2>/dev/null || echo '(unknown)' ) \ +to $(cat src/VERSION)" + +prompt "$upgrade_details" \ + "$upgrade_details + + Note: getting '(unknown)' for the 'from' version should only happen once. + Getting '(unknown)' for the 'to' version means you are probably installing + from a tar file dump, not a real clone. This is not an error but it's + nice to have those version numbers in case you need support. Try and + install from a clone" + # MANUAL: create a new key for you as a "gitolite user" (as opposed to you as # the "gitolite admin" who needs to login to the server and get a command # line). For example, "ssh-keygen -t rsa ~/.ssh/sitaram"; this would create @@ -167,7 +186,7 @@ fi if ssh-add -l &>/dev/null then - prompt " ...adding key to agent..." \ + prompt " ...adding key to agent..." \ "you're running ssh-agent. We'll try and do an ssh-add of the private key we just created, otherwise this key won't get picked up. If you specified a passphrase in the previous step, you'll get asked for one @@ -228,6 +247,7 @@ rm $HOME/.ssh/.gl-stanza ssh -p $port $user@$host mkdir -p gitolite-install rsync $quiet -e "ssh -p $port" -a src conf doc $user@$host:gitolite-install/ +rm -f src/VERSION # MANUAL: now log on to the server (ssh git@server) and get a command line. # This step is for your convenience; the script does it all from the client @@ -312,7 +332,7 @@ ssh -p $port $user@$host "cd gitolite-install; src/install.pl $quiet" upgrade=0 if ssh -p $port $user@$host cat $GL_ADMINDIR/keydir/$admin_name.pub &> /dev/null then - prompt "upgrade done!" \ + prompt "done!" \ "this looks like an upgrade, based on the fact that a file called $admin_name.pub already exists in $GL_ADMINDIR/keydir on the server. From a19a7f01d74fae087b3e7882b47754d881446257 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 28 Oct 2009 13:33:24 +0530 Subject: [PATCH 120/637] auth, doc/3: print useful information when no command given --- doc/3-faq-tips-etc.mkd | 39 +++++++++++++++++++++++++-------------- src/gl-auth-command | 22 ++++++++++++++++------ 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 3fced01..0dc851a 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -16,7 +16,7 @@ In this document: * better logging * one user, many keys * support for git installed outside default PATH - * who am I? + * what repos do I have access to? * other cool things * "personal" branches * design choices @@ -348,24 +348,35 @@ attempting to run git stuff. Very easy, very simple, and completely transparent to the users :-) -#### who am I? + -As a developer, I send a file called `id_rsa.pub` to the gitolite admin. He -would rename it to "sitaram.pub" and put it in the key directory. Then he'd -add "sitaram" to the config file for the repos which I have access to. +#### what repos do I have access to? -But he could have called me "foobar" instead of "sitaram" -- as long as he -uses it consistently, it'll all work the same and look the same to me, because -the public key is all that matters. +Sometimes there are too many repos, maybe even named similarly, or with the +potential for typos, confusion about hyphens/underscores or upper/lower case, +etc. You'd just like a simple way to know what repos you have access to. -So do I have no reason to know what the admin named me? Well -- maybe (see -next section for one possible use). Anyway how do I find out? +Easy! Just use ssh and try to log in as if you were attempting to get a +shell: -In gitolite, it's simple: just ask nicely :-) - - $ ssh git@my.gitolite.server + $ ssh gitolite PTY allocation request failed on channel 0 - no SSH_ORIGINAL_COMMAND? I'm not a shell, sitaram! + hello sitaram, the gitolite version here is v0.6-17-g94ed189 + you have the following permissions: + R W Anu-WSD + R ROtest + R W SecureBrowse + R W entrans + R W git-notes + R W gitolite + R W gitolite-admin + R W indic_web_input + R W proxy + R W vkc + +Note that until this version, we used to put out an ugly `need +SSH_ORIGINAL_COMMAND` error, just like gitosis used to. All we did is put +that code path to better use :-) ### other cool things diff --git a/src/gl-auth-command b/src/gl-auth-command index 62e5add..4d9b805 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -24,7 +24,7 @@ use warnings; # ---------------------------------------------------------------------------- -our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH); +our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $GL_ADMINDIR); our %repos; # the common setup module is in the same directory as this running program is @@ -60,11 +60,21 @@ my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere! # sanity checks on SSH_ORIGINAL_COMMAND # ---------------------------------------------------------------------------- -# SSH_ORIGINAL_COMMAND must exist. Since we also captured $user, we print -# that in the message so people saying "ssh git@server" can see which gitolite -# user he is being recognised as -my $cmd = $ENV{SSH_ORIGINAL_COMMAND} - or die "no SSH_ORIGINAL_COMMAND? I'm not a shell, $user!\n"; +# SSH_ORIGINAL_COMMAND must exist; if not, we die with a nice message +unless ($ENV{SSH_ORIGINAL_COMMAND}) { + # send back some useful info if no command was given + print "hello $user, the gitolite version here is "; + system("cat", "$GL_ADMINDIR/src/VERSION"); + print "\ryou have the following permissions:\n\r"; + for my $r (sort keys %repos) { + my $perm .= " R" if $repos{$r}{R}{$user}; + $perm .= " W" if $repos{$r}{W}{$user}; + print "$perm\t$r\n\r" if $perm; + } + exit 1; +} + +my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; # split into command and arguments; the pattern allows old style as well as # new style: "git-subcommand arg" or "git subcommand arg", just like gitosis From 071ff4c210bec491dced6e3865e236d520a2d8f9 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 29 Oct 2009 22:12:29 +0530 Subject: [PATCH 121/637] easy install: cleaned up the closing credits; err I mean instructions :) --- src/00-easy-install.sh | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index 4b889ce..333ad4f 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -332,7 +332,13 @@ ssh -p $port $user@$host "cd gitolite-install; src/install.pl $quiet" upgrade=0 if ssh -p $port $user@$host cat $GL_ADMINDIR/keydir/$admin_name.pub &> /dev/null then - prompt "done!" \ + prompt "done! + + If you forgot the help message you saw when you first ran this, there's a + somewhat generic version of it at the end of this file. Try: + + tail -30 $0 +" \ "this looks like an upgrade, based on the fact that a file called $admin_name.pub already exists in $GL_ADMINDIR/keydir on the server. @@ -414,19 +420,35 @@ git clone gitolite:gitolite-admin.git echo echo echo ------------------------------------------------------------------------ -echo "Cool -- we're done. Now you can edit the config file (currently -in ~/gitolite-admin/conf/gitolite.conf) to add more repos, users, etc. -When done, 'git add' the changed files, 'git commit' and 'git push'. +echo " +All done! -Read the comments in conf/example.conf for information about the config -file format -- like the rc file, this also has inline documentation. +The admin repo is currently cloned at ~/gitolite-admin; you can clone it +anywhere you like. To administer gitolite, make changes to the config file +(config/gitolite.conf) and/or the pubkeys (in subdirectory 'keydir') in any +clone, then git add, git commit, and git push. -Your URL for cloning any repo on this server will be +ADDING REPOS: Edit the config file to give *some* user access to the repo. +When you push, an empty repo will be created on the server, which authorised +users can then clone from, or push to. + +ADDING USERS: copy their pubkey as keydir/.pub, add it, commit and +push. + +CONFIG FILE FORMAT: see comments in conf/example.conf in the gitolite source. + +SSH MAGIC: Remember you (the admin) now have *two* keys to access the server +hosting your gitolite setup -- one to get you a command line, and one to get +you gitolite access; see doc/6-complex-ssh-setups.mkd. If you're not using +keychain or some such software, you may have to run this each time you log in: + + ssh-add ~/.ssh/$admin_name + +URLS: *Your* URL for cloning any repo on this server will be gitolite:reponame.git -However, any other users you set up will have to use +*Other* users you set up will have to use $user@$host:reponame.git - -unless they also create similar settings in their '.ssh/config' file." +" From 648dce20ec70bbad58bca2b4215780704dd87efa Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 29 Oct 2009 20:52:06 +0530 Subject: [PATCH 122/637] auth: make ".git" at the end optional --- src/gl-auth-command | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index 4d9b805..f516657 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -84,9 +84,8 @@ my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; # git-receive-pack 'reponame.git' # including the single quotes -my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*).git'/); -die "bad command: $cmd. Make sure the repo name is exactly\n" . - "as in your config (no extra stuff before the name), plus a \".git\" at the end\n" +my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:.git)?'/); +die "bad command: $cmd. Make sure the repo name is exactly as in your config\n" unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo and $repo =~ $REPONAME_PATT ); From 26b4992162e83cfb6f5f8908886e7a3b3b482b97 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 23 Oct 2009 08:55:03 +0530 Subject: [PATCH 123/637] compile: (gh issue 2) apparently pubkeys don't always end in a newline I've never encountered this but it's an easy fix --- src/gl-compile-conf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index cbdaf99..665d1d0 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -415,7 +415,10 @@ for my $pubkey (glob("*")) unless $user_list{$user}; $user_list{$user} = 'has pubkey'; print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS "; - print $newkeys_fh `cat $pubkey`; + # apparently some pubkeys don't end in a newline... + my $pubkey_content = `cat $pubkey`; + $pubkey_content =~ s/\s*$/\n/; + print $newkeys_fh $pubkey_content; } # lint check 3; a little more severe than the first two I guess... for my $user (sort keys %user_list) From b94ff910d11763b7514a9268250f30d90cce1354 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 30 Oct 2009 17:42:26 +0530 Subject: [PATCH 124/637] doc/6: explain that all this is *only* for the admin --- doc/6-complex-ssh-setups.mkd | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/6-complex-ssh-setups.mkd b/doc/6-complex-ssh-setups.mkd index fa69abf..f2292fa 100644 --- a/doc/6-complex-ssh-setups.mkd +++ b/doc/6-complex-ssh-setups.mkd @@ -11,6 +11,19 @@ In this document: * sanity checks * two gitolite servers to manage? +---- + +> But before we get to all that, let's clarify that all this is applicable +> **only** to the gitolite **admin**. He's the only one who needs both a +> shell and gitolite access, so he has **two** pubkeys in play. + +> Normal users have only one pubkey, since they are only allowed to access +> gitolite itself. They do not need to worry about any of this +> `~/.ssh/config` stuff, and their repo urls are very simple, like: +> `git@my.git.server:reponame.git`. + +---- + ### files on client * default keypair; used to get shell access to servers. You would have From 7907213316f85b5bb59b9f59b1f563d34f401dc2 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 30 Oct 2009 17:43:26 +0530 Subject: [PATCH 125/637] easy install: clean up after yourself :) --- src/00-easy-install.sh | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index 333ad4f..db097a7 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -15,6 +15,10 @@ # command!) set -e +export tmpgli=tmp-gl-install +trap "rm -rf $tmpgli" 0 +mkdir -p $tmpgli + die() { echo "$@"; echo; echo "run $0 again without any arguments for help and tips"; exit 1; } prompt() { # receives two arguments. A short piece of text to be displayed, without @@ -212,7 +216,7 @@ host gitolite user $user hostname $host port $port - identityfile ~/.ssh/$admin_name" > $HOME/.ssh/.gl-stanza + identityfile ~/.ssh/$admin_name" > $tmpgli/.gl-stanza if grep 'host *gitolite' $HOME/.ssh/config &>/dev/null then @@ -223,19 +227,18 @@ then In case you want to check right now (from another terminal) if they're correct, here's what they are *supposed* to look like: -$(cat ~/.ssh/.gl-stanza)" +$(cat $tmpgli/.gl-stanza)" else prompt "creating gitolite para in ~/.ssh/config..." \ "creating settings for your gitolite access in $HOME/.ssh/config; these are the lines that will be appended to your ~/.ssh/config: -$(cat ~/.ssh/.gl-stanza)" +$(cat $tmpgli/.gl-stanza)" - cat $HOME/.ssh/.gl-stanza >> $HOME/.ssh/config + cat $tmpgli/.gl-stanza >> $HOME/.ssh/config # if the file didn't exist at all, it might have the wrong permissions chmod 644 $HOME/.ssh/config fi -rm $HOME/.ssh/.gl-stanza # ---------------------------------------------------------------------- # client side stuff almost done; server side now @@ -269,16 +272,16 @@ prompt "finding/creating gitolite rc..." \ all the paths etc. represent paths on the server!" # lets try and get the file from there first -if scp -P $port $user@$host:.gitolite.rc . &>/dev/null +if scp -P $port $user@$host:.gitolite.rc $tmpgli &>/dev/null then prompt " ...trying to reuse existing rc" \ "Oh hey... you already had a '.gitolite.rc' file on the server. Let's see if we can use that instead of the default one..." - sort < .gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > glrc.old - sort < conf/example.gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > glrc.new - if diff -u glrc.old glrc.new + sort < $tmpgli/.gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > $tmpgli/glrc.old + sort < conf/example.gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > $tmpgli/glrc.new + if diff -u $tmpgli/glrc.old $tmpgli/glrc.new then - [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} .gitolite.rc + [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} $tmpgli/.gitolite.rc else prompt "" \ " looks like you're upgrading, and there are some new rc variables @@ -297,15 +300,15 @@ then [It's upto you to figure out how your editor handles 2 filename arguments, switch between them, copy lines, etc ;-)]" - ${VISUAL:-${EDITOR:-vi}} conf/example.gitolite.rc .gitolite.rc + ${VISUAL:-${EDITOR:-vi}} conf/example.gitolite.rc $tmpgli/.gitolite.rc fi else - cp conf/example.gitolite.rc .gitolite.rc - [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} .gitolite.rc + cp conf/example.gitolite.rc $tmpgli/.gitolite.rc + [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} $tmpgli/.gitolite.rc fi # copy the rc across -scp $quiet -P $port .gitolite.rc $user@$host: +scp $quiet -P $port $tmpgli/.gitolite.rc $user@$host: prompt "installing/upgrading..." \ "ignore any 'please edit this file' or 'run this command' type @@ -368,10 +371,10 @@ repo gitolite-admin repo testing RW+ = @all -" > gitolite.conf +" > $tmpgli/gitolite.conf # send the config and the key to the remote -scp $quiet -P $port gitolite.conf $user@$host:$GL_ADMINDIR/conf/ +scp $quiet -P $port $tmpgli/gitolite.conf $user@$host:$GL_ADMINDIR/conf/ scp $quiet -P $port $HOME/.ssh/$admin_name.pub $user@$host:$GL_ADMINDIR/keydir # MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" From 92d5062ad0ef5a1431ef3cdb3d78340f4269c77c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 30 Oct 2009 21:25:06 +0530 Subject: [PATCH 126/637] doc/src: major doc/help text revamp also removed some dead code from compile (pre PTA days) --- README.mkd | 20 ++++---- conf/example.gitolite.rc | 7 +-- doc/0-INSTALL.mkd | 37 +++++++------- doc/0-UPGRADE.mkd | 107 --------------------------------------- doc/1-migrate.mkd | 16 +++--- doc/2-admin.mkd | 13 +++-- doc/3-faq-tips-etc.mkd | 6 +++ doc/4-push-to-admin.mkd | 105 -------------------------------------- doc/5-delegation.mkd | 98 +++++++++-------------------------- src/00-easy-install.sh | 4 ++ src/gl-compile-conf | 17 ------- 11 files changed, 82 insertions(+), 348 deletions(-) delete mode 100644 doc/0-UPGRADE.mkd delete mode 100644 doc/4-push-to-admin.mkd diff --git a/README.mkd b/README.mkd index 81075cb..660e8f2 100644 --- a/README.mkd +++ b/README.mkd @@ -1,10 +1,8 @@ # gitolite -> [IMPORTANT: There is now an "upgrade" document in the "doc" directory; -> please read if upgrading gitolite] - -> [Update 2009-10-10: apart from all the nifty new features, there's now an -> "easy install" script in the src directory. Please see the INSTALL +> [Update 2009-10-28: apart from all the nifty new features, there's now an +> "easy install" script in the src directory. This script can be used to +> install as well as upgrade a gitolite install. Please see the INSTALL > document in the doc directory for details] ---- @@ -64,8 +62,10 @@ deleting a branch (which is really just an extreme form of rewind). I needed something in between allowing anyone to do it (the default) and disabling it completely (`receive.denyNonFastForwards` or `receive.denyDeletes`). -Here're **some more features**. All of them are documented in detail -somewhere in the `doc/` subdirectory. +Here're **some more features**. All of them, and more, are documented in +detail [here][gsdiff]. + +[gsdiff]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#diff * simpler, yet far more powerful, config file syntax, including specifying gitweb/daemon access. You'll need this power if you manage lots of @@ -73,6 +73,8 @@ somewhere in the `doc/` subdirectory. * config file syntax gets checked upfront, and much more thoroughly * if your requirements are still too complex, you can split up the config file and delegate authority over parts of it + * easier to specify gitweb/daemon access, and easier to link gitweb + authorisation with gitolite * more comprehensive logging [aka: management does not think "blame" is just a synonym for "annotate" :-)] * "personal namespace" prefix for each dev @@ -85,8 +87,8 @@ somewhere in the `doc/` subdirectory. Due to the environment in which this was created and the need it fills, I consider this a "security" program, albeit a very modest one. The code is very small and easily reviewable -- the 2 programs that actually control -access when a user logs in total about 200 lines of code (about -80 lines according to "sloccount"). +access when a user logs in total about 220 lines of code (about 90 lines +according to "sloccount"). For the first person to find a security hole in it, defined as allowing a normal user (not the gitolite admin) to read a repo, or write/rewind a ref, diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index 16bab4d..4cda90b 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -57,12 +57,7 @@ $GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y-%m.log"; # -------------------------------------- -# Please DO NOT change the following paths unless you really know what you're -# doing. It'll work for now but it's officially deprecated to have them -# elsewhere from now on, and may break some future features. - -# Anyway, the conf files and keydirs don't grow constantly, (like the logs and -# the repositories do), so I don't think this is a major problem for anyone. +# Please DO NOT change these three paths $GL_CONF="$GL_ADMINDIR/conf/gitolite.conf"; $GL_KEYDIR="$GL_ADMINDIR/keydir"; diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index e6bfff5..c25a69e 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -1,9 +1,12 @@ # installing gitolite This document tells you how to install gitolite. After the install is done, -you may want to see the "admin" document for adding users, repos, etc. +you may want to see the [admin document][admin] for adding users, repos, etc. -There's an easy install script for Linux, and for other Unixes there's a +[admin]: http://github.com/sitaramc/gitolite/blob/pu/doc/2-admin.mkd + +There's an easy install script that requires bash (**strongly** recommended), +but if you have no bash or you're on one of the legacy Unixes there's a slightly more manual process. Both are explained here. In this document: @@ -25,12 +28,12 @@ case. **This script is meant to be run on your workstation, not on the server!** It will take care of all the server side work, *and* get you "push-to-admin" too :-) In short, it does **everything**! -Assumptions: +Assumptions/pre-requisites: * you have a server to host gitolite * git is installed on that server (and so is perl) * you have a userid on that server - * you have ssh-pubkey (password-less) login to that userid + * you have ssh-pubkey (**password-less**) login to that userid * (if you have only password access, run `ssh-keygen -t rsa` to create a new keypair if needed, then run `ssh-copy-id user@host`) * you have a clone or an archive of gitolite somewhere on your workstation @@ -64,26 +67,24 @@ actually doing, I suggest you skip the `-q`. #### disadvantages - * has been tested only with Linux + * need a recent bash ### manual install -If for some reason you cannot use the easy-install method, (for example, -you're on a non-Linux machine), it's not very complicated. Just open the file -`src/00-easy-install.sh` in a nice, syntax coloring, text editor, and follow -the instructions marked "MANUAL" :-) +If you don't have bash, it's not very complicated to do it manually. Just +open the file `src/00-easy-install.sh` in a nice, syntax coloring, text +editor, and follow the instructions marked "MANUAL" :-) ### other notes - * If you edit `~/.gitolite.rc` and change any paths, be sure to keep the - perl syntax -- you *don't* have to know perl to do so, it's fairly easy to - guess in this limited case - - * the config file is (by default) at `~/.gitolite/conf/gitolite.conf`, - though you can change its location in the "rc" file. Edit the file as you - wish. The comments in the example file (`conf/example.conf`) ought to be - clear enough but let me know if not + * if you run `src/00-easy-install.sh` without the `-q` option, you will be + given a chance to edit `~/.gitolite.rc`. You can change any options (such + as paths, for instance), but be sure to keep the perl syntax -- you + *don't* have to know perl to do so, it's fairly easy to guess in this + limited case. ### next steps -See the "admin" document for how to add users, etc. +The last message produced by the easy install script should tell you how to +add users, repos, etc., and you will find more details in the [admin][admin] +document. diff --git a/doc/0-UPGRADE.mkd b/doc/0-UPGRADE.mkd deleted file mode 100644 index 5e242f6..0000000 --- a/doc/0-UPGRADE.mkd +++ /dev/null @@ -1,107 +0,0 @@ -# upgrading gitolite atomically - -Upgrading is done **manually, on the server** (except the last step, which is -on your admin repo clone), even if you installed it using the easy install -script on the client. First, it's not as difficult as an install so you don't -really need a script. Second, you may have customised the "rc" file -(`~/.gitolite.rc` on the server) and I'm reluctant to mess with that in an -automated way. - -### general upgrade notes - -If you follow the steps below, you can make the upgrade "atomic", so you don't -have to do it at a "quiet" time or something. - -1. copy a tar file containing the new version to the server, untar it to some - temp directory and `cd` to it - -2. *prepare* the new version of `~/.gitolite.rc`. It **must** have **all** - the variables defined in `conf/example.gitolite.rc` (the "new" rc file), - because the new versions of the programs will be depending on seeing these - variables. - - However, it must also retain any customisations you made to the **old** - variables. - - So this is what you do: - - * make a copy of `conf/example.gitolite.rc` as `~/glrc.new` - * if your current `~/.gitolite.rc` had any customisations (where you - changed the defaults in some way), edit `~/glrc.new` and make those - same changes there - -3. upgrade the rc file first - - cp ~/glrc.new ~/.gitolite.rc - -4. upgrade the software - - src/install.pl - -5. compile the config once again, in case the *internal* format of the - compiled config file (`$GL_CONF_COMPILED`) has changed. - - To do this, you have to do a "git push" on the client side. That might - require a dummy change (maybe add a blank line somewhere) because - otherwise the push will not happen. - -And you're done. - -### upgrade notes for specific versions - -If any extra steps beyond the generic ones above are needed, they will be -listed here, newest first. - -#### upgrading from 410c9ba - -Between 410c9ba and this version, gitolite managed to make "push to admin" the -default for new installs, but in a much more painless way. If you're -upgrading, you're not forced to use "push to admin", but I'd suggest you: - - * make sure you have password-less (pubkey) login to a command line on your - server - * save your `~/.gitolite.rc`, `keydir/*.pub` and your `conf/gitolite.conf` - files from the server, bring them to your workstation - * then run `src/00-easy-install.sh` on the workstation, as if it were a - fresh install - * when the editor pops up to edit the rc file, delete all the lines in - it and copy them from the saved `~/.gitolite.rc` - * at the end of the script, after the gitolite-admin repo has been - cloned successfully, copy the saved `conf/gitolite.conf` and - `keydir/*.pub` to the clone, then add, commit, and push - -Gitolite also learnt to delegate parts of the config to other users. See -`doc/5-delegation.mkd` for details. - -#### upgrading from 8217ef9 - -Between 8217ef9 and this version, gitolite learnt to handle gitweb/daemon -access. As a result, the rc file acquired a new variable, `$PROJECTS_LIST`, -which you have to set to whatever your gitweb installation requires. - -#### upgrading from 86faae4 - -Between 86faae4 and this version, gitolite had a *major* change in the -*internal* format of the compiled config file. Please do not omit step 5 in -the generic instructions above. - -#### upgrading from 5758f69 - -Between 5758f69 and this version, gitolite learnt to allow "groupnames" for -repos as well. The `conf/example.conf` has been recommented to explain the -syntax but it's really a no-brainer: what you could previously do only for -usernames, you can now do for reponames also. - -#### upgrading from abb4580 - -Two new features (personal branches, and customisable logfile names/locations) -have been added between abb4580 and this version. - - * if you want to enable the personal branches feature, choose one of the - alternative values given for `$PERSONAL` or change it to something you - like; by default it is empty, which disables the feature - - * if you want the log files named or grouped differently, choose one of the - alternative values for `$GL_LOGT`. **Note** that if you choose to put - them in some other directory than the default, you **must** create that - directory (`mkdir`) yourself; gitolite will not do that for you diff --git a/doc/1-migrate.mkd b/doc/1-migrate.mkd index fb0491f..aba1002 100644 --- a/doc/1-migrate.mkd +++ b/doc/1-migrate.mkd @@ -36,8 +36,11 @@ Here's how we migrated my work repos: Now, log off the server and get back to the client: -1. follow instructions to install gitolite; see install document. Make sure - that you **don't** change the default path for `$REPO_BASE`! +[inst]: http://github.com/sitaramc/gitolite/blob/pu/doc/0-INSTALL.mkd + +1. follow instructions to install gitolite; see the [install document][inst]. + Make sure that you **don't** change the default path for `$REPO_BASE` if + you edit the config file! 2. **convert** your gitosis config file. Substitute the path for your gitosis-admin clone in `$GSAC` below, and similarly the path for your @@ -51,10 +54,11 @@ Now, log off the server and get back to the client: cp $GSAC/keydir/* $GLAC/keydir -4. **Important: expand any multi-key files you may have**. See the "faq, - tips, etc" document in the doc directory for an explanation of what - multi-keys are, how gitosis does them and how gitolite does it - differently. +4. **Important: expand any multi-key files you may have**. [Here][mk]'s an + explanation of what multi-keys are, how gitosis does them and how gitolite + does it differently. + +[mk]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#multikeys You can split the keys manually, or use the following code (just copy-paste it into your xterm after "cd"-ing to your gitolite-admin repo diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index 133455a..ed1d361 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -14,9 +14,9 @@ Please read on to see how to do this correctly. #### adding users and repos * ask each user who will get access to send you a public key. See other - sources (for example - [here](http://sitaramc.github.com/0-installing/2-access-gitolite.html#generating_a_public_key)) - for how to do this + sources (for example [here][genpub]) for how to do this + +[genpub]: http://sitaramc.github.com/0-installing/2-access-gitolite.html#generating_a_public_key * rename each public key according to the user's name, with a `.pub` extension, like `sitaram.pub` or `john-smith.pub`. You can also use @@ -40,8 +40,11 @@ so here goes. There's **no special syntax** for this -- just give read permission to a user called `gitweb` or `daemon`! (This also means you can't have a normal user -with either of those two names, but I doubt that's a problem!). See the "faq, -tips, etc" document for easy ways to specify access for multiple repositories. +with either of those two names, but I doubt that's a problem!). See the [faq, +tips, etc][ss] document for easy ways to specify access for multiple +repositories. + +[ss]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#gwd Note that this does **not** install or configure gitweb/daemon -- that is a one-time setup you must do separately. All this does is: diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 0dc851a..64997b1 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -101,6 +101,8 @@ Apart from the big ones listed in the top level README, and subjective ones like "better config file format", there are some small, but significant and concrete, differences from gitosis. + + #### simpler syntax The basic syntax is simpler and cleaner but it goes beyond that: **you can @@ -192,6 +194,8 @@ access control for their own pieces. See [doc/5-delegation.mkd](http://github.com/sitaramc/gitolite/blob/pu/doc/5-delegation.mkd) for details. + + #### easier to specify gitweb/daemon access Which of your repos should be accessible via plain HTTP or the `git://` @@ -308,6 +312,8 @@ The other parts of the log line are the name of the repo, the refname being updated, the user updating it, and the refex pattern (from the config file) that matched, in case you need to debug the config file itself. + + #### one user, many keys I have a laptop and a desktop I need to access the server from. I have diff --git a/doc/4-push-to-admin.mkd b/doc/4-push-to-admin.mkd deleted file mode 100644 index 7e5fc77..0000000 --- a/doc/4-push-to-admin.mkd +++ /dev/null @@ -1,105 +0,0 @@ -# "push to admin" in gitolite - -**WARNING: THIS DOCUMENT IS OBSOLETE. DO NOT USE. IT IS RETAINED ONLY FOR -HISTORICAL PURPOSES**. Gitolite now does "push-to-admin" by default, and does -it very easily and simply by front-loading the ssh problem. See the install -doc for details. - ----- - -Gitosis's default mode of admin is by cloning and pushing the `gitosis-admin` -repo. I call this "push to admin". It's a very cool/cute feature, and I -loved it at first. - -But it's a ***support nightmare***. Half the gitosis angst on `#git` is -because of this feature. Gitolite does not use or endorse this method for -people new to git, or ssh or (worse) both. - -However, ***if*** you know git and ssh really, *really*, well and you know -what you're doing, this is a pretty nice thing to have -- does make life -easier, I admit. So, here is how to make PTA (hey nice acronym, just missing -an "I") work on gitolite as well. - -Note: - - * unlike the rest of gitolite, I can't help you with this unless you - convince me very quickly it's not a layer 8 problem :-) - - * here's a test to see if you should use this feature: after reading this - document, think about how you would switch back and forth between the - normal method and push-to-admin. If you can't immediately see what you - would need to do, please don't use it :-) - -The instructions are presented as shell commands; they should be fairly -obvious. All paths are from the default `~/.gitolite.rc`; if you changed any, -make the same changes below. - ----- - -> **WARNING**: the "compilation" runs via a `post-update` hook. Which, by -> definition, runs *after* the push has successfully completed. As a -> result, a *compilation error* will be visible to the admin doing the `git -> push` but will not otherwise look like an error to client-side git (in -> terms of return codes, scripting, etc., or even the "git gui" if you -> happen to use that for pushing). So be sure to watch out for compile -> error messages on push when you do this. - ----- - -#### server side setup - -1. First, on the server, log on to the `git` userid, add a new repo called - `gitolite-admin` to the config file, give yourself `RW` or `RW+` rights to it, - and "compile": - - cd ~/.gitolite - vim conf/gitolite.conf # add gitolite-admin repo, etc - src/gl-compile-conf - -2. Now, if you look at the "compile" script, it has an *automatic* local commit - inside, just for safety, which kicks in every time you compile. This only - works if it finds a ".git" directory, and it was designed as an "automatic - backup/safety net" type of thing, in case I accidentally deleted the whole - config file or something. - - We need to disable this, because now we have a *better* repo, one that is - manually pushed, and presumably has proper commit messages! - - mv .git .disable.git # yeah it's a hack, sue me - -3. Now the compile command created an empty, bare, "gitolite-admin" repo, so we - seed it with the current contents of the config and keys. (A note on the - `GIT_WORK_TREE` variable: I avoid setting these variables in the normal way - because I always forget to unset them later, and then when I `cd` to other - repos they play havoc with my git commands, so this is how I do it) - - cd ~/repositories/gitolite-admin.git - GIT_WORK_TREE=$HOME/.gitolite git add conf/gitolite.conf keydir - GIT_WORK_TREE=$HOME/.gitolite git commit -am start - -4. Now we have to setup the post-update hook for push-to-admin to work. The hook - should (1) make a forced checkout in the "live" config directory (which is - `~/.gitolite`), and (2) run the compile script. - - `src/pta-hook.sh` has the code you need; just copy it to the right place with - the right name, change the first line if needed, and make it executable: - - # (assuming pwd is still ~/repositories/gitolite-admin.git) - cp ~/.gitolite/src/pta-hook.sh hooks/post-update - - # if you changed $GL_ADMINDIR in ~/.gitolite.conf, then edit the hooks - # and change the first line: - vim hooks/post-update - - chmod +x hooks/post-update - ----- - -#### client side setup - -1. Now get to your workstation, and - - git clone git@server:gitolite-admin.git - - That's it, we're done. You're in gitosis land as far as this is concerned - now. So knock yourself out. Or lock yourself out... :-) diff --git a/doc/5-delegation.mkd b/doc/5-delegation.mkd index 863268f..af73b81 100644 --- a/doc/5-delegation.mkd +++ b/doc/5-delegation.mkd @@ -5,14 +5,13 @@ ### lots of repos, lots of users Gitolite tries to make it easy to manage access to lots of users and repos, -exploiting commonalities wherever possible. (The example in the section -"simpler syntax" in [this page][ml] should give you an idea). As you can see, -it lets you specify bits and pieces of the access control separately -- i.e., -*all* the access specs for a certain repo need not be together; they can be -scattered, which makes it easier to manage the sort of slice and dice needed -in that example. +exploiting commonalities wherever possible. (The example in [this +section][ss] should give you an idea). As you can see, it lets you specify +bits and pieces of the access control separately -- i.e., *all* the access +specs for a certain repo need not be together; they can be scattered, which +makes it easier to manage the sort of slice and dice needed in that example. -[ml]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd +[ss]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#simpler_syntax But eventually the config file will become too big. If you let only one person have control, he could become a bottleneck. If you give it to multiple @@ -30,14 +29,16 @@ Delegation offers a way to do all that. Note that delegated admins cannot create or remove users, not can they define new repos. They can only define access control rules for a set of repos they have been given authority for. -### splitting up the config file into fragments +---- It's easier to show how it all works with an example instead of long descriptions. +### splitting up the set of repos into groups + To start with, recall that gitolite allows you to specify **groups** (of users or repos, same syntax). So the basic idea is that the main config file -(`~/.gitolite/conf/gitolite.conf` by default) will specify some repo groups: +(`conf/gitolite.conf` in your admin repo clone) will specify some repo groups: # group your projects/repos however you want @webbrowser_repos = firefox lynx @@ -47,71 +48,25 @@ or repos, same syntax). So the basic idea is that the main config file # any other config as usual, including access control lines for any of the # above projects or groups -Now just create these files (assuming default `$GL_ADMINDIR` location): +### delegating ownership of groups of repos - ~/.gitolite/conf/fragments/webbrowser_repos.conf - ~/.gitolite/conf/fragments/webserver_repos.conf - ~/.gitolite/conf/fragments/malware_repos.conf - -Within each of those files put in whatever access control rules you want for -the repos that are members of that group. Notice that the basenames of the -files must be exactly the same as the name of the corresponding repo group in -the main config file. - -For instance, `~/.gitolite/conf/fragments/webbrowser_repos.conf` would only -contain access control for firefox and lynx. If it referenced any other repo -(say "storm") those lines would be ignored (and a warning message generated). - -When you run the compile script (`src/gl-compile-conf`), the **net effect is -as if you appended the contents of all the "fragment" files, in alphabetical -order, to the bottom of the main file**. - -(Except of course, while processing a fragment, it will ignore attempts to set -permissions for repos that are not members of the same-named "repo group"). - -And that's basically it, in the simplest usage. - -["But WAIT, there's MORE!"][bwtm] - -[bwtm]: http://en.wikipedia.org/wiki/Ed_Valenti#But_Wait.21_There.27s_More - -### delegating ownership of fragments - -Splitting up the file does help, but there's also that little security issue --- anyone can make any change to any "fragment", unless you (once again) go -back to Unix permissions to keep them separate. - -Fixing that requires using "push-to-admin", which is the default (and only) -method to make config changes in gitosis. - -For a long time, I refused to make "push to admin" the default because it's a -support nightmare. Don't get me wrong -- it's a great feature, and I use it -myself, but the learning curve was too steep to make it *required*. - -But eventually I figured out a way to front-load the darn ssh problem that -everyone on #git seemed to run up against, created a nice "easy install" -script, and made "push to admin" the default. - -So now, you just add these lines to the main config file, assuming Alice is in -charge of all web browser development projects, Bob takes care of web servers, -and Mallory, as [tradition][abe] dictates, is in charge of malware ;-) +Once the repos are grouped, give each person charge of one or more groups. +For example, Alice may be in charge of all web browser development projects, +Bob takes care of web servers, and Mallory, as [tradition][abe] dictates, is +in charge of malware ;-) [abe]: http://en.wikipedia.org/wiki/Alice_and_Bob#List_of_characters - # these 2 lines were probably present already +You do this by adding branches to the `gitolite-admin` repo: + + # the admin repo access was probably like this to start with: repo gitolite-admin RW+ = sitaram - # now add these 3 lines + # now add these lines to the config for the admin repo RW webbrowser_repos = alice RW webserver_repos = bob RW malware_repos = mallory - # you need these lines too -- they define what repos alice/bob/mallory are - # allowed to control - @webbrowser_repos = firefox lynx - @webserver_repos = apache nginx - @malware_repos = conficker storm - As you can see, **for each repo group** you want to delegate authority over, there's a **branch with the same name** in the `gitolite-admin` repo. If you have write access to that branch, you are allowed to define rules for repos in @@ -136,14 +91,7 @@ Here's how to use this in practice: * Alice then commits and pushes this branch to the `gitolite-admin` repo -Naturally, a successful push invokes the post-update hook that you installed -(while setting up [push-to-admin][ptd]). Here's what it does: - - * for each branch, say `br`, of the `gitolite-admin` repo, it checks if - there is a file called `conf/fragments/br.conf` - - * if there is, it extracts it and copies it with the exact same name and - path, into the `$GL_ADMINDIR` directory (`~/.gitolite` by default) - -After that, it runs the compile script, and things work the same as described -in the previous section. +Naturally, a successful push invokes the post-update hook that the admin repo +has, which eventually runs the compile script. The **net effect** is as if +you appended the contents of all the "fragment" files, in alphabetical order, +to the bottom of the main file. diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index db097a7..b8ad9fe 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -283,6 +283,10 @@ then then [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} $tmpgli/.gitolite.rc else + # MANUAL: if you're upgrading, read the instructions below and + # manually make sure your final ~/.gitolite.rc has both your existing + # customisations as well as any new variables that the new version of + # gitolite has introduced prompt "" \ " looks like you're upgrading, and there are some new rc variables that this version is expecting that your old rc file doesn't have. diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 665d1d0..629bded 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -434,20 +434,3 @@ close $newkeys_fh or die "$ATTN close newkeys failed: $!\n"; system("cat $ENV{HOME}/.ssh/authorized_keys > $ENV{HOME}/.ssh/old_authkeys"); system("cat $ENV{HOME}/.ssh/new_authkeys > $ENV{HOME}/.ssh/authorized_keys"); system("rm $ENV{HOME}/.ssh/new_authkeys"); - -# if the gl admin directory (~/.gitolite) is itself a git repo, do an -# autocheckin. nothing fancy; this is a "just in case" type of thing. -wrap_chdir($GL_ADMINDIR); -if (-d ".git") -{ - system("git add -A conf keydir"); # stage all operational data - # and if there are any - if (system("git diff --cached --quiet") ) - { - open my $commit_ph, "|-", "git commit -F -" - or die "$ATTN open commit failed: $!\n"; - print $commit_ph "keydir changed\n\n"; - print $commit_ph `git diff --cached --name-status`; - close $commit_ph or die "$ATTN close commit failed: $!\n"; - } -} From 33305ed8e7cdc3527ecfe4b681ace165885a2cdc Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 5 Nov 2009 13:59:36 +0530 Subject: [PATCH 127/637] doc/1: fix formatting problem on github (local mkd worked fine... weird!) --- doc/1-migrate.mkd | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/1-migrate.mkd b/doc/1-migrate.mkd index aba1002..fb5de34 100644 --- a/doc/1-migrate.mkd +++ b/doc/1-migrate.mkd @@ -58,8 +58,6 @@ Now, log off the server and get back to the client: explanation of what multi-keys are, how gitosis does them and how gitolite does it differently. -[mk]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#multikeys - You can split the keys manually, or use the following code (just copy-paste it into your xterm after "cd"-ing to your gitolite-admin repo clone): @@ -84,3 +82,6 @@ Now, log off the server and get back to the client: the files to make sure this worked properly* 5. Check all your changes to your gitolite-admin clone, commit, and push + +[mk]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#multikeys + From 8aecaa2da24142c2047b397272bde038828238aa Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 3 Nov 2009 14:01:03 +0530 Subject: [PATCH 128/637] doc/6: rename the file, change focus completely --- ...h-setups.mkd => 6-ssh-troubleshooting.mkd} | 133 +++++++++++++----- src/00-easy-install.sh | 2 +- 2 files changed, 95 insertions(+), 40 deletions(-) rename doc/{6-complex-ssh-setups.mkd => 6-ssh-troubleshooting.mkd} (57%) diff --git a/doc/6-complex-ssh-setups.mkd b/doc/6-ssh-troubleshooting.mkd similarity index 57% rename from doc/6-complex-ssh-setups.mkd rename to doc/6-ssh-troubleshooting.mkd index f2292fa..efd5f2b 100644 --- a/doc/6-complex-ssh-setups.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -1,15 +1,17 @@ -# more complex ssh setups +# ssh troubleshooting -What do you need to know in order to create more complex ssh setups (for -instance if you have *two* gitolite servers you are administering)? Once more -unto the breach, here's more ssh magic! +Ssh has always been the biggest troublespot in all this. While gitolite makes +it as easy as possible, you might still run into trouble sometimes. In this document: - * files on client - * files on the server - * sanity checks - * two gitolite servers to manage? + * ssh sanity checks + * explanation + * files on the server + * files on client + * more complex ssh setups + * two gitolite servers to manage? + * further reading ---- @@ -18,31 +20,61 @@ In this document: > shell and gitolite access, so he has **two** pubkeys in play. > Normal users have only one pubkey, since they are only allowed to access -> gitolite itself. They do not need to worry about any of this -> `~/.ssh/config` stuff, and their repo urls are very simple, like: -> `git@my.git.server:reponame.git`. +> gitolite itself. They do not need to worry about any of this stuff, and +> their repo urls are very simple, like: `git@my.git.server:reponame.git`. ---- -### files on client +### ssh sanity checks - * default keypair; used to get shell access to servers. You would have - copied this pubkey to the gitolite server in order to log in without a - password. (On Linux systems you may have used `ssh-copy-id` to do that). - You would have done this *before* you ran the easy install script, because - otherwise easy install won't run! +There are two quick sanity checks you can run: - ~/.ssh/id_rsa - ~/.ssh/id_rsa.pub + * running `ssh gitolite` should get you a list of repos you have rights to + access, as described [here][myrights] - * gitolite keypair; the "sitaram" in this is the 3rd argument to the - `src/00-easy-install.sh` command you ran; the easy install script does the - rest +[myrights]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#myrights - ~/.ssh/sitaram - ~/.ssh/sitaram.pub + * conversely, `ssh git@server` should get you a command line -### files on the server +If one or both of these does not work as expected, do this: + + * first, check that your `~/.ssh` has two public keys, like below: + + $ ls -al ~/.ssh/*.pub + -rw-r--r-- 1 sitaram sitaram 409 2008-04-21 17:42 /home/sitaram/.ssh/id_rsa.pub + -rw-r--r-- 1 sitaram sitaram 409 2009-10-15 16:25 /home/sitaram/.ssh/sitaram.pub + + If it doesn't you have either lost your keys or you're on the wrong + machine. As long as you have password access to the server you can alweys + recover; just pretend you're installing from scratch and start over. + + * next, try running `ssh-add -l`. On my desktop the output looks like this: + + 2048 63:ea:ab:10:d2:4f:88:f4:85:cb:d3:7d:3a:83:37:9a /home/sitaram/.ssh/id_rsa (RSA) + 2048 d7:23:89:12:5f:22:4f:ad:54:7d:7e:f8:f5:2a:e9:13 /home/sitaram/.ssh/sitaram (RSA) + + If you get only one line (typically the top one), you should ssh-add the + other one, using (in my case) `ssh-add ~/.ssh/sitaram`. + + If you get no output, add both of them and check `ssh-add -l` again. + + If this error keeps happening please consider installing [keychain][kch] + or something similar, or add these commands to your bash startup scripts. + +[kch]: http://www.gentoo.org/proj/en/keychain/ + + * Finally, make sure your `~/.ssh/config` has the required `host gitolite` + para (see below for more on this). + +Once these sanity checks have passed, things should be fine. However, if you +still have problems, make sure that the "origin" URL in any clones looks like +`gitolite:reponame.git`, not `git@server:reponame.git`. + +### explanation + +Here's how it all hangs together. + +#### files on the server * the authkeys file; this contains one line containing the pubkey of each user who is permitted to login without a password. @@ -72,12 +104,30 @@ In this document: argument `sitaram`. This is how gitolite is invoked, (and is told the user logging in is "sitaram"). +#### files on client + + * default keypair; used to get shell access to servers. You would have + copied this pubkey to the gitolite server in order to log in without a + password. (On Linux systems you may have used `ssh-copy-id` to do that). + You would have done this *before* you ran the easy install script, because + otherwise easy install won't run! + + ~/.ssh/id_rsa + ~/.ssh/id_rsa.pub + + * gitolite keypair; the "sitaram" in this is the 3rd argument to the + `src/00-easy-install.sh` command you ran; the easy install script does the + rest + + ~/.ssh/sitaram + ~/.ssh/sitaram.pub + * config file; this file has an entry for gitolite access: ~/.ssh/config - Let's step back a bit. Normally, you might expect to access gitolite - repos like this: + To understand why we need that, let's step back a bit. Normally, you + might expect to access gitolite repos like this: ssh://git@server/reponame.git @@ -87,7 +137,7 @@ In this document: control will work. You need to force ssh to use the *other* keypair when performing a git - operation. With just ssh, that would be + operation. With normal ssh, that would be ssh -i ~/.ssh/sitaram git@server @@ -103,8 +153,8 @@ In this document: hostname server identityfile ~/.ssh/sitaram - (The "gitolite" can be anything you want of course; it's like an alias for - all the stuff below it). This ensures that typing + (The "gitolite" can be anything you want of course; it's like a group name + for all the stuff below it). This ensures that typing ssh gitolite @@ -119,17 +169,12 @@ In this document: now works as expected, invoking the special keypair instead of the default one. -### sanity checks +### more complex ssh setups - * `ssh gitolite` should get you the `SSH_ORIGINAL_COMMAND` error. If you - get a command line, something is wrong +What do you need to know in order to create more complex ssh setups (for +instance if you have *two* gitolite servers you are administering)? - * conversely, `ssh git@server` should get you a command line - - * the "origin" URL in any clones should look like `gitolite:reponame.git` - instead of something more complex - -### two gitolite servers to manage? +#### two gitolite servers to manage? * they can have the same key; no harm there (example, sitaram.pub) @@ -146,3 +191,13 @@ In this document: hostname server2 identityfile ~/.ssh/sitaram + * now access one server's repos as `gitolite:reponame.git` and the other + server's repos as `gitolite2:reponame.git`. + +### further reading + +While this focused mostly on the client side ssh, you may also want to read +[this][glb] for a much more detailed explanation of the ssh magic on the +server side. + +[glb]: http://sitaramc.github.com/0-installing/9-gitolite-basics.html#IMPORTANT_overview_of_ssh diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index b8ad9fe..4cc6ff1 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -446,7 +446,7 @@ CONFIG FILE FORMAT: see comments in conf/example.conf in the gitolite source. SSH MAGIC: Remember you (the admin) now have *two* keys to access the server hosting your gitolite setup -- one to get you a command line, and one to get -you gitolite access; see doc/6-complex-ssh-setups.mkd. If you're not using +you gitolite access; see doc/6-ssh-troubleshooting.mkd. If you're not using keychain or some such software, you may have to run this each time you log in: ssh-add ~/.ssh/$admin_name From 31fd24a76c77bc9e9a927c7244d5ca27753dc06e Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 3 Nov 2009 20:24:53 +0530 Subject: [PATCH 129/637] compile: death should be a little louder and clearer :) --- src/gl-compile-conf | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 629bded..3d8177b 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -55,7 +55,8 @@ our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UM # now that this thing *may* be run via "push to admin", any errors have to # grab the admin's ATTENTION so he won't miss them among the other messages a # typical push generates -my $ATTN = "\n\t\t***** ERROR *****\n "; +my $ABRT = "\n\t\t***** ABORTING *****\n "; +my $WARN = "\n\t\t***** WARNING *****\n "; # the common setup module is in the same directory as this running program is my $bindir = $0; @@ -64,7 +65,7 @@ require "$bindir/gitolite.pm"; # ask where the rc file is, get it, and "do" it &where_is_rc(); -die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; +die "$ABRT parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; # add a custom path for git binaries, if specified $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; @@ -113,11 +114,11 @@ umask($REPO_UMASK); # ---------------------------------------------------------------------------- sub wrap_chdir { - chdir($_[0]) or die "$ATTN chdir $_[0] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; + chdir($_[0]) or die "$ABRT chdir $_[0] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; } sub wrap_open { - open (my $fh, $_[0], $_[1]) or die "$ATTN open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n" . + open (my $fh, $_[0], $_[1]) or die "$ABRT open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n" . ( $_[2] || '' ); # suffix custom error message if given return $fh; } @@ -131,10 +132,10 @@ sub expand_list { # we test with the slightly more relaxed pattern here; we'll catch the # "/" in user name thing later; it doesn't affect security anyway - die "$ATTN bad user or repo name $item\n" unless $item =~ $REPONAME_PATT; + die "$ABRT bad user or repo name $item\n" unless $item =~ $REPONAME_PATT; if ($item =~ /^@/) # nested group { - die "$ATTN undefined group $item\n" unless $groups{$item}; + die "$ABRT undefined group $item\n" unless $groups{$item}; # add those names to the list push @new_list, sort keys %{ $groups{$item} }; } @@ -185,7 +186,7 @@ sub parse_conf_file # the group was *first* created by using $fragment as the *value* do { $groups{$1}{$_} ||= $fragment } for ( expand_list( split(' ', $2) ) ); # again, we take the more "relaxed" pattern - die "$ATTN bad group $1\n" unless $1 =~ $REPONAME_PATT; + die "$ABRT bad group $1\n" unless $1 =~ $REPONAME_PATT; } # repo(s) elsif (/^repo (.*)/) @@ -210,7 +211,7 @@ sub parse_conf_file # expand the user list, unless it is just "@all" @users = expand_list ( @users ) unless (@users == 1 and $users[0] eq '@all'); - do { die "$ATTN bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users; + do { die "$ABRT bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users; # ok, we can finally populate the %repos hash for my $repo (@repos) # each repo in the current stanza @@ -250,7 +251,7 @@ sub parse_conf_file } else { - die "$ATTN can't make head or tail of '$_'\n"; + die "$ABRT can't make head or tail of '$_'\n"; } } for my $ig (sort keys %ignored) @@ -275,7 +276,7 @@ for my $fragment_file (glob("conf/fragments/*.conf")) my $compiled_fh = wrap_open( ">", $GL_CONF_COMPILED ); print $compiled_fh Data::Dumper->Dump([\%repos], [qw(*repos)]); -close $compiled_fh or die "$ATTN close compiled-conf failed: $!\n"; +close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n"; # ---------------------------------------------------------------------------- # any new repos to be created? @@ -288,7 +289,7 @@ close $compiled_fh or die "$ATTN close compiled-conf failed: $!\n"; # but it turns out not everyone has "modern" gits :) my $git_version = `git --version`; my ($gv_maj, $gv_min, $gv_patchrel) = ($git_version =~ m/git version (\d+)\.(\d+)\.(\d+)/); -die "$ATTN I can't understand $git_version\n" unless ($gv_maj >= 1); +die "$ABRT I can't understand $git_version\n" unless ($gv_maj >= 1); $git_version = $gv_maj*10000 + $gv_min*100 + $gv_patchrel; # now it's "normalised" my $git_too_old = 0; @@ -300,7 +301,7 @@ for my $repo (keys %repos) { unless (-d "$repo.git") { - system("mkdir", "-p", "$repo.git") and die "$ATTN mkdir $repo.git failed: $!\n"; + system("mkdir", "-p", "$repo.git") and die "$ABRT mkdir $repo.git failed: $!\n"; # erm, note that's "and die" not "or die" as is normal in perl wrap_chdir("$repo.git"); system("git --bare init"); @@ -424,11 +425,11 @@ for my $pubkey (glob("*")) for my $user (sort keys %user_list) { next if $user =~ /^(gitweb|daemon|\@all)$/ or $user_list{$user} eq 'has pubkey'; - print STDERR "$ATTN user $user in config, but has no pubkey!\n"; + print STDERR "$WARN user $user in config, but has no pubkey!\n"; } print $newkeys_fh "# gitolite end\n"; -close $newkeys_fh or die "$ATTN close newkeys failed: $!\n"; +close $newkeys_fh or die "$ABRT close newkeys failed: $!\n"; # all done; overwrite the file (use cat to avoid perm changes) system("cat $ENV{HOME}/.ssh/authorized_keys > $ENV{HOME}/.ssh/old_authkeys"); From 130478ed93448801059d43a333bc99532493b848 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 5 Nov 2009 22:16:18 +0530 Subject: [PATCH 130/637] easy install: handle upgrades specially - "it's an upgrade" is decided by presence of gitolite.conf (not a pubkey) - admin_name optional (and will be ignored if given) for upgrades plus a lot of comments and some minor text changes --- src/00-easy-install.sh | 215 +++++++++++++++++++++++------------------ 1 file changed, 119 insertions(+), 96 deletions(-) diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index 4cc6ff1..5e468d5 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -15,10 +15,18 @@ # command!) set -e +# ---------------------------------------------------------------------- +# temp files +# ---------------------------------------------------------------------- + export tmpgli=tmp-gl-install trap "rm -rf $tmpgli" 0 mkdir -p $tmpgli +# ---------------------------------------------------------------------- +# service functions +# ---------------------------------------------------------------------- + die() { echo "$@"; echo; echo "run $0 again without any arguments for help and tips"; exit 1; } prompt() { # receives two arguments. A short piece of text to be displayed, without @@ -38,15 +46,16 @@ prompt() { } usage() { cat </dev/null -then - prompt " ...adding key to agent..." \ - "you're running ssh-agent. We'll try and do an ssh-add of the - private key we just created, otherwise this key won't get picked up. If - you specified a passphrase in the previous step, you'll get asked for one - now -- type in the same one." + if ssh-add -l &>/dev/null + then + prompt " ...adding key to agent..." \ + "you're running ssh-agent. We'll try and do an ssh-add of the + private key we just created, otherwise this key won't get picked up. If + you specified a passphrase in the previous step, you'll get asked for one + now -- type in the same one." - ssh-add $HOME/.ssh/$admin_name -fi + ssh-add $HOME/.ssh/$admin_name + fi -# MANUAL: you now need to add some lines to the end of your ~/.ssh/config -# file. If the file doesn't exist, create it. Make sure the file is "chmod -# 644". + # MANUAL: you now need to add some lines to the end of your ~/.ssh/config + # file. If the file doesn't exist, create it. Make sure the file is + # "chmod 644". -# The lines to be included look like this: + # The lines to be included look like this: -# host gitolite -# user git -# hostname server -# port 22 -# identityfile ~/.ssh/sitaram + # host gitolite + # user git + # hostname server + # port 22 + # identityfile ~/.ssh/sitaram -echo " + echo " host gitolite - user $user - hostname $host - port $port - identityfile ~/.ssh/$admin_name" > $tmpgli/.gl-stanza + user $user + hostname $host + port $port + identityfile ~/.ssh/$admin_name" > $tmpgli/.gl-stanza -if grep 'host *gitolite' $HOME/.ssh/config &>/dev/null -then - prompt "found gitolite para in ~/.ssh/config; assuming it is correct..." \ - "your \$HOME/.ssh/config already has settings for gitolite. I will - assume they're correct, but if they're not, please edit that file, delete - that paragraph (that line and the following few lines), Ctrl-C, and rerun. + if grep 'host *gitolite' $HOME/.ssh/config &>/dev/null + then + prompt "found gitolite para in ~/.ssh/config; assuming it is correct..." \ + "your \$HOME/.ssh/config already has settings for gitolite. I will + assume they're correct, but if they're not, please edit that file, delete + that paragraph (that line and the following few lines), Ctrl-C, and rerun. - In case you want to check right now (from another terminal) if they're - correct, here's what they are *supposed* to look like: -$(cat $tmpgli/.gl-stanza)" + In case you want to check right now (from another terminal) if they're + correct, here's what they are *supposed* to look like: + $(cat $tmpgli/.gl-stanza)" -else - prompt "creating gitolite para in ~/.ssh/config..." \ - "creating settings for your gitolite access in $HOME/.ssh/config; - these are the lines that will be appended to your ~/.ssh/config: -$(cat $tmpgli/.gl-stanza)" + else + prompt "creating gitolite para in ~/.ssh/config..." \ + "creating settings for your gitolite access in $HOME/.ssh/config; + these are the lines that will be appended to your ~/.ssh/config: + $(cat $tmpgli/.gl-stanza)" - cat $tmpgli/.gl-stanza >> $HOME/.ssh/config - # if the file didn't exist at all, it might have the wrong permissions - chmod 644 $HOME/.ssh/config -fi + cat $tmpgli/.gl-stanza >> $HOME/.ssh/config + # if the file didn't exist at all, it might have the wrong permissions + chmod 644 $HOME/.ssh/config + fi +} # ---------------------------------------------------------------------- -# client side stuff almost done; server side now +# server side # ---------------------------------------------------------------------- # MANUAL: copy the gitolite directories "src", "conf", and "doc" to the @@ -323,42 +344,44 @@ prompt "installing/upgrading..." \ GL_ADMINDIR=$(ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'") REPO_BASE=$( ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$REPO_BASE'") +# determine if this is an upgrade; we decide based on whether a file called +# $GL_ADMINDIR/conf/gitolite.conf exists on the remote side. We can't do this +# till we know the correct value for GL_ADMINDIR +upgrade=0 +if ssh -p $port $user@$host cat $GL_ADMINDIR/conf/gitolite.conf &> /dev/null +then + upgrade=1 + [[ -n $admin_name ]] && echo "looks like an upgrade... not using new key '$admin_name' after all!" +else + [[ -z $admin_name ]] && die "this doesn't look like an upgrade... I need a name for the admin" +fi + # MANUAL: still in the "gitolite-install" directory? Good. Run # "src/install.pl" ssh -p $port $user@$host "cd gitolite-install; src/install.pl $quiet" -# MANUAL: if you're upgrading, just go to your clone of the admin repo, make a -# dummy change, and push. (This assumes that you didn't change the -# admin_name, pubkeys, userids, ports, or whatever, and you ran easy install -# only to upgrade the software). And then you are **done** -- ignore the rest -# of this file for the purposes of an upgrade +# MANUAL: if you're upgrading, run "src/gl-compile-conf" and you're done! -- +# ignore the rest of this file for the purposes of an upgrade -# determine if this is an upgrade; we decide based on whether a pubkey called -# $admin_name.pub exists in $GL_ADMINDIR/keydir on the remote side -upgrade=0 -if ssh -p $port $user@$host cat $GL_ADMINDIR/keydir/$admin_name.pub &> /dev/null -then - prompt "done! +[[ $upgrade == 1 ]] && { + # just compile it, in case the config file's internal format has changed + # and the hooks expect something different + ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf $quiet" + + prompt "" "done! If you forgot the help message you saw when you first ran this, there's a somewhat generic version of it at the end of this file. Try: tail -30 $0 -" \ - "this looks like an upgrade, based on the fact that a file called - $admin_name.pub already exists in $GL_ADMINDIR/keydir on the server. - - Please go to your clone of the admin repo, make a dummy change (like maybe - add a blank line to something), commit, and push. You're done! - - (This assumes that you didn't change the admin_name, pubkeys, userids, - ports, or whatever, and you ran easy install only to upgrade the - software)." - +" exit 0 +} -fi +# ---------------------------------------------------------------------- +# from here on it's install only +# ---------------------------------------------------------------------- # MANUAL: setup the initial config file. Edit $GL_ADMINDIR/conf/gitolite.conf # and add at least the following lines to it: From c4069dd85f9d48d02b70c157cabcc111bbe8337e Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 6 Nov 2009 08:59:32 +0530 Subject: [PATCH 131/637] (please read full commit message) upgrade behaviour changed **upgrades no longer touch the config or the keydir** When you first install gitolite, the easy install script has to do two *distinct* things: * install the software * create and seed the gitolite-admin repo with a minimum config file and the newly created pubkey That's fine for an install, because nothing exists yet anyway. Subsequent invocations of the script should only do the first task (so that gitolite itself can be upgraded), and not attempt to fiddle with the config file and pubkeys. Unfortunately, until now I had not been separating these two activities cleanly enough. For instance, the commit message for 8e47e01 said: IMPORTANT: we assume that $admin_name remains the same in an upgrade -- that's how we detect it is an upgrade! Change that name or his pubkey, and you're toast! Ouch! So now I decided to clean things up. The "Usage" message tells you clearly what to do for an upgrade. Should have been like this from the beginning, but hey we got there eventually :) ---- Code-wise, this is a major refactor of the easy install script. It uses an old forgotten trick to get forward refs for bash functions ;-) and in the process cleans up the flow quite a bit. --- doc/0-INSTALL.mkd | 17 ++ src/00-easy-install.sh | 595 ++++++++++++++++++++++++----------------- 2 files changed, 364 insertions(+), 248 deletions(-) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index c25a69e..c5ef5b1 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -16,6 +16,7 @@ In this document: * advantages over the older install methods * disadvantages * manual install + * upgrades * other notes * next steps @@ -75,6 +76,22 @@ If you don't have bash, it's not very complicated to do it manually. Just open the file `src/00-easy-install.sh` in a nice, syntax coloring, text editor, and follow the instructions marked "MANUAL" :-) +### upgrades + +Upgrading gitolite is easy. + +To upgrade, pull the latest "master" (or other) branch in your gitolite repo +clone, then run the same exact command you ran to do the install, except you +can leave out the last argument. + +And you might want to add a `-q` to speed things up :-) + +Note that this only upgrades the software. Unlike earlier versions, it does +**not** touch the `conf/gitolite.conf` file or the contents of `keydir` in any +way. I decided that it is not possible to **safely** let an upgrade do +something meaningful with them -- fiddling with existing config files (as +opposed to merely creating one which did not exist) is best left to a human. + ### other notes * if you run `src/00-easy-install.sh` without the `-q` option, you will be diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh index 5e468d5..eea3997 100755 --- a/src/00-easy-install.sh +++ b/src/00-easy-install.sh @@ -16,31 +16,82 @@ set -e # ---------------------------------------------------------------------- -# temp files +# bootstrap and main +# ---------------------------------------------------------------------- +if [[ $1 != boot/strap ]] +then + # did someone tell you you can't call functions before they're defined in + # bash? Don't believe everything you hear ;-) + . $0 boot/strap + main "$@" + cleanup + exit 0 +fi + +# ---------------------------------------------------------------------- +# no direct executable statements after this; only functions # ---------------------------------------------------------------------- -export tmpgli=tmp-gl-install -trap "rm -rf $tmpgli" 0 -mkdir -p $tmpgli +main() { + setup_tempdir + + basic_sanity "$@" + + version_info "$@" + + [[ -n $admin_name ]] && setup_local_ssh + + copy_gl # src, conf, etc + + run_install + + [[ $upgrade == 1 ]] && { + # just compile it, in case the config file's internal format has + # changed and the hooks expect something different + ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf $quiet" + + eval "echo \"$v_done\"" + cleanup + exit 0 + } + + initial_conf_key + + setup_pta +} + +# ---------------------------------------------------------------------- +# setup temp files +# ---------------------------------------------------------------------- + +setup_tempdir() { + export tmpgli=tmp-gl-install + trap cleanup 0 + mkdir -p $tmpgli +} + +cleanup() { + rm -rf $tmpgli +} # ---------------------------------------------------------------------- # service functions # ---------------------------------------------------------------------- -die() { echo "$@"; echo; echo "run $0 again without any arguments for help and tips"; exit 1; } +die() { echo "$@"; echo; echo "run $0 without any arguments for help and tips"; cleanup; exit 1; } prompt() { # receives two arguments. A short piece of text to be displayed, without # pausing, in "quiet" mode, and a much longer one to be displayed, *with* # a pause, in normal (verbose) mode [[ $quiet == -q ]] && [[ -n $1 ]] && { - echo "$1" + eval "echo \"$1\"" return } shift echo echo echo ------------------------------------------------------------------------ - echo " $1" + eval "echo \"$1\"" echo read -p '...press enter to continue or Ctrl-C to bail out' } @@ -55,7 +106,8 @@ Usage: $0 [-q] user host [port] admin_name # install - "host" is that server's hostname (or IP address) - "port" is the ssh server port on "host"; optional, defaults to 22 - "admin_name" is *your* name as it should appear in the eventual gitolite - config file (not needed/used for upgrades) + config file. For upgrades (ie., gitolite is already installed on the + server), this argument is not needed, and will be *ignored* if provided. Example usage: $0 git my.git.server sitaram @@ -84,116 +136,96 @@ EOFU # basic sanity / argument checks # ---------------------------------------------------------------------- -# MANUAL: this *must* be run as "src/00-easy-install.sh", not by cd-ing to src -# and then running "./00-easy-install.sh" +basic_sanity() { + # MANUAL: this *must* be run as "src/00-easy-install.sh", not by cd-ing to + # src and then running "./00-easy-install.sh" -[[ $0 =~ ^src/00-easy-install.sh$ ]] || -{ - echo "please cd to the gitolite repo top level directory and run this as - 'src/00-easy-install.sh'" - exit 1; + [[ $0 =~ ^src/00-easy-install.sh$ ]] || + { + die "please cd to the gitolite repo top level directory and run this as + 'src/00-easy-install.sh'" + } + + # are we in quiet mode? + quiet= + [[ "$1" == "-q" ]] && { + quiet=-q + shift + } + + # MANUAL: (info) we'll use "git" as the user, "server" as the host, and + # "sitaram" as the admin_name in example commands shown below, if any + + [[ -z $2 ]] && usage + user=$1 + host=$2 + port=22 + admin_name=$3 + # but if the 3rd arg is a number, that's a port number, and the 4th arg is + # the admin_name + if [[ $3 =~ ^[0-9]+$ ]] + then + port=$3 + admin_name=$4 + fi + + [[ "$user" =~ [^a-zA-Z0-9._-] ]] && die "user '$user' invalid" + [[ -n $admin_name ]] && [[ "$admin_name" =~ [^a-zA-Z0-9._-] ]] && die "admin_name '$admin_name' invalid" + + # MANUAL: make sure you're in the gitolite directory, at the top level. + # The following files should all be visible: + + ls src/gl-auth-command \ + src/gl-compile-conf \ + src/install.pl \ + src/update-hook.pl \ + conf/example.conf \ + conf/example.gitolite.rc >/dev/null || + die "cant find at least some files in gitolite sources/config; aborting" + + # MANUAL: make sure you have password-less (pubkey) auth on the server. + # That is, running "ssh git@server" should log in straight away, without + # asking for a password + + ssh -p $port -o PasswordAuthentication=no $user@$host true || + die "pubkey access didn't work; please set it up using 'ssh-copy-id' or something" } -# are we in quiet mode? -quiet= -[[ "$1" == "-q" ]] && { - quiet=-q - shift -} - -# MANUAL: (info) we'll use "git" as the user, "server" as the host, and -# "sitaram" as the admin_name in example commands shown below, if any - -[[ -z $2 ]] && usage -user=$1 -host=$2 -port=22 -admin_name=$3 -# but if the 3rd arg is a number, that's a port number, and the 4th arg is the -# admin_name -if [[ $3 =~ ^[0-9]+$ ]] -then - port=$3 - admin_name=$4 -fi - -[[ "$user" =~ [^a-zA-Z0-9._-] ]] && die "user '$user' invalid" -[[ -n $admin_name ]] && [[ "$admin_name" =~ [^a-zA-Z0-9._-] ]] && die "admin_name '$admin_name' invalid" - -# MANUAL: make sure you're in the gitolite directory, at the top level. -# The following files should all be visible: - -ls src/gl-auth-command \ - src/gl-compile-conf \ - src/install.pl \ - src/update-hook.pl \ - conf/example.conf \ - conf/example.gitolite.rc >/dev/null || - die "cant find at least some files in gitolite sources/config; aborting" - -# MANUAL: make sure you have password-less (pubkey) auth on the server. That -# is, running "ssh git@server" should log in straight away, without asking for -# a password - -ssh -p $port -o PasswordAuthentication=no $user@$host true || - die "pubkey access didn't work; please set it up using 'ssh-copy-id' or something" - # ---------------------------------------------------------------------- # version info # ---------------------------------------------------------------------- -# MANUAL: if needed, make a note of the version you are upgrading from, and to +version_info() { -# record which version is being sent across; we assume it's HEAD -git describe --tags --long HEAD 2>/dev/null > src/VERSION || echo '(unknown)' > src/VERSION + # MANUAL: if needed, make a note of the version you are upgrading from, and to -# what was the old version there? -export upgrade_details="you are upgrading from \ -$(ssh -p $port $user@$host cat gitolite-install/src/VERSION 2>/dev/null || echo '(unknown)' ) \ -to $(cat src/VERSION)" + # record which version is being sent across; we assume it's HEAD + git describe --tags --long HEAD 2>/dev/null > src/VERSION || echo '(unknown)' > src/VERSION -prompt "$upgrade_details" \ - "$upgrade_details + # what was the old version there? + export upgrade_details="you are upgrading from \ + $(ssh -p $port $user@$host cat gitolite-install/src/VERSION 2>/dev/null || echo '(unknown)' ) \ + to $(cat src/VERSION)" - Note: getting '(unknown)' for the 'from' version should only happen once. - Getting '(unknown)' for the 'to' version means you are probably installing - from a tar file dump, not a real clone. This is not an error but it's - nice to have those version numbers in case you need support. Try and - install from a clone" + prompt "$upgrade_details" "$v_upgrade_details" +} # ---------------------------------------------------------------------- # new keypair, ssh-config para; only on "install" (not upgrade) # ---------------------------------------------------------------------- -[[ -n $admin_name ]] && { +setup_local_ssh() { # MANUAL: create a new key for you as a "gitolite user" (as opposed to you # as the "gitolite admin" who needs to login to the server and get a # command line). For example, "ssh-keygen -t rsa ~/.ssh/sitaram"; this # would create two files in ~/.ssh (sitaram and sitaram.pub) - prompt "setting up keypair..." \ - "the next command will create a new keypair for your gitolite access - - The pubkey will be $HOME/.ssh/$admin_name.pub. You will have to choose a - passphrase or hit enter for none. I recommend not having a passphrase for - now, *especially* if you do not have a passphrase for the key which you - are already using to get server access! - - Add one using 'ssh-keygen -p' after all the setup is done and you've - successfully cloned and pushed the gitolite-admin repo. After that, - install 'keychain' or something similar, and add the following command to - your bashrc (since this is a non-default key) - - ssh-add \$HOME/.ssh/$admin_name - - This makes using passphrases very convenient." + prompt "setting up keypair..." "$v_setting_up_keypair" if [[ -f $HOME/.ssh/$admin_name.pub ]] then - prompt " ...reusing $HOME/.ssh/$admin_name.pub..." \ - "Hmmm... pubkey $HOME/.ssh/$admin_name.pub exists; should I just re-use it? - Be sure you remember the passphrase, if you gave one when you created it!" + prompt " ...reusing $HOME/.ssh/$admin_name.pub..." "$v_reuse_pubkey" else ssh-keygen -t rsa -f $HOME/.ssh/$admin_name || die "ssh-keygen failed for some reason..." fi @@ -210,12 +242,7 @@ prompt "$upgrade_details" \ if ssh-add -l &>/dev/null then - prompt " ...adding key to agent..." \ - "you're running ssh-agent. We'll try and do an ssh-add of the - private key we just created, otherwise this key won't get picked up. If - you specified a passphrase in the previous step, you'll get asked for one - now -- type in the same one." - + prompt " ...adding key to agent..." "$v_ssh_add" ssh-add $HOME/.ssh/$admin_name fi @@ -231,30 +258,17 @@ prompt "$upgrade_details" \ # port 22 # identityfile ~/.ssh/sitaram - echo " -host gitolite - user $user - hostname $host - port $port - identityfile ~/.ssh/$admin_name" > $tmpgli/.gl-stanza + echo "host gitolite + user $user + hostname $host + port $port + identityfile ~/.ssh/$admin_name" > $tmpgli/.gl-stanza if grep 'host *gitolite' $HOME/.ssh/config &>/dev/null then - prompt "found gitolite para in ~/.ssh/config; assuming it is correct..." \ - "your \$HOME/.ssh/config already has settings for gitolite. I will - assume they're correct, but if they're not, please edit that file, delete - that paragraph (that line and the following few lines), Ctrl-C, and rerun. - - In case you want to check right now (from another terminal) if they're - correct, here's what they are *supposed* to look like: - $(cat $tmpgli/.gl-stanza)" - + prompt "found gitolite para in ~/.ssh/config; assuming it is correct..." "$v_found_para" else - prompt "creating gitolite para in ~/.ssh/config..." \ - "creating settings for your gitolite access in $HOME/.ssh/config; - these are the lines that will be appended to your ~/.ssh/config: - $(cat $tmpgli/.gl-stanza)" - + prompt "creating gitolite para in ~/.ssh/config..." "$v_creating_para" cat $tmpgli/.gl-stanza >> $HOME/.ssh/config # if the file didn't exist at all, it might have the wrong permissions chmod 644 $HOME/.ssh/config @@ -265,118 +279,83 @@ host gitolite # server side # ---------------------------------------------------------------------- -# MANUAL: copy the gitolite directories "src", "conf", and "doc" to the -# server, to a directory called (for example) "gitolite-install". You may -# have to create the directory first. +copy_gl() { -ssh -p $port $user@$host mkdir -p gitolite-install -rsync $quiet -e "ssh -p $port" -a src conf doc $user@$host:gitolite-install/ -rm -f src/VERSION + # MANUAL: copy the gitolite directories "src", "conf", and "doc" to the + # server, to a directory called (for example) "gitolite-install". You may + # have to create the directory first. -# MANUAL: now log on to the server (ssh git@server) and get a command line. -# This step is for your convenience; the script does it all from the client -# side but that may be too much typing for manual use ;-) + ssh -p $port $user@$host mkdir -p gitolite-install + rsync $quiet -e "ssh -p $port" -a src conf doc $user@$host:gitolite-install/ + rm -f src/VERSION -# MANUAL: cd to the "gitolite-install" directory where the sources are. Then -# copy conf/example.gitolite.rc as ~/.gitolite.rc and edit it if you wish to -# change any paths. Make a note of the GL_ADMINDIR and REPO_BASE paths; you -# will need them later + # MANUAL: now log on to the server (ssh git@server) and get a command + # line. This step is for your convenience; the script does it all from + # the client side but that may be too much typing for manual use ;-) -prompt "finding/creating gitolite rc..." \ - "the gitolite rc file needs to be edited by hand. The defaults - are sensible, so if you wish, you can just exit the editor. + # MANUAL: cd to the "gitolite-install" directory where the sources are. + # Then copy conf/example.gitolite.rc as ~/.gitolite.rc and edit it if you + # wish to change any paths. Make a note of the GL_ADMINDIR and REPO_BASE + # paths; you will need them later - Otherwise, make any changes you wish and save it. Read the comments to - understand what is what -- the rc file's documentation is inline. + prompt "finding/creating gitolite rc..." "$v_edit_glrc" - Please remember this file will actually be copied to the server, and that - all the paths etc. represent paths on the server!" - -# lets try and get the file from there first -if scp -P $port $user@$host:.gitolite.rc $tmpgli &>/dev/null -then - prompt " ...trying to reuse existing rc" \ - "Oh hey... you already had a '.gitolite.rc' file on the server. - Let's see if we can use that instead of the default one..." - sort < $tmpgli/.gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > $tmpgli/glrc.old - sort < conf/example.gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > $tmpgli/glrc.new - if diff -u $tmpgli/glrc.old $tmpgli/glrc.new + # lets try and get the file from there first + if scp -P $port $user@$host:.gitolite.rc $tmpgli &>/dev/null then - [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} $tmpgli/.gitolite.rc + prompt " ...trying to reuse existing rc" \ + "Oh hey... you already had a '.gitolite.rc' file on the server. +Let's see if we can use that instead of the default one..." + sort < $tmpgli/.gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > $tmpgli/glrc.old + sort < conf/example.gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > $tmpgli/glrc.new + if diff -u $tmpgli/glrc.old $tmpgli/glrc.new + then + [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} $tmpgli/.gitolite.rc + else + # MANUAL: if you're upgrading, read the instructions below and + # manually make sure your final ~/.gitolite.rc has both your existing + # customisations as well as any new variables that the new version of + # gitolite has introduced + prompt "" "$v_upgrade_glrc" + ${VISUAL:-${EDITOR:-vi}} conf/example.gitolite.rc $tmpgli/.gitolite.rc + fi else - # MANUAL: if you're upgrading, read the instructions below and - # manually make sure your final ~/.gitolite.rc has both your existing - # customisations as well as any new variables that the new version of - # gitolite has introduced - prompt "" \ - " looks like you're upgrading, and there are some new rc variables - that this version is expecting that your old rc file doesn't have. - - I'm going to run your editor with two filenames. The first is the - example file from this gitolite version. It will have a block (code - and comments) for each of the variables shown above with a '+' sign. - - The second is your current rc file, the destination. Copy those lines - into this file, preferably *with* the surrounding comments (for - clarity) and save it. - - This is necessary; please dont skip this! - - [It's upto you to figure out how your editor handles 2 filename - arguments, switch between them, copy lines, etc ;-)]" - - ${VISUAL:-${EDITOR:-vi}} conf/example.gitolite.rc $tmpgli/.gitolite.rc + cp conf/example.gitolite.rc $tmpgli/.gitolite.rc + [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} $tmpgli/.gitolite.rc fi -else - cp conf/example.gitolite.rc $tmpgli/.gitolite.rc - [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} $tmpgli/.gitolite.rc -fi -# copy the rc across -scp $quiet -P $port $tmpgli/.gitolite.rc $user@$host: + # copy the rc across + scp $quiet -P $port $tmpgli/.gitolite.rc $user@$host: +} -prompt "installing/upgrading..." \ - "ignore any 'please edit this file' or 'run this command' type - lines in the next set of command outputs coming up. They're only relevant - for a manual install, not this one..." +run_install() { -# extract the GL_ADMINDIR and REPO_BASE locations -GL_ADMINDIR=$(ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'") -REPO_BASE=$( ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$REPO_BASE'") + prompt "installing/upgrading..." "$v_ignore_stuff" -# determine if this is an upgrade; we decide based on whether a file called -# $GL_ADMINDIR/conf/gitolite.conf exists on the remote side. We can't do this -# till we know the correct value for GL_ADMINDIR -upgrade=0 -if ssh -p $port $user@$host cat $GL_ADMINDIR/conf/gitolite.conf &> /dev/null -then - upgrade=1 - [[ -n $admin_name ]] && echo "looks like an upgrade... not using new key '$admin_name' after all!" -else - [[ -z $admin_name ]] && die "this doesn't look like an upgrade... I need a name for the admin" -fi + # extract the GL_ADMINDIR and REPO_BASE locations + GL_ADMINDIR=$(ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'") + REPO_BASE=$( ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$REPO_BASE'") -# MANUAL: still in the "gitolite-install" directory? Good. Run -# "src/install.pl" + # determine if this is an upgrade; we decide based on whether a file + # called $GL_ADMINDIR/conf/gitolite.conf exists on the remote side. We + # can't do this till we know the correct value for GL_ADMINDIR + upgrade=0 + if ssh -p $port $user@$host cat $GL_ADMINDIR/conf/gitolite.conf &> /dev/null + then + upgrade=1 + [[ -n $admin_name ]] && echo -e "\n *** WARNING ***: looks like an upgrade... ignoring argument '$admin_name'" + else + [[ -z $admin_name ]] && die " *** ERROR ***: doesn't look like an upgrade, so I need a name for the admin" + fi -ssh -p $port $user@$host "cd gitolite-install; src/install.pl $quiet" + # MANUAL: still in the "gitolite-install" directory? Good. Run + # "src/install.pl" -# MANUAL: if you're upgrading, run "src/gl-compile-conf" and you're done! -- -# ignore the rest of this file for the purposes of an upgrade + ssh -p $port $user@$host "cd gitolite-install; src/install.pl $quiet" -[[ $upgrade == 1 ]] && { - # just compile it, in case the config file's internal format has changed - # and the hooks expect something different - ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf $quiet" + # MANUAL: if you're upgrading, run "src/gl-compile-conf" and you're done! + # -- ignore the rest of this file for the purposes of an upgrade - prompt "" "done! - - If you forgot the help message you saw when you first ran this, there's a - somewhat generic version of it at the end of this file. Try: - - tail -30 $0 -" - exit 0 } # ---------------------------------------------------------------------- @@ -389,7 +368,8 @@ ssh -p $port $user@$host "cd gitolite-install; src/install.pl $quiet" # repo gitolite-admin # RW+ = sitaram -echo "#gitolite conf +initial_conf_key() { + echo "#gitolite conf # please see conf/example.conf for details on syntax and features repo gitolite-admin @@ -400,57 +380,176 @@ repo testing " > $tmpgli/gitolite.conf -# send the config and the key to the remote -scp $quiet -P $port $tmpgli/gitolite.conf $user@$host:$GL_ADMINDIR/conf/ -scp $quiet -P $port $HOME/.ssh/$admin_name.pub $user@$host:$GL_ADMINDIR/keydir + # send the config and the key to the remote + scp $quiet -P $port $tmpgli/gitolite.conf $user@$host:$GL_ADMINDIR/conf/ + scp $quiet -P $port $HOME/.ssh/$admin_name.pub $user@$host:$GL_ADMINDIR/keydir -# MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" -ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf $quiet" + # MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" + ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf $quiet" +} # ---------------------------------------------------------------------- # hey lets go the whole hog on this; setup push-to-admin! # ---------------------------------------------------------------------- -# MANUAL: you have to now make the first commit in the admin repo. This is -# a little more complex, so read carefully and substitute the correct paths. -# What you have to do is: +setup_pta() { -# cd $REPO_BASE/gitolite-admin.git -# GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir -# GIT_WORK_TREE=$GL_ADMINDIR git commit -am start + # MANUAL: you have to now make the first commit in the admin repo. This + # is a little more complex, so read carefully and substitute the correct + # paths. What you have to do is: -# Substitute $GL_ADMINDIR and $REPO_BASE appropriately. Note there is no -# space around the "=" in the second and third lines. + # cd $REPO_BASE/gitolite-admin.git + # GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir + # GIT_WORK_TREE=$GL_ADMINDIR git commit -am start -echo "cd $REPO_BASE/gitolite-admin.git + # Substitute $GL_ADMINDIR and $REPO_BASE appropriately. Note there is no + # space around the "=" in the second and third lines. + + echo "cd $REPO_BASE/gitolite-admin.git GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir GIT_WORK_TREE=$GL_ADMINDIR git commit -am start --allow-empty " | ssh -p $port $user@$host -# MANUAL: now that the admin repo is created, you have to set the hooks -# properly. The install program does this. So cd back to the -# "gitolite-install" directory and run "src/install.pl" + # MANUAL: now that the admin repo is created, you have to set the hooks + # properly. The install program does this. So cd back to the + # "gitolite-install" directory and run "src/install.pl" -ssh -p $port $user@$host "cd gitolite-install; src/install.pl $quiet" + ssh -p $port $user@$host "cd gitolite-install; src/install.pl $quiet" -# MANUAL: you're done! Log out of the server, come back to your workstation, -# and clone the admin repo using "git clone gitolite:gitolite-admin.git", or -# pull once again if you already have a clone + # MANUAL: you're done! Log out of the server, come back to your + # workstation, and clone the admin repo using "git clone + # gitolite:gitolite-admin.git", or pull once again if you already have a + # clone -prompt "cloning gitolite-admin repo..." \ -"now we will clone the gitolite-admin repo to your workstation - and see if it all hangs together. We'll do this in your \$HOME for now, - and you can move it elsewhere later if you wish to." + prompt "cloning gitolite-admin repo..." "$v_cloning" -cd $HOME -git clone gitolite:gitolite-admin.git + cleanup + cd $HOME + git clone gitolite:gitolite-admin.git -# MANUAL: be sure to read the message below; this applies to you too... + # MANUAL: be sure to read the message below; this applies to you too... -echo -echo -echo ------------------------------------------------------------------------ -echo " + echo + echo + echo --------------------------------------------------------------- + eval "echo \"$tail\"" +} + +# ---------------------------------------------------------------------- +# prompt strings +# ---------------------------------------------------------------------- + +v_upgrade_details=" +\$upgrade_details + +Note: getting '(unknown)' for the 'from' version should only happen once. +Getting '(unknown)' for the 'to' version means you are probably installing +from a tar file dump, not a real clone. This is not an error but it's nice to +have those version numbers in case you need support. Try and install from a +clone +" + +v_setting_up_keypair=" +the next command will create a new keypair for your gitolite access + +The pubkey will be \$HOME/.ssh/\$admin_name.pub. You will have to choose a +passphrase or hit enter for none. I recommend not having a passphrase for +now, *especially* if you do not have a passphrase for the key which you are +already using to get server access! + +Add one using 'ssh-keygen -p' after all the setup is done and you've +successfully cloned and pushed the gitolite-admin repo. After that, install +'keychain' or something similar, and add the following command to your bashrc +(since this is a non-default key) + + ssh-add \\\$HOME/.ssh/\$admin_name + +This makes using passphrases very convenient. +" + +v_reuse_pubkey=" +Hmmm... pubkey \$HOME/.ssh/\$admin_name.pub exists; should I just re-use it? +Be sure you remember the passphrase, if you gave one when you created it! +" + +v_ssh_add=" +you're running ssh-agent. We'll try and do an ssh-add of the +private key we just created, otherwise this key won't get picked up. If +you specified a passphrase in the previous step, you'll get asked for one +now -- type in the same one. +" + +v_found_para=" +your \\\$HOME/.ssh/config already has settings for gitolite. I will assume +they're correct, but if they're not, please edit that file, delete that +paragraph (that line and the following few lines), Ctrl-C, and rerun. + +In case you want to check right now (from another terminal) if they're +correct, here's what they are *supposed* to look like: + +\$(cat \$tmpgli/.gl-stanza) + +" + +v_creating_para=" +creating settings for your gitolite access in \$HOME/.ssh/config; +these are the lines that will be appended to your ~/.ssh/config: + +\$(cat \$tmpgli/.gl-stanza) + +" + +v_edit_glrc=" +the gitolite rc file needs to be edited by hand. The defaults are sensible, +so if you wish, you can just exit the editor. + +Otherwise, make any changes you wish and save it. Read the comments to +understand what is what -- the rc file's documentation is inline. + +Please remember this file will actually be copied to the server, and that all +the paths etc. represent paths on the server! +" + +v_upgrade_glrc=" +looks like you're upgrading, and there are some new rc variables that this +version is expecting that your old rc file doesn't have. + +I'm going to run your editor with two filenames. The first is the example +file from this gitolite version. It will have a block (code and comments) for +each of the variables shown above with a '+' sign. + +The second is your current rc file, the destination. Copy those lines into +this file, preferably *with* the surrounding comments (for clarity) and save +it. + +This is necessary; please dont skip this! + +[It's upto you to figure out how your editor handles 2 filename arguments, +switch between them, copy lines, etc ;-)] +" + +v_ignore_stuff=" +ignore any 'please edit this file' or 'run this command' type lines in the +next set of command outputs coming up. They're only relevant for a manual +install, not this one... +" + +v_done=" +done! + +If you forgot the help message you saw when you first ran this, there's a +somewhat generic version of it at the end of this file. Try: + + tail -31 \$0 +" + +v_cloning=" +now we will clone the gitolite-admin repo to your workstation and see if it +all hangs together. We'll do this in your \\\$HOME for now, and you can move +it elsewhere later if you wish to. +" + +tail=" All done! The admin repo is currently cloned at ~/gitolite-admin; you can clone it @@ -472,7 +571,7 @@ hosting your gitolite setup -- one to get you a command line, and one to get you gitolite access; see doc/6-ssh-troubleshooting.mkd. If you're not using keychain or some such software, you may have to run this each time you log in: - ssh-add ~/.ssh/$admin_name + ssh-add ~/.ssh/\$admin_name URLS: *Your* URL for cloning any repo on this server will be @@ -480,5 +579,5 @@ URLS: *Your* URL for cloning any repo on this server will be *Other* users you set up will have to use - $user@$host:reponame.git + \$user@\$host:reponame.git " From 4d9c064a7a4569df18fb04bdcf278acac923817a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 7 Nov 2009 10:43:52 +0530 Subject: [PATCH 132/637] new program for emergency addkey; run without args for usage --- src/99-emergency-addkey.sh | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100755 src/99-emergency-addkey.sh diff --git a/src/99-emergency-addkey.sh b/src/99-emergency-addkey.sh new file mode 100755 index 0000000..d488798 --- /dev/null +++ b/src/99-emergency-addkey.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# what/why: re-establish gitolite admin access when admin key(s) lost +# where: on server (NOT client!) + +# pre-req: shell access to the server (even with password is fine) +# pre-work: - make yourself a new keypair on your workstation +# - copy the pubkey and this script to the server + +# usage: $0 admin_name client_host_shortname pubkeyfile +# notes: - admin_name should already have RW or RW+ access to the +# gitolite-admin repo +# - client_host_shortname is any simple word; see example below + +# WARNING: ABSOLUTELY NO ARGUMENT CHECKING DONE +# WARNING: NEWER GITS ONLY ON SERVER SIDE (for now) + +# example: $0 sitaram laptop /tmp/sitaram.pub +# result: a new keyfile named sitaram@laptop.pub would be added + +# ENDHELP + +[[ -z $1 ]] && { perl -pe "s(\\\$0)($0); last if /ENDHELP/" < $0; exit 1; } + +set -e + +cd +REPO_BASE=$( perl -e 'do ".gitolite.rc"; print $REPO_BASE' ) +GL_ADMINDIR=$(perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR') + +cd; cd $GL_ADMINDIR/keydir; pwd +cp -v $3 $1@$2.pub + +cd; cd $REPO_BASE/gitolite-admin.git; pwd +# XXX FIXME TODO -- fix this to work with older gits also +GIT_WORK_TREE=$GL_ADMINDIR git add keydir +GIT_WORK_TREE=$GL_ADMINDIR git commit -m "emergency add $1@$2.pub" + +cd $GL_ADMINDIR +src/gl-compile-conf From be972d04d0f714c2e5f5d007b61a3aef743bcd25 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 12 Nov 2009 07:17:37 +0530 Subject: [PATCH 133/637] doc/6: added two keys explanation and workaround --- doc/6-ssh-troubleshooting.mkd | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/doc/6-ssh-troubleshooting.mkd b/doc/6-ssh-troubleshooting.mkd index efd5f2b..2bc04a0 100644 --- a/doc/6-ssh-troubleshooting.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -9,6 +9,7 @@ In this document: * explanation * files on the server * files on client + * why two keys on client * more complex ssh setups * two gitolite servers to manage? * further reading @@ -169,6 +170,70 @@ Here's how it all hangs together. now works as expected, invoking the special keypair instead of the default one. + + +#### why two keys on client + +Why do I (the admin) need two **different** keypairs? + +There are two types of access the admin will make to the server: a normal +login, to get a shell prompt, and gitolite access (clone/fetch/push etc). The +first access needs an authkeys line *without* any "command=" restrictions, +while the second requires a line *with* such a restriction. + +And we can't use the same key for both because there is no way to disambiguate +them; the ssh server will always (*always*) pick the first one in sequence +when the key is offered by the ssh client. + +So the next question is usually "I have other ways to get a shell on that +account, so why do I need a key for shell access at all?" + +The answer to this is that the "easy install" script, being written for the +most general case, needs shell access via ssh to do its stuff. + +If you really, really, want to get rid of the extra key, here's a transcript +that should have enough info to get you going (but it helps to know ssh well): + + * on "sitaram" user, on my workstation + + cd ~/.ssh + cp id_rsa sitaram + cp id_rsa.pub sitaram.pub + cd ~/gitolite-clone + src/00-easy-install.sh -q git my.git.server sitaram + + that last command produces something like the following: + + you are upgrading from (unknown) to v0.80-6-gdde8c4e + setting up keypair... + ...reusing /home/sitaram/.ssh/sitaram.pub... + creating gitolite para in ~/.ssh/config... + finding/creating gitolite rc... + installing/upgrading... + Pseudo-terminal will not be allocated because stdin is not a terminal. + [master (root-commit) e717a89] start + 2 files changed, 11 insertions(+), 0 deletions(-) + create mode 100644 conf/gitolite.conf + create mode 100644 keydir/sitaram.pub + cloning gitolite-admin repo... + Initialized empty Git repository in /home/sitaram/gitolite-admin/.git/ + fatal: 'gitolite-admin.git' does not appear to be a git repository + fatal: The remote end hung up unexpectedly + + notice that the final step (the clone of the newly created gitolite-admin + repo) failed, as expected + + * now log on to the git hosting account (`git@my.git.server` in this + example), edit `~/.ssh/authorized_keys`, and delete the line with the + first occurrence of your key (this should be *before* the `# gitolite + start` line) + + * now go back to your workstation and + + git clone git@my.git.server:gitolite-admin + +That should do it. + ### more complex ssh setups What do you need to know in order to create more complex ssh setups (for From e81264d1001afcf8847012a28919c5e2b7964fcd Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 12 Nov 2009 14:49:39 +0530 Subject: [PATCH 134/637] compile: added repo descriptions example line in config file: gitolite = "fast, secure, access control for git in a corporate environment" --- src/gl-compile-conf | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 3d8177b..3306b0d 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -106,6 +106,9 @@ my %rurp_seen = (); # catch usernames<->pubkeys mismatches; search for "lint" below my %user_list = (); +# gitweb descriptions, plain text, keyed by repo +my %desc = (); + # set the umask before creating any files umask($REPO_UMASK); @@ -249,6 +252,15 @@ sub parse_conf_file } } } + # very simple syntax for the gitweb description of repo + elsif (/^(\S+) = "(.*)"$/) + { + my ($repo, $desc) = ($1, $2); + die "$ABRT bad repo name $repo\n" unless $repo =~ $REPONAME_PATT; + die "$WARN $fragment attempting to set description for $repo\n" if + $fragment ne 'master' and $fragment ne $repo and ($groups{"\@$fragment"}{$repo} || '') ne 'master'; + $desc{$repo} = $desc; + } else { die "$ABRT can't make head or tail of '$_'\n"; @@ -359,13 +371,17 @@ for my $repo (sort keys %repos) { # ...then gitwebs for my $repo (sort keys %repos) { - if ($repos{$repo}{'R'}{'gitweb'}) { + my $desc_file = "$repo.git/description"; + # note: having a description also counts as enabling gitweb + if ($repos{$repo}{'R'}{'gitweb'} or $desc{$repo}) { unless ($projlist{"$repo.git"}) { # not in the old list; add it to the new one $projlist{"$repo.git"} = 1; $projlist_changed = 1; print "gitweb add $repo.git\n"; } + # add the description file; no messages to user or error checking :) + open(DESC, ">", $desc_file) and print DESC "$desc{$repo}\n" and close DESC; } else { if ($projlist{"$repo.git"}) { # delete it from new list @@ -373,6 +389,8 @@ for my $repo (sort keys %repos) { $projlist_changed = 1; print "gitweb del $repo.git\n"; } + # delete the description file; no messages to user or error checking :) + unlink $desc_file; } } From 76520693a336f3a5cd3e56de4ef53189f1b57933 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 12 Nov 2009 18:53:49 +0530 Subject: [PATCH 135/637] doc/2: add docs for gitweb description, plus some minor cleanup --- doc/2-admin.mkd | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index ed1d361..240b647 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -38,22 +38,27 @@ This is a feature that I personally do not use (corporate environments don't like unauthenticated access of any kind to any repo!), but someone wanted it, so here goes. -There's **no special syntax** for this -- just give read permission to a user -called `gitweb` or `daemon`! (This also means you can't have a normal user -with either of those two names, but I doubt that's a problem!). See the [faq, -tips, etc][ss] document for easy ways to specify access for multiple -repositories. +To make a repo or repo group accessible via "git daemon", just give read +permission to the special user "daemon". See the [faq, tips, etc][ss] +document for easy ways to specify access for multiple repositories. [ss]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#gwd -Note that this does **not** install or configure gitweb/daemon -- that is a -one-time setup you must do separately. All this does is: +There's a special user called "gitweb" also, which works the same way. +However, setting a description for the project also enables gitweb permissions +so you may as well use that method and kill two birds with one stone, like so: + gitolite = "fast, secure, access control for git in a corporate environment" + +Note that gitolite does **not** install or configure gitweb/daemon -- that is +a one-time setup you must do separately. All this does is: + + * for daemon, create the file `git-daemon-export-ok` in the repository * for gitweb, add the repo to the list of projects to be served by gitweb (see the config file variable `$PROJECTS_LIST`, which should have the same value you specified for `$projects_list` when setting up gitweb) - * for daemon, create the file `git-daemon-export-ok` in the repository + * put the description, if given, in `$repo/description` The "compile" script will keep these files consistent with the config settings --- this includes removing such settings if you remove "read" permissions for -the special usernames. +-- this includes removing such settings/files if you remove "read" permissions +for the special usernames or remove the description line. From 012d4b1fb62fdb6113842d0f86c9e4aa051fe55c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 12 Nov 2009 18:28:08 +0530 Subject: [PATCH 136/637] example conf: total reformat/refactor, include gitweb description --- conf/example.conf | 167 ++++++++++++++++++++++++++-------------------- 1 file changed, 93 insertions(+), 74 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index 81b6148..752c279 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -1,9 +1,11 @@ # example conf file for gitolite +# ---------------------------------------------------------------------------- # overall syntax: -# - everything in this is space-separated; no commas, semicolons, etc +# - everything is space-separated; no commas, semicolons, etc (except in +# the description string for gitweb) # - comments in the normal shell-ish style; no surprises there -# - there are no continuation lines of any kind +# - there are NO continuation lines of any kind # - user/repo names as simple as possible # (usernames: only alphanumerics, ".", "_", "-"; # reponames: same, plus "/", but not at the start) @@ -14,56 +16,79 @@ # - specify who can push a branch/tag # - specify who can rewind a branch/rewrite a tag -# convenience: allow specifying the access control in bits and pieces, even if -# they overlap. Keeps the config file smaller and saner. See the example in -# the "faq, tips, etc" document - # ---------------------------------------------------------------------------- -# LISTS + +# GROUPS +# ------ # syntax: -# @listname = name [...] -# lists can be used as shorthand for usernames as well as reponames +# @groupname = [one or more names] -# a list is equivalent to typing out all the right hand side names, so why do -# we need lists at all? (1) to be able to reuse the same set of usernames in -# the paras for different repos, (2) to keep the lines short, because lists -# accumulate, like squid ACLs, so you can say: +# groups let you club names together for convenience in specifying +# permissions. A group is simply expanded to whatever names are on the right +# hand side when it is actually used -# @cust_A = cust1 cust2 -# @cust_A = cust99 + # you can have a group of people... +@staff = sitaram some_dev another-dev -# and this is the same as listing all three on the same line + # ...or a group of repos +@oss_repos = gitolite linux git perl rakudo entrans vkc -# you can nest groups, but not recursively of course! + # even sliced and diced differently +@admins = sitaram admin2 + # notice that sitaram is in 2 groups (staff and admins) -# @interns = indy james -# @staff = bob @interns + # if you repeat a group name in another definition line, the + # new ones get added to the old ones (they accumulate) +@staff = au.thor + # so now "@staff" expands to all 4 names -# @staff = me alice -# @secret_staff = bruce whitfield martin + # groups can include other groups (but not recursively) +@interns = indy james +@staff = bob @interns + # "@staff" expands to 7 names now -# @pubrepos = linux git - -# @privrepos = supersecretrepo anothersecretrepo - -# ---------------------------------------------------------------------------- -# REPOS, REFS, and PERMISSIONS +# REPO AND BRANCH PERMISSIONS +# --------------------------- # syntax: -# repo [one or more repos] -# (R|RW|RW+) [zero or more refexes] = [one or more users] +# start line: +# repo [one or more repos and/or repo groups] +# followed by one or more permissions lines: +# (R|RW|RW+) [zero or more refexes] = [one or more users] -# notes: +# there are 3 types of permissions: R, RW, and RW+. The "+" means permission +# to "rewind" (force push a non-fast forward to) a branch -# - the reponame is a simple name. Do not add the ".git" extension -- -# that will be added by the program when the actual repo is created +# how permissions are matched: +# - user, repo, and access (W or +) are known. For that combination, if +# any of the refexes match the refname being updated, the push succeeds. +# If none of them match, it fails -# - RW+ means non-ff push is allowed -# - you can't write just "W" or "+"; it has to be R, or RW, or RW+ +# what's a refex? a regex to match against the ref being updated (get it?) -# - a refex is a regex that matches a ref :-) If you see the examples -# below you'll get it easy enough +# BASIC PERMISSIONS (repo level only; apply to all branches/tags in repo) + + # most important rule of all -- specify who can make changes + # to *this* file take effect +repo gitolite-admin + RW+ = @admins + + # "@all" is a special, predefined, group name +repo testing + RW+ = @all + + # this repo is visible to staff but only sitaram can write to it +repo gitolite + R = @staff + RW+ = sitaram + + # you can split up access rules for a repo as convenient + # (notice that @oss_repos contains gitolite also) +repo @oss_repos + R = @all + +# ADVANCED PERMISSIONS USING REFEXES # - refexes are specified in perl regex syntax # - if no refex appears, the rule applies to all refs in that repo @@ -71,53 +96,47 @@ # with "refs/" (so tags have to be explicitly named as # refs/tags/pattern) -# - the list of users or repos can inlude any group name defined earlier -# - "@all" is a special, predefined, groupname that means "all users" -# (there is no corresponding shortcut for all repos) + # here's the example from + # Documentation/howto/update-hook-example.txt: -# matching: + # 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 -# - user, repo, and access (W or +) are known. For that combination, if -# any of the refexes match the refname being updated, the push succeeds. -# If none of them match, it fails + # and here're the equivalent gitolite refexes +repo git + RW master = junio + RW+ pu = junio + RW cogito$ = pasky + RW bw/ = linus + RW tmp/ = @all + RW refs/tags/v[0-9] = junio -# anyone can play in the sandbox, including making non-fastforward commits -# (that's what the "+" means) -# repo sandbox -# RW+ = @all +# GITWEB AND DAEMON STUFF +# ----------------------- -# my repo and alice's repo have the same memberships and access, so we just -# put them both in the same stanza +# No specific syntax for gitweb and daemon access; just make the repo readable +# ("R" access) to the special users "gitweb" and "daemon" -# repo myrepo alicerepo -# RW+ = me alice -# R = bob eve + # make "@oss_repos" (all 7 of them!) accessible via git daemon +repo @oss_repos + R = daemon -# this repo is visible to customers from company A but they can't write to it + # make the two *large* repos accessible via gitweb +repo linux perl + R = gitweb -# repo cust_A_repo -# R = @cust_A -# RW = @staff +# GITWEB DESCRIPTION LINE -# idea for the tags syntax shamelessly copied from git.git -# Documentation/howto/update-hook-example.txt :) +# syntax: +# reponame = "some description string in double quotes" -# repo @privrepos thirdsecretrepo -# RW+ pu = bruce -# RW master next = bruce -# RW refs/tags/v[0-9].* = bruce -# RW refs/tags/ss/ = @secret_staff -# RW tmp/.* = @secret_staff -# R = @secret_staff +# note: setting a description also gives gitweb access; you do not have to +# give gitweb access as described above if you're specifying a description -# ---------------------------------------------------------------------------- -# GITWEB AND DAEMON CONTROL - -# there is no special syntax for this. If a repo gives read permissions to -# the special user "gitweb" or "daemon", the corresponding changes are made -# when you compile; see "faq, tips, etc" document for details. - -# this means you cannot have a real user called "gitweb" or "daemon" but I -# don't think that is a problem :-) +gitolite = "fast, secure, access control for git in a corporate environment" From 448c0d37baec8fac4508af06f211e1e02862c363 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 12 Nov 2009 20:45:49 +0530 Subject: [PATCH 137/637] compile: writing description file should be conditional --- src/gl-compile-conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 3306b0d..ab8774f 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -381,7 +381,7 @@ for my $repo (sort keys %repos) { print "gitweb add $repo.git\n"; } # add the description file; no messages to user or error checking :) - open(DESC, ">", $desc_file) and print DESC "$desc{$repo}\n" and close DESC; + $desc{$repo} and open(DESC, ">", $desc_file) and print DESC "$desc{$repo}\n" and close DESC; } else { if ($projlist{"$repo.git"}) { # delete it from new list From c54d3eabbc4ffe448ee38f91a6431784d7303d3e Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 13 Nov 2009 05:03:09 +0530 Subject: [PATCH 138/637] all src: (please read full commit message): allow local admin-defined hooks You can now add your own hooks into src/hooks/ and they get propagated along with the update hook that is present there now. Please read the new section in the admin document, and make sure you understand the security implications of accidentally fiddling with the "update" script. This also prompted a major rename spree of all the files to be consistent, etc. Plus people said that the .sh and .pl suffixes should be avoided (and I was feeling the same way). I've also been inconsistent with that "gl-" prefix, so I cleaned that up, and the 00- and 99- were also funny animals. Time to get all this cleaned up before we get 1.0 :) So these are the changes, in case you're looking at just the commit message and not the diffstat: src/pta-hook.sh -> src/ga-post-update-hook src/conf-convert.pl -> src/gl-conf-convert src/00-easy-install.sh -> src/gl-easy-install src/99-emergency-addkey.sh -> src/gl-emergency-addkey src/install.pl -> src/gl-install src/update-hook.pl -> src/hooks/update --- doc/0-INSTALL.mkd | 8 ++--- doc/1-migrate.mkd | 2 +- doc/2-admin.mkd | 23 ++++++++++++++ doc/6-ssh-troubleshooting.mkd | 4 +-- src/{pta-hook.sh => ga-post-update-hook} | 0 src/gl-compile-conf | 3 +- src/{conf-convert.pl => gl-conf-convert} | 0 src/{00-easy-install.sh => gl-easy-install} | 30 ++++++++++--------- ...mergency-addkey.sh => gl-emergency-addkey} | 0 src/{install.pl => gl-install} | 14 ++++++--- src/{update-hook.pl => hooks/update} | 0 11 files changed, 58 insertions(+), 26 deletions(-) rename src/{pta-hook.sh => ga-post-update-hook} (100%) rename src/{conf-convert.pl => gl-conf-convert} (100%) rename src/{00-easy-install.sh => gl-easy-install} (96%) rename src/{99-emergency-addkey.sh => gl-emergency-addkey} (100%) rename src/{install.pl => gl-install} (82%) rename src/{update-hook.pl => hooks/update} (100%) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index c5ef5b1..984f040 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -39,7 +39,7 @@ Assumptions/pre-requisites: new keypair if needed, then run `ssh-copy-id user@host`) * you have a clone or an archive of gitolite somewhere on your workstation -If so, just `cd` to that clone and run `src/00-easy-install.sh` and follow the +If so, just `cd` to that clone and run `src/gl-easy-install` and follow the prompts! (Running it without any arguments shows you usage plus other useful info). @@ -47,7 +47,7 @@ info). A typical run for me is: - src/00-easy-install.sh -q git my.git.server sitaram + src/gl-easy-install -q git my.git.server sitaram `-q` stands for "quiet" mode -- very minimal output, no verbose descriptions of what it is going to do, and no pauses unless absolutely needed. However, @@ -73,7 +73,7 @@ actually doing, I suggest you skip the `-q`. ### manual install If you don't have bash, it's not very complicated to do it manually. Just -open the file `src/00-easy-install.sh` in a nice, syntax coloring, text +open the file `src/gl-easy-install` in a nice, syntax coloring, text editor, and follow the instructions marked "MANUAL" :-) ### upgrades @@ -94,7 +94,7 @@ opposed to merely creating one which did not exist) is best left to a human. ### other notes - * if you run `src/00-easy-install.sh` without the `-q` option, you will be + * if you run `src/gl-easy-install` without the `-q` option, you will be given a chance to edit `~/.gitolite.rc`. You can change any options (such as paths, for instance), but be sure to keep the perl syntax -- you *don't* have to know perl to do so, it's fairly easy to guess in this diff --git a/doc/1-migrate.mkd b/doc/1-migrate.mkd index fb5de34..c09fe13 100644 --- a/doc/1-migrate.mkd +++ b/doc/1-migrate.mkd @@ -46,7 +46,7 @@ Now, log off the server and get back to the client: gitosis-admin clone in `$GSAC` below, and similarly the path for your gito**lite**-admin clone in `$GLAC` - src/conf-convert.pl < $GSAC/gitosis.conf > $GLAC/gitolite.conf + src/gl-conf-convert < $GSAC/gitosis.conf > $GLAC/gitolite.conf Be sure to check the file to make sure it converted correctly diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index 240b647..7038fb9 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -3,6 +3,13 @@ *Note*: some of the paths in this document use variable names. Just refer to `~/.gitolite.rc` for the correct values for *your* installation. +In this document: + + * administer + * adding users and repos + * specifying gitweb and daemon access + * custom hooks + ### administer First of all, ***do NOT add new repos manually***, unless you know how to add @@ -62,3 +69,19 @@ a one-time setup you must do separately. All this does is: The "compile" script will keep these files consistent with the config settings -- this includes removing such settings/files if you remove "read" permissions for the special usernames or remove the description line. + +#### custom hooks + +If you want to put in your own, custom, hooks every time a new repo is created +by gitolite, put a **tested** hook script in `src/hooks`. As distributed, the +only file there is the `update` hook, but everything (*everything*) in that +directory will get copied to the `hooks/` subdirectory of every *new* repo +created. + +In order to push a new or updated hook script to *existing* repos as well, +just run easy install once again; it'll do it to existing repos also. + +**VERY IMPORTANT SECURITY NOTE: the `update` hook in `src/hooks` is what +implements all the branch-level permissions in gitolite. If you fiddle with +the hooks directory, please make sure you do not mess with this file +accidentally, or all your fancy per-branch permissions will stop working.** diff --git a/doc/6-ssh-troubleshooting.mkd b/doc/6-ssh-troubleshooting.mkd index 2bc04a0..462b59a 100644 --- a/doc/6-ssh-troubleshooting.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -117,7 +117,7 @@ Here's how it all hangs together. ~/.ssh/id_rsa.pub * gitolite keypair; the "sitaram" in this is the 3rd argument to the - `src/00-easy-install.sh` command you ran; the easy install script does the + `src/gl-easy-install` command you ran; the easy install script does the rest ~/.ssh/sitaram @@ -200,7 +200,7 @@ that should have enough info to get you going (but it helps to know ssh well): cp id_rsa sitaram cp id_rsa.pub sitaram.pub cd ~/gitolite-clone - src/00-easy-install.sh -q git my.git.server sitaram + src/gl-easy-install -q git my.git.server sitaram that last command produces something like the following: diff --git a/src/pta-hook.sh b/src/ga-post-update-hook similarity index 100% rename from src/pta-hook.sh rename to src/ga-post-update-hook diff --git a/src/gl-compile-conf b/src/gl-compile-conf index ab8774f..de3cf00 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -317,7 +317,8 @@ for my $repo (keys %repos) # erm, note that's "and die" not "or die" as is normal in perl wrap_chdir("$repo.git"); system("git --bare init"); - system("cp $GL_ADMINDIR/src/update-hook.pl hooks/update"); + # propagate our own, plus any local admin-defined, hooks + system("cp $GL_ADMINDIR/src/hooks/* hooks/"); chmod 0755, "hooks/update"; wrap_chdir("$repo_base_abs"); $git_too_old++ if $git_version < 10602; # that's 1.6.2 to you diff --git a/src/conf-convert.pl b/src/gl-conf-convert similarity index 100% rename from src/conf-convert.pl rename to src/gl-conf-convert diff --git a/src/00-easy-install.sh b/src/gl-easy-install similarity index 96% rename from src/00-easy-install.sh rename to src/gl-easy-install index eea3997..e5d23da 100755 --- a/src/00-easy-install.sh +++ b/src/gl-easy-install @@ -120,7 +120,7 @@ Notes: Pre-requisites: - you must run this from the gitolite working tree top level directory. - This means you run this as "src/00-easy-install.sh" + This means you run this as "src/gl-easy-install" - you must already have pubkey based access to user@host. If you currently only have password access, use "ssh-copy-id" or something equivalent (or copy the key manually). Somehow (doesn't matter how), get to the point @@ -137,13 +137,13 @@ EOFU # ---------------------------------------------------------------------- basic_sanity() { - # MANUAL: this *must* be run as "src/00-easy-install.sh", not by cd-ing to - # src and then running "./00-easy-install.sh" + # MANUAL: this *must* be run as "src/gl-easy-install", not by cd-ing to + # src and then running "./gl-easy-install" - [[ $0 =~ ^src/00-easy-install.sh$ ]] || + [[ $0 =~ ^src/gl-easy-install$ ]] || { die "please cd to the gitolite repo top level directory and run this as - 'src/00-easy-install.sh'" + 'src/gl-easy-install'" } # are we in quiet mode? @@ -175,11 +175,13 @@ basic_sanity() { # MANUAL: make sure you're in the gitolite directory, at the top level. # The following files should all be visible: - ls src/gl-auth-command \ - src/gl-compile-conf \ - src/install.pl \ - src/update-hook.pl \ - conf/example.conf \ + ls src/ga-post-update-hook \ + src/gitolite.pm \ + src/gl-install \ + src/gl-auth-command \ + src/gl-compile-conf \ + src/hooks/update \ + conf/example.conf \ conf/example.gitolite.rc >/dev/null || die "cant find at least some files in gitolite sources/config; aborting" @@ -349,9 +351,9 @@ run_install() { fi # MANUAL: still in the "gitolite-install" directory? Good. Run - # "src/install.pl" + # "src/gl-install" - ssh -p $port $user@$host "cd gitolite-install; src/install.pl $quiet" + ssh -p $port $user@$host "cd gitolite-install; src/gl-install $quiet" # MANUAL: if you're upgrading, run "src/gl-compile-conf" and you're done! # -- ignore the rest of this file for the purposes of an upgrade @@ -412,9 +414,9 @@ GIT_WORK_TREE=$GL_ADMINDIR git commit -am start --allow-empty # MANUAL: now that the admin repo is created, you have to set the hooks # properly. The install program does this. So cd back to the - # "gitolite-install" directory and run "src/install.pl" + # "gitolite-install" directory and run "src/gl-install" - ssh -p $port $user@$host "cd gitolite-install; src/install.pl $quiet" + ssh -p $port $user@$host "cd gitolite-install; src/gl-install $quiet" # MANUAL: you're done! Log out of the server, come back to your # workstation, and clone the admin repo using "git clone diff --git a/src/99-emergency-addkey.sh b/src/gl-emergency-addkey similarity index 100% rename from src/99-emergency-addkey.sh rename to src/gl-emergency-addkey diff --git a/src/install.pl b/src/gl-install similarity index 82% rename from src/install.pl rename to src/gl-install index 09a8345..afea0dc 100755 --- a/src/install.pl +++ b/src/gl-install @@ -66,20 +66,26 @@ unless (-f $GL_CONF) { EOF } -# finally, any potential changes to src/update-hook.pl must be propagated to -# all the repos' hook directories +# finally, hooks must be propagated to all the repos in case they changed chdir("$repo_base_abs") or die "chdir $repo_base_abs failed: $!\n"; for my $repo (`find . -type d -name "*.git"`) { chomp ($repo); - system("cp $GL_ADMINDIR/src/update-hook.pl $repo/hooks/update"); + # propagate our own, plus any local admin-defined, hooks + system("cp $GL_ADMINDIR/src/hooks/* $repo/hooks/"); chmod 0755, "$repo/hooks/update"; } # oh and one of those repos is a bit more special and has an extra hook :) if ( -d "gitolite-admin.git/hooks" ) { print "copying post-update hook to gitolite-admin repo...\n"; - system("cp -v $GL_ADMINDIR/src/pta-hook.sh gitolite-admin.git/hooks/post-update"); + system("cp -v $GL_ADMINDIR/src/ga-post-update-hook gitolite-admin.git/hooks/post-update"); system("perl", "-i", "-p", "-e", "s(export GL_ADMINDIR=.*)(export GL_ADMINDIR=$GL_ADMINDIR)", "gitolite-admin.git/hooks/post-update"); chmod 0755, "gitolite-admin.git/hooks/post-update"; } + +# fixup program renames +for my $oldname qw(pta-hook.sh conf-convert.pl 00-easy-install.sh 99-emergency-addkey.sh install.pl update-hook.pl) { + unlink "$GL_ADMINDIR/src/$oldname"; + unlink "$ENV{HOME}/gitolite-install/src/$oldname"; +} diff --git a/src/update-hook.pl b/src/hooks/update similarity index 100% rename from src/update-hook.pl rename to src/hooks/update From e8270e9b7272458b17f8ed2ccb76ec05f5c45b2a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 16 Nov 2009 19:25:34 +0530 Subject: [PATCH 139/637] update hook: 'sub check_ref' to prepare for rebel+ factor out the code to check $ref into a sub; will help rebel+, which wants (horrors!) to restrict based on PATH names too! --- src/hooks/update | 56 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/src/hooks/update b/src/hooks/update index cdf7402..a11b9ad 100755 --- a/src/hooks/update +++ b/src/hooks/update @@ -64,22 +64,44 @@ push @allowed_refs, { "$PERSONAL/$ENV{GL_USER}/" => "RW+" } if $PERSONAL; # we want specific perms to override @all, so they come first push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$ENV{GL_USER}} || [] }; push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{'@all'} || [] }; -for my $ar (@allowed_refs) -{ - my $refex = (keys %$ar)[0]; - # refex? sure -- a regex to match a ref against :) - next unless $ref =~ /$refex/; - if ($ar->{$refex} =~ /\Q$perm/) - { - # if log failure isn't important enough to block pushes, get rid of - # all the error checking - open my $log_fh, ">>", $ENV{GL_LOG} - or die "open log failed: $!\n"; - print $log_fh "$ENV{GL_TS} $perm\t" . - substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . - "\t$ENV{GL_REPO}\t$ref\t$ENV{GL_USER}\t$refex\n"; - close $log_fh or die "close log failed: $!\n"; - exit 0; + +my $refex = ''; + +# check one ref +sub check_ref { + + # normally, the $ref will be whatever ref the commit is trying to update + # (like refs/heads/master or whatever). At least one of the refexes that + # pertain to this user must match this ref **and** the corresponding + # permission must also match the action (W or +) being attempted. If none + # of them match, the access is denied. + + # Notice that the function DIES!!! Any future changes that require more + # work to be done *after* this, even on failure, can start using return + # codes etc., but for now we're happy to just die. + + my $ref = shift; + for my $ar (@allowed_refs) { + $refex = (keys %$ar)[0]; + # refex? sure -- a regex to match a ref against :) + next unless $ref =~ /$refex/; + + # as far as *this* ref is concerned we're ok + return if ($ar->{$refex} =~ /\Q$perm/); } + die "$perm $ref $ENV{GL_REPO} $ENV{GL_USER} DENIED by fallthru\n"; } -die "$perm $ref $ENV{GL_REPO} $ENV{GL_USER} DENIED by fallthru\n"; + +# and in this version, we have only one ref to check +check_ref($ref); + +# if we returned at all, the check succeeded, so we log the action and exit 0 + +# logging note: if log failure isn't important enough to block pushes, get rid +# of all the error checking +open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!\n"; +print $log_fh "$ENV{GL_TS} $perm\t" . + substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . + "\t$ENV{GL_REPO}\t$ref\t$ENV{GL_USER}\t$refex\n"; +close $log_fh or die "close log failed: $!\n"; +exit 0; From 05a06a2c75fd6f9172b6cb5fea4825a01331a6a6 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 18 Nov 2009 07:18:05 +0530 Subject: [PATCH 140/637] README, doc/3: gitweb "description" feature --- README.mkd | 4 ++-- doc/3-faq-tips-etc.mkd | 26 +++++++++++++++++--------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/README.mkd b/README.mkd index 660e8f2..e1a9616 100644 --- a/README.mkd +++ b/README.mkd @@ -73,8 +73,8 @@ detail [here][gsdiff]. * config file syntax gets checked upfront, and much more thoroughly * if your requirements are still too complex, you can split up the config file and delegate authority over parts of it - * easier to specify gitweb/daemon access, and easier to link gitweb - authorisation with gitolite + * easier to specify gitweb "description" and gitweb/daemon access + * easier to sync gitweb (http) authorisation with gitolite's access config * more comprehensive logging [aka: management does not think "blame" is just a synonym for "annotate" :-)] * "personal namespace" prefix for each dev diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 64997b1..05eb8b7 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -11,7 +11,7 @@ In this document: * two levels of access rights checking * error checking the config file * delegating parts of the config file - * easier to specify gitweb/daemon access + * easier to specify gitweb "description" and gitweb/daemon access * easier to link gitweb authorisation with gitolite * better logging * one user, many keys @@ -196,17 +196,25 @@ for details. -#### easier to specify gitweb/daemon access +#### easier to specify gitweb "description" and gitweb/daemon access -Which of your repos should be accessible via plain HTTP or the `git://` -protocols (gitweb and git daemon, respectively)? +To enable access to a repo via gitweb *and* create a "description" for it to +show up on the webpage, just add a line like this, anywhere in the config +file: -Specifying gitweb and/or daemon access for a repo is simple: give "read" -permissions to two special usernames: `gitweb` and `daemon`. + reponame = "one line of description" -You can also keep these pieces separate from the detailed, branch level access -for each repo, if you like, since you can write the access control specs in -bits and pieces. Here's an example, using short repo names for convenience: +To enable access to one or more repos via git daemon, just give "read" +permissions to the special username `daemon`. + +There is also a special user called `gitweb` to specify gitweb access; useful +if you don't care about specifying individual descriptions for each repo and +just want to quickly enable gitweb access to one or more repos. + +Remember gitolite lets you specify the access control specs in bits and +pieces, so you can keep all the daemon/gitweb access in one place, even if +each repo has more specific branch-level access config specified elsewhere. +Here's an example, using really short reponames because I'm lazy: # maybe near the top of the file, for ease of access: From c727d68caad769d45935a6d80eddc78ac0c800c7 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 18 Nov 2009 13:37:14 +0530 Subject: [PATCH 141/637] install doc: server and client requirements spelled out --- doc/0-INSTALL.mkd | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index 984f040..0ab8c97 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -19,6 +19,7 @@ In this document: * upgrades * other notes * next steps + * appendix: server and client requirements ---- @@ -105,3 +106,72 @@ opposed to merely creating one which did not exist) is best left to a human. The last message produced by the easy install script should tell you how to add users, repos, etc., and you will find more details in the [admin][admin] document. + + + +### appendix: server and client requirements + +There are 3 machines *potentially* involved in installing and administering +gitolite. + +#### server + +This is where gitolite is eventually installed. You need a *normal* userid +(typically "git" but can be anything) on this machine; root access is *not* +needed. + +You need the following software on it: + + * git + * can be in a non-PATH location if you are unable to install it + normally; see the `$GIT_PATH` variable in the "rc" file + * perl + * default install is fine; no special modules are needed + * (a normal install of git also requires/installs perl, so you probably + have it already) + * openssh server + * (I guess any ssh server that can understand the `authorized_keys` file + format should work) + * **bash shell** + * a small part of the gitolite server side is written in "bash"; I + intend to test it (time permitting) on non-bash (like ksh or plain sh) + on non-Linux servers. Once that is done, this bash dependency will go + away. + +#### install workstation + +Installing or upgrading the gitolite software itself is best done by running +the easy-install program from a clone, usually on a different machine. + +This script is heavily dependent on bash, so you need a unix machine that has +a bash shell available. Unlike the server side dependency mentioned above, +this bash dependency is probably permament. + +*However*, you do not need to use this machine every day. Day to day +administration of gitolite (adding users, repos, etc) can be done from +anywhere; see next section. + +If you do not have such a machine, you have the following alternatives: + + * use a different userid on the same server + * use the same userid on the same server! + * (either of the above cases makes bash mandatory on the server, by the way) + * manually simulate the script directly on the server; doable, but tedious + +#### admin workstation(s) + +When you install gitolite, it creates a repository called "gitolite-admin" and +gives you permissions on it. + +Administering gitolite (adding repos/users, assigning permissions, etc) is +done by cloning this repo, making changes to a file called +`conf/gitolite.conf`, adding users' pubkeys to `keydir/`, and pushing the +changes back to the server. + +Which means all this can be done from *any* machine, even Windows. [Just make +sure your pubkey (from the Windows machine) has been added and permissions +given to allow you to push to the gitolite-admin repo. This is also how other +people can be added as "admins": add their pubkey to as +`keydir/.pub`, and give `` at least `RW` permissions on +the `gitolite-admin` repo in the `conf/gitolite.conf` file.] + From 0bdf1f360f240d524a76dd7fe01ef74c90959256 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 18 Nov 2009 14:54:18 +0530 Subject: [PATCH 142/637] all src/ and conf/: force crlf=input via gitattributes msysgit needs this on the initial clone, so it has to be on master. It doesn't seem to "apply" the gitattributes if you checkout a different branch later that has that setting; didn't investigate why --- .gitattributes | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5d894ad --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +conf/* crlf=input +src/* crlf=input +src/hooks/* crlf=input From 23be2b62406f0b0cfdd8b93999582285efa088c8 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 18 Nov 2009 14:37:04 +0530 Subject: [PATCH 143/637] easy install (+doc): make it run from msys. Here's how: - all $HOME/blah becomes "$HOME/blah" (bl**dy "Documents and Settings" crap) - replace bash regex with perl, and in one case replace the check with something else - rsync changed to appropriate scp - since we no longer insist on running from a specific directory, create tmpgli dir *after* you cd to the right place --- doc/0-INSTALL.mkd | 35 +++++++++++++++-------------------- src/gl-easy-install | 40 ++++++++++++++++++++-------------------- 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index 0ab8c97..b12edd2 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -1,5 +1,7 @@ # installing gitolite +[Update 2009-11-18: easy install now works from msysgit also!] + This document tells you how to install gitolite. After the install is done, you may want to see the [admin document][admin] for adding users, repos, etc. @@ -141,22 +143,18 @@ You need the following software on it: #### install workstation Installing or upgrading the gitolite software itself is best done by running -the easy-install program from a clone, usually on a different machine. +the easy-install program from a gitolite clone. -This script is heavily dependent on bash, so you need a unix machine that has -a bash shell available. Unlike the server side dependency mentioned above, -this bash dependency is probably permament. +This script is heavily dependent on bash, so you need a machine with a bash +shell. Even the bash that comes with msysgit is fine, if you don't have a +Linux box handy. -*However*, you do not need to use this machine every day. Day to day -administration of gitolite (adding users, repos, etc) can be done from -anywhere; see next section. +If you have neither Linux nor Windows+msysgit, you still have a few +alternatives: -If you do not have such a machine, you have the following alternatives: - - * use a different userid on the same server - * use the same userid on the same server! - * (either of the above cases makes bash mandatory on the server, by the way) - * manually simulate the script directly on the server; doable, but tedious + * use a different userid on the same server (assuming it has bash) + * use the same userid on the same server (same assumption) + * manually simulate the script directly on the server (doable, but tedious) #### admin workstation(s) @@ -168,10 +166,7 @@ done by cloning this repo, making changes to a file called `conf/gitolite.conf`, adding users' pubkeys to `keydir/`, and pushing the changes back to the server. -Which means all this can be done from *any* machine, even Windows. [Just make -sure your pubkey (from the Windows machine) has been added and permissions -given to allow you to push to the gitolite-admin repo. This is also how other -people can be added as "admins": add their pubkey to as -`keydir/.pub`, and give `` at least `RW` permissions on -the `gitolite-admin` repo in the `conf/gitolite.conf` file.] - +Which means all this can be done from *any* machine. You'll normally do it +from the same machine you used to install gitolite, but it doesn't have to be +the same one, as long as your pubkey has been added and permissions given to +allow you to push to the gitolite-admin repo. diff --git a/src/gl-easy-install b/src/gl-easy-install index e5d23da..99e0d8a 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -33,10 +33,10 @@ fi # ---------------------------------------------------------------------- main() { - setup_tempdir - basic_sanity "$@" + setup_tempdir + version_info "$@" [[ -n $admin_name ]] && setup_local_ssh @@ -140,11 +140,9 @@ basic_sanity() { # MANUAL: this *must* be run as "src/gl-easy-install", not by cd-ing to # src and then running "./gl-easy-install" - [[ $0 =~ ^src/gl-easy-install$ ]] || - { - die "please cd to the gitolite repo top level directory and run this as - 'src/gl-easy-install'" - } + bindir=${0%/*} + # switch to parent of bindir; we assume the conf files are all there + cd $bindir; cd .. # are we in quiet mode? quiet= @@ -163,14 +161,16 @@ basic_sanity() { admin_name=$3 # but if the 3rd arg is a number, that's a port number, and the 4th arg is # the admin_name - if [[ $3 =~ ^[0-9]+$ ]] + if echo $3 | perl -lne 'exit 1 unless /^[0-9]+$/' then port=$3 admin_name=$4 fi - [[ "$user" =~ [^a-zA-Z0-9._-] ]] && die "user '$user' invalid" - [[ -n $admin_name ]] && [[ "$admin_name" =~ [^a-zA-Z0-9._-] ]] && die "admin_name '$admin_name' invalid" + echo $user | perl -lne 'exit 1 if /[^a-zA-Z0-9._-]/' || + die "user '$user' invalid" + echo $admin_name | perl -lne 'exit 1 if /[^a-zA-Z0-9._-]/' || + die "admin_name '$admin_name' invalid" # MANUAL: make sure you're in the gitolite directory, at the top level. # The following files should all be visible: @@ -225,11 +225,11 @@ setup_local_ssh() { prompt "setting up keypair..." "$v_setting_up_keypair" - if [[ -f $HOME/.ssh/$admin_name.pub ]] + if [[ -f "$HOME/.ssh/$admin_name.pub" ]] then prompt " ...reusing $HOME/.ssh/$admin_name.pub..." "$v_reuse_pubkey" else - ssh-keygen -t rsa -f $HOME/.ssh/$admin_name || die "ssh-keygen failed for some reason..." + ssh-keygen -t rsa -f "$HOME/.ssh/$admin_name" || die "ssh-keygen failed for some reason..." fi # MANUAL: copy the pubkey created to the server, say to /tmp. This would @@ -245,7 +245,7 @@ setup_local_ssh() { if ssh-add -l &>/dev/null then prompt " ...adding key to agent..." "$v_ssh_add" - ssh-add $HOME/.ssh/$admin_name + ssh-add "$HOME/.ssh/$admin_name" fi # MANUAL: you now need to add some lines to the end of your ~/.ssh/config @@ -266,14 +266,14 @@ setup_local_ssh() { port $port identityfile ~/.ssh/$admin_name" > $tmpgli/.gl-stanza - if grep 'host *gitolite' $HOME/.ssh/config &>/dev/null + if grep 'host *gitolite' "$HOME/.ssh/config" &>/dev/null then prompt "found gitolite para in ~/.ssh/config; assuming it is correct..." "$v_found_para" else prompt "creating gitolite para in ~/.ssh/config..." "$v_creating_para" - cat $tmpgli/.gl-stanza >> $HOME/.ssh/config + cat $tmpgli/.gl-stanza >> "$HOME/.ssh/config" # if the file didn't exist at all, it might have the wrong permissions - chmod 644 $HOME/.ssh/config + chmod 644 "$HOME/.ssh/config" fi } @@ -288,7 +288,7 @@ copy_gl() { # have to create the directory first. ssh -p $port $user@$host mkdir -p gitolite-install - rsync $quiet -e "ssh -p $port" -a src conf doc $user@$host:gitolite-install/ + scp $quiet -P $port -r src conf doc $user@$host:gitolite-install/ rm -f src/VERSION # MANUAL: now log on to the server (ssh git@server) and get a command @@ -384,7 +384,7 @@ repo testing # send the config and the key to the remote scp $quiet -P $port $tmpgli/gitolite.conf $user@$host:$GL_ADMINDIR/conf/ - scp $quiet -P $port $HOME/.ssh/$admin_name.pub $user@$host:$GL_ADMINDIR/keydir + scp $quiet -P $port "$HOME/.ssh/$admin_name.pub" $user@$host:$GL_ADMINDIR/keydir # MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf $quiet" @@ -426,7 +426,7 @@ GIT_WORK_TREE=$GL_ADMINDIR git commit -am start --allow-empty prompt "cloning gitolite-admin repo..." "$v_cloning" cleanup - cd $HOME + cd "$HOME" git clone gitolite:gitolite-admin.git # MANUAL: be sure to read the message below; this applies to you too... @@ -464,7 +464,7 @@ successfully cloned and pushed the gitolite-admin repo. After that, install 'keychain' or something similar, and add the following command to your bashrc (since this is a non-default key) - ssh-add \\\$HOME/.ssh/\$admin_name + ssh-add "\\\$HOME/.ssh/\$admin_name" This makes using passphrases very convenient. " From d332b1537dcf246117d0687215d2e4a38cd9def1 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 19 Nov 2009 17:38:40 +0530 Subject: [PATCH 144/637] serverside install/ga-hook: solaris compat --- src/ga-post-update-hook | 4 ++-- src/gl-install | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ga-post-update-hook b/src/ga-post-update-hook index 2613340..8090b8a 100755 --- a/src/ga-post-update-hook +++ b/src/ga-post-update-hook @@ -2,7 +2,7 @@ # get this from your .gitolite.conf; and don't forget this is shell, while # that is perl :-) -export GL_ADMINDIR=$HOME/.gitolite +export GL_ADMINDIR; GL_ADMINDIR=$HOME/.gitolite # checkout the master branch to $GL_ADMINDIR GIT_WORK_TREE=$GL_ADMINDIR git checkout -f master @@ -13,7 +13,7 @@ GIT_WORK_TREE=$GL_ADMINDIR git checkout -f master rm -rf $GL_ADMINDIR/conf/fragments # collect all the delegated fragments mkdir $GL_ADMINDIR/conf/fragments -for br in $(git for-each-ref --format='%(refname:short)') +for br in `git for-each-ref --format='%(refname:short)'` do # skip master (duh!) [ "$br" = "master" ] && continue diff --git a/src/gl-install b/src/gl-install index afea0dc..a4dc729 100755 --- a/src/gl-install +++ b/src/gl-install @@ -78,8 +78,8 @@ for my $repo (`find . -type d -name "*.git"`) { # oh and one of those repos is a bit more special and has an extra hook :) if ( -d "gitolite-admin.git/hooks" ) { print "copying post-update hook to gitolite-admin repo...\n"; - system("cp -v $GL_ADMINDIR/src/ga-post-update-hook gitolite-admin.git/hooks/post-update"); - system("perl", "-i", "-p", "-e", "s(export GL_ADMINDIR=.*)(export GL_ADMINDIR=$GL_ADMINDIR)", + system("cp $GL_ADMINDIR/src/ga-post-update-hook gitolite-admin.git/hooks/post-update"); + system("perl", "-i", "-p", "-e", "s(GL_ADMINDIR=.*)(GL_ADMINDIR=$GL_ADMINDIR)", "gitolite-admin.git/hooks/post-update"); chmod 0755, "gitolite-admin.git/hooks/post-update"; } From cba66c6e5a56043c1a747104eb49b1356df5716c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 23 Nov 2009 18:04:18 +0530 Subject: [PATCH 145/637] compile: make compiled config be key-sorted makes debugging access changes much easier (doh! why didn't I do this earlier!) --- src/gl-compile-conf | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index de3cf00..247e5cc 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -4,6 +4,7 @@ use strict; use warnings; use Data::Dumper; $Data::Dumper::Indent = 1; +$Data::Dumper::Sortkeys = 1; # === add-auth-keys === From 516c028b815fd54231bfac962b919ab5af600835 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 23 Nov 2009 22:45:00 +0530 Subject: [PATCH 146/637] compile: (oopsies...) plug security hole in delegation feature I was trying to determine how close gitolite can come to the ACL model of a proprietary product called codebeamer, and one of the items was how to make a "role" (like QA_Lead) have different "members" in different projects. I then realised delegation already does that! Which is great, but as I thought about it more, I realised... well, we'll let the in-code comments speak for themselves :-) Anyway, all it needed was a 1-line fix, luckily... And it would have only affected people who use delegation. --- src/gl-compile-conf | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 247e5cc..77d340b 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -86,7 +86,7 @@ my $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple patter # $groups{group}{member} = "master" (or name of fragment file in which the # group is defined). -my %groups = (); +our %groups = (); # %repos has two functions. @@ -282,6 +282,24 @@ parse_conf_file($GL_CONF, 'master'); wrap_chdir($GL_ADMINDIR); for my $fragment_file (glob("conf/fragments/*.conf")) { + # we already check (elsewhere) that a fragment called "foo" will not try + # to specify access control for a repo whose name is not "foo" or is not + # part of a group called "foo" created by master + + # meanwhile, I found a possible attack where the admin for group B creates + # a "convenience" group of (a subset of) his users, and then the admin for + # repo group A (alphabetically before B) adds himself to that same group + # in his own fragment. + + # as a result, admin_A now has access to group B repos :( + + # so now we lock the groups hash to the value it had after parsing + # "master", and localise any changes to it by this fragment so that they + # don't propagate to the next fragment. Thus, each fragment now has only + # those groups that are defined in "master" and itself + + local %groups = %groups; + my $fragment = $fragment_file; $fragment =~ s/^conf\/fragments\/(.*).conf$/$1/; parse_conf_file($fragment_file, $fragment); From de2e38c3726a4cbc41243d187b0210e3a8066761 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 22 Nov 2009 10:21:22 +0530 Subject: [PATCH 147/637] minor doc/message updates/clarifications --- doc/0-INSTALL.mkd | 12 +++--------- doc/6-ssh-troubleshooting.mkd | 2 ++ src/gl-auth-command | 2 +- src/gl-easy-install | 10 ++++------ 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index b12edd2..106e415 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -28,9 +28,8 @@ In this document: ### easy install There is an easy install script that makes installing very easy for the common -case. **This script is meant to be run on your workstation, not on the -server!** It will take care of all the server side work, *and* get you -"push-to-admin" too :-) In short, it does **everything**! +case. **This script will setup everything on the server, but you have to run +it on your workstation, NOT on the server!** Assumptions/pre-requisites: @@ -120,7 +119,7 @@ gitolite. This is where gitolite is eventually installed. You need a *normal* userid (typically "git" but can be anything) on this machine; root access is *not* -needed. +needed, but it has to be some sort of Unix (not Windows). You need the following software on it: @@ -134,11 +133,6 @@ You need the following software on it: * openssh server * (I guess any ssh server that can understand the `authorized_keys` file format should work) - * **bash shell** - * a small part of the gitolite server side is written in "bash"; I - intend to test it (time permitting) on non-bash (like ksh or plain sh) - on non-Linux servers. Once that is done, this bash dependency will go - away. #### install workstation diff --git a/doc/6-ssh-troubleshooting.mkd b/doc/6-ssh-troubleshooting.mkd index 462b59a..29459ba 100644 --- a/doc/6-ssh-troubleshooting.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -137,6 +137,8 @@ Here's how it all hangs together. the `gl-auth-command` program at all, and so none of gitolite's access control will work. + + You need to force ssh to use the *other* keypair when performing a git operation. With normal ssh, that would be diff --git a/src/gl-auth-command b/src/gl-auth-command index f516657..5da4878 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -96,7 +96,7 @@ die "bad command: $cmd. Make sure the repo name is exactly as in your config\n" # we know the user and repo; we just need to know what perm he's trying my $perm = ($verb =~ $R_COMMANDS ? 'R' : 'W'); -die "$perm access for $repo denied to $user\n" +die "$perm access for $repo DENIED to $user\n" unless $repos{$repo}{$perm}{$user} or $repos{$repo}{$perm}{'@all'}; diff --git a/src/gl-easy-install b/src/gl-easy-install index 99e0d8a..e3616ef 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -119,8 +119,6 @@ Notes: gitolite admin Pre-requisites: - - you must run this from the gitolite working tree top level directory. - This means you run this as "src/gl-easy-install" - you must already have pubkey based access to user@host. If you currently only have password access, use "ssh-copy-id" or something equivalent (or copy the key manually). Somehow (doesn't matter how), get to the point @@ -420,14 +418,14 @@ GIT_WORK_TREE=$GL_ADMINDIR git commit -am start --allow-empty # MANUAL: you're done! Log out of the server, come back to your # workstation, and clone the admin repo using "git clone - # gitolite:gitolite-admin.git", or pull once again if you already have a + # gitolite:gitolite-admin", or pull once again if you already have a # clone prompt "cloning gitolite-admin repo..." "$v_cloning" cleanup cd "$HOME" - git clone gitolite:gitolite-admin.git + git clone gitolite:gitolite-admin # MANUAL: be sure to read the message below; this applies to you too... @@ -577,9 +575,9 @@ keychain or some such software, you may have to run this each time you log in: URLS: *Your* URL for cloning any repo on this server will be - gitolite:reponame.git + gitolite:reponame *Other* users you set up will have to use - \$user@\$host:reponame.git + \$user@\$host:reponame " From ad6d46fab908bc4f08ad1432597265536bdb35b2 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 22 Nov 2009 10:42:32 +0530 Subject: [PATCH 148/637] easy install: when the first install doesn't go right... We detect an upgrade situation by the presence of $GL_ADMINDIR/conf/gitolite.conf -- if it exists, we reason, this is not a fresh install. And if so we skip setting up PTA, and the initial clone. Well, turns out this is not always true. I've had a few cases where the first install didn't go right, but left enough stuff in to make the subsequent attempt think this is an upgrade. [This mostly happened to me when I was testing the "oldgits" branch, and also when I was making it work from msysgit I think... regardless of why, it'd be good to fix] So this changes the flow somewhat. Now the *only* difference between a fresh install and an ugrade is the "initial_conf_key" function call (you don't want to overwrite an existing conf file or keydir!) --- src/gl-easy-install | 48 ++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/gl-easy-install b/src/gl-easy-install index e3616ef..6a51dfa 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -45,19 +45,14 @@ main() { run_install - [[ $upgrade == 1 ]] && { - # just compile it, in case the config file's internal format has - # changed and the hooks expect something different - ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf $quiet" + [[ $upgrade == 0 ]] && initial_conf_key - eval "echo \"$v_done\"" - cleanup - exit 0 - } - - initial_conf_key + # MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" + ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf $quiet" setup_pta + + clone_it } # ---------------------------------------------------------------------- @@ -140,7 +135,7 @@ basic_sanity() { bindir=${0%/*} # switch to parent of bindir; we assume the conf files are all there - cd $bindir; cd .. + cd "$bindir"; cd .. # are we in quiet mode? quiet= @@ -383,9 +378,6 @@ repo testing # send the config and the key to the remote scp $quiet -P $port $tmpgli/gitolite.conf $user@$host:$GL_ADMINDIR/conf/ scp $quiet -P $port "$HOME/.ssh/$admin_name.pub" $user@$host:$GL_ADMINDIR/keydir - - # MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" - ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf $quiet" } # ---------------------------------------------------------------------- @@ -407,7 +399,7 @@ setup_pta() { echo "cd $REPO_BASE/gitolite-admin.git GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir -GIT_WORK_TREE=$GL_ADMINDIR git commit -am start --allow-empty +GIT_WORK_TREE=$GL_ADMINDIR git diff --cached --quiet || GIT_WORK_TREE=$GL_ADMINDIR git commit -am start " | ssh -p $port $user@$host # MANUAL: now that the admin repo is created, you have to set the hooks @@ -420,19 +412,26 @@ GIT_WORK_TREE=$GL_ADMINDIR git commit -am start --allow-empty # workstation, and clone the admin repo using "git clone # gitolite:gitolite-admin", or pull once again if you already have a # clone +} - prompt "cloning gitolite-admin repo..." "$v_cloning" - +clone_it() +{ cleanup cd "$HOME" - git clone gitolite:gitolite-admin + if [[ -d gitolite-admin ]] + then + echo $HOME/gitolite-admin exists, skipping clone step... + else + prompt "cloning gitolite-admin repo..." "$v_cloning" + git clone gitolite:gitolite-admin + fi # MANUAL: be sure to read the message below; this applies to you too... echo echo echo --------------------------------------------------------------- - eval "echo \"$tail\"" + eval "echo \"$v_done\"" } # ---------------------------------------------------------------------- @@ -537,10 +536,15 @@ install, not this one... v_done=" done! -If you forgot the help message you saw when you first ran this, there's a -somewhat generic version of it at the end of this file. Try: +Reminder: + *Your* URL for cloning any repo on this server will be + gitolite:reponame.git + *Other* users you set up will have to use + \$user@\$host:reponame.git - tail -31 \$0 + If this is your first time installing gitolite, please also: + tail -31 \$0 + for next steps. " v_cloning=" From 1c786d880a4c813139701fabbaeaf044f6077ed8 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 25 Nov 2009 08:46:21 +0530 Subject: [PATCH 149/637] doc/3: $repo_base -> $projectroot; honor @all in perm check - it appears that what we call $repo_base, gitweb already needs as $projectroot - allow read of repos defined as readable by @all plus some minor declaration changes to make the sample code work as is (thanks to teemu dot matilainen at iki dot fi) --- doc/3-faq-tips-etc.mkd | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 05eb8b7..5dcd935 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -267,11 +267,13 @@ already done and we just use it! # completely untested... but the basic idea should work fine # change these as needed - $repo_base = '/home/git/repositories/'; - $gl_conf_compiled = '/home/git/.gitolite/conf/gitolite.conf-compiled.pm'; + # projectroot should be the same as gitolite's REPO_BASE, but converted to + # an absolute path + $projectroot = '/home/git/repositories/'; + my $gl_conf_compiled = '/home/git/.gitolite/conf/gitolite.conf-compiled.pm'; # I assume this gives us the HTTP auth username - $username = $cgi->remote_user; + my $username = $cgi->remote_user; # ---------- @@ -284,12 +286,13 @@ already done and we just use it! $export_auth_hook = sub { my $reponame = shift; # gitweb passes us the full repo path; so we strip the beginning... - $reponame =~ s/\Q$repo_base//; + $reponame =~ s/\Q$projectroot//; # ...and the end, to get the repo name as it is specified in gitolite conf $reponame =~ s/\.git$//; - return exists $repos{$reponame}{R}{$username}; - } + return exists $repos{$reponame}{R}{$username} + || exists $repos{$reponame}{R}{'@all'}; + }; [leho]: http://leho.kraav.com/news/2009/10/27/using-apache-authentication-with-gitweb-gitosis-repository-access-control/ From 9a85f5d0d6dea071bf8322069325379338d12f92 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 25 Nov 2009 09:15:36 +0530 Subject: [PATCH 150/637] auth: print permissions for @all also I don't have a use for "@all" at all (pun not intended!) other than the "testing" repo, but sent in a patch to mark those repos with "R" and "W" in the permissions list, and I started thinking about it. This could actually be useful if we *differentiated* such access from normal (explicit username) access. From the "corporate environment" angle, it would be nice if a project manager could quickly check if any of his projects have erroneously been made accessible by @all. So what we do now is print "@" in the corresponding column if "@all" has the corresponding access. Also, when someone has access both as himself *and* via @all, we print the "@"; printing the "R" or "W" would hide the "@", and wouldn't correctly satisfy the use case described above. --- doc/3-faq-tips-etc.mkd | 1 + src/gl-auth-command | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 5dcd935..6147e95 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -389,6 +389,7 @@ shell: R W gitolite-admin R W indic_web_input R W proxy + @ @ testing R W vkc Note that until this version, we used to put out an ugly `need diff --git a/src/gl-auth-command b/src/gl-auth-command index 5da4878..31d1fa8 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -67,8 +67,8 @@ unless ($ENV{SSH_ORIGINAL_COMMAND}) { system("cat", "$GL_ADMINDIR/src/VERSION"); print "\ryou have the following permissions:\n\r"; for my $r (sort keys %repos) { - my $perm .= " R" if $repos{$r}{R}{$user}; - $perm .= " W" if $repos{$r}{W}{$user}; + my $perm .= ( $repos{$r}{R}{'@all'} ? ' @' : ( $repos{$r}{R}{$user} ? ' R' : '' ) ); + $perm .= ( $repos{$r}{W}{'@all'} ? ' @' : ( $repos{$r}{W}{$user} ? ' W' : '' ) ); print "$perm\t$r\n\r" if $perm; } exit 1; From a02a48e8f5439a998554db0cf070c8d89b458766 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 26 Nov 2009 12:13:42 +0530 Subject: [PATCH 151/637] easy install: dont allow root, plus warn about shell access using the given key - refuse to install to root - when a pubkey is being used that was not freshly created by ourselves, warn the user that this key can not be used to get shell access to the server. Prevents some corner cases of people being locked out... Also, change the final message to be even more clear that this is all on the workstation, not the server --- doc/6-ssh-troubleshooting.mkd | 4 ++-- src/gl-easy-install | 25 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/doc/6-ssh-troubleshooting.mkd b/doc/6-ssh-troubleshooting.mkd index 29459ba..2b9febc 100644 --- a/doc/6-ssh-troubleshooting.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -91,7 +91,7 @@ Here's how it all hangs together. 400 characters; I snipped 'em in the middle, as you can see. In contrast, pubkey lines that give access to git repos hosted by gitolite - looks like this: + look like this: command="[some path]src/gl-auth-command sitaram",[some restrictions] ssh-rsa AAAAB3NzaC[snip]s18OnB42oQ== sitaram@sita-lt @@ -146,7 +146,7 @@ Here's how it all hangs together. but git does not support putting an alternate keypair in the URL. - Luckily, ssh has a very convenient way of capturing all the mundane + Luckily, ssh has a very convenient way of capturing all the connection information (username, hostname, port number (if it's not the default 22), and keypair to be used) in one "paragraph". This is what the para looks like for us (the easy install script puts it there the first time): diff --git a/src/gl-easy-install b/src/gl-easy-install index 6a51dfa..c577307 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -162,6 +162,7 @@ basic_sanity() { echo $user | perl -lne 'exit 1 if /[^a-zA-Z0-9._-]/' || die "user '$user' invalid" + [[ "$user" == "root" ]] && die I refuse to install to root echo $admin_name | perl -lne 'exit 1 if /[^a-zA-Z0-9._-]/' || die "admin_name '$admin_name' invalid" @@ -220,7 +221,7 @@ setup_local_ssh() { if [[ -f "$HOME/.ssh/$admin_name.pub" ]] then - prompt " ...reusing $HOME/.ssh/$admin_name.pub..." "$v_reuse_pubkey" + prompt "" "$v_reuse_pubkey" else ssh-keygen -t rsa -f "$HOME/.ssh/$admin_name" || die "ssh-keygen failed for some reason..." fi @@ -467,8 +468,14 @@ This makes using passphrases very convenient. " v_reuse_pubkey=" -Hmmm... pubkey \$HOME/.ssh/\$admin_name.pub exists; should I just re-use it? -Be sure you remember the passphrase, if you gave one when you created it! +Hmmm... pubkey \$HOME/.ssh/\$admin_name.pub exists; should I just (re-)use it? + +IMPORTANT: once the install completes, *this* key can no longer be used to get +a command line on the server -- it will be used by gitolite, for git access +only. If that is a problem, please ABORT now. + +doc/6-ssh-troubleshooting.mkd will explain what is happening here, if you need +more info. " v_ssh_add=" @@ -554,16 +561,16 @@ it elsewhere later if you wish to. " tail=" -All done! +NOTE: All the below stuff is on your *workstation*. You shoud not, normally, +have to do anything directly on your server to administer/use gitolite. -The admin repo is currently cloned at ~/gitolite-admin; you can clone it -anywhere you like. To administer gitolite, make changes to the config file -(config/gitolite.conf) and/or the pubkeys (in subdirectory 'keydir') in any +The admin repo is currently cloned at ~/gitolite-admin. You can reclone it +anywhere else if you wish. To administer gitolite, make changes to the config +file (conf/gitolite.conf) and/or the pubkeys (in subdirectory 'keydir') in any clone, then git add, git commit, and git push. ADDING REPOS: Edit the config file to give *some* user access to the repo. -When you push, an empty repo will be created on the server, which authorised -users can then clone from, or push to. +When you push, an empty repo will be created on the server. ADDING USERS: copy their pubkey as keydir/.pub, add it, commit and push. From 6e0855eb4d4050a11d1fd7d3539e52e07139dba4 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 26 Nov 2009 19:30:37 +0530 Subject: [PATCH 152/637] compile: gitweb/daemon writes are unconditional now writing the export_ok files and the gitweb project list are now unconditional. They're idempotent anyway, and I doubt anyone cared about all the fancy logic to detect and report *just* the new ones on each compile. This paves the way for gitweb ownership to be added later; that code was becoming too complex otherwise... --- src/gl-compile-conf | 48 ++++++++------------------------------------- 1 file changed, 8 insertions(+), 40 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 77d340b..69b2ccb 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -360,67 +360,35 @@ warn "\n\t\t***** WARNING *****\n" . wrap_chdir("$repo_base_abs"); -# get the current project list; note that the file may not yet exist if no -# gitweb access has been specified so far -my %projlist = (); -if (-f $PROJECTS_LIST) { - my $projlist_fh = wrap_open( "<", $PROJECTS_LIST); - while(<$projlist_fh>) { - chomp; - $projlist{$_} = 1; - } - close $projlist_fh; -} -my $projlist_changed = 0; - # daemons first... for my $repo (sort keys %repos) { my $export_ok = "$repo.git/git-daemon-export-ok"; if ($repos{$repo}{'R'}{'daemon'}) { - unless (-f $export_ok) { - system("touch $export_ok"); - print "daemon add $repo.git\n"; - } + system("touch $export_ok"); } else { - if (-f $export_ok) { - unlink($export_ok); - print "daemon del $repo.git\n"; - } + unlink($export_ok); } } +my %projlist = (); # ...then gitwebs for my $repo (sort keys %repos) { my $desc_file = "$repo.git/description"; # note: having a description also counts as enabling gitweb if ($repos{$repo}{'R'}{'gitweb'} or $desc{$repo}) { - unless ($projlist{"$repo.git"}) { - # not in the old list; add it to the new one - $projlist{"$repo.git"} = 1; - $projlist_changed = 1; - print "gitweb add $repo.git\n"; - } + $projlist{"$repo.git"} = 1; # add the description file; no messages to user or error checking :) $desc{$repo} and open(DESC, ">", $desc_file) and print DESC "$desc{$repo}\n" and close DESC; } else { - if ($projlist{"$repo.git"}) { - # delete it from new list - delete $projlist{"$repo.git"}; - $projlist_changed = 1; - print "gitweb del $repo.git\n"; - } # delete the description file; no messages to user or error checking :) unlink $desc_file; } } -# has there been a change in the gitweb projects list? -if ($projlist_changed) { - print "updating gitweb project list $PROJECTS_LIST\n"; - my $projlist_fh = wrap_open( ">", $PROJECTS_LIST); - print $projlist_fh join("\n", sort keys %projlist), "\n" if %projlist; - close $projlist_fh; -} +# update the project list +my $projlist_fh = wrap_open( ">", $PROJECTS_LIST); +print $projlist_fh join("\n", sort keys %projlist), "\n" if %projlist; +close $projlist_fh; # ---------------------------------------------------------------------------- # "compile" ssh authorized_keys From d2a053ba3c4384059922ae6821dcdef1bb6f1cb8 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 27 Nov 2009 13:23:48 +0530 Subject: [PATCH 153/637] compile: add owner field in the same line as the gitweb descriptions this goes into the project list --- src/gl-compile-conf | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 69b2ccb..6ca9b9b 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -107,8 +107,9 @@ my %rurp_seen = (); # catch usernames<->pubkeys mismatches; search for "lint" below my %user_list = (); -# gitweb descriptions, plain text, keyed by repo +# gitweb descriptions and owners; plain text, keyed by "$repo.git" my %desc = (); +my %owner = (); # set the umask before creating any files umask($REPO_UMASK); @@ -253,14 +254,18 @@ sub parse_conf_file } } } - # very simple syntax for the gitweb description of repo - elsif (/^(\S+) = "(.*)"$/) + # very simple syntax for the gitweb description of repo; one of: + # reponame = "some description string" + # reponame "owner name" = "some description string" + elsif (/^(\S+)(?: "(.*?)")? = "(.*)"$/) { - my ($repo, $desc) = ($1, $2); + my ($repo, $owner, $desc) = ($1, $2, $3); die "$ABRT bad repo name $repo\n" unless $repo =~ $REPONAME_PATT; die "$WARN $fragment attempting to set description for $repo\n" if $fragment ne 'master' and $fragment ne $repo and ($groups{"\@$fragment"}{$repo} || '') ne 'master'; - $desc{$repo} = $desc; + $desc{"$repo.git"} = $desc; + $owner =~ s/ /+/g if $owner; # gitweb/INSTALL wants more, but meh...! + $owner{"$repo.git"} = $owner || ''; } else { @@ -375,10 +380,10 @@ my %projlist = (); for my $repo (sort keys %repos) { my $desc_file = "$repo.git/description"; # note: having a description also counts as enabling gitweb - if ($repos{$repo}{'R'}{'gitweb'} or $desc{$repo}) { + 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} and open(DESC, ">", $desc_file) and print DESC "$desc{$repo}\n" and close DESC; + $desc{"$repo.git"} and open(DESC, ">", $desc_file) and print DESC $desc{"$repo.git"} . "\n" and close DESC; } else { # delete the description file; no messages to user or error checking :) unlink $desc_file; @@ -387,7 +392,9 @@ for my $repo (sort keys %repos) { # update the project list my $projlist_fh = wrap_open( ">", $PROJECTS_LIST); -print $projlist_fh join("\n", sort keys %projlist), "\n" if %projlist; +for my $proj (sort keys %projlist) { + print $projlist_fh "$proj" . ( $owner{$proj} ? " $owner{$proj}" : "" ) . "\n"; +} close $projlist_fh; # ---------------------------------------------------------------------------- From d8cb62934fc391a4a8dc8e51679a2866d3f50877 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 27 Nov 2009 13:47:21 +0530 Subject: [PATCH 154/637] docs: document how to specify "owner" for gitweb --- README.mkd | 2 +- conf/example.conf | 8 ++++---- doc/2-admin.mkd | 11 ++++++++--- doc/3-faq-tips-etc.mkd | 4 ++++ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/README.mkd b/README.mkd index e1a9616..904d9f3 100644 --- a/README.mkd +++ b/README.mkd @@ -73,7 +73,7 @@ detail [here][gsdiff]. * config file syntax gets checked upfront, and much more thoroughly * if your requirements are still too complex, you can split up the config file and delegate authority over parts of it - * easier to specify gitweb "description" and gitweb/daemon access + * easier to specify gitweb owner, description and gitweb/daemon access * easier to sync gitweb (http) authorisation with gitolite's access config * more comprehensive logging [aka: management does not think "blame" is just a synonym for "annotate" :-)] diff --git a/conf/example.conf b/conf/example.conf index 752c279..e00ec25 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -130,13 +130,13 @@ repo @oss_repos repo linux perl R = gitweb -# GITWEB DESCRIPTION LINE +# REPO OWNER/DESCRIPTION LINE FOR GITWEB -# syntax: +# syntax, one of: # reponame = "some description string in double quotes" +# reponame "owner name" = "some description string in double quotes" # note: setting a description also gives gitweb access; you do not have to # give gitweb access as described above if you're specifying a description -gitolite = "fast, secure, access control for git in a corporate environment" - +gitolite "Sitaram Chamarty" = "fast, secure, access control for git in a corporate environment" diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index 7038fb9..d0460ec 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -57,13 +57,18 @@ so you may as well use that method and kill two birds with one stone, like so: gitolite = "fast, secure, access control for git in a corporate environment" +You can also specify an owner for gitweb to show, if you like: + + gitolite "Sitaram Chamarty" = "fast, secure, access control for git in a corporate environment" + Note that gitolite does **not** install or configure gitweb/daemon -- that is a one-time setup you must do separately. All this does is: * for daemon, create the file `git-daemon-export-ok` in the repository - * for gitweb, add the repo to the list of projects to be served by gitweb - (see the config file variable `$PROJECTS_LIST`, which should have the same - value you specified for `$projects_list` when setting up gitweb) + * for gitweb, add the repo (plus owner name, if given) to the list of + projects to be served by gitweb (see the config file variable + `$PROJECTS_LIST`, which should have the same value you specified for + `$projects_list` when setting up gitweb) * put the description, if given, in `$repo/description` The "compile" script will keep these files consistent with the config settings diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 6147e95..7450994 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -204,6 +204,10 @@ file: reponame = "one line of description" +You can also specify an "owner": + + reponame "owner name" = "one line of description" + To enable access to one or more repos via git daemon, just give "read" permissions to the special username `daemon`. From c3b5e3b1af5cca572a044da412e6822f968090a1 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 26 Nov 2009 21:30:59 +0530 Subject: [PATCH 155/637] compile, pm: factor out new repo creation ...also wrap_chdir, wrap_open, $ABRT, and $WARN --- src/gitolite.pm | 33 +++++++++++++++++++++++++++++++-- src/gl-compile-conf | 28 ++++------------------------ 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 2c325e2..c7276bd 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -9,8 +9,9 @@ # the name of this file will change as soon as its function/feature set # stabilises enough ;-) -# right now all it does is define a function that tells you where to find the -# rc file +# right now all it does is +# - define a function that tells you where to find the rc file +# - define a function that creates a new repo and give it our update hook # ---------------------------------------------------------------------------- # where is the rc file hiding? @@ -42,4 +43,32 @@ sub where_is_rc } } +$ABRT = "\n\t\t***** ABORTING *****\n "; +$WARN = "\n\t\t***** WARNING *****\n "; +sub wrap_chdir { + chdir($_[0]) or die "$ABRT chdir $_[0] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; +} + +sub wrap_open { + open (my $fh, $_[0], $_[1]) or die "$ABRT open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n" . + ( $_[2] || '' ); # suffix custom error message if given + return $fh; +} + +# NOTE: this sub will change your cwd; caller beware! +sub new_repo +{ + my ($repo, $hooks_dir) = @_; + + umask($REPO_UMASK); + + system("mkdir", "-p", "$repo.git") and die "$ABRT mkdir $repo.git failed: $!\n"; + # erm, note that's "and die" not "or die" as is normal in perl + wrap_chdir("$repo.git"); + system("git --bare init >&2"); + # propagate our own, plus any local admin-defined, hooks + system("cp $hooks_dir/* hooks/"); + chmod 0755, "hooks/update"; +} + 1; diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 6ca9b9b..d939e5f 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -52,12 +52,7 @@ $Data::Dumper::Sortkeys = 1; open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH); - -# now that this thing *may* be run via "push to admin", any errors have to -# grab the admin's ATTENTION so he won't miss them among the other messages a -# typical push generates -my $ABRT = "\n\t\t***** ABORTING *****\n "; -my $WARN = "\n\t\t***** WARNING *****\n "; +our ($ABRT, $WARN); # the common setup module is in the same directory as this running program is my $bindir = $0; @@ -118,16 +113,6 @@ umask($REPO_UMASK); # subroutines # ---------------------------------------------------------------------------- -sub wrap_chdir { - chdir($_[0]) or die "$ABRT chdir $_[0] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; -} - -sub wrap_open { - open (my $fh, $_[0], $_[1]) or die "$ABRT open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n" . - ( $_[2] || '' ); # suffix custom error message if given - return $fh; -} - sub expand_list { my @list = @_; @@ -337,15 +322,10 @@ for my $repo (keys %repos) { unless (-d "$repo.git") { - system("mkdir", "-p", "$repo.git") and die "$ABRT mkdir $repo.git failed: $!\n"; - # erm, note that's "and die" not "or die" as is normal in perl - wrap_chdir("$repo.git"); - system("git --bare init"); - # propagate our own, plus any local admin-defined, hooks - system("cp $GL_ADMINDIR/src/hooks/* hooks/"); - chmod 0755, "hooks/update"; - wrap_chdir("$repo_base_abs"); + new_repo($repo, "$GL_ADMINDIR/src/hooks"); $git_too_old++ if $git_version < 10602; # that's 1.6.2 to you + # new_repo would have chdir'd us away; come back + wrap_chdir("$repo_base_abs"); } } warn "\n\t\t***** WARNING *****\n" . From b78a720ceeecf7db985dd8d013c08829ea8cfc81 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 27 Nov 2009 23:00:58 +0530 Subject: [PATCH 156/637] auth/compile: auto-vivify is default now, so: the "create a new repo" code moves from compile to auth. Only someone who has W access can create it, but he can do so even on a "R" operation (like clone or ls-remote). This is a pre-requisite for rebel's wildcard repos, where autovivification is the only way you can create arbitrary repos matching a pattern. The only reason it's getting into master is because it looks cool! ---- OK that's a lie; the real reason is to keep the two branches as similar as possible, though they;ve diverged quite a bit since the "only one-line difference" days where "rebel" just meant "deny/exclude" rules!) --- src/gl-auth-command | 9 +++++++++ src/gl-compile-conf | 21 +++++---------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index 31d1fa8..a135be6 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -100,6 +100,15 @@ die "$perm access for $repo DENIED to $user\n" unless $repos{$repo}{$perm}{$user} or $repos{$repo}{$perm}{'@all'}; +# create the repo if it doesn't already exist and the user has "W" access +my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); +if ( ( $repos{$repo}{W}{$user} + or $repos{$repo}{W}{'@all'} ) and not -d "$repo_base_abs/$repo.git" ) { + wrap_chdir("$repo_base_abs"); + new_repo($repo, "$GL_ADMINDIR/src/hooks"); + wrap_chdir($ENV{HOME}); +} + # ---------------------------------------------------------------------------- # logging, timestamp. also setup env vars for later # ---------------------------------------------------------------------------- diff --git a/src/gl-compile-conf b/src/gl-compile-conf index d939e5f..583a3f0 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -312,27 +312,12 @@ my $git_version = `git --version`; my ($gv_maj, $gv_min, $gv_patchrel) = ($git_version =~ m/git version (\d+)\.(\d+)\.(\d+)/); die "$ABRT I can't understand $git_version\n" unless ($gv_maj >= 1); $git_version = $gv_maj*10000 + $gv_min*100 + $gv_patchrel; # now it's "normalised" -my $git_too_old = 0; -# repo-base needs to be an absolute path for this loop to work right -# so if it was not already absolute, prefix $HOME. -my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); -wrap_chdir("$repo_base_abs"); -for my $repo (keys %repos) -{ - unless (-d "$repo.git") - { - new_repo($repo, "$GL_ADMINDIR/src/hooks"); - $git_too_old++ if $git_version < 10602; # that's 1.6.2 to you - # new_repo would have chdir'd us away; come back - wrap_chdir("$repo_base_abs"); - } -} warn "\n\t\t***** WARNING *****\n" . "\tyour git version is older than 1.6.2\n" . "\tgitolite will work but you MUST read the section on\n" . "\t\"git version dependency\" in doc/3-faq-tips-etc.mkd\n" - if $git_too_old; + if $git_version < 10602; # that's 1.6.2 to you # ---------------------------------------------------------------------------- # handle gitweb and daemon @@ -343,6 +328,10 @@ warn "\n\t\t***** WARNING *****\n" . # :-) These are now "pseduo users" -- giving them "R" access to a repo is all # you have to do +# repo-base needs to be an absolute path for this loop to work right +# so if it was not already absolute, prefix $HOME. +my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); + wrap_chdir("$repo_base_abs"); # daemons first... From 6576e82e3342a869e18845df432ef4128e693131 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 27 Nov 2009 23:53:38 +0530 Subject: [PATCH 157/637] easy install: needs a minor fix to accommodate auto-vivification --- src/gl-easy-install | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gl-easy-install b/src/gl-easy-install index c577307..cf3a859 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -398,6 +398,7 @@ setup_pta() { # Substitute $GL_ADMINDIR and $REPO_BASE appropriately. Note there is no # space around the "=" in the second and third lines. + git ls-remote gitolite:gitolite-admin echo "cd $REPO_BASE/gitolite-admin.git GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir GIT_WORK_TREE=$GL_ADMINDIR git diff --cached --quiet || GIT_WORK_TREE=$GL_ADMINDIR git commit -am start From bfc3b6cd58510cd4d1bc8b538cc3d320248ccabb Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 30 Nov 2009 07:00:31 +0530 Subject: [PATCH 158/637] example conf: clarify group name parsing --- conf/example.conf | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index e00ec25..6bdc834 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -24,9 +24,13 @@ # syntax: # @groupname = [one or more names] -# groups let you club names together for convenience in specifying -# permissions. A group is simply expanded to whatever names are on the right -# hand side when it is actually used +# groups let you club (user or group) names together for convenience + +# * a group is like a #define in C except that it can *accumulate* values +# * the config file is parsed in a single-pass, so later *additions* to a +# group name cannot affect earlier *uses* of it + +# The following examples should illustrate all this: # you can have a group of people... @staff = sitaram some_dev another-dev @@ -43,10 +47,14 @@ @staff = au.thor # so now "@staff" expands to all 4 names - # groups can include other groups (but not recursively) + # groups can include other groups, and the included group will + # be expanded to whatever value it currently has @interns = indy james @staff = bob @interns # "@staff" expands to 7 names now +@interns = han + # "@interns" now has 3 names in it, but note that this does + # not change @staff # REPO AND BRANCH PERMISSIONS # --------------------------- From 604669ca02cd11b5ebe9375f7a4fbdd898ad2f8c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 18 Sep 2009 18:03:53 +0530 Subject: [PATCH 159/637] rebel edition -- cos when you need it, you need it bad :-) Summary: much as I did not want to use "excludes", I guess if we don't put the code in "master" it's OK to at least *write* (and test) the code! See the example config file for how to use it. See "design choices" section in the "faq, tips, etc" document for how it works. --- README.mkd | 21 ++++++++++++++++++ conf/example.conf | 20 +++++++++++++++++ doc/3-faq-tips-etc.mkd | 50 ++++++++++++++++++++++++++++++++++++++++++ src/gl-compile-conf | 2 +- src/hooks/update | 1 + 5 files changed, 93 insertions(+), 1 deletion(-) diff --git a/README.mkd b/README.mkd index 904d9f3..087bf77 100644 --- a/README.mkd +++ b/README.mkd @@ -3,6 +3,27 @@ > [Update 2009-10-28: apart from all the nifty new features, there's now an > "easy install" script in the src directory. This script can be used to > install as well as upgrade a gitolite install. Please see the INSTALL + +> ---- + +> ***REBEL FORK WARNING*** + +> The paranoid half of Sitaram's brain would like to warn you that the other +> half rebelled and created this fork. + +> 1. please read the "design choices" section in the [faqs, tips, etc] +> [fted] document for background +> 2. please read the comments in the example.conf file carefully before +> creating your own config file +> 3. please test your configuration carefully +> 4. if you still have problems and contact me, please mention right up +> front that you're using the ***rebel edition***; makes it easier to +> troubleshoot + +[fted]: http://github.com/sitaramc/gitolite/blob/rebel/doc/3-faq-tips-etc.mkd + +---- + > document in the doc directory for details] ---- diff --git a/conf/example.conf b/conf/example.conf index 6bdc834..9a196bc 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -123,6 +123,26 @@ repo git RW tmp/ = @all RW refs/tags/v[0-9] = junio +# REBEL BRANCH -- DENY/EXCLUDE RULES + +# please see the "faq, tips, etc" document for details on the "rebel" edition. + +# in the example above, you cannot easily say "anyone can write any tag, +# except version tags can only be written by junio". The following might look +# like it works but it doesn't: + + # RW refs/tags/v[0-9] = junio + # RW refs/tags/ = junio linus pasky @others + +# in the "rebel" branch, however, you can do this: + + RW refs/tags/v[0-9] = junio + - refs/tags/v[0-9] = linus pasky @others + RW refs/tags/ = junio linus pasky @others + +# Briefly, the rule is: the first matching refex that has the operation you're +# looking for (`W` or `+`), or a minus (`-`), results in success, or failure, +# respectively. A fallthrough also results in failure # GITWEB AND DAEMON STUFF # ----------------------- diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 7450994..b98050c 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -22,6 +22,7 @@ In this document: * design choices * keeping the parser and the access control separate * why we don't do "excludes" + * how the rebel edition does it anyway :) ### common errors and mistakes @@ -527,3 +528,52 @@ The lack of overlap between refexes ensures ***no confusion*** in specifying, understanding, and ***auditing***, what is allowed and what is not. And in security, "no confusion" is a good thing :-) + +#### how the rebel edition does it anyway :) + +Well, reuss on #git asked, I pointed him to the discussion above and said +"no", and he was nice enough to agree. But then I started thinking that maybe +not everyone needs such "safe" environments. And so, as the README says: + +> The paranoid half of Sitaram's brain would like to warn you that the other +> half rebelled and created this fork. + +Let's recap the **existing semantics**: + +> the first matching refex that has the permission you're looking for (`W` +> or `+`), results in success. A fallthrough results in failure + +First I changed the format of the compiled config file (an internal file that +the user doesn't have to worry about, as long as he remembers to run the +compile script on every upgrade). The format change kept the existing syntax +and semantics of the config file, but it laid the ground work for allowing +"excludes" using **just one extra line of code** (and a very slight change to +another)! + +This extremely small delta between the rebel edition and the main one will +make it trivial to maintain it as a separate branch, regardless of what other +changes happen in the mainline, because I honestly don't see rebel merging +into the mainline. + +Yet ;-) + +Here are the **rebel semantics**, with changes from the "main" one in bold: + +> the first matching refex that has the permission you're looking for (`W` +> or `+`) **or a minus (`-`)**, results in success **or failure, +> respectively**. A fallthrough **also** results in failure + +So the example we started with becomes, in the rebel edition: + + RW refs/tags/v[0-9].* = bruce + - refs/tags/v[0-9].* = @staff + RW refs/tags = @staff + +And here's how it works: + + * for non-version tags, only the 3rd rule matches, so anyone on staff can + push them + * for version tags by bruce, the first rule matches so he can push them + * for version tags by staffers *other than bruce*, the second rule matches + before the third one, and it has a `-` as the permission, so the push + fails diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 583a3f0..e307e2b 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -186,7 +186,7 @@ sub parse_conf_file @repos = expand_list ( @repos ); } # actual permission line - elsif (/^(R|RW|RW\+) (.* )?= (.+)/) + elsif (/^(-|R|RW|RW\+) (.* )?= (.+)/) { my $perms = $1; my @refs; @refs = split(' ', $2) if $2; diff --git a/src/hooks/update b/src/hooks/update index a11b9ad..19f02e8 100755 --- a/src/hooks/update +++ b/src/hooks/update @@ -85,6 +85,7 @@ sub check_ref { $refex = (keys %$ar)[0]; # refex? sure -- a regex to match a ref against :) next unless $ref =~ /$refex/; + die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-'; # as far as *this* ref is concerned we're ok return if ($ar->{$refex} =~ /\Q$perm/); From 601eaf8ea16efc3726d949d17b277239575fe97e Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Oct 2009 10:15:55 +0530 Subject: [PATCH 160/637] tips doc: add pointer to later section on excludes --- doc/3-faq-tips-etc.mkd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index b98050c..8763b6e 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -179,6 +179,9 @@ Each refex that allows `W` access (or `+` if this is a rewind) for *this* user, on *this* repo, is matched against the actual refname being updated. If any of the refexes match, the push succeeds. If none of them match, it fails. +The normal gitolite does not allow "exclude" or "deny" rules. But see the +"how the rebel edition does it anyway" below. + #### error checking the config file gitosis does not do any. I just found out that if you mis-spell `members` as From e922dfb939b051a03c34047f2f92bde1245932f6 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 16 Nov 2009 20:05:08 +0530 Subject: [PATCH 161/637] compile: allow PATH/foo and populate the hash correctly --- src/gl-compile-conf | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index e307e2b..3f1e231 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -194,9 +194,9 @@ sub parse_conf_file # if no ref is given, this PERM applies to all refs @refs = qw(refs/.*) unless @refs; - # fully qualify refs that dont start with "refs/"; prefix them with - # "refs/heads/" - @refs = map { m(^refs/) or s(^)(refs/heads/); $_ } @refs; + # fully qualify refs that dont start with "refs/" or "PATH/"; + # prefix them with "refs/heads/" + @refs = map { m(^(refs|PATH)/) or s(^)(refs/heads/); $_ } @refs; # expand the user list, unless it is just "@all" @users = expand_list ( @users ) @@ -233,6 +233,13 @@ sub parse_conf_file # for 2nd level check, store each "ref, perms" pair in order for my $ref (@refs) { + # checking PATH based restrictions is expensive for + # the update hook (see the changes to src/hooks/update + # in this commit for why) so we would *very* much like + # to avoid doing it for the large majority of repos + # that do *not* use PATH limits. Setting a flag that + # can be checked right away will help us do that + $repos{$repo}{PATH_LIMITS} = 1 if $ref =~ /^PATH\//; push @{ $repos{$repo}{$user} }, { $ref => $perms } unless $rurp_seen{$repo}{$user}{$ref}{$perms}++; } From 498e62c2f3a90513b90dcc7b916b28387cfdcb0f Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 16 Nov 2009 21:45:55 +0530 Subject: [PATCH 162/637] update hook: allow multiple "refs" to be checked --- src/hooks/update | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/hooks/update b/src/hooks/update index 19f02e8..235fa40 100755 --- a/src/hooks/update +++ b/src/hooks/update @@ -47,9 +47,6 @@ chomp($merge_base = `git merge-base $oldsha $newsha`) unless $oldsha eq '0' x 40 or $newsha eq '0' x 40; -# some of this is from an example hook in Documentation/howto of git.git, with -# some variations - # what are you trying to do? (is it 'W' or '+'?) my $perm = 'W'; # rewriting a tag is considered a rewind, in terms of permissions @@ -65,6 +62,25 @@ push @allowed_refs, { "$PERSONAL/$ENV{GL_USER}/" => "RW+" } if $PERSONAL; push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$ENV{GL_USER}} || [] }; push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{'@all'} || [] }; +# prepare the list of refs to be checked + +# previously, we just checked $ref -- the ref being updated, which is passed +# to us by git (see man githooks). Now we also have to treat each PATH being +# updated as a potential "ref" and check that, if PATH-based restrictions have +# been specified + +my @refs = ($ref); # the first ref to check is the real one +if (exists $repos{$ENV{GL_REPO}}{PATH_LIMITS}) { + # this is special to git -- the hash of an empty tree + my $empty='4b825dc642cb6eb9a060e54bf8d69288fbee4904'; + # well they're not really "trees" but $empty is indeed the empty tree so + # we can just pretend $oldsha/$newsha are also trees, and anyway 'git + # diff' only wants trees + my $oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha; + my $newtree = $newsha eq '0' x 40 ? $empty : $newsha; + push @refs, map { chomp; s/^/PATH\//; $_; } `git diff --name-only $oldtree $newtree`; +} + my $refex = ''; # check one ref @@ -88,21 +104,25 @@ sub check_ref { die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-'; # as far as *this* ref is concerned we're ok - return if ($ar->{$refex} =~ /\Q$perm/); + return $refex if ($ar->{$refex} =~ /\Q$perm/); } die "$perm $ref $ENV{GL_REPO} $ENV{GL_USER} DENIED by fallthru\n"; } -# and in this version, we have only one ref to check -check_ref($ref); +# and in this version, we have many "refs" to check. The one we print in the +# log is the *first* one (which is a *real* ref, like refs/heads/master), +# while all the rest (if they exist) are like PATH/something. So we do the +# first one separately to capture it, then run the rest (if any) +my $log_refex = check_ref(shift @refs); +check_ref($_) for @refs; -# if we returned at all, the check succeeded, so we log the action and exit 0 +# if we returned at all, all the checks succeeded, so we log the action and exit 0 # logging note: if log failure isn't important enough to block pushes, get rid # of all the error checking open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!\n"; print $log_fh "$ENV{GL_TS} $perm\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . - "\t$ENV{GL_REPO}\t$ref\t$ENV{GL_USER}\t$refex\n"; + "\t$ENV{GL_REPO}\t$ref\t$ENV{GL_USER}\t$log_refex\n"; close $log_fh or die "close log failed: $!\n"; exit 0; From d71720d050089ae2443ce9b4b64d521f43ee67ef Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 1 Dec 2009 07:15:05 +0530 Subject: [PATCH 163/637] fold rebel into master :) [please read] Well, something even more outrageous than deny rules and path-based limits came along, so I decided that "rebel" was actually quite "conformist" in comparision ;-) Jokes apart, the fact is that the access control rules, even when using deny rules and path-limits, are still *auditable*. Which means it is good enough for "corporate use". [The stuff that I'm working on now takes away the auditability aspect -- individual users can "own" repos, create rules for themselves, etc. So let's just say that is the basis of distinguishing "master" now.] --- README.mkd | 26 +------ conf/example.conf | 13 +++- doc/3-faq-tips-etc.mkd | 156 ++++++++++++++--------------------------- 3 files changed, 63 insertions(+), 132 deletions(-) diff --git a/README.mkd b/README.mkd index 087bf77..b766f7e 100644 --- a/README.mkd +++ b/README.mkd @@ -3,28 +3,7 @@ > [Update 2009-10-28: apart from all the nifty new features, there's now an > "easy install" script in the src directory. This script can be used to > install as well as upgrade a gitolite install. Please see the INSTALL - -> ---- - -> ***REBEL FORK WARNING*** - -> The paranoid half of Sitaram's brain would like to warn you that the other -> half rebelled and created this fork. - -> 1. please read the "design choices" section in the [faqs, tips, etc] -> [fted] document for background -> 2. please read the comments in the example.conf file carefully before -> creating your own config file -> 3. please test your configuration carefully -> 4. if you still have problems and contact me, please mention right up -> front that you're using the ***rebel edition***; makes it easier to -> troubleshoot - -[fted]: http://github.com/sitaramc/gitolite/blob/rebel/doc/3-faq-tips-etc.mkd - ----- - -> document in the doc directory for details] +> document for details] ---- @@ -100,8 +79,7 @@ detail [here][gsdiff]. a synonym for "annotate" :-)] * "personal namespace" prefix for each dev * migration guide and simple converter for gitosis conf file - * "exclude" (or "deny") rights in the config file -- this is the "rebel" - branch in the repository, and always will be ;-) + * "exclude" (or "deny") rights at the branch/tag level ### security diff --git a/conf/example.conf b/conf/example.conf index 9a196bc..64ac059 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -99,6 +99,10 @@ repo @oss_repos # ADVANCED PERMISSIONS USING REFEXES # - refexes are specified in perl regex syntax +# - refexes are matched without any anchoring, which means a refex like +# "refs/tags/v[0-9]" matches anything *containing* that pattern. There +# may be text before and after it (example: refs/tags/v4-r3p7), and it +# will still match # - if no refex appears, the rule applies to all refs in that repo # - a refex is automatically prefixed by "refs/heads/" if it doesn't start # with "refs/" (so tags have to be explicitly named as @@ -123,9 +127,11 @@ repo git RW tmp/ = @all RW refs/tags/v[0-9] = junio -# REBEL BRANCH -- DENY/EXCLUDE RULES +# DENY/EXCLUDE RULES -# please see the "faq, tips, etc" document for details on the "rebel" edition. +# ***IMPORTANT NOTE: if you use deny rules, the order of the rules also makes +# a difference, where earlier it did not. Please review your ruleset +# carefully or test it***. Or ask me. # in the example above, you cannot easily say "anyone can write any tag, # except version tags can only be written by junio". The following might look @@ -134,7 +140,8 @@ repo git # RW refs/tags/v[0-9] = junio # RW refs/tags/ = junio linus pasky @others -# in the "rebel" branch, however, you can do this: +# if you use "deny" rules, however, you can do this (a "deny" rule just uses +# "-" instead of "R" or "RW" or "RW+" in the permission field) RW refs/tags/v[0-9] = junio - refs/tags/v[0-9] = linus pasky @others diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 8763b6e..c112fd4 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -17,12 +17,10 @@ In this document: * one user, many keys * support for git installed outside default PATH * what repos do I have access to? - * other cool things + * "exclude" (or "deny") rules * "personal" branches * design choices * keeping the parser and the access control separate - * why we don't do "excludes" - * how the rebel edition does it anyway :) ### common errors and mistakes @@ -179,8 +177,8 @@ Each refex that allows `W` access (or `+` if this is a rewind) for *this* user, on *this* repo, is matched against the actual refname being updated. If any of the refexes match, the push succeeds. If none of them match, it fails. -The normal gitolite does not allow "exclude" or "deny" rules. But see the -"how the rebel edition does it anyway" below. +Gitolite also allows "exclude" or "deny" rules. See later in this document +for details. #### error checking the config file @@ -404,7 +402,54 @@ Note that until this version, we used to put out an ugly `need SSH_ORIGINAL_COMMAND` error, just like gitosis used to. All we did is put that code path to better use :-) -### other cool things +#### "exclude" (or "deny") rules + +Take a look at the following snippet, which *seems* to say that "bruce" can +write versioned tags (anything containing `refs/tags/v[0-9]`), but the other +staffers can't: + + @staff = bruce whitfield martin + [... and later ...] + RW refs/tags/v[0-9] = bruce + RW refs/tags = @staff + +But that's not how the matching works. As long as any refex matches the +refname being updated, it's a "yes". Since the second refex (which says +"anything containing `refs/tags`") is a superset of the first one, it lets +anyone on `@staff` create versioned tags, not just Bruce. + +One way to fix this is to allow "excludes" -- some changes in syntax, combined +with a rigorous, ordered, interpretation would do it. + +Let's recap the **existing semantics**: + +> the first matching refex that has the permission you're looking for (`W` +> or `+`), results in success. A fallthrough results in failure + +Here are the **new semantics**, with changes from the "main" one in bold: + +> the first matching refex that has the permission you're looking for (`W` +> or `+`) **or a minus (`-`)**, results in success **or failure, +> respectively**. A fallthrough **also** results in failure + +So the example we started with becomes, if you use "deny" rules: + + RW refs/tags/v[0-9] = bruce + - refs/tags/v[0-9] = @staff + RW refs/tags = @staff + +And here's how it works: + + * for non-version tags, only the 3rd rule matches, so anyone on staff can + push them + * for version tags by bruce, the first rule matches so he can push them + * for version tags by staffers *other than bruce*, the second rule matches + before the third one, and it has a `-` as the permission, so the push + fails + +***IMPORTANT NOTE: if you use deny rules, the order of the rules also makes a +difference, where earlier it did not. Please review your ruleset carefully or +test it***. Or ask me. #### "personal" branches @@ -481,102 +526,3 @@ have to be first "compiled", and the access control programs use this If you choose the "easy install" method, all this is quite transparent to you anyway. If you cannot use the easy install and must install manually, I have clear instructions on how to set it up. - -#### why we don't do "excludes" - -[umm... having said all this, I implemented it anyway; see the "rebel" -branch!] - -I found an error in the example conf file. This snippet *seems* to say that -"bruce" can write versioned tags (`refs/tags/v[0-9].*`), but the other -staffers can't: - - @staff = bruce whitfield martin - [... and later ...] - RW refs/tags/v[0-9].* = bruce - RW refs/tags = @staff - -But that's not how the matching works. As long as any refex matches the -refname being updated, it's a "yes". So the second refex lets anyone on -`@staff` create versioned tags, not just Bruce. - -One way to fix this is to allow "excludes" -- some changes in syntax, combined -with a rigorous, ordered, interpretation would do it. - -But if you're ever played with squid ACLs, or the include/exclude rules for -rsync, or rdiff-backup, or even git's own ignore mechanism, you'll see why I -won't do this. It bloats the code and the docs, and, despite all the docs, -*still* confuses people, which may then *reduce* security! - -Squid, rsync, gitignore, and all *need* the feature and so tolerate all this; -but we don't need it. All we need to do is make the refexes *disjoint* in -what they match (i.e., ensure that no refname can be matched by more than one -refex): - - RW refs/tags/v[0-9].* = bruce - RW refs/tags/staff/ = @staff - -In general, you probably want to control the refnames writable by devs anyway, -if at least to maintain some sanity, so being forced to make the refexes -disjoint is not a big problem. Here's an example: only the `project_lead` can -make arbitrarily named refs, while the rest have to stay within their assigned -namespaces: - - RW+ = project_lead - RW refs/tags/qa/ = @qa_team - RW bugID/ = @dev_team - RW trac/ = @dev_team - -The lack of overlap between refexes ensures ***no confusion*** in specifying, -understanding, and ***auditing***, what is allowed and what is not. - -And in security, "no confusion" is a good thing :-) - -#### how the rebel edition does it anyway :) - -Well, reuss on #git asked, I pointed him to the discussion above and said -"no", and he was nice enough to agree. But then I started thinking that maybe -not everyone needs such "safe" environments. And so, as the README says: - -> The paranoid half of Sitaram's brain would like to warn you that the other -> half rebelled and created this fork. - -Let's recap the **existing semantics**: - -> the first matching refex that has the permission you're looking for (`W` -> or `+`), results in success. A fallthrough results in failure - -First I changed the format of the compiled config file (an internal file that -the user doesn't have to worry about, as long as he remembers to run the -compile script on every upgrade). The format change kept the existing syntax -and semantics of the config file, but it laid the ground work for allowing -"excludes" using **just one extra line of code** (and a very slight change to -another)! - -This extremely small delta between the rebel edition and the main one will -make it trivial to maintain it as a separate branch, regardless of what other -changes happen in the mainline, because I honestly don't see rebel merging -into the mainline. - -Yet ;-) - -Here are the **rebel semantics**, with changes from the "main" one in bold: - -> the first matching refex that has the permission you're looking for (`W` -> or `+`) **or a minus (`-`)**, results in success **or failure, -> respectively**. A fallthrough **also** results in failure - -So the example we started with becomes, in the rebel edition: - - RW refs/tags/v[0-9].* = bruce - - refs/tags/v[0-9].* = @staff - RW refs/tags = @staff - -And here's how it works: - - * for non-version tags, only the 3rd rule matches, so anyone on staff can - push them - * for version tags by bruce, the first rule matches so he can push them - * for version tags by staffers *other than bruce*, the second rule matches - before the third one, and it has a `-` as the permission, so the push - fails From e7e608535116a8f4257c467a2e54e26007cbb839 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 1 Dec 2009 21:54:23 +0530 Subject: [PATCH 164/637] compile: fix description and export-ok problem part of comment on b78a720ceeecf7db985dd8d013c08829ea8cfc81: The only reason it's getting into master is because it looks cool! I hate it when something that looks cool doesn't work right :( creating a repo on gitolite-admin push is *needed* in order to get descriptions and export-ok files to work right --- src/gl-compile-conf | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 3f1e231..f79b713 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -320,6 +320,20 @@ my ($gv_maj, $gv_min, $gv_patchrel) = ($git_version =~ m/git version (\d+)\.(\d+ die "$ABRT I can't understand $git_version\n" unless ($gv_maj >= 1); $git_version = $gv_maj*10000 + $gv_min*100 + $gv_patchrel; # now it's "normalised" +# repo-base needs to be an absolute path for this loop to work right +# so if it was not already absolute, prefix $HOME. +my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); + +wrap_chdir("$repo_base_abs"); + +for my $repo (sort keys %repos) { + unless (-d "$repo.git") { + new_repo($repo, "$GL_ADMINDIR/src/hooks"); + # new_repo would have chdir'd us away; come back + wrap_chdir("$repo_base_abs"); + } +} + warn "\n\t\t***** WARNING *****\n" . "\tyour git version is older than 1.6.2\n" . "\tgitolite will work but you MUST read the section on\n" . @@ -335,10 +349,6 @@ warn "\n\t\t***** WARNING *****\n" . # :-) These are now "pseduo users" -- giving them "R" access to a repo is all # you have to do -# repo-base needs to be an absolute path for this loop to work right -# so if it was not already absolute, prefix $HOME. -my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); - wrap_chdir("$repo_base_abs"); # daemons first... From a283b8ad4948ebaed9cfe44ad4210df28deeeebf Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 1 Dec 2009 22:07:46 +0530 Subject: [PATCH 165/637] compile: kill preceding space when killing comments consider: repo = "some desc" # some comment (and note that the regex for recognising a description expects that dblquote to be the *last* character on the line) --- src/gl-compile-conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index f79b713..bbb2eb6 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -165,7 +165,7 @@ sub parse_conf_file s/^ //; s/ $//; # kill comments - s/#.*//; + s/\s*#.*//; # and blank lines next unless /\S/; From c3dbdae134b36498b2b6540a144b46d50209fbe3 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 2 Dec 2009 11:49:58 +0530 Subject: [PATCH 166/637] easy install tail message was apparently too confusing --- src/gl-easy-install | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/gl-easy-install b/src/gl-easy-install index cf3a859..db20e2f 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -581,15 +581,14 @@ CONFIG FILE FORMAT: see comments in conf/example.conf in the gitolite source. SSH MAGIC: Remember you (the admin) now have *two* keys to access the server hosting your gitolite setup -- one to get you a command line, and one to get you gitolite access; see doc/6-ssh-troubleshooting.mkd. If you're not using -keychain or some such software, you may have to run this each time you log in: +keychain or some such software, you may have to run an 'ssh-add' command to +add that key each time you log in. - ssh-add ~/.ssh/\$admin_name +URLS: *Your* URL for cloning any repo on this server is different from the +url that the *other* users have to use. The easy install command should tell +you what these URLs look like, at the end of each successful run. Feel free +to re-run easy install again (using the same arguments) if you missed it. -URLS: *Your* URL for cloning any repo on this server will be - - gitolite:reponame - -*Other* users you set up will have to use - - \$user@\$host:reponame +UPGRADING GITOLITE: just pull a fresh clone from github, and run the same easy +install command as before, with the same arguments. " From e6da853082689ff6c44c57e29574a2c85c90c9c1 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 4 Dec 2009 09:51:22 +0530 Subject: [PATCH 167/637] auth, compile, pm: good bit of refactoring all of this is prep for the upcoming, all-new, chrome-plated, "wildrepos" branch :) - many variables go to gitolite.pm now, and are "our"d into the other files as needed - new functions parse_acl, report_basic to replace inlined code --- src/gitolite.pm | 75 ++++++++++++++++++++++++++++++++++++++------- src/gl-auth-command | 37 ++++++++-------------- src/gl-compile-conf | 11 +++---- 3 files changed, 82 insertions(+), 41 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index c7276bd..71c0b0c 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -13,6 +13,35 @@ # - define a function that tells you where to find the rc file # - define a function that creates a new repo and give it our update hook +# ---------------------------------------------------------------------------- +# common definitions +# ---------------------------------------------------------------------------- + +$ABRT = "\n\t\t***** ABORTING *****\n "; +$WARN = "\n\t\t***** WARNING *****\n "; + +# commands we're expecting +$R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/; +$W_COMMANDS=qr/^git[ -]receive-pack$/; + +# note that REPONAME_PATT allows a "/" also, which USERNAME_PATT doesn't +$REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern +$USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern + +# ---------------------------------------------------------------------------- +# convenience subs +# ---------------------------------------------------------------------------- + +sub wrap_chdir { + chdir($_[0]) or die "$ABRT chdir $_[0] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; +} + +sub wrap_open { + open (my $fh, $_[0], $_[1]) or die "$ABRT open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n" . + ( $_[2] || '' ); # suffix custom error message if given + return $fh; +} + # ---------------------------------------------------------------------------- # where is the rc file hiding? # ---------------------------------------------------------------------------- @@ -43,17 +72,9 @@ sub where_is_rc } } -$ABRT = "\n\t\t***** ABORTING *****\n "; -$WARN = "\n\t\t***** WARNING *****\n "; -sub wrap_chdir { - chdir($_[0]) or die "$ABRT chdir $_[0] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; -} - -sub wrap_open { - open (my $fh, $_[0], $_[1]) or die "$ABRT open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n" . - ( $_[2] || '' ); # suffix custom error message if given - return $fh; -} +# ---------------------------------------------------------------------------- +# create a new repository +# ---------------------------------------------------------------------------- # NOTE: this sub will change your cwd; caller beware! sub new_repo @@ -71,4 +92,36 @@ sub new_repo chmod 0755, "hooks/update"; } +# ---------------------------------------------------------------------------- +# parse the compiled acl +# ---------------------------------------------------------------------------- + +sub parse_acl +{ + my $GL_CONF_COMPILED = shift; + die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; +} + +# ---------------------------------------------------------------------------- +# print a report of $user's basic permissions +# ---------------------------------------------------------------------------- + +# basic means wildcards will be shown as wildcards; this is pretty much what +# got parsed by the compile script +sub report_basic +{ + my($GL_ADMINDIR, $GL_CONF_COMPILED, $user) = @_; + + &parse_acl($GL_CONF_COMPILED); + + # send back some useful info if no command was given + print "hello $user, the gitolite version here is "; + system("cat", "$GL_ADMINDIR/src/VERSION"); + print "\ryou have the following permissions:\n\r"; + for my $r (sort keys %repos) { + my $perm .= ( $repos{$r}{R}{'@all'} ? ' @' : ( $repos{$r}{R}{$user} ? ' R' : '' ) ); + $perm .= ( $repos{$r}{W}{'@all'} ? ' @' : ( $repos{$r}{W}{$user} ? ' W' : '' ) ); + print "$perm\t$r\n\r" if $perm; + } +} 1; diff --git a/src/gl-auth-command b/src/gl-auth-command index a135be6..86fec88 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -24,7 +24,10 @@ use warnings; # ---------------------------------------------------------------------------- +# these are set by the "rc" file our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $GL_ADMINDIR); +# and these are set by gitolite.pm +our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT); our %repos; # the common setup module is in the same directory as this running program is @@ -35,20 +38,10 @@ require "$bindir/gitolite.pm"; # ask where the rc file is, get it, and "do" it &where_is_rc(); die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; -# then "do" the compiled config file, whose name we now know -die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; # add a custom path for git binaries, if specified $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; -# ---------------------------------------------------------------------------- -# definitions specific to this program -# ---------------------------------------------------------------------------- - -my $R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/; -my $W_COMMANDS=qr/^git[ -]receive-pack$/; -my $REPONAME_PATT=qr(^[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern - # ---------------------------------------------------------------------------- # start... # ---------------------------------------------------------------------------- @@ -62,15 +55,7 @@ my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere! # SSH_ORIGINAL_COMMAND must exist; if not, we die with a nice message unless ($ENV{SSH_ORIGINAL_COMMAND}) { - # send back some useful info if no command was given - print "hello $user, the gitolite version here is "; - system("cat", "$GL_ADMINDIR/src/VERSION"); - print "\ryou have the following permissions:\n\r"; - for my $r (sort keys %repos) { - my $perm .= ( $repos{$r}{R}{'@all'} ? ' @' : ( $repos{$r}{R}{$user} ? ' R' : '' ) ); - $perm .= ( $repos{$r}{W}{'@all'} ? ' @' : ( $repos{$r}{W}{$user} ? ' W' : '' ) ); - print "$perm\t$r\n\r" if $perm; - } + &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user); exit 1; } @@ -93,6 +78,9 @@ die "bad command: $cmd. Make sure the repo name is exactly as in your config\n" # first level permissions check # ---------------------------------------------------------------------------- +# parse the compiled acl; goes into %repos (global) +&parse_acl($GL_CONF_COMPILED); + # we know the user and repo; we just need to know what perm he's trying my $perm = ($verb =~ $R_COMMANDS ? 'R' : 'W'); @@ -102,11 +90,12 @@ die "$perm access for $repo DENIED to $user\n" # create the repo if it doesn't already exist and the user has "W" access my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); -if ( ( $repos{$repo}{W}{$user} - or $repos{$repo}{W}{'@all'} ) and not -d "$repo_base_abs/$repo.git" ) { - wrap_chdir("$repo_base_abs"); - new_repo($repo, "$GL_ADMINDIR/src/hooks"); - wrap_chdir($ENV{HOME}); +if ( not -d "$repo_base_abs/$repo.git" ) { + if ( $repos{$repo}{W}{$user} or $repos{$repo}{W}{'@all'} ) { + wrap_chdir("$repo_base_abs"); + new_repo($repo, "$GL_ADMINDIR/src/hooks"); + wrap_chdir($ENV{HOME}); + } } # ---------------------------------------------------------------------------- diff --git a/src/gl-compile-conf b/src/gl-compile-conf index bbb2eb6..56b11ed 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -51,8 +51,10 @@ $Data::Dumper::Sortkeys = 1; # setup quiet mode if asked; please do not use this when running manually open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); +# these are set by the "rc" file our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH); -our ($ABRT, $WARN); +# and these are set by gitolite.pm +our ($REPONAME_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN); # the common setup module is in the same directory as this running program is my $bindir = $0; @@ -71,11 +73,8 @@ $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; # ---------------------------------------------------------------------------- # command and options for authorized_keys -my $AUTH_COMMAND="$GL_ADMINDIR/src/gl-auth-command"; -my $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; -# note that REPONAME_PATT allows a "/" also, which USERNAME_PATT doesn't -my $REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern -my $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern +$AUTH_COMMAND="$GL_ADMINDIR/src/gl-auth-command"; +$AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; # groups can now represent user groups or repo groups. From 8a4bb453a0d40e90cfab79997c6a32aa8e468e6b Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 5 Dec 2009 14:41:08 +0530 Subject: [PATCH 168/637] document that @all doesnt work as expected in deny rules @all in a deny rule doesnt work as it might look in the config file, because @all rights are checked last. This is fine if you dont have any DENYs (and so rule order doesn't matter), but with DENY it causes some problems. I never bothered to document it because I did not expect that any repo that is "serious" enough to have deny rules *at all* should then allow *any* kind of "write* access to @all. That's a very big contradiction in terms of paranoia! Translation: this will not be supported. Don't bother asking. You know who you are :) --- conf/example.conf | 3 ++- doc/3-faq-tips-etc.mkd | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index 64ac059..b5ead17 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -131,7 +131,8 @@ repo git # ***IMPORTANT NOTE: if you use deny rules, the order of the rules also makes # a difference, where earlier it did not. Please review your ruleset -# carefully or test it***. Or ask me. +# carefully or test it. In particular, do not use `@all` in a deny rule -- it +# won't work as you might expect***. # in the example above, you cannot easily say "anyone can write any tag, # except version tags can only be written by junio". The following might look diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index c112fd4..464a4e5 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -404,6 +404,11 @@ that code path to better use :-) #### "exclude" (or "deny") rules +***IMPORTANT CAVEAT: if you use deny rules, the order of the rules also makes +a difference, where earlier it did not. Please review your ruleset carefully +or test it. In particular, do not use `@all` in a deny rule -- it won't work +as you might expect***. + Take a look at the following snippet, which *seems* to say that "bruce" can write versioned tags (anything containing `refs/tags/v[0-9]`), but the other staffers can't: @@ -447,10 +452,6 @@ And here's how it works: before the third one, and it has a `-` as the permission, so the push fails -***IMPORTANT NOTE: if you use deny rules, the order of the rules also makes a -difference, where earlier it did not. Please review your ruleset carefully or -test it***. Or ask me. - #### "personal" branches "personal" branches are great for corporate environments, where From 77306567e96b2551c1bf126d80eceb0b176093ee Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 5 Dec 2009 18:38:46 +0530 Subject: [PATCH 169/637] wildrepos: teach compile the new syntax There's a new "C" permission to let someone *create* a repo that matches the pattern given in the "repo ..." line. If the word CREATER appears in the repo pattern, then that is forced to the actual user performing that operation. Something like this (we'll discuss READERS and WRITERS later): repo personal/CREATER/.+ C = @staff R [foo] = READERS RW [bar] = WRITERS ...various other permissions as usual... Delegation checking also changes quite a bit... see comments in code Implementation: there's also a sneaky little trick we're playing here with the dumped hash --- src/gitolite.pm | 2 ++ src/gl-compile-conf | 66 ++++++++++++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 71c0b0c..5864466 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -27,6 +27,8 @@ $W_COMMANDS=qr/^git[ -]receive-pack$/; # note that REPONAME_PATT allows a "/" also, which USERNAME_PATT doesn't $REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern +# same as REPONAME, plus some common regex metas +$REPOPATT_PATT=qr(^\@?[0-9a-zA-Z][\\^.$|()[\]*+?{}0-9a-zA-Z._/-]*$); # ---------------------------------------------------------------------------- # convenience subs diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 56b11ed..54448f6 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -54,7 +54,7 @@ open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); # these are set by the "rc" file our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH); # and these are set by gitolite.pm -our ($REPONAME_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN); +our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN); # the common setup module is in the same directory as this running program is my $bindir = $0; @@ -85,7 +85,8 @@ our %groups = (); # %repos has two functions. # $repos{repo}{R|W}{user} = 1 if user has R (or W) permissions for at least -# one branch in repo. This is used by the "level 1 check" (see faq) +# one branch in repo. This is used by the "level 1 check" (see faq). There's +# also the new "C" (create a repo) permission now # $repos{repo}{user} is a list of {ref, perms} pairs. This is used by the # level 2 check. In order to allow "exclude" rules, the order of rules now @@ -119,9 +120,7 @@ sub expand_list for my $item (@list) { - # we test with the slightly more relaxed pattern here; we'll catch the - # "/" in user name thing later; it doesn't affect security anyway - die "$ABRT bad user or repo name $item\n" unless $item =~ $REPONAME_PATT; + die "$ABRT bad user or repo name $item\n" unless $item =~ $REPOPATT_PATT; if ($item =~ /^@/) # nested group { die "$ABRT undefined group $item\n" unless $groups{$item}; @@ -183,9 +182,11 @@ sub parse_conf_file # grab the list and expand any @stuff in it @repos = split ' ', $1; @repos = expand_list ( @repos ); + + s/\bCREAT[EO]R\b/\$creater/g for @repos; } # actual permission line - elsif (/^(-|R|RW|RW\+) (.* )?= (.+)/) + elsif (/^(-|C|R|RW|RW\+) (.* )?= (.+)/) { my $perms = $1; my @refs; @refs = split(' ', $2) if $2; @@ -202,21 +203,39 @@ sub parse_conf_file unless (@users == 1 and $users[0] eq '@all'); do { die "$ABRT bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users; + s/\bCREAT[EO]R\b/\$creater/g for @users; + s/\bREADERS\b/\$readers/g for @users; + s/\bWRITERS\b/\$writers/g for @users; + # ok, we can finally populate the %repos hash for my $repo (@repos) # each repo in the current stanza { # if we're processing a delegated config file (not the master - # config), and if that fragment name is not the same as the - # current repo - if ($fragment ne 'master' and $fragment ne $repo) - { - # then the fragment must be a group name and the repo - # being processed must be a member of that "@group". - # Also, the value of the hash for that combination must be - # "master", signifying a group created in the master - # config file and not in one of the delegates - unless ( ($groups{"\@$fragment"}{$repo} || '') eq 'master') - { + # config), we need to prevent attempts by that admin to obtain + # rights on stuff outside his domain + + # trying to set access for $repo (='foo')... + if ( + # processing the master config, not a fragment + ( $fragment eq 'master' ) or + # fragment is also called 'foo' (you're allowed to have a + # fragment that is only concerned with one repo) + ( $fragment eq $repo ) or + # fragment is called "bar" and "@bar = foo" has been + # defined in the master config + ( ($groups{"\@$fragment"}{$repo} || '') eq 'master' ) + ) { + # all these are fine + } else { + # this is a little more complex + + # fragment is called "bar", one or more "@bar = regex" + # have been specified in master, and "foo" matches some + # such "regex" + my @matched = grep { $repo =~ /^$_$/ } + grep { $groups{"\@$fragment"}{$_} eq 'master' } + sort keys %{ $groups{"\@$fragment"} }; + if (@matched < 1) { $ignored{$fragment}{$repo} = 1; next; } @@ -226,6 +245,7 @@ sub parse_conf_file $user_list{$user}++; # only to catch lint, see later # for 1st level check (see faq/tips doc) + $repos{$repo}{C}{$user} = 1, next if $perms eq 'C'; $repos{$repo}{R}{$user} = 1 if $perms =~ /R/; $repos{$repo}{W}{$user} = 1 if $perms =~ /W/; @@ -302,7 +322,12 @@ for my $fragment_file (glob("conf/fragments/*.conf")) } my $compiled_fh = wrap_open( ">", $GL_CONF_COMPILED ); -print $compiled_fh Data::Dumper->Dump([\%repos], [qw(*repos)]); +my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]); +# the dump uses single quotes, but we convert any strings containing $creater, +# $readers, $writers, to double quoted strings. A wee bit sneaky, but not too +# much... +$dumped_data =~ s/'(?=[^']*\$(?:creater|readers|writers))(.*?)'/"$1"/g; +print $compiled_fh $dumped_data; close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n"; # ---------------------------------------------------------------------------- @@ -326,6 +351,7 @@ my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" wrap_chdir("$repo_base_abs"); for my $repo (sort keys %repos) { + next unless $repo =~ $REPONAME_PATT; unless (-d "$repo.git") { new_repo($repo, "$GL_ADMINDIR/src/hooks"); # new_repo would have chdir'd us away; come back @@ -352,6 +378,7 @@ wrap_chdir("$repo_base_abs"); # daemons first... for my $repo (sort keys %repos) { + next unless $repo =~ $REPONAME_PATT; my $export_ok = "$repo.git/git-daemon-export-ok"; if ($repos{$repo}{'R'}{'daemon'}) { system("touch $export_ok"); @@ -363,6 +390,7 @@ for my $repo (sort keys %repos) { my %projlist = (); # ...then gitwebs for my $repo (sort keys %repos) { + next unless $repo =~ $REPONAME_PATT; my $desc_file = "$repo.git/description"; # note: having a description also counts as enabling gitweb if ($repos{$repo}{'R'}{'gitweb'} or $desc{"$repo.git"}) { @@ -422,7 +450,7 @@ for my $pubkey (glob("*")) # lint check 3; a little more severe than the first two I guess... for my $user (sort keys %user_list) { - next if $user =~ /^(gitweb|daemon|\@all)$/ or $user_list{$user} eq 'has pubkey'; + next if $user =~ /^(gitweb|daemon|\@all|\$creater|\$readers|\$writers)$/ or $user_list{$user} eq 'has pubkey'; print STDERR "$WARN user $user in config, but has no pubkey!\n"; } From f49eddd6607f9722a4a4d948287bb52a64802ad2 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 5 Dec 2009 22:39:56 +0530 Subject: [PATCH 170/637] wildrepos: teach auth and update hook about wildcard repos - new_repo now takes a "creater" parameter; if given, this user is recorded (in a file called "gl-creater") as the creater of the repo. Only applicable to wildcards - repo_rights reads "gl-creater" and "gl-perms" to tell you who created it, and whether you (the $user) are in the list of READERS or WRITERS **NOTE** that the mechanism to create/update gl-perms has not been written yet... (as of this commit) - parse_acl takes 4 more arguments, all optional. The repo name we're interested in (set by all except the access reporting function), and the names to be interpolated as $creater, $readers, writers - report_basic now knows about the "C" permission and shows it - auth now autovivifies a repo if the user has "C" and it's a wildcard match, or (the old case) the user has "W" and it's not a wildcard. In the former case, the creater is also set IMPLEMENTATION NOTES: - the Dumper code now uses a custom hash key sort to make sure $creater etc land up at the *end* - a wee bit of duplication exists in the update hook; it borrows a little code from parse_acl. I dont (yet) want to include all of gitolite.pm for that little piece... --- src/gitolite.pm | 76 +++++++++++++++++++++++++++++++++++++++++---- src/gl-auth-command | 36 +++++++++++++-------- src/gl-compile-conf | 10 ++++++ src/hooks/update | 23 +++++++++++--- 4 files changed, 121 insertions(+), 24 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 5864466..f341ab1 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -81,7 +81,7 @@ sub where_is_rc # NOTE: this sub will change your cwd; caller beware! sub new_repo { - my ($repo, $hooks_dir) = @_; + my ($repo, $hooks_dir, $creater) = @_; umask($REPO_UMASK); @@ -89,19 +89,81 @@ sub new_repo # erm, note that's "and die" not "or die" as is normal in perl wrap_chdir("$repo.git"); system("git --bare init >&2"); + system("echo $creater > gl-creater") if $creater; # propagate our own, plus any local admin-defined, hooks system("cp $hooks_dir/* hooks/"); chmod 0755, "hooks/update"; } +# ---------------------------------------------------------------------------- +# metaphysics (like, "is there a god?", "who created me?", etc) +# ---------------------------------------------------------------------------- + +# "who created this repo", "am I on the R list", and "am I on the RW list"? +sub repo_rights +{ + my ($repo_base_abs, $repo, $user) = @_; + # creater + my $c = ''; + if ( -f "$repo_base_abs/$repo.git/gl-creater") { + my $fh = wrap_open("<", "$repo_base_abs/$repo.git/gl-creater"); + chomp($c = <$fh>); + } + # $user's R and W rights + my ($r, $w); $r = ''; $w = ''; + if ($user and -f "$repo_base_abs/$repo.git/gl-perms") { + my $fh = wrap_open("<", "$repo_base_abs/$repo.git/gl-perms"); + my $perms = join ("", <$fh>); + if ($perms) { + $r = $user if $perms =~ /^\s*R(?=\s).*\s$user(\s|$)/m; + $w = $user if $perms =~ /^\s*RW(?=\s).*\s$user(\s|$)/m; + } + } + + return ($c, $r, $w); +} + # ---------------------------------------------------------------------------- # parse the compiled acl # ---------------------------------------------------------------------------- sub parse_acl { - my $GL_CONF_COMPILED = shift; + # IMPLEMENTATION NOTE: a wee bit of this is duplicated in the update hook; + # please update that also if the interface or the env vars change + + my ($GL_CONF_COMPILED, $repo, $c, $r, $w) = @_; + + # void $r if same as $w (otherwise "readers" overrides "writers"; this is + # the same problem that needed a sort sub for the Dumper in the compile + # script, but localised to just $readers and $writers) + $r = "" if $r eq $w; + + # set up the variables for a parse to interpolate stuff from the dumped + # hash (remember the selective conversion of single to double quotes?). + + # if they're not passed in, then we look for an env var of that name, else + # we default to "NOBODY" (we hope there isn't a real user called NOBODY!) + # And in any case, we set those env vars so level 2 can redo the last + # parse without any special code + + our $creater = $ENV{GL_CREATER} = $c || $ENV{GL_CREATER} || "NOBODY"; + our $readers = $ENV{GL_READERS} = $r || $ENV{GL_READERS} || "NOBODY"; + our $writers = $ENV{GL_WRITERS} = $w || $ENV{GL_WRITERS} || "NOBODY"; + die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; + + # access reporting doesn't send $repo, and doesn't need to + return unless $repo; + + return $ENV{GL_REPOPATT} = "" if $repos{$repo}; + my @matched = grep { $repo =~ /^$_$/ } sort keys %repos; + die "$repo has no matches\n" unless @matched; + die "$repo has multiple matches\n@matched\n" if @matched > 1; + # found exactly one pattern that matched, copy its ACL + $repos{$repo} = $repos{$matched[0]}; + # and return the pattern + return $ENV{GL_REPOPATT} = $matched[0]; } # ---------------------------------------------------------------------------- @@ -114,16 +176,18 @@ sub report_basic { my($GL_ADMINDIR, $GL_CONF_COMPILED, $user) = @_; - &parse_acl($GL_CONF_COMPILED); + &parse_acl($GL_CONF_COMPILED, "", "CREATER", "READERS", "WRITERS"); # send back some useful info if no command was given print "hello $user, the gitolite version here is "; system("cat", "$GL_ADMINDIR/src/VERSION"); print "\ryou have the following permissions:\n\r"; for my $r (sort keys %repos) { - my $perm .= ( $repos{$r}{R}{'@all'} ? ' @' : ( $repos{$r}{R}{$user} ? ' R' : '' ) ); - $perm .= ( $repos{$r}{W}{'@all'} ? ' @' : ( $repos{$r}{W}{$user} ? ' W' : '' ) ); - print "$perm\t$r\n\r" if $perm; + my $perm .= ( $repos{$r}{C}{'@all'} ? ' @' : ( $repos{$r}{C}{$user} ? ' C' : ' ' ) ); + $perm .= ( $repos{$r}{R}{'@all'} ? ' @' : ( $repos{$r}{R}{$user} ? ' R' : ' ' ) ); + $perm .= ( $repos{$r}{W}{'@all'} ? ' @' : ( $repos{$r}{W}{$user} ? ' W' : ' ' ) ); + print "$perm\t$r\n\r" if $perm =~ /\S/; } } 1; + diff --git a/src/gl-auth-command b/src/gl-auth-command index 86fec88..eff0d77 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -78,8 +78,28 @@ die "bad command: $cmd. Make sure the repo name is exactly as in your config\n" # first level permissions check # ---------------------------------------------------------------------------- -# parse the compiled acl; goes into %repos (global) -&parse_acl($GL_CONF_COMPILED); +my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); + +if ( -d "$repo_base_abs/$repo.git" ) { + # existing repo + my ($creater, $user_R, $user_W) = &repo_rights($repo_base_abs, $repo, $user); + my $patt = &parse_acl($GL_CONF_COMPILED, $repo, $creater, $user_R, $user_W); +} else { + my $patt = &parse_acl($GL_CONF_COMPILED, $repo, $user, $user, $user); + # parse_acl returns "" if the repo was non-wildcard, or the pattern + # that matched if it was a wildcard + + # auto-vivify new repo; 2 situations allow autoviv -- normal repos + # with W access (the old mode), and wildcard repos with C access + my $W_ok = $repos{$repo}{W}{$user} || $repos{$repo}{W}{'@all'}; + my $C_ok = $repos{$repo}{C}{$user} || $repos{$repo}{C}{'@all'}; + if ($W_ok and not $patt or $C_ok and $patt) { + wrap_chdir("$repo_base_abs"); + # for wildcard repos, we also want to set the "creater" + new_repo($repo, "$GL_ADMINDIR/src/hooks", ( $patt ? $user : "")); + wrap_chdir($ENV{HOME}); + } +} # we know the user and repo; we just need to know what perm he's trying my $perm = ($verb =~ $R_COMMANDS ? 'R' : 'W'); @@ -88,16 +108,6 @@ die "$perm access for $repo DENIED to $user\n" unless $repos{$repo}{$perm}{$user} or $repos{$repo}{$perm}{'@all'}; -# create the repo if it doesn't already exist and the user has "W" access -my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); -if ( not -d "$repo_base_abs/$repo.git" ) { - if ( $repos{$repo}{W}{$user} or $repos{$repo}{W}{'@all'} ) { - wrap_chdir("$repo_base_abs"); - new_repo($repo, "$GL_ADMINDIR/src/hooks"); - wrap_chdir($ENV{HOME}); - } -} - # ---------------------------------------------------------------------------- # logging, timestamp. also setup env vars for later # ---------------------------------------------------------------------------- @@ -113,7 +123,7 @@ for ($s, $min, $h, $d, $m) { } $ENV{GL_TS} = "$y-$m-$d.$h:$min:$s"; -# substitute template parameters and set the logfile name +# substitute template parameters and set the logfile name $GL_LOGT =~ s/%y/$y/g; $GL_LOGT =~ s/%m/$m/g; $GL_LOGT =~ s/%d/$d/g; diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 54448f6..da1822a 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -5,6 +5,15 @@ use warnings; use Data::Dumper; $Data::Dumper::Indent = 1; $Data::Dumper::Sortkeys = 1; +$Data::Dumper::Sortkeys = sub { return [ reverse sort keys %{$_[0]} ]; }; + # this is to make sure that $creater etc go to the end of the dumped hash. + # Without this, a setup that has something like + # @team = u1 u2 u3 + # repo priv/CREATER/.+ + # RW+ = CREATER + # RW = @team + # has a problem. The RW overrides the RW+ when the dumped hash is read in + # (simply going by sequence), so creater's special privs are lost # === add-auth-keys === @@ -352,6 +361,7 @@ wrap_chdir("$repo_base_abs"); for my $repo (sort keys %repos) { next unless $repo =~ $REPONAME_PATT; + print STDERR "creating $repo...\n"; unless (-d "$repo.git") { new_repo($repo, "$GL_ADMINDIR/src/hooks"); # new_repo would have chdir'd us away; come back diff --git a/src/hooks/update b/src/hooks/update index 235fa40..0ee7ad0 100755 --- a/src/hooks/update +++ b/src/hooks/update @@ -30,8 +30,21 @@ our %repos; # we should already have the GL_RC env var set when we enter this hook die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; -# then "do" the compiled config file, whose name we now know -die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; +# then "do" the compiled config file, whose name we now know. Before doing +# that we setup the creater etc from environment variables so that the parse +# interpolates them. We've minimised the duplication but this *does* +# duplicate a bit of parse_acl from gitolite.pm; we don't want to include that +# file here just for that little bit +{ + our $creater = $ENV{GL_CREATER}; + our $readers = $ENV{GL_READERS}; + our $writers = $ENV{GL_WRITERS}; + + die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; + + $repos{$ENV{GL_REPO}} = $repos{$ENV{GL_REPOPATT}} if ( $ENV{GL_REPOPATT} ); +} +my $reported_repo = $ENV{GL_REPO} . ( $ENV{GL_REPOPATT} ? " ($ENV{GL_REPOPATT})" : "" ); # ---------------------------------------------------------------------------- # start... @@ -101,12 +114,12 @@ sub check_ref { $refex = (keys %$ar)[0]; # refex? sure -- a regex to match a ref against :) next unless $ref =~ /$refex/; - die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-'; + die "$perm $ref $reported_repo $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-'; # as far as *this* ref is concerned we're ok return $refex if ($ar->{$refex} =~ /\Q$perm/); } - die "$perm $ref $ENV{GL_REPO} $ENV{GL_USER} DENIED by fallthru\n"; + die "$perm $ref $reported_repo $ENV{GL_USER} DENIED by fallthru\n"; } # and in this version, we have many "refs" to check. The one we print in the @@ -123,6 +136,6 @@ check_ref($_) for @refs; open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!\n"; print $log_fh "$ENV{GL_TS} $perm\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . - "\t$ENV{GL_REPO}\t$ref\t$ENV{GL_USER}\t$log_refex\n"; + "\t$reported_repo\t$ref\t$ENV{GL_USER}\t$log_refex\n"; close $log_fh or die "close log failed: $!\n"; exit 0; From 6214ad3ab6a8d94dbbe3454afe1beda0d583c89d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 6 Dec 2009 11:41:02 +0530 Subject: [PATCH 171/637] wildrepos: first cut documentation --- conf/example.conf | 5 + doc/3-faq-tips-etc.mkd | 8 +- doc/4-wildcard-repositories.mkd | 207 ++++++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 doc/4-wildcard-repositories.mkd diff --git a/conf/example.conf b/conf/example.conf index b5ead17..cf52e46 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -56,6 +56,11 @@ # "@interns" now has 3 names in it, but note that this does # not change @staff +# WILDCARD REPOSITORIES ("wildrepos" BRANCH ONLY) +# ----------------------------------------- + +# Please see doc/4-wildcard-repositories.mkd for details + # REPO AND BRANCH PERMISSIONS # --------------------------- diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 464a4e5..c5e85e8 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -19,6 +19,7 @@ In this document: * what repos do I have access to? * "exclude" (or "deny") rules * "personal" branches + * repos named with wildcards * design choices * keeping the parser and the access control separate @@ -90,7 +91,7 @@ plain "git archive", because the Makefile adds a file called git clone git://sitaramc.indefero.net/sitaramc/gitolite.git cd gitolite make master.tar - # or maybe "make rebel.tar" or "make pu.tar" + # or maybe "make wildrepos.tar" or "make pu.tar" @@ -506,6 +507,11 @@ first check: Just don't *show* the user this config file; it might sound insulting :-) +#### repos named with wildcards + +**This feature only exists in the "wildrepos" branch!** Please see +`doc/4-wildcard-repositories.mkd` for all the details. + ### design choices #### keeping the parser and the access control separate diff --git a/doc/4-wildcard-repositories.mkd b/doc/4-wildcard-repositories.mkd new file mode 100644 index 0000000..07cc9a8 --- /dev/null +++ b/doc/4-wildcard-repositories.mkd @@ -0,0 +1,207 @@ +# repositories named with wildcards + +***IMPORTANT NOTE***: + +This branch contains features that are likely to be much more brittle than the +"master" branch. Creating repositories based on wild cards, giving +"ownership" to the specific user who created it, allowing him/her to hand out +R and RW permissions to other users to collaborate, all these are possible. +And any of these could have a bug in it. + +---- + +In this document: + + * wildcard repos + * wildcard repos with creater name in them + * wildcard repos without creater name in them + * side-note: line-anchored regexes + * contrast with refexes + * handing out rights to wildcard-matached repos + * reporting + * other issues and discussion + +This document is mostly "by example". + +---- + +### Wildcard repos + +Which of these alternatives you choose depends on your needs, and the social +aspects of your environment. The first one is a little more rigid, making it +harder to make mistakes, and the second is more flexible and trusting. + +#### Wildcard repos with creater name in them + +Here's an example snippet: + + @prof = u1 + @TAs = u2 u3 + @students = u4 u5 u6 + + repo assignments/CREATER/a[0-9][0-9] + C = @students + RW+ = CREATER + RW = WRITERS @TAs + R = READERS @prof + +For now, ignore the special usernames READERS and WRITERS, and just create a +new repo, as user "u4" (a student): + + $ git clone git@server:assignments/u4/a12 + Initialized empty Git repository in /home/sitaram/t/a12/.git/ + Initialized empty Git repository in /home/gitolite/repositories/assignments/u4/a12.git/ + warning: You appear to have cloned an empty repository. + +Notice the *two* empty repo inits, and the order in which they occur ;-) Now +make some changes and push, and after that, that specific repo +(`assignments/u4/a12`) behaves as if the access control looked like this: + + # effective config + repo assignments/u4/a12 + RW+ = u4 + RW = WRITERS @TAs + R = READERS @prof + +#### Wildcard repos without creater name in them + +Here's how the same example would look if you did not want the CREATER's name +to be part of the actual repo name. + + repo assignments/a[0-9][0-9] + C = @students + RW+ = CREATER + RW = WRITERS @TAs + R = READERS @prof + +We haven't changed anything except the repo name pattern. This means that the +first student that creates, say, `assignments/a12` becomes the owner. +Mistakes (such as claiming a12 instead of a13) need to be rectified by an +admin logging on to the back end, though it's not too difficult. + +You could also repace the C line like this: + + C = @TAs + +and have a TA create the repos in advance. + +In either case, they could then use the (currently not implemented) `setperms` +feature to specify which users are "READERS" and which are "WRITERS". See +later for details. + +### Side-note: Line-anchored regexes + +A regex like + + repo assignments/S[0-9]+/A[0-9]+ + +would match `assignments/S02/A37`. It will not match `assignments/S02/ABC`, +or `assignments/S02/a37`, obviously. + +But you may be surprised to find that it does not match even +`assignments/S02/A37/B99`. This is because internally, gitolite +*line-anchors* the given regex; so that regex actually becomes +`^assignments/S[0-9]+/A[0-9]+$` -- notice the line beginning and ending +metacharacters. + +#### Contrast with refexes + +Just for interest, note that this is in contrast to the refexes for the normal +"branch" permissions, as described in `conf/example.conf` and elsewhere. +Those "refexes" are *not* anchored; a pattern like `refs/heads/master` +actually matches `foo/refs/heads/master01/bar` as well, even if no one will +actually push such a branch! You can anchor it if you really care, by using +`master$` instead of `master`, but anchoring is *not* the default for +refexes.] + +### Handing out rights to wildcard-matached repos + +***not yet implemented*** + +In the examples above, we saw two special "user" names: READERS and WRITERS. +The permissions they have are controlled by the config file, but ***who is +part of this list*** is controlled by the person who created the repository. + +The use case is that, although our toy example has only 3 students, in reality +there will be a few dozen, but each assignment will be worked on only by a +handful from among those. This allows the creater to take ad hoc sets of +users from among the actual users in the system, and place them into one of +two categories (whose permissions are, in this example, R and RW +respectively). In theory you could do the same thing by creating lots of +little "assignment-NN" groups in the config file but that may be a little too +cumbersome for non-secret environments. + +Create a small text file that contains the permissions you desire: + + $ cat > myperms + R user1 user3 + RW user2 + (hit ctrl-d here) + +...and use the new "getperms" command to set permissions for your repo: + + $ ssh git@server setperms XXX/XXX/XXX < myperms + New perms are: + R user1 user3 + RW user2 + +'setperms' will helpfully print what the new permissions are but you can also +use 'getperms' to check: + + $ ssh git@server getperms XXX/XXX/XXX + R user1 user3 + RW user2 + +The following points are important: + + * note the syntax of the commands; it's not a "git" command,and there's no + `:` like in a repo URL. The first space-separated word is R or RW, and + the rest are simple usernames. + + * whoever you specify as "R" will match the special user READERS. "RW" will + match WRITERS. + +### Reporting + +Remember the cool stuff you see when you just do `ssh git@server` (grep for +"myrights" in `doc/3-faq-tips-etc.mkd` if you forgot, or go [here][mr]). + +[mr]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#myrights + +This still works, except the format is a little more compressed to accommodate +a new column (at the start) for "C" permissions, which indicate that you are +allowed to *create* repos matching that pattern. + +In addition, there's a second level of reporting now, which is used to find +what *actual* repos are available when you supply a pattern. + + XXX to be done XXX + +### Other issues and discussion + + * *what if the repo name being pushed matches more than one pattern*? + + I think it would be very hard to reason about access if we were to do + something like combine all the access rights in all the matching patterns. + No matter how you do it, and how carefully you document it, there'll be + someone who is surprised by the result. + + And in security, that's a ***Bad Thing***. + + So we don't combine permissions. At runtime, we die if we find more than + one match. Let 'em go holler at the admin for creating multiple matching + repo patterns :-) + + This can make some repos inaccessible if the patterns changed *after* they + were created. The administrator should be careful not to do this. Most + of the time, it won't be difficult; the fixed prefix will usually be + different anyway so there won't be overlaps. + +---- + +Enjoy, and please use with care. This is pretty powerful stuff. As they say: +if you break it, you get to keep both pieces :) + +[jwzq]: http://regex.info/blog/2006-09-15/247 + +[av]: http://en.wikipedia.org/wiki/Autovivification From f620044156b1e80354b45ac0d8934bed5da1c098 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 6 Dec 2009 14:39:40 +0530 Subject: [PATCH 172/637] wildrepos: implement getperms and setperms --- src/gitolite.pm | 20 ++++++++++++++++++++ src/gl-auth-command | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index f341ab1..4507f54 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -123,6 +123,26 @@ sub repo_rights return ($c, $r, $w); } +# ---------------------------------------------------------------------------- +# getperms and setperms +# ---------------------------------------------------------------------------- + +sub get_set_perms +{ + my($repo_base_abs, $repo, $verb, $user) = @_; + my ($creater, $dummy, $dummy2) = &repo_rights($repo_base_abs, $repo, ""); + die "$repo doesnt exist or is not yours\n" unless $user eq $creater; + wrap_chdir("$repo_base_abs"); + wrap_chdir("$repo.git"); + if ($verb eq 'getperms') { + print STDERR `cat gl-perms 2>/dev/null`; + } else { + system("cat > gl-perms"); + print STDERR "New perms are:\n"; + print STDERR `cat gl-perms`; + } +} + # ---------------------------------------------------------------------------- # parse the compiled acl # ---------------------------------------------------------------------------- diff --git a/src/gl-auth-command b/src/gl-auth-command index eff0d77..d74b20d 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -60,6 +60,36 @@ unless ($ENV{SSH_ORIGINAL_COMMAND}) { } my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; +my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); + +# ---------------------------------------------------------------------------- +# get and set perms for actual repo created by wildcard-autoviv +# ---------------------------------------------------------------------------- + +my $CUSTOM_COMMANDS=qr/^\s*(expand|getperms|setperms)\s/; + +# note that all the subs called here chdir somewhere else and do not come +# back; they all blithely take advantage of the fact that processing custom +# commands is sort of a dead end for normal (git) processing + +if ($cmd =~ $CUSTOM_COMMANDS) { + my ($verb, $repo) = ($cmd =~ /^\s*(\S+)\s+\/?(.*?)(?:.git)?$/); + if ($repo =~ $REPONAME_PATT and $verb =~ /getperms|setperms/) { + # with an actual reponame, you can "getperms" or "setperms" + get_set_perms($repo_base_abs, $repo, $verb, $user); + } + elsif ($repo !~ $REPONAME_PATT and $verb eq 'expand') { + # with a wildcard, you can "expand" it to see what repos actually match + die "not implemented yet\n"; + } else { + die "$cmd doesn't make sense to me\n"; + } + exit 1; +} + +# ---------------------------------------------------------------------------- +# normal (git) processing +# ---------------------------------------------------------------------------- # split into command and arguments; the pattern allows old style as well as # new style: "git-subcommand arg" or "git subcommand arg", just like gitosis @@ -78,8 +108,6 @@ die "bad command: $cmd. Make sure the repo name is exactly as in your config\n" # first level permissions check # ---------------------------------------------------------------------------- -my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); - if ( -d "$repo_base_abs/$repo.git" ) { # existing repo my ($creater, $user_R, $user_W) = &repo_rights($repo_base_abs, $repo, $user); From 02cee1d5cf513f21023a223b554b05903cf027da Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 6 Dec 2009 15:26:53 +0530 Subject: [PATCH 173/637] wildrepos: expanded access reporting This feature has *no* warranty, and so no documentation. Not more than this transcript anyway. config file: @prof = u1 @TAs = u2 u3 @students = u4 u5 u6 repo assignments/CREATER/a[0-9][0-9] C = @students RW+ = CREATER RW = WRITERS @TAs R = READERS @prof session: as user "u4": # check your permissions $ ssh git@server PTY allocation request failed on channel 0 hello u4, the gitolite version here is v0.95-31-gbcb14ca you have the following permissions: C assignments/CREATER/a[0-9][0-9] @ @ testing Connection to localhost closed. # autovivify repos for assignment 12 and 24 $ git clone git@server:assignments/u4/a12 a12 Initialized empty Git repository in /home/sitaram/t/a12/.git/ Initialized empty Git repository in /home/gitolite/repositories/assignments/u4/a12.git/ warning: You appear to have cloned an empty repository. $ git clone git@server:assignments/u4/a24 a24 Initialized empty Git repository in /home/sitaram/t/a24/.git/ Initialized empty Git repository in /home/gitolite/repositories/assignments/u4/a24.git/ warning: You appear to have cloned an empty repository. # check what repos you autovivified $ ssh git@server expand assignments/u4/a[0-9][0-9] (u4) assignments/u4/a12 (u4) assignments/u4/a24 as user "u5": # check your basic permissions $ ssh git@server PTY allocation request failed on channel 0 hello u5, the gitolite version here is v0.95-31-gbcb14ca you have the following permissions: C assignments/CREATER/a[0-9][0-9] @ @ testing Connection to localhost closed. # see if you have access to any of u4's repos $ ssh git@server expand assignments/u4/a[0-9][0-9] # (no output produced) as user "u4": # allow "u5" read access to assignment 12 # note you type in "R u5", hit enter, then hit Ctrl-D. Gitolite # then produces a confirmation message starting "New perms are:" $ ssh git@server setperms assignments/u4/a12 R u5 New perms are: R u5 as user "u5": # again see if you have access to any u4 repos $ ssh git@server expand assignments/u4/a[0-9][0-9] (u4) assignments/u4/a12 as user "u4": # check what permissions you gave to assignment 12 $ ssh git@server getperms assignments/u4/a12 R u5 # add RW access to "u6" to assignment 12 # again, type 'em in, then hit Ctrl-D; and note each time you run # this you're starting from scratch -- you can't "add to" the # permissions. Deal with it... $ ssh git@server setperms assignments/u4/a12 R u5 RW u6 New perms are: R u5 RW u6 as user "u6": # check what u4 stuff you have access to $ ssh git@server expand assignments/u4/a[0-9][0-9] (u4) assignments/u4/a12 --- doc/4-wildcard-repositories.mkd | 21 ++++++++----------- src/gitolite.pm | 36 ++++++++++++++++++++++++++++++--- src/gl-auth-command | 2 +- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/doc/4-wildcard-repositories.mkd b/doc/4-wildcard-repositories.mkd index 07cc9a8..a4c1f75 100644 --- a/doc/4-wildcard-repositories.mkd +++ b/doc/4-wildcard-repositories.mkd @@ -134,23 +134,23 @@ cumbersome for non-secret environments. Create a small text file that contains the permissions you desire: $ cat > myperms - R user1 user3 - RW user2 + R u5 + RW u6 (hit ctrl-d here) ...and use the new "getperms" command to set permissions for your repo: - $ ssh git@server setperms XXX/XXX/XXX < myperms + $ ssh git@server setperms assignments/u4/a12 < myperms New perms are: - R user1 user3 - RW user2 + R u5 + RW u6 'setperms' will helpfully print what the new permissions are but you can also use 'getperms' to check: - $ ssh git@server getperms XXX/XXX/XXX - R user1 user3 - RW user2 + $ ssh git@server getperms assignments/u4/a12 + R u5 + RW u6 The following points are important: @@ -172,11 +172,6 @@ This still works, except the format is a little more compressed to accommodate a new column (at the start) for "C" permissions, which indicate that you are allowed to *create* repos matching that pattern. -In addition, there's a second level of reporting now, which is used to find -what *actual* repos are available when you supply a pattern. - - XXX to be done XXX - ### Other issues and discussion * *what if the repo name being pushed matches more than one pattern*? diff --git a/src/gitolite.pm b/src/gitolite.pm index 4507f54..41c45b1 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -156,8 +156,8 @@ sub parse_acl # void $r if same as $w (otherwise "readers" overrides "writers"; this is # the same problem that needed a sort sub for the Dumper in the compile - # script, but localised to just $readers and $writers) - $r = "" if $r eq $w; + # script, but in this case it's limited to just $readers and $writers) + $r = "NOBODY" if $r eq $w; # set up the variables for a parse to interpolate stuff from the dumped # hash (remember the selective conversion of single to double quotes?). @@ -209,5 +209,35 @@ sub report_basic print "$perm\t$r\n\r" if $perm =~ /\S/; } } -1; +# ---------------------------------------------------------------------------- +# print a report of $user's basic permissions +# ---------------------------------------------------------------------------- + +sub expand_wild +{ + my($GL_CONF_COMPILED, $repo_base_abs, $repo, $user) = @_; + + # display matching repos (from *all* the repos in the system) that $user + # has at least "R" access to + + chdir("$repo_base_abs") or die "chdir $repo_base_abs failed: $!\n"; + for my $actual_repo (`find . -type d -name "*.git"|sort`) { + chomp ($actual_repo); + $actual_repo =~ s/^\.\///; + $actual_repo =~ s/\.git$//; + # it has to match the pattern being expanded + next unless $actual_repo =~ /^$repo$/; + + # find the creater and subsitute in repos + my ($creater, $read, $write) = &repo_rights($repo_base_abs, $actual_repo, $user); + # get access list with this + &parse_acl($GL_CONF_COMPILED, "", $creater, $read || "NOBODY", $write || "NOBODY"); + + # you need a minimum of "R" access to the regex we're talking about + next unless $repos{$repo}{R}{'@all'} or $repos{$repo}{R}{$user}; + print STDERR "($creater)\t$actual_repo\n"; + } +} + +1; diff --git a/src/gl-auth-command b/src/gl-auth-command index d74b20d..cc4b5f9 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -80,7 +80,7 @@ if ($cmd =~ $CUSTOM_COMMANDS) { } elsif ($repo !~ $REPONAME_PATT and $verb eq 'expand') { # with a wildcard, you can "expand" it to see what repos actually match - die "not implemented yet\n"; + expand_wild($GL_CONF_COMPILED, $repo_base_abs, $repo, $user); } else { die "$cmd doesn't make sense to me\n"; } From 135079c9d753f354ae350eb42146e7e5ea0ad9eb Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 6 Dec 2009 20:40:23 +0530 Subject: [PATCH 174/637] minor docfix --- doc/4-wildcard-repositories.mkd | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/4-wildcard-repositories.mkd b/doc/4-wildcard-repositories.mkd index a4c1f75..7e54a1c 100644 --- a/doc/4-wildcard-repositories.mkd +++ b/doc/4-wildcard-repositories.mkd @@ -85,9 +85,8 @@ You could also repace the C line like this: and have a TA create the repos in advance. -In either case, they could then use the (currently not implemented) `setperms` -feature to specify which users are "READERS" and which are "WRITERS". See -later for details. +In either case, they could then use the `setperms` feature to specify which +users are "READERS" and which are "WRITERS". See later for details. ### Side-note: Line-anchored regexes @@ -116,8 +115,6 @@ refexes.] ### Handing out rights to wildcard-matached repos -***not yet implemented*** - In the examples above, we saw two special "user" names: READERS and WRITERS. The permissions they have are controlled by the config file, but ***who is part of this list*** is controlled by the person who created the repository. From 5416e38ea8e11ed856386e43c759da6ab3958a7d Mon Sep 17 00:00:00 2001 From: Teemu Matilainen Date: Fri, 4 Dec 2009 17:04:40 +0200 Subject: [PATCH 175/637] Fix default configuration paths in documentation Signed-off-by: Teemu Matilainen --- src/gl-compile-conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 56b11ed..6dfc050 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -27,11 +27,11 @@ $Data::Dumper::Sortkeys = 1; # - anytime a pubkey is added/deleted # - anytime gitolite.conf is changed # input: -# - GL_CONF (default: ~/.gitolite/gitolite.conf) +# - GL_CONF (default: ~/.gitolite/conf/gitolite.conf) # - GL_KEYDIR (default: ~/.gitolite/keydir) # output: # - ~/.ssh/authorized_keys (dictated by sshd) -# - GL_CONF_COMPILED (default: ~/.gitolite/gitolite.conf-compiled.pm) +# - GL_CONF_COMPILED (default: ~/.gitolite/conf/gitolite.conf-compiled.pm) # security: # - touches a very critical system file that manages the restrictions on # incoming users. Be sure to audit AUTH_COMMAND and AUTH_OPTIONS (see From cdb7245d44a57ec73ebac50f073eef1388bec15b Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 8 Dec 2009 13:51:11 +0530 Subject: [PATCH 176/637] example conf: clarify what @all means ...thanks to Grum for catching this --- conf/example.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conf/example.conf b/conf/example.conf index b5ead17..3768667 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -82,7 +82,8 @@ repo gitolite-admin RW+ = @admins - # "@all" is a special, predefined, group name + # "@all" is a special, predefined, group name of all users + # (everyone who has a pubkey in keydir) repo testing RW+ = @all From 4441ed82e4967b08ce18018ee7d6467bab3b689b Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 8 Dec 2009 15:03:38 +0530 Subject: [PATCH 177/637] compile: allow full email addresses as usernames we had usurped the email style syntax to separate multiple keys belonging to the same person, like sitaram@desktop.pub and sitaram@laptop.pub. If you have so many users that you need the full email address to disambiguate some of them (or you want to do it for just plain convenience), you couldn't. This patch fixes that in a backward compatible way. See doc/3-faq-tips-etc.mkd for details. --- conf/example.conf | 10 +++++++--- doc/3-faq-tips-etc.mkd | 28 ++++++++++++++++++++++++++-- src/gitolite.pm | 6 +++--- src/gl-compile-conf | 7 +++---- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index b5ead17..b2173e3 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -6,9 +6,13 @@ # the description string for gitweb) # - comments in the normal shell-ish style; no surprises there # - there are NO continuation lines of any kind -# - user/repo names as simple as possible -# (usernames: only alphanumerics, ".", "_", "-"; -# reponames: same, plus "/", but not at the start) +# - user/repo names as simple as possible; they must start with an +# alphanumeric, but after that they can also contain ".", "_", "-". +# - usernames can optionally be followed by an "@" and a domainname +# containing at least one "." (this allows you to use an email +# address as someone's username) +# - reponames can contain "/" characters (this allows you to +# put your repos in a tree-structure for convenience) # objectives, over and above gitosis: # - simpler syntax diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 464a4e5..9a1d413 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -344,12 +344,36 @@ gitolite knows these two keys belong to the same person. Note that you don't say "sitaram@laptop" and so on in the **config** file -- as far as the config file is concerned there's just **one** user called -"sitaram" -- so you only say "sitaram" there. Only the **pubkey files** have -the extra "@" stuff. +"sitaram" -- so you only say "sitaram" there. I think this is easier to maintain if you have to delete or change one of those keys. +However, now that `sitaramc@gmail.com` is also a valid username, we need to +distinguish between `sitaramc@gmail.com.pub` and `sitaramc@desktop.pub`. We +do that by requiring that the multi-key suffix you use (like "desktop" and +"laptop") should not have a `"."` in it. If it does, it looks like an email +address. The following table lists sample pubkey filenames and the +corresponding derived usernames (which is what goes into the +`conf/gitolite.conf` file): + + * old style multikeys; not mistaken for emails because there is no "." in + hostname part + + sitaramc.pub sitaramc + sitaramc@laptop.pub sitaramc + sitaramc@desktop.pub sitaramc + + * new style, email keys; there is a "." in hostname part; so it's an email + address + + sitaramc@gmail.com.pub sitaramc@gmail.com + + * multikeys *with* email address + + sitaramc@gmail.com@laptop.pub sitaramc@gmail.com + sitaramc@gmail.com@desktop.pub sitaramc@gmail.com + #### support for git installed outside default PATH The normal solution is to add to the system default PATH somehow, either by diff --git a/src/gitolite.pm b/src/gitolite.pm index 71c0b0c..ee0fc77 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -24,9 +24,9 @@ $WARN = "\n\t\t***** WARNING *****\n "; $R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/; $W_COMMANDS=qr/^git[ -]receive-pack$/; -# note that REPONAME_PATT allows a "/" also, which USERNAME_PATT doesn't -$REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern -$USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern +# note that REPONAME_PATT allows "/", while USERNAME_PATT allows "@" +$REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern +$USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@-]*$); # very simple pattern # ---------------------------------------------------------------------------- # convenience subs diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 56b11ed..1125add 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -121,7 +121,7 @@ sub expand_list { # we test with the slightly more relaxed pattern here; we'll catch the # "/" in user name thing later; it doesn't affect security anyway - die "$ABRT bad user or repo name $item\n" unless $item =~ $REPONAME_PATT; + die "$ABRT bad user or repo name $item\n" unless $item =~ $REPONAME_PATT or $item =~ $USERNAME_PATT; if ($item =~ /^@/) # nested group { die "$ABRT undefined group $item\n" unless $groups{$item}; @@ -174,7 +174,6 @@ sub parse_conf_file # store the members of each group as hash key. Keep track of when # the group was *first* created by using $fragment as the *value* do { $groups{$1}{$_} ||= $fragment } for ( expand_list( split(' ', $2) ) ); - # again, we take the more "relaxed" pattern die "$ABRT bad group $1\n" unless $1 =~ $REPONAME_PATT; } # repo(s) @@ -200,7 +199,7 @@ sub parse_conf_file # expand the user list, unless it is just "@all" @users = expand_list ( @users ) unless (@users == 1 and $users[0] eq '@all'); - do { die "$ABRT bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users; + do { die "$ABRT bad username $_ PATT is $USERNAME_PATT,\n" unless $_ =~ $USERNAME_PATT } for @users; # ok, we can finally populate the %repos hash for my $repo (@repos) # each repo in the current stanza @@ -408,7 +407,7 @@ for my $pubkey (glob("*")) print STDERR "WARNING: pubkey files should end with \".pub\", ignoring $pubkey\n"; next; } - my $user = $pubkey; $user =~ s/(\@.+)?\.pub$//; + my $user = $pubkey; $user =~ s/(\@[^.]+)?\.pub$//; # lint check 2 print STDERR "WARNING: pubkey $pubkey exists but user $user not in config\n" unless $user_list{$user}; From 3403d40d0e0a478252fa4111a44fb42218f92f15 Mon Sep 17 00:00:00 2001 From: Teemu Matilainen Date: Mon, 7 Dec 2009 22:20:29 +0200 Subject: [PATCH 178/637] Add support for repo configurations Git repository configurations can be set/unset by declaring "config" lines in "repo" stanzas in gitolite.conf. For example: repo gitolite config hooks.mailinglist = gitolite-commits@example.tld config hooks.emailprefix = "[gitolite] " config foo.bar = "" config foo.baz = The firs two set (override) the values. Double quotes must be used to preserve preceding spaces. Third one sets an empty value and the last removes all keys. Signed-off-by: Teemu Matilainen --- src/gl-compile-conf | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 7526da5..138249d 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -101,6 +101,9 @@ my %rurp_seen = (); # catch usernames<->pubkeys mismatches; search for "lint" below my %user_list = (); +# repo configurations +my %repo_config = (); + # gitweb descriptions and owners; plain text, keyed by "$repo.git" my %desc = (); my %owner = (); @@ -244,6 +247,16 @@ sub parse_conf_file } } } + # configuration + elsif (/^config (.+) = ?(.*)/) + { + my ($key, $value) = ($1, $2); + die "$WARN $fragment attempting to set repo configuration\n" if $fragment ne 'master'; + for my $repo (@repos) # each repo in the current stanza + { + $repo_config{$repo}{$key} = $value; + } + } # very simple syntax for the gitweb description of repo; one of: # reponame = "some description string" # reponame "owner name" = "some description string" @@ -338,6 +351,22 @@ warn "\n\t\t***** WARNING *****\n" . "\t\"git version dependency\" in doc/3-faq-tips-etc.mkd\n" if $git_version < 10602; # that's 1.6.2 to you +# ---------------------------------------------------------------------------- +# update repo configurations +# ---------------------------------------------------------------------------- + +for my $repo (keys %repo_config) { + wrap_chdir("$repo_base_abs/$repo.git"); + while ( my ($key, $value) = each(%{ $repo_config{$repo} }) ) { + if ($value) { + $value =~ s/^"(.*)"$/$1/; + system("git", "config", $key, $value); + } else { + system("git", "config", "--unset-all", $key); + } + } +} + # ---------------------------------------------------------------------------- # handle gitweb and daemon # ---------------------------------------------------------------------------- From 64979c18ea2758f87960965bf325d310ba1611cf Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 9 Dec 2009 12:16:22 +0530 Subject: [PATCH 179/637] document repo config support --- conf/example.conf | 23 +++++++++++++++++++++++ doc/2-admin.mkd | 23 +++++++++++++++++++++++ doc/3-faq-tips-etc.mkd | 7 +++++++ 3 files changed, 53 insertions(+) diff --git a/conf/example.conf b/conf/example.conf index 76db973..84ccff2 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -181,3 +181,26 @@ repo linux perl # give gitweb access as described above if you're specifying a description gitolite "Sitaram Chamarty" = "fast, secure, access control for git in a corporate environment" + +# REPO SPECIFIC GITCONFIG +# ----------------------- + +# (Thanks to teemu dot matilainen at iki dot fi) + +# this should be specified within a "repo" stanza + +# syntax: +# config sectionname.keyname = [optional value_string] + +# example usage: if you placed a hook in src/hooks that requires configuration +# information that is specific to each repo, you could do this: + +repo gitolite + config hooks.mailinglist = gitolite-commits@example.tld + config hooks.emailprefix = "[gitolite] " + config foo.bar = "" + config foo.baz = + +# This does either a plain "git config section.key value" (for the first 3 +# examples above) or "git config --unset-all section.key" (for the last +# example). Other forms (--add, the value_regex, etc) are not supported. diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index d0460ec..a93df4b 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -9,6 +9,7 @@ In this document: * adding users and repos * specifying gitweb and daemon access * custom hooks + * custom git config ### administer @@ -90,3 +91,25 @@ just run easy install once again; it'll do it to existing repos also. implements all the branch-level permissions in gitolite. If you fiddle with the hooks directory, please make sure you do not mess with this file accidentally, or all your fancy per-branch permissions will stop working.** + +#### custom git config + +The custom hooks feature is a blunt instrument -- all repos get the hook you +specified and will run it. In order to make it a little more fine-grained, +you could set your hooks to only work if a certain "gitconfig" variable was +set. Which means we now need a way to specify "git config" settings on a per +repository basis. + +Thanks to Teemu (teemu dot matilainen at iki dot fi), gitolite now does this +very easily. For security reasons, this can only be done from the master +config file (i.e., if you're using delegation, the delegated admins cannot +specify git config settings). + +Please see `conf/example.conf` for syntax. Note that this only supports the +basic forms of the "git config" command: + + git config section.key value # value may be an empty string + git config --unset-all section.key + +It does not (currently) support other options like `--add`, the `value_regex`, +etc. diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 9a1d413..b824dec 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -19,6 +19,7 @@ In this document: * what repos do I have access to? * "exclude" (or "deny") rules * "personal" branches + * custom hooks and custom git config * design choices * keeping the parser and the access control separate @@ -530,6 +531,12 @@ first check: Just don't *show* the user this config file; it might sound insulting :-) +#### custom hooks and custom git config + +You can specify hooks that you want to propagate to all repos, as well as +per-repo "gitconfig" settings. Please see `doc/2-admin.mkd` and +`conf/example.conf` for details. + ### design choices #### keeping the parser and the access control separate From 780b4cca200cdee3c761215e4c949f2432fc8897 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 10 Dec 2009 17:07:46 +0530 Subject: [PATCH 180/637] ssh-copy-id workaround detail plus a couple other doc fixes --- doc/0-INSTALL.mkd | 13 ++++++++----- doc/3-faq-tips-etc.mkd | 44 +++++++++++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index 106e415..af4022f 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -37,13 +37,16 @@ Assumptions/pre-requisites: * git is installed on that server (and so is perl) * you have a userid on that server * you have ssh-pubkey (**password-less**) login to that userid - * (if you have only password access, run `ssh-keygen -t rsa` to create a - new keypair if needed, then run `ssh-copy-id user@host`) + * if you have only password access, run `ssh-keygen -t rsa` to create a + new keypair if needed, then run `ssh-copy-id user@host`. If you do + not have `ssh-copy-id`, read doc/3-faq-tips-etc.mkd and look for + `ssh-copy-id` in that file for instructions * you have a clone or an archive of gitolite somewhere on your workstation + * if you don't have one, just run `git clone git://github.com/sitaramc/gitolite` -If so, just `cd` to that clone and run `src/gl-easy-install` and follow the -prompts! (Running it without any arguments shows you usage plus other useful -info). +Once you have all this, just `cd` to that clone and run `src/gl-easy-install` +and follow the prompts! (Running it without any arguments shows you usage +plus other useful info). #### typical example run diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index b824dec..b6c65d9 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -25,16 +25,25 @@ In this document: ### common errors and mistakes - * forgetting to suffix `.git` to the end of the reponame in the `git clone`. - This suffix is *not* used in the gitolite config file for the sake of - clarity and cleaner syntax, but don't let that fool you. It's a - convention in the git world that **bare repos** end with `.git`. - * adding `repositories/` at the start of the repo name in the `git clone`. This error is typically made by the *admin* himself -- because he knows what `$REPO_BASE` is set to and thinks he has to provide that prefix on - the client side also :-) In fact gitolite prepends `$REPO_BASE` when it - is required anyway, so you shouldn't do the same thing! + the client side also :-) In fact gitolite prepends `$REPO_BASE` + internally, so you shouldn't also do the same thing! + + * being able to clone but getting errors on push. Most likely caused by a + combination of: + + * you already have shell access to the server, not just "gitolite" + access, *and* + + * you cloned using `git clone git@server:repositories/repo.git` (notice + there's an extra "repositories/" in there?) + + In other words, you used a key that completely bypassed gitolite and went + straight to the shell to do the clone. + + Please see doc/6-ssh-troubleshooting.mkd for what all this means. ### git version dependency @@ -67,6 +76,27 @@ normal way, since it's not empty anymore. ### other errors, warnings, notes... + * don't have `ssh-copy-id`? This is broadly what that command does, if you + want to replicate it manually. The input is your pubkey, typically + `~/.ssh/id_rsa.pub` from your client/workstation. + + * it copies it to the server as some file + + * it appends that file to `~/.ssh/authorized_keys` on the server + (creating it if it doesn't already exist) + + * it then makes sure that all these files/directories have go-w perms + set (assuming user is "git"): + + /home/git/.ssh/authorized_keys + /home/git/.ssh + /home/git + + [Actually, sshd requires that even directories *above* ~ (/, /home, + typically) also must be `go-w`, but that needs root. And typically + they're already set that way anyway. (Or if they're not, you've got + bigger problems than gitolite install not working!)] + * cloning an empty repo is only possible with clients greater than 1.6.2. So at least one of your clients needs to have a recent git. Once at least one commit has been made, older clients can also use it From 33fc0a7e9fe98dac1eec284119cf47509d68ab8c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 11 Dec 2009 09:52:29 +0530 Subject: [PATCH 181/637] compile, parse_acl: treat foo/CREATER (no regex metas) correctly Teemu's testing brought up a situtation I had not anticipated: "repo foo/CREATER" looks like a non-regex, and its creation then (a) goes by "W" permissions instead of "C" permissions, and (b) the creater's name does not get recorded (no gl-creater file). SIDE NOTE: one way is to reduce the paranoia, and just put the creater name in anyway. Treat a repo created from gl-auth as a wildcard-matched autovivified repo, because the *other* kind would have actually got created by gl-compile anyway. However, I can think of *one* very far-out situation where this could backfire on an unwary admin, and I'm paranoid :-) So we need to force it to look like a regex. Moving the line-anchoring from `parse_acl` to gl-compile sounded fine, until I realised that the "$" isn't easy. Backslashitis, bigtime, plus the single/double quote tricks we're playing with the dumped hash adds its own complexities. Best of both worlds, promote the "^" to gl-compile, keep the "$" where it is...! --- src/gitolite.pm | 8 +++++++- src/gl-compile-conf | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 41c45b1..0534a87 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -177,9 +177,15 @@ sub parse_acl return unless $repo; return $ENV{GL_REPOPATT} = "" if $repos{$repo}; - my @matched = grep { $repo =~ /^$_$/ } sort keys %repos; + + # didn't find $repo in %repos, so it must be a wildcard-match case + + # note that the repo regexes in %repos have a leading ^ but not a trailing + # $; we need to add the $ here to complete the "line-anchoring" + my @matched = grep { $repo =~ /$_$/ } sort keys %repos; die "$repo has no matches\n" unless @matched; die "$repo has multiple matches\n@matched\n" if @matched > 1; + # found exactly one pattern that matched, copy its ACL $repos{$repo} = $repos{$matched[0]}; # and return the pattern diff --git a/src/gl-compile-conf b/src/gl-compile-conf index da1822a..618dcae 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -192,7 +192,11 @@ sub parse_conf_file @repos = split ' ', $1; @repos = expand_list ( @repos ); - s/\bCREAT[EO]R\b/\$creater/g for @repos; + # CREAT[EO]R must be changed to $creater. Also, prefix a "^" to + # force it to look like a regex. Otherwise, foo/CREATER/bar (no + # regex metas) looks like an ordinary reponame, and the logic (in + # gl-auth) that decides when to allow autovivify gets confused. + s/\bCREAT[EO]R\b/\$creater/g && s/^/^/ for @repos; } # actual permission line elsif (/^(-|C|R|RW|RW\+) (.* )?= (.+)/) From a15e910cf8b0ff305f512cddcb64cbf76f4132b1 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 11 Dec 2009 21:37:33 +0530 Subject: [PATCH 182/637] auth: dont allow trailing slash in reponames... ...like "git clone host:foo/", even if it matches "repo foo/.*" NOTE: I expect a few more of these special cases to be found as time goes on and people find new ways to abuse the regex system, whether it is done intentionally or not. Anything not fixable by changing the config file will be fixed in the code asap. This one, for instance, seems fixable by using "foo/.+" instead of "foo/.*". But it actually isn't; the user can do "git clone host:foo//" and bypass that :( Still I suspect most situations will get an entry in the "then don't do that" file :) ---- patient: "doc, it hurts when I do this" doc: "then don't do that" --- src/gl-auth-command | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gl-auth-command b/src/gl-auth-command index cc4b5f9..3f0ea3b 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -103,6 +103,7 @@ my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:.git)?'/); die "bad command: $cmd. Make sure the repo name is exactly as in your config\n" unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo and $repo =~ $REPONAME_PATT ); +die "$repo ends with a slash; I don't like that\n" if $repo =~ /\/$/; # ---------------------------------------------------------------------------- # first level permissions check From 3eb29e17fc6a1fe6b79c31192e09d8d084f4def8 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 12 Dec 2009 09:51:29 +0530 Subject: [PATCH 183/637] allow @ in repo names and patterns stuff like "repo foo/CREATER/.+" means the reponame has to be a superset of the username in terms of allowed characters --- src/gitolite.pm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 1ce0480..56336de 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -24,11 +24,13 @@ $WARN = "\n\t\t***** WARNING *****\n "; $R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/; $W_COMMANDS=qr/^git[ -]receive-pack$/; -# note that REPONAME_PATT allows "/", while USERNAME_PATT allows "@" -$REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern +# note that REPONAME_PATT allows "/", while USERNAME_PATT does not +# also, the reason REPONAME_PATT is a superset of USERNAME_PATT is (duh!) +# because in this version, a repo can have "CREATER" in the name (see docs) +$REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/-]*$); # very simple pattern $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@-]*$); # very simple pattern # same as REPONAME, plus some common regex metas -$REPOPATT_PATT=qr(^\@?[0-9a-zA-Z][\\^.$|()[\]*+?{}0-9a-zA-Z._/-]*$); +$REPOPATT_PATT=qr(^\@?[0-9a-zA-Z][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/-]*$); # ---------------------------------------------------------------------------- # convenience subs From ecdf6f2350e1d57d77d86ac5af6477e45bc25433 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 13 Dec 2009 12:43:44 +0530 Subject: [PATCH 184/637] auth: don't allow literal ".." in reponames thanks to Teemu for catching this --- src/gl-auth-command | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gl-auth-command b/src/gl-auth-command index 3f0ea3b..b9e6359 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -104,6 +104,7 @@ die "bad command: $cmd. Make sure the repo name is exactly as in your config\n" unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo and $repo =~ $REPONAME_PATT ); die "$repo ends with a slash; I don't like that\n" if $repo =~ /\/$/; +die "$repo has two consecutive periods; I don't like that\n" if $repo =~ /\.\./; # ---------------------------------------------------------------------------- # first level permissions check From ed2bf526f82eb2d281fb83e8126580b78223fa94 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 13 Dec 2009 19:17:18 +0530 Subject: [PATCH 185/637] minor docfix --- doc/3-faq-tips-etc.mkd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index b6c65d9..0e3af99 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -101,10 +101,10 @@ normal way, since it's not empty anymore. So at least one of your clients needs to have a recent git. Once at least one commit has been made, older clients can also use it - * when you clone an empty repo, git seems to complain about the remote - hanging up or something. I have no idea what that is, but it doesn't seem - to hurt anything. This happens even in normal git, not just gitolite. - [Update 2009-09-14; this has been fixed in git 1.6.4.3] + * when you clone an empty repo, git seems to complain about `fatal: The + remote end hung up unexpectedly`. However, you can ignore this, since it + doesn't seem to hurt anything. [Update 2009-09-14; this has been fixed in + git 1.6.4.3] * gitweb not able to read your repos? You can change the umask for newly created repos to something more relaxed -- see the `~/.gitolite.rc` file From b7404aa772adee312483760da869497f6d2a64a2 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 15 Dec 2009 12:35:48 +0530 Subject: [PATCH 186/637] auth/install/pu-hook: pass ADMINDIR and BINDIR via ENV The admin repo's post-update hook needs to know where $GL_ADMINDIR is, and we had a weird way of doing that which depended on gl-install actually munging the hook code. We also always assumed the binaries are in GL_ADMINDIR/src. We now use an env var to pass both these values. This removes the weird dependency on gl-install that the post-update hook had, as well as make running other programs easier due to the new $GL_BINDIR env var. --- src/ga-post-update-hook | 7 ++----- src/gl-auth-command | 5 +++++ src/gl-compile-conf | 2 +- src/gl-easy-install | 2 +- src/gl-install | 2 -- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ga-post-update-hook b/src/ga-post-update-hook index 8090b8a..91d2bfb 100755 --- a/src/ga-post-update-hook +++ b/src/ga-post-update-hook @@ -1,10 +1,7 @@ #!/bin/sh -# get this from your .gitolite.conf; and don't forget this is shell, while -# that is perl :-) -export GL_ADMINDIR; GL_ADMINDIR=$HOME/.gitolite - # checkout the master branch to $GL_ADMINDIR +# (the GL_ADMINDIR env var would have been set by gl-auth-command) GIT_WORK_TREE=$GL_ADMINDIR git checkout -f master # remove all fragments. otherwise, you get spurious error messages when you @@ -31,4 +28,4 @@ do done cd $GL_ADMINDIR -src/gl-compile-conf +$GL_BINDIR/gl-compile-conf diff --git a/src/gl-auth-command b/src/gl-auth-command index 86fec88..12b3234 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -39,6 +39,11 @@ require "$bindir/gitolite.pm"; &where_is_rc(); die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; +# we need to pass GL_ADMINDIR and the bindir to the child hooks (well only the +# admin repo's post-update hook but still...) +$ENV{GL_ADMINDIR} = $GL_ADMINDIR; +$ENV{GL_BINDIR} = $bindir; + # add a custom path for git binaries, if specified $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 138249d..ab1155c 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -73,7 +73,7 @@ $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; # ---------------------------------------------------------------------------- # command and options for authorized_keys -$AUTH_COMMAND="$GL_ADMINDIR/src/gl-auth-command"; +$AUTH_COMMAND="$bindir/gl-auth-command"; $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; # groups can now represent user groups or repo groups. diff --git a/src/gl-easy-install b/src/gl-easy-install index db20e2f..d73c82d 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -48,7 +48,7 @@ main() { [[ $upgrade == 0 ]] && initial_conf_key # MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" - ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf $quiet" + ssh -p $port $user@$host "cd $GL_ADMINDIR; \$PWD/src/gl-compile-conf $quiet" setup_pta diff --git a/src/gl-install b/src/gl-install index a4dc729..9494fc6 100755 --- a/src/gl-install +++ b/src/gl-install @@ -79,8 +79,6 @@ for my $repo (`find . -type d -name "*.git"`) { if ( -d "gitolite-admin.git/hooks" ) { print "copying post-update hook to gitolite-admin repo...\n"; system("cp $GL_ADMINDIR/src/ga-post-update-hook gitolite-admin.git/hooks/post-update"); - system("perl", "-i", "-p", "-e", "s(GL_ADMINDIR=.*)(GL_ADMINDIR=$GL_ADMINDIR)", - "gitolite-admin.git/hooks/post-update"); chmod 0755, "gitolite-admin.git/hooks/post-update"; } From 512fc4a0a547a3c29d1502e8caddd692ac880890 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 15 Dec 2009 16:11:21 +0530 Subject: [PATCH 187/637] auth: set umask when autoviv-ing repos Looks like I'd forgotten this when I did the autoviv code. Repos created via gl-compile (when you add a new repo to the config file and push) worked fine, but repos created via gl-auth (when you autoviv a repo, wild or not) did not. This *should* be merged into wildrepos soon after testing; wildrepos will have a lot more autoviv-ing than master. --- src/gl-auth-command | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index 12b3234..463439e 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -23,9 +23,8 @@ use warnings; # common definitions # ---------------------------------------------------------------------------- - # these are set by the "rc" file -our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $GL_ADMINDIR); +our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR); # and these are set by gitolite.pm our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT); our %repos; @@ -47,6 +46,9 @@ $ENV{GL_BINDIR} = $bindir; # add a custom path for git binaries, if specified $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; +# set the umask before creating any files +umask($REPO_UMASK); + # ---------------------------------------------------------------------------- # start... # ---------------------------------------------------------------------------- From b679bbb56bdc1a44a316298c9c3f7fb0d52f13dc Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 18 Dec 2009 10:15:35 +0530 Subject: [PATCH 188/637] allow '+' as valid character in user/reponames --- src/gitolite.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index ee0fc77..d905a72 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -25,8 +25,8 @@ $R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/; $W_COMMANDS=qr/^git[ -]receive-pack$/; # note that REPONAME_PATT allows "/", while USERNAME_PATT allows "@" -$REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern -$USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@-]*$); # very simple pattern +$REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/+-]*$); # very simple pattern +$USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); # very simple pattern # ---------------------------------------------------------------------------- # convenience subs From 75de6c0438090d0d4d4e593867739d638cb7f8d6 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 19 Dec 2009 20:52:30 +0530 Subject: [PATCH 189/637] auth: (WDITOT?) allow special users to get a shell ".../gl-auth-command username" is the normal command that authkeys forces, and this prevents that key from being used to get a shell. We now allow the user to get a shell if the forced command has a "-s" before the "username", like ".../gl-auth-command -s sitaram". (Now that a plain "ssh gitolite" gets you a shell, there's a new "info" command that such privileged keys can use to get basic access info). Thanks to Jesse Keating for the idea! I can't believe this never occurred to me before, but I guess I was so enamoured of my "innovation" in converting what used to be an error into some useful info I didn't think a bit more :/ --- src/gl-auth-command | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index 463439e..2de7d43 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -53,6 +53,14 @@ umask($REPO_UMASK); # start... # ---------------------------------------------------------------------------- +# if the first argument is a "-s", this user is allowed to get a shell using +# this key +my $shell_allowed = 0; +if ($ARGV[0] eq '-s') { + $shell_allowed = 1; + shift; +} + # first, fix the biggest gripe I have with gitosis, a 1-line change my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere! @@ -60,13 +68,24 @@ my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere! # sanity checks on SSH_ORIGINAL_COMMAND # ---------------------------------------------------------------------------- -# SSH_ORIGINAL_COMMAND must exist; if not, we die with a nice message +# print basic access info if SSH_ORIGINAL_COMMAND does not exist unless ($ENV{SSH_ORIGINAL_COMMAND}) { + # unless the user is allowed to use a shell + if ($shell_allowed) { + my $shell = $ENV{SHELL}; + $shell =~ s/.*\//-/; # change "/bin/bash" to "-bash" + exec { $ENV{SHELL} } $shell; + } &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user); exit 1; } my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; +# people allowed to get a shell can get basic access info by asking nicely +if ($shell_allowed and $cmd eq 'info') { + &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user); + exit 1; +} # split into command and arguments; the pattern allows old style as well as # new style: "git-subcommand arg" or "git subcommand arg", just like gitosis @@ -77,9 +96,12 @@ my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; # including the single quotes my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:.git)?'/); -die "bad command: $cmd. Make sure the repo name is exactly as in your config\n" - unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) - and $repo and $repo =~ $REPONAME_PATT ); +unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo and $repo =~ $REPONAME_PATT ) { + # if the user is allowed a shell, just run the command + exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND} if $shell_allowed; + # otherwise, whine + die "bad command: $cmd\n"; +} # ---------------------------------------------------------------------------- # first level permissions check From 2cc19091ca53e38bf4458d6c39ca409a8c95979d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 19 Dec 2009 21:36:55 +0530 Subject: [PATCH 190/637] compile: gitolite key as good as shell key for users in @SHELL group done by inserting a "-s" into the authkey forced command. (They also lose the "no-pty" restriction, for good measure!) --- src/gl-compile-conf | 9 +++++++-- src/gl-easy-install | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index ab1155c..b8e8a1c 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -74,7 +74,8 @@ $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; # command and options for authorized_keys $AUTH_COMMAND="$bindir/gl-auth-command"; -$AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; +$AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding"; + # note, for most users there's also a "no-pty" added to this, see later # groups can now represent user groups or repo groups. @@ -441,7 +442,11 @@ for my $pubkey (glob("*")) print STDERR "WARNING: pubkey $pubkey exists but user $user not in config\n" unless $user_list{$user}; $user_list{$user} = 'has pubkey'; - print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS "; + if ($groups{'@SHELL'}{$user}) { + print $newkeys_fh "command=\"$AUTH_COMMAND -s $user\",$AUTH_OPTIONS "; + } else { + print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS,no-pty "; + } # apparently some pubkeys don't end in a newline... my $pubkey_content = `cat $pubkey`; $pubkey_content =~ s/\s*$/\n/; diff --git a/src/gl-easy-install b/src/gl-easy-install index d73c82d..b74621a 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -361,6 +361,7 @@ run_install() { # MANUAL: setup the initial config file. Edit $GL_ADMINDIR/conf/gitolite.conf # and add at least the following lines to it: +# @SHELL = sitaram # repo gitolite-admin # RW+ = sitaram @@ -368,6 +369,8 @@ initial_conf_key() { echo "#gitolite conf # please see conf/example.conf for details on syntax and features +@SHELL = $admin_name + repo gitolite-admin RW+ = $admin_name From 714e2142586069810f8a6e7abe4dac41c3fa4440 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 20 Dec 2009 11:57:53 +0530 Subject: [PATCH 191/637] wildrepos: catch-all disclaimer for missing features ;-) --- doc/4-wildcard-repositories.mkd | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/4-wildcard-repositories.mkd b/doc/4-wildcard-repositories.mkd index 7e54a1c..1feac40 100644 --- a/doc/4-wildcard-repositories.mkd +++ b/doc/4-wildcard-repositories.mkd @@ -8,6 +8,14 @@ This branch contains features that are likely to be much more brittle than the R and RW permissions to other users to collaborate, all these are possible. And any of these could have a bug in it. +"Brittle" also means some features in "master" may not work here. For +example, you cannot specify gitconfig values for a wildcard repo; it only +works for actual repos. + +There may be other such missing features. Sometimes it's just not possible to +make it work. Or it may be cumbersome enough that unless there are *no* +workarounds I may not have the time to code it right away. + ---- In this document: From 6f45f75ca11a2711e03ff057c7cfbbddac9e65dd Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 21 Dec 2009 06:23:25 +0530 Subject: [PATCH 192/637] minor docfix --- doc/6-ssh-troubleshooting.mkd | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/6-ssh-troubleshooting.mkd b/doc/6-ssh-troubleshooting.mkd index 2b9febc..4bf980f 100644 --- a/doc/6-ssh-troubleshooting.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -148,8 +148,9 @@ Here's how it all hangs together. Luckily, ssh has a very convenient way of capturing all the connection information (username, hostname, port number (if it's not the default 22), - and keypair to be used) in one "paragraph". This is what the para looks - like for us (the easy install script puts it there the first time): + and keypair to be used) in one "paragraph" of `~/.ssh/config`. This is + what the para looks like for us (the easy install script puts it there the + first time): host gitolite user git From 981d693dec7e01fed4da502964b38df6101762a1 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 21 Dec 2009 17:33:53 +0530 Subject: [PATCH 193/637] Revert "compile, parse_acl: treat foo/CREATER (no regex metas) correctly" This reverts commit 33fc0a7e9fe98dac1eec284119cf47509d68ab8c. Was causing too much trouble with access reporting (basic and expanded) because of the extra ^ at the start... The paranoia referred to in that commit was this sequence: - admin creates a named (non wildcard) repo using config file push - somehow that gets deleted (OS error, corruption, ...) - admin just asks anyone with a current repo to push it to auto-revive it (because we allow people with "W" access to non-wildcard repos to auto-viv repos) - if you're treating this the same as a wildcard creation, you end up making this guy the "creater" of that repo, which means he can add users etc... We resolve that paranois by disallowing autoviv of "W" access repos at all... Only "C" access repos can be autovived by a user (this will be in the next commit) --- src/gitolite.pm | 8 +------- src/gl-compile-conf | 6 +----- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 23f7e23..657c0fa 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -179,15 +179,9 @@ sub parse_acl return unless $repo; return $ENV{GL_REPOPATT} = "" if $repos{$repo}; - - # didn't find $repo in %repos, so it must be a wildcard-match case - - # note that the repo regexes in %repos have a leading ^ but not a trailing - # $; we need to add the $ here to complete the "line-anchoring" - my @matched = grep { $repo =~ /$_$/ } sort keys %repos; + my @matched = grep { $repo =~ /^$_$/ } sort keys %repos; die "$repo has no matches\n" unless @matched; die "$repo has multiple matches\n@matched\n" if @matched > 1; - # found exactly one pattern that matched, copy its ACL $repos{$repo} = $repos{$matched[0]}; # and return the pattern diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 9da28ed..7d8e1d9 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -195,11 +195,7 @@ sub parse_conf_file @repos = split ' ', $1; @repos = expand_list ( @repos ); - # CREAT[EO]R must be changed to $creater. Also, prefix a "^" to - # force it to look like a regex. Otherwise, foo/CREATER/bar (no - # regex metas) looks like an ordinary reponame, and the logic (in - # gl-auth) that decides when to allow autovivify gets confused. - s/\bCREAT[EO]R\b/\$creater/g && s/^/^/ for @repos; + s/\bCREAT[EO]R\b/\$creater/g for @repos; } # actual permission line elsif (/^(-|C|R|RW|RW\+) (.* )?= (.+)/) From 6fb2296e2c7b136d25d86fc487aafce101a7201a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 21 Dec 2009 17:49:21 +0530 Subject: [PATCH 194/637] autoviv new repos by user only on "C" access we've removed the facility of auto-viving "W" access repos when they are not wildcards. A wildcard pattern like foo/CREATER was indistinguishable from a non-wildcard repo, and resolving it was becoming kludgier and kludgier. (See the revert in the commit before this one for details). As a side effect of not being able to distinguish wildcard repos from real repos easily, the expand command now works for a normal repo too (because we have to make it work for "foo/CREATER") --- src/gitolite.pm | 2 ++ src/gl-auth-command | 18 ++++++------------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 657c0fa..1a3655f 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -179,6 +179,8 @@ sub parse_acl return unless $repo; return $ENV{GL_REPOPATT} = "" if $repos{$repo}; + + # didn't find $repo in %repos, so it must be a wildcard-match case my @matched = grep { $repo =~ /^$_$/ } sort keys %repos; die "$repo has no matches\n" unless @matched; die "$repo has multiple matches\n@matched\n" if @matched > 1; diff --git a/src/gl-auth-command b/src/gl-auth-command index c5b78ae..1ced74c 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -99,7 +99,7 @@ if ($cmd =~ $CUSTOM_COMMANDS) { # with an actual reponame, you can "getperms" or "setperms" get_set_perms($repo_base_abs, $repo, $verb, $user); } - elsif ($repo !~ $REPONAME_PATT and $verb eq 'expand') { + elsif ($verb eq 'expand') { # with a wildcard, you can "expand" it to see what repos actually match expand_wild($GL_CONF_COMPILED, $repo_base_abs, $repo, $user); } else { @@ -143,20 +143,14 @@ die "$repo has two consecutive periods; I don't like that\n" if $repo =~ /\.\./; if ( -d "$repo_base_abs/$repo.git" ) { # existing repo my ($creater, $user_R, $user_W) = &repo_rights($repo_base_abs, $repo, $user); - my $patt = &parse_acl($GL_CONF_COMPILED, $repo, $creater, $user_R, $user_W); + &parse_acl($GL_CONF_COMPILED, $repo, $creater, $user_R, $user_W); } else { - my $patt = &parse_acl($GL_CONF_COMPILED, $repo, $user, $user, $user); - # parse_acl returns "" if the repo was non-wildcard, or the pattern - # that matched if it was a wildcard + &parse_acl($GL_CONF_COMPILED, $repo, $user, $user, $user); - # auto-vivify new repo; 2 situations allow autoviv -- normal repos - # with W access (the old mode), and wildcard repos with C access - my $W_ok = $repos{$repo}{W}{$user} || $repos{$repo}{W}{'@all'}; - my $C_ok = $repos{$repo}{C}{$user} || $repos{$repo}{C}{'@all'}; - if ($W_ok and not $patt or $C_ok and $patt) { + # auto-vivify new repo if you have C access + if ( $repos{$repo}{C}{$user} || $repos{$repo}{C}{'@all'} ) { wrap_chdir("$repo_base_abs"); - # for wildcard repos, we also want to set the "creater" - new_repo($repo, "$GL_ADMINDIR/src/hooks", ( $patt ? $user : "")); + new_repo($repo, "$GL_ADMINDIR/src/hooks", $user); wrap_chdir($ENV{HOME}); } } From 03babe77727b4290d4e3aa6199c692e0ce3ca6fb Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 21 Dec 2009 17:07:31 +0530 Subject: [PATCH 195/637] parse_acl (gitolite.pm): minor convenience added --- src/gitolite.pm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 1a3655f..4cfa8e1 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -175,7 +175,8 @@ sub parse_acl die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; - # access reporting doesn't send $repo, and doesn't need to + # basic access reporting doesn't send $repo, and doesn't need to; you just + # want the config dumped as is, really return unless $repo; return $ENV{GL_REPOPATT} = "" if $repos{$repo}; @@ -222,6 +223,10 @@ sub expand_wild { my($GL_CONF_COMPILED, $repo_base_abs, $repo, $user) = @_; + # this is for convenience; he can copy-paste the output of the basic + # access report instead of having to manually change CREATER to his name + $repo =~ s/\bCREAT[EO]R\b/$user/g; + # display matching repos (from *all* the repos in the system) that $user # has at least "R" access to From f37fb451447639b6ceeb1f389f371068ba5b0b90 Mon Sep 17 00:00:00 2001 From: Teemu Matilainen Date: Mon, 21 Dec 2009 01:55:45 +0200 Subject: [PATCH 196/637] compile: support "repo @all" definitions "repo @all" can be used to set permissions or configurations for all already defined repos. (A repository is defined if it has permission rules associated, empty "repo" stanza or "@group=..." line is not enough.) For example to allow a backup user to clone all repos: # All other configuration [...] repo @all R = backup Signed-off-by: Teemu Matilainen --- conf/example.conf | 7 +++++++ src/gl-compile-conf | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/conf/example.conf b/conf/example.conf index 84ccff2..6204c91 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -101,6 +101,13 @@ repo gitolite repo @oss_repos R = @all + # set permissions to all already defined repos + # (a repository is defined if it has permission rules + # associated, empty "repo" stanza or "@group=..." line is + # not enough) +repo @all + RW+ = @admins + # ADVANCED PERMISSIONS USING REFEXES # - refexes are specified in perl regex syntax diff --git a/src/gl-compile-conf b/src/gl-compile-conf index b8e8a1c..4c34d1d 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -185,7 +185,11 @@ sub parse_conf_file { # grab the list and expand any @stuff in it @repos = split ' ', $1; - @repos = expand_list ( @repos ); + if (@repos == 1 and $repos[0] eq '@all') { + @repos = keys %repos; + } else { + @repos = expand_list ( @repos ); + } } # actual permission line elsif (/^(-|R|RW|RW\+) (.* )?= (.+)/) From ba3cbd7ecf02078b84e1527cc27e923306358727 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 21 Dec 2009 22:58:47 +0530 Subject: [PATCH 197/637] doc/3, conf: document @all for repos plus some refactoring of doc/3 --- conf/example.conf | 3 +- doc/3-faq-tips-etc.mkd | 72 ++++++++++++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index 6204c91..26317e2 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -104,7 +104,8 @@ repo @oss_repos # set permissions to all already defined repos # (a repository is defined if it has permission rules # associated, empty "repo" stanza or "@group=..." line is - # not enough) + # not enough). *Please* do see doc/3-faq-tips-etc.mkd for + # some important notes on this feature repo @all RW+ = @admins diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 0e3af99..a7ee05a 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -5,6 +5,10 @@ In this document: * common errors and mistakes * git version dependency * other errors, warnings, notes... + * ssh-copy-id + * cloning an empty repo + * `@all` syntax for repos + * umask setting * getting a tar file from a clone * differences from gitosis * simpler syntax @@ -76,38 +80,58 @@ normal way, since it's not empty anymore. ### other errors, warnings, notes... - * don't have `ssh-copy-id`? This is broadly what that command does, if you - want to replicate it manually. The input is your pubkey, typically - `~/.ssh/id_rsa.pub` from your client/workstation. +#### ssh-copy-id - * it copies it to the server as some file +don't have `ssh-copy-id`? This is broadly what that command does, if you want +to replicate it manually. The input is your pubkey, typically +`~/.ssh/id_rsa.pub` from your client/workstation. - * it appends that file to `~/.ssh/authorized_keys` on the server - (creating it if it doesn't already exist) + * it copies it to the server as some file - * it then makes sure that all these files/directories have go-w perms - set (assuming user is "git"): + * it appends that file to `~/.ssh/authorized_keys` on the server + (creating it if it doesn't already exist) - /home/git/.ssh/authorized_keys - /home/git/.ssh - /home/git + * it then makes sure that all these files/directories have go-w perms + set (assuming user is "git"): - [Actually, sshd requires that even directories *above* ~ (/, /home, - typically) also must be `go-w`, but that needs root. And typically - they're already set that way anyway. (Or if they're not, you've got - bigger problems than gitolite install not working!)] + /home/git/.ssh/authorized_keys + /home/git/.ssh + /home/git - * cloning an empty repo is only possible with clients greater than 1.6.2. - So at least one of your clients needs to have a recent git. Once at least - one commit has been made, older clients can also use it +[Actually, sshd requires that even directories *above* ~ (/, /home, +typically) also must be `go-w`, but that needs root. And typically +they're already set that way anyway. (Or if they're not, you've got +bigger problems than gitolite install not working!)] - * when you clone an empty repo, git seems to complain about `fatal: The - remote end hung up unexpectedly`. However, you can ignore this, since it - doesn't seem to hurt anything. [Update 2009-09-14; this has been fixed in - git 1.6.4.3] +#### cloning an empty repo - * gitweb not able to read your repos? You can change the umask for newly - created repos to something more relaxed -- see the `~/.gitolite.rc` file +Cloning an empty repo is only possible with clients greater than 1.6.2. So at +least one of your clients needs to have a recent git. Once at least one +commit has been made, older clients can also use it + +When you clone an empty repo, git seems to complain about `fatal: The remote +end hung up unexpectedly`. However, you can ignore this, since it doesn't +seem to hurt anything. [Update 2009-09-14; this has been fixed in git +1.6.4.3] + +#### `@all` syntax for repos + +There *is* a way to use the `@all` syntax for repos also, as described in +`conf/example.conf`. However, there is an important difference between this +and the old `@all` (for users): + + * `@all` for repos is immediately expanded, when found, into the currently + known list of repos. "Currently" means upto this point in the config + file, and "known" means having some user with some permissions associated + with the repo! + + * This means that if you really want *all* repos, you'd better put this para + at the **end** of the config file! + +#### umask setting + +Gitweb not able to read your repos? You can change the umask for newly +created repos to something more relaxed -- see the `~/.gitolite.rc` file ### getting a tar file from a clone From 9c3abb20e1138a53d6d42521ffe6d7aec44b98aa Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 22 Dec 2009 14:27:40 +0530 Subject: [PATCH 198/637] easy install: minor user message change for first-time install --- src/gl-easy-install | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gl-easy-install b/src/gl-easy-install index b74621a..819eb20 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -199,8 +199,8 @@ version_info() { git describe --tags --long HEAD 2>/dev/null > src/VERSION || echo '(unknown)' > src/VERSION # what was the old version there? - export upgrade_details="you are upgrading from \ - $(ssh -p $port $user@$host cat gitolite-install/src/VERSION 2>/dev/null || echo '(unknown)' ) \ + export upgrade_details="you are upgrading \ + $(ssh -p $port $user@$host cat gitolite-install/src/VERSION 2>/dev/null || echo '(or installing first-time)' ) \ to $(cat src/VERSION)" prompt "$upgrade_details" "$v_upgrade_details" From b0ce84d47f02d2f6cf920ef19b61aa225667bf9a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 23 Dec 2009 19:56:53 +0530 Subject: [PATCH 199/637] document @SHELL feature, allow "info" for all, ...but still distinguish shell folks with a small extra line telling them they have shell access --- conf/example.conf | 16 ++++++++++++++++ doc/6-ssh-troubleshooting.mkd | 15 +++++++++++++++ src/gl-auth-command | 3 ++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/conf/example.conf b/conf/example.conf index 26317e2..2c3c6fb 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -212,3 +212,19 @@ repo gitolite # This does either a plain "git config section.key value" (for the first 3 # examples above) or "git config --unset-all section.key" (for the last # example). Other forms (--add, the value_regex, etc) are not supported. + +# SHELL ACCESS +# ------------ + +# It is possible to give certain users shell access as well as allow them to +# use gitolite features for their git repo access. The idea is to eliminate +# the need for 2 keys when both shell and gitolite access are needed. + +# To give a user shell access, add the username to the special @SHELL group: + +@SHELL = sitaram + +# Do not add people to this group indiscriminately. AUDITABILITY OF ACCESS +# CONTROL CHANGES (AND OF REPO ACCESSES) WILL BE COMPROMISED IF ADMINS CAN +# FIDDLE WITH THE ACTUAL (PLAIN TEXT) LOG FILES THAT GITOLITE KEEPS, WHICH +# THEY CAN EASILY DO IF THEY HAVE A SHELL. diff --git a/doc/6-ssh-troubleshooting.mkd b/doc/6-ssh-troubleshooting.mkd index 4bf980f..9602dd2 100644 --- a/doc/6-ssh-troubleshooting.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -1,5 +1,20 @@ # ssh troubleshooting +Update 2009-12-23: most of this document is now of historical interest and +will be totally revamped when I have time. For now, just note this amendment. + +The document below says "we can't use the same key for both [gitolite access +and shell access]...". We've managed (thanks to an idea from Jesse Keating) +to get around this. Now it *is* possible for a single key to allow both +gitolite access *and* shell access. + +This is done by placing such a user in a special `@SHELL` group in the +gitolite config file. As usual, please see `conf/example.conf` for more info +on this, since I'm using that as a central place to document anything +concerned with the conf file. + +---- + Ssh has always been the biggest troublespot in all this. While gitolite makes it as easy as possible, you might still run into trouble sometimes. diff --git a/src/gl-auth-command b/src/gl-auth-command index 2de7d43..1e0d1d2 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -82,8 +82,9 @@ unless ($ENV{SSH_ORIGINAL_COMMAND}) { my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; # people allowed to get a shell can get basic access info by asking nicely -if ($shell_allowed and $cmd eq 'info') { +if ($cmd eq 'info') { &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user); + print "you also have shell access\n\r" if $shell_allowed; exit 1; } From d03152316f0d0d96c2a6c11c5ee6c7b31b0dc7d8 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 24 Dec 2009 22:35:43 +0530 Subject: [PATCH 200/637] install transcript --- doc/7-install-transcript.mkd | 211 +++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 doc/7-install-transcript.mkd diff --git a/doc/7-install-transcript.mkd b/doc/7-install-transcript.mkd new file mode 100644 index 0000000..026c98e --- /dev/null +++ b/doc/7-install-transcript.mkd @@ -0,0 +1,211 @@ +# gitolite install transcript + +This is a *complete* transcript of a full gitolite install, *from scratch*, +using brand new userids ("sita" on the client, "git" on the server). Please +note that you can use existing userids also, it is not necessary to use +dedicated user IDs for this. Also, you don't have to use some *other* server +for all this, both server and client can be "localhost" if you like. + +Please note that this entire transcript can be summarised as: + + * create users on client and server (optional) + * get pubkey access to server from client (`ssh-copy-id` or manual eqvt) + * run one command ***on client*** (`gl-easy-install`) + +...and only that last step is actually gitolite. In fact, the bulk of the +transcript is **non**-gitolite stuff :) + +---- + +### create userids on server and client (optional) + +Client side: add user, give him a password + + sita-lt:~ # useradd sita + + sita-lt:~ # passwd sita + Changing password for user sita. + New UNIX password: + Retype new UNIX password: + passwd: all authentication tokens updated successfully. + +Server side: (log on to server, then) add user, give it a password + + sita-lt:~ # ssh sitaram@server + sitaram@server's password: + Last login: Fri Dec 18 20:25:06 2009 + -bash-3.2$ su - + Password: + + sita-sv:~ # useradd git + + sita-sv:~ # passwd git + Changing password for user git. + New UNIX password: + Retype new UNIX password: + passwd: all authentication tokens updated successfully. + +Server side: allow ssh access to "git" user + +This is done by editing the sshd config file and adding "git" to the +"AllowUsers" list (the grep command is just confirming the change we made, +because I'm not showing the actual "vi" session): + + sita-sv:~ # vim /etc/ssh/sshd_config + + sita-sv:~ # grep -i allowusers /etc/ssh/sshd_config + AllowUsers sitaram git + + sita-sv:~ # service sshd restart + Stopping sshd: [ OK ] + Starting sshd: [ OK ] + +---- + +### get pubkey access from client to server + +This involves creating a keypair for yourself (using `ssh-keygen`), and +copying the public part of that keypair to the `~/.ssh/authorized_keys` file +on the server (using `ssh-copy-id`, if you're on Linux, or the manual method +described in the `ssh-copy-id` section in `doc/3-faq-tips-etc.mkd`). + + sita-lt:~ $ su - sita + Password: + + sita@sita-lt:~ $ ssh-keygen + Generating public/private rsa key pair. + Enter file in which to save the key (/home/sita/.ssh/id_rsa): + Created directory '/home/sita/.ssh'. + Enter passphrase (empty for no passphrase): + Enter same passphrase again: + Your identification has been saved in /home/sita/.ssh/id_rsa. + Your public key has been saved in /home/sita/.ssh/id_rsa.pub. + The key fingerprint is: + 8a:e0:60:1b:04:58:68:50:a4:d7:d0:3a:a5:2d:bf:0a sita@sita-lt.atc.tcs.com + The key's randomart image is: + +--[ RSA 2048]----+ + |===. | + |+o oo | + |o..=. | + |..= . | + |.o.+ S | + |.oo... . | + |E.. ... | + | . . | + | .. | + +-----------------+ + + sita@sita-lt:~ $ ssh-copy-id -i ~/.ssh/id_rsa git@server + git@server's password: + /usr/bin/xauth: creating new authority file /home/git/.Xauthority + Now try logging into the machine, with "ssh 'git@server'", and check in: + + .ssh/authorized_keys + + to make sure we haven't added extra keys that you weren't expecting. + +Double check to make sure you can log on to `git@server` without a password: + + sita@sita-lt:~ $ ssh git@server pwd + /home/git + +---- + +### get gitolite source + + sita@sita-lt:~ $ git clone git://github.com/sitaramc/gitolite gitolite-source + Initialized empty Git repository in /home/sita/gitolite-source/.git/ + remote: Counting objects: 1157, done. + remote: Compressing objects: 100% (584/584), done. + remote: Total 1157 (delta 756), reused 912 (delta 562) + Receiving objects: 100% (1157/1157), 270.08 KiB | 61 KiB/s, done. + Resolving deltas: 100% (756/756), done. + +### install gitolite + +Note that gitolite is installed from the *client*. The `easy-install` script +runs on the client but installs gitolite on the server! + + sita@sita-lt:~ $ cd gitolite-source/src + + **This is the only gitolite specific command in a typical +install sequence**. Run it without any arguments to see a usage +message. Run it without the `-q` to get a more verbose, pause-at-every-step, +install mode that allows you to change the defaults etc. + + + sita@sita-lt:src $ ./gl-easy-install -q git server sitaram + you are upgrading (or installing first-time) to v0.95-38-gb0ce84d + setting up keypair... + Generating public/private rsa key pair. + Enter passphrase (empty for no passphrase): + Enter same passphrase again: + Your identification has been saved in /home/sita/.ssh/sitaram. + Your public key has been saved in /home/sita/.ssh/sitaram.pub. + The key fingerprint is: + 2a:8e:88:42:36:7e:71:e8:cc:ff:4c:54:64:8e:cf:19 sita@sita-lt.atc.tcs.com + The key's randomart image is: + +--[ RSA 2048]----+ + | o | + | = | + | . E | + | + o | + | . .S+ | + | + o ... | + |+ = + .. | + |oo B .o | + |+ o o..o | + +-----------------+ + creating gitolite para in ~/.ssh/config... + finding/creating gitolite rc... + installing/upgrading... + Initialized empty Git repository in /home/git/repositories/gitolite-admin.git/ + Initialized empty Git repository in /home/git/repositories/testing.git/ + Pseudo-terminal will not be allocated because stdin is not a terminal. + fatal: No HEAD commit to compare with (yet) + [master (root-commit) 2f40d4b] start + 2 files changed, 13 insertions(+), 0 deletions(-) + create mode 100644 conf/gitolite.conf + create mode 100644 keydir/sitaram.pub + cloning gitolite-admin repo... + Initialized empty Git repository in /home/sita/gitolite-admin/.git/ + remote: Counting objects: 6, done. + remote: Compressing objects: 100% (4/4), done. + remote: Total 6 (delta 0), reused 0 (delta 0) + Receiving objects: 100% (6/6), done. + + + --------------------------------------------------------------- + + done! + + Reminder: + *Your* URL for cloning any repo on this server will be + gitolite:reponame.git + *Other* users you set up will have to use + git@server:reponame.git + + If this is your first time installing gitolite, please also: + tail -31 ./gl-easy-install + for next steps. + +---- + +### examine what you have + + sita@sita-lt:src $ cd ~/gitolite-admin/ + + sita@sita-lt:gitolite-admin $ git --no-pager log --stat + commit 2f40d4bb80d424dc39aae5d0973f8c1b2e395666 + Author: git + Date: Thu Dec 24 21:39:15 2009 +0530 + + start + + conf/gitolite.conf | 12 ++++++++++++ + keydir/sitaram.pub | 1 + + 2 files changed, 13 insertions(+), 0 deletions(-) + +And that's really all. Add keys to keydir here, edit conf/gitolite.conf as +needed, then add, commit, and push the changes to the server. Try out that +`tail -31 ./gl-easy-install` too :) From 79647078a3d285a7d0d6a97741f3b3bf3aa3fde0 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 25 Dec 2009 01:13:31 +0530 Subject: [PATCH 201/637] auth: regex goof on my part for those not yet able to upgrade (or until I merge this into the branch you care about), if you have a repo called, say "bk2git", just refer to it as "bk2git.git" in the clone command! [Thanks to Mark Frazer for finding this...] --- src/gl-auth-command | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index 1e0d1d2..d6482ed 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -96,7 +96,7 @@ if ($cmd eq 'info') { # git-receive-pack 'reponame.git' # including the single quotes -my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:.git)?'/); +my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:\.git)?'/); unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo and $repo =~ $REPONAME_PATT ) { # if the user is allowed a shell, just run the command exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND} if $shell_allowed; From ab3c861241b774b73e5e3b6d7abaf642f0231939 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 30 Dec 2009 22:06:16 +0530 Subject: [PATCH 202/637] Revert "easy install: needs a minor fix to accommodate auto-vivification" This reverts commit 6576e82e3342a869e18845df432ef4128e693131. On oddball configs, where the shell key is reused as the gitolite key by smart( people|-alecks), the ls-remote stops the program dead, preventing the "git add" and "git commit" that seed the admin repo. This makes extra work in terms of fixing it after the fact; removing it makes the install go further, and all you need to do is (1) delete the first line from ~/.ssh/authorized_keys on the server and (2) back on the client do a "git clone gitolite:gitolite-admin". OK so it needs to be removed. Explaining that was the easy part! The hard part is explaining why removing it is harmless. Look at the commit tree around that commit, and see that the commit before that (b78a720) was partially reverted in e7e6085. b78a720 removed the new_repo call from compile, forcing it to happen only on auth, which forced this workaround for seeding the admin repo. Since e7e6085 reverted that part of b78a720, giving back new_repo functions to compile, this line of code wasn't doing any good. QED and all that :) --- src/gl-easy-install | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gl-easy-install b/src/gl-easy-install index 819eb20..6077e23 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -401,7 +401,6 @@ setup_pta() { # Substitute $GL_ADMINDIR and $REPO_BASE appropriately. Note there is no # space around the "=" in the second and third lines. - git ls-remote gitolite:gitolite-admin echo "cd $REPO_BASE/gitolite-admin.git GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir GIT_WORK_TREE=$GL_ADMINDIR git diff --cached --quiet || GIT_WORK_TREE=$GL_ADMINDIR git commit -am start From 5ad2056a9c1e25f7da35ce69ab4bed7c47dbf56a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 8 Jan 2010 06:20:19 +0530 Subject: [PATCH 203/637] typo fix in doc/4; thanks Teemu! --- doc/4-wildcard-repositories.mkd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/4-wildcard-repositories.mkd b/doc/4-wildcard-repositories.mkd index 1feac40..2324a02 100644 --- a/doc/4-wildcard-repositories.mkd +++ b/doc/4-wildcard-repositories.mkd @@ -143,7 +143,7 @@ Create a small text file that contains the permissions you desire: RW u6 (hit ctrl-d here) -...and use the new "getperms" command to set permissions for your repo: +...and use the new "setperms" command to set permissions for your repo: $ ssh git@server setperms assignments/u4/a12 < myperms New perms are: From 7124faa9f3ee06bb095b8eea3cea1caeab3a885f Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 7 Jan 2010 17:59:34 +0530 Subject: [PATCH 204/637] NAME-based restrictions Gitolite allows you to restrict changes by file/dir name. The syntax for this used "PATH/" as a prefix to denote such file/dir patterns. This has now been changed to "NAME/" because PATH is potentially confusing. While this is technically a backward-incompatible change, the feature itself was hitherto undocumented, and only a few people were using it, so I guess it's not that bad... Also added documentation now. --- conf/example.conf | 34 ++++++++++++++++++++++++++++++++++ doc/3-faq-tips-etc.mkd | 10 ++++++++++ src/gl-compile-conf | 10 +++++----- src/hooks/update | 10 +++++----- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index 2c3c6fb..a9b037c 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -165,6 +165,40 @@ repo git # looking for (`W` or `+`), or a minus (`-`), results in success, or failure, # respectively. A fallthrough also results in failure +# FILE/DIR NAME BASED RESTRICTIONS +# -------------------------------- + +# Here's a hopefully self-explanatory example. Assume the project has the +# following contents at the top level: a README, a "doc/" directory, and an +# "src/" directory. + +repo foo + RW+ = lead_dev # rule 1 + RW = dev1 dev2 dev3 dev4 # rule 2 + + RW NAME/ = lead_dev # rule 3 + RW NAME/doc/ = dev1 dev2 # rule 4 + RW NAME/src/ = dev1 dev2 dev3 dev4 # rule 5 + +# Notes + +# - the "NAME/" is part of the syntax; think of it as a keyword if you like + +# - file/dir NAME-based restrictions are *in addition* to normal (branch-name +# based) restrictions; they are not a *replacement* for them. This is why +# rule #2 (or something like it, maybe with a more specific branch-name) is +# needed; without it, dev1/2/3/4 cannot push any branches. + +# - if a repo has *any* NAME/ rules, then NAME-based restrictions are checked +# for *all* users. This is why rule 3 is needed, even though we don't +# actually have any NAME-based restrictions on lead_dev. Notice the pattern +# on rule 3. + +# - *each* file touched by the commits being pushed is checked against those +# rules. So, lead_dev can push changes to any files, dev1/2 can push +# changes to files in "doc/" and "src/" (but not the top level README), and +# dev3/4 can only push changes to files in "src/". + # GITWEB AND DAEMON STUFF # ----------------------- diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index a7ee05a..808cf03 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -13,6 +13,7 @@ In this document: * differences from gitosis * simpler syntax * two levels of access rights checking + * file/dir NAME based restrictions * error checking the config file * delegating parts of the config file * easier to specify gitweb "description" and gitweb/daemon access @@ -235,6 +236,15 @@ any of the refexes match, the push succeeds. If none of them match, it fails. Gitolite also allows "exclude" or "deny" rules. See later in this document for details. +#### file/dir NAME based restrictions + +In addition to branch-name based restrictions, gitolite also allows you to +restrict what files or directories can be involved in changes being pushed. +This basically uses `git diff --name-only` to obtain the list of files being +changed, treating each filename as a "ref" to be matched. + +Please see `conf/example.conf` for syntax and examples. + #### error checking the config file gitosis does not do any. I just found out that if you mis-spell `members` as diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 4c34d1d..23f26c7 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -200,9 +200,9 @@ sub parse_conf_file # if no ref is given, this PERM applies to all refs @refs = qw(refs/.*) unless @refs; - # fully qualify refs that dont start with "refs/" or "PATH/"; + # fully qualify refs that dont start with "refs/" or "NAME/"; # prefix them with "refs/heads/" - @refs = map { m(^(refs|PATH)/) or s(^)(refs/heads/); $_ } @refs; + @refs = map { m(^(refs|NAME)/) or s(^)(refs/heads/); $_ } @refs; # expand the user list, unless it is just "@all" @users = expand_list ( @users ) @@ -239,13 +239,13 @@ sub parse_conf_file # for 2nd level check, store each "ref, perms" pair in order for my $ref (@refs) { - # checking PATH based restrictions is expensive for + # checking NAME based restrictions is expensive for # the update hook (see the changes to src/hooks/update # in this commit for why) so we would *very* much like # to avoid doing it for the large majority of repos - # that do *not* use PATH limits. Setting a flag that + # that do *not* use NAME limits. Setting a flag that # can be checked right away will help us do that - $repos{$repo}{PATH_LIMITS} = 1 if $ref =~ /^PATH\//; + $repos{$repo}{NAME_LIMITS} = 1 if $ref =~ /^NAME\//; push @{ $repos{$repo}{$user} }, { $ref => $perms } unless $rurp_seen{$repo}{$user}{$ref}{$perms}++; } diff --git a/src/hooks/update b/src/hooks/update index 235fa40..a3c1be4 100755 --- a/src/hooks/update +++ b/src/hooks/update @@ -65,12 +65,12 @@ push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{'@all'} || [] }; # prepare the list of refs to be checked # previously, we just checked $ref -- the ref being updated, which is passed -# to us by git (see man githooks). Now we also have to treat each PATH being -# updated as a potential "ref" and check that, if PATH-based restrictions have +# to us by git (see man githooks). Now we also have to treat each NAME being +# updated as a potential "ref" and check that, if NAME-based restrictions have # been specified my @refs = ($ref); # the first ref to check is the real one -if (exists $repos{$ENV{GL_REPO}}{PATH_LIMITS}) { +if (exists $repos{$ENV{GL_REPO}}{NAME_LIMITS}) { # this is special to git -- the hash of an empty tree my $empty='4b825dc642cb6eb9a060e54bf8d69288fbee4904'; # well they're not really "trees" but $empty is indeed the empty tree so @@ -78,7 +78,7 @@ if (exists $repos{$ENV{GL_REPO}}{PATH_LIMITS}) { # diff' only wants trees my $oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha; my $newtree = $newsha eq '0' x 40 ? $empty : $newsha; - push @refs, map { chomp; s/^/PATH\//; $_; } `git diff --name-only $oldtree $newtree`; + push @refs, map { chomp; s/^/NAME\//; $_; } `git diff --name-only $oldtree $newtree`; } my $refex = ''; @@ -111,7 +111,7 @@ sub check_ref { # and in this version, we have many "refs" to check. The one we print in the # log is the *first* one (which is a *real* ref, like refs/heads/master), -# while all the rest (if they exist) are like PATH/something. So we do the +# while all the rest (if they exist) are like NAME/something. So we do the # first one separately to capture it, then run the rest (if any) my $log_refex = check_ref(shift @refs); check_ref($_) for @refs; From 08ef3555a1ca3797c9873f41ca293b23d7f6d12f Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 9 Jan 2010 19:57:44 +0530 Subject: [PATCH 205/637] deprecation warning about old style PATH/ syntax (this commit will probably get reverted after a suitable period has elapsed and no one is likely to still be using the old syntax). Forgetting to change it to NAME/ after is a security issue -- you end up permitting stuff you don't want to! This commit allows the old syntax but prints a warning --- src/gl-compile-conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 23f26c7..2787893 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -200,6 +200,8 @@ sub parse_conf_file # if no ref is given, this PERM applies to all refs @refs = qw(refs/.*) unless @refs; + # deprecation warning + map { warn "WARNING: old syntax 'PATH/' found; please use new syntax 'NAME/'\n" if s(^PATH/)(NAME/) } @refs; # fully qualify refs that dont start with "refs/" or "NAME/"; # prefix them with "refs/heads/" @refs = map { m(^(refs|NAME)/) or s(^)(refs/heads/); $_ } @refs; From 839027f7a798832a6ddc4d815672622ef085be61 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 7 Jan 2010 06:56:04 +0530 Subject: [PATCH 206/637] change delegation to NAME/ style (warning: backward compat breakage) This is a backward incompatible change. If you are using delegation and you upgrade to this version, please do the following: * change your gitolite.conf file to use the new syntax (see doc/5-delegation.mkd in this commit) * for each branch "foo" in the gitolite-admin repo, do this: # (on "master" branch) git checkout foo -- conf/fragments/foo.conf * git add all those new fragments and commit to master * delete all the branches on your clone and the server # again, for each branch foo git branch -D foo git push origin :foo --- doc/5-delegation.mkd | 34 +++++++++++++++++----------------- src/ga-post-update-hook | 23 ----------------------- 2 files changed, 17 insertions(+), 40 deletions(-) diff --git a/doc/5-delegation.mkd b/doc/5-delegation.mkd index af73b81..6c62b24 100644 --- a/doc/5-delegation.mkd +++ b/doc/5-delegation.mkd @@ -61,35 +61,35 @@ You do this by adding branches to the `gitolite-admin` repo: # the admin repo access was probably like this to start with: repo gitolite-admin - RW+ = sitaram + RW+ = sitaram # now add these lines to the config for the admin repo - RW webbrowser_repos = alice - RW webserver_repos = bob - RW malware_repos = mallory + RW NAME/conf/fragments/webbrowser_repos = alice + RW NAME/conf/fragments/webserver_repos = bob + RW NAME/conf/fragments/malware_repos = mallory + +This uses gitolite's ability to restrict pushes by file/dir name being changed +-- the syntax you see above ensures that, while "sitaram" does not have any +NAME based restrictions, the other 3 users do. See `conf/example.conf` for +syntax and notes. As you can see, **for each repo group** you want to delegate authority over, -there's a **branch with the same name** in the `gitolite-admin` repo. If you -have write access to that branch, you are allowed to define rules for repos in -that repo group. +there's a rule for a **corresponding file** in `conf/fragments` in the +`gitolite-admin` repo. If you have write access to that file, you are allowed +to define rules for repos in that repo group. -In other words, we use gitolite's per-branch permissions to "enforce" the -separation between the delegated configs! +In other words, we use gitolite's file/dir NAME-based permissions to "enforce" +the separation between the delegated configs! Here's how to use this in practice: - * Alice clones the `gitolite-admin` repo, creates (if not already created) and - checks out a new branch called `webbrowser_repos`, and adds a file called - `conf/fragments/webbrowser_repos.conf` in that branch - - * (the rest of the contents of that branch do not matter; she can keep - all the other files or delete all of them -- it doesn't make any - difference. Only that one specific file is used). + * Alice clones the `gitolite-admin` repo, and adds a file called + `conf/fragments/webbrowser_repos.conf` * she writes in this file any access control rules for the "firefox" and "lynx" repos. She should not write access rules for any other project -- they will be ignored - * Alice then commits and pushes this branch to the `gitolite-admin` repo + * Alice then commits and pushes to the `gitolite-admin` repo Naturally, a successful push invokes the post-update hook that the admin repo has, which eventually runs the compile script. The **net effect** is as if diff --git a/src/ga-post-update-hook b/src/ga-post-update-hook index 91d2bfb..b84dfa8 100755 --- a/src/ga-post-update-hook +++ b/src/ga-post-update-hook @@ -4,28 +4,5 @@ # (the GL_ADMINDIR env var would have been set by gl-auth-command) GIT_WORK_TREE=$GL_ADMINDIR git checkout -f master -# remove all fragments. otherwise, you get spurious error messages when you -# take away someone's delegation in the main config but the fragment is still -# hanging around. The ones that are valid will get re-created anyway -rm -rf $GL_ADMINDIR/conf/fragments -# collect all the delegated fragments -mkdir $GL_ADMINDIR/conf/fragments -for br in `git for-each-ref --format='%(refname:short)'` -do - # skip master (duh!) - [ "$br" = "master" ] && continue - - # all other branches *should* contain a file called .conf - # inside conf/fragments; if so copy it - if git show $br:conf/fragments/$br.conf > /dev/null 2>&1 - then - git show $br:conf/fragments/$br.conf > $GL_ADMINDIR/conf/fragments/$br.conf - echo "(extracted $br conf; `wc -l < $GL_ADMINDIR/conf/fragments/$br.conf` lines)" - else - echo " ***** ERROR *****" - echo " branch $br does not contain conf/fragments/$br.conf" - fi -done - cd $GL_ADMINDIR $GL_BINDIR/gl-compile-conf From 6c38e30e9ae2ec206913e6e0eca5dc29abee7a27 Mon Sep 17 00:00:00 2001 From: Teemu Matilainen Date: Tue, 5 Jan 2010 20:34:40 +0200 Subject: [PATCH 207/637] compile: support "include" definition Support config file including using: include "filename" If filename is not an absolute path, it is looked from the $GL_ADMINDIR/conf/ directory. For security reasons include is not allowed for fragments. Signed-off-by: Teemu Matilainen --- src/gl-compile-conf | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 2787893..b654e69 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -264,6 +264,16 @@ sub parse_conf_file $repo_config{$repo}{$key} = $value; } } + # include + elsif (/^include "(.+)"/) + { + my $file = $1; + $file = "$GL_ADMINDIR/conf/$file" unless $file =~ /^\//; + die "$WARN $fragment attempting to include configuration\n" if $fragment ne 'master'; + die "$ABRT included file not found: '$file'\n" unless -f $file; + + parse_conf_file($file, $fragment); + } # very simple syntax for the gitweb description of repo; one of: # reponame = "some description string" # reponame "owner name" = "some description string" From 15475f666c07e66d91fd00added2a50544d9221b Mon Sep 17 00:00:00 2001 From: Teemu Matilainen Date: Fri, 8 Jan 2010 14:05:11 +0200 Subject: [PATCH 208/637] Fix exit codes for allowed ssh commands gitolite specific ssh commands ("getperms", "setperms", "info" etc.) should exit with non-error code in case of success. Also "get/setperms" should print to STDOUT instead of STDERR. This change is specially needed for the gitolite-tools (http://github.com/tmatilai/gitolite-tools) to work. Signed-off-by: Teemu Matilainen --- src/gitolite.pm | 6 +++--- src/gl-auth-command | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 4cfa8e1..74e1bbc 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -137,11 +137,11 @@ sub get_set_perms wrap_chdir("$repo_base_abs"); wrap_chdir("$repo.git"); if ($verb eq 'getperms') { - print STDERR `cat gl-perms 2>/dev/null`; + system("cat", "gl-perms") if -f "gl-perms"; } else { system("cat > gl-perms"); - print STDERR "New perms are:\n"; - print STDERR `cat gl-perms`; + print "New perms are:\n"; + system("cat", "gl-perms"); } } diff --git a/src/gl-auth-command b/src/gl-auth-command index 6be5bde..1d6a33a 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -105,14 +105,14 @@ if ($cmd =~ $CUSTOM_COMMANDS) { } else { die "$cmd doesn't make sense to me\n"; } - exit 1; + exit 0; } # people allowed to get a shell can get basic access info by asking nicely if ($cmd eq 'info') { &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user); print "you also have shell access\n\r" if $shell_allowed; - exit 1; + exit 0; } # ---------------------------------------------------------------------------- From a9824464e5729f1c8b031b491c384a49a199d4ff Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 13 Jan 2010 14:59:45 +0530 Subject: [PATCH 209/637] update hook: anchor refex with ^ when matching refs Currently, a line like RW foo = user1 allows user1 to push any ref that contains the string refs/heads/foo. This includes refs like refs/heads/foo refs/heads/foobar refs/heads/foo/bar which is fine; that is what is intended. (You can always use foo$ instead of foo if you want to prevent the latter two). Similarly, RW refs/foo = user1 allows refs/foo refs/foobar refs/foo/bar Now, I don't see this as a "security risk" but the fact is that this allows someone to clutter your repo with junk like refs/bar/refs/heads/foo refs/heads/bar/refs/heads/foo (or, with the second config line example, refs/bar/refs/foo refs/heads/bar/refs/foo ) My personal advice is if you find someone doing that intentionally, you should probably take him out and shoot him [*], but since now *two* people have complained about this, here goes... ---- [*] you don't have to take him out if you don't want to --- src/hooks/update | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/update b/src/hooks/update index a3c1be4..c1ff18f 100755 --- a/src/hooks/update +++ b/src/hooks/update @@ -100,7 +100,7 @@ sub check_ref { for my $ar (@allowed_refs) { $refex = (keys %$ar)[0]; # refex? sure -- a regex to match a ref against :) - next unless $ref =~ /$refex/; + next unless $ref =~ /^$refex/; die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-'; # as far as *this* ref is concerned we're ok From ecfd20e793df7f63682b412603161d7e1b5ed7e3 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 14 Jan 2010 15:14:40 +0530 Subject: [PATCH 210/637] @SHELL is now $SHELL_USERS in the rc file (warning: backward compat breakage) Stop conflating the privilege to push changes to the admin repo with the privilege to get a shell on the server. Please read doc/6 carefully before upgrading to this version. Also please ensure that the gitolite key is *not* your only means to get a command line on the server --- conf/example.conf | 16 ---------------- conf/example.gitolite.rc | 13 +++++++++++++ doc/6-ssh-troubleshooting.mkd | 32 ++++++++++++++++++++++++++++---- src/gl-compile-conf | 4 ++-- src/gl-easy-install | 26 ++++++++++++++++++++------ 5 files changed, 63 insertions(+), 28 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index a9b037c..07f60ab 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -246,19 +246,3 @@ repo gitolite # This does either a plain "git config section.key value" (for the first 3 # examples above) or "git config --unset-all section.key" (for the last # example). Other forms (--add, the value_regex, etc) are not supported. - -# SHELL ACCESS -# ------------ - -# It is possible to give certain users shell access as well as allow them to -# use gitolite features for their git repo access. The idea is to eliminate -# the need for 2 keys when both shell and gitolite access are needed. - -# To give a user shell access, add the username to the special @SHELL group: - -@SHELL = sitaram - -# Do not add people to this group indiscriminately. AUDITABILITY OF ACCESS -# CONTROL CHANGES (AND OF REPO ACCESSES) WILL BE COMPROMISED IF ADMINS CAN -# FIDDLE WITH THE ACTUAL (PLAIN TEXT) LOG FILES THAT GITOLITE KEEPS, WHICH -# THEY CAN EASILY DO IF THEY HAVE A SHELL. diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index 4cda90b..d53b65f 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -93,6 +93,19 @@ $PERSONAL=""; $GIT_PATH=""; # $GIT_PATH="/opt/bin/"; +# -------------------------------------- + +# if you want to give shell access to any gitolite user(s), name them here. +# Please see doc/6-ssh-troubleshooting.mkd for details on how this works. + +# Do not add people to this list indiscriminately. AUDITABILITY OF ACCESS +# CONTROL CHANGES (AND OF REPO ACCESSES) WILL BE COMPROMISED IF ADMINS CAN +# FIDDLE WITH THE ACTUAL (PLAIN TEXT) LOG FILES THAT GITOLITE KEEPS, WHICH +# THEY CAN EASILY DO IF THEY HAVE A SHELL. + +# syntax: space separated list of gitolite usernames in *one* string variable. +# $SHELL_USERS = "alice bob"; + # -------------------------------------- # per perl rules, this should be the last line in such a file: 1; diff --git a/doc/6-ssh-troubleshooting.mkd b/doc/6-ssh-troubleshooting.mkd index 9602dd2..570e1bd 100644 --- a/doc/6-ssh-troubleshooting.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -8,10 +8,34 @@ and shell access]...". We've managed (thanks to an idea from Jesse Keating) to get around this. Now it *is* possible for a single key to allow both gitolite access *and* shell access. -This is done by placing such a user in a special `@SHELL` group in the -gitolite config file. As usual, please see `conf/example.conf` for more info -on this, since I'm using that as a central place to document anything -concerned with the conf file. +This is done by: + + * (**on the server**) listing all such users in a variable called + `$SHELL_USERS` in the `~/.gitolite.rc` file. For example: + + $SHELL_USERS = "alice bob"; + + (Note the syntax: a space separated list of users in one string variable). + + * (**on your client**) make at least a dummy change to your clone of the + gitolite-admin repo and push it. + +**IMPORTANT UPGRADE NOTE**: a previous implementation of this feature worked +by adding people to a special group (`@SHELL`) in the *config* file. This +meant that anyone with gitolite-admin repo write access could add himself to +the `@SHELL` group and push, thus obtaining shell. + +This is not a problem for most setups, but if someone wants to separate these +two privileges (the right to push the admin repo and the right to get a shell) +then it does pose a problem. Since the "rc" file can only be edited by +someone who already has shell access, we now use that instead, even though +this forces a change in the syntax. + +To migrate from the old scheme to the new one, add a new variable +`$SHELL_USERS` to `~/.gitolite.rc` on the server with the appropriate names in +it. **It is best to do this directly on the server *before* upgrading to this +version.** (After the upgrade is done and tested you can remove the `@SHELL` +lines from the gitolite config file). ---- diff --git a/src/gl-compile-conf b/src/gl-compile-conf index b654e69..dcd2535 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -52,7 +52,7 @@ $Data::Dumper::Sortkeys = 1; open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); # these are set by the "rc" file -our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH); +our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $SHELL_USERS); # and these are set by gitolite.pm our ($REPONAME_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN); @@ -458,7 +458,7 @@ for my $pubkey (glob("*")) print STDERR "WARNING: pubkey $pubkey exists but user $user not in config\n" unless $user_list{$user}; $user_list{$user} = 'has pubkey'; - if ($groups{'@SHELL'}{$user}) { + if ($SHELL_USERS and $SHELL_USERS =~ /(^|\s)$user(\s|$)/) { print $newkeys_fh "command=\"$AUTH_COMMAND -s $user\",$AUTH_OPTIONS "; } else { print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS,no-pty "; diff --git a/src/gl-easy-install b/src/gl-easy-install index 6077e23..e8a000b 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -302,12 +302,16 @@ copy_gl() { prompt " ...trying to reuse existing rc" \ "Oh hey... you already had a '.gitolite.rc' file on the server. Let's see if we can use that instead of the default one..." - sort < $tmpgli/.gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > $tmpgli/glrc.old - sort < conf/example.gitolite.rc | perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' > $tmpgli/glrc.new - if diff -u $tmpgli/glrc.old $tmpgli/glrc.new + < $tmpgli/.gitolite.rc perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' | sort > $tmpgli/glrc.old + < conf/example.gitolite.rc perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' | sort > $tmpgli/glrc.new + comm -13 $tmpgli/glrc.old $tmpgli/glrc.new > $tmpgli/glrc.comm13 + if [[ ! -s $tmpgli/glrc.comm13 ]] then [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} $tmpgli/.gitolite.rc else + echo new variables found in rc file: + cat $tmpgli/glrc.comm13 + echo # MANUAL: if you're upgrading, read the instructions below and # manually make sure your final ~/.gitolite.rc has both your existing # customisations as well as any new variables that the new version of @@ -339,6 +343,8 @@ run_install() { if ssh -p $port $user@$host cat $GL_ADMINDIR/conf/gitolite.conf &> /dev/null then upgrade=1 + ssh -p $port $user@$host cat $GL_ADMINDIR/conf/gitolite.conf 2> /dev/null | grep '@SHELL' && + prompt "" "$v_at_shell_bwi" [[ -n $admin_name ]] && echo -e "\n *** WARNING ***: looks like an upgrade... ignoring argument '$admin_name'" else [[ -z $admin_name ]] && die " *** ERROR ***: doesn't look like an upgrade, so I need a name for the admin" @@ -361,7 +367,6 @@ run_install() { # MANUAL: setup the initial config file. Edit $GL_ADMINDIR/conf/gitolite.conf # and add at least the following lines to it: -# @SHELL = sitaram # repo gitolite-admin # RW+ = sitaram @@ -369,8 +374,6 @@ initial_conf_key() { echo "#gitolite conf # please see conf/example.conf for details on syntax and features -@SHELL = $admin_name - repo gitolite-admin RW+ = $admin_name @@ -543,6 +546,17 @@ next set of command outputs coming up. They're only relevant for a manual install, not this one... " +v_at_shell_bwi=" +you are using the @SHELL feature in your gitolite config. This feature has +now changed in a backward incompatible way; see doc/6-ssh-troubleshooting.mkd +for information on migrating this to the new syntax. + +DO NOT hit enter unless you have understood that information and properly +migrated your setup, or you are sure you have shell access to the server +through some other means than the $admin_name key. + +" + v_done=" done! From d61890301fea2a920d246bafe308a173bcf50369 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 15 Jan 2010 09:27:47 +0530 Subject: [PATCH 211/637] delegation doc: minor oops I know hardly anyone is using delegation, but if you find yourself locked out from pushing because of this one little thing, do this: * on your gitolite-admin clone, add the required lines per this patch, and commit * on the server, edit ~/.gitolite/conf/gitolite.conf-compiled.pm, and delete the following line 'NAME_LIMITS' => 1 from the entry for "gitolite-admin" (if you don't know what that means delete *all* such lines) and save the file * back on your admin repo clone, do a push --- doc/5-delegation.mkd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/5-delegation.mkd b/doc/5-delegation.mkd index 6c62b24..9260046 100644 --- a/doc/5-delegation.mkd +++ b/doc/5-delegation.mkd @@ -63,6 +63,8 @@ You do this by adding branches to the `gitolite-admin` repo: repo gitolite-admin RW+ = sitaram # now add these lines to the config for the admin repo + RW = alice bob mallory + RW+ NAME/ = sitaram RW NAME/conf/fragments/webbrowser_repos = alice RW NAME/conf/fragments/webserver_repos = bob RW NAME/conf/fragments/malware_repos = mallory From e7962e5eda6bde4137e8a26b9d35df38074436c9 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 15 Jan 2010 09:27:47 +0530 Subject: [PATCH 212/637] delegation doc: minor oops I know hardly anyone is using delegation, but if you find yourself locked out from pushing because of this one little thing, do this: * on your gitolite-admin clone, add the required lines per this patch, and commit * on the server, edit ~/.gitolite/conf/gitolite.conf-compiled.pm, and delete the following line 'NAME_LIMITS' => 1 from the entry for "gitolite-admin" (if you don't know what that means delete *all* such lines) and save the file * back on your admin repo clone, do a push --- doc/5-delegation.mkd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/5-delegation.mkd b/doc/5-delegation.mkd index 6c62b24..9260046 100644 --- a/doc/5-delegation.mkd +++ b/doc/5-delegation.mkd @@ -63,6 +63,8 @@ You do this by adding branches to the `gitolite-admin` repo: repo gitolite-admin RW+ = sitaram # now add these lines to the config for the admin repo + RW = alice bob mallory + RW+ NAME/ = sitaram RW NAME/conf/fragments/webbrowser_repos = alice RW NAME/conf/fragments/webserver_repos = bob RW NAME/conf/fragments/malware_repos = mallory From 261b289609c19143573b599ae10c8951f371874d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 15 Jan 2010 10:40:07 +0530 Subject: [PATCH 213/637] mention NAME-based restrictions in README --- README.mkd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.mkd b/README.mkd index b766f7e..1df3bf8 100644 --- a/README.mkd +++ b/README.mkd @@ -70,6 +70,8 @@ detail [here][gsdiff]. * simpler, yet far more powerful, config file syntax, including specifying gitweb/daemon access. You'll need this power if you manage lots of users+repos+combinations of access + * apart from branch-name based restrictions, you can also restrict by + file/dir name changed (i.e., output of `git diff --name-only`) * config file syntax gets checked upfront, and much more thoroughly * if your requirements are still too complex, you can split up the config file and delegate authority over parts of it From 645ab77af5d3854e1d8c2cef39101c2c5c21f33e Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 15 Jan 2010 14:20:00 +0530 Subject: [PATCH 214/637] compile: disallow multiple pubkeys in one file The way pubkey files are handled by gitolite, this could be used by a repo admin to get shell access. It's always been there as an undocumented emergency mechanism for an admin who lost his shell keys or overwrote them due to not understanding ssh well enough (and it has been so used at least once). But not any more... Like the @SHELL case, this reflects a shift away from treating people with repo admin rights as eqvt to people who have shell on the server, and systematically making the former lesser privileged than the latter. While in most cases (including my $DAYJOB) these two may be the same person, I am told that's not a valid assumption for others, and there've been requests to close this potential loophole. --- src/gl-compile-conf | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index dcd2535..141f7d7 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -458,14 +458,20 @@ for my $pubkey (glob("*")) print STDERR "WARNING: pubkey $pubkey exists but user $user not in config\n" unless $user_list{$user}; $user_list{$user} = 'has pubkey'; + # apparently some pubkeys don't end in a newline... + my $pubkey_content = `cat $pubkey`; + $pubkey_content =~ s/\s*$/\n/; + # don't trust files with multiple lines (i.e., something after a newline) + if ($pubkey_content =~ /\n./) + { + print STDERR "WARNING: a pubkey file can only have one line (key); ignoring $pubkey\n"; + next; + } if ($SHELL_USERS and $SHELL_USERS =~ /(^|\s)$user(\s|$)/) { print $newkeys_fh "command=\"$AUTH_COMMAND -s $user\",$AUTH_OPTIONS "; } else { print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS,no-pty "; } - # apparently some pubkeys don't end in a newline... - my $pubkey_content = `cat $pubkey`; - $pubkey_content =~ s/\s*$/\n/; print $newkeys_fh $pubkey_content; } # lint check 3; a little more severe than the first two I guess... From c1de05a8a559827bd5f8f2b037c9e680a9c802fe Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 21 Jan 2010 08:40:26 +0530 Subject: [PATCH 215/637] doc/3: gitweb integ; trailing slash on $projectroot It's not clear whether $projectroot has or does not have a trailing slash. Current code assumes it does, but we need to cater for it not having one also. Otherwise the final reponame ends up with a leading slash, once $projectroot has been stripped from the beginning of the full repo path. --- doc/3-faq-tips-etc.mkd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index a7ee05a..572d06c 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -347,7 +347,7 @@ already done and we just use it! $export_auth_hook = sub { my $reponame = shift; # gitweb passes us the full repo path; so we strip the beginning... - $reponame =~ s/\Q$projectroot//; + $reponame =~ s/\Q$projectroot\E\/?//; # ...and the end, to get the repo name as it is specified in gitolite conf $reponame =~ s/\.git$//; From e68d76f1274150a794a8e6d843569e034556411a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 22 Jan 2010 20:05:14 +0530 Subject: [PATCH 216/637] doc/6 revamp; would appreciate reviews ;-) --- doc/3-faq-tips-etc.mkd | 2 +- doc/6-ssh-troubleshooting.mkd | 280 ++++++++++++++++++++++------------ 2 files changed, 184 insertions(+), 98 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 808cf03..9361099 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -471,7 +471,7 @@ etc. You'd just like a simple way to know what repos you have access to. Easy! Just use ssh and try to log in as if you were attempting to get a shell: - $ ssh gitolite + $ ssh gitolite info PTY allocation request failed on channel 0 hello sitaram, the gitolite version here is v0.6-17-g94ed189 you have the following permissions: diff --git a/doc/6-ssh-troubleshooting.mkd b/doc/6-ssh-troubleshooting.mkd index 570e1bd..b09c09f 100644 --- a/doc/6-ssh-troubleshooting.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -1,116 +1,175 @@ # ssh troubleshooting -Update 2009-12-23: most of this document is now of historical interest and -will be totally revamped when I have time. For now, just note this amendment. - -The document below says "we can't use the same key for both [gitolite access -and shell access]...". We've managed (thanks to an idea from Jesse Keating) -to get around this. Now it *is* possible for a single key to allow both -gitolite access *and* shell access. - -This is done by: - - * (**on the server**) listing all such users in a variable called - `$SHELL_USERS` in the `~/.gitolite.rc` file. For example: - - $SHELL_USERS = "alice bob"; - - (Note the syntax: a space separated list of users in one string variable). - - * (**on your client**) make at least a dummy change to your clone of the - gitolite-admin repo and push it. - -**IMPORTANT UPGRADE NOTE**: a previous implementation of this feature worked -by adding people to a special group (`@SHELL`) in the *config* file. This -meant that anyone with gitolite-admin repo write access could add himself to -the `@SHELL` group and push, thus obtaining shell. - -This is not a problem for most setups, but if someone wants to separate these -two privileges (the right to push the admin repo and the right to get a shell) -then it does pose a problem. Since the "rc" file can only be edited by -someone who already has shell access, we now use that instead, even though -this forces a change in the syntax. - -To migrate from the old scheme to the new one, add a new variable -`$SHELL_USERS` to `~/.gitolite.rc` on the server with the appropriate names in -it. **It is best to do this directly on the server *before* upgrading to this -version.** (After the upgrade is done and tested you can remove the `@SHELL` -lines from the gitolite config file). - ----- - -Ssh has always been the biggest troublespot in all this. While gitolite makes -it as easy as possible, you might still run into trouble sometimes. - In this document: - * ssh sanity checks - * explanation + * basic ssh troubleshooting + * passphrases versus passwords + * ssh-agent problems + * basic ssh troubleshooting for the main admin + * basic ssh troubleshooting for a normal user + * details * files on the server * files on client * why two keys on client * more complex ssh setups * two gitolite servers to manage? - * further reading + * giving shell access to gitolite users ---- -> But before we get to all that, let's clarify that all this is applicable -> **only** to the gitolite **admin**. He's the only one who needs both a -> shell and gitolite access, so he has **two** pubkeys in play. +This document should help you troubleshoot ssh-related problems in accessing +gitolite *after* the install has completed successfully. -> Normal users have only one pubkey, since they are only allowed to access -> gitolite itself. They do not need to worry about any of this stuff, and -> their repo urls are very simple, like: `git@my.git.server:reponame.git`. +In addition, I **strongly** recommend reading [this document][glb] -- it's a +very detailed look at how gitolite uses ssh's features on the server side. +Most people don't know ssh as well as they *think* they do; even if you dont +have any problems right now, it's worth skimming over. ----- +Please also note that ssh problems don't always look like ssh problems. One +common example: when the remote says the repo you're trying to access "does +not appear to be a git repository", and yet you are sure it exists, you +haven't mis-spelled it, etc. -### ssh sanity checks +### basic ssh troubleshooting -There are two quick sanity checks you can run: +[glb]: http://sitaramc.github.com/0-installing/9-gitolite-basics.html#IMPORTANT_overview_of_ssh - * running `ssh gitolite` should get you a list of repos you have rights to - access, as described [here][myrights] +I assume the gitolite server is called "server" and the user hosting all the +gitolite repos is "git". I will also be using "sitaram" as the *gitolite +username* of the admin. + +Unless specifically mentioned, all these commands are run on the user's or +admin's workstation, not on the server. + +#### passphrases versus passwords + +When you create an ssh keypair, you have the option of protecting it with a +passphrase. When you subsequently use that keypair to access a remote host, +your *local* ssh client needs to unlock the corresponding private key, and ssh +will probably ask for the passphrase you set when you created the keypair. + +Do not confuse or mistake this prompt (`Enter passphrase for key +'/home/sitaram/.ssh/id_rsa':`) for a password prompt from the remote server! + +You have two choices to avoid this prompt every time you try to access the +remote. The first is to create keypairs *without* a passphrase (just hit +enter when prompted for one). **Be sure to add a passphrase later, once +everything is working, using `ssh-keygen -p`**. + +The second is to use `ssh-agent` (or `keychain`, which in turn uses +`ssh-agent`) or something like that to manage your keys. Other than the next +section, further discussion of this is out of scope of this document. + +#### ssh-agent problems + +1. Run `ssh-add -l`. If this responds with either "The agent has no + identities." or "Could not open a connection to your authentication + agent.", skip this section. + +2. However, if it lists some keys, like this: + + 2048 fc:c1:48:1e:06:31:97:a4:8b:fc:37:b2:76:14:c7:53 /home/sitaram/.ssh/id_rsa (RSA) + 2048 d2:e0:7f:fa:1a:89:22:41:bb:06:d9:ff:a7:27:36:5c /home/sitaram/.ssh/sitaram (RSA) + + then run `ls ~/.ssh` and make sure that all the keypairs you have there + are represented in the `ssh-add -l` output. + +3. If you find any keypairs in `~/.ssh` that are not represented in the + `ssh-add -l` output, add them. For instance, if `ssh-add -l` showed me + only the `id_rsa` key, but I also had a `sitaram` (and `sitaram.pub`) + keypair, I'd run `ssh-add ~/.ssh/sitaram` to add it. + +This is because ssh-agent has a quirk: if `ssh-add -l` shows *any* keys at +all, ssh will only use those keys. Even if you explicitly specify an unlisted +key using `ssh -i` or an `identityfile` directive in the config file, it won't +use it. + +#### basic ssh troubleshooting for the main admin + +You're the "main admin" if you're trying to access gitolite from the same +workstation and user account where you ran the "easy install" command. You +should have two keypairs in your `~/.ssh` directory. The pair called `id_rsa` +(and `id_rsa.pub`) was probably the first one you created, and you used this +to get passwordless (pubkey based) access to the server (which was a +pre-requisite for running the easy install command). + +The second keypair has the same name as the last argument in the easy install +command you ran (in my case, `sitaram` and `sitaram.pub`). It was probably +created by the easy install script, and is the key used for gitolite access. + +In addition, you should have a "gitolite" paragraph in your `~/.ssh/config`, +looking something like this: + + host gitolite + user git + hostname server + identityfile ~/.ssh/sitaram + +If any of these are not true, you did something funky in your install; email +me or hop onto #git and hope for the best ;-) + +Otherwise, run these checks: + +1. `ssh git@server` should get you a command line. + + If it asks you for a password, then your `id_rsa` keypair changed after + you ran the easy install, or someone fiddled with the + `~/.ssh/authorized_keys` file on the server. + + If it prints [gitolite version and access info][myrights], you managed to + overwrite the `id_rsa` keypair with the `sitaram` keypair, or something + equally weird. + +2. `ssh gitolite info` should print some [gitolite version and access + info][myrights]. If you get the output of the GNU info command instead, + you probably reused your `id_rsa` keypair as your `sitaram` keypair, or + overwrote the `sitaram` keypair with the `id_rsa` keypair. + +There are many ways to fix this, depending on where and what the damage is. +The most generic way (and therefore time-taking) is to re-install gitolite +from scratch: + + * make a backup of your gitolite-admin repo clone somewhere (basically your + "keydir/*.pub" and your "conf/gitolite.conf"). If necessary get these + files from the server's `~/.gitolite` directory. + * log on to the server somehow (using some other account, using a password, + su-ing in, etc) and delete `~/.ssh/authorized_keys`. Rename or move aside + `~/.gitolite` so that also looks like it is missing. + * back on your workstation, make sure you have 2 keypairs (`id_rsa` and + `sitaram`, along with corresponding `.pub` files). Create them if needed. + Also make sure they are *different* and not a copy of each other :-) + * install gitolite normally: + * run `ssh-copy-id -i ~/.ssh/id_rsa git@server` to get passwordless + access to the server. (Mac users may have to do this step manually) + * make sure `ssh git@server pwd` prints the `$HOME` of `git@server` + **without** asking for a password. Do not proceed till this works. + * run easy install again, (in my case: `cd gitolite-source; + src/gl-easy-install -q git server sitaram`) + * go to your gitolite-admin repo clone, and copy `conf/gitolite.conf` and + `keydir/*.pub` from your backup to this directory + * copy (be sure to overwrite!) `~/.ssh/sitaram.pub` also to keydir + * now `git add keydir; git commit; git push -f` + +That's a long sequence but it should work. + +#### basic ssh troubleshooting for a normal user + +For a normal user, life is much simpler. They should have only one pubkey, +which was previously sent to the gitolite admin to add into the admin repo's +`keydir` as "user.pub", and then "user" given permissions to some repo. + +`ssh git@server info` should get you [gitolite version and access +info][myrights]. If it asks you for a password, your pubkey was not sent to +the server properly. Check with your admin. [myrights]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#myrights - * conversely, `ssh git@server` should get you a command line +If it gets you the GNU info command output, you have shell access. This means +you had command line access to the server *before* you were added as a +gitolite user. If you send that same key to your gitolite admin to include in +the admin repo, it won't work. For reasons why, see below. -If one or both of these does not work as expected, do this: - - * first, check that your `~/.ssh` has two public keys, like below: - - $ ls -al ~/.ssh/*.pub - -rw-r--r-- 1 sitaram sitaram 409 2008-04-21 17:42 /home/sitaram/.ssh/id_rsa.pub - -rw-r--r-- 1 sitaram sitaram 409 2009-10-15 16:25 /home/sitaram/.ssh/sitaram.pub - - If it doesn't you have either lost your keys or you're on the wrong - machine. As long as you have password access to the server you can alweys - recover; just pretend you're installing from scratch and start over. - - * next, try running `ssh-add -l`. On my desktop the output looks like this: - - 2048 63:ea:ab:10:d2:4f:88:f4:85:cb:d3:7d:3a:83:37:9a /home/sitaram/.ssh/id_rsa (RSA) - 2048 d7:23:89:12:5f:22:4f:ad:54:7d:7e:f8:f5:2a:e9:13 /home/sitaram/.ssh/sitaram (RSA) - - If you get only one line (typically the top one), you should ssh-add the - other one, using (in my case) `ssh-add ~/.ssh/sitaram`. - - If you get no output, add both of them and check `ssh-add -l` again. - - If this error keeps happening please consider installing [keychain][kch] - or something similar, or add these commands to your bash startup scripts. - -[kch]: http://www.gentoo.org/proj/en/keychain/ - - * Finally, make sure your `~/.ssh/config` has the required `host gitolite` - para (see below for more on this). - -Once these sanity checks have passed, things should be fine. However, if you -still have problems, make sure that the "origin" URL in any clones looks like -`gitolite:reponame.git`, not `git@server:reponame.git`. - -### explanation +### details Here's how it all hangs together. @@ -301,10 +360,37 @@ instance if you have *two* gitolite servers you are administering)? * now access one server's repos as `gitolite:reponame.git` and the other server's repos as `gitolite2:reponame.git`. -### further reading +### giving shell access to gitolite users -While this focused mostly on the client side ssh, you may also want to read -[this][glb] for a much more detailed explanation of the ssh magic on the -server side. +We've managed (thanks to an idea from Jesse Keating) to make it possible for a +single key to allow both gitolite access *and* shell access. + +This is done by: + + * (**on the server**) listing all such users in a variable called + `$SHELL_USERS` in the `~/.gitolite.rc` file. For example: + + $SHELL_USERS = "alice bob"; + + (Note the syntax: a space separated list of users in one string variable). + + * (**on your client**) make at least a dummy change to your clone of the + gitolite-admin repo and push it. + +**IMPORTANT UPGRADE NOTE**: a previous implementation of this feature worked +by adding people to a special group (`@SHELL`) in the *config* file. This +meant that anyone with gitolite-admin repo write access could add himself to +the `@SHELL` group and push, thus obtaining shell. + +This is not a problem for most setups, but if someone wants to separate these +two privileges (the right to push the admin repo and the right to get a shell) +then it does pose a problem. Since the "rc" file can only be edited by +someone who already has shell access, we now use that instead, even though +this forces a change in the syntax. + +To migrate from the old scheme to the new one, add a new variable +`$SHELL_USERS` to `~/.gitolite.rc` on the server with the appropriate names in +it. **It is best to do this directly on the server *before* upgrading to this +version.** (After the upgrade is done and tested you can remove the `@SHELL` +lines from the gitolite config file). -[glb]: http://sitaramc.github.com/0-installing/9-gitolite-basics.html#IMPORTANT_overview_of_ssh From c8d4aef460e84bfd90e12a9c3f741065f030d2ca Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 23 Jan 2010 14:57:22 +0530 Subject: [PATCH 217/637] compile: allow "#" in *simple* strings like: config notify.ircChannel = "#foo" (thanks, jhelwig) --- src/gl-compile-conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 141f7d7..e88819a 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -162,13 +162,13 @@ sub parse_conf_file my @repos; while (<$conf_fh>) { + # kill comments, but take care of "#" inside *simple* strings + s/^((".*?"|[^#"])*)#.*/$1/; # normalise whitespace; keeps later regexes very simple s/=/ = /; s/\s+/ /g; s/^ //; s/ $//; - # kill comments - s/\s*#.*//; # and blank lines next unless /\S/; From 11e8ab048af553024b32110c41d83dcb235396dd Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 25 Jan 2010 09:49:39 +0530 Subject: [PATCH 218/637] doc/6 revamp: minor addition --- doc/6-ssh-troubleshooting.mkd | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/doc/6-ssh-troubleshooting.mkd b/doc/6-ssh-troubleshooting.mkd index b09c09f..9548792 100644 --- a/doc/6-ssh-troubleshooting.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -28,7 +28,16 @@ have any problems right now, it's worth skimming over. Please also note that ssh problems don't always look like ssh problems. One common example: when the remote says the repo you're trying to access "does not appear to be a git repository", and yet you are sure it exists, you -haven't mis-spelled it, etc. +haven't mis-spelled it, etc. Another example is being able to access +repositories using the full unix path (typically like +`git@server:repositories/reponame.git`, assuming default `$REPO_BASE` setting, +instead of specifying only the part below `$REPO_BASE`, i.e., +`git@server:reponame.git`). + +[Both these errors indicate that you managed to bypass gitolite completely and +are using your shell access -- instead of running via +`/some/path/gl-auth-command ` it is just going to bash and +working from there!] ### basic ssh troubleshooting From c3ec349721a4e07ecf2d80ef3e49f2e8198a4466 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 25 Jan 2010 12:29:01 +0530 Subject: [PATCH 219/637] sshkeys-lint: new program run without arguments for usage --- doc/6-ssh-troubleshooting.mkd | 4 ++ src/sshkeys-lint | 100 ++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100755 src/sshkeys-lint diff --git a/doc/6-ssh-troubleshooting.mkd b/doc/6-ssh-troubleshooting.mkd index 9548792..8189af8 100644 --- a/doc/6-ssh-troubleshooting.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -25,6 +25,10 @@ very detailed look at how gitolite uses ssh's features on the server side. Most people don't know ssh as well as they *think* they do; even if you dont have any problems right now, it's worth skimming over. +In addition to both these documents, there's now a program called +`sshkeys-lint` that you can run on your client. Run it without arguments to +get help on how to run it and what inputs it needs. + Please also note that ssh problems don't always look like ssh problems. One common example: when the remote says the repo you're trying to access "does not appear to be a git repository", and yet you are sure it exists, you diff --git a/src/sshkeys-lint b/src/sshkeys-lint new file mode 100755 index 0000000..acb8d72 --- /dev/null +++ b/src/sshkeys-lint @@ -0,0 +1,100 @@ +#!/usr/bin/perl -w + +use strict; +our (%users, %linenos); + +&usage unless $ARGV[0] and -f $ARGV[0]; +my @authlines = &filelines($ARGV[0]); +my $lineno = 0; +for (@authlines) +{ + $lineno++; + if (/^# gitolite start/ .. /^# gitolite end/) { + warn "line $lineno: non-gitolite key found in gitolite section" if /ssh-rsa|ssh-dss/ and not /command=.*gl-auth-command/; + } else { + warn "line $lineno: gitolite key found outside gitolite section" if /command=.*gl-auth-command/; + } + next if /\# gitolite (start|end)/; + die "line $lineno: unrecognised line\n" unless /^(?:command=".*gl-auth-command (\S+?)"\S+ )?(?:ssh-rsa|ssh-dss) (\S+)/; + my ($user, $key) = ($1 || '', $2); + if ($linenos{$key}) { + warn "authkeys file line $lineno is repeat of line $linenos{$key}, will be ignored by server sshd\n"; + next; + } + $linenos{$key} = $lineno; + $users{$key} = ($user ? "maps to gitolite user $user" : "gets you a command line"); +} + +print "\n"; + +# all *.pub in current dir should be exactly one line, starting with ssh-rsa +# or ssh-dss + +my @pubkeys = glob("*.pub"); +die "no *.pub files here\n" unless @pubkeys; +for my $pub (@pubkeys) { + my @lines = &filelines($pub); + die "$pub has more than one line\n" if @lines > 1; + die "$pub does not start with ssh-rsa or ssh-dss\n" unless $lines[0] =~ /^(?:ssh-rsa|ssh-dss) (\S+)/; + my $key = $1; + if ($users{$key}) { + print "$pub $users{$key}\n"; + } else { + print "$pub has NO ACCESS to the server\n"; + } +} + +print <; +} + +sub usage +{ + print STDERR < Date: Mon, 25 Jan 2010 14:36:02 +0530 Subject: [PATCH 220/637] (rats! msysgit doesnt have 'comm'...) --- src/gl-easy-install | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/gl-easy-install b/src/gl-easy-install index e8a000b..9887d70 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -304,7 +304,10 @@ copy_gl() { Let's see if we can use that instead of the default one..." < $tmpgli/.gitolite.rc perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' | sort > $tmpgli/glrc.old < conf/example.gitolite.rc perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' | sort > $tmpgli/glrc.new - comm -13 $tmpgli/glrc.old $tmpgli/glrc.new > $tmpgli/glrc.comm13 + # msysgit doesn't have "comm". diff is not ideal for our purposes + # because we only care about differences in one direction, but we'll + # have to make do... + diff -u $tmpgli/glrc.old $tmpgli/glrc.new | grep '^+' > $tmpgli/glrc.comm13 if [[ ! -s $tmpgli/glrc.comm13 ]] then [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} $tmpgli/.gitolite.rc @@ -526,7 +529,7 @@ v_upgrade_glrc=" looks like you're upgrading, and there are some new rc variables that this version is expecting that your old rc file doesn't have. -I'm going to run your editor with two filenames. The first is the example +I'm going to run your \\\$EDITOR with two filenames. The first is the example file from this gitolite version. It will have a block (code and comments) for each of the variables shown above with a '+' sign. @@ -536,7 +539,7 @@ it. This is necessary; please dont skip this! -[It's upto you to figure out how your editor handles 2 filename arguments, +[It's upto you to figure out how your \\\$EDITOR handles 2 filename arguments, switch between them, copy lines, etc ;-)] " From 7afaafc54aa84ba614d07f19cb5dc284c296c686 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 27 Jan 2010 16:48:56 +0530 Subject: [PATCH 221/637] document the "include" mechanism --- conf/example.conf | 13 +++++++++++++ doc/3-faq-tips-etc.mkd | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/conf/example.conf b/conf/example.conf index 07f60ab..9d73aca 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -246,3 +246,16 @@ repo gitolite # This does either a plain "git config section.key value" (for the first 3 # examples above) or "git config --unset-all section.key" (for the last # example). Other forms (--add, the value_regex, etc) are not supported. + +# INCLUDE SOME OTHER FILE +# ----------------------- + + include "foo.conf" + +# this includes the contents of $GL_ADMINDIR/conf/foo.conf here + +# Notes: +# - the include statement is not allowed inside delegated fragments for +# security reasons. +# - you can also use an absolute path if you like, although in the interests +# of cloning the admin-repo sanely you should avoid doing this! diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 9361099..8b82086 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -15,6 +15,7 @@ In this document: * two levels of access rights checking * file/dir NAME based restrictions * error checking the config file + * including config lines from other files * delegating parts of the config file * easier to specify gitweb "description" and gitweb/daemon access * easier to link gitweb authorisation with gitolite @@ -254,6 +255,10 @@ was denied. Gitolite "compiles" the config file first and keyword typos *are* caught so you know right away. +#### including config lines from other files + +See the entry under "INCLUDE SOME OTHER FILE" in `conf/example.conf`. + #### delegating parts of the config file You can now split up the config file and delegate the authority to specify From 98d73965b6c42cd7670fcf799e5e9b51177b1a9b Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 27 Jan 2010 19:34:37 +0530 Subject: [PATCH 222/637] easy install: two rc file update bugs fixed The "msysgit doesnt have 'comm'" commit (from 2 days ago), had 2 bugs: - (smaller) the "+++" which was part of the diff header was triggering a spurious rc file "new variables" warning, but there were no actual variables to update - (bigger) worse, the grep command, when there were no matches, coupled with the "set -e" to kill the program right there (ouch!) --- src/gl-easy-install | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gl-easy-install b/src/gl-easy-install index 9887d70..35c31e2 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -307,7 +307,9 @@ Let's see if we can use that instead of the default one..." # msysgit doesn't have "comm". diff is not ideal for our purposes # because we only care about differences in one direction, but we'll # have to make do... - diff -u $tmpgli/glrc.old $tmpgli/glrc.new | grep '^+' > $tmpgli/glrc.comm13 + set +e + diff -u $tmpgli/glrc.old $tmpgli/glrc.new | grep '^+.*\$' > $tmpgli/glrc.comm13 + set -e if [[ ! -s $tmpgli/glrc.comm13 ]] then [[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} $tmpgli/.gitolite.rc From 9c171d166dced181787f22d3abbf1cce8075bb62 Mon Sep 17 00:00:00 2001 From: Teemu Matilainen Date: Thu, 28 Jan 2010 22:18:12 +0200 Subject: [PATCH 223/637] "expand" should print to SDTOUT instead of STDERR Other ssh commands where fixed in 15475f666c07e66d91fd00added2a50544d9221b, but "expand" was somehow missed. Signed-off-by: Teemu Matilainen --- src/gitolite.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 74e1bbc..950ad5e 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -245,7 +245,7 @@ sub expand_wild # you need a minimum of "R" access to the regex we're talking about next unless $repos{$repo}{R}{'@all'} or $repos{$repo}{R}{$user}; - print STDERR "($creater)\t$actual_repo\n"; + print "($creater)\t$actual_repo\n"; } } From 4142be4e59f11c7ba6be612b86bba1e5cd3eb439 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 29 Jan 2010 14:39:03 +0530 Subject: [PATCH 224/637] auth: reporting changes for wildcard-created repos - see *all* wildcard repos you have access to (this uses line-anchored regexes as described in doc/4). Examples: ssh git@server expand '.*' ssh git@server expand 'assignment.*' - show perms like the info command does Please see comments against 02cee1d for more details and caveats. --- src/gitolite.pm | 23 ++++++++++++++++++++--- src/gl-auth-command | 3 ++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 950ad5e..cccd310 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -227,6 +227,10 @@ sub expand_wild # access report instead of having to manually change CREATER to his name $repo =~ s/\bCREAT[EO]R\b/$user/g; + # get the list of repo patterns + &parse_acl($GL_CONF_COMPILED, "", "NOBODY", "NOBODY", "NOBODY"); + my @repopatts = grep { $_ !~ $REPONAME_PATT } sort keys %repos; + # display matching repos (from *all* the repos in the system) that $user # has at least "R" access to @@ -237,15 +241,28 @@ sub expand_wild $actual_repo =~ s/\.git$//; # it has to match the pattern being expanded next unless $actual_repo =~ /^$repo$/; + # it also has to match one of the repo patterns in %repos (which we + # already snarfed earlier) + my @patts = grep { $actual_repo =~ /^$_$/ } @repopatts; + # should be exactly one match + # (see reasoning in the "other issues" section of doc/4) + if (@patts != 1) { + # though if it's more than one we print an additional message + print "ignoring $actual_repo; has multiple matches\n(@patts)\n" if @patts > 1; + next; + } # find the creater and subsitute in repos my ($creater, $read, $write) = &repo_rights($repo_base_abs, $actual_repo, $user); # get access list with this &parse_acl($GL_CONF_COMPILED, "", $creater, $read || "NOBODY", $write || "NOBODY"); - # you need a minimum of "R" access to the regex we're talking about - next unless $repos{$repo}{R}{'@all'} or $repos{$repo}{R}{$user}; - print "($creater)\t$actual_repo\n"; + my $perm = ""; + $perm .= ($repos{$patts[0]}{C}{'@all'} or $repos{$patts[0]}{C}{$user}) ? " C" : " "; + $perm .= ($repos{$patts[0]}{R}{'@all'} or $repos{$patts[0]}{R}{$user}) ? " R" : " "; + $perm .= ($repos{$patts[0]}{W}{'@all'} or $repos{$patts[0]}{W}{$user}) ? " W" : " "; + next if $perm eq " "; + print "$perm\t($creater)\t$actual_repo\n"; } } diff --git a/src/gl-auth-command b/src/gl-auth-command index a78c157..f6b2453 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -26,7 +26,7 @@ use warnings; # these are set by the "rc" file our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR); # and these are set by gitolite.pm -our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT); +our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT, $REPOPATT_PATT); our %repos; # the common setup module is in the same directory as this running program is @@ -101,6 +101,7 @@ if ($cmd =~ $CUSTOM_COMMANDS) { } elsif ($verb eq 'expand') { # with a wildcard, you can "expand" it to see what repos actually match + die "$repo has invalid characters" unless "x$repo" =~ $REPOPATT_PATT; expand_wild($GL_CONF_COMPILED, $repo_base_abs, $repo, $user); } else { die "$cmd doesn't make sense to me\n"; From bc0a478e64dcbd7944ebbf894507665ba794ad17 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 30 Jan 2010 04:48:51 +0530 Subject: [PATCH 225/637] auth: minor fix to reporting on wildcard repos Mpenz asked what would happen if the config looked like repo foo/abc R sitaram repo foo/.* RW sitaram If you asked for an expand of '.*', it would pick up permissions from the second set (i.e., "RW") and print them against "foo/abc". This is misleading, since those are not the permissions that will actually be *used*. Gitolite always uses the more specific form if it is given, which means your actual permissions are just "R". This patch is to prevent that misleading reporting in this corner case. --- src/gitolite.pm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gitolite.pm b/src/gitolite.pm index cccd310..ba72f93 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -230,6 +230,7 @@ sub expand_wild # get the list of repo patterns &parse_acl($GL_CONF_COMPILED, "", "NOBODY", "NOBODY", "NOBODY"); my @repopatts = grep { $_ !~ $REPONAME_PATT } sort keys %repos; + my %reponames = map { $_ => 1 } grep { $_ =~ $REPONAME_PATT } sort keys %repos; # display matching repos (from *all* the repos in the system) that $user # has at least "R" access to @@ -239,6 +240,11 @@ sub expand_wild chomp ($actual_repo); $actual_repo =~ s/^\.\///; $actual_repo =~ s/\.git$//; + # actual_repo should not be present "as is" in the config, because if + # it does, those permissions will override anything inherited from a + # wildcard that also happens to match, and it would be misleading to + # show that here + next if $reponames{$actual_repo}; # it has to match the pattern being expanded next unless $actual_repo =~ /^$repo$/; # it also has to match one of the repo patterns in %repos (which we From b4a65ab73ce7abeaccd5f23c5b0a77d069e029bc Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 30 Jan 2010 08:35:43 +0530 Subject: [PATCH 226/637] doc/3: couple of clarifications - deny rules only apply to "W" ops - be more specific about what allows "R" to pass --- doc/3-faq-tips-etc.mkd | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 8b82086..4f305d0 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -217,8 +217,8 @@ Note that at this point no git program has entered the picture, and we have no way of knowing what **ref** (branch, tag, etc) he is trying to update, even if it is a "write" operation. -For a "read" operation to pass this check, the username (or `@all`) must be -mentioned on some line in the config for this repo. +For a "read" operation to pass this check, the username (or `@all`) must have +read permission (i.e., R, RW, or RW+) on at least one branch of the repo. For a "write" operation, there is an additional restriction: lines specifying only `R` (read access) don't count. *The user must have write access to @@ -501,7 +501,9 @@ that code path to better use :-) ***IMPORTANT CAVEAT: if you use deny rules, the order of the rules also makes a difference, where earlier it did not. Please review your ruleset carefully or test it. In particular, do not use `@all` in a deny rule -- it won't work -as you might expect***. +as you might expect***. Also, deny rules are only processed in the second +level checks (see "two levels of access rights checking" above), which means +they only apply to write operations. Take a look at the following snippet, which *seems* to say that "bruce" can write versioned tags (anything containing `refs/tags/v[0-9]`), but the other From 0b960cfae2d89e4bd3cdc1b56823ed34d90e5567 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 31 Jan 2010 23:10:12 +0530 Subject: [PATCH 227/637] auth/update-hook/pm: make &log() a common function --- src/gitolite.pm | 6 ++++++ src/gl-auth-command | 10 ++-------- src/hooks/update | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index d905a72..84dfc9a 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -42,6 +42,12 @@ sub wrap_open { return $fh; } +sub log_it { + open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!\n"; + print $log_fh @_; + close $log_fh or die "close log failed: $!\n"; +} + # ---------------------------------------------------------------------------- # where is the rc file hiding? # ---------------------------------------------------------------------------- diff --git a/src/gl-auth-command b/src/gl-auth-command index d6482ed..f494cc8 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -38,8 +38,7 @@ require "$bindir/gitolite.pm"; &where_is_rc(); die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; -# we need to pass GL_ADMINDIR and the bindir to the child hooks (well only the -# admin repo's post-update hook but still...) +# we need to pass GL_ADMINDIR and the bindir to the child hooks $ENV{GL_ADMINDIR} = $GL_ADMINDIR; $ENV{GL_BINDIR} = $bindir; @@ -149,12 +148,7 @@ $GL_LOGT =~ s/%m/$m/g; $GL_LOGT =~ s/%d/$d/g; $ENV{GL_LOG} = $GL_LOGT; -# if log failure isn't important enough to block access, get rid of all the -# error checking -open my $log_fh, ">>", $ENV{GL_LOG} - or die "open log failed: $!\n"; -print $log_fh "$ENV{GL_TS}\t$ENV{SSH_ORIGINAL_COMMAND}\t$user\n"; -close $log_fh or die "close log failed: $!\n"; +&log_it("$ENV{GL_TS}\t$ENV{SSH_ORIGINAL_COMMAND}\t$user\n"); # ---------------------------------------------------------------------------- # over to git now diff --git a/src/hooks/update b/src/hooks/update index c1ff18f..a68ce18 100755 --- a/src/hooks/update +++ b/src/hooks/update @@ -33,6 +33,10 @@ die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; # then "do" the compiled config file, whose name we now know die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; +# we've started to need some common subs in what used to be a small, cute, +# little script that barely spanned a few lines :( +require "$ENV{GL_BINDIR}/gitolite.pm"; + # ---------------------------------------------------------------------------- # start... # ---------------------------------------------------------------------------- @@ -118,11 +122,7 @@ check_ref($_) for @refs; # if we returned at all, all the checks succeeded, so we log the action and exit 0 -# logging note: if log failure isn't important enough to block pushes, get rid -# of all the error checking -open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!\n"; -print $log_fh "$ENV{GL_TS} $perm\t" . +&log_it("$ENV{GL_TS} $perm\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . - "\t$ENV{GL_REPO}\t$ref\t$ENV{GL_USER}\t$log_refex\n"; -close $log_fh or die "close log failed: $!\n"; + "\t$ENV{GL_REPO}\t$ref\t$ENV{GL_USER}\t$log_refex\n"); exit 0; From 7f203fc020858a01c39caa2a45587c6b3efa836e Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 31 Jan 2010 23:56:58 +0530 Subject: [PATCH 228/637] update-hook/pm: made check_ref a common sub --- src/gitolite.pm | 26 ++++++++++++++++++++++++++ src/hooks/update | 32 ++------------------------------ 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 84dfc9a..8ad5567 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -48,6 +48,32 @@ sub log_it { close $log_fh or die "close log failed: $!\n"; } +# check one ref +sub check_ref { + + # normally, the $ref will be whatever ref the commit is trying to update + # (like refs/heads/master or whatever). At least one of the refexes that + # pertain to this user must match this ref **and** the corresponding + # permission must also match the action (W or +) being attempted. If none + # of them match, the access is denied. + + # Notice that the function DIES!!! Any future changes that require more + # work to be done *after* this, even on failure, can start using return + # codes etc., but for now we're happy to just die. + + my ($allowed_refs, $repo, $ref, $perm) = @_; + for my $ar (@{$allowed_refs}) { + my $refex = (keys %$ar)[0]; + # refex? sure -- a regex to match a ref against :) + next unless $ref =~ /^$refex/; + die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-'; + + # as far as *this* ref is concerned we're ok + return $refex if ($ar->{$refex} =~ /\Q$perm/); + } + die "$perm $ref $repo $ENV{GL_USER} DENIED by fallthru\n"; +} + # ---------------------------------------------------------------------------- # where is the rc file hiding? # ---------------------------------------------------------------------------- diff --git a/src/hooks/update b/src/hooks/update index a68ce18..876f46b 100755 --- a/src/hooks/update +++ b/src/hooks/update @@ -85,40 +85,12 @@ if (exists $repos{$ENV{GL_REPO}}{NAME_LIMITS}) { push @refs, map { chomp; s/^/NAME\//; $_; } `git diff --name-only $oldtree $newtree`; } -my $refex = ''; - -# check one ref -sub check_ref { - - # normally, the $ref will be whatever ref the commit is trying to update - # (like refs/heads/master or whatever). At least one of the refexes that - # pertain to this user must match this ref **and** the corresponding - # permission must also match the action (W or +) being attempted. If none - # of them match, the access is denied. - - # Notice that the function DIES!!! Any future changes that require more - # work to be done *after* this, even on failure, can start using return - # codes etc., but for now we're happy to just die. - - my $ref = shift; - for my $ar (@allowed_refs) { - $refex = (keys %$ar)[0]; - # refex? sure -- a regex to match a ref against :) - next unless $ref =~ /^$refex/; - die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-'; - - # as far as *this* ref is concerned we're ok - return $refex if ($ar->{$refex} =~ /\Q$perm/); - } - die "$perm $ref $ENV{GL_REPO} $ENV{GL_USER} DENIED by fallthru\n"; -} - # and in this version, we have many "refs" to check. The one we print in the # log is the *first* one (which is a *real* ref, like refs/heads/master), # while all the rest (if they exist) are like NAME/something. So we do the # first one separately to capture it, then run the rest (if any) -my $log_refex = check_ref(shift @refs); -check_ref($_) for @refs; +my $log_refex = check_ref(\@allowed_refs, $ENV{GL_REPO}, (shift @refs), $perm); +&check_ref (\@allowed_refs, $ENV{GL_REPO}, $_ , $perm) for @refs; # if we returned at all, all the checks succeeded, so we log the action and exit 0 From 98a4c79dce63ff0a7663d9f677f1a7339cb7c4fe Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 31 Jan 2010 20:24:36 +0530 Subject: [PATCH 229/637] (read this in full) access control for non-git commands running over ssh This is actually a pretty big deal, and I am seriously starting wonder if calling this "gito*lite*" is justified anymore. Anyway, in for a penny, in for a pound... This patch implements a generic way to allow access control for external commands, as long as they are invoked via ssh and present a server-side command that contains enough information to make an access control decision. The first (and only, so far) such command implemented is rsync. Please read the changes in this commit (at least the ones in conf/ and doc/) carefully. --- README.mkd | 5 +++ conf/example.conf | 18 ++++++++++ conf/example.gitolite.rc | 11 ++++++ doc/3-faq-tips-etc.mkd | 15 ++++++++ src/gitolite.pm | 78 ++++++++++++++++++++++++++++++++++++++++ src/gl-auth-command | 7 ++-- src/gl-compile-conf | 1 + 7 files changed, 132 insertions(+), 3 deletions(-) diff --git a/README.mkd b/README.mkd index 1df3bf8..7f3956a 100644 --- a/README.mkd +++ b/README.mkd @@ -99,6 +99,11 @@ or in Unix, perl, shell, etc.)... well I can't afford 1000 USD rewards like djb, so you'll have to settle for 1000 INR (Indian Rupees) as a "token" prize :-) +Update 2010-01-31: this security promise does not apply if you enable any of +the external command helpers (like rsync). It's probably quite secure, but I +just haven't thought about it enough to be able to make such promises, like I +can for the rest of "master". + ---- ### contact and license diff --git a/conf/example.conf b/conf/example.conf index 9d73aca..6dcc4e2 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -259,3 +259,21 @@ repo gitolite # security reasons. # - you can also use an absolute path if you like, although in the interests # of cloning the admin-repo sanely you should avoid doing this! + +# EXTERNAL COMMAND HELPERS -- RSYNC +# --------------------------------- + +# If $RSYNC_BASE is non-empty, the following config entries come into play +# (otherwise they are ignored): + +# a "fake" git repository to collect rsync rules. Gitolite does not +# auto-create any repo whose name starts with EXTCMD/ +repo EXTCMD/rsync +# grant permissions to files/dirs within the $RSYNC_BASE tree. A leading +# NAME/ is required as a prefix; the actual path starts after that. Matching +# follows the same rules as elsewhere in gitolite. + RW NAME/ = sitaram + RW NAME/foo/ = user1 + R NAME/bar/ = user2 +# just to remind you that these are perl regexes, not shell globs + RW NAME/baz/.*/*.c = user3 diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index d53b65f..cf7fe3d 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -106,6 +106,17 @@ $GIT_PATH=""; # syntax: space separated list of gitolite usernames in *one* string variable. # $SHELL_USERS = "alice bob"; +# -------------------------------------- + +# EXTERNAL COMMAND HELPER -- RSYNC +# +# base path of all the files that are accessible via rsync. Must be an +# absolute path. Leave it undefined or set to the empty string to disable the +# rsync helper. +$RSYNC_BASE = ""; +# $RSYNC_BASE = "/home/git/up-down"; +# $RSYNC_BASE = "/tmp/up-down"; + # -------------------------------------- # per perl rules, this should be the last line in such a file: 1; diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index cd3bdb7..edc6351 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -26,6 +26,7 @@ In this document: * "exclude" (or "deny") rules * "personal" branches * custom hooks and custom git config + * access control for external commands * design choices * keeping the parser and the access control separate @@ -608,6 +609,20 @@ You can specify hooks that you want to propagate to all repos, as well as per-repo "gitconfig" settings. Please see `doc/2-admin.mkd` and `conf/example.conf` for details. +#### access control for external commands + +Gitolite now has a mechanism for allowing access control for arbitrary +external commands, as long as they are invoked via ssh and present a +server-side command that contains enough information to make an access control +decision. The first (and only, so far) such command implemented is rsync. + +Note that this is incompatible with giving people shell access as described in +`doc/6-ssh-troubleshooting.mkd` -- people who have shell access are not +subject to this mechanism (it wouldn't make sense to try and control someone +who has shell access anyway). + +Please see the config files (both of them) for examples and usage. + ### design choices #### keeping the parser and the access control separate diff --git a/src/gitolite.pm b/src/gitolite.pm index 8ad5567..c5bcd9e 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -156,4 +156,82 @@ sub report_basic print "$perm\t$r\n\r" if $perm; } } + +# ---------------------------------------------------------------------------- +# E X T E R N A L C O M M A N D H E L P E R S +# ---------------------------------------------------------------------------- + +sub ext_cmd +{ + my ($GL_CONF_COMPILED, $RSYNC_BASE, $cmd) = @_; + + # check each external command we know about and call it if enabled + if ($RSYNC_BASE and $cmd =~ /^rsync /) { + &ext_cmd_rsync($GL_CONF_COMPILED, $RSYNC_BASE, $cmd); + } else { + die "bad command: $cmd\n"; + } +} + +# ---------------------------------------------------------------------------- +# generic check access routine +# ---------------------------------------------------------------------------- + +sub check_access +{ + my ($GL_CONF_COMPILED, $repo, $path, $perm) = @_; + my $ref = "NAME/$path"; + + &parse_acl($GL_CONF_COMPILED); + + # until I do some major refactoring (which will bloat the update hook a + # bit, sadly), this code duplicates stuff in the current update hook. + + my @allowed_refs; + # we want specific perms to override @all, so they come first + push @allowed_refs, @ { $repos{$repo}{$ENV{GL_USER}} || [] }; + push @allowed_refs, @ { $repos{$repo}{'@all'} || [] }; + + for my $ar (@allowed_refs) { + my $refex = (keys %$ar)[0]; + next unless $ref =~ /^$refex/; + die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-'; + return if ($ar->{$refex} =~ /\Q$perm/); + } + die "$perm $ref $ENV{GL_REPO} $ENV{GL_USER} DENIED by fallthru\n"; +} + +# ---------------------------------------------------------------------------- +# external command helper: rsync +# ---------------------------------------------------------------------------- + +sub ext_cmd_rsync +{ + my ($GL_CONF_COMPILED, $RSYNC_BASE, $cmd) = @_; + + # test the command patterns; reject if they don't fit. Rsync sends + # commands that looks like one of these to the server (the first one is + # for a read, the second for a write) + # rsync --server --sender -some.flags . some/path + # rsync --server -some.flags . some/path + + die "bad rsync command: $cmd" + unless $cmd =~ /^rsync --server( --sender)? -[\w.]+ \. (\S+)$/; + my $perm = "W"; + $perm = "R" if $1; + my $path = $2; + die "I dont like absolute paths in $cmd\n" if $path =~ /^\//; + die "I dont like '..' paths in $cmd\n" if $path =~ /\.\./; + + # ok now check if we're permitted to execute a $perm action on $path + # (taken as a refex) using rsync. + + &check_access($GL_CONF_COMPILED, 'EXTCMD/rsync', $path, $perm); + # that should "die" if there's a problem + + wrap_chdir($RSYNC_BASE); + exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND}; +} + + 1; diff --git a/src/gl-auth-command b/src/gl-auth-command index f494cc8..1dd2fba 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -24,7 +24,7 @@ use warnings; # ---------------------------------------------------------------------------- # these are set by the "rc" file -our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR); +our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR, $RSYNC_BASE); # and these are set by gitolite.pm our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT); our %repos; @@ -99,8 +99,9 @@ my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:\.git)?'/); unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo and $repo =~ $REPONAME_PATT ) { # if the user is allowed a shell, just run the command exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND} if $shell_allowed; - # otherwise, whine - die "bad command: $cmd\n"; + # otherwise, call the external command helper + &ext_cmd($GL_CONF_COMPILED, $RSYNC_BASE, $cmd); + exit; # in case the external command helper forgot :-) } # ---------------------------------------------------------------------------- diff --git a/src/gl-compile-conf b/src/gl-compile-conf index e88819a..0a8d369 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -355,6 +355,7 @@ my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" wrap_chdir("$repo_base_abs"); for my $repo (sort keys %repos) { + next if $repo =~ m(^EXTCMD/); # these are not real repos unless (-d "$repo.git") { new_repo($repo, "$GL_ADMINDIR/src/hooks"); # new_repo would have chdir'd us away; come back From 18312de77ab6f85ce2e7535d47041717deb85912 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 31 Jan 2010 21:09:05 +0530 Subject: [PATCH 230/637] rsync: add support for delete/partial --- src/gitolite.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index c5bcd9e..54dd1da 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -216,7 +216,7 @@ sub ext_cmd_rsync # rsync --server -some.flags . some/path die "bad rsync command: $cmd" - unless $cmd =~ /^rsync --server( --sender)? -[\w.]+ \. (\S+)$/; + unless $cmd =~ /^rsync --server( --sender)? -[\w.]+(?: --(?:delete|partial))* \. (\S+)$/; my $perm = "W"; $perm = "R" if $1; my $path = $2; From 20c29c01450fc4b4f69a75ad93c50602a32262e2 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 1 Feb 2010 11:35:35 +0530 Subject: [PATCH 231/637] rsync: log the command used --- src/gitolite.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gitolite.pm b/src/gitolite.pm index 54dd1da..c6d75de 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -230,6 +230,7 @@ sub ext_cmd_rsync # that should "die" if there's a problem wrap_chdir($RSYNC_BASE); + &log_it("$ENV{GL_TS}\t$ENV{SSH_ORIGINAL_COMMAND}\t$ENV{USER}\n"); exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND}; } From 43da598c0852c4dcbd78fdb7251ed338c14fe5ce Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 1 Feb 2010 11:36:24 +0530 Subject: [PATCH 232/637] auth: minor flow change when defaulting to "info" --- src/gl-auth-command | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index 1dd2fba..5b3dc42 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -67,16 +67,16 @@ my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere! # sanity checks on SSH_ORIGINAL_COMMAND # ---------------------------------------------------------------------------- -# print basic access info if SSH_ORIGINAL_COMMAND does not exist +# no SSH_ORIGINAL_COMMAND given... unless ($ENV{SSH_ORIGINAL_COMMAND}) { - # unless the user is allowed to use a shell + # if the user is allowed to use a shell, give him one if ($shell_allowed) { my $shell = $ENV{SHELL}; $shell =~ s/.*\//-/; # change "/bin/bash" to "-bash" exec { $ENV{SHELL} } $shell; } - &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user); - exit 1; + # otherwise, pretend he typed in "info" and carry on... + $ENV{SSH_ORIGINAL_COMMAND} = 'info'; } my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; From 09195afd443e3fb92b5c8f62fb3971a176d09c31 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 31 Jan 2010 11:43:43 +0530 Subject: [PATCH 233/637] document deny rules a bit better --- conf/example.conf | 20 ++++++++++++-------- doc/3-faq-tips-etc.mkd | 9 +++------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index 6dcc4e2..6ad91ad 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -142,10 +142,18 @@ repo git # DENY/EXCLUDE RULES -# ***IMPORTANT NOTE: if you use deny rules, the order of the rules also makes -# a difference, where earlier it did not. Please review your ruleset -# carefully or test it. In particular, do not use `@all` in a deny rule -- it -# won't work as you might expect***. +# ***IMPORTANT NOTES ABOUT "DENY" RULES***: + +# - deny rules do NOT affect read access. They only apply to `W` and `+`. +# +# - when using deny rules, the order of your rules starts to matter, where +# earlier it did not. The first matching rule applies, where "matching" is +# defined as either permitting the operation you're attempting (`W` or `+`), +# which results in success, or a "deny" (`-`), which results in failure. +# (As before, a fallthrough also results in failure). +# +# - do not use `@all` when your config has any deny rules; it won't work as +# you probably expect it to! # in the example above, you cannot easily say "anyone can write any tag, # except version tags can only be written by junio". The following might look @@ -161,10 +169,6 @@ repo git - refs/tags/v[0-9] = linus pasky @others RW refs/tags/ = junio linus pasky @others -# Briefly, the rule is: the first matching refex that has the operation you're -# looking for (`W` or `+`), or a minus (`-`), results in success, or failure, -# respectively. A fallthrough also results in failure - # FILE/DIR NAME BASED RESTRICTIONS # -------------------------------- diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index edc6351..67ef956 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -499,12 +499,9 @@ that code path to better use :-) #### "exclude" (or "deny") rules -***IMPORTANT CAVEAT: if you use deny rules, the order of the rules also makes -a difference, where earlier it did not. Please review your ruleset carefully -or test it. In particular, do not use `@all` in a deny rule -- it won't work -as you might expect***. Also, deny rules are only processed in the second -level checks (see "two levels of access rights checking" above), which means -they only apply to write operations. +Here is an illustrative explanation of "deny" rules. However, please be sure +to read the "DENY/EXCLUDE RULES" section in `conf/example.conf` for important +notes/caveats before using "deny" rules. Take a look at the following snippet, which *seems* to say that "bruce" can write versioned tags (anything containing `refs/tags/v[0-9]`), but the other From 2d9c4c4ae9e04b9ce0fa91cb1e5857ad541858d6 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 1 Feb 2010 16:54:39 +0530 Subject: [PATCH 234/637] oops; logging bug --- src/gl-auth-command | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index 5b3dc42..8fae9f5 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -63,6 +63,24 @@ if ($ARGV[0] eq '-s') { # first, fix the biggest gripe I have with gitosis, a 1-line change my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere! +# ---------------------------------------------------------------------------- +# logging, timestamp env vars +# ---------------------------------------------------------------------------- + +# timestamp +my ($s, $min, $h, $d, $m, $y) = (localtime)[0..5]; +$y += 1900; $m++; # usual adjustments +for ($s, $min, $h, $d, $m) { + $_ = "0$_" if $_ < 10; +} +$ENV{GL_TS} = "$y-$m-$d.$h:$min:$s"; + +# substitute template parameters and set the logfile name +$GL_LOGT =~ s/%y/$y/g; +$GL_LOGT =~ s/%m/$m/g; +$GL_LOGT =~ s/%d/$d/g; +$ENV{GL_LOG} = $GL_LOGT; + # ---------------------------------------------------------------------------- # sanity checks on SSH_ORIGINAL_COMMAND # ---------------------------------------------------------------------------- @@ -108,6 +126,8 @@ unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo an # first level permissions check # ---------------------------------------------------------------------------- +$ENV{GL_REPO}=$repo; + # parse the compiled acl; goes into %repos (global) &parse_acl($GL_CONF_COMPILED); @@ -128,32 +148,11 @@ if ( not -d "$repo_base_abs/$repo.git" ) { } } -# ---------------------------------------------------------------------------- -# logging, timestamp. also setup env vars for later -# ---------------------------------------------------------------------------- - -# reponame -$ENV{GL_REPO}=$repo; - -# timestamp -my ($s, $min, $h, $d, $m, $y) = (localtime)[0..5]; -$y += 1900; $m++; # usual adjustments -for ($s, $min, $h, $d, $m) { - $_ = "0$_" if $_ < 10; -} -$ENV{GL_TS} = "$y-$m-$d.$h:$min:$s"; - -# substitute template parameters and set the logfile name -$GL_LOGT =~ s/%y/$y/g; -$GL_LOGT =~ s/%m/$m/g; -$GL_LOGT =~ s/%d/$d/g; -$ENV{GL_LOG} = $GL_LOGT; - -&log_it("$ENV{GL_TS}\t$ENV{SSH_ORIGINAL_COMMAND}\t$user\n"); - # ---------------------------------------------------------------------------- # over to git now # ---------------------------------------------------------------------------- +&log_it("$ENV{GL_TS}\t$ENV{SSH_ORIGINAL_COMMAND}\t$user\n"); + $repo = "'$REPO_BASE/$repo.git'"; exec("git", "shell", "-c", "$verb $repo"); From b1659db7428fb69e9ec32df428154a88847f24d2 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 1 Feb 2010 22:55:42 +0530 Subject: [PATCH 235/637] more fixes to wildcard reporting... (thank God I don't warrant this part of gitolite ;-) --- src/gitolite.pm | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index f0e37a6..045e339 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -261,7 +261,6 @@ sub expand_wild # get the list of repo patterns &parse_acl($GL_CONF_COMPILED, "", "NOBODY", "NOBODY", "NOBODY"); - my @repopatts = grep { $_ !~ $REPONAME_PATT } sort keys %repos; my %reponames = map { $_ => 1 } grep { $_ =~ $REPONAME_PATT } sort keys %repos; # display matching repos (from *all* the repos in the system) that $user @@ -279,26 +278,15 @@ sub expand_wild next if $reponames{$actual_repo}; # it has to match the pattern being expanded next unless $actual_repo =~ /^$repo$/; - # it also has to match one of the repo patterns in %repos (which we - # already snarfed earlier) - my @patts = grep { $actual_repo =~ /^$_$/ } @repopatts; - # should be exactly one match - # (see reasoning in the "other issues" section of doc/4) - if (@patts != 1) { - # though if it's more than one we print an additional message - print "ignoring $actual_repo; has multiple matches\n(@patts)\n" if @patts > 1; - next; - } # find the creater and subsitute in repos my ($creater, $read, $write) = &repo_rights($repo_base_abs, $actual_repo, $user); # get access list with this - &parse_acl($GL_CONF_COMPILED, "", $creater, $read || "NOBODY", $write || "NOBODY"); + &parse_acl($GL_CONF_COMPILED, $actual_repo, $creater, $read || "NOBODY", $write || "NOBODY"); - my $perm = ""; - $perm .= ($repos{$patts[0]}{C}{'@all'} or $repos{$patts[0]}{C}{$user}) ? " C" : " "; - $perm .= ($repos{$patts[0]}{R}{'@all'} or $repos{$patts[0]}{R}{$user}) ? " R" : " "; - $perm .= ($repos{$patts[0]}{W}{'@all'} or $repos{$patts[0]}{W}{$user}) ? " W" : " "; + my $perm = " "; + $perm .= ($repos{$actual_repo}{R}{'@all'} or $repos{$actual_repo}{R}{$user}) ? " R" : " "; + $perm .= ($repos{$actual_repo}{W}{'@all'} or $repos{$actual_repo}{W}{$user}) ? " W" : " "; next if $perm eq " "; print "$perm\t($creater)\t$actual_repo\n"; } From 0a7fa6c6b58b8c64e1c866f8781fbb5140b6f750 Mon Sep 17 00:00:00 2001 From: "martin f. krafft" Date: Wed, 3 Feb 2010 21:13:47 +1300 Subject: [PATCH 236/637] Tell gitweb about repo owner via git-config Gitolite uses projects.list to set the owners for gitweb's use. Unfortunately, this does not work for gitweb setups that set $projectroot to a directory, thus generating the list of repositories on the fly. This patch changes that: gitolite now writes the gitweb.owner configuration variable for each repository (and properly cleans up after itself if the owner is removed). The patch causes gitolite not to write the owner to projects.list anymore, as this would be redundant. The owner also needs no longer be escaped, so this patch removes the poor man's 's/ /+/g' escaping previously in place. Note that I am not a Perl coder. Thus there are probably better ways to implement this, but at least it works. Cc: Sitaram Chamarty Signed-off-by: martin f. krafft --- src/gl-compile-conf | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 0a8d369..bb05d1e 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -284,7 +284,6 @@ sub parse_conf_file die "$WARN $fragment attempting to set description for $repo\n" if $fragment ne 'master' and $fragment ne $repo and ($groups{"\@$fragment"}{$repo} || '') ne 'master'; $desc{"$repo.git"} = $desc; - $owner =~ s/ /+/g if $owner; # gitweb/INSTALL wants more, but meh...! $owner{"$repo.git"} = $owner || ''; } else @@ -415,16 +414,32 @@ for my $repo (sort keys %repos) { $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" . ( $owner{$proj} ? " $owner{$proj}" : "" ) . "\n"; + print $projlist_fh "$proj\n"; } close $projlist_fh; From 67c10a34fe4d934ae92cc9c8320a396c657d8344 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 1 Feb 2010 15:37:35 +0530 Subject: [PATCH 237/637] auth: new subcommand "htpasswd" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit great idea by Robin Smidsrød: since users are already capable of authenticating themselves to gitolite via ssh keys, use that to let them set or change their own HTTP passwords (ie, run the "htpasswd" command with the correct parameters on behalf of the "git" user on the server) code, rc para, and documentation. In fact everything except... ahem... testing ;-) and while we're about it, we also reorganised the way these helper commands (including the venerable "info" are called) --- conf/example.gitolite.rc | 9 ++++++++ doc/3-faq-tips-etc.mkd | 29 +++++++++++++++++++------ src/gitolite.pm | 46 +++++++++++++++++++++++++++++++++++----- src/gl-auth-command | 42 ++++++++++++++++++------------------ 4 files changed, 94 insertions(+), 32 deletions(-) diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index cf7fe3d..bcb281f 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -108,6 +108,15 @@ $GIT_PATH=""; # -------------------------------------- +# if you want to enable the "htpasswd" command, give this the absolute path to +# whatever file apache (etc) expect to find the passwords in. + +$HTPASSWD_FILE = ""; + +# Look in doc/3 ("easier to link gitweb authorisation with gitolite" section) +# for more details on using this feature. + +# -------------------------------------- # EXTERNAL COMMAND HELPER -- RSYNC # # base path of all the files that are accessible via rsync. Must be an diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 67ef956..6723000 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -331,9 +331,25 @@ This requires that: * the HTTP auth should use the same username (like "sitaram") as used in the gitolite config (for the corresponding user) -Once that is done, it's easy. Gitweb allows you to specify a subroutine to -decide on access. We use that feature and tie it to gitolite. Sample code -(untested, munged from something I saw [here][leho]) is given below. +Normally a superuser sets up passwords for users using the "htpasswd" command, +but this is an administrative chore. + +Robin Smidsrød had the *great* idea that, since each user already has pubkey +access to `git@server`, this gives us a very neat way of using gitolite to let +the users *manage their own HTTP passwords*. Here's how: + + * setup apache so that the htaccess file it looks for is owned by the "git" + user + * in the `~/.gitolite.rc` file, look for the variable `$HTPASSWD_FILE` and + point it to this file + * tell your users to type in `ssh git@server htpasswd` to set or change + their HTTP passwords + +Here's the rest of how it hangs together. + +Gitweb allows you to specify a subroutine to decide on access. We use that +feature and tie it to gitolite. Sample code (untested by me, but others do +use it, munged from something I saw [here][leho]) is given below. Note the **utter simplicity** of the actual check (just 1 line!). This is an unexpected piece of luck coming from the decision to keep the config parse @@ -349,7 +365,7 @@ already done and we just use it! $projectroot = '/home/git/repositories/'; my $gl_conf_compiled = '/home/git/.gitolite/conf/gitolite.conf-compiled.pm'; - # I assume this gives us the HTTP auth username + # I am told this gives us the HTTP auth username my $username = $cgi->remote_user; # ---------- @@ -359,10 +375,11 @@ already done and we just use it! die "parse $gl_conf_compiled failed: " . ($! or $@) unless do $gl_conf_compiled; # this is gitweb's mechanism; it calls whatever sub is pointed at by this - # variable to decide access yes/no + # variable to decide access yes/no. Gitweb calls it with one argument + # containing the full path of the repo being accessed $export_auth_hook = sub { my $reponame = shift; - # gitweb passes us the full repo path; so we strip the beginning... + # take the full path provided, strip the beginning... $reponame =~ s/\Q$projectroot\E\/?//; # ...and the end, to get the repo name as it is specified in gitolite conf $reponame =~ s/\.git$//; diff --git a/src/gitolite.pm b/src/gitolite.pm index c6d75de..a8127e9 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -158,17 +158,28 @@ sub report_basic } # ---------------------------------------------------------------------------- -# E X T E R N A L C O M M A N D H E L P E R S +# S P E C I A L C O M M A N D S # ---------------------------------------------------------------------------- -sub ext_cmd +sub special_cmd { - my ($GL_CONF_COMPILED, $RSYNC_BASE, $cmd) = @_; + my ($GL_ADMINDIR, $GL_CONF_COMPILED, $RSYNC_BASE, $HTPASSWD_FILE) = @_; - # check each external command we know about and call it if enabled - if ($RSYNC_BASE and $cmd =~ /^rsync /) { + my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; + my $user = $ENV{GL_USER}; + + # check each special command we know about and call it if enabled + if ($cmd eq 'info') { + &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user); + print "you also have shell access\n\r" if $shell_allowed; + } elsif ($HTPASSWD_FILE and $cmd eq 'htpasswd') { + &ext_cmd_htpasswd($HTPASSWD_FILE); + } elsif ($RSYNC_BASE and $cmd =~ /^rsync /) { &ext_cmd_rsync($GL_CONF_COMPILED, $RSYNC_BASE, $cmd); } else { + # if the user is allowed a shell, just run the command + exec $ENV{SHELL}, "-c", $cmd if $shell_allowed; + die "bad command: $cmd\n"; } } @@ -234,5 +245,30 @@ sub ext_cmd_rsync exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND}; } +# ---------------------------------------------------------------------------- +# external command helper: htpasswd +# ---------------------------------------------------------------------------- + +sub ext_cmd_htpasswd +{ + my $HTPASSWD_FILE = shift; + + die "$HTPASSWD_FILE doesn't exist or is not writable\n" unless -w $HTPASSWD_FILE; + $|++; + print <; + $password =~ s/[\n\r]*$//; + my $rc = system("htpasswd", "-b", $HTPASSWD_FILE, $ENV{GL_USER}, $password); + die "htpasswd command seems to have failed with $rc return code...\n" if $rc; +} 1; diff --git a/src/gl-auth-command b/src/gl-auth-command index 8fae9f5..8aa2f65 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -24,7 +24,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); +our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR, $RSYNC_BASE, $HTPASSWD_FILE); # and these are set by gitolite.pm our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT); our %repos; @@ -97,31 +97,31 @@ unless ($ENV{SSH_ORIGINAL_COMMAND}) { $ENV{SSH_ORIGINAL_COMMAND} = 'info'; } -my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; -# people allowed to get a shell can get basic access info by asking nicely -if ($cmd eq 'info') { - &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user); - print "you also have shell access\n\r" if $shell_allowed; - exit 1; -} +# ---------------------------------------------------------------------------- +# non-git commands +# ---------------------------------------------------------------------------- -# split into command and arguments; the pattern allows old style as well as -# new style: "git-subcommand arg" or "git subcommand arg", just like gitosis -# does, although I'm not sure how necessary that is -# -# keep in mind this is how git sends across the command: -# git-receive-pack 'reponame.git' -# including the single quotes +# if the command does NOT fit the pattern of a normal git command, send it off +# somewhere else... -my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:\.git)?'/); +# side notes on detecting a normal git command: the pattern we check allows +# old style as well as new style ("git-subcommand arg" or "git subcommand +# arg"), just like gitosis does, although I'm not sure how necessary that is. +# Currently, this is how git sends across the command (including the single +# quotes): +# git-receive-pack 'reponame.git' + +my ($verb, $repo) = ($ENV{SSH_ORIGINAL_COMMAND} =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:\.git)?'/); unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo and $repo =~ $REPONAME_PATT ) { - # if the user is allowed a shell, just run the command - exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND} if $shell_allowed; - # otherwise, call the external command helper - &ext_cmd($GL_CONF_COMPILED, $RSYNC_BASE, $cmd); - exit; # in case the external command helper forgot :-) + # ok, it's not a normal git command; call the special command helper + &special_cmd ($GL_ADMINDIR, $GL_CONF_COMPILED, $RSYNC_BASE, $HTPASSWD_FILE); + exit; } +# ---------------------------------------------------------------------------- +# the real git commands (git-receive-pack, etc...) +# ---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # first level permissions check # ---------------------------------------------------------------------------- From 86166f7adc60f191b145818eda56d095aca7ed7f Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 4 Feb 2010 15:16:47 +0530 Subject: [PATCH 238/637] $shell_allowed needs to be passed to specal_cmds brought on by realising that you lost $shell_allowed when refactoring (previous commit) but perl hadn't caught it because -- damn -- you didn't have "use strict" in gitolite.pm --- src/gitolite.pm | 18 +++++++++++------- src/gl-auth-command | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index a8127e9..a6dba92 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -1,3 +1,4 @@ +use strict; # this file is commonly used using "require". It is not required to use "use" # (because it doesn't live in a different package) @@ -17,16 +18,19 @@ # common definitions # ---------------------------------------------------------------------------- -$ABRT = "\n\t\t***** ABORTING *****\n "; -$WARN = "\n\t\t***** WARNING *****\n "; +our $ABRT = "\n\t\t***** ABORTING *****\n "; +our $WARN = "\n\t\t***** WARNING *****\n "; # commands we're expecting -$R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/; -$W_COMMANDS=qr/^git[ -]receive-pack$/; +our $R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/; +our $W_COMMANDS=qr/^git[ -]receive-pack$/; # note that REPONAME_PATT allows "/", while USERNAME_PATT allows "@" -$REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/+-]*$); # very simple pattern -$USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); # very simple pattern +our $REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/+-]*$); # very simple pattern +our $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); # very simple pattern + +our $REPO_UMASK; +our %repos; # ---------------------------------------------------------------------------- # convenience subs @@ -163,7 +167,7 @@ sub report_basic sub special_cmd { - my ($GL_ADMINDIR, $GL_CONF_COMPILED, $RSYNC_BASE, $HTPASSWD_FILE) = @_; + my ($GL_ADMINDIR, $GL_CONF_COMPILED, $shell_allowed, $RSYNC_BASE, $HTPASSWD_FILE) = @_; my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; my $user = $ENV{GL_USER}; diff --git a/src/gl-auth-command b/src/gl-auth-command index 8aa2f65..48c67be 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -114,7 +114,7 @@ unless ($ENV{SSH_ORIGINAL_COMMAND}) { my ($verb, $repo) = ($ENV{SSH_ORIGINAL_COMMAND} =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:\.git)?'/); unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo and $repo =~ $REPONAME_PATT ) { # ok, it's not a normal git command; call the special command helper - &special_cmd ($GL_ADMINDIR, $GL_CONF_COMPILED, $RSYNC_BASE, $HTPASSWD_FILE); + &special_cmd ($GL_ADMINDIR, $GL_CONF_COMPILED, $shell_allowed, $RSYNC_BASE, $HTPASSWD_FILE); exit; } From 55a71f00e1fcf055638fac612197ca5c36456db9 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 4 Feb 2010 22:55:11 +0530 Subject: [PATCH 239/637] compile: die on authkeys write failure --- src/gl-compile-conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index bb05d1e..eb8f8d7 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -502,5 +502,6 @@ close $newkeys_fh or die "$ABRT close newkeys failed: $!\n"; # all done; overwrite the file (use cat to avoid perm changes) system("cat $ENV{HOME}/.ssh/authorized_keys > $ENV{HOME}/.ssh/old_authkeys"); -system("cat $ENV{HOME}/.ssh/new_authkeys > $ENV{HOME}/.ssh/authorized_keys"); +system("cat $ENV{HOME}/.ssh/new_authkeys > $ENV{HOME}/.ssh/authorized_keys") + and die "couldn't write authkeys file\n"; system("rm $ENV{HOME}/.ssh/new_authkeys"); From 00b793f5e64ea95a1556f7dae3e5ba6dd17a0f50 Mon Sep 17 00:00:00 2001 From: Teemu Matilainen Date: Wed, 3 Feb 2010 19:11:09 +0200 Subject: [PATCH 240/637] Set gitweb.owner config for new wildrepos When creating new wildrepos, add git config to tell gitweb the owner of the repository. Signed-off-by: Teemu Matilainen --- src/gitolite.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 045e339..a73f255 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -123,7 +123,10 @@ sub new_repo # erm, note that's "and die" not "or die" as is normal in perl wrap_chdir("$repo.git"); system("git --bare init >&2"); - system("echo $creater > gl-creater") if $creater; + if ($creater) { + system("echo $creater > gl-creater"); + system("git", "config", "gitweb.owner", $creater); + } # propagate our own, plus any local admin-defined, hooks system("cp $hooks_dir/* hooks/"); chmod 0755, "hooks/update"; From fa65d719a83fa33d8f8d95abdf4c9013dc1effc4 Mon Sep 17 00:00:00 2001 From: Teemu Matilainen Date: Thu, 4 Feb 2010 23:40:13 +0200 Subject: [PATCH 241/637] Enable setting desription for wildrepos Allow users to set and display description (for gitweb) for their own wildcard repositories using ssh commands: setdesc getdesc Signed-off-by: Teemu Matilainen --- src/gitolite.pm | 20 ++++++++++++++++++++ src/gl-auth-command | 6 +++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index a73f255..eb70dec 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -180,6 +180,26 @@ sub get_set_perms } } +# ---------------------------------------------------------------------------- +# getdesc and setdesc +# ---------------------------------------------------------------------------- + +sub get_set_desc +{ + my($repo_base_abs, $repo, $verb, $user) = @_; + my ($creater, $dummy, $dummy2) = &repo_rights($repo_base_abs, $repo, ""); + die "$repo doesnt exist or is not yours\n" unless $user eq $creater; + wrap_chdir("$repo_base_abs"); + wrap_chdir("$repo.git"); + if ($verb eq 'getdesc') { + system("cat", "description") if -f "description"; + } else { + system("cat > description"); + print "New description is:\n"; + system("cat", "description"); + } +} + # ---------------------------------------------------------------------------- # parse the compiled acl # ---------------------------------------------------------------------------- diff --git a/src/gl-auth-command b/src/gl-auth-command index 26fbf71..253ccad 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -86,7 +86,7 @@ my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" # get and set perms for actual repo created by wildcard-autoviv # ---------------------------------------------------------------------------- -my $CUSTOM_COMMANDS=qr/^\s*(expand|getperms|setperms)\s/; +my $CUSTOM_COMMANDS=qr/^\s*(expand|(get|set)(perms|desc))\s/; # note that all the subs called here chdir somewhere else and do not come # back; they all blithely take advantage of the fact that processing custom @@ -98,6 +98,10 @@ if ($cmd =~ $CUSTOM_COMMANDS) { # with an actual reponame, you can "getperms" or "setperms" get_set_perms($repo_base_abs, $repo, $verb, $user); } + elsif ($repo =~ $REPONAME_PATT and $verb =~ /(get|set)desc/) { + # with an actual reponame, you can "getdesc" or "setdesc" + get_set_desc($repo_base_abs, $repo, $verb, $user); + } elsif ($verb eq 'expand') { # with a wildcard, you can "expand" it to see what repos actually match die "$repo has invalid characters" unless "x$repo" =~ $REPOPATT_PATT; From 85cc31c77134d78d3ff3ca2fd2229d0e5b791530 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 5 Feb 2010 06:49:07 +0530 Subject: [PATCH 242/637] install/pm: turn hooks from copies to symlinks --- src/gitolite.pm | 14 +++++++++++++- src/gl-install | 6 ++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index a6dba92..9f2193f 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -78,6 +78,18 @@ sub check_ref { die "$perm $ref $repo $ENV{GL_USER} DENIED by fallthru\n"; } +# ln -sf :-) +sub ln_sf +{ + my($srcdir, $glob, $dstdir) = @_; + for my $hook ( glob("$srcdir/$glob") ) { + $hook =~ s/$srcdir\///; + unlink "$dstdir/$hook"; + symlink "$srcdir/$hook", "$dstdir/$hook" or die "could not symlink $hook\n"; + } +} + + # ---------------------------------------------------------------------------- # where is the rc file hiding? # ---------------------------------------------------------------------------- @@ -124,7 +136,7 @@ sub new_repo wrap_chdir("$repo.git"); system("git --bare init >&2"); # propagate our own, plus any local admin-defined, hooks - system("cp $hooks_dir/* hooks/"); + ln_sf($hooks_dir, "*", "hooks"); chmod 0755, "hooks/update"; } diff --git a/src/gl-install b/src/gl-install index 9494fc6..9159ed5 100755 --- a/src/gl-install +++ b/src/gl-install @@ -71,14 +71,16 @@ chdir("$repo_base_abs") or die "chdir $repo_base_abs failed: $!\n"; for my $repo (`find . -type d -name "*.git"`) { chomp ($repo); # propagate our own, plus any local admin-defined, hooks - system("cp $GL_ADMINDIR/src/hooks/* $repo/hooks/"); + ln_sf("$GL_ADMINDIR/src/hooks", "*", "$repo/hooks"); chmod 0755, "$repo/hooks/update"; } # oh and one of those repos is a bit more special and has an extra hook :) if ( -d "gitolite-admin.git/hooks" ) { print "copying post-update hook to gitolite-admin repo...\n"; - system("cp $GL_ADMINDIR/src/ga-post-update-hook gitolite-admin.git/hooks/post-update"); + unlink "gitolite-admin.git/hooks/post-update"; + symlink "$GL_ADMINDIR/src/ga-post-update-hook", "gitolite-admin.git/hooks/post-update" + or die "could not symlink post-update hook\n"; chmod 0755, "gitolite-admin.git/hooks/post-update"; } From 388f4d873d9844f771589e8c7aec2a862d84f1df Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 5 Feb 2010 16:00:47 +0530 Subject: [PATCH 243/637] (IMPORTANT; read this in full) no more "wildrepos" The wildrepos branch has been merged into master, and deleted. It will no longer exist as a separate branch. Instead, a new variable called $GL_WILDREPOS has been added which acts as a switch; when off (which is the default), many wildrepos features are disabled. (the "C" permissions, and the getperms (etc.) commands mainly). Important: if you are using wildrepos, please set "$GL_WILDREPOS = 1;" in the RC file when you upgrade to this version (or just before you do the upgrade). --- README.mkd | 13 +++++------ conf/example.gitolite.rc | 40 ++++++++++++++++++++++++++++++++- doc/3-faq-tips-etc.mkd | 5 ++--- doc/4-wildcard-repositories.mkd | 33 +++++++++++++++++---------- src/gitolite.pm | 8 ++++++- src/gl-auth-command | 7 +++--- src/gl-compile-conf | 6 +++-- 7 files changed, 82 insertions(+), 30 deletions(-) diff --git a/README.mkd b/README.mkd index 7f3956a..12596a4 100644 --- a/README.mkd +++ b/README.mkd @@ -86,10 +86,7 @@ detail [here][gsdiff]. ### security Due to the environment in which this was created and the need it fills, I -consider this a "security" program, albeit a very modest one. The code is -very small and easily reviewable -- the 2 programs that actually control -access when a user logs in total about 220 lines of code (about 90 lines -according to "sloccount"). +consider this a "security" program, albeit a very modest one. For the first person to find a security hole in it, defined as allowing a normal user (not the gitolite admin) to read a repo, or write/rewind a ref, @@ -99,10 +96,10 @@ or in Unix, perl, shell, etc.)... well I can't afford 1000 USD rewards like djb, so you'll have to settle for 1000 INR (Indian Rupees) as a "token" prize :-) -Update 2010-01-31: this security promise does not apply if you enable any of -the external command helpers (like rsync). It's probably quite secure, but I -just haven't thought about it enough to be able to make such promises, like I -can for the rest of "master". +However, there are a few optional features (which must be explicitly enabled +in the RC file) where I just haven't had the time to reason about security +thoroughly enough. Please read the comments in `conf/example.gitolite.rc` for +details, looking for the word "security". ---- diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index bcb281f..ddf5baa 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -108,6 +108,26 @@ $GIT_PATH=""; # -------------------------------------- + + +# ---------------------------------------------------------------------- +# SECURITY SENSITIVE SETTINGS +# +# Settings below this point may have security implications. That +# usually means that I have not thought hard enough about all the +# possible ways to crack security if these settings are enabled. + +# Please see details on each setting for specifics, if any. +# ---------------------------------------------------------------------- + + + +# -------------------------------------- +# EXTERNAL COMMAND HELPER -- HTPASSWD + +# security note: runs an external command (htpasswd) with specific arguments, +# including a user-chosen "password". + # if you want to enable the "htpasswd" command, give this the absolute path to # whatever file apache (etc) expect to find the passwords in. @@ -118,7 +138,10 @@ $HTPASSWD_FILE = ""; # -------------------------------------- # EXTERNAL COMMAND HELPER -- RSYNC -# + +# security note: runs an external command (rsync) with specific arguments, all +# presumably filled in correctly by the client-side rsync. + # base path of all the files that are accessible via rsync. Must be an # absolute path. Leave it undefined or set to the empty string to disable the # rsync helper. @@ -126,6 +149,21 @@ $RSYNC_BASE = ""; # $RSYNC_BASE = "/home/git/up-down"; # $RSYNC_BASE = "/tmp/up-down"; +# -------------------------------------- +# ALLOW REPO CONFIG TO USE WILDCARDS + +# security note: this used to in a separate "wildrepos" branch. You can +# create repositories based on wild cards, give "ownership" to the specific +# user who created it, allow him/her to hand out R and RW permissions to other +# users to collaborate, etc. This is powerful stuff, and I've made it as +# secure as I can, but it hasn't had the kind of rigorous line-by-line +# analysis that the old "master" branch had. + +# This has now been rolled into master, with all the functionality gated by +# this variable. Set this to 1 if you want to enable the wildrepos features. +# Please see doc/4-wildcard-repositories.mkd for details. +# $GL_WILDREPOS = 0; + # -------------------------------------- # per perl rules, this should be the last line in such a file: 1; diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index b2a1ba7..2cf7381 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -149,7 +149,7 @@ plain "git archive", because the Makefile adds a file called git clone git://sitaramc.indefero.net/sitaramc/gitolite.git cd gitolite make master.tar - # or maybe "make wildrepos.tar" or "make pu.tar" + # or maybe "make pu.tar" @@ -626,8 +626,7 @@ per-repo "gitconfig" settings. Please see `doc/2-admin.mkd` and #### repos named with wildcards -**This feature only exists in the "wildrepos" branch!** Please see -`doc/4-wildcard-repositories.mkd` for all the details. +Please see `doc/4-wildcard-repositories.mkd` for all the details. #### access control for external commands diff --git a/doc/4-wildcard-repositories.mkd b/doc/4-wildcard-repositories.mkd index 2324a02..aba4f66 100644 --- a/doc/4-wildcard-repositories.mkd +++ b/doc/4-wildcard-repositories.mkd @@ -2,15 +2,14 @@ ***IMPORTANT NOTE***: -This branch contains features that are likely to be much more brittle than the -"master" branch. Creating repositories based on wild cards, giving -"ownership" to the specific user who created it, allowing him/her to hand out -R and RW permissions to other users to collaborate, all these are possible. -And any of these could have a bug in it. +This feature may be somewhat "brittle" in terms of security. Creating +repositories based on wild cards, giving "ownership" to the specific user who +created it, allowing him/her to hand out R and RW permissions to other users +to collaborate, all these are possible. And any of these could have a bug in +it. I haven't found any yet, but that doesn't mean there aren't any. -"Brittle" also means some features in "master" may not work here. For -example, you cannot specify gitconfig values for a wildcard repo; it only -works for actual repos. +Also, there are some limitations. For example, you cannot specify gitconfig +values for a wildcard repo; it only works for actual repos. There may be other such missing features. Sometimes it's just not possible to make it work. Or it may be cumbersome enough that unless there are *no* @@ -25,7 +24,8 @@ In this document: * wildcard repos without creater name in them * side-note: line-anchored regexes * contrast with refexes - * handing out rights to wildcard-matached repos + * handing out rights to wildcard-matched repos + * setting a gitweb description for a wildcard-matched repo * reporting * other issues and discussion @@ -121,7 +121,7 @@ actually push such a branch! You can anchor it if you really care, by using `master$` instead of `master`, but anchoring is *not* the default for refexes.] -### Handing out rights to wildcard-matached repos +### Handing out rights to wildcard-matched repos In the examples above, we saw two special "user" names: READERS and WRITERS. The permissions they have are controlled by the config file, but ***who is @@ -166,7 +166,12 @@ The following points are important: * whoever you specify as "R" will match the special user READERS. "RW" will match WRITERS. -### Reporting +### setting a gitweb description for a wildcard-matched repo + +Similar to the getperm/setperm commands, there are the getdesc/setdesc +commands, thanks to Teemu. + +### reporting Remember the cool stuff you see when you just do `ssh git@server` (grep for "myrights" in `doc/3-faq-tips-etc.mkd` if you forgot, or go [here][mr]). @@ -177,7 +182,11 @@ This still works, except the format is a little more compressed to accommodate a new column (at the start) for "C" permissions, which indicate that you are allowed to *create* repos matching that pattern. -### Other issues and discussion +In addition, there is also the "expand" command, which takes any regex pattern +and returns you a list of all wildcard-created repos that you have access to +which fit that pattern. + +### other issues and discussion * *what if the repo name being pushed matches more than one pattern*? diff --git a/src/gitolite.pm b/src/gitolite.pm index 106624a..7f85315 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -33,7 +33,8 @@ our $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); # very simple patter # same as REPONAME, plus some common regex metas our $REPOPATT_PATT=qr(^\@?[0-9a-zA-Z][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/-]*$); -our $REPO_UMASK; +# these come from the RC file +our ($REPO_UMASK, $GL_WILDREPOS); our %repos; # ---------------------------------------------------------------------------- @@ -134,6 +135,8 @@ sub new_repo my ($repo, $hooks_dir, $creater) = @_; umask($REPO_UMASK); + die "wildrepos disabled, can't set creater $creater on new repo $repo\n" + if $creater and not $GL_WILDREPOS; system("mkdir", "-p", "$repo.git") and die "$ABRT mkdir $repo.git failed: $!\n"; # erm, note that's "and die" not "or die" as is normal in perl @@ -226,6 +229,7 @@ sub parse_acl # please update that also if the interface or the env vars change my ($GL_CONF_COMPILED, $repo, $c, $r, $w) = @_; + $c = $r = $w = "NOBODY" unless $GL_WILDREPOS; # void $r if same as $w (otherwise "readers" overrides "writers"; this is # the same problem that needed a sort sub for the Dumper in the compile @@ -251,6 +255,8 @@ sub parse_acl return unless $repo; return $ENV{GL_REPOPATT} = "" if $repos{$repo}; + # didn't find it, but wild is off? too bad, die!!! muahahaha + die "$repo not found in compiled config\n" unless $GL_WILDREPOS; # didn't find $repo in %repos, so it must be a wildcard-match case my @matched = grep { $repo =~ /^$_$/ } sort keys %repos; diff --git a/src/gl-auth-command b/src/gl-auth-command index 3628c19..f7f415a 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -24,7 +24,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); +our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR, $RSYNC_BASE, $HTPASSWD_FILE, $GL_WILDREPOS); # and these are set by gitolite.pm our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT, $REPOPATT_PATT); our %repos; @@ -110,6 +110,7 @@ my $CUSTOM_COMMANDS=qr/^\s*(expand|(get|set)(perms|desc))\s/; # commands is sort of a dead end for normal (git) processing if ($ENV{SSH_ORIGINAL_COMMAND} =~ $CUSTOM_COMMANDS) { + die "wildrepos disabled, sorry\n" unless $GL_WILDREPOS; my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; my ($verb, $repo) = ($cmd =~ /^\s*(\S+)\s+\/?(.*?)(?:.git)?$/); if ($repo =~ $REPONAME_PATT and $verb =~ /getperms|setperms/) { @@ -171,8 +172,8 @@ if ( -d "$repo_base_abs/$repo.git" ) { } else { &parse_acl($GL_CONF_COMPILED, $repo, $user, $user, $user); - # auto-vivify new repo if you have C access - if ( $repos{$repo}{C}{$user} || $repos{$repo}{C}{'@all'} ) { + # auto-vivify new repo if you have C access (and wildrepos is on) + if ( $GL_WILDREPOS and $repos{$repo}{C}{$user} || $repos{$repo}{C}{'@all'} ) { wrap_chdir("$repo_base_abs"); new_repo($repo, "$GL_ADMINDIR/src/hooks", $user); wrap_chdir($ENV{HOME}); diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 5c19ebb..e173a56 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -61,7 +61,7 @@ $Data::Dumper::Sortkeys = sub { return [ reverse sort keys %{$_[0]} ]; }; open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); # these are set by the "rc" file -our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $SHELL_USERS); +our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $SHELL_USERS, $GL_WILDREPOS); # and these are set by gitolite.pm our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN); @@ -133,7 +133,8 @@ sub expand_list for my $item (@list) { - die "$ABRT bad user or repo name $item\n" unless $item =~ $REPOPATT_PATT or $item =~ $USERNAME_PATT; + die "$ABRT bad user or repo name $item\n" + unless ($GL_WILDREPOS ? $item =~ $REPOPATT_PATT : $item =~ $REPONAME_PATT) or $item =~ $USERNAME_PATT; if ($item =~ /^@/) # nested group { die "$ABRT undefined group $item\n" unless $groups{$item}; @@ -206,6 +207,7 @@ sub parse_conf_file my $perms = $1; my @refs; @refs = split(' ', $2) if $2; my @users = split ' ', $3; + die "wildrepos disabled, cant use 'C' in config\n" if $perms eq 'C' and not $GL_WILDREPOS; # if no ref is given, this PERM applies to all refs @refs = qw(refs/.*) unless @refs; From b299ff09c37146a597c4b0af9eae5f2fe19bdbc0 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 6 Feb 2010 05:43:16 +0530 Subject: [PATCH 244/637] rsync: restrict the "path" part of the received command Although I have washed my hands off the security aspect if you use external commands, that doesn't mean I won't make them as tight as I can ;-) Right now, this is just a place holder -- if people use it and complain that the pattern is too restrictive, I'll change it. --- src/gitolite.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gitolite.pm b/src/gitolite.pm index 7f85315..0f84272 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -411,6 +411,8 @@ sub ext_cmd_rsync my $perm = "W"; $perm = "R" if $1; my $path = $2; + die "I dont like some of the characters in $path\n" unless $path =~ $REPOPATT_PATT; + # XXX make a better pattern for this if people complain ;-) die "I dont like absolute paths in $cmd\n" if $path =~ /^\//; die "I dont like '..' paths in $cmd\n" if $path =~ /\.\./; From a472bf30df3e87991eb92002ffef5386db0620a3 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 7 Feb 2010 13:09:16 +0530 Subject: [PATCH 245/637] compile: tighten up the 'git config' feature Gitolite allows you to set git repo options using the "config" keyword; see conf/example.conf for details and syntax. However, if you are in an installation where the repo admin does not (and should not) have shell access to the server, then allowing him to set arbitrary repo config options *may* be a security risk -- some config settings may allow executing arbitrary commands. This patch fixes it, introducing a new RC variable to control the behaviour. See conf/example.gitolite.rc for details --- conf/example.conf | 4 ++++ conf/example.gitolite.rc | 26 ++++++++++++++++++++++++++ src/gl-compile-conf | 6 ++++-- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index a17161e..01ea6d4 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -236,6 +236,10 @@ gitolite "Sitaram Chamarty" = "fast, secure, access control for git in a corpora # REPO SPECIFIC GITCONFIG # ----------------------- +# update 2010-02-06; this won't work unless the rc file has the right +# settings; please see comments around the variable $GL_GITCONFIG_KEYS in +# conf/example.gitolite.rc for details and security information. + # (Thanks to teemu dot matilainen at iki dot fi) # this should be specified within a "repo" stanza diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index ddf5baa..00e6a57 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -122,6 +122,32 @@ $GIT_PATH=""; +# -------------------------------------- +# ALLOW REPO ADMIN TO SET GITCONFIG KEYS +# +# Gitolite allows you to set git repo options using the "config" keyword; see +# conf/example.conf for details and syntax. +# +# However, if you are in an installation where the repo admin does not (and +# should not) have shell access to the server, then allowing him to set +# arbitrary repo config options *may* be a security risk -- some config +# settings may allow executing arbitrary commands. +# +# You have 3 choices. By default $GL_GITCONFIG_KEYS is left undefined, which +# completely disables this feature (meaning you cannot set git configs from +# the repo config). +# +# The second choice is to give it a space separated list of settings you +# consider safe. (These are actually treated as a set of regular expression +# patterns, and any one of them must match). For example: +# $GL_GITCONFIG_KEYS = "core\.logAllRefUpdates core\..*compression"; +# allows repo admins to set one of those 3 config keys (yes, that second +# pattern matches two settings from "man git-config", if you look) +# +# The third choice (which you may have guessed already if you're familiar with +# regular expressions) is to allow anything and everything: +# $GL_GITCONFIG_KEYS = ".*"; + # -------------------------------------- # EXTERNAL COMMAND HELPER -- HTPASSWD diff --git a/src/gl-compile-conf b/src/gl-compile-conf index e173a56..6a02107 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -61,7 +61,7 @@ $Data::Dumper::Sortkeys = sub { return [ reverse sort keys %{$_[0]} ]; }; open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); # these are set by the "rc" file -our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $SHELL_USERS, $GL_WILDREPOS); +our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $SHELL_USERS, $GL_WILDREPOS, $GL_GITCONFIG_KEYS); # and these are set by gitolite.pm our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN); @@ -288,7 +288,9 @@ sub parse_conf_file elsif (/^config (.+) = ?(.*)/) { my ($key, $value) = ($1, $2); - die "$WARN $fragment attempting to set repo configuration\n" if $fragment ne 'master'; + my @validkeys = split (' ', ($GL_GITCONFIG_KEYS || '')); + my @matched = grep { $key =~ /^$_$/ } @validkeys; + die "$ABRT git config $key not allowed\n" if (@matched < 1); for my $repo (@repos) # each repo in the current stanza { $repo_config{$repo}{$key} = $value; From 1f9fbfa71eaacec179d6d2694384531568adfdd0 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 7 Feb 2010 19:10:53 +0530 Subject: [PATCH 246/637] get "info" for users other than yourself if you have read access to the admin repo, you can say ssh git@server info user1 [...] Original idea and code by Karteek E. The motivation is to quickly and easily check what perms a user has. Technically nothing that you can't glean from the config file itself but it serves as a double check or a mild debugging aid perhaps. However note that the branch level rules are much more complex and they do not, as yet, have any such "helpful" aids. Life is like that sometimes. --- src/gitolite.pm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/gitolite.pm b/src/gitolite.pm index 0f84272..5638d05 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -352,6 +352,14 @@ sub special_cmd if ($cmd eq 'info') { &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user); print "you also have shell access\n\r" if $shell_allowed; + } elsif ($cmd =~ /^info\s+(.+)$/) { + my @otherusers = split ' ', $1; + &parse_acl($GL_CONF_COMPILED); + die "you can't ask for others' permissions\n" unless $repos{'gitolite-admin'}{'R'}{$user}; + for my $otheruser (@otherusers) { + warn("ignoring illegal username $otheruser\n"), next unless $otheruser =~ $USERNAME_PATT; + &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $otheruser); + } } elsif ($HTPASSWD_FILE and $cmd eq 'htpasswd') { &ext_cmd_htpasswd($HTPASSWD_FILE); } elsif ($RSYNC_BASE and $cmd =~ /^rsync /) { From a6b7928bc140a9f08b14af4865595440ad20cd6c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 8 Feb 2010 06:02:36 +0530 Subject: [PATCH 247/637] example RC: GL_GITCONFIG_KEYS was not showing up in easy install diff --- conf/example.gitolite.rc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index 00e6a57..f1e5b7e 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -133,9 +133,10 @@ $GIT_PATH=""; # arbitrary repo config options *may* be a security risk -- some config # settings may allow executing arbitrary commands. # -# You have 3 choices. By default $GL_GITCONFIG_KEYS is left undefined, which +# You have 3 choices. By default $GL_GITCONFIG_KEYS is left empty, which # completely disables this feature (meaning you cannot set git configs from # the repo config). +$GL_GITCONFIG_KEYS = ""; # # The second choice is to give it a space separated list of settings you # consider safe. (These are actually treated as a set of regular expression @@ -188,7 +189,7 @@ $RSYNC_BASE = ""; # This has now been rolled into master, with all the functionality gated by # this variable. Set this to 1 if you want to enable the wildrepos features. # Please see doc/4-wildcard-repositories.mkd for details. -# $GL_WILDREPOS = 0; +$GL_WILDREPOS = 0; # -------------------------------------- # per perl rules, this should be the last line in such a file: From 0e96c2f08a5f099ac4df668fa460baba1c7cb05a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 8 Feb 2010 09:59:27 +0530 Subject: [PATCH 248/637] example conf: doc on NAME/ being a refex etc was not clear --- conf/example.conf | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index 01ea6d4..e6aac3b 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -83,6 +83,7 @@ # If none of them match, it fails # what's a refex? a regex to match against the ref being updated (get it?) +# See next section for more on refexes # BASIC PERMISSIONS (repo level only; apply to all branches/tags in repo) @@ -114,13 +115,16 @@ repo @oss_repos repo @all RW+ = @admins -# ADVANCED PERMISSIONS USING REFEXES +# SPECIFYING AND USING A REFEX # - refexes are specified in perl regex syntax -# - refexes are matched without any anchoring, which means a refex like -# "refs/tags/v[0-9]" matches anything *containing* that pattern. There -# may be text before and after it (example: refs/tags/v4-r3p7), and it -# will still match +# - refexes are prefix-matched (they are internally anchored with "^" +# before being used), which means a refex like "refs/tags/v[0-9]" +# matches anything *starting with* that pattern. There may be text +# after it (example: refs/tags/v4-r3/p7), and it will still match + +# ADVANCED PERMISSIONS USING REFEXES + # - if no refex appears, the rule applies to all refs in that repo # - a refex is automatically prefixed by "refs/heads/" if it doesn't start # with "refs/" (so tags have to be explicitly named as @@ -191,7 +195,9 @@ repo foo # Notes -# - the "NAME/" is part of the syntax; think of it as a keyword if you like +# - the "NAME/" is part of the syntax; think of it as a keyword if you like. +# The rest of it is treated as a refex to match against each file being +# touched (see "SPECIFYING AND USING A REFEX" above for details) # - file/dir NAME-based restrictions are *in addition* to normal (branch-name # based) restrictions; they are not a *replacement* for them. This is why @@ -284,7 +290,7 @@ repo gitolite repo EXTCMD/rsync # grant permissions to files/dirs within the $RSYNC_BASE tree. A leading # NAME/ is required as a prefix; the actual path starts after that. Matching -# follows the same rules as elsewhere in gitolite. +# follows the same rules as given in "FILE/DIR NAME BASED RESTRICTIONS" above RW NAME/ = sitaram RW NAME/foo/ = user1 R NAME/bar/ = user2 From 72bac2a21a53c2c85fe69008a93e87ccd832810f Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 8 Feb 2010 16:38:28 +0530 Subject: [PATCH 249/637] dps: (distro packaging support) dont let install copy the sample conf --- src/gl-install | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/gl-install b/src/gl-install index 9159ed5..8a59569 100755 --- a/src/gl-install +++ b/src/gl-install @@ -56,13 +56,14 @@ for my $dir qw(conf doc keydir logs src) { system("cp -R src doc $GL_ADMINDIR"); unless (-f $GL_CONF) { - system("cp conf/example.conf $GL_CONF"); print < Date: Thu, 11 Feb 2010 09:03:26 +0530 Subject: [PATCH 250/637] doc/3 reorg; one section was getting too long! --- doc/3-faq-tips-etc.mkd | 561 +++++++++++++++++++++-------------------- 1 file changed, 290 insertions(+), 271 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 2cf7381..784f58b 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -10,28 +10,33 @@ In this document: * `@all` syntax for repos * umask setting * getting a tar file from a clone - * differences from gitosis - * simpler syntax - * two levels of access rights checking - * file/dir NAME based restrictions - * error checking the config file - * including config lines from other files - * delegating parts of the config file - * easier to specify gitweb "description" and gitweb/daemon access - * easier to link gitweb authorisation with gitolite - * better logging - * one user, many keys - * support for git installed outside default PATH - * what repos do I have access to? - * "exclude" (or "deny") rules - * "personal" branches - * custom hooks and custom git config - * repos named with wildcards - * access control for external commands + * features + * syntax and normal usage + * simpler syntax + * one user, many keys + * security, access control, and auditing + * two levels of access rights checking + * better logging + * "exclude" (or "deny") rules + * file/dir NAME based restrictions + * delegating parts of the config file + * convenience features + * what repos do I have access to? + * error checking the config file + * including config lines from other files + * support for git installed outside default PATH + * "personal" branches + * custom hooks and custom git config + * helping with gitweb + * easier to specify gitweb "description" and gitweb/daemon access + * easier to link gitweb authorisation with gitolite + * advanced features + * repos named with wildcards + * access control for external commands * design choices * keeping the parser and the access control separate -### common errors and mistakes +## common errors and mistakes * adding `repositories/` at the start of the repo name in the `git clone`. This error is typically made by the *admin* himself -- because he knows @@ -53,7 +58,7 @@ In this document: Please see doc/6-ssh-troubleshooting.mkd for what all this means. -### git version dependency +## git version dependency Here's a workaround for a version dependency that the normal flow of gitolite has. @@ -82,9 +87,9 @@ and then push. Something like: Once this is done, the repo is available for cloning by anyone else in the normal way, since it's not empty anymore. -### other errors, warnings, notes... +## other errors, warnings, notes... -#### ssh-copy-id +### ssh-copy-id don't have `ssh-copy-id`? This is broadly what that command does, if you want to replicate it manually. The input is your pubkey, typically @@ -107,7 +112,7 @@ typically) also must be `go-w`, but that needs root. And typically they're already set that way anyway. (Or if they're not, you've got bigger problems than gitolite install not working!)] -#### cloning an empty repo +### cloning an empty repo Cloning an empty repo is only possible with clients greater than 1.6.2. So at least one of your clients needs to have a recent git. Once at least one @@ -118,7 +123,7 @@ end hung up unexpectedly`. However, you can ignore this, since it doesn't seem to hurt anything. [Update 2009-09-14; this has been fixed in git 1.6.4.3] -#### `@all` syntax for repos +### `@all` syntax for repos There *is* a way to use the `@all` syntax for repos also, as described in `conf/example.conf`. However, there is an important difference between this @@ -132,12 +137,12 @@ and the old `@all` (for users): * This means that if you really want *all* repos, you'd better put this para at the **end** of the config file! -#### umask setting +### umask setting Gitweb not able to read your repos? You can change the umask for newly created repos to something more relaxed -- see the `~/.gitolite.rc` file -### getting a tar file from a clone +## getting a tar file from a clone You can clone the repo from github or indefero, then execute a make command to extract a tar file of the branch you want. Please use the make command, not a @@ -153,13 +158,9 @@ plain "git archive", because the Makefile adds a file called -### differences from gitosis +## features -Apart from the big ones listed in the top level README, and subjective ones -like "better config file format", there are some small, but significant and -concrete, differences from gitosis. - - +### syntax and normal usage #### simpler syntax @@ -204,6 +205,51 @@ do not worry that this causes some duplication or inefficiency. It doesn't See the "specify gitweb/daemon access" section below for one more example. +#### one user, many keys + +I have a laptop and a desktop I need to access the server from. I have +different private keys on them, but as far as gitolite is concerned both of +them should be treated as "sitaram". How does this work? + +In gitosis, the admin creates a single "sitaram.pub" containing one line for +each of my pubkeys. In gitolite, we keep them separate: "sitaram@laptop.pub" +and "sitaram@desktop.pub". The part before the "@" is the username, so +gitolite knows these two keys belong to the same person. + +Note that you don't say "sitaram@laptop" and so on in the **config** file -- +as far as the config file is concerned there's just **one** user called +"sitaram" -- so you only say "sitaram" there. + +I think this is easier to maintain if you have to delete or change one of +those keys. + +However, now that `sitaramc@gmail.com` is also a valid username, we need to +distinguish between `sitaramc@gmail.com.pub` and `sitaramc@desktop.pub`. We +do that by requiring that the multi-key suffix you use (like "desktop" and +"laptop") should not have a `"."` in it. If it does, it looks like an email +address. The following table lists sample pubkey filenames and the +corresponding derived usernames (which is what goes into the +`conf/gitolite.conf` file): + + * old style multikeys; not mistaken for emails because there is no "." in + hostname part + + sitaramc.pub sitaramc + sitaramc@laptop.pub sitaramc + sitaramc@desktop.pub sitaramc + + * new style, email keys; there is a "." in hostname part; so it's an email + address + + sitaramc@gmail.com.pub sitaramc@gmail.com + + * multikeys *with* email address + + sitaramc@gmail.com@laptop.pub sitaramc@gmail.com + sitaramc@gmail.com@desktop.pub sitaramc@gmail.com + +### security, access control, and auditing + #### two levels of access rights checking Gitolite has two levels of access checks. The **first check** is what I will @@ -239,6 +285,89 @@ any of the refexes match, the push succeeds. If none of them match, it fails. Gitolite also allows "exclude" or "deny" rules. See later in this document for details. +Apart from the big ones listed in the top level README, and subjective ones +like "better config file format", gitolite has evolved to have many useful +fearures than the original goal of "gitosis + branch-level access control". + + + +#### better logging + +If you have been too liberal with the permission to rewind, it has built-in +logging as an emergency fallback if someone goes too far, or for audit +purposes [`*`]. The logfile names and location are configurable, and can +include the year/month/day etc in the filename for easy archival or further +processing. The log file even tells you which pattern in the config file +matched to allow that specific access to proceed. + +> [`*`] setting `core.logAllRefUpdates true` does provide a safety net +> against over-zealous rewinds, but it does not tell you "who". And +> strangely, management does not seem to share the view that "blame" is just +> a synonym for "annotate" ;-)] + +The log lines look like this: + + 2009-09-19.10:24:37 + b4e76569659939 4fb16f2a88d8b5 myrepo refs/heads/master user2 refs/heads/master + +The "+" at the start indicates a non-fast forward update, in this case from +b4e76569659939 to 4fb16f2a88d8b5. So b4e76569659939 is the one to restore! +Can it get easier? + +The other parts of the log line are the name of the repo, the refname being +updated, the user updating it, and the refex pattern (from the config file) +that matched, in case you need to debug the config file itself. + + + +#### "exclude" (or "deny") rules + +Here is an illustrative explanation of "deny" rules. However, please be sure +to read the "DENY/EXCLUDE RULES" section in `conf/example.conf` for important +notes/caveats before using "deny" rules. + +Take a look at the following snippet, which *seems* to say that "bruce" can +write versioned tags (anything containing `refs/tags/v[0-9]`), but the other +staffers can't: + + @staff = bruce whitfield martin + [... and later ...] + RW refs/tags/v[0-9] = bruce + RW refs/tags = @staff + +But that's not how the matching works. As long as any refex matches the +refname being updated, it's a "yes". Since the second refex (which says +"anything containing `refs/tags`") is a superset of the first one, it lets +anyone on `@staff` create versioned tags, not just Bruce. + +One way to fix this is to allow "excludes" -- some changes in syntax, combined +with a rigorous, ordered, interpretation would do it. + +Let's recap the **existing semantics**: + +> the first matching refex that has the permission you're looking for (`W` +> or `+`), results in success. A fallthrough results in failure + +Here are the **new semantics**, with changes from the "main" one in bold: + +> the first matching refex that has the permission you're looking for (`W` +> or `+`) **or a minus (`-`)**, results in success **or failure, +> respectively**. A fallthrough **also** results in failure + +So the example we started with becomes, if you use "deny" rules: + + RW refs/tags/v[0-9] = bruce + - refs/tags/v[0-9] = @staff + RW refs/tags = @staff + +And here's how it works: + + * for non-version tags, only the 3rd rule matches, so anyone on staff can + push them + * for version tags by bruce, the first rule matches so he can push them + * for version tags by staffers *other than bruce*, the second rule matches + before the third one, and it has a `-` as the permission, so the push + fails + #### file/dir NAME based restrictions In addition to branch-name based restrictions, gitolite also allows you to @@ -248,6 +377,46 @@ changed, treating each filename as a "ref" to be matched. Please see `conf/example.conf` for syntax and examples. +#### delegating parts of the config file + +You can now split up the config file and delegate the authority to specify +access control for their own pieces. See +[doc/5-delegation.mkd](http://github.com/sitaramc/gitolite/blob/pu/doc/5-delegation.mkd) +for details. + + + +### convenience features + +#### what repos do I have access to? + +Sometimes there are too many repos, maybe even named similarly, or with the +potential for typos, confusion about hyphens/underscores or upper/lower case, +etc. You'd just like a simple way to know what repos you have access to. + +Easy! Just use ssh and try to log in as if you were attempting to get a +shell: + + $ ssh gitolite info + PTY allocation request failed on channel 0 + hello sitaram, the gitolite version here is v0.6-17-g94ed189 + you have the following permissions: + R W Anu-WSD + R ROtest + R W SecureBrowse + R W entrans + R W git-notes + R W gitolite + R W gitolite-admin + R W indic_web_input + R W proxy + @ @ testing + R W vkc + +Note that until this version, we used to put out an ugly `need +SSH_ORIGINAL_COMMAND` error, just like gitosis used to. All we did is put +that code path to better use :-) + #### error checking the config file gitosis does not do any. I just found out that if you mis-spell `members` as @@ -261,14 +430,94 @@ you know right away. See the entry under "INCLUDE SOME OTHER FILE" in `conf/example.conf`. -#### delegating parts of the config file +#### support for git installed outside default PATH -You can now split up the config file and delegate the authority to specify -access control for their own pieces. See -[doc/5-delegation.mkd](http://github.com/sitaramc/gitolite/blob/pu/doc/5-delegation.mkd) -for details. +The normal solution is to add to the system default PATH somehow, either by +munging `/etc/profile` or by enabling `PermitUserEnvironment` in +`/etc/ssh/sshd_config` and then setting the PATH in `~/.ssh/.environment`. +All these are security risks because they allow a lot more than just you and +your git install :-) - +And if you don't have root, you can't do this anyway. + +The only solution till now has been to ask every client to set the config +parameters `remote..receivepack` and `remote..uploadpack`. But +telling *every* client to do so is a pain... + +Gitolite lets you specify the directory in which git binaries are to be found, +via a new variable (`$GIT_PATH`) in the "rc" file. If this variable is +non-empty, it will be appended to the PATH environment variable before +attempting to run git stuff. + +Very easy, very simple, and completely transparent to the users :-) + + + +#### "personal" branches + +"personal" branches are great for corporate environments, where +unauthenticated pull/clone is a no-no. Since a dev workstation cannot do +authentication, even work shared just between 2 devs has to go *via* the +server. This causes the same branch name clutter as in a centralised VCS, +plus setting up permissions for this becomes a chore for the admin. + +gitolite lets you define a "personal" or "scratch" namespace prefix for +each developer (e.g., `refs/personal//*`), with full +permissions for that dev and read-only for everyone else. And you get +this without adding a single line to the access config file -- pretty +much fire and forget as far as the admin is concerned, even if there is +constant churn in the project teams. + +Not bad for something that took just *one* line of code to implement. +And that's one clean, readable, line, by the way ;-) + +The admin would set `$PERSONAL_BRANCH_PREFIX` in the rc file and communicate +this to all users. It could be something like `refs/heads/personal`, which +means all such branches will show up in `git branch` lookups and `git clone` +will fetch them. Or he could use, say, `refs/personal`, which means it won't +show up in any normal "branch-y" commands and stuff, and generally be much +less noisy. + +**Note that a user who has NO write access cannot have personal branches**; if +you read the section (above) on "two levels of access rights checking" you'll +understand why. + +For instance, in the following example, `user3` cannot push to any +`refs/heads/personal/user3/*` branches because the first level check stops him +cold: + + # assume $PERSONAL = 'refs/heads/personal' in ~/.gitolite.rc + repo myrepo + RW+ master = sitaram + RW+ release = qa_guy + RW = user1 user2 + R = user3 + +If we relax that check, *any* access becomes *write* access. Yes it will be +caught later, by the hook, but it's good practice to catch things in multiple +places. + +If you want `user3` to have his own personal branch, but without write access +to any of the "real" branches (like "master", "release", etc.), just use a +dummy branch. Choose a name that will never exist in practice, or even if +someone creates it, we don't care. For example, this will get him past the +first check: + + RW dummy = user3 + +Just don't *show* the user this config file; it might sound insulting :-) + +#### custom hooks and custom git config + +You can specify hooks that you want to propagate to all repos, as well as +per-repo "gitconfig" settings. Please see `doc/2-admin.mkd` and +`conf/example.conf` for details. + +### helping with gitweb + +Although gitweb is a completely separate program, gitolite can do quite a +lot to help you manage gitweb access as well; once the initial setup is +complete, you can do it all from within the gitolite config file! #### easier to specify gitweb "description" and gitweb/daemon access @@ -392,237 +641,7 @@ already done and we just use it! [leho]: http://leho.kraav.com/news/2009/10/27/using-apache-authentication-with-gitweb-gitosis-repository-access-control/ -#### better logging - -If you have been too liberal with the permission to rewind, it has built-in -logging as an emergency fallback if someone goes too far, or for audit -purposes [`*`]. The logfile names and location are configurable, and can -include the year/month/day etc in the filename for easy archival or further -processing. The log file even tells you which pattern in the config file -matched to allow that specific access to proceed. - -> [`*`] setting `core.logAllRefUpdates true` does provide a safety net -> against over-zealous rewinds, but it does not tell you "who". And -> strangely, management does not seem to share the view that "blame" is just -> a synonym for "annotate" ;-)] - -The log lines look like this: - - 2009-09-19.10:24:37 + b4e76569659939 4fb16f2a88d8b5 myrepo refs/heads/master user2 refs/heads/master - -The "+" at the start indicates a non-fast forward update, in this case from -b4e76569659939 to 4fb16f2a88d8b5. So b4e76569659939 is the one to restore! -Can it get easier? - -The other parts of the log line are the name of the repo, the refname being -updated, the user updating it, and the refex pattern (from the config file) -that matched, in case you need to debug the config file itself. - - - -#### one user, many keys - -I have a laptop and a desktop I need to access the server from. I have -different private keys on them, but as far as gitolite is concerned both of -them should be treated as "sitaram". How does this work? - -In gitosis, the admin creates a single "sitaram.pub" containing one line for -each of my pubkeys. In gitolite, we keep them separate: "sitaram@laptop.pub" -and "sitaram@desktop.pub". The part before the "@" is the username, so -gitolite knows these two keys belong to the same person. - -Note that you don't say "sitaram@laptop" and so on in the **config** file -- -as far as the config file is concerned there's just **one** user called -"sitaram" -- so you only say "sitaram" there. - -I think this is easier to maintain if you have to delete or change one of -those keys. - -However, now that `sitaramc@gmail.com` is also a valid username, we need to -distinguish between `sitaramc@gmail.com.pub` and `sitaramc@desktop.pub`. We -do that by requiring that the multi-key suffix you use (like "desktop" and -"laptop") should not have a `"."` in it. If it does, it looks like an email -address. The following table lists sample pubkey filenames and the -corresponding derived usernames (which is what goes into the -`conf/gitolite.conf` file): - - * old style multikeys; not mistaken for emails because there is no "." in - hostname part - - sitaramc.pub sitaramc - sitaramc@laptop.pub sitaramc - sitaramc@desktop.pub sitaramc - - * new style, email keys; there is a "." in hostname part; so it's an email - address - - sitaramc@gmail.com.pub sitaramc@gmail.com - - * multikeys *with* email address - - sitaramc@gmail.com@laptop.pub sitaramc@gmail.com - sitaramc@gmail.com@desktop.pub sitaramc@gmail.com - -#### support for git installed outside default PATH - -The normal solution is to add to the system default PATH somehow, either by -munging `/etc/profile` or by enabling `PermitUserEnvironment` in -`/etc/ssh/sshd_config` and then setting the PATH in `~/.ssh/.environment`. -All these are security risks because they allow a lot more than just you and -your git install :-) - -And if you don't have root, you can't do this anyway. - -The only solution till now has been to ask every client to set the config -parameters `remote..receivepack` and `remote..uploadpack`. But -telling *every* client to do so is a pain... - -Gitolite lets you specify the directory in which git binaries are to be found, -via a new variable (`$GIT_PATH`) in the "rc" file. If this variable is -non-empty, it will be appended to the PATH environment variable before -attempting to run git stuff. - -Very easy, very simple, and completely transparent to the users :-) - - - -#### what repos do I have access to? - -Sometimes there are too many repos, maybe even named similarly, or with the -potential for typos, confusion about hyphens/underscores or upper/lower case, -etc. You'd just like a simple way to know what repos you have access to. - -Easy! Just use ssh and try to log in as if you were attempting to get a -shell: - - $ ssh gitolite info - PTY allocation request failed on channel 0 - hello sitaram, the gitolite version here is v0.6-17-g94ed189 - you have the following permissions: - R W Anu-WSD - R ROtest - R W SecureBrowse - R W entrans - R W git-notes - R W gitolite - R W gitolite-admin - R W indic_web_input - R W proxy - @ @ testing - R W vkc - -Note that until this version, we used to put out an ugly `need -SSH_ORIGINAL_COMMAND` error, just like gitosis used to. All we did is put -that code path to better use :-) - -#### "exclude" (or "deny") rules - -Here is an illustrative explanation of "deny" rules. However, please be sure -to read the "DENY/EXCLUDE RULES" section in `conf/example.conf` for important -notes/caveats before using "deny" rules. - -Take a look at the following snippet, which *seems* to say that "bruce" can -write versioned tags (anything containing `refs/tags/v[0-9]`), but the other -staffers can't: - - @staff = bruce whitfield martin - [... and later ...] - RW refs/tags/v[0-9] = bruce - RW refs/tags = @staff - -But that's not how the matching works. As long as any refex matches the -refname being updated, it's a "yes". Since the second refex (which says -"anything containing `refs/tags`") is a superset of the first one, it lets -anyone on `@staff` create versioned tags, not just Bruce. - -One way to fix this is to allow "excludes" -- some changes in syntax, combined -with a rigorous, ordered, interpretation would do it. - -Let's recap the **existing semantics**: - -> the first matching refex that has the permission you're looking for (`W` -> or `+`), results in success. A fallthrough results in failure - -Here are the **new semantics**, with changes from the "main" one in bold: - -> the first matching refex that has the permission you're looking for (`W` -> or `+`) **or a minus (`-`)**, results in success **or failure, -> respectively**. A fallthrough **also** results in failure - -So the example we started with becomes, if you use "deny" rules: - - RW refs/tags/v[0-9] = bruce - - refs/tags/v[0-9] = @staff - RW refs/tags = @staff - -And here's how it works: - - * for non-version tags, only the 3rd rule matches, so anyone on staff can - push them - * for version tags by bruce, the first rule matches so he can push them - * for version tags by staffers *other than bruce*, the second rule matches - before the third one, and it has a `-` as the permission, so the push - fails - -#### "personal" branches - -"personal" branches are great for corporate environments, where -unauthenticated pull/clone is a no-no. Since a dev workstation cannot do -authentication, even work shared just between 2 devs has to go *via* the -server. This causes the same branch name clutter as in a centralised VCS, -plus setting up permissions for this becomes a chore for the admin. - -gitolite lets you define a "personal" or "scratch" namespace prefix for -each developer (e.g., `refs/personal//*`), with full -permissions for that dev and read-only for everyone else. And you get -this without adding a single line to the access config file -- pretty -much fire and forget as far as the admin is concerned, even if there is -constant churn in the project teams. - -Not bad for something that took just *one* line of code to implement. -And that's one clean, readable, line, by the way ;-) - -The admin would set `$PERSONAL_BRANCH_PREFIX` in the rc file and communicate -this to all users. It could be something like `refs/heads/personal`, which -means all such branches will show up in `git branch` lookups and `git clone` -will fetch them. Or he could use, say, `refs/personal`, which means it won't -show up in any normal "branch-y" commands and stuff, and generally be much -less noisy. - -**Note that a user who has NO write access cannot have personal branches**; if -you read the section (above) on "two levels of access rights checking" you'll -understand why. - -For instance, in the following example, `user3` cannot push to any -`refs/heads/personal/user3/*` branches because the first level check stops him -cold: - - # assume $PERSONAL = 'refs/heads/personal' in ~/.gitolite.rc - repo myrepo - RW+ master = sitaram - RW+ release = qa_guy - RW = user1 user2 - R = user3 - -If we relax that check, *any* access becomes *write* access. Yes it will be -caught later, by the hook, but it's good practice to catch things in multiple -places. - -If you want `user3` to have his own personal branch, but without write access -to any of the "real" branches (like "master", "release", etc.), just use a -dummy branch. Choose a name that will never exist in practice, or even if -someone creates it, we don't care. For example, this will get him past the -first check: - - RW dummy = user3 - -Just don't *show* the user this config file; it might sound insulting :-) - -#### custom hooks and custom git config - -You can specify hooks that you want to propagate to all repos, as well as -per-repo "gitconfig" settings. Please see `doc/2-admin.mkd` and -`conf/example.conf` for details. +### advanced features #### repos named with wildcards @@ -642,9 +661,9 @@ who has shell access anyway). Please see the config files (both of them) for examples and usage. -### design choices +## design choices -#### keeping the parser and the access control separate +### keeping the parser and the access control separate There are two programs concerned with access control: From 65b8c0c48a9c954533ee3ececb1401749b323023 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 9 Feb 2010 17:00:01 +0530 Subject: [PATCH 251/637] make $bindir absolute --- src/gl-auth-command | 1 + src/gl-compile-conf | 1 + 2 files changed, 2 insertions(+) diff --git a/src/gl-auth-command b/src/gl-auth-command index f7f415a..d1a7a84 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -32,6 +32,7 @@ our %repos; # the common setup module is in the same directory as this running program is my $bindir = $0; $bindir =~ s/\/[^\/]+$//; +$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//; require "$bindir/gitolite.pm"; # ask where the rc file is, get it, and "do" it diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 6a02107..42c0143 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -68,6 +68,7 @@ our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTION # the common setup module is in the same directory as this running program is my $bindir = $0; $bindir =~ s/\/[^\/]+$//; +$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//; require "$bindir/gitolite.pm"; # ask where the rc file is, get it, and "do" it From 74d70e3b9f36455dc14331003e55a977551b6933 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 9 Feb 2010 19:37:37 +0530 Subject: [PATCH 252/637] move hooks out of src src/hooks is now hooks/common src/ga... is now hooks/gitolite-admin/post-update --- {src/hooks => hooks/common}/update | 0 .../gitolite-admin/post-update | 0 src/gl-auth-command | 2 +- src/gl-compile-conf | 2 +- src/gl-easy-install | 6 +++--- src/gl-install | 10 +++++----- 6 files changed, 10 insertions(+), 10 deletions(-) rename {src/hooks => hooks/common}/update (100%) rename src/ga-post-update-hook => hooks/gitolite-admin/post-update (100%) diff --git a/src/hooks/update b/hooks/common/update similarity index 100% rename from src/hooks/update rename to hooks/common/update diff --git a/src/ga-post-update-hook b/hooks/gitolite-admin/post-update similarity index 100% rename from src/ga-post-update-hook rename to hooks/gitolite-admin/post-update diff --git a/src/gl-auth-command b/src/gl-auth-command index d1a7a84..c18d880 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -176,7 +176,7 @@ if ( -d "$repo_base_abs/$repo.git" ) { # auto-vivify new repo if you have C access (and wildrepos is on) if ( $GL_WILDREPOS and $repos{$repo}{C}{$user} || $repos{$repo}{C}{'@all'} ) { wrap_chdir("$repo_base_abs"); - new_repo($repo, "$GL_ADMINDIR/src/hooks", $user); + new_repo($repo, "$GL_ADMINDIR/hooks/common", $user); wrap_chdir($ENV{HOME}); } } diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 42c0143..e7fcb9c 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -396,7 +396,7 @@ for my $repo (sort keys %repos) { next if $repo =~ m(^EXTCMD/); # these are not real repos unless (-d "$repo.git") { print STDERR "creating $repo...\n"; - new_repo($repo, "$GL_ADMINDIR/src/hooks"); + new_repo($repo, "$GL_ADMINDIR/hooks/common"); # new_repo would have chdir'd us away; come back wrap_chdir("$repo_base_abs"); } diff --git a/src/gl-easy-install b/src/gl-easy-install index 35c31e2..a8dc013 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -169,12 +169,12 @@ basic_sanity() { # MANUAL: make sure you're in the gitolite directory, at the top level. # The following files should all be visible: - ls src/ga-post-update-hook \ + ls hooks/gitolite-admin/post-update \ + hooks/common/update \ src/gitolite.pm \ src/gl-install \ src/gl-auth-command \ src/gl-compile-conf \ - src/hooks/update \ conf/example.conf \ conf/example.gitolite.rc >/dev/null || die "cant find at least some files in gitolite sources/config; aborting" @@ -282,7 +282,7 @@ copy_gl() { # have to create the directory first. ssh -p $port $user@$host mkdir -p gitolite-install - scp $quiet -P $port -r src conf doc $user@$host:gitolite-install/ + scp $quiet -P $port -r src conf doc hooks $user@$host:gitolite-install/ rm -f src/VERSION # MANUAL: now log on to the server (ssh git@server) and get a command diff --git a/src/gl-install b/src/gl-install index 8a59569..dbebe22 100755 --- a/src/gl-install +++ b/src/gl-install @@ -48,12 +48,12 @@ my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" wrap_mkdir($repo_base_abs); wrap_mkdir($GL_ADMINDIR); # mkdir $GL_ADMINDIR's subdirs -for my $dir qw(conf doc keydir logs src) { +for my $dir qw(conf doc keydir logs src hooks hooks/common hooks/gitolite-admin) { wrap_mkdir("$GL_ADMINDIR/$dir"); } # "src" and "doc" will be overwritten on each install, but not conf -system("cp -R src doc $GL_ADMINDIR"); +system("cp -R src doc hooks $GL_ADMINDIR"); unless (-f $GL_CONF) { print < Date: Wed, 10 Feb 2010 09:24:27 +0530 Subject: [PATCH 253/637] install: initial create of glrc should not assume PWD is project root make it work regardless of how it is invoked, though we *do* assume ../conf/example.gitolite.rc exists --- src/gl-install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl-install b/src/gl-install index dbebe22..01a2317 100755 --- a/src/gl-install +++ b/src/gl-install @@ -31,7 +31,7 @@ require "$bindir/gitolite.pm"; unless ($ENV{GL_RC}) { # doesn't exist. Copy it across, tell user to edit it and come back my $glrc = $ENV{HOME} . "/.gitolite.rc"; - system("cp conf/example.gitolite.rc $glrc"); + system("cp $bindir/../conf/example.gitolite.rc $glrc"); print "created $glrc\n"; print "please edit it, change the paths if you wish to, and RERUN THIS SCRIPT\n"; exit; From 927b6bb1aaf6d1010d2d17261d37af6247309900 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 10 Feb 2010 10:29:49 +0530 Subject: [PATCH 254/637] dps: make install aware of distro-based setup gl-install copies - the initial rc file to ~/.gitolite.rc if it doesn't exist - src and hooks to GL_ADMINDIR Make it aware of a package-based setup sequence, where the above two change somewhat; see code diff. This should be the last bit of change needed to prepare gitolite setup so that a distro package maintainer does not have to fiddle too much with code inside. (What remains is docs, and a setup script for server-side use, to replace the latter part of easy install) --- src/gl-install | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/gl-install b/src/gl-install index 01a2317..13798ae 100755 --- a/src/gl-install +++ b/src/gl-install @@ -3,7 +3,7 @@ use strict; use warnings; -our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF, $GIT_PATH); +our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF, $GIT_PATH, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS); # setup quiet mode if asked; please do not use this when running manually open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); @@ -31,7 +31,11 @@ require "$bindir/gitolite.pm"; unless ($ENV{GL_RC}) { # doesn't exist. Copy it across, tell user to edit it and come back my $glrc = $ENV{HOME} . "/.gitolite.rc"; - system("cp $bindir/../conf/example.gitolite.rc $glrc"); + if ($GL_PACKAGE_CONF) { + system("cp $GL_PACKAGE_CONF/example.gitolite.rc $glrc"); + } else { + system("cp $bindir/../conf/example.gitolite.rc $glrc"); + } print "created $glrc\n"; print "please edit it, change the paths if you wish to, and RERUN THIS SCRIPT\n"; exit; @@ -49,13 +53,18 @@ wrap_mkdir($repo_base_abs); wrap_mkdir($GL_ADMINDIR); # mkdir $GL_ADMINDIR's subdirs for my $dir qw(conf doc keydir logs src hooks hooks/common hooks/gitolite-admin) { + # some of them will stay empty; too lazy to fix right now ;-) wrap_mkdir("$GL_ADMINDIR/$dir"); } # "src" and "doc" will be overwritten on each install, but not conf -system("cp -R src doc hooks $GL_ADMINDIR"); +if ($GL_PACKAGE_HOOKS) { + system("cp -R $GL_PACKAGE_HOOKS $GL_ADMINDIR"); +} else { + system("cp -R $bindir/../src $bindir/../doc $bindir/../hooks $GL_ADMINDIR"); +} -unless (-f $GL_CONF) { +unless (-f $GL_CONF or $GL_PACKAGE_CONF) { print < Date: Mon, 8 Feb 2010 20:01:14 +0530 Subject: [PATCH 255/637] added server-side setup script --- src/gl-setup | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100755 src/gl-setup diff --git a/src/gl-setup b/src/gl-setup new file mode 100755 index 0000000..b48975c --- /dev/null +++ b/src/gl-setup @@ -0,0 +1,84 @@ +#!/bin/sh + +GL_PACKAGE_CONF=/tmp/share/gitolite/conf +# must be the same as the value for the same variable in +# $GL_PACKAGE_CONF/example.gitolite.rc. Sorry about the catch-22 :) + +# TODO need to fix for portability to ksh and so on +# TODO need to get the version in there somehow + +# This program is meant to be completely non-interactive, suitable for running +# server-side from a "post RPM/DEB install" script, or manually by users. +# Please see the doc/0-user-setup.mkd for details. + +# usage: +# $0 [foo.pub] + +# The pubkey filename must end with ".pub" and is mandatory when you first run +# this command. Otherwise it is optional, and can be used to override a +# pubkey file if you happen to have lost all gitolite-access to the repos (but +# do have shell access via some other means) + +die() { echo "$@"; echo death at line number ${BASH_LINENO[0]}; exit 1; } + +pubkey_file=$1 +admin_name= +if [[ -n $pubkey_file ]] +then + [[ $pubkey_file =~ .pub$ ]] || die "$pubkey_file must end in .pub" + [[ -f $pubkey_file ]] || die "cant find $pubkey_file" + admin_name=$(basename $pubkey_file .pub) +fi + +if [[ -f ~/.gitolite.rc ]] +then + perl -ne 's/^\s+//; s/[\s=].*//; print if /^\$/;' < $GL_PACKAGE_CONF/example.gitolite.rc | sort > .newvars + perl -ne 's/^\s+//; s/[\s=].*//; print if /^\$/;' < ~/.gitolite.rc | sort > .oldvars + comm -23 .newvars .oldvars > .diffvars + if [[ -s .diffvars ]] + then + cp $GL_PACKAGE_CONF/example.gitolite.rc ~/.gitolite.rc.new + echo new version of the rc file saved in ~/.gitolite.rc.new + echo + echo please update ~/.gitolite.rc manually if you need features + echo controlled by any of the following variables: + echo ---- + sed -e 's/^/ /' < .diffvars + echo ---- + fi + rm -f .newvars .oldvars .diffvars +else + [[ -n $pubkey_file ]] || die "looks like first run -- I need a pubkey file" + cp $GL_PACKAGE_CONF/example.gitolite.rc ~/.gitolite.rc +fi + +gl-install -q + +GL_ADMINDIR=$(cd;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR') +REPO_BASE=$( cd;perl -e 'do ".gitolite.rc"; print $REPO_BASE' ) + +[[ -f $GL_ADMINDIR/conf/gitolite.conf ]] || { + echo DEBUG NO DEFAULT CONF FOUND .. CREATING .. >&2 + cat < $GL_ADMINDIR/conf/gitolite.conf + repo gitolite-admin + RW+ = $admin_name + + repo testing + RW+ = @all +EOF +} +cp $pubkey_file $GL_ADMINDIR/keydir + +touch $HOME/.ssh/authorized_keys +gl-compile-conf -q + +# setup push-to-admin +od=$PWD +cd; cd $REPO_BASE/gitolite-admin.git +GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir +GIT_WORK_TREE=$GL_ADMINDIR git diff --cached --quiet || GIT_WORK_TREE=$GL_ADMINDIR git commit -am start +cd $od + +# now that the admin repo is created, you have to set the hooks properly; best +# do it by running install again +gl-install -q From 06d8ab4c18d158577cb1fe561a062fc3543c4f63 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 10 Feb 2010 12:35:01 +0530 Subject: [PATCH 256/637] make VERSION work in both types of setups The old install method will now use conf/VERSION instead of src/VERSION everywhere. The new one, if you use the builtin make file to "make branch.tar" will also create just such a file --- Makefile | 8 ++++---- src/gitolite.pm | 4 ++-- src/gl-easy-install | 7 +++---- src/gl-install | 3 ++- src/gl-setup | 3 +-- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 730fa72..7780900 100644 --- a/Makefile +++ b/Makefile @@ -7,10 +7,10 @@ # Note: I'm not sure if that "-r" is a GNU tar extension... .GITOLITE-VERSION: - @touch .GITOLITE-VERSION + @touch conf/VERSION %.tar: .GITOLITE-VERSION - git describe --all --long $* > .GITOLITE-VERSION + git describe --tags --long $* > conf/VERSION git archive $* > $@ - tar -r -f $@ .GITOLITE-VERSION - rm .GITOLITE-VERSION + tar -r -f $@ conf/VERSION + rm conf/VERSION diff --git a/src/gitolite.pm b/src/gitolite.pm index 5638d05..15728cc 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -34,7 +34,7 @@ our $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); # very simple patter our $REPOPATT_PATT=qr(^\@?[0-9a-zA-Z][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/-]*$); # these come from the RC file -our ($REPO_UMASK, $GL_WILDREPOS); +our ($REPO_UMASK, $GL_WILDREPOS, $GL_PACKAGE_CONF); our %repos; # ---------------------------------------------------------------------------- @@ -282,7 +282,7 @@ sub report_basic # send back some useful info if no command was given print "hello $user, the gitolite version here is "; - system("cat", "$GL_ADMINDIR/src/VERSION"); + system("cat", ($GL_PACKAGE_CONF || "$GL_ADMINDIR/conf") . "/VERSION"); print "\ryou have the following permissions:\n\r"; for my $r (sort keys %repos) { my $perm .= ( $repos{$r}{C}{'@all'} ? ' @' : ( $repos{$r}{C}{$user} ? ' C' : ' ' ) ); diff --git a/src/gl-easy-install b/src/gl-easy-install index a8dc013..989f3a6 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -196,12 +196,12 @@ version_info() { # MANUAL: if needed, make a note of the version you are upgrading from, and to # record which version is being sent across; we assume it's HEAD - git describe --tags --long HEAD 2>/dev/null > src/VERSION || echo '(unknown)' > src/VERSION + git describe --tags --long HEAD 2>/dev/null > conf/VERSION || echo '(unknown)' > conf/VERSION # what was the old version there? export upgrade_details="you are upgrading \ - $(ssh -p $port $user@$host cat gitolite-install/src/VERSION 2>/dev/null || echo '(or installing first-time)' ) \ - to $(cat src/VERSION)" + $(ssh -p $port $user@$host cat gitolite-install/conf/VERSION 2>/dev/null || echo '(or installing first-time)' ) \ + to $(cat conf/VERSION)" prompt "$upgrade_details" "$v_upgrade_details" } @@ -283,7 +283,6 @@ copy_gl() { ssh -p $port $user@$host mkdir -p gitolite-install scp $quiet -P $port -r src conf doc hooks $user@$host:gitolite-install/ - rm -f src/VERSION # MANUAL: now log on to the server (ssh git@server) and get a command # line. This step is for your convenience; the script does it all from diff --git a/src/gl-install b/src/gl-install index 13798ae..485683a 100755 --- a/src/gl-install +++ b/src/gl-install @@ -62,6 +62,7 @@ if ($GL_PACKAGE_HOOKS) { system("cp -R $GL_PACKAGE_HOOKS $GL_ADMINDIR"); } else { system("cp -R $bindir/../src $bindir/../doc $bindir/../hooks $GL_ADMINDIR"); + system("cp $bindir/../conf/VERSION $GL_ADMINDIR/conf"); } unless (-f $GL_CONF or $GL_PACKAGE_CONF) { @@ -95,7 +96,7 @@ if ( -d "gitolite-admin.git/hooks" ) { } # fixup program renames -for my $oldname qw(pta-hook.sh conf-convert.pl 00-easy-install.sh 99-emergency-addkey.sh install.pl update-hook.pl hooks/update ga-post-update-hook) { +for my $oldname qw(pta-hook.sh conf-convert.pl 00-easy-install.sh 99-emergency-addkey.sh install.pl update-hook.pl hooks/update ga-post-update-hook VERSION) { unlink "$GL_ADMINDIR/src/$oldname"; unlink "$ENV{HOME}/gitolite-install/src/$oldname"; } diff --git a/src/gl-setup b/src/gl-setup index b48975c..25e4e0f 100755 --- a/src/gl-setup +++ b/src/gl-setup @@ -58,7 +58,6 @@ GL_ADMINDIR=$(cd;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR') REPO_BASE=$( cd;perl -e 'do ".gitolite.rc"; print $REPO_BASE' ) [[ -f $GL_ADMINDIR/conf/gitolite.conf ]] || { - echo DEBUG NO DEFAULT CONF FOUND .. CREATING .. >&2 cat < $GL_ADMINDIR/conf/gitolite.conf repo gitolite-admin RW+ = $admin_name @@ -67,7 +66,7 @@ REPO_BASE=$( cd;perl -e 'do ".gitolite.rc"; print $REPO_BASE' ) RW+ = @all EOF } -cp $pubkey_file $GL_ADMINDIR/keydir +[[ -n $pubkey_file ]] && cp $pubkey_file $GL_ADMINDIR/keydir touch $HOME/.ssh/authorized_keys gl-compile-conf -q From e674a7c64a04812a55329f66b19be4329e27d759 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 10 Feb 2010 16:19:20 +0530 Subject: [PATCH 257/637] (package maintainers read this) install doc updated (about this commit) The install doc now describes both the ways of installing gitolite. It also has a handy appendix for package maintainers describing what they need to do. (about the "dps" -- distro packaging support -- commit series) This commit is the last in the chain meant to make gitolite more friendly for package maintainers. Frankly, I never really thought gitolite would get big enough or important enough for someone to package it, and I always did just the bare minimum I needed to get it working, first for myself, then anyone who hopped onto #git and asked. As a result, it had some quirks in terms of what is expected where and so on... Luckily, it didn't take a lot of changes to fix it, and this series of commits should help make it very easy to package gitolite for system-wide use. --- doc/0-INSTALL.mkd | 130 +++++++++++++++++++++++++++++++++++++++------- src/gitolite.pm | 5 +- src/gl-install | 5 ++ 3 files changed, 121 insertions(+), 19 deletions(-) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index af4022f..c0929ac 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -2,30 +2,59 @@ [Update 2009-11-18: easy install now works from msysgit also!] +Gitolite is somewhat unusual as far as "server" software goes -- every userid +on the system is a potential "gitolite host" and can install his own version +if he chooses to. + This document tells you how to install gitolite. After the install is done, you may want to see the [admin document][admin] for adding users, repos, etc. [admin]: http://github.com/sitaramc/gitolite/blob/pu/doc/2-admin.mkd -There's an easy install script that requires bash (**strongly** recommended), -but if you have no bash or you're on one of the legacy Unixes there's a -slightly more manual process. Both are explained here. +[[TOC]] In this document: - * easy install + * install methods + * user install * typical example run * advantages over the older install methods * disadvantages - * manual install - * upgrades - * other notes + * upgrades + * other notes + * system install / user setup * next steps - * appendix: server and client requirements + * appendix A: server and client requirements for user install + * server + * install workstation + * admin workstation(s) + * appendix B: NOTE TO PACKAGE MAINTAINERS ---- -### easy install +### install methods + +There are 2 ways to install gitolite: The **user-install** mode was the +traditional way, and is used when *any* of the following is true: + + * you don't have root on your "server" (some types of hosting setups, many + corporate paranoia setups ;-) + * your server distro does not have gitolite in its package repositories + * your server distro's package repositories have an old version of gitolite + * you want to stay current with the latest gitolite versions + * your server is not Linux (maybe AIX, or Solaris, etc.) + +The "user install" section describes this method. + +The **system-install followed by user-setup** mode is used when you (or +someone who has root) has installed an RPM or DEB of gitolite and you intend +to use that version. + +The "system install / user setup" section describes this method. + +---- + +### user install There is an easy install script that makes installing very easy for the common case. **This script will setup everything on the server, but you have to run @@ -43,6 +72,7 @@ Assumptions/pre-requisites: `ssh-copy-id` in that file for instructions * you have a clone or an archive of gitolite somewhere on your workstation * if you don't have one, just run `git clone git://github.com/sitaramc/gitolite` + * your workstation has bash (even msysgit bash will do) Once you have all this, just `cd` to that clone and run `src/gl-easy-install` and follow the prompts! (Running it without any arguments shows you usage @@ -75,13 +105,7 @@ actually doing, I suggest you skip the `-q`. * need a recent bash -### manual install - -If you don't have bash, it's not very complicated to do it manually. Just -open the file `src/gl-easy-install` in a nice, syntax coloring, text -editor, and follow the instructions marked "MANUAL" :-) - -### upgrades +#### upgrades Upgrading gitolite is easy. @@ -97,7 +121,7 @@ way. I decided that it is not possible to **safely** let an upgrade do something meaningful with them -- fiddling with existing config files (as opposed to merely creating one which did not exist) is best left to a human. -### other notes +#### other notes * if you run `src/gl-easy-install` without the `-q` option, you will be given a chance to edit `~/.gitolite.rc`. You can change any options (such @@ -105,6 +129,36 @@ opposed to merely creating one which did not exist) is best left to a human. *don't* have to know perl to do so, it's fairly easy to guess in this limited case. +### system install / user setup + +In this mode a system administrator installs gitolite using the server's +distro package mechanism (yum install, apt-get install, etc). + +Once this is done, you as a user must run a command like this (unlike in the +"user install" mode, this is done directly on the server): + + gl-setup yourname.pub + +where yourname.pub is a copy of a public key from your workstation. The first +time you run this, it will create a "gitolite-admin" repo and populate it with +the right configuration for whoever has the corresponding private key to +clone and push it. In other words, that person is the administrator for this +particular gitolite instance. + +If your system administrator upgrades gitolite itself, things will usually +just work without any change; you should not have to do anything special. +However, some new features may require additional settings in your +`~/.gitolite.rc` file. + +Finally, in the rare case that you managed to lose your keys to the admin repo +and want to supply a new pubkey, you can use this command to replace any such +key. Could be useful in an emergency -- just get your new "yourname.pub" to +the server and run the same command as above. + +**IMPORTANT**: there are two variables in the `~/.gitolite.rc` file: +`$GL_PACKAGE_CONF` and `$GL_PACKAGE_HOOKS`. If you remove or change either of +them, expect trouble :-) + ### next steps The last message produced by the easy install script should tell you how to @@ -113,7 +167,7 @@ document. -### appendix: server and client requirements +### appendix A: server and client requirements for user install There are 3 machines *potentially* involved in installing and administering gitolite. @@ -167,3 +221,43 @@ Which means all this can be done from *any* machine. You'll normally do it from the same machine you used to install gitolite, but it doesn't have to be the same one, as long as your pubkey has been added and permissions given to allow you to push to the gitolite-admin repo. + +### appendix B: NOTE TO PACKAGE MAINTAINERS + +Here's how you'd package gitolite. In the following description, location "X" +can be, say, `/usr/share/gitolite/conf` or some such, and similarly location +"Y" can be perhaps `/usr/share/gitolite/hooks`. It's upto your distro +policies where they are. + +These are the content changes needed (no trailing slashes in the location +values please): + + * `gl-setup` should have the following line: + + GL_PACKAGE_CONF="X" + + * `example.gitolite.rc` should have the following lines: + + $GL_PACKAGE_CONF="X"; + $GL_PACKAGE_HOOKS="Y"; + +This is where the files should be installed: + + * everything in "src" goes somewhere on the PATH + * everything in "conf" goes to location "X" + * everything in "hooks" goes to location "Y" + +You might also want to delete the `gl-easy-install` script, since that is +meant for a totally different mode of installation and probably would *not* +work if a user tried to run it :-) + +On the initial install, you could also choose to setup a userid called +"gitolite", and run "gl-setup" as that user; however I do not know how you +would come up with the initial pubkey that is needed. Anyway, the point is +that the "gitolite" user is no more special than any other in terms of hosting +gitolite. Any user can host it by just running "gl-setup". + +When you upgrade, just overwrite all the files; it'll all just work. In fact, +other than the initial "gl-setup" run, the only time a gitolite hosting user +has to actually do anything is to edit their own `~/.gitolite.rc` file if they +want to enable or disable specific features. diff --git a/src/gitolite.pm b/src/gitolite.pm index 15728cc..0bbd604 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -34,7 +34,7 @@ our $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); # very simple patter our $REPOPATT_PATT=qr(^\@?[0-9a-zA-Z][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/-]*$); # these come from the RC file -our ($REPO_UMASK, $GL_WILDREPOS, $GL_PACKAGE_CONF); +our ($REPO_UMASK, $GL_WILDREPOS, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS); our %repos; # ---------------------------------------------------------------------------- @@ -148,6 +148,9 @@ sub new_repo } # propagate our own, plus any local admin-defined, hooks ln_sf($hooks_dir, "*", "hooks"); + # in case of package install, GL_ADMINDIR is no longer the top cop; + # override with the package hooks + ln_sf("$GL_PACKAGE_HOOKS/common", "*", "hooks") if $GL_PACKAGE_HOOKS; chmod 0755, "hooks/update"; } diff --git a/src/gl-install b/src/gl-install index 485683a..b163439 100755 --- a/src/gl-install +++ b/src/gl-install @@ -83,6 +83,9 @@ for my $repo (`find . -type d -name "*.git"`) { chomp ($repo); # propagate our own, plus any local admin-defined, hooks ln_sf("$GL_ADMINDIR/hooks/common", "*", "$repo/hooks"); + # in case of package install, GL_ADMINDIR is no longer the top cop; + # override with the package hooks + ln_sf("$GL_PACKAGE_HOOKS/common", "*", "$repo/hooks") if $GL_PACKAGE_HOOKS; chmod 0755, "$repo/hooks/update"; } @@ -92,6 +95,8 @@ if ( -d "gitolite-admin.git/hooks" ) { unlink "gitolite-admin.git/hooks/post-update"; symlink "$GL_ADMINDIR/hooks/gitolite-admin/post-update", "gitolite-admin.git/hooks/post-update" or die "could not symlink post-update hook\n"; + # ditto... (see previous block) + ln_sf("$GL_PACKAGE_HOOKS/gitolite-admin", "post-update", "gitolite-admin.git/hooks") if $GL_PACKAGE_HOOKS; chmod 0755, "gitolite-admin.git/hooks/post-update"; } From 998ff2d13b9aa913be32c26460d7c54391f6179a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 13 Feb 2010 19:18:28 +0530 Subject: [PATCH 258/637] doc/1 minor fix thanks to bremner for catching this... --- doc/1-migrate.mkd | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/1-migrate.mkd b/doc/1-migrate.mkd index c09fe13..641f9c5 100644 --- a/doc/1-migrate.mkd +++ b/doc/1-migrate.mkd @@ -42,11 +42,14 @@ Now, log off the server and get back to the client: Make sure that you **don't** change the default path for `$REPO_BASE` if you edit the config file! -2. **convert** your gitosis config file. Substitute the path for your - gitosis-admin clone in `$GSAC` below, and similarly the path for your - gito**lite**-admin clone in `$GLAC` + This will give you a gitolite config that has the required entries for the + "gitolite-admin" repo. - src/gl-conf-convert < $GSAC/gitosis.conf > $GLAC/gitolite.conf +2. **convert** your gitosis config file and append it to your gitolite config + file. Substitute the path for your gitosis-admin clone in `$GSAC` below, + and similarly the path for your gito**lite**-admin clone in `$GLAC` + + src/gl-conf-convert < $GSAC/gitosis.conf >> $GLAC/gitolite.conf Be sure to check the file to make sure it converted correctly From 690604d79a5e8f35f6e39c0154cb6fdd3f211adf Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 13 Feb 2010 20:00:45 +0530 Subject: [PATCH 259/637] compile: users and repos have groups... why not refs? this came up in some other discussion with bremner. As usual I said no I won't do it because I don't see any real need. ...then I realised it's just one line :) --- src/gl-compile-conf | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 6a02107..371e912 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -206,6 +206,7 @@ sub parse_conf_file { my $perms = $1; my @refs; @refs = split(' ', $2) if $2; + @refs = expand_list ( @refs ); my @users = split ' ', $3; die "wildrepos disabled, cant use 'C' in config\n" if $perms eq 'C' and not $GL_WILDREPOS; From 83a017f8845abcc88b98eed27e4532b819f77a21 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 14 Feb 2010 09:51:51 +0530 Subject: [PATCH 260/637] htpassword: disallow empty passwords [TODO: allow a callback for a password checking function, such as "passwd_policy_check". Question is where the function would go. ~/.gitolite.rc is the only possible place among the current set of files but I'd rather leave that as a list of simple name=value lines for all sorts of reasons. So maybe something like ~/.gitolite.pm (analogous to the "gitolite.pm" in the sources I supply), which would get "require'd" if found, and would contain all user-defined functions like this one... needs some thinking about] --- src/gitolite.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gitolite.pm b/src/gitolite.pm index 5638d05..6f8025c 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -457,6 +457,7 @@ EOFhtp my $password = <>; $password =~ s/[\n\r]*$//; + die "empty passwords are not allowed\n" unless $password; my $rc = system("htpasswd", "-b", $HTPASSWD_FILE, $ENV{GL_USER}, $password); die "htpasswd command seems to have failed with $rc return code...\n" if $rc; } From 6f740339e4902f3803ca1e4c2cf61728eb30dfa5 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 16 Feb 2010 04:57:14 +0530 Subject: [PATCH 261/637] doc/3 last reorg missed moving some anchors and preamble text --- doc/3-faq-tips-etc.mkd | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 784f58b..684086a 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -156,12 +156,18 @@ plain "git archive", because the Makefile adds a file called make master.tar # or maybe "make pu.tar" - + ## features +Apart from the big ones listed in the top level README, and subjective ones +like "better config file format", gitolite has evolved to have many useful +fearures than the original goal of "gitosis + branch-level access control". + ### syntax and normal usage + + #### simpler syntax The basic syntax is simpler and cleaner but it goes beyond that: **you can @@ -205,6 +211,8 @@ do not worry that this causes some duplication or inefficiency. It doesn't See the "specify gitweb/daemon access" section below for one more example. + + #### one user, many keys I have a laptop and a desktop I need to access the server from. I have @@ -250,6 +258,8 @@ corresponding derived usernames (which is what goes into the ### security, access control, and auditing + + #### two levels of access rights checking Gitolite has two levels of access checks. The **first check** is what I will @@ -285,12 +295,6 @@ any of the refexes match, the push succeeds. If none of them match, it fails. Gitolite also allows "exclude" or "deny" rules. See later in this document for details. -Apart from the big ones listed in the top level README, and subjective ones -like "better config file format", gitolite has evolved to have many useful -fearures than the original goal of "gitosis + branch-level access control". - - - #### better logging If you have been too liberal with the permission to rewind, it has built-in @@ -317,8 +321,6 @@ The other parts of the log line are the name of the repo, the refname being updated, the user updating it, and the refex pattern (from the config file) that matched, in case you need to debug the config file itself. - - #### "exclude" (or "deny") rules Here is an illustrative explanation of "deny" rules. However, please be sure @@ -384,10 +386,10 @@ access control for their own pieces. See [doc/5-delegation.mkd](http://github.com/sitaramc/gitolite/blob/pu/doc/5-delegation.mkd) for details. - - ### convenience features + + #### what repos do I have access to? Sometimes there are too many repos, maybe even named similarly, or with the @@ -451,8 +453,6 @@ attempting to run git stuff. Very easy, very simple, and completely transparent to the users :-) - - #### "personal" branches "personal" branches are great for corporate environments, where @@ -513,6 +513,8 @@ You can specify hooks that you want to propagate to all repos, as well as per-repo "gitconfig" settings. Please see `doc/2-admin.mkd` and `conf/example.conf` for details. + + ### helping with gitweb Although gitweb is a completely separate program, gitolite can do quite a From 8d382a6d25d5531e26e372d767964d982aef4058 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 16 Feb 2010 05:02:14 +0530 Subject: [PATCH 262/637] doc/6 now has anchors --- doc/6-ssh-troubleshooting.mkd | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/6-ssh-troubleshooting.mkd b/doc/6-ssh-troubleshooting.mkd index 8189af8..83d908b 100644 --- a/doc/6-ssh-troubleshooting.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -43,6 +43,8 @@ are using your shell access -- instead of running via `/some/path/gl-auth-command ` it is just going to bash and working from there!] + + ### basic ssh troubleshooting [glb]: http://sitaramc.github.com/0-installing/9-gitolite-basics.html#IMPORTANT_overview_of_ssh @@ -182,6 +184,8 @@ you had command line access to the server *before* you were added as a gitolite user. If you send that same key to your gitolite admin to include in the admin repo, it won't work. For reasons why, see below. + + ### details Here's how it all hangs together. @@ -348,6 +352,8 @@ that should have enough info to get you going (but it helps to know ssh well): That should do it. + + ### more complex ssh setups What do you need to know in order to create more complex ssh setups (for @@ -373,6 +379,8 @@ instance if you have *two* gitolite servers you are administering)? * now access one server's repos as `gitolite:reponame.git` and the other server's repos as `gitolite2:reponame.git`. + + ### giving shell access to gitolite users We've managed (thanks to an idea from Jesse Keating) to make it possible for a From 087aa274c6ef602e99e24acb6a9a27036ad11cf9 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 16 Feb 2010 06:39:27 +0530 Subject: [PATCH 263/637] doc/0: added uninstall instructions --- doc/0-INSTALL.mkd | 54 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index af4022f..88ba053 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -21,7 +21,8 @@ In this document: * upgrades * other notes * next steps - * appendix: server and client requirements + * appendix A: server and client requirements + * appendix B: uninstalling gitolite ---- @@ -113,7 +114,7 @@ document. -### appendix: server and client requirements +### appendix A: server and client requirements There are 3 machines *potentially* involved in installing and administering gitolite. @@ -167,3 +168,52 @@ Which means all this can be done from *any* machine. You'll normally do it from the same machine you used to install gitolite, but it doesn't have to be the same one, as long as your pubkey has been added and permissions given to allow you to push to the gitolite-admin repo. + + + +### uninstalling gitolite + +Sometimes you might find gitolite is overkill -- you have only one user +(yourself) pushing maybe. Or maybe gitolite is just not enough -- you want a +web-based front end that users can use to manage their keys themselves, etc., +in which case you'd probably switch to [github][g1], [girocco][g2], +[indefero][g3] or [gitorious][g4]. Either way, you'd like to uninstall +gitolite. + +[g1]: http://github.com +[g2]: http://repo.or.cz/w/girocco.git +[g3]: http://www.indefero.net/ +[g4]: http://gitorious.com/ + +Uninstalling gitolite is fairly easy. Just log on to the server and do the +following (assuming `$REPO_BASE` in the rc file was left at its default of +`~/repositories`; if not, adjust accordingly): + + * edit `~/.ssh/authorized_keys` and delete the `# gitolite start` and `# + gitolite end` markers and all the lines between them. This will prevent + any of your users from attempting a push while you are doing this. + + If you are the only user, and/or *need* one or more of those keys to + continue to access this account (like if one of them is your laptop or + your home desktop etc.) then instead of deleting the line you can just + delete everything upto but not including the words "ssh-rsa" or "ssh-dss". + + * Now remove (or move aside or rename to something else if you're paranoid) + the following files and directories. + + ~/.gitolite + ~/.gitolite.rc + ~/repositories/gitolite-admin.git + + * Then remove all the `update` hooks that git installs on each repository. + The easiest way is: + + find ~/repositories -wholename "*.git/hooks/update" | xargs rm -f + + but you can do it manually if you want to be careful. + + * Finally, any remote users that still have access must update their clone's + remote URLs (edit `.git/config` in the repo) to prefix `repositories/` + before the actual path used, in order for the remote to still work. This + is because you'll now be accessing it through plain ssh, which means you + have to give it the full path. From 16cea9bf8c7c040db559dd2d8888e8dfede37af5 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 18 Feb 2010 06:10:08 +0530 Subject: [PATCH 264/637] compile: move checking of reponame/repopatt/username out of expand_list let expand_list be just that "expand a list", and leave checking to be done outside. otherwise, commit 690604d79 has the side effect of restricting refs to $REPOPATT_PATT, and so for instance barfing on the perfectly valid RW+ refs/(?!heads/master) = alice bob (thanks to Teemu for catching this) --- src/gl-compile-conf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 371e912..842a30a 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -133,8 +133,6 @@ sub expand_list for my $item (@list) { - die "$ABRT bad user or repo name $item\n" - unless ($GL_WILDREPOS ? $item =~ $REPOPATT_PATT : $item =~ $REPONAME_PATT) or $item =~ $USERNAME_PATT; if ($item =~ /^@/) # nested group { die "$ABRT undefined group $item\n" unless $groups{$item}; @@ -198,6 +196,7 @@ sub parse_conf_file @repos = keys %repos; } else { @repos = expand_list ( @repos ); + do { die "$ABRT bad reponame $_\n" unless ($GL_WILDREPOS ? $_ =~ $REPOPATT_PATT : $_ =~ $REPONAME_PATT) } for @repos; } s/\bCREAT[EO]R\b/\$creater/g for @repos; } From e7ac085d61ad558b0a53cc68a20e8a859af91773 Mon Sep 17 00:00:00 2001 From: Teemu Matilainen Date: Wed, 10 Feb 2010 22:00:43 +0200 Subject: [PATCH 265/637] List also non-wildcard repos in expand_wild List also all matching and accessible non-wildcard repositories in ssh expand command. Signed-off-by: Teemu Matilainen --- src/gitolite.pm | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 6f8025c..b87e45b 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -306,7 +306,7 @@ sub expand_wild # get the list of repo patterns &parse_acl($GL_CONF_COMPILED, "", "NOBODY", "NOBODY", "NOBODY"); - my %reponames = map { $_ => 1 } grep { $_ =~ $REPONAME_PATT } sort keys %repos; + my %normal_repos = %repos; # display matching repos (from *all* the repos in the system) that $user # has at least "R" access to @@ -316,24 +316,28 @@ sub expand_wild chomp ($actual_repo); $actual_repo =~ s/^\.\///; $actual_repo =~ s/\.git$//; - # actual_repo should not be present "as is" in the config, because if - # it does, those permissions will override anything inherited from a - # wildcard that also happens to match, and it would be misleading to - # show that here - next if $reponames{$actual_repo}; - # it has to match the pattern being expanded + # actual_repo has to match the pattern being expanded next unless $actual_repo =~ /^$repo$/; - - # find the creater and subsitute in repos - my ($creater, $read, $write) = &repo_rights($repo_base_abs, $actual_repo, $user); - # get access list with this - &parse_acl($GL_CONF_COMPILED, $actual_repo, $creater, $read || "NOBODY", $write || "NOBODY"); - - my $perm = " "; - $perm .= ($repos{$actual_repo}{R}{'@all'} or $repos{$actual_repo}{R}{$user}) ? " R" : " "; - $perm .= ($repos{$actual_repo}{W}{'@all'} or $repos{$actual_repo}{W}{$user}) ? " W" : " "; - next if $perm eq " "; - print "$perm\t($creater)\t$actual_repo\n"; + # if actual_repo is present "as is" in the config, those + # permissions will override anything inherited from a + # wildcard that also happens to match + my $creater; + if ($normal_repos{$actual_repo}) { + %repos = %normal_repos; + $creater = ''; + } else { + # find the creater and subsitute in repos + my ($read, $write); + ($creater, $read, $write) = &repo_rights($repo_base_abs, $actual_repo, $user); + # get access list with this + &parse_acl($GL_CONF_COMPILED, $actual_repo, $creater, $read || "NOBODY", $write || "NOBODY"); + $creater = "($creater)"; + } + my $perm = ' '; + $perm .= ( $repos{$actual_repo}{R}{'@all'} ? ' @' : ( $repos{$actual_repo}{R}{$user} ? ' R' : ' ' ) ); + $perm .= ( $repos{$actual_repo}{W}{'@all'} ? ' @' : ( $repos{$actual_repo}{W}{$user} ? ' W' : ' ' ) ); + next if $perm eq ' '; + print "$perm\t$creater\t$actual_repo\n"; } } From 1de9e963f0abf9d971689a88c6c646481a57b61a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 18 Feb 2010 19:20:46 +0530 Subject: [PATCH 266/637] auth: behave better when no argument supplied to wild commands expand gets a default '.*' argument others die with an error message --- src/gl-auth-command | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index f7f415a..db16113 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -103,7 +103,7 @@ unless ($ENV{SSH_ORIGINAL_COMMAND}) { # get and set perms for actual repo created by wildcard-autoviv # ---------------------------------------------------------------------------- -my $CUSTOM_COMMANDS=qr/^\s*(expand|(get|set)(perms|desc))\s/; +my $CUSTOM_COMMANDS=qr/^\s*(expand|(get|set)(perms|desc))\b/; # note that all the subs called here chdir somewhere else and do not come # back; they all blithely take advantage of the fact that processing custom @@ -112,7 +112,9 @@ my $CUSTOM_COMMANDS=qr/^\s*(expand|(get|set)(perms|desc))\s/; if ($ENV{SSH_ORIGINAL_COMMAND} =~ $CUSTOM_COMMANDS) { die "wildrepos disabled, sorry\n" unless $GL_WILDREPOS; my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; - my ($verb, $repo) = ($cmd =~ /^\s*(\S+)\s+\/?(.*?)(?:.git)?$/); + my ($verb, $repo) = ($cmd =~ /^\s*(\S+)(?:\s+\/?(.*?)(?:.git)?)?$/); + # deal with "no argument" cases + $verb eq 'expand' ? $repo = '.*' : die "$verb needs an argument\n" unless $repo; if ($repo =~ $REPONAME_PATT and $verb =~ /getperms|setperms/) { # with an actual reponame, you can "getperms" or "setperms" get_set_perms($repo_base_abs, $repo, $verb, $user); From d1d399f6b74e72a3796813c09a88159300461ae5 Mon Sep 17 00:00:00 2001 From: Teemu Matilainen Date: Thu, 25 Feb 2010 04:27:51 +0200 Subject: [PATCH 267/637] contrib: Add info of Vim syntax highlight Grand opening of the "contrib" directory. =) Signed-off-by: Teemu Matilainen --- contrib/vim/README.mkd | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 contrib/vim/README.mkd diff --git a/contrib/vim/README.mkd b/contrib/vim/README.mkd new file mode 100644 index 0000000..ba6a000 --- /dev/null +++ b/contrib/vim/README.mkd @@ -0,0 +1,11 @@ +# Vim Syntax Highlight + +[Vim][] Syntax highlight for `gitolite.conf` can be found from: + +- [vim.org script page][vim.org] (Releases) +- [GitHub][] (Sources) + + +[Vim]: http://www.vim.org/ +[vim.org]: http://www.vim.org/scripts/script.php?script_id=2900 +[GitHub]: http://github.com/tmatilai/gitolite.vim From 802f925f1d739b5e296831f043953480c1552219 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 25 Feb 2010 20:13:16 +0530 Subject: [PATCH 268/637] doc/CHANGELOG added --- doc/CHANGELOG | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 doc/CHANGELOG diff --git a/doc/CHANGELOG b/doc/CHANGELOG new file mode 100644 index 0000000..2a648aa --- /dev/null +++ b/doc/CHANGELOG @@ -0,0 +1,39 @@ +Major changes to gitolite, master branch only, most recent first, no dates but +the tags can help you position stuff approximately + + - v1.1 + + - contrib directory added + - expand now lists non-wildcard repos also + - refs also have groups now + - allow admins to get "info" for other users + + - wildrepos merged + - getdesc and setdesc for wildrepos + - htpasswd subcommand + - access control for rsync + + - v1.0 + + - sshkeys-lint program added, doc/6 revamped + - @SHELL in config changed to $SHELL_USERS in rc + - "include" mechanism + - delegation now uses NAME/ instead of branches + - PATH/ changed to NAME/ + + - @SHELL in config + - use of @all for repos also (see doc for caveat) + - config entries for repos + + - deny rules (no more "rebel" branch!) + - PATH/ + - specify gitweb owner + + - v0.95 + - easy install can run from msysgit also + - v0.90 + - allow admin defined hooks + - specify gitweb desc + - v0.85 + - emergency addkey program + - v0.80 From 42cc720eaac6082e4efc71981427541f3a445488 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 26 Feb 2010 07:13:19 +0530 Subject: [PATCH 269/637] easy install: be more specific about NOT adding repos manually --- src/gl-easy-install | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gl-easy-install b/src/gl-easy-install index 35c31e2..6586e87 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -583,7 +583,7 @@ it elsewhere later if you wish to. " tail=" -NOTE: All the below stuff is on your *workstation*. You shoud not, normally, +NOTE: All the below stuff is on your *workstation*. You should not, normally, have to do anything directly on your server to administer/use gitolite. The admin repo is currently cloned at ~/gitolite-admin. You can reclone it @@ -591,8 +591,9 @@ anywhere else if you wish. To administer gitolite, make changes to the config file (conf/gitolite.conf) and/or the pubkeys (in subdirectory 'keydir') in any clone, then git add, git commit, and git push. -ADDING REPOS: Edit the config file to give *some* user access to the repo. -When you push, an empty repo will be created on the server. +ADDING REPOS: Do NOT add repos manually on the server. Edit the config file +to give *some* user access to the repo. When you push, an empty repo will be +created on the server. ADDING USERS: copy their pubkey as keydir/.pub, add it, commit and push. From deda3da18271ab86e1d8be971159654d31a23885 Mon Sep 17 00:00:00 2001 From: Teemu Matilainen Date: Fri, 26 Feb 2010 16:55:28 +0200 Subject: [PATCH 270/637] auth: do not anchor the pattern given for expand Currently the pattern of expand command is line anchored. This is different than in e.g. grep, and causes extra work to add '.*' prefix and/or suffix in many use cases. The new semantics now mean you might get more matches than you would have gotten earlier. However, the expand command is still totally undocumented, so I think it is acceptable to change the functionality. ;) This patch removes the anchoring. So for earlier behavior the specified pattern needs be in form of '^$'. The default pattern is also changed from '.*' to '^', so there might be even a small speed improvement. =) Signed-off-by: Teemu Matilainen --- src/gitolite.pm | 2 +- src/gl-auth-command | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 46d537c..c61a566 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -320,7 +320,7 @@ sub expand_wild $actual_repo =~ s/^\.\///; $actual_repo =~ s/\.git$//; # actual_repo has to match the pattern being expanded - next unless $actual_repo =~ /^$repo$/; + next unless $actual_repo =~ /$repo/; # if actual_repo is present "as is" in the config, those # permissions will override anything inherited from a # wildcard that also happens to match diff --git a/src/gl-auth-command b/src/gl-auth-command index 443bd8f..3d7eefc 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -115,7 +115,7 @@ if ($ENV{SSH_ORIGINAL_COMMAND} =~ $CUSTOM_COMMANDS) { my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; my ($verb, $repo) = ($cmd =~ /^\s*(\S+)(?:\s+\/?(.*?)(?:.git)?)?$/); # deal with "no argument" cases - $verb eq 'expand' ? $repo = '.*' : die "$verb needs an argument\n" unless $repo; + $verb eq 'expand' ? $repo = '^' : die "$verb needs an argument\n" unless $repo; if ($repo =~ $REPONAME_PATT and $verb =~ /getperms|setperms/) { # with an actual reponame, you can "getperms" or "setperms" get_set_perms($repo_base_abs, $repo, $verb, $user); From 4cf18d8339e7f3902158a4df98bbfd5cf9f77265 Mon Sep 17 00:00:00 2001 From: Eli Barzilay Date: Fri, 26 Feb 2010 15:11:36 -0500 Subject: [PATCH 271/637] make Emacs use perl mode --- conf/example.gitolite.rc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index f1e5b7e..0ecad64 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -195,4 +195,7 @@ $GL_WILDREPOS = 0; # per perl rules, this should be the last line in such a file: 1; +# Local variables: +# mode: perl +# End: # vim: set syn=perl: From 9f805646fe9b18d1830f69721eee470145799aa7 Mon Sep 17 00:00:00 2001 From: Eli Barzilay Date: Fri, 26 Feb 2010 15:11:38 -0500 Subject: [PATCH 272/637] minor typos --- conf/example.conf | 2 +- doc/3-faq-tips-etc.mkd | 2 +- doc/4-wildcard-repositories.mkd | 2 +- doc/6-ssh-troubleshooting.mkd | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index e6aac3b..de2ccf1 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -102,7 +102,7 @@ repo gitolite R = @staff RW+ = sitaram - # you can split up access rules for a repo as convenient + # you can split up access rules for a repo for convenience # (notice that @oss_repos contains gitolite also) repo @oss_repos R = @all diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 684086a..ef92ca8 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -107,7 +107,7 @@ to replicate it manually. The input is your pubkey, typically /home/git/.ssh /home/git -[Actually, sshd requires that even directories *above* ~ (/, /home, +[Actually, `sshd` requires that even directories *above* `~` (`/`, `/home`, typically) also must be `go-w`, but that needs root. And typically they're already set that way anyway. (Or if they're not, you've got bigger problems than gitolite install not working!)] diff --git a/doc/4-wildcard-repositories.mkd b/doc/4-wildcard-repositories.mkd index aba4f66..a0fc1dc 100644 --- a/doc/4-wildcard-repositories.mkd +++ b/doc/4-wildcard-repositories.mkd @@ -159,7 +159,7 @@ use 'getperms' to check: The following points are important: - * note the syntax of the commands; it's not a "git" command,and there's no + * note the syntax of the commands; it's not a "git" command, and there's no `:` like in a repo URL. The first space-separated word is R or RW, and the rest are simple usernames. diff --git a/doc/6-ssh-troubleshooting.mkd b/doc/6-ssh-troubleshooting.mkd index 83d908b..976c6b6 100644 --- a/doc/6-ssh-troubleshooting.mkd +++ b/doc/6-ssh-troubleshooting.mkd @@ -22,7 +22,7 @@ gitolite *after* the install has completed successfully. In addition, I **strongly** recommend reading [this document][glb] -- it's a very detailed look at how gitolite uses ssh's features on the server side. -Most people don't know ssh as well as they *think* they do; even if you dont +Most people don't know ssh as well as they *think* they do; even if you don't have any problems right now, it's worth skimming over. In addition to both these documents, there's now a program called From 572a34740f521bc1d7ff651f0d510bfbe84503bb Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 27 Feb 2010 12:46:02 +0530 Subject: [PATCH 273/637] doc/0: emphasise the importance of ssh --- doc/0-INSTALL.mkd | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index bb22bb6..a0cf54b 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -9,7 +9,14 @@ if he chooses to. This document tells you how to install gitolite. After the install is done, you may want to see the [admin document][admin] for adding users, repos, etc. +**Please note** that gitolite depends heavily on proper ssh setup and pubkey +based access. Sadly, most people don't know ssh as well as they think they +do. To make matters worse, ssh problems in gitolite don't always look like +ssh problems. Please read about [ssh troubleshooting][doc6] if you have *any* +kind of trouble installing gitolite! + [admin]: http://github.com/sitaramc/gitolite/blob/pu/doc/2-admin.mkd +[doc6]: http://github.com/sitaramc/gitolite/blob/pu/doc/6-ssh-troubleshooting.mkd In this document: From 8031f72fa8fbd939b261f69735a6980c93ce9f06 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 27 Feb 2010 17:28:06 +0530 Subject: [PATCH 274/637] progit article added to doc/ --- doc/progit-article.mkd | 158 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 doc/progit-article.mkd diff --git a/doc/progit-article.mkd b/doc/progit-article.mkd new file mode 100644 index 0000000..4c2c193 --- /dev/null +++ b/doc/progit-article.mkd @@ -0,0 +1,158 @@ +## Gitolite ## + +Git has started to become very popular in corporate environments, which tend to have some additional requirements in terms of access control. Gitolite was created to help with those requirements. + +Gitolite allows you to specify permissions not just by repository (like Gitosis does), but also by branch or tag names within each repository. That is, you can specify that certain people (or groups of people) can only push certain "refs" (branches or tags) but not others. + +### Installing ### + +Installing Gitolite is very easy, even if you don't read the extensive documentation that comes with it. You need an account on a Unix server of some kind; various Linux flavours, and Solaris 10, have been tested. You do not need root access, assuming git, perl, and an openssh compatible ssh server are already installed. In the examples below, we will use the `gitolite` account on a host called `gitserver`. + +Curiously, Gitolite is installed by running a script *on the workstation*, so your workstation must have a bash shell available. Even the bash that comes with msysgit will do, in case you're wondering. + +You start by obtaining public key based access to your server, so that you can log in from your workstation to the server without getting a password prompt. The following method works on Linux; for other workstation OSs you may have to do this manually. We assume you already had a key pair generated using `ssh-keygen`. + + $ ssh-copy-id -i ~/.ssh/id_rsa gitolite@gitserver + +This will ask you for the password to the gitolite account, and then set up public key access. This is **essential** for the install script, so check to make sure you can run a command without getting a password prompt: + + $ ssh gitolite@gitserver pwd + /home/gitolite + +Next, you clone Gitolite from the project's main site and run the "easy install" script (the third argument is your name as you would like it to appear in the resulting gitolite-admin repository): + + $ git clone git://github.com/sitaramc/gitolite + $ cd gitolite/src + $ ./gl-easy-install -q gitolite gitserver sitaram + +And you're done! Gitolite has now been installed on the server, and you now have a brand new repository called `gitolite-admin` in the home directory of your workstation. You administer your gitolite setup by making changes to this repository and pushing (just like Gitosis). + +[By the way, *upgrading* gitolite is also done the same way. Also, if you're interested, run the script without any arguments to get a usage message.] + +That last command does produce a fair amount of output, which might be interesting to read. Also, the first time you run this, a new keypair is created; you will have to choose a passphrase or hit enter for none. Why a second keypair is needed, and how it is used, is explained in the "ssh troubleshooting" document that comes with Gitolite. (Hey the documentation has to be good for *something*!) + +### Customising the Install ### + +While the default, quick, install works for most people, there are some ways to customise the install if you need to. If you omit the `-q` argument, you get a "verbose" mode install -- detailed information on what the install is doing at each step. The verbose mode also allows you to change certain server-side parameters, such as the location of the actual repositories, by editing an "rc" file that the server uses. This "rc" file is liberally commented so you should be able to make any changes you need quite easily, save it, and continue. This file also contains various settings that you can change to enable or disable some of gitolite's advanced features. + +### Config File and Access Control Rules ### + +So once the install is done, you switch to the `gitolite-admin` repository (placed in your HOME directory) and poke around to see what you got: + + $ cd ~/gitolite-admin/ + $ ls + conf/ keydir/ + $ find conf keydir -type f + conf/gitolite.conf + keydir/sitaram.pub + $ cat conf/gitolite.conf + #gitolite conf + # please see conf/example.conf for details on syntax and features + + repo gitolite-admin + RW+ = sitaram + + repo testing + RW+ = @all + +Notice that "sitaram" (the last argument in the `gl-easy-install` command you gave earlier) has read-write permissions on the `gitolite-admin` repository as well as a public key file of the same name. + +The config file syntax for Gitolite is *quite* different from Gitosis. Again, this is liberally documented in `conf/example.conf`, so we'll only mention some highlights here. + +You can group users or repos for convenience. The group names are just like macros; when defining them, it doesn't even matter whether they are projects or users; that distinction is only made when you *use* the "macro". + + @oss_repos = linux perl rakudo git gitolite + @secret_repos = fenestra pear + + @admins = scott # Adams, not Chacon, sorry :) + @interns = ashok # get the spelling right, Scott! + @engineers = sitaram dilbert wally alice + @staff = @admins @engineers @interns + +You can control permissions at the "ref" level. In the following example, interns can only push the "int" branch. Engineers can push any branch whose name starts with "eng-", and tags that start with "rc" followed by a digit. And the admins can do anything (including rewind) to any ref. + + repo @oss_repos + RW int$ = @interns + RW eng- = @engineers + RW refs/tags/rc[0-9] = @engineers + RW+ = @admins + +The expression after the `RW` or `RW+` is a regular expression (regex) that the refname (ref) being pushed is matched against. So we call it a "refex"! Of course, a refex can be far more powerful than shown here, so don't overdo it if you're not comfortable with perl regexes. + +Also, as you probably guessed, Gitolite prefixes `refs/heads/` as a syntactic convenience if the refex does not begin with `refs/`. + +An important feature of the config file's syntax is that all the rules for a repository need not be in one place. You can keep all the common stuff together, like the rules for all `oss_repos` shown above, then add specific rules for specific cases later on, like so: + + repo gitolite + RW+ = sitaram + +That rule will just get added to the ruleset for the `gitolite` repository. + +At this point you might be wondering how the access control rules are actually applied, so let's go over that briefly. + +There are two levels of access control in gitolite. The first is at the repository level; if you have read (or write) access to *any* ref in the repository, then you have read (or write) access to the repository. This is the only access control that Gitosis had. + +The second level, applicable only to "write" access, is by branch or tag within a repository. The username, the access being attempted (`W` or `+`), and the refname being updated are known. The access rules are checked in order of appearance in the config file, looking for a match for this combination (but remember that the refname is regex-matched, not merely string-matched). If a match is found, the push succeeds. A fallthrough results in access being denied. + +### Advanced Access Control with "deny" rules ### + +So far, we've only seen permissions to be one or `R`, `RW`, or `RW+`. However, gitolite allows another permission: `-`, standing for "deny". This gives you a lot more power, at the expense of some complexity, because now fallthrough is not the *only* way for access to be denied, so the *order of the rules now matters*! + +Let us say, in the situation above, we want engineers to be able to rewind any branch *except* master and integ. Here's how to do that: + + RW master integ = @engineers + - master integ = @engineers + RW+ = @engineers + +Again, you simply follow the rules top down until you hit a match for your access mode, or a deny. Non-rewind push to master or integ is allowed by the first rule. A rewind push to those refs does not match the first rule, drops down to the second, and is therefore denied. Any push (rewind or non-rewind) to refs other than master or integ won't match the first two rules anyway, and the third rule allows it. + +### Restricting pushes by files changed ### + +In addition to restricting what branches a user can push changes to, you can also restrict what files they are allowed to touch. For example, perhaps the Makefile (or some other program) is really not supposed to be changed by just anyone, because a lot of things depend on it or would break if the changes are not done *just right*. You can tell gitolite: + + repo foo + RW = @junior_devs @senior_devs + + RW NAME/ = @senior_devs + - NAME/Makefile = @junior_devs + RW NAME/ = @junior_devs + +This powerful feature is documented in `conf/example.conf`. + +### Personal Branches ### + +Gitolite also has a feature called "personal branches" (or rather, "personal branch namespace") that can be very useful in a corporate environment. + +A lot of code exchange in the git world happens by "please pull" requests. In a corporate environment, however, unauthenticated access is a no-no, and a developer workstation cannot do authentication, so you have to push to the central server and ask someone to pull from there. + +This would normally cause the same branch name clutter as in a centralised VCS, plus setting up permissions for this becomes a chore for the admin. + +Gitolite lets you define a "personal" or "scratch" namespace prefix for each developer (for example, `refs/personal//*`), with full permissions for that dev only, and read access for everyone else. Just choose a verbose install and set the `$PERSONAL` variable in the "rc" file to `refs/personal`. That's all; it's pretty much fire and forget as far as the admin is concerned, even if there is constant churn in the project team composition. + +### "Wildcard" repositories ### + +Gitolite allows you to specify repositories with wildcards (actually perl regexes), like, for example `assignments/s[0-9][0-9]/a[0-9][0-9]`, to pick a random example. This is a *very* powerful feature, which has to be enabled by setting `$GL_WILDREPOS = 1;` in the rc file. It allows you to assign a new permission mode ("C") which allows users to create repositories based on such wild cards, automatically assigns ownership to the specific user who created it, allows him/her to hand out R and RW permissions to other users to collaborate, etc. This feature is documented in `doc/4-wildcard-repositories.mkd`. + +### Other Features ### + +We'll round off this discussion with a bunch of other features, all of which are described in great detail in the "faqs, tips, etc" document. + +**Logging**: Gitolite logs all successful accesses. If you were somewhat relaxed about giving people rewind permissions (`RW+`) and some kid blew away "master", the log file is a life saver, in terms of easily and quickly finding the SHA that got hosed. + +**Git outside normal PATH**: One extremely useful convenience feature in gitolite is support for git installed outside the normal `$PATH` (this is more common than you think; some corporate environments or even some hosting providers refuse to install things system-wide and you end up putting them in your own directories). Normally, you are forced to make the *client-side* git aware of this non-standard location of the git binaries in some way. With gitolite, just choose a verbose install and set `$GIT_PATH` in the "rc" files. No client-side changes are required after that :-) + +**Access rights reporting**: Another convenient feature is what happens when you try and just ssh to the server. Older versions of gitolite used to complain about the `SSH_ORIGINAL_COMMAND` environment variable being empty (see the ssh documentation if interested). Now Gitolite comes up with something like this: + + hello sitaram, the gitolite version here is v0.90-9-g91e1e9f + you have the following permissions: + R anu-wsd + R entrans + R W git-notes + R W gitolite + R W gitolite-admin + R indic_web_input + R shreelipi_converter + +**Delegation**: For really large installations, you can delegate responsibility for groups of repositories to various people and have them manage those pieces independently. This reduces the load on the main admin, and makes him less of a bottleneck. This feature has its own documentation file in the `doc/` directory. + +**Gitweb support**: Gitolite supports gitweb in several ways. You can specify which repos are visible via gitweb. You can set the "owner" and "description" for gitweb from the gitolite config file. Gitweb has a mechanism for you to implement access control based on HTTP authentication, so you can make it use the "compiled" config file that gitolite produces, which means the same access control rules (for read access) apply for gitweb and gitolite. From 6dbaa0d325fadd05eade40656d52e968c0ef0e6c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 1 Mar 2010 20:32:54 +0530 Subject: [PATCH 275/637] auth: expand etc. *may* have single-quotes around reponame --- src/gl-auth-command | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl-auth-command b/src/gl-auth-command index 3d7eefc..a21167b 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -113,7 +113,7 @@ my $CUSTOM_COMMANDS=qr/^\s*(expand|(get|set)(perms|desc))\b/; if ($ENV{SSH_ORIGINAL_COMMAND} =~ $CUSTOM_COMMANDS) { die "wildrepos disabled, sorry\n" unless $GL_WILDREPOS; my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; - my ($verb, $repo) = ($cmd =~ /^\s*(\S+)(?:\s+\/?(.*?)(?:.git)?)?$/); + my ($verb, $repo) = ($cmd =~ /^\s*(\S+)(?:\s+'?\/?(.*?)(?:\.git)?'?)?$/); # deal with "no argument" cases $verb eq 'expand' ? $repo = '^' : die "$verb needs an argument\n" unless $repo; if ($repo =~ $REPONAME_PATT and $verb =~ /getperms|setperms/) { From be92feca6d903f092afab4a6cf3fb66fd35a5ca9 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 2 Mar 2010 05:39:19 +0530 Subject: [PATCH 276/637] add conf/VERSION to .gitignore conf/VERSION is programmatically created, not manually, so you shouldn't be checking it in, which means it looks cleaner to explicitly put it in .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f60da1e..6316699 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.tgz *.tar.gz *.tar.bz2 +conf/VERSION From de0ecd04310b29eaf9cbdf28509523ec5ee53a59 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 7 Mar 2010 19:05:56 +0530 Subject: [PATCH 277/637] compile: make it easier to move repos into gitolite when repos are copied over from elsewhere, one had to run easy install once again to make the new (OS-copied) repo contain the proper update hook. We eliminate this step now, using a new, empty, "hook" as a sentinel and having "compile" check/fix all repos' hooks. Since you have to add the repos to conf anyway, this makes it as seamless as possible. The correct sequence now is - (server) copy the repo at the OS level - (admin clone) add it to conf/gitolite.conf, commit, push --- doc/2-admin.mkd | 20 ++++++++++++++++++++ hooks/common/gitolite-hooked | 0 src/gl-compile-conf | 13 ++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 hooks/common/gitolite-hooked diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index a93df4b..ad9f18e 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -7,6 +7,7 @@ In this document: * administer * adding users and repos + * moving pre-existing repos into gitolite * specifying gitweb and daemon access * custom hooks * custom git config @@ -40,6 +41,25 @@ Please read on to see how to do this correctly. * when done, commit your changes and push +#### moving pre-existing repos into gitolite + +One simple way to add a pre-existing repo to gitolite is to let gitolite +create it as a brand new repo as in the previous section, and then, from an +existing clone, "push --all" to the new one. + +However, if you have many existing repos to add, this can be time-consuming +and error-prone. Here's how to take a bunch of existing repos and add them to +gitolite: + + * make sure they're *bare* repos ;-) + + * log on to the server and copy the repos to `$REPO_BASE` (which defaults to + `~/repositories`), making sure that the directory names end in ".git". + + * back on your workstation, add each repo (without the `.git` suffix) to + `conf/gitolite.conf` in your gitolite-admin repo clone. Then add, commit, + push. + #### specifying gitweb and daemon access This is a feature that I personally do not use (corporate environments don't diff --git a/hooks/common/gitolite-hooked b/hooks/common/gitolite-hooked new file mode 100644 index 0000000..e69de29 diff --git a/src/gl-compile-conf b/src/gl-compile-conf index bd2780f..c9b953b 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -61,7 +61,7 @@ $Data::Dumper::Sortkeys = sub { return [ reverse sort keys %{$_[0]} ]; }; open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); # these are set by the "rc" file -our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $SHELL_USERS, $GL_WILDREPOS, $GL_GITCONFIG_KEYS); +our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $SHELL_USERS, $GL_WILDREPOS, $GL_GITCONFIG_KEYS, $GL_PACKAGE_HOOKS); # and these are set by gitolite.pm our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN); @@ -400,6 +400,17 @@ for my $repo (sort keys %repos) { # new_repo would have chdir'd us away; come back wrap_chdir("$repo_base_abs"); } + + # when repos are copied over from elsewhere, one had to run easy install + # once again to make the new (OS-copied) repo contain the proper update + # hook. Perhaps we can make this easier now, and eliminate the easy + # install, with a quick check (and a new, empty, "hook" as a sentinel) + unless (-l "$repo.git/hooks/gitolite-hooked") { + ln_sf("$GL_ADMINDIR/hooks/common", "*", "$repo.git/hooks"); + # in case of package install, GL_ADMINDIR is no longer the top cop; + # override with the package hooks + ln_sf("$GL_PACKAGE_HOOKS/common", "*", "$repo.git/hooks") if $GL_PACKAGE_HOOKS; + } } warn "\n\t\t***** WARNING *****\n" . From 08811fa9c2fef044edc68121cf75635814360714 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 7 Mar 2010 19:31:21 +0530 Subject: [PATCH 278/637] easy install: update ending message when non-std ssh port used --- src/gl-easy-install | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gl-easy-install b/src/gl-easy-install index 05dd5dc..d12f98a 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -567,8 +567,11 @@ done! Reminder: *Your* URL for cloning any repo on this server will be gitolite:reponame.git + *Other* users you set up will have to use \$user@\$host:reponame.git + However, if your server uses a non-standard ssh port, they should use + ssh://\$user@\$host:\$port/reponame.git If this is your first time installing gitolite, please also: tail -31 \$0 From 369ff45d9236932b0b420bb6ff79cf57a27c5586 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 9 Mar 2010 21:57:26 +0530 Subject: [PATCH 279/637] easy install seemed to out of the GIT_PATH loop for some reason, I apparently did not test easy install with a non-standard path! Fixed... --- src/gl-easy-install | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gl-easy-install b/src/gl-easy-install index d12f98a..90a4061 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -336,9 +336,10 @@ run_install() { prompt "installing/upgrading..." "$v_ignore_stuff" - # extract the GL_ADMINDIR and REPO_BASE locations + # extract the GL_ADMINDIR, REPO_BASE and GIT_PATH locations GL_ADMINDIR=$(ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'") REPO_BASE=$( ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$REPO_BASE'") + GIT_PATH=$( ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GIT_PATH'") # determine if this is an upgrade; we decide based on whether a file # called $GL_ADMINDIR/conf/gitolite.conf exists on the remote side. We @@ -409,6 +410,7 @@ setup_pta() { # space around the "=" in the second and third lines. echo "cd $REPO_BASE/gitolite-admin.git +PATH=$PATH:$GIT_PATH GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir GIT_WORK_TREE=$GL_ADMINDIR git diff --cached --quiet || GIT_WORK_TREE=$GL_ADMINDIR git commit -am start " | ssh -p $port $user@$host From 4b7d144971e9201061f3e7a7cfe7e2367eb81544 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 9 Mar 2010 22:07:57 +0530 Subject: [PATCH 280/637] easy install: suppress that misleading "fatal" get rid of the "fatal: No HEAD commit to compare with (yet)" message --- src/gl-easy-install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl-easy-install b/src/gl-easy-install index 90a4061..75b7017 100755 --- a/src/gl-easy-install +++ b/src/gl-easy-install @@ -412,7 +412,7 @@ setup_pta() { echo "cd $REPO_BASE/gitolite-admin.git PATH=$PATH:$GIT_PATH GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir -GIT_WORK_TREE=$GL_ADMINDIR git diff --cached --quiet || GIT_WORK_TREE=$GL_ADMINDIR git commit -am start +GIT_WORK_TREE=$GL_ADMINDIR git diff --cached --quiet 2>/dev/null || GIT_WORK_TREE=$GL_ADMINDIR git commit -am start " | ssh -p $port $user@$host # MANUAL: now that the admin repo is created, you have to set the hooks From b3945d44c9c0f70054aebb3edc7b488c0544210b Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 10 Mar 2010 06:24:53 +0530 Subject: [PATCH 281/637] docs and .gitattributes hadn't been updated for the change in hooks dir --- .gitattributes | 7 ++++--- conf/example.conf | 4 ++-- doc/2-admin.mkd | 10 +++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.gitattributes b/.gitattributes index 5d894ad..85f7e74 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ -conf/* crlf=input -src/* crlf=input -src/hooks/* crlf=input +conf/* crlf=input +src/* crlf=input +hooks/common/* crlf=input +hooks/gitolite-admin/* crlf=input diff --git a/conf/example.conf b/conf/example.conf index de2ccf1..c197dbc 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -253,8 +253,8 @@ gitolite "Sitaram Chamarty" = "fast, secure, access control for git in a corpora # syntax: # config sectionname.keyname = [optional value_string] -# example usage: if you placed a hook in src/hooks that requires configuration -# information that is specific to each repo, you could do this: +# example usage: if you placed a hook in hooks/common that requires +# configuration information that is specific to each repo, you could do this: repo gitolite config hooks.mailinglist = gitolite-commits@example.tld diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index ad9f18e..b5a6c08 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -99,15 +99,15 @@ for the special usernames or remove the description line. #### custom hooks If you want to put in your own, custom, hooks every time a new repo is created -by gitolite, put a **tested** hook script in `src/hooks`. As distributed, the -only file there is the `update` hook, but everything (*everything*) in that -directory will get copied to the `hooks/` subdirectory of every *new* repo -created. +by gitolite, put a **tested** hook script in `hooks/common` of your gitolite +clone before running easy-install. As distributed, there are only two files +there, but everything (*everything*) in that directory will get copied to the +`hooks/` subdirectory of every *new* repo created. In order to push a new or updated hook script to *existing* repos as well, just run easy install once again; it'll do it to existing repos also. -**VERY IMPORTANT SECURITY NOTE: the `update` hook in `src/hooks` is what +**VERY IMPORTANT SECURITY NOTE: the `update` hook in `hooks/common` is what implements all the branch-level permissions in gitolite. If you fiddle with the hooks directory, please make sure you do not mess with this file accidentally, or all your fancy per-branch permissions will stop working.** From 7588c8cf541f3557b0bde8d8bde24a42a68b4953 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 12 Mar 2010 09:04:00 +0530 Subject: [PATCH 282/637] dps: gl-setup may have to create ~/.ssh and touch the authkeys file... I've been unwilling to create the authkeys file if it does not already exist, because it represents a significant change in accessibility for that account. However, in the "distro package" scenario, one wants to make it as easy as possible for the end-user (who is actually an admin for the gitolite being hosted on his account, let's not forget) to use. And it seems that in some cases that might mean he does not (yet) have a ~/.ssh even... --- src/gl-setup | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gl-setup b/src/gl-setup index 25e4e0f..4f9ccd1 100755 --- a/src/gl-setup +++ b/src/gl-setup @@ -52,6 +52,15 @@ else cp $GL_PACKAGE_CONF/example.gitolite.rc ~/.gitolite.rc fi +# setup ssh stuff. We break our normal rule that we will not fiddle with +# authkeys etc., because in this case it seems appropriate +cd +mkdir -p .ssh +touch .ssh/authorized_keys +chmod go-w . .ssh .ssh/authorized_keys + +# now we get to gitolite itself + gl-install -q GL_ADMINDIR=$(cd;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR') From d660822ab5d1e2245eb1bca208117b2f2ebc784a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 12 Mar 2010 10:24:53 +0530 Subject: [PATCH 283/637] dps: made dps section clearer and more step-by-step --- doc/0-INSTALL.mkd | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index a0cf54b..15f9532 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -284,33 +284,50 @@ can be, say, `/usr/share/gitolite/conf` or some such, and similarly location "Y" can be perhaps `/usr/share/gitolite/hooks`. It's upto your distro policies where they are. -These are the content changes needed (no trailing slashes in the location -values please): +**Step 1**: Clone the gitolite repo and run the make command inside the clone - * `gl-setup` should have the following line: + git clone git://github.com/sitaramc/gitolite.git + cd gitolite + make pu.tar # or "make master.tar" or "make v1.2.tar" etc + +Then you explode the tar file in some temporary location. + +*Alternatively, you can `git checkout` the tag or branch you want, and run +this command in the clone directly*: + + git describe --tags --long > conf/VERSION + +**Step 2**: Now make the following changes (no trailing slashes in the +location values please): + + * `src/gl-setup` should have the following line: GL_PACKAGE_CONF="X" - * `example.gitolite.rc` should have the following lines: + * `conf/example.gitolite.rc` should have the following lines: $GL_PACKAGE_CONF="X"; $GL_PACKAGE_HOOKS="Y"; -This is where the files should be installed: + * delete `src/gl-easy-install`; that script is meant for a totally different + mode of installation and does *not* play well in this mode :-) + +**Step 3**: Move (or arrange to move) the files to their proper locations as +given below: * everything in "src" goes somewhere on the PATH * everything in "conf" goes to location "X" * everything in "hooks" goes to location "Y" -You might also want to delete the `gl-easy-install` script, since that is -meant for a totally different mode of installation and probably would *not* -work if a user tried to run it :-) +**Step 4**: There is no step 4. Unless you count telling your users to run +`gl-setup` as a step :) -On the initial install, you could also choose to setup a userid called -"gitolite", and run "gl-setup" as that user; however I do not know how you -would come up with the initial pubkey that is needed. Anyway, the point is -that the "gitolite" user is no more special than any other in terms of hosting -gitolite. Any user can host it by just running "gl-setup". +On the initial install (urpmi, yum install, or apt-get install), you could +also choose to setup a userid called "gitolite", and run "gl-setup" as that +user; however I do not know how you would come up with the initial pubkey that +is needed. Anyway, the point is that the "gitolite" user is no more special +than any other in terms of hosting gitolite. Any user can host gitolite on +his userid by just running "gl-setup". When you upgrade, just overwrite all the files; it'll all just work. In fact, other than the initial "gl-setup" run, the only time a gitolite hosting user From 367e8f893264d325a428b12accef078fb181bd2f Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 12 Mar 2010 11:08:51 +0530 Subject: [PATCH 284/637] minor LFCR -> CRLF fix --- src/gitolite.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index c61a566..fbebeb3 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -286,12 +286,12 @@ sub report_basic # send back some useful info if no command was given print "hello $user, the gitolite version here is "; system("cat", ($GL_PACKAGE_CONF || "$GL_ADMINDIR/conf") . "/VERSION"); - print "\ryou have the following permissions:\n\r"; + print "\ryou have the following permissions:\r\n"; for my $r (sort keys %repos) { my $perm .= ( $repos{$r}{C}{'@all'} ? ' @' : ( $repos{$r}{C}{$user} ? ' C' : ' ' ) ); $perm .= ( $repos{$r}{R}{'@all'} ? ' @' : ( $repos{$r}{R}{$user} ? ' R' : ' ' ) ); $perm .= ( $repos{$r}{W}{'@all'} ? ' @' : ( $repos{$r}{W}{$user} ? ' W' : ' ' ) ); - print "$perm\t$r\n\r" if $perm =~ /\S/; + print "$perm\t$r\r\n" if $perm =~ /\S/; } } @@ -358,7 +358,7 @@ sub special_cmd # check each special command we know about and call it if enabled if ($cmd eq 'info') { &report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user); - print "you also have shell access\n\r" if $shell_allowed; + print "you also have shell access\r\n" if $shell_allowed; } elsif ($cmd =~ /^info\s+(.+)$/) { my @otherusers = split ' ', $1; &parse_acl($GL_CONF_COMPILED); From bf7aba7e0b080cbad5cde84c78d0abd27ed37a4b Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 12 Mar 2010 17:20:12 +0530 Subject: [PATCH 285/637] changelog --- doc/CHANGELOG | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/CHANGELOG b/doc/CHANGELOG index 2a648aa..4dd89b7 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -1,6 +1,15 @@ Major changes to gitolite, master branch only, most recent first, no dates but the tags can help you position stuff approximately + - v1.3 + + - easier to move repos into gitolite + - pattern for expand is no longer anchored + + - v1.2 + + - distro packaging support -- easy to install systemwide now + - v1.1 - contrib directory added From 23c57b71f17309b0d6d65eb153c7a3703e5f5357 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 9 Apr 2010 14:48:41 +0530 Subject: [PATCH 286/637] standalone demo of Data::Dumper problem [needless to say, this branch will be deleted eventually...] Please see comments in the data-dumper-returns-undef.pl program to understand what is happening. Typically anyone with large configurations see this happening. I have worked around it by coding around the need for a custom sort sub, and as you can see there is at least one other way to make it work (look for WTF in the code!) but those are just work arounds... --- data-dumper-returns-undef.pl | 712 +++++++++++++++++++++++++++++++++++ 1 file changed, 712 insertions(+) create mode 100644 data-dumper-returns-undef.pl diff --git a/data-dumper-returns-undef.pl b/data-dumper-returns-undef.pl new file mode 100644 index 0000000..97eaaf1 --- /dev/null +++ b/data-dumper-returns-undef.pl @@ -0,0 +1,712 @@ +#!/usr/bin/perl -w + +# instructions +# +# run it as is, and the program segfaults (Data::Dumper returns undef) +# perl -w $0 | wc +# +# make it not use a sort sub, and program works; prints 18429 bytes to +# STDOUT: +# NO_SORT_SUB=1 perl -w $0 | wc +# +# run the bizarre workaround that slackorama found (issue #15 on gitolite's +# github repo), and the program works; prints 18429 bytes to STDOUT +# WTF=1 perl -w $0 | wc +# +# run it by populating the hash in one shot (not possible in real life), and +# the program works; prints 18429 bytes to STDOUT +# ONE_SHOT_SETUP=1 perl -w $0 | wc + +# step 1 -- setup the %repos hash +our %repos = (); +if (exists $ENV{ONE_SHOT_SETUP}) { + # do it in one shot, from a fully populated hash directly coded into the + # program. This is not useful as a workaround for us in real life but it + # may help debugging the actual problem + &one_shot_setup(); +} else { + # or do it in the sequence that gitolite "compile" step actually does + &real_life_setup(); +} + +use Data::Dumper; +$Data::Dumper::Indent = 1; +# sorting is not a problem... +$Data::Dumper::Sortkeys = 1; + +# ...but a custom sort sub is! Not calling one helps, and is the official +# workaround for this problem, currently... +$Data::Dumper::Sortkeys = sub { return [ reverse sort keys %{$_[0]} ]; } + unless exists $ENV{NO_SORT_SUB}; + +# ...or you could use this totally meaningless operation too! The bizarreness +# of this has prompted me to write this test program +if (exists $ENV{WTF}) { + for my $key (sort keys %repos) { + my @wtf = sort keys %{ $repos{$key} }; + } +} + +my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]); +print $dumped_data; +print STDERR "dumped " . length($dumped_data) . " bytes\n"; + +sub one_shot_setup +{ + # set up the %repos hash in one shot... in real life we cannot do this of + # course! + %repos = ('testing' => {'guser13' => [{'refs/.*' => + 'RW+'},{'refs/heads/master' => '-'}],'user88' => + [{'refs/heads/fun/' => 'RW+'},{'refs/.*' => 'R'}],'user1' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser76' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser25' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user4' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => + '-'},{'refs/heads/master' => 'RW+'}],'guser36' => [{'refs/.*' + => 'RW+'},{'refs/heads/master' => '-'}],'guser2' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser14' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser60' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser57' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser31' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser7' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser66' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser75' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser58' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser1' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser73' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser35' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser74' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user5' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => + '-'},{'refs/heads/master' => 'RW+'}],'guser11' => [{'refs/.*' + => 'RW+'},{'refs/heads/master' => '-'}],'guser33' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser53' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser5' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser4' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser85' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser50' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser38' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser59' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser56' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user3' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => + '-'},{'refs/heads/master' => 'RW'},{'refs/heads/master' => + 'RW+'}],'guser54' => [{'refs/.*' => + 'RW+'},{'refs/heads/master' => '-'}],'guser20' => [{'refs/.*' + => 'RW+'},{'refs/heads/master' => '-'}],'guser27' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user9' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser0' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser32' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user8' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser41' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser26' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser18' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser78' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser52' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser43' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser22' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser29' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser64' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser17' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser34' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user2' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => + '-'},{'refs/heads/master' => 'RW'},{'refs/heads/master' => + 'RW+'}],'guser82' => [{'refs/.*' => + 'RW+'},{'refs/heads/master' => '-'}],'guser86' => + [{'refs/heads/fun/' => 'RW+'},{'refs/.*' => 'R'}],'guser44' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser62' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser45' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser48' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser37' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user16' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => + '-'},{'refs/heads/master' => 'RW+'}],'user6' => [{'refs/.*' => + 'RW+'},{'refs/heads/master' => '-'}],'guser83' => [{'refs/.*' + => 'RW+'},{'refs/heads/master' => '-'}],'user7' => [{'refs/.*' + => 'RW+'},{'refs/heads/master' => '-'}],'user13' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => + '-'},{'refs/heads/master' => 'RW'}],'guser55' => [{'refs/.*' + => 'RW+'},{'refs/heads/master' => '-'}],'user10' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'W' => + {'guser13' => 1,'user88' => 1,'user1' => 1,'guser76' => + 1,'guser25' => 1,'user4' => 1,'guser36' => 1,'guser2' => + 1,'guser14' => 1,'guser60' => 1,'guser57' => 1,'guser31' => + 1,'guser7' => 1,'guser66' => 1,'guser75' => 1,'guser58' => + 1,'guser1' => 1,'guser73' => 1,'guser35' => 1,'guser74' => + 1,'user5' => 1,'guser11' => 1,'guser33' => 1,'guser53' => + 1,'guser5' => 1,'guser4' => 1,'guser85' => 1,'guser50' => + 1,'guser38' => 1,'guser59' => 1,'guser56' => 1,'user3' => + 1,'guser54' => 1,'guser20' => 1,'guser27' => 1,'user9' => + 1,'guser0' => 1,'guser32' => 1,'user8' => 1,'guser41' => + 1,'guser26' => 1,'guser18' => 1,'guser78' => 1,'guser52' => + 1,'guser43' => 1,'guser22' => 1,'guser29' => 1,'guser64' => + 1,'guser17' => 1,'guser34' => 1,'user2' => 1,'guser82' => + 1,'guser86' => 1,'guser44' => 1,'guser62' => 1,'guser45' => + 1,'guser48' => 1,'guser37' => 1,'user16' => 1,'user6' => + 1,'guser83' => 1,'user7' => 1,'user13' => 1,'guser55' => + 1,'user10' => 1,'guser9' => 1,'guser8' => 1,'guser23' => + 1,'guser51' => 1,'guser68' => 1,'guser6' => 1,'guser69' => + 1,'user12' => 1,'guser21' => 1,'guser87' => 1,'user11' => + 1,'guser77' => 1,'guser63' => 1,'guser39' => 1,'guser79' => + 1,'guser49' => 1,'guser3' => 1,'guser84' => 1,'guser80' => + 1,'guser65' => 1,'guser10' => 1,'guser12' => 1,'guser42' => + 1,'user15' => 1,'guser15' => 1,'guser71' => 1,'@all' => + 1,'guser47' => 1,'guser40' => 1,'guser70' => 1,'guser28' => + 1,'guser67' => 1,'grussell' => 1,'guser19' => 1,'guser61' => + 1,'user14' => 1,'guser16' => 1,'guser81' => 1,'guser72' => + 1,'guser46' => 1,'guser30' => 1,'guser24' => 1},'guser9' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser8' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser23' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser51' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser68' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser6' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser69' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user12' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => + '-'},{'refs/heads/master' => 'RW'}],'guser21' => [{'refs/.*' + => 'RW+'},{'refs/heads/master' => '-'}],'guser87' => + [{'refs/heads/fun/' => 'RW+'},{'refs/.*' => 'R'}],'user11' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => + '-'},{'refs/heads/master' => 'RW'}],'guser77' => [{'refs/.*' + => 'RW+'},{'refs/heads/master' => '-'}],'guser63' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser39' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser79' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser49' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser3' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser84' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser80' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser65' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser10' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser12' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser42' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user15' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => + '-'},{'refs/heads/master' => 'RW'}],'guser15' => [{'refs/.*' + => 'RW+'},{'refs/heads/master' => '-'}],'guser71' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'@all' => + [{'refs/.*' => 'RW+'}],'guser47' => [{'refs/.*' => + 'RW+'},{'refs/heads/master' => '-'}],'guser40' => [{'refs/.*' + => 'RW+'},{'refs/heads/master' => '-'}],'guser70' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser28' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser67' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'grussell' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => + '-'},{'refs/heads/master' => 'RW+'}],'guser19' => [{'refs/.*' + => 'RW+'},{'refs/heads/master' => '-'}],'guser61' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user14' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => + '-'},{'refs/heads/master' => 'RW'}],'guser16' => [{'refs/.*' + => 'RW+'},{'refs/heads/master' => '-'}],'guser81' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser72' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'R' => + {'guser13' => 1,'user88' => 1,'user1' => 1,'guser76' => + 1,'guser25' => 1,'user4' => 1,'guser36' => 1,'guser2' => + 1,'guser14' => 1,'guser60' => 1,'guser57' => 1,'guser31' => + 1,'guser7' => 1,'guser66' => 1,'guser75' => 1,'guser58' => + 1,'guser1' => 1,'guser73' => 1,'guser35' => 1,'guser74' => + 1,'user5' => 1,'guser11' => 1,'guser33' => 1,'guser53' => + 1,'guser5' => 1,'guser4' => 1,'guser85' => 1,'guser50' => + 1,'guser38' => 1,'guser59' => 1,'guser56' => 1,'user3' => + 1,'guser54' => 1,'guser20' => 1,'guser27' => 1,'user9' => + 1,'guser0' => 1,'guser32' => 1,'user8' => 1,'guser41' => + 1,'guser26' => 1,'guser18' => 1,'guser78' => 1,'guser52' => + 1,'guser43' => 1,'guser22' => 1,'guser29' => 1,'guser64' => + 1,'guser17' => 1,'guser34' => 1,'user2' => 1,'guser82' => + 1,'guser86' => 1,'guser44' => 1,'guser62' => 1,'guser45' => + 1,'guser48' => 1,'guser37' => 1,'user16' => 1,'user6' => + 1,'guser83' => 1,'user7' => 1,'user13' => 1,'guser55' => + 1,'user10' => 1,'guser9' => 1,'guser8' => 1,'guser23' => + 1,'guser51' => 1,'guser68' => 1,'guser6' => 1,'guser69' => + 1,'user12' => 1,'guser21' => 1,'guser87' => 1,'user11' => + 1,'guser77' => 1,'guser63' => 1,'guser39' => 1,'guser79' => + 1,'guser49' => 1,'guser3' => 1,'guser84' => 1,'guser80' => + 1,'guser65' => 1,'guser10' => 1,'guser12' => 1,'guser42' => + 1,'user15' => 1,'guser15' => 1,'guser71' => 1,'@all' => + 1,'guser47' => 1,'guser40' => 1,'guser70' => 1,'guser28' => + 1,'guser67' => 1,'grussell' => 1,'guser19' => 1,'guser61' => + 1,'user14' => 1,'guser16' => 1,'guser81' => 1,'guser72' => + 1,'guser46' => 1,'guser30' => 1,'guser24' => 1},'guser46' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser30' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser24' => + [{'refs/.*' => 'RW+'},{'refs/heads/master' => + '-'}]},'gitolite-admin' => {'W' => {'sitaram' => 1},'sitaram' + => [{'refs/.*' => 'RW+'}],'R' => {'sitaram' => 1}}); +} + +sub real_life_setup { + # set up the %repos hash in a manner that reflects a real run of + # gitolite's "compiler" script: + $repos{'gitolite-admin'}{R}{'sitaram'} = 1; + $repos{'gitolite-admin'}{W}{'sitaram'} = 1; + push @{ $repos{'gitolite-admin'}{'sitaram'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'@all'} = 1; + $repos{'testing'}{W}{'@all'} = 1; + push @{ $repos{'testing'}{'@all'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser86'} = 1; + $repos{'testing'}{W}{'guser86'} = 1; + push @{ $repos{'testing'}{'guser86'} }, { 'refs/heads/fun/' => 'RW+' }; + $repos{'testing'}{R}{'guser87'} = 1; + $repos{'testing'}{W}{'guser87'} = 1; + push @{ $repos{'testing'}{'guser87'} }, { 'refs/heads/fun/' => 'RW+' }; + $repos{'testing'}{R}{'user88'} = 1; + $repos{'testing'}{W}{'user88'} = 1; + push @{ $repos{'testing'}{'user88'} }, { 'refs/heads/fun/' => 'RW+' }; + $repos{'testing'}{R}{'guser86'} = 1; + push @{ $repos{'testing'}{'guser86'} }, { 'refs/.*' => 'R' }; + $repos{'testing'}{R}{'guser87'} = 1; + push @{ $repos{'testing'}{'guser87'} }, { 'refs/.*' => 'R' }; + $repos{'testing'}{R}{'user88'} = 1; + push @{ $repos{'testing'}{'user88'} }, { 'refs/.*' => 'R' }; + $repos{'testing'}{R}{'grussell'} = 1; + $repos{'testing'}{W}{'grussell'} = 1; + push @{ $repos{'testing'}{'grussell'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser0'} = 1; + $repos{'testing'}{W}{'guser0'} = 1; + push @{ $repos{'testing'}{'guser0'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser1'} = 1; + $repos{'testing'}{W}{'guser1'} = 1; + push @{ $repos{'testing'}{'guser1'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser10'} = 1; + $repos{'testing'}{W}{'guser10'} = 1; + push @{ $repos{'testing'}{'guser10'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser11'} = 1; + $repos{'testing'}{W}{'guser11'} = 1; + push @{ $repos{'testing'}{'guser11'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser12'} = 1; + $repos{'testing'}{W}{'guser12'} = 1; + push @{ $repos{'testing'}{'guser12'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser13'} = 1; + $repos{'testing'}{W}{'guser13'} = 1; + push @{ $repos{'testing'}{'guser13'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser14'} = 1; + $repos{'testing'}{W}{'guser14'} = 1; + push @{ $repos{'testing'}{'guser14'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser15'} = 1; + $repos{'testing'}{W}{'guser15'} = 1; + push @{ $repos{'testing'}{'guser15'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser16'} = 1; + $repos{'testing'}{W}{'guser16'} = 1; + push @{ $repos{'testing'}{'guser16'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser17'} = 1; + $repos{'testing'}{W}{'guser17'} = 1; + push @{ $repos{'testing'}{'guser17'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser18'} = 1; + $repos{'testing'}{W}{'guser18'} = 1; + push @{ $repos{'testing'}{'guser18'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser19'} = 1; + $repos{'testing'}{W}{'guser19'} = 1; + push @{ $repos{'testing'}{'guser19'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser2'} = 1; + $repos{'testing'}{W}{'guser2'} = 1; + push @{ $repos{'testing'}{'guser2'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser20'} = 1; + $repos{'testing'}{W}{'guser20'} = 1; + push @{ $repos{'testing'}{'guser20'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser21'} = 1; + $repos{'testing'}{W}{'guser21'} = 1; + push @{ $repos{'testing'}{'guser21'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser22'} = 1; + $repos{'testing'}{W}{'guser22'} = 1; + push @{ $repos{'testing'}{'guser22'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser23'} = 1; + $repos{'testing'}{W}{'guser23'} = 1; + push @{ $repos{'testing'}{'guser23'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser24'} = 1; + $repos{'testing'}{W}{'guser24'} = 1; + push @{ $repos{'testing'}{'guser24'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser25'} = 1; + $repos{'testing'}{W}{'guser25'} = 1; + push @{ $repos{'testing'}{'guser25'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser26'} = 1; + $repos{'testing'}{W}{'guser26'} = 1; + push @{ $repos{'testing'}{'guser26'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser27'} = 1; + $repos{'testing'}{W}{'guser27'} = 1; + push @{ $repos{'testing'}{'guser27'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser28'} = 1; + $repos{'testing'}{W}{'guser28'} = 1; + push @{ $repos{'testing'}{'guser28'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser29'} = 1; + $repos{'testing'}{W}{'guser29'} = 1; + push @{ $repos{'testing'}{'guser29'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser3'} = 1; + $repos{'testing'}{W}{'guser3'} = 1; + push @{ $repos{'testing'}{'guser3'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser30'} = 1; + $repos{'testing'}{W}{'guser30'} = 1; + push @{ $repos{'testing'}{'guser30'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser31'} = 1; + $repos{'testing'}{W}{'guser31'} = 1; + push @{ $repos{'testing'}{'guser31'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser32'} = 1; + $repos{'testing'}{W}{'guser32'} = 1; + push @{ $repos{'testing'}{'guser32'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser33'} = 1; + $repos{'testing'}{W}{'guser33'} = 1; + push @{ $repos{'testing'}{'guser33'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser34'} = 1; + $repos{'testing'}{W}{'guser34'} = 1; + push @{ $repos{'testing'}{'guser34'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser35'} = 1; + $repos{'testing'}{W}{'guser35'} = 1; + push @{ $repos{'testing'}{'guser35'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser36'} = 1; + $repos{'testing'}{W}{'guser36'} = 1; + push @{ $repos{'testing'}{'guser36'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser37'} = 1; + $repos{'testing'}{W}{'guser37'} = 1; + push @{ $repos{'testing'}{'guser37'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser38'} = 1; + $repos{'testing'}{W}{'guser38'} = 1; + push @{ $repos{'testing'}{'guser38'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser39'} = 1; + $repos{'testing'}{W}{'guser39'} = 1; + push @{ $repos{'testing'}{'guser39'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser4'} = 1; + $repos{'testing'}{W}{'guser4'} = 1; + push @{ $repos{'testing'}{'guser4'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser40'} = 1; + $repos{'testing'}{W}{'guser40'} = 1; + push @{ $repos{'testing'}{'guser40'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser41'} = 1; + $repos{'testing'}{W}{'guser41'} = 1; + push @{ $repos{'testing'}{'guser41'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser42'} = 1; + $repos{'testing'}{W}{'guser42'} = 1; + push @{ $repos{'testing'}{'guser42'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser43'} = 1; + $repos{'testing'}{W}{'guser43'} = 1; + push @{ $repos{'testing'}{'guser43'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser44'} = 1; + $repos{'testing'}{W}{'guser44'} = 1; + push @{ $repos{'testing'}{'guser44'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser45'} = 1; + $repos{'testing'}{W}{'guser45'} = 1; + push @{ $repos{'testing'}{'guser45'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser46'} = 1; + $repos{'testing'}{W}{'guser46'} = 1; + push @{ $repos{'testing'}{'guser46'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser47'} = 1; + $repos{'testing'}{W}{'guser47'} = 1; + push @{ $repos{'testing'}{'guser47'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser48'} = 1; + $repos{'testing'}{W}{'guser48'} = 1; + push @{ $repos{'testing'}{'guser48'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser49'} = 1; + $repos{'testing'}{W}{'guser49'} = 1; + push @{ $repos{'testing'}{'guser49'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser5'} = 1; + $repos{'testing'}{W}{'guser5'} = 1; + push @{ $repos{'testing'}{'guser5'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser50'} = 1; + $repos{'testing'}{W}{'guser50'} = 1; + push @{ $repos{'testing'}{'guser50'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser51'} = 1; + $repos{'testing'}{W}{'guser51'} = 1; + push @{ $repos{'testing'}{'guser51'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser52'} = 1; + $repos{'testing'}{W}{'guser52'} = 1; + push @{ $repos{'testing'}{'guser52'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser53'} = 1; + $repos{'testing'}{W}{'guser53'} = 1; + push @{ $repos{'testing'}{'guser53'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser54'} = 1; + $repos{'testing'}{W}{'guser54'} = 1; + push @{ $repos{'testing'}{'guser54'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser55'} = 1; + $repos{'testing'}{W}{'guser55'} = 1; + push @{ $repos{'testing'}{'guser55'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser56'} = 1; + $repos{'testing'}{W}{'guser56'} = 1; + push @{ $repos{'testing'}{'guser56'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser57'} = 1; + $repos{'testing'}{W}{'guser57'} = 1; + push @{ $repos{'testing'}{'guser57'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser58'} = 1; + $repos{'testing'}{W}{'guser58'} = 1; + push @{ $repos{'testing'}{'guser58'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser59'} = 1; + $repos{'testing'}{W}{'guser59'} = 1; + push @{ $repos{'testing'}{'guser59'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser6'} = 1; + $repos{'testing'}{W}{'guser6'} = 1; + push @{ $repos{'testing'}{'guser6'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser60'} = 1; + $repos{'testing'}{W}{'guser60'} = 1; + push @{ $repos{'testing'}{'guser60'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser61'} = 1; + $repos{'testing'}{W}{'guser61'} = 1; + push @{ $repos{'testing'}{'guser61'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser62'} = 1; + $repos{'testing'}{W}{'guser62'} = 1; + push @{ $repos{'testing'}{'guser62'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser63'} = 1; + $repos{'testing'}{W}{'guser63'} = 1; + push @{ $repos{'testing'}{'guser63'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser64'} = 1; + $repos{'testing'}{W}{'guser64'} = 1; + push @{ $repos{'testing'}{'guser64'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser65'} = 1; + $repos{'testing'}{W}{'guser65'} = 1; + push @{ $repos{'testing'}{'guser65'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser66'} = 1; + $repos{'testing'}{W}{'guser66'} = 1; + push @{ $repos{'testing'}{'guser66'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser67'} = 1; + $repos{'testing'}{W}{'guser67'} = 1; + push @{ $repos{'testing'}{'guser67'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser68'} = 1; + $repos{'testing'}{W}{'guser68'} = 1; + push @{ $repos{'testing'}{'guser68'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser69'} = 1; + $repos{'testing'}{W}{'guser69'} = 1; + push @{ $repos{'testing'}{'guser69'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser7'} = 1; + $repos{'testing'}{W}{'guser7'} = 1; + push @{ $repos{'testing'}{'guser7'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser70'} = 1; + $repos{'testing'}{W}{'guser70'} = 1; + push @{ $repos{'testing'}{'guser70'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser71'} = 1; + $repos{'testing'}{W}{'guser71'} = 1; + push @{ $repos{'testing'}{'guser71'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser72'} = 1; + $repos{'testing'}{W}{'guser72'} = 1; + push @{ $repos{'testing'}{'guser72'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser73'} = 1; + $repos{'testing'}{W}{'guser73'} = 1; + push @{ $repos{'testing'}{'guser73'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser74'} = 1; + $repos{'testing'}{W}{'guser74'} = 1; + push @{ $repos{'testing'}{'guser74'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser75'} = 1; + $repos{'testing'}{W}{'guser75'} = 1; + push @{ $repos{'testing'}{'guser75'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser76'} = 1; + $repos{'testing'}{W}{'guser76'} = 1; + push @{ $repos{'testing'}{'guser76'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser77'} = 1; + $repos{'testing'}{W}{'guser77'} = 1; + push @{ $repos{'testing'}{'guser77'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser78'} = 1; + $repos{'testing'}{W}{'guser78'} = 1; + push @{ $repos{'testing'}{'guser78'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser79'} = 1; + $repos{'testing'}{W}{'guser79'} = 1; + push @{ $repos{'testing'}{'guser79'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser8'} = 1; + $repos{'testing'}{W}{'guser8'} = 1; + push @{ $repos{'testing'}{'guser8'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser80'} = 1; + $repos{'testing'}{W}{'guser80'} = 1; + push @{ $repos{'testing'}{'guser80'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser81'} = 1; + $repos{'testing'}{W}{'guser81'} = 1; + push @{ $repos{'testing'}{'guser81'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser82'} = 1; + $repos{'testing'}{W}{'guser82'} = 1; + push @{ $repos{'testing'}{'guser82'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser83'} = 1; + $repos{'testing'}{W}{'guser83'} = 1; + push @{ $repos{'testing'}{'guser83'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser84'} = 1; + $repos{'testing'}{W}{'guser84'} = 1; + push @{ $repos{'testing'}{'guser84'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser85'} = 1; + $repos{'testing'}{W}{'guser85'} = 1; + push @{ $repos{'testing'}{'guser85'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'guser9'} = 1; + $repos{'testing'}{W}{'guser9'} = 1; + push @{ $repos{'testing'}{'guser9'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user1'} = 1; + $repos{'testing'}{W}{'user1'} = 1; + push @{ $repos{'testing'}{'user1'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user10'} = 1; + $repos{'testing'}{W}{'user10'} = 1; + push @{ $repos{'testing'}{'user10'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user11'} = 1; + $repos{'testing'}{W}{'user11'} = 1; + push @{ $repos{'testing'}{'user11'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user12'} = 1; + $repos{'testing'}{W}{'user12'} = 1; + push @{ $repos{'testing'}{'user12'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user13'} = 1; + $repos{'testing'}{W}{'user13'} = 1; + push @{ $repos{'testing'}{'user13'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user14'} = 1; + $repos{'testing'}{W}{'user14'} = 1; + push @{ $repos{'testing'}{'user14'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user15'} = 1; + $repos{'testing'}{W}{'user15'} = 1; + push @{ $repos{'testing'}{'user15'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user16'} = 1; + $repos{'testing'}{W}{'user16'} = 1; + push @{ $repos{'testing'}{'user16'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user2'} = 1; + $repos{'testing'}{W}{'user2'} = 1; + push @{ $repos{'testing'}{'user2'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user3'} = 1; + $repos{'testing'}{W}{'user3'} = 1; + push @{ $repos{'testing'}{'user3'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user4'} = 1; + $repos{'testing'}{W}{'user4'} = 1; + push @{ $repos{'testing'}{'user4'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user5'} = 1; + $repos{'testing'}{W}{'user5'} = 1; + push @{ $repos{'testing'}{'user5'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user6'} = 1; + $repos{'testing'}{W}{'user6'} = 1; + push @{ $repos{'testing'}{'user6'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user7'} = 1; + $repos{'testing'}{W}{'user7'} = 1; + push @{ $repos{'testing'}{'user7'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user8'} = 1; + $repos{'testing'}{W}{'user8'} = 1; + push @{ $repos{'testing'}{'user8'} }, { 'refs/.*' => 'RW+' }; + $repos{'testing'}{R}{'user9'} = 1; + $repos{'testing'}{W}{'user9'} = 1; + push @{ $repos{'testing'}{'user9'} }, { 'refs/.*' => 'RW+' }; + push @{ $repos{'testing'}{'grussell'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser0'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser1'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser10'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser11'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser12'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser13'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser14'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser15'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser16'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser17'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser18'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser19'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser2'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser20'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser21'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser22'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser23'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser24'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser25'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser26'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser27'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser28'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser29'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser3'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser30'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser31'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser32'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser33'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser34'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser35'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser36'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser37'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser38'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser39'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser4'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser40'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser41'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser42'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser43'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser44'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser45'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser46'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser47'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser48'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser49'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser5'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser50'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser51'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser52'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser53'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser54'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser55'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser56'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser57'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser58'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser59'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser6'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser60'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser61'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser62'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser63'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser64'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser65'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser66'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser67'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser68'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser69'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser7'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser70'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser71'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser72'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser73'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser74'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser75'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser76'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser77'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser78'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser79'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser8'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser80'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser81'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser82'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser83'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser84'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser85'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'guser9'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user1'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user10'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user11'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user12'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user13'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user14'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user15'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user16'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user2'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user3'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user4'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user5'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user6'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user7'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user8'} }, { 'refs/heads/master' => '-' }; + push @{ $repos{'testing'}{'user9'} }, { 'refs/heads/master' => '-' }; + $repos{'testing'}{R}{'user11'} = 1; + $repos{'testing'}{W}{'user11'} = 1; + push @{ $repos{'testing'}{'user11'} }, { 'refs/heads/master' => 'RW' }; + $repos{'testing'}{R}{'user12'} = 1; + $repos{'testing'}{W}{'user12'} = 1; + push @{ $repos{'testing'}{'user12'} }, { 'refs/heads/master' => 'RW' }; + $repos{'testing'}{R}{'user13'} = 1; + $repos{'testing'}{W}{'user13'} = 1; + push @{ $repos{'testing'}{'user13'} }, { 'refs/heads/master' => 'RW' }; + $repos{'testing'}{R}{'user14'} = 1; + $repos{'testing'}{W}{'user14'} = 1; + push @{ $repos{'testing'}{'user14'} }, { 'refs/heads/master' => 'RW' }; + $repos{'testing'}{R}{'user15'} = 1; + $repos{'testing'}{W}{'user15'} = 1; + push @{ $repos{'testing'}{'user15'} }, { 'refs/heads/master' => 'RW' }; + $repos{'testing'}{R}{'user2'} = 1; + $repos{'testing'}{W}{'user2'} = 1; + push @{ $repos{'testing'}{'user2'} }, { 'refs/heads/master' => 'RW' }; + $repos{'testing'}{R}{'user3'} = 1; + $repos{'testing'}{W}{'user3'} = 1; + push @{ $repos{'testing'}{'user3'} }, { 'refs/heads/master' => 'RW' }; + $repos{'testing'}{R}{'grussell'} = 1; + $repos{'testing'}{W}{'grussell'} = 1; + push @{ $repos{'testing'}{'grussell'} }, { 'refs/heads/master' => 'RW+' }; + $repos{'testing'}{R}{'user16'} = 1; + $repos{'testing'}{W}{'user16'} = 1; + push @{ $repos{'testing'}{'user16'} }, { 'refs/heads/master' => 'RW+' }; + $repos{'testing'}{R}{'user2'} = 1; + $repos{'testing'}{W}{'user2'} = 1; + push @{ $repos{'testing'}{'user2'} }, { 'refs/heads/master' => 'RW+' }; + $repos{'testing'}{R}{'user3'} = 1; + $repos{'testing'}{W}{'user3'} = 1; + push @{ $repos{'testing'}{'user3'} }, { 'refs/heads/master' => 'RW+' }; + $repos{'testing'}{R}{'user4'} = 1; + $repos{'testing'}{W}{'user4'} = 1; + push @{ $repos{'testing'}{'user4'} }, { 'refs/heads/master' => 'RW+' }; + $repos{'testing'}{R}{'user5'} = 1; + $repos{'testing'}{W}{'user5'} = 1; + push @{ $repos{'testing'}{'user5'} }, { 'refs/heads/master' => 'RW+' }; +} From 5fd06036edf408f81dbc33dff869f732c0b057e2 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Tue, 28 Feb 2012 22:15:45 +0530 Subject: [PATCH 287/637] empty From 60e190215e5e6defe593df8b3eb2e7d3bd409f46 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 8 Mar 2012 13:30:13 +0530 Subject: [PATCH 288/637] very basic, usable, first cut done - sausage making hidden - lots of important features missing --- Gitolite/Commands/QueryRc.pm | 81 +++++ Gitolite/Commands/Setup.pm | 161 +++++++++ Gitolite/Common.pm | 175 ++++++++++ Gitolite/Conf.pm | 183 ++++++++++ Gitolite/Conf/Load.pm | 169 ++++++++++ Gitolite/Conf/Store.pm | 356 ++++++++++++++++++++ Gitolite/Conf/Sugar.pm | 82 +++++ Gitolite/Hooks/PostUpdate.pm | 69 ++++ Gitolite/Hooks/Update.pm | 114 +++++++ Gitolite/Rc.pm | 112 +++++++ Gitolite/Test.pm | 34 ++ Gitolite/Test/Tsh.pm | 624 +++++++++++++++++++++++++++++++++++ g3-info | 35 ++ g3-install | 20 ++ gitolite | 54 +++ gitolite-shell | 57 ++++ src/gitolite | 106 ++++++ t/gitolite-receive-pack | 12 + t/gitolite-upload-pack | 12 + t/glt | 28 ++ t/t01-basic | 110 ++++++ 21 files changed, 2594 insertions(+) create mode 100644 Gitolite/Commands/QueryRc.pm create mode 100644 Gitolite/Commands/Setup.pm create mode 100644 Gitolite/Common.pm create mode 100644 Gitolite/Conf.pm create mode 100644 Gitolite/Conf/Load.pm create mode 100644 Gitolite/Conf/Store.pm create mode 100644 Gitolite/Conf/Sugar.pm create mode 100644 Gitolite/Hooks/PostUpdate.pm create mode 100644 Gitolite/Hooks/Update.pm create mode 100644 Gitolite/Rc.pm create mode 100644 Gitolite/Test.pm create mode 100644 Gitolite/Test/Tsh.pm create mode 100755 g3-info create mode 100755 g3-install create mode 100755 gitolite create mode 100755 gitolite-shell create mode 100755 src/gitolite create mode 100755 t/gitolite-receive-pack create mode 100755 t/gitolite-upload-pack create mode 100755 t/glt create mode 100755 t/t01-basic diff --git a/Gitolite/Commands/QueryRc.pm b/Gitolite/Commands/QueryRc.pm new file mode 100644 index 0000000..a36e4bd --- /dev/null +++ b/Gitolite/Commands/QueryRc.pm @@ -0,0 +1,81 @@ +package Gitolite::Commands::QueryRc; + +# implements 'gitolite query-rc' +# ---------------------------------------------------------------------- + +=for usage + +Usage: gitolite query-rc -a + gitolite query-rc + +Example: + + gitolite query-rc GL_ADMIN_BASE GL_UMASK + # prints "/home/git/.gitolite0077" or similar + + gitolite query-rc -a + # prints all known variables and values, one per line +=cut + +# ---------------------------------------------------------------------- + +@EXPORT = qw( + query_rc +); + +use Exporter 'import'; +use Getopt::Long; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Rc; +use Gitolite::Common; + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +my $all = 0; + +# ---------------------------------------------------------------------- + +sub query_rc { + trace( 1, "rc file not found; default should be " . glrc_default_filename() ) if not glrc_filename(); + + my @vars = args(); + + no strict 'refs'; + + if ( $vars[0] eq '-a' ) { + for my $e (@Gitolite::Rc::EXPORT) { + # perl-ism warning: if you don't do this the implicit aliasing + # screws up Rc's EXPORT list + my $v = $e; + # we stop on the first non-$ var + last unless $v =~ s/^\$//; + print "$v=" . ( defined($$v) ? $$v : 'undef' ) . "\n"; + } + } + + our $GL_BINDIR = $ENV{GL_BINDIR}; + + print join( "\t", map { $$_ } grep { $$_ } @vars ) . "\n" if @vars; +} + +# ---------------------------------------------------------------------- + +sub args { + my $help = 0; + + GetOptions( + 'all|a' => \$all, + 'help|h' => \$help, + ) or usage(); + + usage("'-a' cannot be combined with other arguments") if $all and @ARGV; + return '-a' if $all; + usage() if not @ARGV or $help; + return @ARGV; +} + +1; diff --git a/Gitolite/Commands/Setup.pm b/Gitolite/Commands/Setup.pm new file mode 100644 index 0000000..aa312ad --- /dev/null +++ b/Gitolite/Commands/Setup.pm @@ -0,0 +1,161 @@ +package Gitolite::Commands::Setup; + +# implements 'gitolite setup' +# ---------------------------------------------------------------------- + +=for usage +Usage: gitolite setup [] + + + -a, --admin admin user name + -pk --pubkey pubkey file name + -f, --fixup-hooks fixup hooks + +First run: + -a required + -pk required for ssh mode install + +Later runs: + no options required; but '-f' can be specified for clarity +=cut + +# ---------------------------------------------------------------------- + +@EXPORT = qw( + setup +); + +use Exporter 'import'; +use Getopt::Long; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Rc; +use Gitolite::Common; +use Gitolite::Conf::Store; + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +sub setup { + my ( $admin, $pubkey, $argv ) = args(); + # first time + if ( first_run() ) { + trace( 1, "..should happen only on first run" ); + setup_glrc(); + setup_gladmin( $admin, $pubkey, $argv ); + } + + system("$ENV{GL_BINDIR}/gitolite compile"); + + hook_repos(); # all of them, just to be sure +} + +# ---------------------------------------------------------------------- + +sub first_run { + # if the rc file could not be found, it's *definitely* a first run! + return not glrc_filename(); +} + +sub args { + my $admin = ''; + my $pubkey = ''; + my $fixup = 0; + my $help = 0; + my $argv = join( " ", @ARGV ); + + GetOptions( + 'admin|a=s' => \$admin, + 'pubkey|pk=s' => \$pubkey, + 'fixup-hooks|f' => \$fixup, + 'help|h' => \$help, + ) or usage(); + + usage() if $help; + usage("first run requires '-a'") if first_run() and not($admin); + _warn("not setting up ssh...") if first_run() and $admin and not $pubkey; + _warn("first run, ignoring '-f'...") if first_run() and $fixup; + _warn("not first run, ignoring '-a' / '-pk'...") if not first_run() and ( $admin or $pubkey ); + + if ($pubkey) { + $pubkey =~ /\.pub$/ or _die "$pubkey name does not end in .pub"; + tsh_try("cat $pubkey") or _die "$pubkey not a readable file"; + tsh_lines() == 1 or _die "$pubkey must have exactly one line"; + tsh_try("ssh-keygen -l -f $pubkey") or _die "$pubkey does not seem to be a valid ssh pubkey file"; + } + + return ( $admin || '', $pubkey || '', $argv ); +} + +sub setup_glrc { + trace(1); + _print( glrc_default_filename(), glrc_default_text() ); +} + +sub setup_gladmin { + my ( $admin, $pubkey, $argv ) = @_; + trace( 1, $admin ); + + # reminder: 'admin files' are in ~/.gitolite, 'admin repo' is + # $GL_REPO_BASE/gitolite-admin.git + + # grab the pubkey content before we chdir() away + + my $pubkey_content = ''; + if ($pubkey) { + $pubkey_content = slurp($pubkey); + $pubkey =~ s(.*/)(); # basename + } + + # set up the admin files in admin-base + + _mkdir($GL_ADMIN_BASE); + _chdir($GL_ADMIN_BASE); + + _mkdir("conf"); + my $conf; + { + local $/ = undef; + $conf = ; + } + $conf =~ s/%ADMIN/$admin/g; + + _print( "conf/gitolite.conf", $conf ); + + if ($pubkey) { + _mkdir("keydir"); + _print( "keydir/$pubkey", $pubkey_content ); + } + + # set up the admin repo in repo-base + + _chdir(); + _mkdir($GL_REPO_BASE); + _chdir($GL_REPO_BASE); + + new_repo("gitolite-admin"); + + # commit the admin files to the admin repo + + $ENV{GIT_WORK_TREE} = $GL_ADMIN_BASE; + _chdir("$GL_REPO_BASE/gitolite-admin.git"); + system("git add conf/gitolite.conf"); + system("git add keydir") if $pubkey; + tsh_try("git config --get user.email") or tsh_run( "git config user.email $ENV{USER}\@" . `hostname` ); + tsh_try("git config --get user.name") or tsh_run( "git config user.name '$ENV{USER} on '" . `hostname` ); + tsh_try("git diff --cached --quiet") + or tsh_try("git commit -am 'gl-setup $argv'") + or die "setup failed to commit to the admin repo"; + delete $ENV{GIT_WORK_TREE}; +} + +1; + +__DATA__ +repo gitolite-admin + RW+ = %ADMIN + +repo testing + RW+ = @all diff --git a/Gitolite/Common.pm b/Gitolite/Common.pm new file mode 100644 index 0000000..e5d492a --- /dev/null +++ b/Gitolite/Common.pm @@ -0,0 +1,175 @@ +package Gitolite::Common; + +# common (non-gitolite-specific) functions +# ---------------------------------------------------------------------- + +#<<< +@EXPORT = qw( + print2 dbg _mkdir _open ln_sf tsh_rc sort_u + say _warn _chdir _print tsh_text + say2 _die slurp tsh_lines + trace tsh_try + usage tsh_run +); +#>>> +use Exporter 'import'; +use File::Path qw(mkpath); +use Carp qw(carp cluck croak confess); + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +sub print2 { + local $/ = "\n"; + print STDERR @_; +} + +sub say { + local $/ = "\n"; + print @_, "\n"; +} + +sub say2 { + local $/ = "\n"; + print STDERR @_, "\n"; +} + +sub trace { + return unless defined( $ENV{D} ); + + my $level = shift; + my $args = ''; $args = join( ", ", @_ ) if @_; + my $sub = ( caller 1 )[3] || ''; $sub =~ s/.*://; $sub .= ' ' x ( 32 - length($sub) ); + say2 "TRACE $level $sub", $args if $ENV{D} >= $level; +} + +sub dbg { + use Data::Dumper; + return unless defined( $ENV{D} ); + for my $i (@_) { + print STDERR "DBG: " . Dumper($i); + } +} + +sub _warn { + if ( $ENV{D} and $ENV{D} >= 3 ) { + cluck "WARNING: ", @_, "\n"; + } elsif ( defined( $ENV{D} ) ) { + carp "WARNING: ", @_, "\n"; + } else { + warn "WARNING: ", @_, "\n"; + } +} + +sub _die { + if ( $ENV{D} and $ENV{D} >= 3 ) { + confess "FATAL: " . join( ",", @_ ) . "\n" if defined( $ENV{D} ); + } elsif ( defined( $ENV{D} ) ) { + croak "FATAL: " . join( ",", @_ ) . "\n"; + } else { + die "FATAL: " . join( ",", @_ ) . "\n"; + } +} + +sub usage { + _warn(shift) if @_; + my $scriptname = ( caller() )[1]; + my $script = slurp($scriptname); + $script =~ /^=for usage(.*?)^=cut/sm; + say2( $1 ? $1 : "...no usage message in $scriptname" ); + exit 1; +} + +sub _mkdir { + # it's not an error if the directory exists, but it is an error if it + # doesn't exist and we can't create it + my $dir = shift; + my $perm = shift; # optional + return if -d $dir; + mkpath($dir); + chmod $perm, $dir if $perm; + return 1; +} + +sub _chdir { + chdir( $_[0] || $ENV{HOME} ) or _die "chdir $_[0] failed: $!\n"; +} + +sub _open { + open( my $fh, $_[0], $_[1] ) or _die "open $_[1] failed: $!\n"; + return $fh; +} + +sub _print { + my ( $file, @text ) = @_; + my $fh = _open( ">", "$file.$$" ); + print $fh @text; + close($fh) or _die "close $file failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; + my $oldmode = ( ( stat $file )[2] ); + rename "$file.$$", $file; + chmod $oldmode, $file if $oldmode; +} + +sub slurp { + local $/ = undef; + my $fh = _open( "<", $_[0] ); + return <$fh>; +} + +sub dos2unix { + # WARNING: when calling this, make sure you supply a list context + s/\r\n/\n/g for @_; + return @_; +} + +sub ln_sf { + trace( 4, @_ ); + my ( $srcdir, $glob, $dstdir ) = @_; + for my $hook ( glob("$srcdir/$glob") ) { + $hook =~ s/$srcdir\///; + unlink "$dstdir/$hook"; + symlink "$srcdir/$hook", "$dstdir/$hook" or croak "could not symlink $srcdir/$hook to $dstdir\n"; + } +} + +sub sort_u { + my %uniq; + my $listref = shift; + return [] unless @{ $listref }; + undef @uniq{ @{ $listref } }; # expect a listref + my @sort_u = sort keys %uniq; + return \@sort_u; +} +# ---------------------------------------------------------------------- + +# bare-minimum subset of 'Tsh' (see github.com/sitaramc/tsh) +{ + my ( $rc, $text ); + sub tsh_rc { return $rc || 0; } + sub tsh_text { return $text || ''; } + sub tsh_lines { return split /\n/, $text; } + + sub tsh_try { + my $cmd = shift; die "try: expects only one argument" if @_; + $text = `( $cmd ) 2>&1; echo -n RC=\$?`; + if ( $text =~ s/RC=(\d+)$// ) { + $rc = $1; + trace( 4, $text ); + return ( not $rc ); + } + die "couldnt find RC= in result; this should not happen:\n$text\n\n...\n"; + } + + sub tsh_run { + open( my $fh, "-|", @_ ) or die "popen failed: $!"; + local $/ = undef; $text = <$fh>; + close $fh; warn "pclose failed: $!" if $!; + $rc = ( $? >> 8 ); + trace( 4, $text ); + return $text; + } +} + +1; diff --git a/Gitolite/Conf.pm b/Gitolite/Conf.pm new file mode 100644 index 0000000..8f7e111 --- /dev/null +++ b/Gitolite/Conf.pm @@ -0,0 +1,183 @@ +package Gitolite::Conf; + +# explode/parse a conf file +# ---------------------------------------------------------------------- + +@EXPORT = qw( + compile + explode + parse +); + +use Exporter 'import'; +use Getopt::Long; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Common; +use Gitolite::Rc; +use Gitolite::Conf::Sugar; +use Gitolite::Conf::Store; + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +# 'seen' for include/subconf files +my %included = (); +# 'seen' for group names on LHS +my %prefixed_groupname = (); + +# ---------------------------------------------------------------------- + +sub compile { + trace(3); + # XXX assume we're in admin-base/conf + + _chdir($GL_ADMIN_BASE); + _chdir("conf"); + + explode( 'gitolite.conf', 'master', \&parse ); + + # the order matters; new repos should be created first, to give store a + # place to put the individual gl-conf files + new_repos(); + store(); +} + +sub explode { + trace( 4, @_ ); + my ( $file, $subconf, $parser ) = @_; + + # $parser is a ref to a callback; if not supplied we just print + $parser ||= sub { print shift, "\n"; }; + + # seed the 'seen' list if it's empty + $included{ device_inode("conf/gitolite.conf") }++ unless %included; + + my $fh = _open( "<", $file ); + my @fh = <$fh>; + my @lines = macro_expand( "# BEGIN $file\n", @fh, "# END $file\n" ); + my $line; + while (@lines) { + $line = shift @lines; + + $line = cleanup_conf_line($line); + next unless $line =~ /\S/; + + $line = prefix_groupnames( $line, $subconf ) if $subconf ne 'master'; + + if ( $line =~ /^(include|subconf) "(.+)"$/ or $line =~ /^(include|subconf) '(.+)'$/ ) { + incsub( $1, $2, $subconf, $parser ); + } else { + # normal line, send it to the callback function + $parser->($line); + } + } +} + +sub parse { + trace( 4, @_ ); + my $line = shift; + + # user or repo groups + if ( $line =~ /^(@\S+) = (.*)/ ) { + add_to_group( $1, split( ' ', $2 ) ); + } elsif ( $line =~ /^repo (.*)/ ) { + set_repolist( split( ' ', $1 ) ); + } elsif ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) { + my $perm = $1; + my @refs = parse_refs( $2 || '' ); + my @users = parse_users($3); + + # XXX what do we do? s/\bCREAT[EO]R\b/~\$creator/g for @users; + + for my $ref (@refs) { + for my $user (@users) { + add_rule( $perm, $ref, $user ); + } + } + } elsif ( $line =~ /^config (.+) = ?(.*)/ ) { + my ( $key, $value ) = ( $1, $2 ); + my @validkeys = split( ' ', ( $GL_GITCONFIG_KEYS || '' ) ); + push @validkeys, "gitolite-options\\..*"; + my @matched = grep { $key =~ /^$_$/ } @validkeys; + # XXX move this also to add_config: _die "git config $key not allowed\ncheck GL_GITCONFIG_KEYS in the rc file for how to allow it" if (@matched < 1); + # XXX both $key and $value must satisfy a liberal but secure pattern + add_config( 1, $key, $value ); + } elsif ( $line =~ /^subconf (\S+)$/ ) { + set_subconf($1); + } else { + _warn "?? $line"; + } +} + +# ---------------------------------------------------------------------- + +sub incsub { + my $is_subconf = ( +shift eq 'subconf' ); + my ( $include_glob, $subconf, $parser ) = @_; + + _die "subconf $subconf attempting to run 'subconf'\n" if $is_subconf and $subconf ne 'master'; + + # XXX move this to Macros... substitute HOSTNAME word if GL_HOSTNAME defined, otherwise leave as is + # $include_glob =~ s/\bHOSTNAME\b/$GL_HOSTNAME/ if $GL_HOSTNAME; + + # XXX g2 diff: include glob is *implicitly* from $GL_ADMIN_BASE/conf, not *explicitly* + # for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$GL_ADMIN_BASE/conf/$include_glob")) { + + trace( 3, $is_subconf, $include_glob ); + + for my $file ( glob($include_glob) ) { + _warn("included file not found: '$file'"), next unless -f $file; + _die "invalid include/subconf filename $file" unless $file =~ m(([^/]+).conf$); + my $basename = $1; + + next if already_included($file); + + if ($is_subconf) { + $parser->("subconf $basename"); + explode( $file, $basename, $parser ); + $parser->("subconf $subconf"); + # XXX g2 delegaton compat: deal with this: $subconf_seen++; + } else { + explode( $file, $subconf, $parser ); + } + } +} + +sub prefix_groupnames { + my ( $line, $subconf ) = @_; + + my $lhs = ''; + # save 'foo' if it's an '@foo = list' line + $lhs = $1 if $line =~ /^@(\S+) = /; + # prefix all @groups in the line + $line =~ s/(^| )(@\S+)(?= |$)/ $1 . ($prefixed_groupname{$subconf}{$2} || $2) /ge; + # now prefix the LHS and store it if needed + if ($lhs) { + $line =~ s/^@\S+ = /"\@$subconf.$lhs = "/e; + trace( 3, "prefixed_groupname.$subconf.\@$lhs = \@$subconf.$lhs" ); + } + + return $line; +} + +sub already_included { + my $file = shift; + + my $file_id = device_inode($file); + return 0 unless $included{$file_id}++; + + _warn("$file already included"); + trace( 3, "$file already included" ); + return 1; +} + +sub device_inode { + my $file = shift; + trace( 3, $file, ( stat $file )[ 0, 1 ] ); + return join( "/", ( stat $file )[ 0, 1 ] ); +} + +1; diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm new file mode 100644 index 0000000..db11679 --- /dev/null +++ b/Gitolite/Conf/Load.pm @@ -0,0 +1,169 @@ +package Gitolite::Conf::Load; + +# load conf data from stored files +# ---------------------------------------------------------------------- + +@EXPORT = qw( + load + access +); + +use Exporter 'import'; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Common; +use Gitolite::Rc; + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +my $subconf = 'master'; + +# our variables, because they get loaded by a 'do' +our $data_version = ''; +our %repos; +our %one_repo; +our %groups; +our %configs; +our %one_config; +our %split_conf; + +# helps maintain the "cache" in both "load_common" and "load_1" +my $last_repo = ''; + +# ---------------------------------------------------------------------- + +{ + my $loaded_repo = ''; + + sub load { + my $repo = shift or _die "load() needs a reponame"; + trace( 4, "$repo" ); + if ( $repo ne $loaded_repo ) { + trace( 3, "loading $repo..." ); + _chdir("$GL_ADMIN_BASE"); load_common(); + _chdir("$GL_REPO_BASE"); load_1($repo); + $loaded_repo = $repo; + } + } +} + +sub access { + my ( $repo, $user, $aa, $ref ) = @_; + trace( 3, "repo=$repo, user=$user, aa=$aa, ref=$ref" ); + load($repo); + + my @rules = rules( $repo, $user ); + trace( 3, scalar(@rules) . " rules found" ); + for my $r (@rules) { + my $perm = $r->[1]; + my $refex = $r->[2]; + trace( 4, "perm=$perm, refex=$refex" ); + + # skip 'deny' rules if the ref is not (yet) known + next if $perm eq '-' and $ref eq 'unknown'; + + # rule matches if ref matches or ref is unknown (see gitolite-shell) + next unless $ref =~ /^$refex/ or $ref eq 'unknown'; + + trace( 3, "DENIED by $refex" ) if $perm eq '-'; + return "DENIED: $aa access to $repo by $user (rule: $refex)" if $perm eq '-'; + + # $perm can be RW\+?(C|D|CD|DC)?M?. $aa can be W, +, C or D, or + # any of these followed by "M". + ( my $aaq = $aa ) =~ s/\+/\\+/; + $aaq =~ s/M/.*M/; + # as far as *this* ref is concerned we're ok + return $refex if ( $perm =~ /$aaq/ ); + } + trace( 3, "DENIED by fallthru" ); + return "DENIED: $aa access to $repo by $user (fallthru)"; +} + +# ---------------------------------------------------------------------- + +sub load_common { + + # we take an unusual approach to caching this function! + # (requires that first call to load_common is before first call to load_1) + if ( $last_repo and $split_conf{$last_repo} ) { + delete $repos{$last_repo}; + delete $configs{$last_repo}; + return; + } + + trace(4); + my $cc = "conf/gitolite.conf-compiled.pm"; + + _die "parse $cc failed: " . ( $! or $@ ) unless do $cc; + + if ( data_version_mismatch() ) { + system("gitolite setup"); + _die "parse $cc failed: " . ( $! or $@ ) unless do $cc; + _die "data version update failed; this is serious" if data_version_mismatch(); + } +} + +sub load_1 { + my $repo = shift; + trace( 4, $repo ); + + if ( $repo eq $last_repo ) { + $repos{$repo} = $one_repo{$repo}; + $configs{$repo} = $one_config{$repo} if $one_config{$repo}; + return; + } + + if ( -f "$repo.git/gl-conf" ) { + _die "split conf not set, gl-conf present for $repo" if not $split_conf{$repo}; + + my $cc = "$repo.git/gl-conf"; + _die "parse $cc failed: " . ( $! or $@ ) unless do $cc; + + $last_repo = $repo; + $repos{$repo} = $one_repo{$repo}; + $configs{$repo} = $one_config{$repo} if $one_config{$repo}; + } else { + _die "split conf set, gl-conf not present for $repo" if $split_conf{$repo}; + } +} + +sub rules { + my ( $repo, $user ) = @_; + trace( 4, "repo=$repo, user=$user" ); + my @rules = (); + + my @repos = memberships($repo); + my @users = memberships($user); + trace( 4, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" ); + + for my $r (@repos) { + for my $u (@users) { + push @rules, @{ $repos{$r}{$u} } if exists $repos{$r}{$u}; + } + } + + # dbg("before sorting rules:", \@rules); + @rules = sort { $a->[0] <=> $b->[0] } @rules; + # dbg("after sorting rules:", \@rules); + + return @rules; +} + +sub memberships { + my $item = shift; + + my @ret = ( $item, '@all' ); + push @ret, @{ $groups{$item} } if $groups{$item}; + + return @ret; +} + +sub data_version_mismatch { + return $data_version ne $current_data_version; +} + +1; + diff --git a/Gitolite/Conf/Store.pm b/Gitolite/Conf/Store.pm new file mode 100644 index 0000000..69056a0 --- /dev/null +++ b/Gitolite/Conf/Store.pm @@ -0,0 +1,356 @@ +package Gitolite::Conf::Store; + +# receive parsed conf data and store it +# ---------------------------------------------------------------------- + +@EXPORT = qw( + add_to_group + expand_list + set_repolist + parse_refs + parse_users + add_rule + set_subconf + new_repos + new_repo + hook_repos + store +); + +use Exporter 'import'; +use Data::Dumper; +$Data::Dumper::Indent = 1; +$Data::Dumper::Sortkeys = 1; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Common; +use Gitolite::Rc; +use Gitolite::Hooks::Update; +use Gitolite::Hooks::PostUpdate; + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +my %repos; +my %groups; +my %configs; +my %split_conf; + +my @repolist; # current repo list; reset on each 'repo ...' line +my $subconf = 'master'; +my $ruleseq = 0; +my %ignored; +# XXX you still have to "warn" if this has any entries + +# ---------------------------------------------------------------------- + +sub add_to_group { + my ( $lhs, @rhs ) = @_; + _die "bad group '$lhs'" unless $lhs =~ $REPONAME_PATT; + + # store the group association, but overload it to keep track of when + # the group was *first* created by using $subconf as the *value* + do { $groups{$lhs}{$_} ||= $subconf } + for ( expand_list(@rhs) ); + + # create the group hash even if empty + $groups{$lhs} = {} unless $groups{$lhs}; +} + +sub expand_list { + my @list = @_; + my @new_list = (); + + for my $item (@list) { + if ( $item =~ /^@/ and $item ne '@all' ) # nested group + { + _die "undefined group $item" unless $groups{$item}; + # add those names to the list + push @new_list, sort keys %{ $groups{$item} }; + } else { + push @new_list, $item; + } + } + + return @new_list; +} + +sub set_repolist { + @repolist = @_; + + # ...sanity checks + for (@repolist) { + _warn "explicit '.git' extension ignored for $_.git" if s/\.git$//; + _die "bad reponame '$_'" if $_ !~ $REPOPATT_PATT; + } + # XXX -- how do we deal with this? s/\bCREAT[EO]R\b/\$creator/g for @{ $repos_p }; +} + +sub parse_refs { + my $refs = shift; + my @refs; @refs = split( ' ', $refs ) if $refs; + @refs = expand_list(@refs); + + # if no ref is given, this PERM applies to all refs + @refs = qw(refs/.*) unless @refs; + + # fully qualify refs that dont start with "refs/" or "NAME/" or "VREF/"; + # prefix them with "refs/heads/" + @refs = map { m(^(refs|NAME|VREF)/) or s(^)(refs/heads/); $_ } @refs; + # XXX what do we do? @refs = map { s(/USER/)(/\$gl_user/); $_ } @refs; + + return @refs; +} + +sub parse_users { + my $users = shift; + my @users = split ' ', $users; + do { _die "bad username '$_'" unless $_ =~ $USERNAME_PATT } + for @users; + + return @users; +} + +sub add_rule { + my ( $perm, $ref, $user ) = @_; + + $ruleseq++; + for my $repo (@repolist) { + if ( check_subconf_repo_disallowed( $subconf, $repo ) ) { + my $repo = $repo; + $repo =~ s/^\@$subconf\./locally modified \@/; + $ignored{$subconf}{$repo} = 1; + next; + } + + push @{ $repos{$repo}{$user} }, [ $ruleseq, $perm, $ref ]; + + # XXX g2 diff: we're not doing a lint check for usernames versus pubkeys; + # maybe we can add that later + + # XXX to do: C/R/W, then CREATE_IS_C, etc + # XXX to do: also NAME_LIMITS + # XXX and hacks like $creator -> "$creatror - wild" + + # XXX consider if you want to use rurp_seen; initially no + } +} + +sub set_subconf { + $subconf = shift; + trace( 1, $subconf ); +} + +sub new_repos { + trace(3); + _chdir($GL_REPO_BASE); + + # normal repos + my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } sort keys %repos; + # add in members of repo groups + map { push @repos, keys %{ $groups{$_} } } grep { /^@/ } keys %repos; + + for my $repo ( @{ sort_u(\@repos) } ) { + next unless $repo =~ $REPONAME_PATT; # skip repo patterns + next if $repo =~ m(^\@|EXTCMD/); # skip groups and fake repos + + # XXX how do we deal with GL_NO_CREATE_REPOS? + new_repo($repo) if not -d "$repo.git"; + } +} + +sub new_repo { + my $repo = shift; + trace( 4, $repo ); + + # XXX ignoring UMASK for now + + _mkdir("$repo.git"); + _chdir("$repo.git"); + system("git init --bare >&2"); + _chdir($GL_REPO_BASE); + hook_1($repo); + + # XXX ignoring creator for now + # XXX ignoring gl-post-init for now +} + +sub hook_repos { + trace(3); + # all repos, all hooks + _chdir($GL_REPO_BASE); + + # XXX g2 diff: we now don't care if it's a symlink -- it's upto the admin + # on the server to make sure things are kosher + for my $repo (`find . -name "*.git" -prune`) { + chomp($repo); + $repo =~ s/\.git$//; + hook_1($repo); + } +} + +sub store { + trace(3); + + # first write out the ones for the physical repos + my @phy_repos = list_physical_repos(1); + + _chdir($GL_REPO_BASE); + for my $repo (@phy_repos) { + store_1($repo); + } + + _chdir($GL_ADMIN_BASE); + store_common(); +} + +# ---------------------------------------------------------------------- + +sub check_subconf_repo_disallowed { + # trying to set access for $repo (='foo')... + my ( $subconf, $repo ) = @_; + + # processing the master config, not a subconf + return 0 if $subconf eq 'master'; + # subconf is also called 'foo' (you're allowed to have a + # subconf that is only concerned with one repo) + return 0 if $subconf eq $repo; + # same thing in big-config-land; foo is just @foo now + return 0 if ( "\@$subconf" eq $repo ); + my @matched = grep { $repo =~ /^$_$/ } + grep { $groups{"\@$subconf"}{$_} eq 'master' } + sort keys %{ $groups{"\@$subconf"} }; + return 0 if @matched > 0; + + trace( 3, "disallowed: $subconf for $repo" ); + return 1; +} + +{ + my @phy_repos = (); + + sub list_physical_repos { + trace(3); + _chdir($GL_REPO_BASE); + + # use cached value only if it exists *and* no arg was received (i.e., + # receiving *any* arg invalidates cache) + return @phy_repos if ( @phy_repos and not @_ ); + + for my $repo (`find . -name "*.git" -prune`) { + chomp($repo); + $repo =~ s(\./(.*)\.git$)($1); + push @phy_repos, $repo; + } + return @phy_repos; + } +} + +sub store_1 { + # warning: writes and *deletes* it from %repos and %configs + my ($repo) = shift; + trace( 4, $repo ); + return unless $repos{$repo} and -d "$repo.git"; + + my ( %one_repo, %one_config ); + + open( my $compiled_fh, ">", "$repo.git/gl-conf" ) or return; + + $one_repo{$repo} = $repos{$repo}; + delete $repos{$repo}; + my $dumped_data = Data::Dumper->Dump( [ \%one_repo ], [qw(*one_repo)] ); + + if ( $configs{$repo} ) { + $one_config{$repo} = $configs{$repo}; + delete $configs{$repo}; + $dumped_data .= Data::Dumper->Dump( [ \%one_config ], [qw(*one_config)] ); + } + + # XXX deal with this better now + # $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g; + print $compiled_fh $dumped_data; + close $compiled_fh; + + $split_conf{$repo} = 1; +} + +sub store_common { + trace(4); + my $cc = "conf/gitolite.conf-compiled.pm"; + my $compiled_fh = _open( ">", "$cc.new" ); + + my $data_version = $current_data_version; + trace( 1, "data_version = $data_version" ); + print $compiled_fh Data::Dumper->Dump( [$data_version], [qw(*data_version)] ); + + my $dumped_data = Data::Dumper->Dump( [ \%repos ], [qw(*repos)] ); + $dumped_data .= Data::Dumper->Dump( [ \%configs ], [qw(*configs)] ) if %configs; + + # XXX and again... + # XXX $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g; + + print $compiled_fh $dumped_data; + + if (%groups) { + my %groups = %{ inside_out( \%groups ) }; + $dumped_data = Data::Dumper->Dump( [ \%groups ], [qw(*groups)] ); + # XXX $dumped_data =~ s/\bCREAT[EO]R\b/\$creator/g; + # XXX $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g; + print $compiled_fh $dumped_data; + } + print $compiled_fh Data::Dumper->Dump( [ \%split_conf ], [qw(*split_conf)] ) if %split_conf; + + close $compiled_fh or _die "close compiled-conf failed: $!\n"; + rename "$cc.new", $cc; +} + +{ + my $hook_reset = 0; + + sub hook_1 { + my $repo = shift; + trace( 4, $repo ); + + # reset the gitolite supplied hooks, in case someone fiddled with + # them, but only once per run + if ( not $hook_reset ) { + _mkdir("$GL_ADMIN_BASE/hooks/common"); + _mkdir("$GL_ADMIN_BASE/hooks/gitolite-admin"); + _print( "$GL_ADMIN_BASE/hooks/common/update", update_hook() ); + _print( "$GL_ADMIN_BASE/hooks/gitolite-admin/post-update", post_update_hook() ); + chmod 0755, "$GL_ADMIN_BASE/hooks/common/update"; + chmod 0755, "$GL_ADMIN_BASE/hooks/gitolite-admin/post-update"; + $hook_reset++; + } + + # propagate user hooks + ln_sf( "$GL_ADMIN_BASE/hooks/common", "*", "$repo.git/hooks" ); + + # propagate admin hook + ln_sf( "$GL_ADMIN_BASE/hooks/gitolite-admin", "*", "$repo.git/hooks" ) if $repo eq 'gitolite-admin'; + + # g2 diff: no "site-wide" hooks (the stuff in between gitolite hooks + # and user hooks) anymore. I don't think anyone used them anyway... + } +} + +sub inside_out { + my $href = shift; + # input conf: @aa = bb cc @bb = @aa dd + + my %ret = (); + while ( my ( $k, $v ) = each( %{$href} ) ) { + # $k is '@aa', $v is a href + for my $k2 ( keys %{$v} ) { + # $k2 is bb, then cc + push @{ $ret{$k2} }, $k; + } + } + return \%ret; + # %groups = ( 'bb' => [ '@bb', '@aa' ], 'cc' => [ '@bb', '@aa' ], 'dd' => [ '@bb' ]); +} + +1; + diff --git a/Gitolite/Conf/Sugar.pm b/Gitolite/Conf/Sugar.pm new file mode 100644 index 0000000..5db96f2 --- /dev/null +++ b/Gitolite/Conf/Sugar.pm @@ -0,0 +1,82 @@ +package Gitolite::Conf::Sugar; + +# syntactic sugar for the conf file, including site-local macros +# ---------------------------------------------------------------------- + +@EXPORT = qw( + macro_expand + cleanup_conf_line +); + +use Exporter 'import'; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Common; +use Gitolite::Rc; + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +sub macro_expand { + # site-local macros, if any, then gitolite internal macros, to munge the + # input conf line if needed + + my @lines = @_; + + # TODO: user macros, how to allow the user to specify them? + + # cheat, to keep *our* regexes simple :) + # XXX but this also kills the special '# BEGIN filename' and '# END + # filename' lines that explode() surrounds the actual data with when it + # called macro_expand(). Right now we don't need it, but... + @lines = grep /\S/, map { cleanup_conf_line($_) } @lines; + + @lines = owner_desc(@lines); + + return @lines; +} + +sub cleanup_conf_line { + my $line = shift; + + # kill comments, but take care of "#" inside *simple* strings + $line =~ s/^((".*?"|[^#"])*)#.*/$1/; + # normalise whitespace; keeps later regexes very simple + $line =~ s/=/ = /; + $line =~ s/\s+/ /g; + $line =~ s/^ //; + $line =~ s/ $//; + return $line; +} + +sub owner_desc { + my @lines = @_; + my @ret; + + for my $line (@lines) { + # reponame = "some description string" + # reponame "owner name" = "some description string" + if ( $line =~ /^(\S+)(?: "(.*?)")? = "(.*)"$/ ) { + my ( $repo, $owner, $desc ) = ( $1, $2, $3 ); + # XXX these two checks should go into add_config + # _die "bad repo name '$repo'" unless $repo =~ $REPONAME_PATT; + # _die "$fragment attempting to set description for $repo" + # if check_fragment_repo_disallowed( $fragment, $repo ); + push @ret, "config gitolite-options.repo-desc = $desc"; + push @ret, "config gitolite-options.repo-owner = $owner" if $owner; + } elsif ( $line =~ /^desc = (\S.*)/ ) { + push @ret, "config gitolite-options.repo-desc = $1"; + } elsif ( $line =~ /^owner = (\S.*)/ ) { + my ( $repo, $owner, $desc ) = ( $1, $2, $3 ); + push @ret, "config gitolite-options.repo-owner = $1"; + } else { + push @ret, $line; + } + } + return @ret; +} + +1; + diff --git a/Gitolite/Hooks/PostUpdate.pm b/Gitolite/Hooks/PostUpdate.pm new file mode 100644 index 0000000..813733f --- /dev/null +++ b/Gitolite/Hooks/PostUpdate.pm @@ -0,0 +1,69 @@ +package Gitolite::Hooks::PostUpdate; + +# everything to do with the post-update hook +# ---------------------------------------------------------------------- + +@EXPORT = qw( + post_update + post_update_hook +); + +use Exporter 'import'; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Rc; +use Gitolite::Common; + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +sub post_update { + trace(3); + # this is the *real* post_update hook for gitolite + + tsh_try("git ls-tree --name-only master"); + _die "no files/dirs called 'hooks' or 'logs' are allowed" if tsh_text() =~ /^(hooks|logs)$/; + + { + local $ENV{GIT_WORK_TREE} = $GL_ADMIN_BASE; + tsh_try("git checkout -f --quiet master"); + } + system("$ENV{GL_BINDIR}/gitolite compile"); + + exit 0; +} + +{ + my $text = ''; + + sub post_update_hook { + trace(1); + if ( not $text ) { + local $/ = undef; + $text = ; + } + return $text; + } +} + +1; + +__DATA__ +#!/usr/bin/perl + +use strict; +use warnings; + +BEGIN { + die "GL_BINDIR not set; aborting\n" unless $ENV{GL_BINDIR}; +} +use lib $ENV{GL_BINDIR}; +use Gitolite::Hooks::PostUpdate; + +# gitolite post-update hook (only for the admin repo) +# ---------------------------------------------------------------------- + +post_update(@ARGV); # is not expected to return +exit 1; # so if it does, something is wrong diff --git a/Gitolite/Hooks/Update.pm b/Gitolite/Hooks/Update.pm new file mode 100644 index 0000000..2c60914 --- /dev/null +++ b/Gitolite/Hooks/Update.pm @@ -0,0 +1,114 @@ +package Gitolite::Hooks::Update; + +# everything to do with the update hook +# ---------------------------------------------------------------------- + +@EXPORT = qw( + update + update_hook +); + +use Exporter 'import'; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Common; +use Gitolite::Conf::Load; + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +sub update { + trace( 3, @_ ); + # this is the *real* update hook for gitolite + + my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV); + + my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref ); + trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref) -> $ret" ); + _die $ret if $ret =~ /DENIED/; + + exit 0; +} + +{ + my $text = ''; + + sub update_hook { + trace(1); + if ( not $text ) { + local $/ = undef; + $text = ; + } + return $text; + } +} + +# ---------------------------------------------------------------------- + +sub args { + my ( $ref, $oldsha, $newsha ) = @_; + my ( $oldtree, $newtree, $aa ); + + # this is special to git -- the hash of an empty tree + my $empty = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'; + $oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha; + $newtree = $newsha eq '0' x 40 ? $empty : $newsha; + + my $merge_base = '0' x 40; + # for branch create or delete, merge_base stays at '0'x40 + chomp( $merge_base = `git merge-base $oldsha $newsha` ) + unless $oldsha eq '0' x 40 + or $newsha eq '0' x 40; + + $aa = 'W'; + # tag rewrite + $aa = '+' if $ref =~ m(refs/tags/) and $oldsha ne ( '0' x 40 ); + # non-ff push to ref (including ref delete) + $aa = '+' if $oldsha ne $merge_base; + + # XXX $aa = 'D' if ( $repos{$ENV{GL_REPO}}{DELETE_IS_D} or $repos{'@all'}{DELETE_IS_D} ) and $newsha eq '0' x 40; + # XXX $aa = 'C' if ( $repos{$ENV{GL_REPO}}{CREATE_IS_C} or $repos{'@all'}{CREATE_IS_C} ) and $oldsha eq '0' x 40; + + # and now "M" commits. This presents a bit of a problem. All the other + # accesses (W, +, C, D) were mutually exclusive in some sense. Sure a W could + # be a C or a + could be a D but that's by design. A merge commit, however, + # could still be any of the others (except a "D"). + + # so we have to *append* 'M' to $aa (if the repo has MERGE_CHECK in + # effect and this push contains a merge inside) + +=for XXX + if ( $repos{ $ENV{GL_REPO} }{MERGE_CHECK} or $repos{'@all'}{MERGE_CHECK} ) { + if ( $oldsha eq '0' x 40 or $newsha eq '0' x 40 ) { + warn "ref create/delete ignored for purposes of merge-check\n"; + } else { + $aa .= 'M' if `git rev-list -n 1 --merges $oldsha..$newsha` =~ /./; + } + } +=cut + + return ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ); +} + +1; + +__DATA__ +#!/usr/bin/perl + +use strict; +use warnings; + +BEGIN { + exit 0 if $ENV{GL_BYPASS_UPDATE_HOOK}; + die "GL_BINDIR not set; aborting\n" unless $ENV{GL_BINDIR}; +} +use lib $ENV{GL_BINDIR}; +use Gitolite::Hooks::Update; + +# gitolite update hook +# ---------------------------------------------------------------------- + +update(@ARGV); # is not expected to return +exit 1; # so if it does, something is wrong diff --git a/Gitolite/Rc.pm b/Gitolite/Rc.pm new file mode 100644 index 0000000..94f6613 --- /dev/null +++ b/Gitolite/Rc.pm @@ -0,0 +1,112 @@ +package Gitolite::Rc; + +# everything to do with 'rc'. Also defines some 'constants' +# ---------------------------------------------------------------------- + +@EXPORT = qw( + $GL_ADMIN_BASE + $GL_REPO_BASE + + $GL_UMASK + + $GL_GITCONFIG_KEYS + + glrc_default_text + glrc_default_filename + glrc_filename + + $ADC_CMD_ARGS_PATT + $REF_OR_FILENAME_PATT + $REPONAME_PATT + $REPOPATT_PATT + $USERNAME_PATT + + $current_data_version +); + +use Exporter 'import'; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Common; + +# variables that are/could be/should be in the rc file +# ---------------------------------------------------------------------- + +$GL_ADMIN_BASE = "$ENV{HOME}/.gitolite"; +$GL_REPO_BASE = "$ENV{HOME}/repositories"; + +# variables that should probably never be changed +# ---------------------------------------------------------------------- + +$current_data_version = "3.0"; + +$ADC_CMD_ARGS_PATT = qr(^[0-9a-zA-Z._\@/+:-]*$); +$REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$); +$REPONAME_PATT = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$); +$REPOPATT_PATT = qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/,-]*$); +$USERNAME_PATT = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); + +# ---------------------------------------------------------------------- + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +my $rc = glrc_filename(); +do $rc if -r $rc; + +{ + my $glrc_default_text = ''; + + sub glrc_default_text { + trace( 1, "..should happen only on first run" ); + return $glrc_default_text if $glrc_default_text; + local $/ = undef; + $glrc_default_text = ; + } +} + +sub glrc_default_filename { + trace( 1, "..should happen only on first run" ); + return "$ENV{HOME}/.gitolite.rc"; +} + +# where is the rc file? +sub glrc_filename { + trace(4); + + # search $HOME first + return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc"; + trace( 2, "$ENV{HOME}/.gitolite.rc not found" ); + + # XXX for fedora, we can add the following line, but I would really prefer + # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc + # XXX return "/etc/gitolite.rc" if -f "/etc/gitolite.rc"; + + return ''; +} + +1; + +# ---------------------------------------------------------------------- + +__DATA__ +# configuration variables for gitolite + +# PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS + +# this file is in perl syntax. However, you do NOT need to know perl to edit +# it; it should be fairly self-explanatory and easy to maintain + +$GL_UMASK = 0077; +$GL_GITCONFIG_KEYS = ""; + +# ------------------------------------------------------------------------------ +# per perl rules, this should be the last line in such a file: +1; + +# Local variables: +# mode: perl +# End: +# vim: set syn=perl: diff --git a/Gitolite/Test.pm b/Gitolite/Test.pm new file mode 100644 index 0000000..6b4a0ae --- /dev/null +++ b/Gitolite/Test.pm @@ -0,0 +1,34 @@ +package Gitolite::Test; + +# functions for the test code to use +# ---------------------------------------------------------------------- + +#<<< +@EXPORT = qw( + try + put +); +#>>> +use Exporter 'import'; +use File::Path qw(mkpath); +use Carp qw(carp cluck croak confess); + +BEGIN { + require Gitolite::Test::Tsh; + *{'try'} = \&Tsh::try; + *{'put'} = \&Tsh::put; +} + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +# required preamble for all tests +try " + DEF gsh = /TRACE: gsh.SOC=/ + ./g3-install -c admin + cd tsh_tempdir; +"; + +1; diff --git a/Gitolite/Test/Tsh.pm b/Gitolite/Test/Tsh.pm new file mode 100644 index 0000000..b4b3b41 --- /dev/null +++ b/Gitolite/Test/Tsh.pm @@ -0,0 +1,624 @@ +#!/usr/bin/perl +use 5.10.0; + +# Tsh -- non interactive Testing SHell in perl + +# TODO items: +# - allow an RC file to be used to add basic and extended commands +# - convert internal defaults to additions to the RC file +# - implement shell commands as you go +# - solve the "pass/fail" inconsistency between shell and perl +# - solve the pipes problem (use 'overload'?) + +# ---------------------------------------------------------------------- +# modules + +package Tsh; + +use Exporter 'import'; +@EXPORT = qw( + try run AUTOLOAD + rc error_count text lines error_list put + cd tsh_tempdir + + $HOME $PWD $USER +); +@EXPORT_OK = qw(); + +use Env qw(@PATH HOME PWD USER TSH_VERBOSE); +# other candidates: +# GL_ADMINDIR GL_BINDIR GL_RC GL_REPO_BASE_ABS GL_REPO GL_USER + +use strict; +use warnings; + +use Text::Tabs; # only used for formatting the usage() message +use Text::ParseWords; + +use File::Temp qw(tempdir); +END { chdir( $ENV{HOME} ); } +# we need this END handler *after* the 'use File::Temp' above. Without +# this, if $PWD at exit was $tempdir, you get errors like "cannot remove +# path when cwd is [...] at /usr/share/perl5/File/Temp.pm line 902". + +use Data::Dumper; + +# ---------------------------------------------------------------------- +# globals + +my $rc; # return code from backticked (external) programs +my $text; # STDOUT+STDERR of backticked (external) programs +my $lec; # the last external command (the rc and text are from this) +my $cmd; # the current command + +my $testnum; # current test number, for info in TAP output +my $testname; # current test name, for error info to user +my $line; # current line number + +my $err_count; # count of test failures +my @errors_in; # list of testnames that errored + +my $tick; # timestamp for git commits + +my %autoloaded; +my $tempdir = ''; + +# ---------------------------------------------------------------------- +# setup + +# unbuffer STDOUT and STDERR +select(STDERR); $|++; +select(STDOUT); $|++; + +# set the timestamp (needed only under harness) +test_tick() if $ENV{HARNESS_ACTIVE}; + +# ---------------------------------------------------------------------- +# this is for one-liner access from outside, using @ARGV, as in: +# perl -MTsh -e 'tsh()' 'tsh command list' +# or via STDIN +# perl -MTsh -e 'tsh()' < file-containing-tsh-commands +# NOTE: it **exits**! + +sub tsh { + my @lines; + + if (@ARGV) { + # simple, single argument which is a readable filename + if ( @ARGV == 1 and $ARGV[0] !~ /\s/ and -r $ARGV[0] ) { + # take the contents of the file + @lines = <>; + } else { + # more than one argument *or* not readable filename + # just take the arguments themselves as the command list + @lines = @ARGV; + @ARGV = (); + } + } else { + # no arguments given, take STDIN + usage() if -t; + @lines = <>; + } + + # and process them + try(@lines); + + # print error summary by default + if ( not defined $TSH_VERBOSE ) { + say STDERR "$err_count error(s)" if $err_count; + } + + exit $err_count; +} + +# these two get called with series of tsh commands, while the autoload, +# (later) handles single commands + +sub try { + $rc = $err_count = 0; + @errors_in = (); + + # break up multiline arguments into separate lines + my @lines = map { split /\n/ } @_; + + # and process them + rc_lines(@lines); + + # bump err_count if the last command had a non-0 rc (that was apparently not checked). + $err_count++ if $rc; + + # finish up... + dbg( 1, "$err_count error(s)" ) if $err_count; + return ( not $err_count ); +} + +# run() differs from try() in that +# - uses open(), not backticks +# - takes only one command, not tsh-things like ok, /patt/ etc +# - - if you pass it an array it uses the list form! + +sub run { + open( my $fh, "-|", @_ ) or die "tell sitaram $!"; + local $/ = undef; $text = <$fh>; + close $fh; warn "tell sitaram $!" if $!; + $rc = ( $? >> 8 ); + return $text; +} + +sub put { + my ( $file, $data ) = @_; + die "probable quoting error in arguments to put: $file\n" if $file =~ /^\s*['"]/; + my $mode = ">"; + $mode = "|-" if $file =~ s/^\s*\|\s*//; + + $rc = 0; + my $fh; + open( $fh, $mode, $file ) + and print $fh $data + and close $fh + and return 1; + + $rc = 1; + dbg( 1, "put $file: $!" ); + return ''; +} + +# ---------------------------------------------------------------------- +# TODO: AUTOLOAD and exportable convenience subs for common shell commands + +sub cd { + my $dir = shift || ''; + _cd($dir); + dbg( 1, "cd $dir: $!" ) if $rc; + return ( not $rc ); +} + +# this is classic AUTOLOAD, almost from the perlsub manpage. Although, if +# instead of `ls('bin');` you want to be able to say `ls 'bin';` you will need +# to predeclare ls, with `sub ls;`. +sub AUTOLOAD { + my $program = $Tsh::AUTOLOAD; + dbg( 4, "program = $program, arg=$_[0]" ); + $program =~ s/.*:://; + $autoloaded{$program}++; + + die "tsh's autoload support expects only one arg\n" if @_ > 1; + _sh("$program $_[0]"); + return ( not $rc ); # perl truth +} + +# ---------------------------------------------------------------------- +# exportable service subs + +sub rc { + return $rc || 0; +} + +sub text { + return $text || ''; +} + +sub lines { + return split /\n/, $text; +} + +sub error_count { + return $err_count; +} + +sub error_list { + return ( + wantarray + ? @errors_in + : join( "\n", @errors_in ) + ); +} + +sub tsh_tempdir { + # create tempdir if not already done + $tempdir = tempdir( "tsh_tempdir.XXXXXXXXXX", TMPDIR => 1, CLEANUP => 1 ) unless $tempdir; + # XXX TODO that 'UNLINK' doesn't work for Ctrl_C + + return $tempdir; +} + +# ---------------------------------------------------------------------- +# internal (non-exportable) service subs + +sub print_plan { + return unless $ENV{HARNESS_ACTIVE}; + my $_ = shift; + say "1..$_"; +} + +sub rc_lines { + my @lines = @_; + + while (@lines) { + my $_ = shift @lines; + chomp; $_ = trim_ws($_); + + # this also sets $testname + next if is_comment_or_empty($_); + + dbg( 2, "L: $_" ); + $line = $_; # save line for printing with 'FAIL:' + + # a DEF has to be on a line by itself + if (/^DEF\s+([-.\w]+)\s*=\s*(\S.*)$/) { + def( $1, $2 ); + next; + } + + my @cmds = cmds($_); + + # process each command + # (note: some of the commands may put stuff back into @lines) + while (@cmds) { + # this needs to be the 'global' one, since fail() prints it + $cmd = shift @cmds; + + # is the current command a "testing" command? + my $testing_cmd = + ( $cmd =~ m(^ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^!ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$) or $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$) ); + + # warn if the previous command failed but rc is not being checked + if ( $rc and not $testing_cmd ) { + dbg( 1, "rc: $rc from cmd prior to '$cmd'\n" ); + # count this as a failure, for exit status purposes + $err_count++; + # and reset the rc, otherwise for example 'ls foo; tt; tt; tt' + # will tell you there are 3 errors! + $rc = 0; + push @errors_in, $testname if $testname; + } + + # prepare to run the command + dbg( 3, "C: $cmd" ); + if ( def($cmd) ) { + # expand macro and replace head of @cmds (unshift) + dbg( 2, "DEF: $cmd" ); + unshift @cmds, cmds( def($cmd) ); + } else { + parse($cmd); + } + # reset rc if checking is done + $rc = 0 if $testing_cmd; + # assumes you will (a) never have *both* 'ok' and '!ok' after + # an action command, and (b) one of them will come immediately + # after the action command, with /patt/ only after it. + } + } +} + +sub def { + my ( $cmd, $list ) = @_; + state %def; + %def = read_rc_file() unless %def; + + if ($list) { + # set mode + die "attempt to redefine macro $cmd\n" if $def{$cmd}; + $def{$cmd} = $list; + return; + } + + # get mode: split the $cmd at spaces, see if there is a definition + # available, substitute any %1, %2, etc., in it and send it back + my ( $c, @d ) = shellwords($cmd); + my $e; # the expanded value + if ( $e = $def{$c} ) { # starting value + for my $i ( 1 .. 9 ) { + last unless $e =~ /%$i/; # no more %N's (we assume sanity) + die "$def{$c} requires more arguments\n" unless @d; + my $f = shift @d; # get the next datum + $e =~ s/%$i/$f/g; # and substitute %N all over + } + return join( " ", $e, @d ); # join up any remaining data + } + return ''; +} + +sub _cd { + my $dir = shift || $HOME; + # a directory name of 'tsh_tempdir' is special + $dir = tsh_tempdir() if $dir eq 'tsh_tempdir'; + $rc = 0; + chdir($dir) or $rc = 1; +} + +sub _sh { + my $cmd = shift; + # TODO: switch to IPC::Open3 or something...? + + dbg( 4, " running: ( $cmd ) 2>&1" ); + $text = `( $cmd ) 2>&1; echo -n RC=\$?`; + $lec = $cmd; + dbg( 4, " results:\n$text" ); + + if ( $text =~ /RC=(\d+)$/ ) { + $rc = $1; + $text =~ s/RC=\d+$//; + } else { + die "couldnt find RC= in result; this should not happen:\n$text\n\n...\n"; + } +} + +sub _perl { + my $perl = shift; + local $_; + $_ = $text; + + dbg( 4, " eval: $perl" ); + my $evrc = eval $perl; + + if ($@) { + $rc = 1; # shell truth + dbg( 1, $@ ); + # leave $text unchanged + } else { + $rc = not $evrc; + # $rc is always shell truth, so we need to cover the case where + # there was no error but it still returned a perl false + $text = $_; + } + dbg( 4, " eval-rc=$evrc, results:\n$text" ); +} + +sub parse { + my $cmd = shift; + + if ( $cmd =~ /^sh (.*)/ ) { + + _sh($1); + + } elsif ( $cmd =~ /^perl (.*)/ ) { + + _perl($1); + + } elsif ( $cmd eq 'tt' or $cmd eq 'test-tick' ) { + + test_tick(); + + } elsif ( $cmd =~ /^plan ?(\d+)$/ ) { + + print_plan($1); + + } elsif ( $cmd =~ /^cd ?(\S*)$/ ) { + + _cd($1); + + } elsif ( $cmd =~ /^ENV (\w+)=['"]?(.+?)['"]?$/ ) { + + $ENV{$1} = $2; + + } elsif ( $cmd =~ /^(?:tc|test-commit)\s+(\S.*)$/ ) { + + # this is the only "git special" really; the default expansions are + # just that -- defaults. But this one is hardwired! + dummy_commits($1); + + } elsif ( $cmd =~ '^put(?:\s+(\S.*))?$' ) { + + if ($1) { + put( $1, $text ); + } else { + print $text if defined $text; + } + + } elsif ( $cmd =~ m(^ok(?:\s+or\s+(.*))?$) ) { + + $rc ? fail( "ok, rc=$rc from $lec", $1 || '' ) : ok(); + + } elsif ( $cmd =~ m(^!ok(?:\s+or\s+(.*))?$) ) { + + $rc ? ok() : fail( "!ok, rc=0 from $lec", $1 || '' ); + + } elsif ( $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$) ) { + + expect( $1, $2 ); + + } elsif ( $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$) ) { + + not_expect( $1, $2 ); + + } else { + + _sh($cmd); + + } +} + +# currently unused +sub executable { + my $cmd = shift; + # path supplied + $cmd =~ m(/) and -x $cmd and return 1; + # barename; look up in $PATH + for my $p (@PATH) { + -x "$p/$cmd" and return 1; + } + return 0; +} + +sub ok { + $testnum++; + say "ok ($testnum)" if $ENV{HARNESS_ACTIVE}; +} + +sub fail { + $testnum++; + say "not ok ($testnum)" if $ENV{HARNESS_ACTIVE}; + + my $die = 0; + my ( $msg1, $msg2 ) = @_; + if ($msg2) { + # if arg2 is non-empty, print it regardless of debug level + $die = 1 if $msg2 =~ s/^die //; + say STDERR "# $msg2"; + } + + local $TSH_VERBOSE = 1 if $ENV{TSH_ERREXIT}; + dbg( 1, "FAIL: $msg1", $testname || '', "test number $testnum", "L: $line", "results:\n$text" ); + + # count the error and add the testname to the list if it is set + $err_count++; + push @errors_in, $testname if $testname; + + return unless $die or $ENV{TSH_ERREXIT}; + dbg( 1, "exiting at cmd $cmd\n" ); + + exit( $rc || 74 ); +} + +sub expect { + my ( $patt, $msg ) = @_; + $msg =~ s/^\s+// if $msg; + my $sm; + if ( $sm = sm($patt) ) { + dbg( 4, " M: $sm" ); + ok(); + } else { + fail( "/$patt/", $msg || '' ); + } +} + +sub not_expect { + my ( $patt, $msg ) = @_; + $msg =~ s/^\s+// if $msg; + my $sm; + if ( $sm = sm($patt) ) { + dbg( 4, " M: $sm" ); + fail( "!/$patt/", $msg || '' ); + } else { + ok(); + } +} + +sub sm { + # smart match? for now we just do regex match + my $patt = shift; + + return ( $text =~ qr($patt) ? $& : "" ); +} + +sub trim_ws { + my $_ = shift; + s/^\s+//; s/\s+$//; + return $_; +} + +sub is_comment_or_empty { + my $_ = shift; + chomp; $_ = trim_ws($_); + if (/^##\s(.*)/) { + $testname = $1; + say "# $1"; + } + return ( /^#/ or /^$/ ); +} + +sub cmds { + my $_ = shift; + chomp; $_ = trim_ws($_); + + # split on unescaped ';'s, then unescape the ';' in the results + my @cmds = map { s/\\;/;/g; $_ } split /(?= $level; + my $all = join( "\n", grep( /./, @_ ) ); + chomp($all); + $all =~ s/\n/\n\t/g; + say STDERR "# $all"; +} + +sub ddump { + for my $i (@_) { + print STDERR "DBG: " . Dumper($i); + } +} + +sub usage { + # TODO + print "Please see documentation at: + + https://github.com/sitaramc/tsh/blob/master/README.mkd + +Meanwhile, here are your local 'macro' definitions: + +"; + my %m = read_rc_file(); + my @m = map { "$_\t$m{$_}\n" } sort keys %m; + $tabstop = 16; + print join( "", expand(@m) ); + exit 1; +} + +# ---------------------------------------------------------------------- +# git-specific internal service subs + +sub dummy_commits { + for my $f ( split ' ', shift ) { + if ( $f eq 'tt' or $f eq 'test-tick' ) { + test_tick(); + next; + } + my $ts = ( $tick ? localtime($tick) : localtime() ); + _sh("echo $f at $ts >> $f && git add $f && git commit -m '$f at $ts'"); + } +} + +sub test_tick { + unless ( $ENV{HARNESS_ACTIVE} ) { + sleep 1; + return; + } + $tick += 60 if $tick; + $tick ||= 1310000000; + $ENV{GIT_COMMITTER_DATE} = "$tick +0530"; + $ENV{GIT_AUTHOR_DATE} = "$tick +0530"; +} + +# ---------------------------------------------------------------------- +# the internal macros, for easy reference and reading + +sub read_rc_file { + my $rcfile = "$HOME/.tshrc"; + my $rctext; + if ( -r $rcfile ) { + local $/ = undef; + open( my $rcfh, "<", $rcfile ) or die "this should not happen: $!\n"; + $rctext = <$rcfh>; + } else { + # this is the default "rc" content + $rctext = " + add = git add + branch = git branch + clone = git clone + checkout = git checkout + commit = git commit + fetch = git fetch + init = git init + push = git push + reset = git reset + tag = git tag + + empty = git commit --allow-empty -m empty + push-om = git push origin master + reset-h = git reset --hard + reset-hu = git reset --hard \@{u} + " + } + + # ignore everything except lines of the form "aa = bb cc dd" + my %commands = ( $rctext =~ /^\s*([-.\w]+)\s*=\s*(\S.*)$/gm ); + return %commands; +} + +1; diff --git a/g3-info b/g3-info new file mode 100755 index 0000000..d4db40e --- /dev/null +++ b/g3-info @@ -0,0 +1,35 @@ +#!/usr/bin/perl + +# gitolite shell, invoked from ~/.ssh/authorized_keys +# ---------------------------------------------------------------------- + +BEGIN { + # find and set bin dir + $ENV{GL_BINDIR} = "$ENV{HOME}/bin"; +} + +use lib $ENV{GL_BINDIR}; +use Gitolite::Rc; +use Gitolite::Common; +use Gitolite::Conf::Load; + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +my $user = shift or die; +my $aa; +my $ref = 'unknown'; + +my $ret; +while (<>) { + chomp; + + my $perm = ''; + for $aa (qw(R W C)) { + $ret = access($_, $user, $aa, $ref); + $perm .= ( $ret =~ /DENIED/ ? " " : " $aa" ); + } + print "$perm\t$_\n" if $perm =~ /\S/; +} diff --git a/g3-install b/g3-install new file mode 100755 index 0000000..ef40012 --- /dev/null +++ b/g3-install @@ -0,0 +1,20 @@ +#!/bin/bash + +# this is specific to my test env; you may want to change it + +set -e + +cd /home/g3 + +if [ "$1" = "-c" ] +then + rm -rf .gito* gito* repositories proj* bin + mkdir bin + cp ~/.ssh/id_rsa.pub ~/.ssh/admin.pub + + cd g3; cp -a gito* Gito* t/glt t/gito* ~/bin + gitolite setup -a ${2:-admin} -pk ~/.ssh/admin.pub +else + cd g3; cp -a gito* Gito* t/glt t/gito* ~/bin + gitolite setup +fi diff --git a/gitolite b/gitolite new file mode 100755 index 0000000..f89f12f --- /dev/null +++ b/gitolite @@ -0,0 +1,54 @@ +#!/usr/bin/perl + +# all gitolite CLI tools run as sub-commands of this command +# ---------------------------------------------------------------------- + +=for usage +Usage: gitolite [sub-command] [options] + +The following subcommands are available; they should all respond to '-h': + + setup 1st run: initial setup; all runs: hook fixups + compile compile gitolite.conf + query-rc get values of rc variables +=cut + +# ---------------------------------------------------------------------- + +use FindBin; + +BEGIN { $ENV{GL_BINDIR} = $FindBin::Bin; } +use lib $ENV{GL_BINDIR}; +use Gitolite::Common; + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +args(); + +# ---------------------------------------------------------------------- + +sub args { + my ( $command, @args ) = @ARGV; + usage() if not $command or $command eq '-h'; + + if ( $command eq 'setup' ) { + shift @ARGV; + require Gitolite::Commands::Setup; + Gitolite::Commands::Setup->import; + setup(); + } elsif ( $command eq 'compile' ) { + shift @ARGV; + _die "'gitolite compile' does not take any arguments" if @ARGV; + require Gitolite::Conf; + Gitolite::Conf->import; + compile(); + } elsif ( $command eq 'query-rc' ) { + shift @ARGV; + require Gitolite::Commands::QueryRc; + Gitolite::Commands::QueryRc->import; + query_rc(); + } +} diff --git a/gitolite-shell b/gitolite-shell new file mode 100755 index 0000000..479773c --- /dev/null +++ b/gitolite-shell @@ -0,0 +1,57 @@ +#!/usr/bin/perl + +# gitolite shell, invoked from ~/.ssh/authorized_keys +# ---------------------------------------------------------------------- + +BEGIN { + # find and set bin dir + $0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ( $1 || "$ENV{PWD}/" ) . $2; +} + +use lib $ENV{GL_BINDIR}; +use Gitolite::Rc; +use Gitolite::Common; +use Gitolite::Conf::Load; + +use strict; +use warnings; +print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n"; +print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n"; + +# ---------------------------------------------------------------------- + +# XXX lots of stuff from gl-auth-command is missing for now... + +# set up the user +my $user = $ENV{GL_USER} = shift; + +# set up the repo and the attempted access +my ( $verb, $repo ) = split_soc(); +sanity($repo); +$ENV{GL_REPO} = $repo; +my $aa = ( $verb =~ 'upload' ? 'R' : 'W' ); + +# a ref of 'unknown' signifies that this is a pre-git check, where we don't +# yet know the ref that will be eventually pushed (and even that won't apply +# if it's a read operation). See the matching code in access() for more. +my $ret = access( $repo, $user, $aa, 'unknown' ); +trace( 1, "access($repo, $user, $aa, 'unknown') -> $ret" ); +_die $ret if $ret =~ /DENIED/; + +$repo = "'$GL_REPO_BASE/$repo.git'"; +exec( "git", "shell", "-c", "$verb $repo" ); + +# ---------------------------------------------------------------------- + +sub split_soc { + my $soc = $ENV{SSH_ORIGINAL_COMMAND}; + return ( $1, $2 ) if $soc =~ m(^(git-(?:upload|receive)-pack) '/?(.*?)(?:\.git)?'$); + _die "unknown command: $soc"; +} + +sub sanity { + my $repo = shift; + _die "'$repo' contains bad characters" if $repo !~ $REPONAME_PATT; + _die "'$repo' ends with a '/'" if $repo =~ m(/$); + _die "'$repo' contains '..'" if $repo =~ m(\.\.$); +} diff --git a/src/gitolite b/src/gitolite new file mode 100755 index 0000000..c6a1f54 --- /dev/null +++ b/src/gitolite @@ -0,0 +1,106 @@ +#!/usr/bin/perl + +# all gitolite CLI tools run as sub-commands of this command +# ---------------------------------------------------------------------- + +=for args +Usage: gitolite [] [] + +The following built-in subcommands are available; they should all respond to +'-h' if you want further details on each: + + setup 1st run: initial setup; all runs: hook fixups + compile compile gitolite.conf + + query-rc get values of rc variables + + list-groups list all group names in conf + list-users list all users/user groups in conf + list-repos list all repos/repo groups in conf + list-phy-repos list all repos actually on disk + list-memberships list all groups a name is a member of + list-members list all members of a group + +Warnings: + - list-users is disk bound and could take a while on sites with 1000s of repos + - list-memberships does not check if the name is known; unknown names come + back with 2 answers: the name itself and '@all' + +In addition, running 'gitolite help' should give you a list of custom commands +available. They may or may not respond to '-h', depending on how they were +written. +=cut + +# ---------------------------------------------------------------------- + +use FindBin; + +BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; } +use lib $ENV{GL_BINDIR}; +use Gitolite::Rc; +use Gitolite::Common; + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +my ( $command, @args ) = @ARGV; +gl_log( 'gitolite', @ARGV ) if -d $rc{GL_ADMIN_BASE}; +args(); + +# the first two commands need options via @ARGV, as they have their own +# GetOptions calls and older perls don't have 'GetOptionsFromArray' + +if ( $command eq 'setup' ) { + shift @ARGV; + require Gitolite::Setup; + Gitolite::Setup->import; + setup(); + +} elsif ( $command eq 'query-rc' ) { + shift @ARGV; + query_rc(); + +# the rest don't need @ARGV per se + +} elsif ( $command eq 'compile' ) { + require Gitolite::Conf; + Gitolite::Conf->import; + compile(@args); + +} elsif ( $command eq 'trigger' ) { + trigger(@args); + +} elsif ( -x "$rc{GL_BINDIR}/commands/$command" ) { + trace( 2, "attempting gitolite command $command" ); + run_command( $command, @args ); + +} elsif ( $command eq 'list-phy-repos' ) { + _chdir( $rc{GL_REPO_BASE} ); + print "$_\n" for ( @{ list_phy_repos(@args) } ); + +} elsif ( $command =~ /^list-/ ) { + trace( 2, "attempting lister command $command" ); + require Gitolite::Conf::Load; + Gitolite::Conf::Load->import; + my $fn = lister_dispatch($command); + print "$_\n" for ( @{ $fn->(@args) } ); + +} else { + _die "unknown gitolite sub-command"; +} + +sub args { + usage() if not $command or $command eq '-h'; +} + +# ---------------------------------------------------------------------- + +sub run_command { + my $pgm = shift; + my $fullpath = "$ENV{GL_BINDIR}/commands/$pgm"; + _die "$pgm not found or not executable" if not -x $fullpath; + _system( $fullpath, @_ ); + exit 0; +} diff --git a/t/gitolite-receive-pack b/t/gitolite-receive-pack new file mode 100755 index 0000000..48c7428 --- /dev/null +++ b/t/gitolite-receive-pack @@ -0,0 +1,12 @@ +#!/usr/bin/perl + +use strict; +use warnings; +print STDERR "TRACE: grp(", join( ")(", @ARGV ), ")\n"; + +my $repo = shift; +$repo =~ s/\.git$//; +my $user = $ENV{G3T_USER} || 'no-such-user'; + +$ENV{SSH_ORIGINAL_COMMAND} = "git-receive-pack '$repo'"; +exec( "$ENV{HOME}/bin/gitolite-shell", $user ); diff --git a/t/gitolite-upload-pack b/t/gitolite-upload-pack new file mode 100755 index 0000000..8888abb --- /dev/null +++ b/t/gitolite-upload-pack @@ -0,0 +1,12 @@ +#!/usr/bin/perl + +use strict; +use warnings; +print STDERR "TRACE: gup(", join( ")(", @ARGV ), ")\n"; + +my $repo = shift; +$repo =~ s/\.git$//; +my $user = $ENV{G3T_USER} || 'no-such-user'; + +$ENV{SSH_ORIGINAL_COMMAND} = "git-upload-pack '$repo'"; +exec( "$ENV{HOME}/bin/gitolite-shell", $user ); diff --git a/t/glt b/t/glt new file mode 100755 index 0000000..b5704f5 --- /dev/null +++ b/t/glt @@ -0,0 +1,28 @@ +#!/usr/bin/perl +use strict; +use warnings; + +print STDERR "TRACE: glt(", join( ")(", @ARGV ), ")\n"; + +my $cmd = shift or die "need command"; +my $user = shift or die "need user"; +my $rc; + +$ENV{G3T_USER} = $user; +if ( $cmd eq 'push' ) { + $rc = system( "git", $cmd, "--receive-pack=$ENV{HOME}/bin/gitolite-receive-pack", @ARGV ); +} else { + $rc = system( "git", $cmd, "--upload-pack=$ENV{HOME}/bin/gitolite-upload-pack", @ARGV ); +} + +if ( $? == -1 ) { + die "F: failed to execute: $!\n"; +} elsif ( $? & 127 ) { + printf STDERR "E: child died with signal %d\n", ( $? & 127 ); + exit 1; +} else { + printf STDERR "W: child exited with value %d\n", $? >> 8 if $? >> 8; + exit( $? >> 8 ); +} + +exit 0; diff --git a/t/t01-basic b/t/t01-basic new file mode 100755 index 0000000..3970308 --- /dev/null +++ b/t/t01-basic @@ -0,0 +1,110 @@ +#!/usr/bin/perl +use strict; +use warnings; + +# this is hardcoded; change it if needed +use lib "$ENV{HOME}/bin"; +use Gitolite::Test; + +# basic tests +# ---------------------------------------------------------------------- + +try " + plan 74 + + ## clone + glt clone dev2 file://gitolite-admin + !ok; gsh + /FATAL: DENIED: R access to gitolite-admin by dev2 .fallthru./ + /fatal: The remote end hung up unexpectedly/ + glt clone admin --progress file://gitolite-admin + ok; gsh + /Counting/; /Compressing/; /Total/ + cd gitolite-admin; ok + "; + +put "conf/gitolite.conf", " + \@admins = admin dev1 + repo gitolite-admin + - mm = \@admins + RW = \@admins + RW+ = admin + + repo testing + RW+ = \@all +"; + +try " + ## push + git add conf; ok + git status -s; ok; /M conf/gitolite.conf/ + git commit -m t01a; ok; /master.*t01a/ + glt push dev2 origin; !ok; gsh + /FATAL: DENIED: W access to gitolite-admin by dev2 .fallthru./ + /fatal: The remote end hung up unexpectedly/ + glt push admin origin; ok; /master -. master/ + tsh empty; ok; + glt push admin origin master:mm + !ok; gsh + /FATAL: DENIED: W access to gitolite-admin by admin .rule: refs/heads/mm./ + /remote: error: hook declined to update refs/heads/mm/ + /To file://gitolite-admin/ + /remote rejected. master -. mm .hook declined./ + /error: failed to push some refs to 'file://gitolite-admin'/ + + "; + +put "conf/gitolite.conf", " + \@admins = admin dev1 + repo gitolite-admin + RW+ = admin + + repo testing + RW+ = \@all + + repo t1 + R = u2 + RW = u3 + RW+ = u4 +"; + +try " + ## push 2 + git add conf; ok + git status -s; ok; /M conf/gitolite.conf/ + git commit -m t01b; ok; /master.*t01b/ + glt push admin origin; ok; gsh + /master -. master/ + + ## clone + cd ..; ok; + glt clone u1 file://t1; !ok; gsh + /FATAL: DENIED: R access to t1 by u1 .fallthru./ + /fatal: The remote end hung up unexpectedly/ + glt clone u2 file://t1; ok; gsh + /warning: You appear to have cloned an empty repository./ + ls -al t1; ok; /$ENV{USER}.*$ENV{USER}.*\.git/ + cd t1; ok; + + ## push + test-commit tc1 tc2 tc2; ok; /f7153e3/ + glt push u2 origin; !ok; gsh + /FATAL: DENIED: W access to t1 by u2 .fallthru./ + /fatal: The remote end hung up unexpectedly/ + glt push u3 origin master; ok; gsh + /master -. master/ + + ## rewind + reset-h HEAD^; ok; /HEAD is now at 537f964 tc2/ + test-tick; test-commit tc3; ok; /a691552/ + glt push u3 origin; !ok; gsh + /rejected.*master -. master.*non-fast-forward./ + glt push u3 -f origin; !ok; gsh + /FATAL: DENIED: \\+ access to t1 by u3 .fallthru./ + /remote: error: hook declined to update refs/heads/master/ + /To file://t1/ + /remote rejected. master -. master .hook declined./ + /error: failed to push some refs to 'file://t1'/ + glt push u4 origin +master; ok; gsh + / \\+ f7153e3...a691552 master -. master.*forced update./ +" From b0ceac2594c17b06c1175845757b7a9bba5d1d9a Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 8 Mar 2012 12:31:01 +0530 Subject: [PATCH 289/637] chdirs moved from load() to load_common() and load_1() --- Gitolite/Conf/Load.pm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm index db11679..af62812 100644 --- a/Gitolite/Conf/Load.pm +++ b/Gitolite/Conf/Load.pm @@ -43,8 +43,8 @@ my $last_repo = ''; trace( 4, "$repo" ); if ( $repo ne $loaded_repo ) { trace( 3, "loading $repo..." ); - _chdir("$GL_ADMIN_BASE"); load_common(); - _chdir("$GL_REPO_BASE"); load_1($repo); + load_common(); + load_1($repo); $loaded_repo = $repo; } } @@ -86,6 +86,8 @@ sub access { sub load_common { + _chdir("$GL_ADMIN_BASE"); + # we take an unusual approach to caching this function! # (requires that first call to load_common is before first call to load_1) if ( $last_repo and $split_conf{$last_repo} ) { @@ -110,6 +112,8 @@ sub load_1 { my $repo = shift; trace( 4, $repo ); + _chdir("$GL_REPO_BASE"); + if ( $repo eq $last_repo ) { $repos{$repo} = $one_repo{$repo}; $configs{$repo} = $one_config{$repo} if $one_config{$repo}; From 1be66dc10e0fe2130fbc8cfb6ddd19c1c20b84e5 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 8 Mar 2012 12:13:50 +0530 Subject: [PATCH 290/637] 'gitolite list-groups' added --- Gitolite/Conf/Load.pm | 19 +++++++++++++++++++ gitolite | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm index af62812..34b6a1e 100644 --- a/Gitolite/Conf/Load.pm +++ b/Gitolite/Conf/Load.pm @@ -6,6 +6,8 @@ package Gitolite::Conf::Load; @EXPORT = qw( load access + + list_groups ); use Exporter 'import'; @@ -169,5 +171,22 @@ sub data_version_mismatch { return $data_version ne $current_data_version; } +# ---------------------------------------------------------------------- +# api functions +# ---------------------------------------------------------------------- + +# list all groups +sub list_groups { + die "\nUsage: gitolite list-groups\n\n(no options, no flags)\n\n" if @ARGV; + + load_common(); + + my @g = (); + while (my ($k, $v) = each ( %groups )) { + push @g, @{ $v }; + } + return (sort_u(\@g)); +} + 1; diff --git a/gitolite b/gitolite index f89f12f..43868a5 100755 --- a/gitolite +++ b/gitolite @@ -11,6 +11,7 @@ The following subcommands are available; they should all respond to '-h': setup 1st run: initial setup; all runs: hook fixups compile compile gitolite.conf query-rc get values of rc variables + list-groups list all group names in conf =cut # ---------------------------------------------------------------------- @@ -50,5 +51,10 @@ sub args { require Gitolite::Commands::QueryRc; Gitolite::Commands::QueryRc->import; query_rc(); + } elsif ( $command eq 'list-groups' ) { + shift @ARGV; + require Gitolite::Conf::Load; + Gitolite::Conf::Load->import; + print "$_\n" for ( @{ list_groups() } ); } } From d88cdbefd644185a332f5854f027780cb8ceca30 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 8 Mar 2012 15:06:05 +0530 Subject: [PATCH 291/637] 'gitolite list-users' added (but see warnings) this is pretty slow if you have thousands of repos, since it has to read and parse a 'gl-conf' file for every repo. (For example, on a Lenovo X201 thinkpad with 11170 repos and a cold cache, it took 288 seconds). (With a hot cache -- like if you run the command again -- it took 2.1 seconds! So if you have a fast disk this may not be an issue for you even if you have 10,000+ repos). --- Gitolite/Conf/Load.pm | 38 ++++++++++++++++++++++++++++++++++++++ gitolite | 9 +++++++++ 2 files changed, 47 insertions(+) diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm index 34b6a1e..c9b09b5 100644 --- a/Gitolite/Conf/Load.pm +++ b/Gitolite/Conf/Load.pm @@ -8,6 +8,7 @@ package Gitolite::Conf::Load; access list_groups + list_users ); use Exporter 'import'; @@ -188,5 +189,42 @@ sub list_groups { return (sort_u(\@g)); } +sub list_users { + my $count = 0; + my $total = 0; + + die "\nUsage: gitolite list-users\n\n - no options, no flags\n - may be slow if you have thousands of repos\n\n" if @ARGV; + + load_common(); + + my @u = map { keys %{ $_ } } values %repos; + $total = scalar(keys %split_conf); + warn "WARNING: you have $total repos to check; this could take some time!\n" if $total > 100; + for my $one ( keys %split_conf ) { + load_1($one); + $count++; print STDERR "$count / $total\r" if not ( $count % 100 ) and timer(5); + push @u, map { keys %{ $_ } } values %one_repo; + } + print STDERR "\n"; + return (sort_u(\@u)); +} + +# ---------------------------------------------------------------------- + +{ + my $start_time = 0; + + sub timer { + unless ($start_time) { + $start_time = time(); + return 0; + } + my $elapsed = shift; + return 0 if time() - $start_time < $elapsed; + $start_time = time(); + return 1; + } +} + 1; diff --git a/gitolite b/gitolite index 43868a5..fb835e2 100755 --- a/gitolite +++ b/gitolite @@ -12,6 +12,10 @@ The following subcommands are available; they should all respond to '-h': compile compile gitolite.conf query-rc get values of rc variables list-groups list all group names in conf + list-users list all user names in conf + +Warnings: + - list-users is disk bound and could take a while on sites with thousands of repos =cut # ---------------------------------------------------------------------- @@ -56,5 +60,10 @@ sub args { require Gitolite::Conf::Load; Gitolite::Conf::Load->import; print "$_\n" for ( @{ list_groups() } ); + } elsif ( $command eq 'list-users' ) { + shift @ARGV; + require Gitolite::Conf::Load; + Gitolite::Conf::Load->import; + print "$_\n" for ( @{ list_users() } ); } } From 4e81d3cfeddb3c30a8c306377d514b2b3375eee0 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 8 Mar 2012 16:14:13 +0530 Subject: [PATCH 292/637] 'gitolite list-repos' added, plus some usage message changes --- Gitolite/Conf/Load.pm | 37 +++++++++++++++++++++++++++++++++++-- gitolite | 8 +++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm index c9b09b5..130850a 100644 --- a/Gitolite/Conf/Load.pm +++ b/Gitolite/Conf/Load.pm @@ -9,6 +9,7 @@ package Gitolite::Conf::Load; list_groups list_users + list_repos ); use Exporter 'import'; @@ -178,7 +179,13 @@ sub data_version_mismatch { # list all groups sub list_groups { - die "\nUsage: gitolite list-groups\n\n(no options, no flags)\n\n" if @ARGV; + die " +Usage: gitolite list-groups + + - lists all group names in conf + - no options, no flags + +" if @ARGV; load_common(); @@ -193,7 +200,14 @@ sub list_users { my $count = 0; my $total = 0; - die "\nUsage: gitolite list-users\n\n - no options, no flags\n - may be slow if you have thousands of repos\n\n" if @ARGV; + die " +Usage: gitolite list-users + + - lists all users/user groups in conf + - no options, no flags + - WARNING: may be slow if you have thousands of repos + +" if @ARGV; load_common(); @@ -209,6 +223,25 @@ sub list_users { return (sort_u(\@u)); } + +sub list_repos { + + die " +Usage: gitolite list-repos + + - lists all repos/repo groups in conf + - no options, no flags + +" if @ARGV; + + load_common(); + + my @r = keys %repos; + push @r, keys %split_conf; + + return (sort_u(\@r)); +} + # ---------------------------------------------------------------------- { diff --git a/gitolite b/gitolite index fb835e2..6adc347 100755 --- a/gitolite +++ b/gitolite @@ -12,7 +12,8 @@ The following subcommands are available; they should all respond to '-h': compile compile gitolite.conf query-rc get values of rc variables list-groups list all group names in conf - list-users list all user names in conf + list-users list all users/user groups in conf + list-repos list all repos/repo groups in conf Warnings: - list-users is disk bound and could take a while on sites with thousands of repos @@ -65,5 +66,10 @@ sub args { require Gitolite::Conf::Load; Gitolite::Conf::Load->import; print "$_\n" for ( @{ list_users() } ); + } elsif ( $command eq 'list-repos' ) { + shift @ARGV; + require Gitolite::Conf::Load; + Gitolite::Conf::Load->import; + print "$_\n" for ( @{ list_repos() } ); } } From c9826eee077a0c43977398efeed6fc2855e31d69 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 8 Mar 2012 17:00:27 +0530 Subject: [PATCH 293/637] 'gitolite list-memberships' added --- Gitolite/Conf/Load.pm | 18 ++++++++++++++++++ gitolite | 10 +++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm index 130850a..cc88225 100644 --- a/Gitolite/Conf/Load.pm +++ b/Gitolite/Conf/Load.pm @@ -10,6 +10,7 @@ package Gitolite::Conf::Load; list_groups list_users list_repos + list_memberships ); use Exporter 'import'; @@ -242,6 +243,23 @@ Usage: gitolite list-repos return (sort_u(\@r)); } +sub list_memberships { + + die " +Usage: gitolite list-memberships + + - list all groups a name is a member of + - takes one user/repo name + +" if @ARGV and $ARGV[0] eq '-h' or not @ARGV and not @_; + + my $name = ( @_ ? shift @_ : shift @ARGV ); + + load_common(); + my @m = memberships($name); + return (sort_u(\@m)); +} + # ---------------------------------------------------------------------- { diff --git a/gitolite b/gitolite index 6adc347..5d5f3dd 100755 --- a/gitolite +++ b/gitolite @@ -14,9 +14,12 @@ The following subcommands are available; they should all respond to '-h': list-groups list all group names in conf list-users list all users/user groups in conf list-repos list all repos/repo groups in conf + list-memberships list all groups a name is a member of Warnings: - - list-users is disk bound and could take a while on sites with thousands of repos + - list-users is disk bound and could take a while on sites with 1000s of repos + - list-memberships does not check if the name is known; unknown names come + back with 2 answers: the name itself and '@all' =cut # ---------------------------------------------------------------------- @@ -71,5 +74,10 @@ sub args { require Gitolite::Conf::Load; Gitolite::Conf::Load->import; print "$_\n" for ( @{ list_repos() } ); + } elsif ( $command eq 'list-memberships' ) { + shift @ARGV; + require Gitolite::Conf::Load; + Gitolite::Conf::Load->import; + print "$_\n" for ( @{ list_memberships() } ); } } From 00934c83045262a1c38214ca94b8bb55af3a1426 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 8 Mar 2012 17:59:44 +0530 Subject: [PATCH 294/637] 'gitolite list-members' added --- Gitolite/Conf/Load.pm | 25 +++++++++++++++++++++++++ gitolite | 6 ++++++ 2 files changed, 31 insertions(+) diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm index cc88225..9367296 100644 --- a/Gitolite/Conf/Load.pm +++ b/Gitolite/Conf/Load.pm @@ -11,6 +11,7 @@ package Gitolite::Conf::Load; list_users list_repos list_memberships + list_members ); use Exporter 'import'; @@ -260,6 +261,30 @@ Usage: gitolite list-memberships return (sort_u(\@m)); } +sub list_members { + + die " +Usage: gitolite list-members + + - list all members of a group + - takes one group name + +" if @ARGV and $ARGV[0] eq '-h' or not @ARGV and not @_; + + my $name = ( @_ ? shift @_ : shift @ARGV ); + + load_common(); + + my @m = (); + while (my ($k, $v) = each ( %groups )) { + for my $g ( @{ $v } ) { + push @m, $k if $g eq $name; + } + } + + return (sort_u(\@m)); +} + # ---------------------------------------------------------------------- { diff --git a/gitolite b/gitolite index 5d5f3dd..dc7fb0e 100755 --- a/gitolite +++ b/gitolite @@ -15,6 +15,7 @@ The following subcommands are available; they should all respond to '-h': list-users list all users/user groups in conf list-repos list all repos/repo groups in conf list-memberships list all groups a name is a member of + list-members list all members of a group Warnings: - list-users is disk bound and could take a while on sites with 1000s of repos @@ -79,5 +80,10 @@ sub args { require Gitolite::Conf::Load; Gitolite::Conf::Load->import; print "$_\n" for ( @{ list_memberships() } ); + } elsif ( $command eq 'list-members' ) { + shift @ARGV; + require Gitolite::Conf::Load; + Gitolite::Conf::Load->import; + print "$_\n" for ( @{ list_members() } ); } } From 95c6952e11eaaef1285342e1667ba99a07ca873c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 8 Mar 2012 19:20:00 +0530 Subject: [PATCH 295/637] list_phy_repos() moved from store.pm to common.pm but you need to chdir() to the right place before calling it --- Gitolite/Common.pm | 22 +++++++++++++++++++++- Gitolite/Conf/Store.pm | 26 +++----------------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Gitolite/Common.pm b/Gitolite/Common.pm index e5d492a..35a5c0f 100644 --- a/Gitolite/Common.pm +++ b/Gitolite/Common.pm @@ -6,7 +6,7 @@ package Gitolite::Common; #<<< @EXPORT = qw( print2 dbg _mkdir _open ln_sf tsh_rc sort_u - say _warn _chdir _print tsh_text + say _warn _chdir _print tsh_text list_phy_repos say2 _die slurp tsh_lines trace tsh_try usage tsh_run @@ -142,6 +142,26 @@ sub sort_u { my @sort_u = sort keys %uniq; return \@sort_u; } + +{ + my @phy_repos = (); + + sub list_phy_repos { + trace(3); + + # use cached value only if it exists *and* no arg was received (i.e., + # receiving *any* arg invalidates cache) + return \@phy_repos if ( @phy_repos and not @_ ); + + for my $repo (`find . -name "*.git" -prune`) { + chomp($repo); + $repo =~ s(\./(.*)\.git$)($1); + push @phy_repos, $repo; + } + return sort_u(\@phy_repos); + } +} + # ---------------------------------------------------------------------- # bare-minimum subset of 'Tsh' (see github.com/sitaramc/tsh) diff --git a/Gitolite/Conf/Store.pm b/Gitolite/Conf/Store.pm index 69056a0..518a750 100644 --- a/Gitolite/Conf/Store.pm +++ b/Gitolite/Conf/Store.pm @@ -195,10 +195,10 @@ sub store { trace(3); # first write out the ones for the physical repos - my @phy_repos = list_physical_repos(1); - _chdir($GL_REPO_BASE); - for my $repo (@phy_repos) { + my $phy_repos = list_phy_repos(1); + + for my $repo (@{ $phy_repos }) { store_1($repo); } @@ -228,26 +228,6 @@ sub check_subconf_repo_disallowed { return 1; } -{ - my @phy_repos = (); - - sub list_physical_repos { - trace(3); - _chdir($GL_REPO_BASE); - - # use cached value only if it exists *and* no arg was received (i.e., - # receiving *any* arg invalidates cache) - return @phy_repos if ( @phy_repos and not @_ ); - - for my $repo (`find . -name "*.git" -prune`) { - chomp($repo); - $repo =~ s(\./(.*)\.git$)($1); - push @phy_repos, $repo; - } - return @phy_repos; - } -} - sub store_1 { # warning: writes and *deletes* it from %repos and %configs my ($repo) = shift; From 1a1be8b222b6b4c7542af49c892eb84a498d1337 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 8 Mar 2012 19:50:34 +0530 Subject: [PATCH 296/637] 'gitolite list-phy-repos' added --- Gitolite/Common.pm | 1 + gitolite | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/Gitolite/Common.pm b/Gitolite/Common.pm index 35a5c0f..16d25ba 100644 --- a/Gitolite/Common.pm +++ b/Gitolite/Common.pm @@ -147,6 +147,7 @@ sub sort_u { my @phy_repos = (); sub list_phy_repos { + _die "'gitolite list_phy_repos' takes no arguments" if @ARGV; trace(3); # use cached value only if it exists *and* no arg was received (i.e., diff --git a/gitolite b/gitolite index dc7fb0e..8cd39d2 100755 --- a/gitolite +++ b/gitolite @@ -14,6 +14,7 @@ The following subcommands are available; they should all respond to '-h': list-groups list all group names in conf list-users list all users/user groups in conf list-repos list all repos/repo groups in conf + list-phy-repos list all repos actually on disk list-memberships list all groups a name is a member of list-members list all members of a group @@ -29,6 +30,7 @@ use FindBin; BEGIN { $ENV{GL_BINDIR} = $FindBin::Bin; } use lib $ENV{GL_BINDIR}; +use Gitolite::Rc; use Gitolite::Common; use strict; @@ -75,6 +77,10 @@ sub args { require Gitolite::Conf::Load; Gitolite::Conf::Load->import; print "$_\n" for ( @{ list_repos() } ); + } elsif ( $command eq 'list-phy-repos' ) { + shift @ARGV; + _chdir($GL_REPO_BASE); + print "$_\n" for ( @{ list_phy_repos() } ); } elsif ( $command eq 'list-memberships' ) { shift @ARGV; require Gitolite::Conf::Load; @@ -85,5 +91,7 @@ sub args { require Gitolite::Conf::Load; Gitolite::Conf::Load->import; print "$_\n" for ( @{ list_members() } ); + } else { + _die "unknown gitolite sub-command"; } } From 8ffc5307d673bfeadc59547231185c320ad4fd7e Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 16 Mar 2012 09:59:45 +0530 Subject: [PATCH 297/637] (lotsa files affected) rc file format changed; see below The rc file used to be a bunch of variables, each one requiring to be declared before being used. While this was nice and all, it was a little cumbersome to add a new flag or option. If you disregard the "catch typos" aspect of having to predeclare variables, it's a lot more useful to have all of rc be in a hash and use any hash keys you want. There could be other uses; for instance it could hold arbitrary data that you would currently put in %ENV, without having to pollute %ENV if you don't need child tasks to inherit it. ---- NOTE: I also ran perltidy, which I don't always remember to :) --- Gitolite/Commands/QueryRc.pm | 2 +- Gitolite/Commands/Setup.pm | 18 +-- Gitolite/Conf.pm | 8 +- Gitolite/Conf/Load.pm | 33 +++--- Gitolite/Conf/Store.pm | 32 +++--- Gitolite/Hooks/PostUpdate.pm | 2 +- Gitolite/Rc.pm | 90 ++++++++------- gitolite | 2 +- gitolite-shell | 2 +- src/Gitolite/Rc.pm | 214 +++++++++++++++++++++++++++++++++++ 10 files changed, 309 insertions(+), 94 deletions(-) create mode 100644 src/Gitolite/Rc.pm diff --git a/Gitolite/Commands/QueryRc.pm b/Gitolite/Commands/QueryRc.pm index a36e4bd..4d241b4 100644 --- a/Gitolite/Commands/QueryRc.pm +++ b/Gitolite/Commands/QueryRc.pm @@ -40,7 +40,7 @@ my $all = 0; # ---------------------------------------------------------------------- sub query_rc { - trace( 1, "rc file not found; default should be " . glrc_default_filename() ) if not glrc_filename(); + trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename'); my @vars = args(); diff --git a/Gitolite/Commands/Setup.pm b/Gitolite/Commands/Setup.pm index aa312ad..939de8e 100644 --- a/Gitolite/Commands/Setup.pm +++ b/Gitolite/Commands/Setup.pm @@ -56,7 +56,7 @@ sub setup { sub first_run { # if the rc file could not be found, it's *definitely* a first run! - return not glrc_filename(); + return not glrc('filename'); } sub args { @@ -91,7 +91,7 @@ sub args { sub setup_glrc { trace(1); - _print( glrc_default_filename(), glrc_default_text() ); + _print( glrc('default-filename'), glrc('default-text') ); } sub setup_gladmin { @@ -99,7 +99,7 @@ sub setup_gladmin { trace( 1, $admin ); # reminder: 'admin files' are in ~/.gitolite, 'admin repo' is - # $GL_REPO_BASE/gitolite-admin.git + # $rc{GL_REPO_BASE}/gitolite-admin.git # grab the pubkey content before we chdir() away @@ -111,8 +111,8 @@ sub setup_gladmin { # set up the admin files in admin-base - _mkdir($GL_ADMIN_BASE); - _chdir($GL_ADMIN_BASE); + _mkdir( $rc{GL_ADMIN_BASE} ); + _chdir( $rc{GL_ADMIN_BASE} ); _mkdir("conf"); my $conf; @@ -132,15 +132,15 @@ sub setup_gladmin { # set up the admin repo in repo-base _chdir(); - _mkdir($GL_REPO_BASE); - _chdir($GL_REPO_BASE); + _mkdir( $rc{GL_REPO_BASE} ); + _chdir( $rc{GL_REPO_BASE} ); new_repo("gitolite-admin"); # commit the admin files to the admin repo - $ENV{GIT_WORK_TREE} = $GL_ADMIN_BASE; - _chdir("$GL_REPO_BASE/gitolite-admin.git"); + $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE}; + _chdir("$rc{GL_REPO_BASE}/gitolite-admin.git"); system("git add conf/gitolite.conf"); system("git add keydir") if $pubkey; tsh_try("git config --get user.email") or tsh_run( "git config user.email $ENV{USER}\@" . `hostname` ); diff --git a/Gitolite/Conf.pm b/Gitolite/Conf.pm index 8f7e111..cffee63 100644 --- a/Gitolite/Conf.pm +++ b/Gitolite/Conf.pm @@ -34,7 +34,7 @@ sub compile { trace(3); # XXX assume we're in admin-base/conf - _chdir($GL_ADMIN_BASE); + _chdir( $rc{GL_ADMIN_BASE} ); _chdir("conf"); explode( 'gitolite.conf', 'master', \&parse ); @@ -99,7 +99,7 @@ sub parse { } } elsif ( $line =~ /^config (.+) = ?(.*)/ ) { my ( $key, $value ) = ( $1, $2 ); - my @validkeys = split( ' ', ( $GL_GITCONFIG_KEYS || '' ) ); + my @validkeys = split( ' ', ( $rc{GL_GITCONFIG_KEYS} || '' ) ); push @validkeys, "gitolite-options\\..*"; my @matched = grep { $key =~ /^$_$/ } @validkeys; # XXX move this also to add_config: _die "git config $key not allowed\ncheck GL_GITCONFIG_KEYS in the rc file for how to allow it" if (@matched < 1); @@ -123,8 +123,8 @@ sub incsub { # XXX move this to Macros... substitute HOSTNAME word if GL_HOSTNAME defined, otherwise leave as is # $include_glob =~ s/\bHOSTNAME\b/$GL_HOSTNAME/ if $GL_HOSTNAME; - # XXX g2 diff: include glob is *implicitly* from $GL_ADMIN_BASE/conf, not *explicitly* - # for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$GL_ADMIN_BASE/conf/$include_glob")) { + # XXX g2 diff: include glob is *implicitly* from $rc{GL_ADMIN_BASE}/conf, not *explicitly* + # for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$rc{GL_ADMIN_BASE}/conf/$include_glob")) { trace( 3, $is_subconf, $include_glob ); diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm index 9367296..3237748 100644 --- a/Gitolite/Conf/Load.pm +++ b/Gitolite/Conf/Load.pm @@ -92,7 +92,7 @@ sub access { sub load_common { - _chdir("$GL_ADMIN_BASE"); + _chdir( $rc{GL_ADMIN_BASE} ); # we take an unusual approach to caching this function! # (requires that first call to load_common is before first call to load_1) @@ -118,7 +118,7 @@ sub load_1 { my $repo = shift; trace( 4, $repo ); - _chdir("$GL_REPO_BASE"); + _chdir( $rc{GL_REPO_BASE} ); if ( $repo eq $last_repo ) { $repos{$repo} = $one_repo{$repo}; @@ -172,7 +172,7 @@ sub memberships { } sub data_version_mismatch { - return $data_version ne $current_data_version; + return $data_version ne glrc('current-data-version'); } # ---------------------------------------------------------------------- @@ -192,10 +192,10 @@ Usage: gitolite list-groups load_common(); my @g = (); - while (my ($k, $v) = each ( %groups )) { - push @g, @{ $v }; + while ( my ( $k, $v ) = each(%groups) ) { + push @g, @{$v}; } - return (sort_u(\@g)); + return ( sort_u( \@g ) ); } sub list_users { @@ -213,19 +213,18 @@ Usage: gitolite list-users load_common(); - my @u = map { keys %{ $_ } } values %repos; - $total = scalar(keys %split_conf); + my @u = map { keys %{$_} } values %repos; + $total = scalar( keys %split_conf ); warn "WARNING: you have $total repos to check; this could take some time!\n" if $total > 100; for my $one ( keys %split_conf ) { load_1($one); - $count++; print STDERR "$count / $total\r" if not ( $count % 100 ) and timer(5); - push @u, map { keys %{ $_ } } values %one_repo; + $count++; print STDERR "$count / $total\r" if not( $count % 100 ) and timer(5); + push @u, map { keys %{$_} } values %one_repo; } print STDERR "\n"; - return (sort_u(\@u)); + return ( sort_u( \@u ) ); } - sub list_repos { die " @@ -241,7 +240,7 @@ Usage: gitolite list-repos my @r = keys %repos; push @r, keys %split_conf; - return (sort_u(\@r)); + return ( sort_u( \@r ) ); } sub list_memberships { @@ -258,7 +257,7 @@ Usage: gitolite list-memberships load_common(); my @m = memberships($name); - return (sort_u(\@m)); + return ( sort_u( \@m ) ); } sub list_members { @@ -276,13 +275,13 @@ Usage: gitolite list-members load_common(); my @m = (); - while (my ($k, $v) = each ( %groups )) { - for my $g ( @{ $v } ) { + while ( my ( $k, $v ) = each(%groups) ) { + for my $g ( @{$v} ) { push @m, $k if $g eq $name; } } - return (sort_u(\@m)); + return ( sort_u( \@m ) ); } # ---------------------------------------------------------------------- diff --git a/Gitolite/Conf/Store.pm b/Gitolite/Conf/Store.pm index 518a750..b99ac0b 100644 --- a/Gitolite/Conf/Store.pm +++ b/Gitolite/Conf/Store.pm @@ -145,14 +145,14 @@ sub set_subconf { sub new_repos { trace(3); - _chdir($GL_REPO_BASE); + _chdir( $rc{GL_REPO_BASE} ); # normal repos my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } sort keys %repos; # add in members of repo groups map { push @repos, keys %{ $groups{$_} } } grep { /^@/ } keys %repos; - for my $repo ( @{ sort_u(\@repos) } ) { + for my $repo ( @{ sort_u( \@repos ) } ) { next unless $repo =~ $REPONAME_PATT; # skip repo patterns next if $repo =~ m(^\@|EXTCMD/); # skip groups and fake repos @@ -170,7 +170,7 @@ sub new_repo { _mkdir("$repo.git"); _chdir("$repo.git"); system("git init --bare >&2"); - _chdir($GL_REPO_BASE); + _chdir( $rc{GL_REPO_BASE} ); hook_1($repo); # XXX ignoring creator for now @@ -180,7 +180,7 @@ sub new_repo { sub hook_repos { trace(3); # all repos, all hooks - _chdir($GL_REPO_BASE); + _chdir( $rc{GL_REPO_BASE} ); # XXX g2 diff: we now don't care if it's a symlink -- it's upto the admin # on the server to make sure things are kosher @@ -195,14 +195,14 @@ sub store { trace(3); # first write out the ones for the physical repos - _chdir($GL_REPO_BASE); + _chdir( $rc{GL_REPO_BASE} ); my $phy_repos = list_phy_repos(1); - for my $repo (@{ $phy_repos }) { + for my $repo ( @{$phy_repos} ) { store_1($repo); } - _chdir($GL_ADMIN_BASE); + _chdir( $rc{GL_ADMIN_BASE} ); store_common(); } @@ -261,7 +261,7 @@ sub store_common { my $cc = "conf/gitolite.conf-compiled.pm"; my $compiled_fh = _open( ">", "$cc.new" ); - my $data_version = $current_data_version; + my $data_version = glrc('current-data-version'); trace( 1, "data_version = $data_version" ); print $compiled_fh Data::Dumper->Dump( [$data_version], [qw(*data_version)] ); @@ -296,20 +296,20 @@ sub store_common { # reset the gitolite supplied hooks, in case someone fiddled with # them, but only once per run if ( not $hook_reset ) { - _mkdir("$GL_ADMIN_BASE/hooks/common"); - _mkdir("$GL_ADMIN_BASE/hooks/gitolite-admin"); - _print( "$GL_ADMIN_BASE/hooks/common/update", update_hook() ); - _print( "$GL_ADMIN_BASE/hooks/gitolite-admin/post-update", post_update_hook() ); - chmod 0755, "$GL_ADMIN_BASE/hooks/common/update"; - chmod 0755, "$GL_ADMIN_BASE/hooks/gitolite-admin/post-update"; + _mkdir("$rc{GL_ADMIN_BASE}/hooks/common"); + _mkdir("$rc{GL_ADMIN_BASE}/hooks/gitolite-admin"); + _print( "$rc{GL_ADMIN_BASE}/hooks/common/update", update_hook() ); + _print( "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin/post-update", post_update_hook() ); + chmod 0755, "$rc{GL_ADMIN_BASE}/hooks/common/update"; + chmod 0755, "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin/post-update"; $hook_reset++; } # propagate user hooks - ln_sf( "$GL_ADMIN_BASE/hooks/common", "*", "$repo.git/hooks" ); + ln_sf( "$rc{GL_ADMIN_BASE}/hooks/common", "*", "$repo.git/hooks" ); # propagate admin hook - ln_sf( "$GL_ADMIN_BASE/hooks/gitolite-admin", "*", "$repo.git/hooks" ) if $repo eq 'gitolite-admin'; + ln_sf( "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin", "*", "$repo.git/hooks" ) if $repo eq 'gitolite-admin'; # g2 diff: no "site-wide" hooks (the stuff in between gitolite hooks # and user hooks) anymore. I don't think anyone used them anyway... diff --git a/Gitolite/Hooks/PostUpdate.pm b/Gitolite/Hooks/PostUpdate.pm index 813733f..1ce07b2 100644 --- a/Gitolite/Hooks/PostUpdate.pm +++ b/Gitolite/Hooks/PostUpdate.pm @@ -27,7 +27,7 @@ sub post_update { _die "no files/dirs called 'hooks' or 'logs' are allowed" if tsh_text() =~ /^(hooks|logs)$/; { - local $ENV{GIT_WORK_TREE} = $GL_ADMIN_BASE; + local $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE}; tsh_try("git checkout -f --quiet master"); } system("$ENV{GL_BINDIR}/gitolite compile"); diff --git a/Gitolite/Rc.pm b/Gitolite/Rc.pm index 94f6613..6fe0ff9 100644 --- a/Gitolite/Rc.pm +++ b/Gitolite/Rc.pm @@ -4,24 +4,14 @@ package Gitolite::Rc; # ---------------------------------------------------------------------- @EXPORT = qw( - $GL_ADMIN_BASE - $GL_REPO_BASE - - $GL_UMASK - - $GL_GITCONFIG_KEYS - - glrc_default_text - glrc_default_filename - glrc_filename + %rc + glrc $ADC_CMD_ARGS_PATT $REF_OR_FILENAME_PATT $REPONAME_PATT $REPOPATT_PATT $USERNAME_PATT - - $current_data_version ); use Exporter 'import'; @@ -32,14 +22,12 @@ use Gitolite::Common; # variables that are/could be/should be in the rc file # ---------------------------------------------------------------------- -$GL_ADMIN_BASE = "$ENV{HOME}/.gitolite"; -$GL_REPO_BASE = "$ENV{HOME}/repositories"; +$rc{GL_ADMIN_BASE} = "$ENV{HOME}/.gitolite"; +$rc{GL_REPO_BASE} = "$ENV{HOME}/repositories"; # variables that should probably never be changed # ---------------------------------------------------------------------- -$current_data_version = "3.0"; - $ADC_CMD_ARGS_PATT = qr(^[0-9a-zA-Z._\@/+:-]*$); $REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$); $REPONAME_PATT = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$); @@ -48,44 +36,56 @@ $USERNAME_PATT = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); # ---------------------------------------------------------------------- +my $current_data_version = "3.0"; + +my $rc = glrc('filename'); +do $rc if -r $rc; +# let values specified in rc file override our internal ones +@rc{ keys %RC } = values %RC; + +# ---------------------------------------------------------------------- + use strict; use warnings; # ---------------------------------------------------------------------- -my $rc = glrc_filename(); -do $rc if -r $rc; - +my $glrc_default_text = ''; { - my $glrc_default_text = ''; + local $/ = undef; + $glrc_default_text = ; +} - sub glrc_default_text { +sub glrc { + my $cmd = shift; + if ( $cmd eq 'default-filename' ) { + trace( 1, "..should happen only on first run" ); + return "$ENV{HOME}/.gitolite.rc"; + } elsif ( $cmd eq 'default-text' ) { trace( 1, "..should happen only on first run" ); return $glrc_default_text if $glrc_default_text; - local $/ = undef; - $glrc_default_text = ; + _die "rc file default text not set; this should not happen!"; + } elsif ( $cmd eq 'filename' ) { + # where is the rc file? + trace(4); + + # search $HOME first + return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc"; + trace( 2, "$ENV{HOME}/.gitolite.rc not found" ); + + # XXX for fedora, we can add the following line, but I would really prefer + # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc + # XXX return "/etc/gitolite.rc" if -f "/etc/gitolite.rc"; + + return ''; + } elsif ( $cmd eq 'current-data-version' ) { + return $current_data_version; + } else { + _die "unknown argument to glrc: $cmd"; } } -sub glrc_default_filename { - trace( 1, "..should happen only on first run" ); - return "$ENV{HOME}/.gitolite.rc"; -} - -# where is the rc file? -sub glrc_filename { - trace(4); - - # search $HOME first - return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc"; - trace( 2, "$ENV{HOME}/.gitolite.rc not found" ); - - # XXX for fedora, we can add the following line, but I would really prefer - # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc - # XXX return "/etc/gitolite.rc" if -f "/etc/gitolite.rc"; - - return ''; -} +# ---------------------------------------------------------------------- 1; @@ -99,8 +99,10 @@ __DATA__ # this file is in perl syntax. However, you do NOT need to know perl to edit # it; it should be fairly self-explanatory and easy to maintain -$GL_UMASK = 0077; -$GL_GITCONFIG_KEYS = ""; +%RC = ( + GL_UMASK => 0077, + GL_GITCONFIG_KEYS => "", +); # ------------------------------------------------------------------------------ # per perl rules, this should be the last line in such a file: diff --git a/gitolite b/gitolite index 8cd39d2..d8d1c3e 100755 --- a/gitolite +++ b/gitolite @@ -79,7 +79,7 @@ sub args { print "$_\n" for ( @{ list_repos() } ); } elsif ( $command eq 'list-phy-repos' ) { shift @ARGV; - _chdir($GL_REPO_BASE); + _chdir( $rc{GL_REPO_BASE} ); print "$_\n" for ( @{ list_phy_repos() } ); } elsif ( $command eq 'list-memberships' ) { shift @ARGV; diff --git a/gitolite-shell b/gitolite-shell index 479773c..59b2984 100755 --- a/gitolite-shell +++ b/gitolite-shell @@ -38,7 +38,7 @@ my $ret = access( $repo, $user, $aa, 'unknown' ); trace( 1, "access($repo, $user, $aa, 'unknown') -> $ret" ); _die $ret if $ret =~ /DENIED/; -$repo = "'$GL_REPO_BASE/$repo.git'"; +$repo = "'$rc{GL_REPO_BASE}/$repo.git'"; exec( "git", "shell", "-c", "$verb $repo" ); # ---------------------------------------------------------------------- diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm new file mode 100644 index 0000000..e84ee0c --- /dev/null +++ b/src/Gitolite/Rc.pm @@ -0,0 +1,214 @@ +package Gitolite::Rc; + +# everything to do with 'rc'. Also defines some 'constants' +# ---------------------------------------------------------------------- + +@EXPORT = qw( + %rc + glrc + query_rc + version + + $REMOTE_COMMAND_PATT + $REF_OR_FILENAME_PATT + $REPONAME_PATT + $REPOPATT_PATT + $USERNAME_PATT +); + +use Exporter 'import'; +use Getopt::Long; + +use Gitolite::Common; + +# ---------------------------------------------------------------------- + +our %rc; + +# ---------------------------------------------------------------------- + +# variables that are/could be/should be in the rc file +# ---------------------------------------------------------------------- + +$rc{GL_BINDIR} = $ENV{GL_BINDIR}; +$rc{GL_ADMIN_BASE} = "$ENV{HOME}/.gitolite"; +$rc{GL_REPO_BASE} = "$ENV{HOME}/repositories"; + +# variables that should probably never be changed +# ---------------------------------------------------------------------- + +$REMOTE_COMMAND_PATT = qr(^[- 0-9a-zA-Z\@\%_=+:,./]*$); +$REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$); +$REPONAME_PATT = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$); +$REPOPATT_PATT = qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/,-]*$); +$USERNAME_PATT = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); + +# ---------------------------------------------------------------------- + +my $current_data_version = "3.0"; + +my $rc = glrc('filename'); +do $rc if -r $rc; +_die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR); +# let values specified in rc file override our internal ones +@rc{ keys %RC } = values %RC; + +# testing sometimes requires all of it to be overridden silently; use an +# env var that is highly unlikely to appear in real life :) +do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC}; + +# fix PATH (TODO: do it only if 'gitolite' isn't in PATH) +$ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}"; + +# ---------------------------------------------------------------------- + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +my $glrc_default_text = ''; +{ + local $/ = undef; + $glrc_default_text = ; +} + +sub glrc { + my $cmd = shift; + if ( $cmd eq 'default-filename' ) { + return "$ENV{HOME}/.gitolite.rc"; + } elsif ( $cmd eq 'default-text' ) { + return $glrc_default_text if $glrc_default_text; + _die "rc file default text not set; this should not happen!"; + } elsif ( $cmd eq 'filename' ) { + # where is the rc file? + + # search $HOME first + return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc"; + + # XXX for fedora, we can add the following line, but I would really prefer + # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc + # XXX return "/etc/gitolite.rc" if -f "/etc/gitolite.rc"; + + return ''; + } elsif ( $cmd eq 'current-data-version' ) { + return $current_data_version; + } else { + _die "unknown argument to glrc: $cmd"; + } +} + +# ---------------------------------------------------------------------- +# implements 'gitolite query-rc' and 'version' +# ---------------------------------------------------------------------- + +# ---------------------------------------------------------------------- + +my $all = 0; +my $nonl = 0; + +sub query_rc { + + my @vars = args(); + + no strict 'refs'; + + if ($all) { + for my $e ( sort keys %rc ) { + print "$e=" . ( defined( $rc{$e} ) ? $rc{$e} : 'undef' ) . "\n"; + } + return; + } + + print join( "\t", map { $rc{$_} || '' } @vars ) . ( $nonl ? '' : "\n" ) if @vars; +} + +sub version { + my $version = ''; + $version = '(unknown)'; + for ("$rc{GL_ADMIN_BASE}/VERSION") { + $version = slurp($_) if -r $_; + } + chomp($version); + return $version; +} + +# ---------------------------------------------------------------------- + +=for args +Usage: gitolite query-rc -a + gitolite query-rc [-n] + + -a print all variables and values + -n do not append a newline + +Example: + + gitolite query-rc GL_ADMIN_BASE UMASK + # prints "/home/git/.gitolite0077" or similar + + gitolite query-rc -a + # prints all known variables and values, one per line +=cut + +sub args { + my $help = 0; + + GetOptions( + 'all|a' => \$all, + 'nonl|n' => \$nonl, + 'help|h' => \$help, + ) or usage(); + + usage("'-a' cannot be combined with other arguments") if $all and @ARGV; + usage() if not $all and not @ARGV or $help; + return @ARGV; +} + +1; + +# ---------------------------------------------------------------------- + +__DATA__ +# configuration variables for gitolite + +# This file is in perl syntax. But you do NOT need to know perl to edit it -- +# just mind the commas and make sure the brackets and braces stay matched up! + +# (Tip: perl allows a comma after the last item in a list also!) + +%RC = ( + UMASK => 0077, + GL_GITCONFIG_KEYS => "", + + # comment out or uncomment as needed + # these will run in sequence during the conf file parse + SYNTACTIC_SUGAR => + [ + # 'continuation-lines', + ], + + # comment out or uncomment as needed + # these will run in sequence after post-update + POST_COMPILE => + [ + 'post-compile/ssh-authkeys', + ], + + # comment out or uncomment as needed + # these are available to remote users + COMMANDS => + { + 'help' => 1, + 'info' => 1, + }, +); + +# ------------------------------------------------------------------------------ +# per perl rules, this should be the last line in such a file: +1; + +# Local variables: +# mode: perl +# End: +# vim: set syn=perl: From b89ac4dd1ef405506f6a5fc4f72e70162eab57e8 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 9 Mar 2012 06:22:01 +0530 Subject: [PATCH 298/637] queryrc.pm rolled into rc.pm, removed --- Gitolite/Commands/QueryRc.pm | 81 ------------------------------------ Gitolite/Rc.pm | 63 ++++++++++++++++++++++++++++ gitolite | 2 - 3 files changed, 63 insertions(+), 83 deletions(-) delete mode 100644 Gitolite/Commands/QueryRc.pm diff --git a/Gitolite/Commands/QueryRc.pm b/Gitolite/Commands/QueryRc.pm deleted file mode 100644 index 4d241b4..0000000 --- a/Gitolite/Commands/QueryRc.pm +++ /dev/null @@ -1,81 +0,0 @@ -package Gitolite::Commands::QueryRc; - -# implements 'gitolite query-rc' -# ---------------------------------------------------------------------- - -=for usage - -Usage: gitolite query-rc -a - gitolite query-rc - -Example: - - gitolite query-rc GL_ADMIN_BASE GL_UMASK - # prints "/home/git/.gitolite0077" or similar - - gitolite query-rc -a - # prints all known variables and values, one per line -=cut - -# ---------------------------------------------------------------------- - -@EXPORT = qw( - query_rc -); - -use Exporter 'import'; -use Getopt::Long; - -use lib $ENV{GL_BINDIR}; -use Gitolite::Rc; -use Gitolite::Common; - -use strict; -use warnings; - -# ---------------------------------------------------------------------- - -my $all = 0; - -# ---------------------------------------------------------------------- - -sub query_rc { - trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename'); - - my @vars = args(); - - no strict 'refs'; - - if ( $vars[0] eq '-a' ) { - for my $e (@Gitolite::Rc::EXPORT) { - # perl-ism warning: if you don't do this the implicit aliasing - # screws up Rc's EXPORT list - my $v = $e; - # we stop on the first non-$ var - last unless $v =~ s/^\$//; - print "$v=" . ( defined($$v) ? $$v : 'undef' ) . "\n"; - } - } - - our $GL_BINDIR = $ENV{GL_BINDIR}; - - print join( "\t", map { $$_ } grep { $$_ } @vars ) . "\n" if @vars; -} - -# ---------------------------------------------------------------------- - -sub args { - my $help = 0; - - GetOptions( - 'all|a' => \$all, - 'help|h' => \$help, - ) or usage(); - - usage("'-a' cannot be combined with other arguments") if $all and @ARGV; - return '-a' if $all; - usage() if not @ARGV or $help; - return @ARGV; -} - -1; diff --git a/Gitolite/Rc.pm b/Gitolite/Rc.pm index 6fe0ff9..47f0cdb 100644 --- a/Gitolite/Rc.pm +++ b/Gitolite/Rc.pm @@ -6,6 +6,7 @@ package Gitolite::Rc; @EXPORT = qw( %rc glrc + query_rc $ADC_CMD_ARGS_PATT $REF_OR_FILENAME_PATT @@ -15,10 +16,17 @@ package Gitolite::Rc; ); use Exporter 'import'; +use Getopt::Long; use lib $ENV{GL_BINDIR}; use Gitolite::Common; +# ---------------------------------------------------------------------- + +our %rc; + +# ---------------------------------------------------------------------- + # variables that are/could be/should be in the rc file # ---------------------------------------------------------------------- @@ -86,6 +94,61 @@ sub glrc { } # ---------------------------------------------------------------------- +# implements 'gitolite query-rc' +# ---------------------------------------------------------------------- + +=for usage + +Usage: gitolite query-rc -a + gitolite query-rc + +Example: + + gitolite query-rc GL_ADMIN_BASE GL_UMASK + # prints "/home/git/.gitolite0077" or similar + + gitolite query-rc -a + # prints all known variables and values, one per line +=cut + +# ---------------------------------------------------------------------- + +my $all = 0; + +sub query_rc { + trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename'); + + my @vars = args(); + + no strict 'refs'; + + if ( $vars[0] eq '-a' ) { + for my $e (sort keys %rc) { + print "$e=" . ( defined($rc{$e}) ? $rc{$e} : 'undef' ) . "\n"; + } + return; + } + + our $GL_BINDIR = $ENV{GL_BINDIR}; + + print join( "\t", map { $rc{$_} } @vars ) . "\n" if @vars; +} + +# ---------------------------------------------------------------------- + +sub args { + my $help = 0; + + GetOptions( + 'all|a' => \$all, + 'help|h' => \$help, + ) or usage(); + + usage("'-a' cannot be combined with other arguments") if $all and @ARGV; + return '-a' if $all; + usage() if not @ARGV or $help; + return @ARGV; +} 1; diff --git a/gitolite b/gitolite index d8d1c3e..f6271b8 100755 --- a/gitolite +++ b/gitolite @@ -59,8 +59,6 @@ sub args { compile(); } elsif ( $command eq 'query-rc' ) { shift @ARGV; - require Gitolite::Commands::QueryRc; - Gitolite::Commands::QueryRc->import; query_rc(); } elsif ( $command eq 'list-groups' ) { shift @ARGV; From 56be906e5d739037f640d9aa2d9cb9c46910a6de Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 9 Mar 2012 13:33:32 +0530 Subject: [PATCH 299/637] deny message change; t01 also changed accordingly --- Gitolite/Conf/Load.pm | 10 +++++----- g3-info | 2 +- gitolite-shell | 6 +++--- t/t01-basic | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm index 3237748..fd117fc 100644 --- a/Gitolite/Conf/Load.pm +++ b/Gitolite/Conf/Load.pm @@ -69,13 +69,13 @@ sub access { trace( 4, "perm=$perm, refex=$refex" ); # skip 'deny' rules if the ref is not (yet) known - next if $perm eq '-' and $ref eq 'unknown'; + next if $perm eq '-' and $ref eq 'any'; - # rule matches if ref matches or ref is unknown (see gitolite-shell) - next unless $ref =~ /^$refex/ or $ref eq 'unknown'; + # rule matches if ref matches or ref is any (see gitolite-shell) + next unless $ref =~ /^$refex/ or $ref eq 'any'; trace( 3, "DENIED by $refex" ) if $perm eq '-'; - return "DENIED: $aa access to $repo by $user (rule: $refex)" if $perm eq '-'; + return "$aa $ref $repo $user DENIED by $refex" if $perm eq '-'; # $perm can be RW\+?(C|D|CD|DC)?M?. $aa can be W, +, C or D, or # any of these followed by "M". @@ -85,7 +85,7 @@ sub access { return $refex if ( $perm =~ /$aaq/ ); } trace( 3, "DENIED by fallthru" ); - return "DENIED: $aa access to $repo by $user (fallthru)"; + return "$aa $ref $repo $user DENIED by fallthru"; } # ---------------------------------------------------------------------- diff --git a/g3-info b/g3-info index d4db40e..28fa9ad 100755 --- a/g3-info +++ b/g3-info @@ -20,7 +20,7 @@ use warnings; my $user = shift or die; my $aa; -my $ref = 'unknown'; +my $ref = 'any'; my $ret; while (<>) { diff --git a/gitolite-shell b/gitolite-shell index 59b2984..46ce08b 100755 --- a/gitolite-shell +++ b/gitolite-shell @@ -31,11 +31,11 @@ sanity($repo); $ENV{GL_REPO} = $repo; my $aa = ( $verb =~ 'upload' ? 'R' : 'W' ); -# a ref of 'unknown' signifies that this is a pre-git check, where we don't +# a ref of 'any' signifies that this is a pre-git check, where we don't # yet know the ref that will be eventually pushed (and even that won't apply # if it's a read operation). See the matching code in access() for more. -my $ret = access( $repo, $user, $aa, 'unknown' ); -trace( 1, "access($repo, $user, $aa, 'unknown') -> $ret" ); +my $ret = access( $repo, $user, $aa, 'any' ); +trace( 1, "access($repo, $user, $aa, 'any') -> $ret" ); _die $ret if $ret =~ /DENIED/; $repo = "'$rc{GL_REPO_BASE}/$repo.git'"; diff --git a/t/t01-basic b/t/t01-basic index 3970308..96d9fc9 100755 --- a/t/t01-basic +++ b/t/t01-basic @@ -15,7 +15,7 @@ try " ## clone glt clone dev2 file://gitolite-admin !ok; gsh - /FATAL: DENIED: R access to gitolite-admin by dev2 .fallthru./ + /DENIED by fallthru/ /fatal: The remote end hung up unexpectedly/ glt clone admin --progress file://gitolite-admin ok; gsh @@ -40,13 +40,13 @@ try " git status -s; ok; /M conf/gitolite.conf/ git commit -m t01a; ok; /master.*t01a/ glt push dev2 origin; !ok; gsh - /FATAL: DENIED: W access to gitolite-admin by dev2 .fallthru./ + /DENIED by fallthru/ /fatal: The remote end hung up unexpectedly/ glt push admin origin; ok; /master -. master/ tsh empty; ok; glt push admin origin master:mm !ok; gsh - /FATAL: DENIED: W access to gitolite-admin by admin .rule: refs/heads/mm./ + /DENIED by refs/heads/mm/ /remote: error: hook declined to update refs/heads/mm/ /To file://gitolite-admin/ /remote rejected. master -. mm .hook declined./ @@ -79,7 +79,7 @@ try " ## clone cd ..; ok; glt clone u1 file://t1; !ok; gsh - /FATAL: DENIED: R access to t1 by u1 .fallthru./ + /DENIED by fallthru/ /fatal: The remote end hung up unexpectedly/ glt clone u2 file://t1; ok; gsh /warning: You appear to have cloned an empty repository./ @@ -89,7 +89,7 @@ try " ## push test-commit tc1 tc2 tc2; ok; /f7153e3/ glt push u2 origin; !ok; gsh - /FATAL: DENIED: W access to t1 by u2 .fallthru./ + /DENIED by fallthru/ /fatal: The remote end hung up unexpectedly/ glt push u3 origin master; ok; gsh /master -. master/ @@ -100,7 +100,7 @@ try " glt push u3 origin; !ok; gsh /rejected.*master -. master.*non-fast-forward./ glt push u3 -f origin; !ok; gsh - /FATAL: DENIED: \\+ access to t1 by u3 .fallthru./ + /DENIED by fallthru/ /remote: error: hook declined to update refs/heads/master/ /To file://t1/ /remote rejected. master -. master .hook declined./ From dfccf1b0de0e1076125891134d6bf86797ed5f0e Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 9 Mar 2012 08:29:41 +0530 Subject: [PATCH 300/637] t01-basic completed, some change to test.pm as well --- Gitolite/Test.pm | 6 ++ t/t01-basic | 222 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 203 insertions(+), 25 deletions(-) diff --git a/Gitolite/Test.pm b/Gitolite/Test.pm index 6b4a0ae..bb4d0a9 100644 --- a/Gitolite/Test.pm +++ b/Gitolite/Test.pm @@ -27,6 +27,12 @@ use warnings; # required preamble for all tests try " DEF gsh = /TRACE: gsh.SOC=/ + DEF reject = /hook declined to update/; /remote rejected.*hook declined/; /error: failed to push some refs to/ + + DEF AP_1 = cd ../gitolite-admin; ok or die cant find admin repo clone; + DEF AP_2 = AP_1; git add conf keydir; ok; git commit -m %1; ok; /master.* %1/ + DEF ADMIN_PUSH = AP_2 %1; glt push admin origin; ok; gsh; /master -> master/ + ./g3-install -c admin cd tsh_tempdir; "; diff --git a/t/t01-basic b/t/t01-basic index 96d9fc9..83508a9 100755 --- a/t/t01-basic +++ b/t/t01-basic @@ -10,9 +10,9 @@ use Gitolite::Test; # ---------------------------------------------------------------------- try " - plan 74 + plan 213 - ## clone + ## subtest 1 glt clone dev2 file://gitolite-admin !ok; gsh /DENIED by fallthru/ @@ -35,23 +35,19 @@ put "conf/gitolite.conf", " "; try " - ## push + # push git add conf; ok git status -s; ok; /M conf/gitolite.conf/ git commit -m t01a; ok; /master.*t01a/ glt push dev2 origin; !ok; gsh /DENIED by fallthru/ /fatal: The remote end hung up unexpectedly/ - glt push admin origin; ok; /master -. master/ + glt push admin origin; ok; /master -> master/ tsh empty; ok; glt push admin origin master:mm !ok; gsh /DENIED by refs/heads/mm/ - /remote: error: hook declined to update refs/heads/mm/ - /To file://gitolite-admin/ - /remote rejected. master -. mm .hook declined./ - /error: failed to push some refs to 'file://gitolite-admin'/ - + reject "; put "conf/gitolite.conf", " @@ -69,14 +65,10 @@ put "conf/gitolite.conf", " "; try " - ## push 2 - git add conf; ok - git status -s; ok; /M conf/gitolite.conf/ - git commit -m t01b; ok; /master.*t01b/ - glt push admin origin; ok; gsh - /master -. master/ + ## subtest 2 + ADMIN_PUSH t01b - ## clone + # clone cd ..; ok; glt clone u1 file://t1; !ok; gsh /DENIED by fallthru/ @@ -86,25 +78,205 @@ try " ls -al t1; ok; /$ENV{USER}.*$ENV{USER}.*\.git/ cd t1; ok; - ## push + # push test-commit tc1 tc2 tc2; ok; /f7153e3/ glt push u2 origin; !ok; gsh /DENIED by fallthru/ /fatal: The remote end hung up unexpectedly/ glt push u3 origin master; ok; gsh - /master -. master/ + /master -> master/ - ## rewind + # rewind reset-h HEAD^; ok; /HEAD is now at 537f964 tc2/ test-tick; test-commit tc3; ok; /a691552/ glt push u3 origin; !ok; gsh - /rejected.*master -. master.*non-fast-forward./ + /rejected.*master -> master.*non-fast-forward./ glt push u3 -f origin; !ok; gsh + reject /DENIED by fallthru/ - /remote: error: hook declined to update refs/heads/master/ - /To file://t1/ - /remote rejected. master -. master .hook declined./ - /error: failed to push some refs to 'file://t1'/ glt push u4 origin +master; ok; gsh - / \\+ f7153e3...a691552 master -. master.*forced update./ + / \\+ f7153e3...a691552 master -> master.*forced update./ +"; + +put "../gitolite-admin/conf/gitolite.conf", " + \@admins = admin dev1 + repo gitolite-admin + RW+ = admin + + include 'i1.conf' +"; + +put "../gitolite-admin/conf/i1.conf", " + \@g1 = u1 + \@g2 = u2 + \@g3 = u3 + \@gaa = aa + repo \@gaa + RW+ = \@g1 + RW = \@g2 + RW+ master = \@g3 + RW master = u4 + - master = u5 + RW+ dev = u5 + RW = u5 +"; + +try " + ## subtest 3 + ADMIN_PUSH t01c + + cd ..; ok +"; + +try " + glt clone u1 file://aa; ok; gsh + cd aa; ok + test-commit set3 t1 t2 t3 t4 t5 t6 t7 t8 t9 + ok + glt push u1 origin HEAD; ok; gsh + /To file://aa/ + /\\* \\[new branch\\] HEAD -> master/ + branch dev; ok + branch foo; ok + + # u1 rewind master ok + reset-h HEAD^; ok + test-commit r1; ok + glt push u1 origin +master; ok; gsh + /To file://aa/ + /\\+ cecf671...70469f5 master -> master .forced update./ + + # u2 rewind master !ok + reset-h HEAD^; ok + test-commit r2; ok + glt push u2 origin +master; !ok; gsh + reject + /DENIED by fallthru/ + + # u3 rewind master ok + reset-h HEAD^; ok + test-commit r3; ok + glt push u3 origin +master; ok; gsh + /To file://aa/ + /\\+ 70469f5...f1e6821 master -> master .forced update./ + + # u4 push master ok + test-commit u4; ok + glt push u4 origin master; ok; gsh + /To file://aa/ + /f1e6821..d308cfb +master -> master/ + + # u4 rewind master !ok + reset-h HEAD^; ok + glt push u4 origin +master; !ok; gsh + reject + /DENIED by fallthru/ + + # u3,u4 push other branches !ok + glt push u3 origin dev; !ok; gsh + reject + /DENIED by fallthru/ + glt push u4 origin dev; !ok; gsh + reject + /DENIED by fallthru/ + glt push u3 origin foo; !ok; gsh + reject + /DENIED by fallthru/ + glt push u4 origin foo; !ok; gsh + reject + /DENIED by fallthru/ + + # clean up for next set + glt push u1 -f origin master dev foo + ok; gsh + /d308cfb...f1e6821 master -> master .forced update./ + /new branch.*dev -> dev/ + /new branch.*foo -> foo/ + + # u5 push master !ok + test-commit u5 + glt push u5 origin master; !ok; gsh + reject + /DENIED by refs/heads/master/ + + # u5 rewind dev ok + glt push u5 origin +dev^:dev + ok; gsh + /\\+ cecf671...5c8a89d dev\\^ -> dev .forced update./ + + + # u5 rewind foo !ok + glt push u5 origin +foo^:foo + !ok; gsh + reject + /remote: FATAL: \\+ refs/heads/foo aa u5 DENIED by fallthru/ + + # u5 push foo ok + git checkout foo + /Switched to branch 'foo'/ + + test-commit u5 + glt push u5 origin foo; ok; gsh + /cecf671..b27cf19 *foo -> foo/ + + # u1 delete dev ok + glt push u1 origin :dev; ok; gsh + / - \\[deleted\\] *dev/ + + # push it back + glt push u1 origin dev; ok; gsh + /\\* \\[new branch\\] *dev -> dev/ + +"; + +put "| cat >> ../gitolite-admin/conf/gitolite.conf", " + \@gr1 = r1 + repo \@gr1 + RW refs/heads/v[0-9] = u1 + RW refs/heads = tester +"; + +try " + ## subtest 4 + ADMIN_PUSH t01d + + cd ..; ok + + glt clone tester file://r1; ok; gsh + /Cloning into 'r1'.../ + cd r1; ok + test-commit r1a r1b r1c r1d r1e r1f + ok + glt push tester origin HEAD;ok; gsh + /\\* \\[new branch\\] *HEAD -> master/ + git branch v1 + glt push tester origin v1; ok; gsh + /\\* \\[new branch\\] *v1 -> v1/ + +"; + +put "| cat >> ../gitolite-admin/conf/gitolite.conf", " + \@gr2 = r2 + repo \@gr2 + RW refs/heads/v[0-9] = u1 + - refs/heads/v[0-9] = tester + RW refs/heads = tester +"; + +try " + ## subtest 5 + ADMIN_PUSH t01e + + cd ..; ok + + glt clone tester file://r2; ok; gsh + /Cloning into 'r2'.../ + cd r2; ok + test-commit r2a r2b r2c r2d r2e r2f + ok + glt push tester origin HEAD;ok; gsh + /\\* \\[new branch\\] *HEAD -> master/ + git branch v1 + glt push tester origin v1; !ok; gsh + /W refs/heads/v1 r2 tester DENIED by refs/heads/v\\[0-9\\]/ " From 4ab8db49252331785373e18ab2a3e241737cdab9 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 9 Mar 2012 17:19:29 +0530 Subject: [PATCH 301/637] gitolite/commands/setup -> gitolite/setup --- Gitolite/{Commands => }/Setup.pm | 2 +- gitolite | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename Gitolite/{Commands => }/Setup.pm (99%) diff --git a/Gitolite/Commands/Setup.pm b/Gitolite/Setup.pm similarity index 99% rename from Gitolite/Commands/Setup.pm rename to Gitolite/Setup.pm index 939de8e..28d1d74 100644 --- a/Gitolite/Commands/Setup.pm +++ b/Gitolite/Setup.pm @@ -1,4 +1,4 @@ -package Gitolite::Commands::Setup; +package Gitolite::Setup; # implements 'gitolite setup' # ---------------------------------------------------------------------- diff --git a/gitolite b/gitolite index f6271b8..646d036 100755 --- a/gitolite +++ b/gitolite @@ -48,8 +48,8 @@ sub args { if ( $command eq 'setup' ) { shift @ARGV; - require Gitolite::Commands::Setup; - Gitolite::Commands::Setup->import; + require Gitolite::Setup; + Gitolite::Setup->import; setup(); } elsif ( $command eq 'compile' ) { shift @ARGV; From acb2f8fe8e3bb6f0f593bf3ff0913a74264fd8f5 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 9 Mar 2012 21:23:16 +0530 Subject: [PATCH 302/637] sugar high! make it easy to handle syntactic sugar. In summary, compile now calls parse(sugar('gitolite.conf')). Details: - cleanup_conf_line went from subar.pm to common.pm - explode() and minions went from conf.pm to the new explode.pm - the callback went away; everyone just passes whole arrays around now - the new sugar() takes a filename and returns a listref - all sugar scripts take and return a listref - the first "built-in" sugar is written (setting gitweb.owner and gitweb.description) the new RC file format (of being a hash called %rc) is getting a nice workout :-) --- Gitolite/Common.pm | 15 +++- Gitolite/Conf.pm | 162 +++++++-------------------------------- Gitolite/Conf/Explode.pm | 121 +++++++++++++++++++++++++++++ Gitolite/Conf/Sugar.pm | 91 +++++++++++++--------- 4 files changed, 219 insertions(+), 170 deletions(-) create mode 100644 Gitolite/Conf/Explode.pm diff --git a/Gitolite/Common.pm b/Gitolite/Common.pm index 16d25ba..0435793 100644 --- a/Gitolite/Common.pm +++ b/Gitolite/Common.pm @@ -8,7 +8,7 @@ package Gitolite::Common; print2 dbg _mkdir _open ln_sf tsh_rc sort_u say _warn _chdir _print tsh_text list_phy_repos say2 _die slurp tsh_lines - trace tsh_try + trace cleanup_conf_line tsh_try usage tsh_run ); #>>> @@ -143,6 +143,19 @@ sub sort_u { return \@sort_u; } +sub cleanup_conf_line { + my $line = shift; + + # kill comments, but take care of "#" inside *simple* strings + $line =~ s/^((".*?"|[^#"])*)#.*/$1/; + # normalise whitespace; keeps later regexes very simple + $line =~ s/=/ = /; + $line =~ s/\s+/ /g; + $line =~ s/^ //; + $line =~ s/ $//; + return $line; +} + { my @phy_repos = (); diff --git a/Gitolite/Conf.pm b/Gitolite/Conf.pm index cffee63..2846f1f 100644 --- a/Gitolite/Conf.pm +++ b/Gitolite/Conf.pm @@ -23,13 +23,6 @@ use warnings; # ---------------------------------------------------------------------- -# 'seen' for include/subconf files -my %included = (); -# 'seen' for group names on LHS -my %prefixed_groupname = (); - -# ---------------------------------------------------------------------- - sub compile { trace(3); # XXX assume we're in admin-base/conf @@ -37,7 +30,7 @@ sub compile { _chdir( $rc{GL_ADMIN_BASE} ); _chdir("conf"); - explode( 'gitolite.conf', 'master', \&parse ); + parse(sugar('gitolite.conf')); # the order matters; new repos should be created first, to give store a # place to put the individual gl-conf files @@ -45,139 +38,42 @@ sub compile { store(); } -sub explode { - trace( 4, @_ ); - my ( $file, $subconf, $parser ) = @_; - - # $parser is a ref to a callback; if not supplied we just print - $parser ||= sub { print shift, "\n"; }; - - # seed the 'seen' list if it's empty - $included{ device_inode("conf/gitolite.conf") }++ unless %included; - - my $fh = _open( "<", $file ); - my @fh = <$fh>; - my @lines = macro_expand( "# BEGIN $file\n", @fh, "# END $file\n" ); - my $line; - while (@lines) { - $line = shift @lines; - - $line = cleanup_conf_line($line); - next unless $line =~ /\S/; - - $line = prefix_groupnames( $line, $subconf ) if $subconf ne 'master'; - - if ( $line =~ /^(include|subconf) "(.+)"$/ or $line =~ /^(include|subconf) '(.+)'$/ ) { - incsub( $1, $2, $subconf, $parser ); - } else { - # normal line, send it to the callback function - $parser->($line); - } - } -} - sub parse { - trace( 4, @_ ); - my $line = shift; + my $lines = shift; + trace(4, scalar(@$lines) . " lines incoming"); - # user or repo groups - if ( $line =~ /^(@\S+) = (.*)/ ) { - add_to_group( $1, split( ' ', $2 ) ); - } elsif ( $line =~ /^repo (.*)/ ) { - set_repolist( split( ' ', $1 ) ); - } elsif ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) { - my $perm = $1; - my @refs = parse_refs( $2 || '' ); - my @users = parse_users($3); + for my $line (@$lines) { + # user or repo groups + if ( $line =~ /^(@\S+) = (.*)/ ) { + add_to_group( $1, split( ' ', $2 ) ); + } elsif ( $line =~ /^repo (.*)/ ) { + set_repolist( split( ' ', $1 ) ); + } elsif ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) { + my $perm = $1; + my @refs = parse_refs( $2 || '' ); + my @users = parse_users($3); - # XXX what do we do? s/\bCREAT[EO]R\b/~\$creator/g for @users; + # XXX what do we do? s/\bCREAT[EO]R\b/~\$creator/g for @users; - for my $ref (@refs) { - for my $user (@users) { - add_rule( $perm, $ref, $user ); + for my $ref (@refs) { + for my $user (@users) { + add_rule( $perm, $ref, $user ); + } } - } - } elsif ( $line =~ /^config (.+) = ?(.*)/ ) { - my ( $key, $value ) = ( $1, $2 ); - my @validkeys = split( ' ', ( $rc{GL_GITCONFIG_KEYS} || '' ) ); - push @validkeys, "gitolite-options\\..*"; - my @matched = grep { $key =~ /^$_$/ } @validkeys; - # XXX move this also to add_config: _die "git config $key not allowed\ncheck GL_GITCONFIG_KEYS in the rc file for how to allow it" if (@matched < 1); - # XXX both $key and $value must satisfy a liberal but secure pattern - add_config( 1, $key, $value ); - } elsif ( $line =~ /^subconf (\S+)$/ ) { - set_subconf($1); - } else { - _warn "?? $line"; - } -} - -# ---------------------------------------------------------------------- - -sub incsub { - my $is_subconf = ( +shift eq 'subconf' ); - my ( $include_glob, $subconf, $parser ) = @_; - - _die "subconf $subconf attempting to run 'subconf'\n" if $is_subconf and $subconf ne 'master'; - - # XXX move this to Macros... substitute HOSTNAME word if GL_HOSTNAME defined, otherwise leave as is - # $include_glob =~ s/\bHOSTNAME\b/$GL_HOSTNAME/ if $GL_HOSTNAME; - - # XXX g2 diff: include glob is *implicitly* from $rc{GL_ADMIN_BASE}/conf, not *explicitly* - # for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$rc{GL_ADMIN_BASE}/conf/$include_glob")) { - - trace( 3, $is_subconf, $include_glob ); - - for my $file ( glob($include_glob) ) { - _warn("included file not found: '$file'"), next unless -f $file; - _die "invalid include/subconf filename $file" unless $file =~ m(([^/]+).conf$); - my $basename = $1; - - next if already_included($file); - - if ($is_subconf) { - $parser->("subconf $basename"); - explode( $file, $basename, $parser ); - $parser->("subconf $subconf"); - # XXX g2 delegaton compat: deal with this: $subconf_seen++; + } elsif ( $line =~ /^config (.+) = ?(.*)/ ) { + my ( $key, $value ) = ( $1, $2 ); + my @validkeys = split( ' ', ( $rc{GL_GITCONFIG_KEYS} || '' ) ); + push @validkeys, "gitolite-options\\..*"; + my @matched = grep { $key =~ /^$_$/ } @validkeys; + # XXX move this also to add_config: _die "git config $key not allowed\ncheck GL_GITCONFIG_KEYS in the rc file for how to allow it" if (@matched < 1); + # XXX both $key and $value must satisfy a liberal but secure pattern + add_config( 1, $key, $value ); + } elsif ( $line =~ /^subconf (\S+)$/ ) { + set_subconf($1); } else { - explode( $file, $subconf, $parser ); + _warn "?? $line"; } } } -sub prefix_groupnames { - my ( $line, $subconf ) = @_; - - my $lhs = ''; - # save 'foo' if it's an '@foo = list' line - $lhs = $1 if $line =~ /^@(\S+) = /; - # prefix all @groups in the line - $line =~ s/(^| )(@\S+)(?= |$)/ $1 . ($prefixed_groupname{$subconf}{$2} || $2) /ge; - # now prefix the LHS and store it if needed - if ($lhs) { - $line =~ s/^@\S+ = /"\@$subconf.$lhs = "/e; - trace( 3, "prefixed_groupname.$subconf.\@$lhs = \@$subconf.$lhs" ); - } - - return $line; -} - -sub already_included { - my $file = shift; - - my $file_id = device_inode($file); - return 0 unless $included{$file_id}++; - - _warn("$file already included"); - trace( 3, "$file already included" ); - return 1; -} - -sub device_inode { - my $file = shift; - trace( 3, $file, ( stat $file )[ 0, 1 ] ); - return join( "/", ( stat $file )[ 0, 1 ] ); -} - 1; diff --git a/Gitolite/Conf/Explode.pm b/Gitolite/Conf/Explode.pm new file mode 100644 index 0000000..43a5778 --- /dev/null +++ b/Gitolite/Conf/Explode.pm @@ -0,0 +1,121 @@ +package Gitolite::Conf::Explode; + +# include/subconf processor +# ---------------------------------------------------------------------- + +@EXPORT = qw( + explode +); + +use Exporter 'import'; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Common; + +use strict; +use warnings; + +# ---------------------------------------------------------------------- + +# 'seen' for include/subconf files +my %included = (); +# 'seen' for group names on LHS +my %prefixed_groupname = (); + +sub explode { + trace( 4, @_ ); + my ( $file, $subconf, $out ) = @_; + + # seed the 'seen' list if it's empty + $included{ device_inode("conf/gitolite.conf") }++ unless %included; + + my $fh = _open( "<", $file ); + while (<$fh>) { + my $line = cleanup_conf_line($_); + next unless $line =~ /\S/; + + $line = prefix_groupnames( $line, $subconf ) if $subconf ne 'master'; + + if ( $line =~ /^(include|subconf) (\S.+)$/ ) { + incsub( $1, $2, $subconf, $out ); + } else { + # normal line, send it to the callback function + push @{$out}, $line; + } + } +} + +sub incsub { + my $is_subconf = ( +shift eq 'subconf' ); + my ( $include_glob, $subconf, $out ) = @_; + + _die "subconf $subconf attempting to run 'subconf'\n" if $is_subconf and $subconf ne 'master'; + + _die "invalid include/subconf file/glob '$include_glob'" + unless $include_glob =~ /^"(.+)"$/ + or $include_glob =~ /^'(.+)'$/; + $include_glob = $1; + + # XXX move this to Macros... substitute HOSTNAME word if GL_HOSTNAME defined, otherwise leave as is + # $include_glob =~ s/\bHOSTNAME\b/$GL_HOSTNAME/ if $GL_HOSTNAME; + + # XXX g2 diff: include glob is *implicitly* from $rc{GL_ADMIN_BASE}/conf, not *explicitly* + # for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$rc{GL_ADMIN_BASE}/conf/$include_glob")) { + + trace( 3, $is_subconf, $include_glob ); + + for my $file ( glob($include_glob) ) { + _warn("included file not found: '$file'"), next unless -f $file; + _die "invalid include/subconf filename $file" unless $file =~ m(([^/]+).conf$); + my $basename = $1; + + next if already_included($file); + + if ($is_subconf) { + push @{$out}, "subconf $basename"; + explode( $file, $basename, $out ); + push @{$out}, "subconf $subconf"; + # XXX g2 delegaton compat: deal with this: $subconf_seen++; + } else { + explode( $file, $subconf, $out ); + } + } +} + +sub prefix_groupnames { + my ( $line, $subconf ) = @_; + + my $lhs = ''; + # save 'foo' if it's an '@foo = list' line + $lhs = $1 if $line =~ /^@(\S+) = /; + # prefix all @groups in the line + $line =~ s/(^| )(@\S+)(?= |$)/ $1 . ($prefixed_groupname{$subconf}{$2} || $2) /ge; + # now prefix the LHS and store it if needed + if ($lhs) { + $line =~ s/^@\S+ = /"\@$subconf.$lhs = "/e; + $prefixed_groupname{$subconf}{"\@$lhs"} = "\@$subconf.$lhs"; + trace( 3, "prefixed_groupname.$subconf.\@$lhs = \@$subconf.$lhs" ); + } + + return $line; +} + +sub already_included { + my $file = shift; + + my $file_id = device_inode($file); + return 0 unless $included{$file_id}++; + + _warn("$file already included"); + trace( 3, "$file already included" ); + return 1; +} + +sub device_inode { + my $file = shift; + trace( 3, $file, ( stat $file )[ 0, 1 ] ); + return join( "/", ( stat $file )[ 0, 1 ] ); +} + +1; + diff --git a/Gitolite/Conf/Sugar.pm b/Gitolite/Conf/Sugar.pm index 5db96f2..bfa7c5f 100644 --- a/Gitolite/Conf/Sugar.pm +++ b/Gitolite/Conf/Sugar.pm @@ -4,78 +4,97 @@ package Gitolite::Conf::Sugar; # ---------------------------------------------------------------------- @EXPORT = qw( - macro_expand - cleanup_conf_line + sugar ); use Exporter 'import'; use lib $ENV{GL_BINDIR}; -use Gitolite::Common; use Gitolite::Rc; +use Gitolite::Common; +use Gitolite::Conf::Explode; use strict; use warnings; # ---------------------------------------------------------------------- -sub macro_expand { - # site-local macros, if any, then gitolite internal macros, to munge the - # input conf line if needed +sub sugar { + # gets a filename, returns a listref - my @lines = @_; + my @lines = (); + explode(shift, 'master', \@lines); - # TODO: user macros, how to allow the user to specify them? + my $lines; + $lines = \@lines; - # cheat, to keep *our* regexes simple :) - # XXX but this also kills the special '# BEGIN filename' and '# END - # filename' lines that explode() surrounds the actual data with when it - # called macro_expand(). Right now we don't need it, but... - @lines = grep /\S/, map { cleanup_conf_line($_) } @lines; + # run through the sugar stack one by one - @lines = owner_desc(@lines); + # first, user supplied sugar: + if (exists $rc{SYNTACTIC_SUGAR}) { + if (ref($rc{SYNTACTIC_SUGAR}) ne 'ARRAY') { + _warn "bad syntax for specifying sugar scripts; see docs"; + } else { + for my $s (@{ $rc{SYNTACTIC_SUGAR} }) { + _warn "ignoring unreadable sugar script $s" if not -r $s; + do $s if -r $s; + $lines = sugar_script($lines); + $lines = [ grep /\S/, map { cleanup_conf_line($_) } @$lines ]; + } + } + } - return @lines; -} + # then our stuff: -sub cleanup_conf_line { - my $line = shift; + $lines = owner_desc($lines); + # $lines = name_vref($lines); - # kill comments, but take care of "#" inside *simple* strings - $line =~ s/^((".*?"|[^#"])*)#.*/$1/; - # normalise whitespace; keeps later regexes very simple - $line =~ s/=/ = /; - $line =~ s/\s+/ /g; - $line =~ s/^ //; - $line =~ s/ $//; - return $line; + return $lines; } sub owner_desc { - my @lines = @_; + my $lines = shift; my @ret; - for my $line (@lines) { - # reponame = "some description string" - # reponame "owner name" = "some description string" + # XXX compat breakage: (1) adding repo/owner does not automatically add an + # entry to projects.list -- we need a post-procesor for that, and (2) + # removing the 'repo' line no longer suffices to remove the config entry + # from projects.list. Maybe the post-procesor should do that as well? + + # owner = "owner name" + # -> config gitweb.owner = owner name + # description = "some long description" + # -> config gitweb.description = some long description + # category = "whatever..." + # -> config gitweb.category = whatever... + + # older formats: + # repo = "some long description" + # repo = "owner name" = "some long description" + # -> config gitweb.owner = owner name + # -> config gitweb.description = some long description + + for my $line (@$lines) { if ( $line =~ /^(\S+)(?: "(.*?)")? = "(.*)"$/ ) { my ( $repo, $owner, $desc ) = ( $1, $2, $3 ); # XXX these two checks should go into add_config # _die "bad repo name '$repo'" unless $repo =~ $REPONAME_PATT; # _die "$fragment attempting to set description for $repo" # if check_fragment_repo_disallowed( $fragment, $repo ); - push @ret, "config gitolite-options.repo-desc = $desc"; - push @ret, "config gitolite-options.repo-owner = $owner" if $owner; + push @ret, "repo $repo"; + push @ret, "config gitweb.description = $desc"; + push @ret, "config gitweb.owner = $owner" if $owner; } elsif ( $line =~ /^desc = (\S.*)/ ) { - push @ret, "config gitolite-options.repo-desc = $1"; + push @ret, "config gitweb.description = $1"; } elsif ( $line =~ /^owner = (\S.*)/ ) { - my ( $repo, $owner, $desc ) = ( $1, $2, $3 ); - push @ret, "config gitolite-options.repo-owner = $1"; + push @ret, "config gitweb.owner = $1"; + } elsif ( $line =~ /^category = (\S.*)/ ) { + push @ret, "config gitweb.category = $1"; } else { push @ret, $line; } } - return @ret; + return \@ret; } 1; From 379b0c9549faa2699d49da22fe49d3048e12f0a1 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 10 Mar 2012 18:56:29 +0530 Subject: [PATCH 303/637] install/test made easy (WARNING: read below) (1) testing is very easy, just run this from a clone t/g3-clean-install-setup-test BUT BE WARNED THIS IS DESTRUCTIVE; details in t/WARNING (2) install is equally simple; see 'INSTALL' in the main directory --- Gitolite/Rc.pm | 177 ------------------ INSTALL | 17 ++ g3-info | 35 ---- g3-install | 20 -- gitolite | 95 ---------- {Gitolite => src/Gitolite}/Common.pm | 15 +- {Gitolite => src/Gitolite}/Conf.pm | 1 - {Gitolite => src/Gitolite}/Conf/Explode.pm | 1 - {Gitolite => src/Gitolite}/Conf/Load.pm | 3 +- {Gitolite => src/Gitolite}/Conf/Store.pm | 3 +- {Gitolite => src/Gitolite}/Conf/Sugar.pm | 1 - .../Gitolite}/Hooks/PostUpdate.pm | 3 +- {Gitolite => src/Gitolite}/Hooks/Update.pm | 1 - src/Gitolite/Rc.pm | 113 ++++------- {Gitolite => src/Gitolite}/Setup.pm | 7 +- {Gitolite => src/Gitolite}/Test.pm | 12 +- {Gitolite => src/Gitolite}/Test/Tsh.pm | 0 src/gitolite | 117 ++++++------ gitolite-shell => src/gitolite-shell | 6 +- t/README | 8 + t/{t01-basic => basic.t} | 2 +- t/gitolite-receive-pack | 2 +- t/gitolite-upload-pack | 2 +- t/glt | 7 +- t/reset | 8 + 25 files changed, 162 insertions(+), 494 deletions(-) delete mode 100644 Gitolite/Rc.pm create mode 100644 INSTALL delete mode 100755 g3-info delete mode 100755 g3-install delete mode 100755 gitolite rename {Gitolite => src/Gitolite}/Common.pm (92%) rename {Gitolite => src/Gitolite}/Conf.pm (98%) rename {Gitolite => src/Gitolite}/Conf/Explode.pm (99%) rename {Gitolite => src/Gitolite}/Conf/Load.pm (99%) rename {Gitolite => src/Gitolite}/Conf/Store.pm (99%) rename {Gitolite => src/Gitolite}/Conf/Sugar.pm (99%) rename {Gitolite => src/Gitolite}/Hooks/PostUpdate.pm (94%) rename {Gitolite => src/Gitolite}/Hooks/Update.pm (99%) rename {Gitolite => src/Gitolite}/Setup.pm (96%) rename {Gitolite => src/Gitolite}/Test.pm (70%) rename {Gitolite => src/Gitolite}/Test/Tsh.pm (100%) rename gitolite-shell => src/gitolite-shell (93%) create mode 100644 t/README rename t/{t01-basic => basic.t} (99%) create mode 100755 t/reset diff --git a/Gitolite/Rc.pm b/Gitolite/Rc.pm deleted file mode 100644 index 47f0cdb..0000000 --- a/Gitolite/Rc.pm +++ /dev/null @@ -1,177 +0,0 @@ -package Gitolite::Rc; - -# everything to do with 'rc'. Also defines some 'constants' -# ---------------------------------------------------------------------- - -@EXPORT = qw( - %rc - glrc - query_rc - - $ADC_CMD_ARGS_PATT - $REF_OR_FILENAME_PATT - $REPONAME_PATT - $REPOPATT_PATT - $USERNAME_PATT -); - -use Exporter 'import'; -use Getopt::Long; - -use lib $ENV{GL_BINDIR}; -use Gitolite::Common; - -# ---------------------------------------------------------------------- - -our %rc; - -# ---------------------------------------------------------------------- - -# variables that are/could be/should be in the rc file -# ---------------------------------------------------------------------- - -$rc{GL_ADMIN_BASE} = "$ENV{HOME}/.gitolite"; -$rc{GL_REPO_BASE} = "$ENV{HOME}/repositories"; - -# variables that should probably never be changed -# ---------------------------------------------------------------------- - -$ADC_CMD_ARGS_PATT = qr(^[0-9a-zA-Z._\@/+:-]*$); -$REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$); -$REPONAME_PATT = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$); -$REPOPATT_PATT = qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/,-]*$); -$USERNAME_PATT = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); - -# ---------------------------------------------------------------------- - -my $current_data_version = "3.0"; - -my $rc = glrc('filename'); -do $rc if -r $rc; -# let values specified in rc file override our internal ones -@rc{ keys %RC } = values %RC; - -# ---------------------------------------------------------------------- - -use strict; -use warnings; - -# ---------------------------------------------------------------------- - -my $glrc_default_text = ''; -{ - local $/ = undef; - $glrc_default_text = ; -} - -sub glrc { - my $cmd = shift; - if ( $cmd eq 'default-filename' ) { - trace( 1, "..should happen only on first run" ); - return "$ENV{HOME}/.gitolite.rc"; - } elsif ( $cmd eq 'default-text' ) { - trace( 1, "..should happen only on first run" ); - return $glrc_default_text if $glrc_default_text; - _die "rc file default text not set; this should not happen!"; - } elsif ( $cmd eq 'filename' ) { - # where is the rc file? - trace(4); - - # search $HOME first - return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc"; - trace( 2, "$ENV{HOME}/.gitolite.rc not found" ); - - # XXX for fedora, we can add the following line, but I would really prefer - # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc - # XXX return "/etc/gitolite.rc" if -f "/etc/gitolite.rc"; - - return ''; - } elsif ( $cmd eq 'current-data-version' ) { - return $current_data_version; - } else { - _die "unknown argument to glrc: $cmd"; - } -} - -# ---------------------------------------------------------------------- -# implements 'gitolite query-rc' -# ---------------------------------------------------------------------- - -=for usage - -Usage: gitolite query-rc -a - gitolite query-rc - -Example: - - gitolite query-rc GL_ADMIN_BASE GL_UMASK - # prints "/home/git/.gitolite0077" or similar - - gitolite query-rc -a - # prints all known variables and values, one per line -=cut - -# ---------------------------------------------------------------------- - -my $all = 0; - -sub query_rc { - trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename'); - - my @vars = args(); - - no strict 'refs'; - - if ( $vars[0] eq '-a' ) { - for my $e (sort keys %rc) { - print "$e=" . ( defined($rc{$e}) ? $rc{$e} : 'undef' ) . "\n"; - } - return; - } - - our $GL_BINDIR = $ENV{GL_BINDIR}; - - print join( "\t", map { $rc{$_} } @vars ) . "\n" if @vars; -} - -# ---------------------------------------------------------------------- - -sub args { - my $help = 0; - - GetOptions( - 'all|a' => \$all, - 'help|h' => \$help, - ) or usage(); - - usage("'-a' cannot be combined with other arguments") if $all and @ARGV; - return '-a' if $all; - usage() if not @ARGV or $help; - return @ARGV; -} - -1; - -# ---------------------------------------------------------------------- - -__DATA__ -# configuration variables for gitolite - -# PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS - -# this file is in perl syntax. However, you do NOT need to know perl to edit -# it; it should be fairly self-explanatory and easy to maintain - -%RC = ( - GL_UMASK => 0077, - GL_GITCONFIG_KEYS => "", -); - -# ------------------------------------------------------------------------------ -# per perl rules, this should be the last line in such a file: -1; - -# Local variables: -# mode: perl -# End: -# vim: set syn=perl: diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..b1c634d --- /dev/null +++ b/INSTALL @@ -0,0 +1,17 @@ +1. Clone the repo and copy src somewhere (or leave it where it is, if you're + sure no one will 'git pull' on a running system!) + + cp -a src /some/full/path + +2. (Optional) Make a symlink for the single executable 'gitolite' to + somewhere in `$PATH` + + ln -sf /full/path/to/some/damn/place/gitolite $HOME/bin + +3. Run setup. That is, either run: + + gitolite setup -a YourName -pk /tmp/YourName.pub + + or, if you did not do step 2, run: + + /some/full/path/src/gitolite -a YourName -pk /tmp/YourName.pub diff --git a/g3-info b/g3-info deleted file mode 100755 index 28fa9ad..0000000 --- a/g3-info +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/perl - -# gitolite shell, invoked from ~/.ssh/authorized_keys -# ---------------------------------------------------------------------- - -BEGIN { - # find and set bin dir - $ENV{GL_BINDIR} = "$ENV{HOME}/bin"; -} - -use lib $ENV{GL_BINDIR}; -use Gitolite::Rc; -use Gitolite::Common; -use Gitolite::Conf::Load; - -use strict; -use warnings; - -# ---------------------------------------------------------------------- - -my $user = shift or die; -my $aa; -my $ref = 'any'; - -my $ret; -while (<>) { - chomp; - - my $perm = ''; - for $aa (qw(R W C)) { - $ret = access($_, $user, $aa, $ref); - $perm .= ( $ret =~ /DENIED/ ? " " : " $aa" ); - } - print "$perm\t$_\n" if $perm =~ /\S/; -} diff --git a/g3-install b/g3-install deleted file mode 100755 index ef40012..0000000 --- a/g3-install +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# this is specific to my test env; you may want to change it - -set -e - -cd /home/g3 - -if [ "$1" = "-c" ] -then - rm -rf .gito* gito* repositories proj* bin - mkdir bin - cp ~/.ssh/id_rsa.pub ~/.ssh/admin.pub - - cd g3; cp -a gito* Gito* t/glt t/gito* ~/bin - gitolite setup -a ${2:-admin} -pk ~/.ssh/admin.pub -else - cd g3; cp -a gito* Gito* t/glt t/gito* ~/bin - gitolite setup -fi diff --git a/gitolite b/gitolite deleted file mode 100755 index 646d036..0000000 --- a/gitolite +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/perl - -# all gitolite CLI tools run as sub-commands of this command -# ---------------------------------------------------------------------- - -=for usage -Usage: gitolite [sub-command] [options] - -The following subcommands are available; they should all respond to '-h': - - setup 1st run: initial setup; all runs: hook fixups - compile compile gitolite.conf - query-rc get values of rc variables - list-groups list all group names in conf - list-users list all users/user groups in conf - list-repos list all repos/repo groups in conf - list-phy-repos list all repos actually on disk - list-memberships list all groups a name is a member of - list-members list all members of a group - -Warnings: - - list-users is disk bound and could take a while on sites with 1000s of repos - - list-memberships does not check if the name is known; unknown names come - back with 2 answers: the name itself and '@all' -=cut - -# ---------------------------------------------------------------------- - -use FindBin; - -BEGIN { $ENV{GL_BINDIR} = $FindBin::Bin; } -use lib $ENV{GL_BINDIR}; -use Gitolite::Rc; -use Gitolite::Common; - -use strict; -use warnings; - -# ---------------------------------------------------------------------- - -args(); - -# ---------------------------------------------------------------------- - -sub args { - my ( $command, @args ) = @ARGV; - usage() if not $command or $command eq '-h'; - - if ( $command eq 'setup' ) { - shift @ARGV; - require Gitolite::Setup; - Gitolite::Setup->import; - setup(); - } elsif ( $command eq 'compile' ) { - shift @ARGV; - _die "'gitolite compile' does not take any arguments" if @ARGV; - require Gitolite::Conf; - Gitolite::Conf->import; - compile(); - } elsif ( $command eq 'query-rc' ) { - shift @ARGV; - query_rc(); - } elsif ( $command eq 'list-groups' ) { - shift @ARGV; - require Gitolite::Conf::Load; - Gitolite::Conf::Load->import; - print "$_\n" for ( @{ list_groups() } ); - } elsif ( $command eq 'list-users' ) { - shift @ARGV; - require Gitolite::Conf::Load; - Gitolite::Conf::Load->import; - print "$_\n" for ( @{ list_users() } ); - } elsif ( $command eq 'list-repos' ) { - shift @ARGV; - require Gitolite::Conf::Load; - Gitolite::Conf::Load->import; - print "$_\n" for ( @{ list_repos() } ); - } elsif ( $command eq 'list-phy-repos' ) { - shift @ARGV; - _chdir( $rc{GL_REPO_BASE} ); - print "$_\n" for ( @{ list_phy_repos() } ); - } elsif ( $command eq 'list-memberships' ) { - shift @ARGV; - require Gitolite::Conf::Load; - Gitolite::Conf::Load->import; - print "$_\n" for ( @{ list_memberships() } ); - } elsif ( $command eq 'list-members' ) { - shift @ARGV; - require Gitolite::Conf::Load; - Gitolite::Conf::Load->import; - print "$_\n" for ( @{ list_members() } ); - } else { - _die "unknown gitolite sub-command"; - } -} diff --git a/Gitolite/Common.pm b/src/Gitolite/Common.pm similarity index 92% rename from Gitolite/Common.pm rename to src/Gitolite/Common.pm index 0435793..6c62403 100644 --- a/Gitolite/Common.pm +++ b/src/Gitolite/Common.pm @@ -7,7 +7,7 @@ package Gitolite::Common; @EXPORT = qw( print2 dbg _mkdir _open ln_sf tsh_rc sort_u say _warn _chdir _print tsh_text list_phy_repos - say2 _die slurp tsh_lines + say2 _die _system slurp tsh_lines trace cleanup_conf_line tsh_try usage tsh_run ); @@ -97,6 +97,19 @@ sub _chdir { chdir( $_[0] || $ENV{HOME} ) or _die "chdir $_[0] failed: $!\n"; } +sub _system { + if ( system(@_) != 0 ) { + say2 "system @_ failed"; + if ( $? == -1 ) { + die "failed to execute: $!\n"; + } elsif ( $? & 127 ) { + die "child died with signal " . ( $? & 127 ) . "\n"; + } else { + die "child exited with value " . ( $? >> 8 ) . "\n"; + } + } +} + sub _open { open( my $fh, $_[0], $_[1] ) or _die "open $_[1] failed: $!\n"; return $fh; diff --git a/Gitolite/Conf.pm b/src/Gitolite/Conf.pm similarity index 98% rename from Gitolite/Conf.pm rename to src/Gitolite/Conf.pm index 2846f1f..6c7dca6 100644 --- a/Gitolite/Conf.pm +++ b/src/Gitolite/Conf.pm @@ -12,7 +12,6 @@ package Gitolite::Conf; use Exporter 'import'; use Getopt::Long; -use lib $ENV{GL_BINDIR}; use Gitolite::Common; use Gitolite::Rc; use Gitolite::Conf::Sugar; diff --git a/Gitolite/Conf/Explode.pm b/src/Gitolite/Conf/Explode.pm similarity index 99% rename from Gitolite/Conf/Explode.pm rename to src/Gitolite/Conf/Explode.pm index 43a5778..a821dc9 100644 --- a/Gitolite/Conf/Explode.pm +++ b/src/Gitolite/Conf/Explode.pm @@ -9,7 +9,6 @@ package Gitolite::Conf::Explode; use Exporter 'import'; -use lib $ENV{GL_BINDIR}; use Gitolite::Common; use strict; diff --git a/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm similarity index 99% rename from Gitolite/Conf/Load.pm rename to src/Gitolite/Conf/Load.pm index fd117fc..1b0d199 100644 --- a/Gitolite/Conf/Load.pm +++ b/src/Gitolite/Conf/Load.pm @@ -16,7 +16,6 @@ package Gitolite::Conf::Load; use Exporter 'import'; -use lib $ENV{GL_BINDIR}; use Gitolite::Common; use Gitolite::Rc; @@ -108,7 +107,7 @@ sub load_common { _die "parse $cc failed: " . ( $! or $@ ) unless do $cc; if ( data_version_mismatch() ) { - system("gitolite setup"); + _system("gitolite setup"); _die "parse $cc failed: " . ( $! or $@ ) unless do $cc; _die "data version update failed; this is serious" if data_version_mismatch(); } diff --git a/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm similarity index 99% rename from Gitolite/Conf/Store.pm rename to src/Gitolite/Conf/Store.pm index b99ac0b..37824c7 100644 --- a/Gitolite/Conf/Store.pm +++ b/src/Gitolite/Conf/Store.pm @@ -22,7 +22,6 @@ use Data::Dumper; $Data::Dumper::Indent = 1; $Data::Dumper::Sortkeys = 1; -use lib $ENV{GL_BINDIR}; use Gitolite::Common; use Gitolite::Rc; use Gitolite::Hooks::Update; @@ -169,7 +168,7 @@ sub new_repo { _mkdir("$repo.git"); _chdir("$repo.git"); - system("git init --bare >&2"); + _system("git init --bare >&2"); _chdir( $rc{GL_REPO_BASE} ); hook_1($repo); diff --git a/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm similarity index 99% rename from Gitolite/Conf/Sugar.pm rename to src/Gitolite/Conf/Sugar.pm index bfa7c5f..932928d 100644 --- a/Gitolite/Conf/Sugar.pm +++ b/src/Gitolite/Conf/Sugar.pm @@ -9,7 +9,6 @@ package Gitolite::Conf::Sugar; use Exporter 'import'; -use lib $ENV{GL_BINDIR}; use Gitolite::Rc; use Gitolite::Common; use Gitolite::Conf::Explode; diff --git a/Gitolite/Hooks/PostUpdate.pm b/src/Gitolite/Hooks/PostUpdate.pm similarity index 94% rename from Gitolite/Hooks/PostUpdate.pm rename to src/Gitolite/Hooks/PostUpdate.pm index 1ce07b2..ef8e522 100644 --- a/Gitolite/Hooks/PostUpdate.pm +++ b/src/Gitolite/Hooks/PostUpdate.pm @@ -10,7 +10,6 @@ package Gitolite::Hooks::PostUpdate; use Exporter 'import'; -use lib $ENV{GL_BINDIR}; use Gitolite::Rc; use Gitolite::Common; @@ -30,7 +29,7 @@ sub post_update { local $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE}; tsh_try("git checkout -f --quiet master"); } - system("$ENV{GL_BINDIR}/gitolite compile"); + _system("$ENV{GL_BINDIR}/gitolite compile"); exit 0; } diff --git a/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm similarity index 99% rename from Gitolite/Hooks/Update.pm rename to src/Gitolite/Hooks/Update.pm index 2c60914..488a3ec 100644 --- a/Gitolite/Hooks/Update.pm +++ b/src/Gitolite/Hooks/Update.pm @@ -10,7 +10,6 @@ package Gitolite::Hooks::Update; use Exporter 'import'; -use lib $ENV{GL_BINDIR}; use Gitolite::Common; use Gitolite::Conf::Load; diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm index e84ee0c..18cc6c5 100644 --- a/src/Gitolite/Rc.pm +++ b/src/Gitolite/Rc.pm @@ -7,9 +7,8 @@ package Gitolite::Rc; %rc glrc query_rc - version - $REMOTE_COMMAND_PATT + $ADC_CMD_ARGS_PATT $REF_OR_FILENAME_PATT $REPONAME_PATT $REPOPATT_PATT @@ -37,7 +36,7 @@ $rc{GL_REPO_BASE} = "$ENV{HOME}/repositories"; # variables that should probably never be changed # ---------------------------------------------------------------------- -$REMOTE_COMMAND_PATT = qr(^[- 0-9a-zA-Z\@\%_=+:,./]*$); +$ADC_CMD_ARGS_PATT = qr(^[0-9a-zA-Z._\@/+:-]*$); $REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$); $REPONAME_PATT = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$); $REPOPATT_PATT = qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/,-]*$); @@ -49,17 +48,9 @@ my $current_data_version = "3.0"; my $rc = glrc('filename'); do $rc if -r $rc; -_die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR); # let values specified in rc file override our internal ones @rc{ keys %RC } = values %RC; -# testing sometimes requires all of it to be overridden silently; use an -# env var that is highly unlikely to appear in real life :) -do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC}; - -# fix PATH (TODO: do it only if 'gitolite' isn't in PATH) -$ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}"; - # ---------------------------------------------------------------------- use strict; @@ -76,15 +67,19 @@ my $glrc_default_text = ''; sub glrc { my $cmd = shift; if ( $cmd eq 'default-filename' ) { + trace( 1, "..should happen only on first run" ); return "$ENV{HOME}/.gitolite.rc"; } elsif ( $cmd eq 'default-text' ) { + trace( 1, "..should happen only on first run" ); return $glrc_default_text if $glrc_default_text; _die "rc file default text not set; this should not happen!"; } elsif ( $cmd eq 'filename' ) { # where is the rc file? + trace(4); # search $HOME first return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc"; + trace( 2, "$ENV{HOME}/.gitolite.rc not found" ); # XXX for fedora, we can add the following line, but I would really prefer # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc @@ -99,68 +94,56 @@ sub glrc { } # ---------------------------------------------------------------------- -# implements 'gitolite query-rc' and 'version' +# implements 'gitolite query-rc' # ---------------------------------------------------------------------- -# ---------------------------------------------------------------------- +=for usage -my $all = 0; -my $nonl = 0; - -sub query_rc { - - my @vars = args(); - - no strict 'refs'; - - if ($all) { - for my $e ( sort keys %rc ) { - print "$e=" . ( defined( $rc{$e} ) ? $rc{$e} : 'undef' ) . "\n"; - } - return; - } - - print join( "\t", map { $rc{$_} || '' } @vars ) . ( $nonl ? '' : "\n" ) if @vars; -} - -sub version { - my $version = ''; - $version = '(unknown)'; - for ("$rc{GL_ADMIN_BASE}/VERSION") { - $version = slurp($_) if -r $_; - } - chomp($version); - return $version; -} - -# ---------------------------------------------------------------------- - -=for args Usage: gitolite query-rc -a - gitolite query-rc [-n] - - -a print all variables and values - -n do not append a newline + gitolite query-rc Example: - gitolite query-rc GL_ADMIN_BASE UMASK + gitolite query-rc GL_ADMIN_BASE GL_UMASK # prints "/home/git/.gitolite0077" or similar gitolite query-rc -a # prints all known variables and values, one per line =cut +# ---------------------------------------------------------------------- + +my $all = 0; + +sub query_rc { + trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename'); + + my @vars = args(); + + no strict 'refs'; + + if ( $vars[0] eq '-a' ) { + for my $e (sort keys %rc) { + print "$e=" . ( defined($rc{$e}) ? $rc{$e} : 'undef' ) . "\n"; + } + return; + } + + print join( "\t", map { $rc{$_} } @vars ) . "\n" if @vars; +} + +# ---------------------------------------------------------------------- + sub args { my $help = 0; GetOptions( 'all|a' => \$all, - 'nonl|n' => \$nonl, 'help|h' => \$help, ) or usage(); usage("'-a' cannot be combined with other arguments") if $all and @ARGV; + return '-a' if $all; usage() if not $all and not @ARGV or $help; return @ARGV; } @@ -172,36 +155,14 @@ sub args { __DATA__ # configuration variables for gitolite -# This file is in perl syntax. But you do NOT need to know perl to edit it -- -# just mind the commas and make sure the brackets and braces stay matched up! +# PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS -# (Tip: perl allows a comma after the last item in a list also!) +# this file is in perl syntax. However, you do NOT need to know perl to edit +# it; it should be fairly self-explanatory and easy to maintain %RC = ( UMASK => 0077, GL_GITCONFIG_KEYS => "", - - # comment out or uncomment as needed - # these will run in sequence during the conf file parse - SYNTACTIC_SUGAR => - [ - # 'continuation-lines', - ], - - # comment out or uncomment as needed - # these will run in sequence after post-update - POST_COMPILE => - [ - 'post-compile/ssh-authkeys', - ], - - # comment out or uncomment as needed - # these are available to remote users - COMMANDS => - { - 'help' => 1, - 'info' => 1, - }, ); # ------------------------------------------------------------------------------ diff --git a/Gitolite/Setup.pm b/src/Gitolite/Setup.pm similarity index 96% rename from Gitolite/Setup.pm rename to src/Gitolite/Setup.pm index 28d1d74..b42e0fc 100644 --- a/Gitolite/Setup.pm +++ b/src/Gitolite/Setup.pm @@ -28,7 +28,6 @@ Later runs: use Exporter 'import'; use Getopt::Long; -use lib $ENV{GL_BINDIR}; use Gitolite::Rc; use Gitolite::Common; use Gitolite::Conf::Store; @@ -47,7 +46,7 @@ sub setup { setup_gladmin( $admin, $pubkey, $argv ); } - system("$ENV{GL_BINDIR}/gitolite compile"); + _system("$ENV{GL_BINDIR}/gitolite compile"); hook_repos(); # all of them, just to be sure } @@ -141,8 +140,8 @@ sub setup_gladmin { $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE}; _chdir("$rc{GL_REPO_BASE}/gitolite-admin.git"); - system("git add conf/gitolite.conf"); - system("git add keydir") if $pubkey; + _system("git add conf/gitolite.conf"); + _system("git add keydir") if $pubkey; tsh_try("git config --get user.email") or tsh_run( "git config user.email $ENV{USER}\@" . `hostname` ); tsh_try("git config --get user.name") or tsh_run( "git config user.name '$ENV{USER} on '" . `hostname` ); tsh_try("git diff --cached --quiet") diff --git a/Gitolite/Test.pm b/src/Gitolite/Test.pm similarity index 70% rename from Gitolite/Test.pm rename to src/Gitolite/Test.pm index bb4d0a9..dcaf3fe 100644 --- a/Gitolite/Test.pm +++ b/src/Gitolite/Test.pm @@ -7,6 +7,7 @@ package Gitolite::Test; @EXPORT = qw( try put + text ); #>>> use Exporter 'import'; @@ -17,6 +18,7 @@ BEGIN { require Gitolite::Test::Tsh; *{'try'} = \&Tsh::try; *{'put'} = \&Tsh::put; + *{'text'} = \&Tsh::text; } use strict; @@ -30,11 +32,15 @@ try " DEF reject = /hook declined to update/; /remote rejected.*hook declined/; /error: failed to push some refs to/ DEF AP_1 = cd ../gitolite-admin; ok or die cant find admin repo clone; - DEF AP_2 = AP_1; git add conf keydir; ok; git commit -m %1; ok; /master.* %1/ + DEF AP_2 = AP_1; git add conf ; ok; git commit -m %1; ok; /master.* %1/ DEF ADMIN_PUSH = AP_2 %1; glt push admin origin; ok; gsh; /master -> master/ - ./g3-install -c admin + mkdir -p $ENV{HOME}/bin + ln -sf $ENV{PWD}/src/gitolite $ENV{PWD}/t/glt ~/bin + cd; rm -vrf .gito* gito* repositories + cd tsh_tempdir; -"; + gitolite setup -a admin +" or die "could not setup the test environment; errors:\n\n" . text() . "\n\n"; 1; diff --git a/Gitolite/Test/Tsh.pm b/src/Gitolite/Test/Tsh.pm similarity index 100% rename from Gitolite/Test/Tsh.pm rename to src/Gitolite/Test/Tsh.pm diff --git a/src/gitolite b/src/gitolite index c6a1f54..0457cc2 100755 --- a/src/gitolite +++ b/src/gitolite @@ -3,17 +3,14 @@ # all gitolite CLI tools run as sub-commands of this command # ---------------------------------------------------------------------- -=for args -Usage: gitolite [] [] +=for usage +Usage: gitolite [sub-command] [options] -The following built-in subcommands are available; they should all respond to -'-h' if you want further details on each: +The following subcommands are available; they should all respond to '-h': setup 1st run: initial setup; all runs: hook fixups compile compile gitolite.conf - query-rc get values of rc variables - list-groups list all group names in conf list-users list all users/user groups in conf list-repos list all repos/repo groups in conf @@ -25,10 +22,6 @@ Warnings: - list-users is disk bound and could take a while on sites with 1000s of repos - list-memberships does not check if the name is known; unknown names come back with 2 answers: the name itself and '@all' - -In addition, running 'gitolite help' should give you a list of custom commands -available. They may or may not respond to '-h', depending on how they were -written. =cut # ---------------------------------------------------------------------- @@ -45,62 +38,58 @@ use warnings; # ---------------------------------------------------------------------- -my ( $command, @args ) = @ARGV; -gl_log( 'gitolite', @ARGV ) if -d $rc{GL_ADMIN_BASE}; args(); -# the first two commands need options via @ARGV, as they have their own -# GetOptions calls and older perls don't have 'GetOptionsFromArray' - -if ( $command eq 'setup' ) { - shift @ARGV; - require Gitolite::Setup; - Gitolite::Setup->import; - setup(); - -} elsif ( $command eq 'query-rc' ) { - shift @ARGV; - query_rc(); - -# the rest don't need @ARGV per se - -} elsif ( $command eq 'compile' ) { - require Gitolite::Conf; - Gitolite::Conf->import; - compile(@args); - -} elsif ( $command eq 'trigger' ) { - trigger(@args); - -} elsif ( -x "$rc{GL_BINDIR}/commands/$command" ) { - trace( 2, "attempting gitolite command $command" ); - run_command( $command, @args ); - -} elsif ( $command eq 'list-phy-repos' ) { - _chdir( $rc{GL_REPO_BASE} ); - print "$_\n" for ( @{ list_phy_repos(@args) } ); - -} elsif ( $command =~ /^list-/ ) { - trace( 2, "attempting lister command $command" ); - require Gitolite::Conf::Load; - Gitolite::Conf::Load->import; - my $fn = lister_dispatch($command); - print "$_\n" for ( @{ $fn->(@args) } ); - -} else { - _die "unknown gitolite sub-command"; -} - -sub args { - usage() if not $command or $command eq '-h'; -} - # ---------------------------------------------------------------------- -sub run_command { - my $pgm = shift; - my $fullpath = "$ENV{GL_BINDIR}/commands/$pgm"; - _die "$pgm not found or not executable" if not -x $fullpath; - _system( $fullpath, @_ ); - exit 0; +sub args { + my ( $command, @args ) = @ARGV; + usage() if not $command or $command eq '-h'; + + if ( $command eq 'setup' ) { + shift @ARGV; + require Gitolite::Setup; + Gitolite::Setup->import; + setup(); + } elsif ( $command eq 'compile' ) { + shift @ARGV; + _die "'gitolite compile' does not take any arguments" if @ARGV; + require Gitolite::Conf; + Gitolite::Conf->import; + compile(); + } elsif ( $command eq 'query-rc' ) { + shift @ARGV; + query_rc(); + } elsif ( $command eq 'list-groups' ) { + shift @ARGV; + require Gitolite::Conf::Load; + Gitolite::Conf::Load->import; + print "$_\n" for ( @{ list_groups() } ); + } elsif ( $command eq 'list-users' ) { + shift @ARGV; + require Gitolite::Conf::Load; + Gitolite::Conf::Load->import; + print "$_\n" for ( @{ list_users() } ); + } elsif ( $command eq 'list-repos' ) { + shift @ARGV; + require Gitolite::Conf::Load; + Gitolite::Conf::Load->import; + print "$_\n" for ( @{ list_repos() } ); + } elsif ( $command eq 'list-phy-repos' ) { + shift @ARGV; + _chdir( $rc{GL_REPO_BASE} ); + print "$_\n" for ( @{ list_phy_repos() } ); + } elsif ( $command eq 'list-memberships' ) { + shift @ARGV; + require Gitolite::Conf::Load; + Gitolite::Conf::Load->import; + print "$_\n" for ( @{ list_memberships() } ); + } elsif ( $command eq 'list-members' ) { + shift @ARGV; + require Gitolite::Conf::Load; + Gitolite::Conf::Load->import; + print "$_\n" for ( @{ list_members() } ); + } else { + _die "unknown gitolite sub-command"; + } } diff --git a/gitolite-shell b/src/gitolite-shell similarity index 93% rename from gitolite-shell rename to src/gitolite-shell index 46ce08b..d7f6a19 100755 --- a/gitolite-shell +++ b/src/gitolite-shell @@ -3,11 +3,9 @@ # gitolite shell, invoked from ~/.ssh/authorized_keys # ---------------------------------------------------------------------- -BEGIN { - # find and set bin dir - $0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ( $1 || "$ENV{PWD}/" ) . $2; -} +use FindBin; +BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; } use lib $ENV{GL_BINDIR}; use Gitolite::Rc; use Gitolite::Common; diff --git a/t/README b/t/README new file mode 100644 index 0000000..c863b41 --- /dev/null +++ b/t/README @@ -0,0 +1,8 @@ +WARNING: THE TEST SUITE DELETES STUFF FIRST! + +Testing gitolite3 is now one command after the clone: + + prove + +But because it starts by cleaning the slate, it's best to do it on a spare +userid that you are ok to lose data on. diff --git a/t/t01-basic b/t/basic.t similarity index 99% rename from t/t01-basic rename to t/basic.t index 83508a9..96c4012 100755 --- a/t/t01-basic +++ b/t/basic.t @@ -3,7 +3,7 @@ use strict; use warnings; # this is hardcoded; change it if needed -use lib "$ENV{HOME}/bin"; +use lib "src"; use Gitolite::Test; # basic tests diff --git a/t/gitolite-receive-pack b/t/gitolite-receive-pack index 48c7428..a4cc5be 100755 --- a/t/gitolite-receive-pack +++ b/t/gitolite-receive-pack @@ -9,4 +9,4 @@ $repo =~ s/\.git$//; my $user = $ENV{G3T_USER} || 'no-such-user'; $ENV{SSH_ORIGINAL_COMMAND} = "git-receive-pack '$repo'"; -exec( "$ENV{HOME}/bin/gitolite-shell", $user ); +exec( "$ENV{GL_BINDIR}/../src/gitolite-shell", $user ); diff --git a/t/gitolite-upload-pack b/t/gitolite-upload-pack index 8888abb..5981f17 100755 --- a/t/gitolite-upload-pack +++ b/t/gitolite-upload-pack @@ -9,4 +9,4 @@ $repo =~ s/\.git$//; my $user = $ENV{G3T_USER} || 'no-such-user'; $ENV{SSH_ORIGINAL_COMMAND} = "git-upload-pack '$repo'"; -exec( "$ENV{HOME}/bin/gitolite-shell", $user ); +exec( "$ENV{GL_BINDIR}/../src/gitolite-shell", $user ); diff --git a/t/glt b/t/glt index b5704f5..45e7b19 100755 --- a/t/glt +++ b/t/glt @@ -2,6 +2,9 @@ use strict; use warnings; +use FindBin; +BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; } + print STDERR "TRACE: glt(", join( ")(", @ARGV ), ")\n"; my $cmd = shift or die "need command"; @@ -10,9 +13,9 @@ my $rc; $ENV{G3T_USER} = $user; if ( $cmd eq 'push' ) { - $rc = system( "git", $cmd, "--receive-pack=$ENV{HOME}/bin/gitolite-receive-pack", @ARGV ); + $rc = system( "git", $cmd, "--receive-pack=$ENV{GL_BINDIR}/gitolite-receive-pack", @ARGV ); } else { - $rc = system( "git", $cmd, "--upload-pack=$ENV{HOME}/bin/gitolite-upload-pack", @ARGV ); + $rc = system( "git", $cmd, "--upload-pack=$ENV{GL_BINDIR}/gitolite-upload-pack", @ARGV ); } if ( $? == -1 ) { diff --git a/t/reset b/t/reset new file mode 100755 index 0000000..8c5dcbf --- /dev/null +++ b/t/reset @@ -0,0 +1,8 @@ +#!/usr/bin/perl +use strict; +use warnings; + +# this is hardcoded; change it if needed +use lib "src"; +use Gitolite::Test; +try 'put'; From a0305ec029c93474f7c4cb09d27b25e1ba2dd428 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 10 Mar 2012 13:56:02 +0530 Subject: [PATCH 304/637] sugar 'option'; see below option foo = bar -> config gitolite-options.foo = bar --- src/Gitolite/Conf/Sugar.pm | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm index 932928d..881cbcf 100644 --- a/src/Gitolite/Conf/Sugar.pm +++ b/src/Gitolite/Conf/Sugar.pm @@ -45,12 +45,30 @@ sub sugar { # then our stuff: + $lines = option($lines); $lines = owner_desc($lines); # $lines = name_vref($lines); return $lines; } +sub option { + my $lines = shift; + my @ret; + + # option foo = bar + # -> config gitolite-options.foo = bar + + for my $line (@$lines) { + if ( $line =~ /^option (\S+) = (\S.*)/ ) { + push @ret, "config gitolite-options.$1 = $2"; + } else { + push @ret, $line; + } + } + return \@ret; +} + sub owner_desc { my $lines = shift; my @ret; From 0fdd80f4871102f7f6312ffa84cc2e490b2df09d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 10 Mar 2012 18:57:04 +0530 Subject: [PATCH 305/637] (tests) added a module test for explode plus a helper function to Test.pm (helps while developing tests) --- src/Gitolite/Test.pm | 8 ++++ t/m-explode.t | 102 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100755 t/m-explode.t diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm index dcaf3fe..8251c41 100644 --- a/src/Gitolite/Test.pm +++ b/src/Gitolite/Test.pm @@ -8,6 +8,7 @@ package Gitolite::Test; try put text + dump ); #>>> use Exporter 'import'; @@ -43,4 +44,11 @@ try " gitolite setup -a admin " or die "could not setup the test environment; errors:\n\n" . text() . "\n\n"; +sub dump { + use Data::Dumper; + for my $i (@_) { + print STDERR "DBG: " . Dumper($i); + } +} + 1; diff --git a/t/m-explode.t b/t/m-explode.t new file mode 100755 index 0000000..aef337f --- /dev/null +++ b/t/m-explode.t @@ -0,0 +1,102 @@ +#!/usr/bin/perl +use strict; +use warnings; +use 5.10.0; + +use Test; +BEGIN { plan tests => + 2 +} + +use lib "$ENV{PWD}/src"; +use Gitolite::Test; +use Gitolite::Conf::Explode; + +my @out; +my @out2; + +warn " + <<< expect a couple of warnings about already included files >>> +"; + +# test 1 -- space normalisation + + put "foo", " + foo line 1 + foo=line 2 + + + foo 3 + "; + @out = (); + explode("foo", 'master', \@out); + @out2 = ( + 'foo line 1', + 'foo = line 2', + 'foo 3', + ); + + ok(@out ~~ @out2); + +# test 2 -- include/subconf processing + + put "foo", " + foo line 1 + \@fog=line 2 + include \"bar.conf\" + + foo line=5 + subconf \"subs/baz.conf\" + include \"bar.conf\" + foo line=7 + include \"bazup.conf\" + "; + + put "bar.conf", " + \@brg=line 1 + + bar line 3 + "; + + mkdir("subs"); + + put "subs/baz.conf", " + \@bzg = line 1 + + include \"subs/baz2.conf\" + + baz=line 3 + "; + + put "subs/baz2.conf", " + baz2 line 1 + baz2 line 2 + include \"bazup.conf\" + baz2 line 4 + "; + + put "bazup.conf", " + whatever... + "; + + @out = (); + explode("foo", 'master', \@out); + + @out2 = ( + 'foo line 1', + '@fog = line 2', + '@brg = line 1', + 'bar line 3', + 'foo line = 5', + 'subconf baz', + '@baz.bzg = line 1', + 'baz2 line 1', + 'baz2 line 2', + 'whatever...', + 'baz2 line 4', + 'baz = line 3', + 'subconf master', + 'foo line = 7' + ); + + ok(@out ~~ @out2); From a9d5adcd106329ab1802e0197ffea44d479e0649 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 10 Mar 2012 19:43:42 +0530 Subject: [PATCH 306/637] example sugar script 'continuation-lines' added --- src/Gitolite/Conf/Sugar.pm | 24 +++++++++++++++--- src/Gitolite/Rc.pm | 9 ++++++- src/syntactic-sugar/continuation-lines | 34 ++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 4 deletions(-) create mode 100755 src/syntactic-sugar/continuation-lines diff --git a/src/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm index 881cbcf..dbf0926 100644 --- a/src/Gitolite/Conf/Sugar.pm +++ b/src/Gitolite/Conf/Sugar.pm @@ -1,3 +1,16 @@ +# and now for something completely different... + +package SugarBox; + +sub run_sugar_script { + my ($ss, $lref) = @_; + do $ss if -x $ss; + $lref = sugar_script($lref); + return $lref; +} + +# ---------------------------------------------------------------------- + package Gitolite::Conf::Sugar; # syntactic sugar for the conf file, including site-local macros @@ -35,9 +48,14 @@ sub sugar { _warn "bad syntax for specifying sugar scripts; see docs"; } else { for my $s (@{ $rc{SYNTACTIC_SUGAR} }) { - _warn "ignoring unreadable sugar script $s" if not -r $s; - do $s if -r $s; - $lines = sugar_script($lines); + + # perl-ism; apart from keeping the full path separate from the + # simple name, this also protects %rc from change by implicit + # aliasing, which would happen if you touched $s itself + my $sfp = "$ENV{GL_BINDIR}/syntactic-sugar/$s"; + + _warn("skipped sugar script '$s'"), next if not -x $sfp; + $lines = SugarBox::run_sugar_script($sfp, $lines); $lines = [ grep /\S/, map { cleanup_conf_line($_) } @$lines ]; } } diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm index 18cc6c5..0b81bf8 100644 --- a/src/Gitolite/Rc.pm +++ b/src/Gitolite/Rc.pm @@ -158,11 +158,18 @@ __DATA__ # PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS # this file is in perl syntax. However, you do NOT need to know perl to edit -# it; it should be fairly self-explanatory and easy to maintain +# it; it should be fairly self-explanatory and easy to maintain. Just mind +# the commas, make sure the brackets and braces stay matched up! %RC = ( UMASK => 0077, GL_GITCONFIG_KEYS => "", + + # uncomment as needed + SYNTACTIC_SUGAR => + [ + # 'continuation-lines', + ] ); # ------------------------------------------------------------------------------ diff --git a/src/syntactic-sugar/continuation-lines b/src/syntactic-sugar/continuation-lines new file mode 100755 index 0000000..1d25379 --- /dev/null +++ b/src/syntactic-sugar/continuation-lines @@ -0,0 +1,34 @@ +# vim: syn=perl: + +# "sugar script" (syntactic sugar helper) for gitolite3 + +# Enabling this script in the rc file allows you to use back-slash escaped +# continuation lines, like in C or shell etc. + +# This script also serves as an example "sugar script" if you want to write +# your own (and maybe send them to me). A "sugar script" in gitolite will be +# executed via a perl 'do' and is expected to contain one function called +# 'sugar_script'. This function should take a listref and return a listref. +# Each item in the list is one line. There are NO newlines; g3 kills them off +# fairly early in the process. + +# If you're not familiar with perl please do not try this. Ask me to write +# you a sugar script instead. + +sub sugar_script { + my $lines = shift; + + my @out = (); + my $keep = ''; + for my $l (@$lines) { + if ($l =~ s/\\$//) { + $keep .= $l; + } else { + $l = $keep . $l if $keep; + $keep = ''; + push @out, $l; + } + } + + return \@out; +} From 877f6eb31bc75bc1b5bffb38a1b603874bfae451 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 10 Mar 2012 20:30:14 +0530 Subject: [PATCH 307/637] catch older gitolite.rc and die gracefully --- src/Gitolite/Rc.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm index 0b81bf8..7ed5cef 100644 --- a/src/Gitolite/Rc.pm +++ b/src/Gitolite/Rc.pm @@ -48,6 +48,7 @@ my $current_data_version = "3.0"; my $rc = glrc('filename'); do $rc if -r $rc; +_die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR); # let values specified in rc file override our internal ones @rc{ keys %RC } = values %RC; From 56cda99edd13bb8ae13fe517b0e800f93665626e Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 10 Mar 2012 22:57:01 +0530 Subject: [PATCH 308/637] fixup CWD in access(); see below Calling access() changes the CWD to $GL_REPO_BASE! This causes a problem in the update script -- you're suddenly in the wrong directory after calling access()! This is actually happening inside load_1(), so fix that. --- src/Gitolite/Conf/Load.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm index 1b0d199..171927e 100644 --- a/src/Gitolite/Conf/Load.pm +++ b/src/Gitolite/Conf/Load.pm @@ -117,7 +117,7 @@ sub load_1 { my $repo = shift; trace( 4, $repo ); - _chdir( $rc{GL_REPO_BASE} ); + _chdir( "$rc{GL_REPO_BASE}/$repo.git" ); if ( $repo eq $last_repo ) { $repos{$repo} = $one_repo{$repo}; @@ -125,10 +125,10 @@ sub load_1 { return; } - if ( -f "$repo.git/gl-conf" ) { + if ( -f "gl-conf" ) { _die "split conf not set, gl-conf present for $repo" if not $split_conf{$repo}; - my $cc = "$repo.git/gl-conf"; + my $cc = "gl-conf"; _die "parse $cc failed: " . ( $! or $@ ) unless do $cc; $last_repo = $repo; From 17476318b9e0f4e2c9b515b1f63c74a0f37f1596 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Mar 2012 09:26:12 +0530 Subject: [PATCH 309/637] (trace) formatting changed when more than one arg passed --- src/Gitolite/Common.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm index 6c62403..d2f2cea 100644 --- a/src/Gitolite/Common.pm +++ b/src/Gitolite/Common.pm @@ -39,10 +39,11 @@ sub say2 { sub trace { return unless defined( $ENV{D} ); - my $level = shift; + my $level = shift; return if $ENV{D} < $level; my $args = ''; $args = join( ", ", @_ ) if @_; my $sub = ( caller 1 )[3] || ''; $sub =~ s/.*://; $sub .= ' ' x ( 32 - length($sub) ); - say2 "TRACE $level $sub", $args if $ENV{D} >= $level; + say2 "TRACE $level $sub", (@_ ? shift : ()); + say2("TRACE $level " . (" " x 32), $_)for @_; } sub dbg { From fb69f6e3284253086bb4e00c4dff9aa841051ca6 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Mar 2012 13:10:32 +0530 Subject: [PATCH 310/637] (test setup) make Test.pm do a bit more --- src/Gitolite/Test.pm | 14 +++++++- t/basic.t | 76 +++++++++++++++++++++++--------------------- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm index 8251c41..0e15181 100644 --- a/src/Gitolite/Test.pm +++ b/src/Gitolite/Test.pm @@ -36,12 +36,24 @@ try " DEF AP_2 = AP_1; git add conf ; ok; git commit -m %1; ok; /master.* %1/ DEF ADMIN_PUSH = AP_2 %1; glt push admin origin; ok; gsh; /master -> master/ + DEF CS_1 = pwd; //tmp/tsh_tempdir.*gitolite-admin/; git remote -v; ok; /file://gitolite-admin/ + DEF CHECK_SETUP = CS_1; git log; ok; /65a1b2acd78dd9a7a401fe81c25380c1ca90067c/ + + DEF CLONE = glt clone + DEF PUSH = glt push + + # clean install mkdir -p $ENV{HOME}/bin ln -sf $ENV{PWD}/src/gitolite $ENV{PWD}/t/glt ~/bin cd; rm -vrf .gito* gito* repositories - cd tsh_tempdir; + # setup gitolite setup -a admin + + # clone admin repo + cd tsh_tempdir + glt clone admin --progress file://gitolite-admin + cd gitolite-admin " or die "could not setup the test environment; errors:\n\n" . text() . "\n\n"; sub dump { diff --git a/t/basic.t b/t/basic.t index 96c4012..8b1aa7c 100755 --- a/t/basic.t +++ b/t/basic.t @@ -10,14 +10,16 @@ use Gitolite::Test; # ---------------------------------------------------------------------- try " - plan 213 + plan 218 + CHECK_SETUP ## subtest 1 - glt clone dev2 file://gitolite-admin + cd .. + CLONE dev2 file://gitolite-admin ga2 !ok; gsh /DENIED by fallthru/ /fatal: The remote end hung up unexpectedly/ - glt clone admin --progress file://gitolite-admin + CLONE admin --progress file://gitolite-admin ga2 ok; gsh /Counting/; /Compressing/; /Total/ cd gitolite-admin; ok @@ -39,12 +41,12 @@ try " git add conf; ok git status -s; ok; /M conf/gitolite.conf/ git commit -m t01a; ok; /master.*t01a/ - glt push dev2 origin; !ok; gsh + PUSH dev2 origin; !ok; gsh /DENIED by fallthru/ /fatal: The remote end hung up unexpectedly/ - glt push admin origin; ok; /master -> master/ + PUSH admin origin; ok; /master -> master/ tsh empty; ok; - glt push admin origin master:mm + PUSH admin origin master:mm !ok; gsh /DENIED by refs/heads/mm/ reject @@ -70,31 +72,31 @@ try " # clone cd ..; ok; - glt clone u1 file://t1; !ok; gsh + CLONE u1 file://t1; !ok; gsh /DENIED by fallthru/ /fatal: The remote end hung up unexpectedly/ - glt clone u2 file://t1; ok; gsh + CLONE u2 file://t1; ok; gsh /warning: You appear to have cloned an empty repository./ ls -al t1; ok; /$ENV{USER}.*$ENV{USER}.*\.git/ cd t1; ok; # push test-commit tc1 tc2 tc2; ok; /f7153e3/ - glt push u2 origin; !ok; gsh + PUSH u2 origin; !ok; gsh /DENIED by fallthru/ /fatal: The remote end hung up unexpectedly/ - glt push u3 origin master; ok; gsh + PUSH u3 origin master; ok; gsh /master -> master/ # rewind reset-h HEAD^; ok; /HEAD is now at 537f964 tc2/ test-tick; test-commit tc3; ok; /a691552/ - glt push u3 origin; !ok; gsh + PUSH u3 origin; !ok; gsh /rejected.*master -> master.*non-fast-forward./ - glt push u3 -f origin; !ok; gsh + PUSH u3 -f origin; !ok; gsh reject /DENIED by fallthru/ - glt push u4 origin +master; ok; gsh + PUSH u4 origin +master; ok; gsh / \\+ f7153e3...a691552 master -> master.*forced update./ "; @@ -129,11 +131,11 @@ try " "; try " - glt clone u1 file://aa; ok; gsh + CLONE u1 file://aa; ok; gsh cd aa; ok test-commit set3 t1 t2 t3 t4 t5 t6 t7 t8 t9 ok - glt push u1 origin HEAD; ok; gsh + PUSH u1 origin HEAD; ok; gsh /To file://aa/ /\\* \\[new branch\\] HEAD -> master/ branch dev; ok @@ -142,52 +144,52 @@ try " # u1 rewind master ok reset-h HEAD^; ok test-commit r1; ok - glt push u1 origin +master; ok; gsh + PUSH u1 origin +master; ok; gsh /To file://aa/ /\\+ cecf671...70469f5 master -> master .forced update./ # u2 rewind master !ok reset-h HEAD^; ok test-commit r2; ok - glt push u2 origin +master; !ok; gsh + PUSH u2 origin +master; !ok; gsh reject /DENIED by fallthru/ # u3 rewind master ok reset-h HEAD^; ok test-commit r3; ok - glt push u3 origin +master; ok; gsh + PUSH u3 origin +master; ok; gsh /To file://aa/ /\\+ 70469f5...f1e6821 master -> master .forced update./ # u4 push master ok test-commit u4; ok - glt push u4 origin master; ok; gsh + PUSH u4 origin master; ok; gsh /To file://aa/ /f1e6821..d308cfb +master -> master/ # u4 rewind master !ok reset-h HEAD^; ok - glt push u4 origin +master; !ok; gsh + PUSH u4 origin +master; !ok; gsh reject /DENIED by fallthru/ # u3,u4 push other branches !ok - glt push u3 origin dev; !ok; gsh + PUSH u3 origin dev; !ok; gsh reject /DENIED by fallthru/ - glt push u4 origin dev; !ok; gsh + PUSH u4 origin dev; !ok; gsh reject /DENIED by fallthru/ - glt push u3 origin foo; !ok; gsh + PUSH u3 origin foo; !ok; gsh reject /DENIED by fallthru/ - glt push u4 origin foo; !ok; gsh + PUSH u4 origin foo; !ok; gsh reject /DENIED by fallthru/ # clean up for next set - glt push u1 -f origin master dev foo + PUSH u1 -f origin master dev foo ok; gsh /d308cfb...f1e6821 master -> master .forced update./ /new branch.*dev -> dev/ @@ -195,18 +197,18 @@ try " # u5 push master !ok test-commit u5 - glt push u5 origin master; !ok; gsh + PUSH u5 origin master; !ok; gsh reject /DENIED by refs/heads/master/ # u5 rewind dev ok - glt push u5 origin +dev^:dev + PUSH u5 origin +dev^:dev ok; gsh /\\+ cecf671...5c8a89d dev\\^ -> dev .forced update./ # u5 rewind foo !ok - glt push u5 origin +foo^:foo + PUSH u5 origin +foo^:foo !ok; gsh reject /remote: FATAL: \\+ refs/heads/foo aa u5 DENIED by fallthru/ @@ -216,15 +218,15 @@ try " /Switched to branch 'foo'/ test-commit u5 - glt push u5 origin foo; ok; gsh + PUSH u5 origin foo; ok; gsh /cecf671..b27cf19 *foo -> foo/ # u1 delete dev ok - glt push u1 origin :dev; ok; gsh + PUSH u1 origin :dev; ok; gsh / - \\[deleted\\] *dev/ # push it back - glt push u1 origin dev; ok; gsh + PUSH u1 origin dev; ok; gsh /\\* \\[new branch\\] *dev -> dev/ "; @@ -242,15 +244,15 @@ try " cd ..; ok - glt clone tester file://r1; ok; gsh + CLONE tester file://r1; ok; gsh /Cloning into 'r1'.../ cd r1; ok test-commit r1a r1b r1c r1d r1e r1f ok - glt push tester origin HEAD;ok; gsh + PUSH tester origin HEAD; ok; gsh /\\* \\[new branch\\] *HEAD -> master/ git branch v1 - glt push tester origin v1; ok; gsh + PUSH tester origin v1; ok; gsh /\\* \\[new branch\\] *v1 -> v1/ "; @@ -269,14 +271,14 @@ try " cd ..; ok - glt clone tester file://r2; ok; gsh + CLONE tester file://r2; ok; gsh /Cloning into 'r2'.../ cd r2; ok test-commit r2a r2b r2c r2d r2e r2f ok - glt push tester origin HEAD;ok; gsh + PUSH tester origin HEAD; ok; gsh /\\* \\[new branch\\] *HEAD -> master/ git branch v1 - glt push tester origin v1; !ok; gsh + PUSH tester origin v1; !ok; gsh /W refs/heads/v1 r2 tester DENIED by refs/heads/v\\[0-9\\]/ " From ef021ee2934b8b0d1233a93c521934115f0653eb Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Mar 2012 16:24:00 +0530 Subject: [PATCH 311/637] (test) forgot to set user.email/name to the standard value caused old test scripts to fail (wherever I was checking the actual SHA anyway) --- src/Gitolite/Test.pm | 4 +++- t/basic.t | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm index 0e15181..f950fb3 100644 --- a/src/Gitolite/Test.pm +++ b/src/Gitolite/Test.pm @@ -37,7 +37,7 @@ try " DEF ADMIN_PUSH = AP_2 %1; glt push admin origin; ok; gsh; /master -> master/ DEF CS_1 = pwd; //tmp/tsh_tempdir.*gitolite-admin/; git remote -v; ok; /file://gitolite-admin/ - DEF CHECK_SETUP = CS_1; git log; ok; /65a1b2acd78dd9a7a401fe81c25380c1ca90067c/ + DEF CHECK_SETUP = CS_1; git log; ok; /6b18ec2ab0f765122ec133959b36c57f77d4565c/ DEF CLONE = glt clone DEF PUSH = glt push @@ -46,6 +46,8 @@ try " mkdir -p $ENV{HOME}/bin ln -sf $ENV{PWD}/src/gitolite $ENV{PWD}/t/glt ~/bin cd; rm -vrf .gito* gito* repositories + git config --global user.name \"gitolite tester\" + git config --global user.email \"tester\@example.com\" # setup gitolite setup -a admin diff --git a/t/basic.t b/t/basic.t index 8b1aa7c..fddc342 100755 --- a/t/basic.t +++ b/t/basic.t @@ -81,7 +81,7 @@ try " cd t1; ok; # push - test-commit tc1 tc2 tc2; ok; /f7153e3/ + test-commit tc1 tc2 tc2; ok; /a530e66/ PUSH u2 origin; !ok; gsh /DENIED by fallthru/ /fatal: The remote end hung up unexpectedly/ @@ -89,15 +89,15 @@ try " /master -> master/ # rewind - reset-h HEAD^; ok; /HEAD is now at 537f964 tc2/ - test-tick; test-commit tc3; ok; /a691552/ + reset-h HEAD^; ok; /HEAD is now at aa2b5c5 tc2/ + test-tick; test-commit tc3; ok; /3ffced1/ PUSH u3 origin; !ok; gsh /rejected.*master -> master.*non-fast-forward./ PUSH u3 -f origin; !ok; gsh reject /DENIED by fallthru/ PUSH u4 origin +master; ok; gsh - / \\+ f7153e3...a691552 master -> master.*forced update./ + / \\+ a530e66...3ffced1 master -> master.*forced update./ "; put "../gitolite-admin/conf/gitolite.conf", " @@ -146,7 +146,7 @@ try " test-commit r1; ok PUSH u1 origin +master; ok; gsh /To file://aa/ - /\\+ cecf671...70469f5 master -> master .forced update./ + /\\+ 27ed463...05adfb0 master -> master .forced update./ # u2 rewind master !ok reset-h HEAD^; ok @@ -160,13 +160,13 @@ try " test-commit r3; ok PUSH u3 origin +master; ok; gsh /To file://aa/ - /\\+ 70469f5...f1e6821 master -> master .forced update./ + /\\+ 05adfb0...6a532fe master -> master .forced update./ # u4 push master ok test-commit u4; ok PUSH u4 origin master; ok; gsh /To file://aa/ - /f1e6821..d308cfb +master -> master/ + /6a532fe..f929773 +master -> master/ # u4 rewind master !ok reset-h HEAD^; ok @@ -191,7 +191,7 @@ try " # clean up for next set PUSH u1 -f origin master dev foo ok; gsh - /d308cfb...f1e6821 master -> master .forced update./ + /f929773...6a532fe master -> master .forced update./ /new branch.*dev -> dev/ /new branch.*foo -> foo/ @@ -204,7 +204,7 @@ try " # u5 rewind dev ok PUSH u5 origin +dev^:dev ok; gsh - /\\+ cecf671...5c8a89d dev\\^ -> dev .forced update./ + /\\+ 27ed463...1ad477a dev\\^ -> dev .forced update./ # u5 rewind foo !ok @@ -219,7 +219,7 @@ try " test-commit u5 PUSH u5 origin foo; ok; gsh - /cecf671..b27cf19 *foo -> foo/ + /27ed463..83da62c *foo -> foo/ # u1 delete dev ok PUSH u1 origin :dev; ok; gsh From 16d17def2a9c4fc24fd8f5db1bf5c55e61759033 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Mar 2012 07:36:42 +0530 Subject: [PATCH 312/637] VREF code --- src/Gitolite/Conf/Load.pm | 50 ++++++++++++++++++++++++++---------- src/Gitolite/Conf/Store.pm | 4 +-- src/Gitolite/Conf/Sugar.pm | 18 ++++++++++++- src/Gitolite/Hooks/Update.pm | 41 +++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm index 171927e..625d7eb 100644 --- a/src/Gitolite/Conf/Load.pm +++ b/src/Gitolite/Conf/Load.pm @@ -6,6 +6,7 @@ package Gitolite::Conf::Load; @EXPORT = qw( load access + vrefs list_groups list_users @@ -139,26 +140,47 @@ sub load_1 { } } -sub rules { - my ( $repo, $user ) = @_; - trace( 4, "repo=$repo, user=$user" ); - my @rules = (); +{ + my $lastrepo = ''; + my $lastuser = ''; + my @cached = (); - my @repos = memberships($repo); - my @users = memberships($user); - trace( 4, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" ); + sub rules { + my ( $repo, $user ) = @_; + trace( 4, "repo=$repo, user=$user" ); - for my $r (@repos) { - for my $u (@users) { - push @rules, @{ $repos{$r}{$u} } if exists $repos{$r}{$u}; + return @cached if ($lastrepo eq $repo and $lastuser eq $user and @cached); + + my @rules = (); + + my @repos = memberships($repo); + my @users = memberships($user); + trace( 4, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" ); + + for my $r (@repos) { + for my $u (@users) { + push @rules, @{ $repos{$r}{$u} } if exists $repos{$r}{$u}; + } } + + @rules = sort { $a->[0] <=> $b->[0] } @rules; + + $lastrepo = $repo; + $lastuser = $user; + @cached = @rules; + + return @rules; } - # dbg("before sorting rules:", \@rules); - @rules = sort { $a->[0] <=> $b->[0] } @rules; - # dbg("after sorting rules:", \@rules); + sub vrefs { + my ( $repo, $user ) = @_; + # fill the cache if needed + rules($repo, $user) unless ($lastrepo eq $repo and $lastuser eq $user and @cached); - return @rules; + my %seen; + my @vrefs = grep { /^VREF\// and not $seen{$_}++ } map { $_->[2] } @cached; + return @vrefs; + } } sub memberships { diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm index 37824c7..23d918f 100644 --- a/src/Gitolite/Conf/Store.pm +++ b/src/Gitolite/Conf/Store.pm @@ -95,9 +95,9 @@ sub parse_refs { # if no ref is given, this PERM applies to all refs @refs = qw(refs/.*) unless @refs; - # fully qualify refs that dont start with "refs/" or "NAME/" or "VREF/"; + # fully qualify refs that dont start with "refs/" or "VREF/"; # prefix them with "refs/heads/" - @refs = map { m(^(refs|NAME|VREF)/) or s(^)(refs/heads/); $_ } @refs; + @refs = map { m(^(refs|VREF)/) or s(^)(refs/heads/); $_ } @refs; # XXX what do we do? @refs = map { s(/USER/)(/\$gl_user/); $_ } @refs; return @refs; diff --git a/src/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm index dbf0926..30dcfc0 100644 --- a/src/Gitolite/Conf/Sugar.pm +++ b/src/Gitolite/Conf/Sugar.pm @@ -65,7 +65,7 @@ sub sugar { $lines = option($lines); $lines = owner_desc($lines); - # $lines = name_vref($lines); + $lines = name_vref($lines); return $lines; } @@ -132,5 +132,21 @@ sub owner_desc { return \@ret; } +sub name_vref { + my $lines = shift; + my @ret; + + # NAME/foo = + # -> VREF/NAME/foo = + + for my $line (@$lines) { + if ( $line =~ /^(-|R\S+) \S.* = \S.*/ ) { + $line =~ s( NAME/)( VREF/NAME/)g; + } + push @ret, $line; + } + return \@ret; +} + 1; diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm index 488a3ec..dc94e97 100644 --- a/src/Gitolite/Hooks/Update.pm +++ b/src/Gitolite/Hooks/Update.pm @@ -28,9 +28,50 @@ sub update { trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref) -> $ret" ); _die $ret if $ret =~ /DENIED/; + check_vrefs($ref, $oldsha, $newsha, $oldtree, $newtree, $aa); + exit 0; } +sub check_vrefs { + my($ref, $oldsha, $newsha, $oldtree, $newtree, $aa) = @_; + my $name_seen = 0; + for my $vref ( vrefs($ENV{GL_REPO}, $ENV{GL_USER}) ) { + trace(1, "vref=$vref"); + if ($vref =~ m(^VREF/NAME/)) { + # this one is special; we process it right here, and only once + next if $name_seen++; + + for my $ref ( map { chomp; s(^)(VREF/NAME/); $_; } `git diff --name-only $oldtree $newtree` ) { + check_vref($aa, $ref); + } + } else { + my($dummy, $pgm, @args) = split '/', $vref; + $pgm = "$ENV{GL_BINDIR}/VREF/$pgm"; + -x $pgm or die "$vref: helper program missing or unexecutable\n"; + + open( my $fh, "-|", $pgm, @_, $vref, @args ) or die "$vref: can't spawn helper program: $!\n"; + while (<$fh>) { + my ( $ref, $deny_message ) = split( ' ', $_, 2 ); + check_vref($aa, $ref, $deny_message); + } + close($fh) or die $! + ? "Error closing sort pipe: $!" + : "$vref: helper program exit status $?"; + } + } +} + +sub check_vref { + my ($aa, $ref, $deny_message) = @_; + + my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref ); + trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" ); + _die "$ret" . ( $deny_message ? "\n$deny_message" : '' ) + if $ret =~ /DENIED/ and $ret !~ /by fallthru/; + trace( 1, "remember, fallthru is success here!") if $ret =~ /by fallthru/; +} + { my $text = ''; From d64663d12e0421e6a806baf7502076697205fa72 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Mar 2012 16:38:46 +0530 Subject: [PATCH 313/637] COUNT VREF and tests --- src/VREF/COUNT | 51 ++++++++++++++++++ src/VREF/FILETYPE | 45 ++++++++++++++++ t/vrefs-1.t | 135 ++++++++++++++++++++++++++++++++++++++++++++++ t/vrefs-2.t | 106 ++++++++++++++++++++++++++++++++++++ 4 files changed, 337 insertions(+) create mode 100755 src/VREF/COUNT create mode 100755 src/VREF/FILETYPE create mode 100755 t/vrefs-1.t create mode 100755 t/vrefs-2.t diff --git a/src/VREF/COUNT b/src/VREF/COUNT new file mode 100755 index 0000000..f61ab57 --- /dev/null +++ b/src/VREF/COUNT @@ -0,0 +1,51 @@ +#!/bin/bash + +# gitolite VREF to count number of changed/new files in a push + +# see gitolite docs for what the first 7 arguments mean + +# inputs: +# arg-8 is a number +# arg-9 is optional, and can be "NEWFILES" +# outputs (STDOUT) +# arg-7 if the number of changed (or new, if arg-9 supplied) files is > arg-8 +# otherwise nothing +# exit status: +# always 0 + +die() { echo "$@" >&2; exit 1; } +[ -z "$8" ] && die "not meant to be run manually" + +newsha=$3 +oldtree=$4 +newtree=$5 +refex=$7 + +max=$8 + +nf= +[ "$9" = "NEWFILES" ] && nf='--diff-filter=A' +# NO_SIGNOFF implies NEWFILES +[ "$9" = "NO_SIGNOFF" ] && nf='--diff-filter=A' + +# count files against all the other commits in the system not just $oldsha +# (why? consider what is $oldtree when you create a new branch, or what is +# $oldsha when you update an old feature branch from master and then push it +count=`git log --name-only $nf --format=%n $newtree --not --all | grep . | sort -u | wc -l` + +[[ $count -gt $max ]] && { + # count has been exceeded. If $9 was NO_SIGNOFF there's still a chance + # for redemption -- if the top commit has a proper signed-off by line + [ "$9" = "NO_SIGNOFF" ] && { + author_email=$(git log --format=%ae -1 $newsha) + git cat-file -p $newsha | + egrep -i >/dev/null "^ *$count +new +files +signed-off by: *$author_email *$" && exit 0 + echo $refex top commit message should include the text \'$count new files signed-off by: $author_email\' + exit 0 + } + echo -n $refex "(too many " + [ -n "$nf" ] && echo -n "new " || echo -n "changed " + echo "files in this push)" +} + +exit 0 diff --git a/src/VREF/FILETYPE b/src/VREF/FILETYPE new file mode 100755 index 0000000..e61acc6 --- /dev/null +++ b/src/VREF/FILETYPE @@ -0,0 +1,45 @@ +#!/bin/bash + +# gitolite VREF to find autogenerated files + +# *completely* site specific; use it as an illustration of what can be done +# with gitolite VREFs if you wish + +# see gitolite docs for what the first 7 arguments mean + +# inputs: +# arg-8 is currently only one possible value: AUTOGENERATED +# outputs (STDOUT) +# arg-7 if any files changed in the push look like they were autogenerated +# otherwise nothing +# exit status: +# always 0 + +die() { echo "$@" >&2; exit 1; } +[ -z "$8" ] && die "not meant to be run manually" + +newsha=$3 +oldtree=$4 +newtree=$5 +refex=$7 + +option=$8 + +[ "$option" = "AUTOGENERATED" ] && { + # currently we only look for ".java" programs with the string "Generated + # by the protocol buffer compiler. DO NOT EDIT" in them. + + git log --name-only $nf --format=%n $newtree --not --all | + grep . | + sort -u | + grep '\.java$' | + while read fn + do + git show "$newtree:$fn" | egrep >/dev/null \ + 'Generated by the protocol buffer compiler. +DO NOT EDIT' || + continue + + echo $refex + exit 0 + done +} diff --git a/t/vrefs-1.t b/t/vrefs-1.t new file mode 100755 index 0000000..4512ab8 --- /dev/null +++ b/t/vrefs-1.t @@ -0,0 +1,135 @@ +#!/usr/bin/perl +use strict; +use warnings; + +# this is hardcoded; change it if needed +use lib "src"; +use Gitolite::Test; + +try "plan 90"; + +put "conf/gitolite.conf", " + repo gitolite-admin + RW+ = admin + + \@gfoo = foo + \@lead = u1 + \@dev2 = u2 + \@dev4 = u4 + \@devs = \@dev2 \@dev4 u6 + repo \@gfoo + RW+ = \@lead \@devs + # intentional mis-spelling + - VREF/MISCOUNT/2 = \@dev2 + - VREF/MISCOUNT/4 = \@dev4 + - VREF/MISCOUNT/3/NEWFILES = u6 + - VREF/MISCOUNT/6 = u6 +"; + +try " + ADMIN_PUSH vr1a + cd .. + ls -al foo; !ok; /cannot access foo: No such file or directory/ + CLONE u1 file://foo; ok; /Cloning into/ + /You appear to have cloned an empty/ + cd foo; ok + ls -Al; ok; /\.git/ + + # VREF not called for u1 + tc a1 a2 a3 a4 a5; ok; /aaf9e8e/ + PUSH u1 origin master; ok; /new branch.*master -. master/ + !/helper program missing/ + !/hook declined/ + !/remote rejected/ + # VREF is called for u2 + tc b1; ok; /1f440d3/ + PUSH u2 origin; !ok; /helper program missing/ + /hook declined/ + /remote rejected/ +"; + +put "../gitolite-admin/conf/gitolite.conf", " + repo gitolite-admin + RW+ = admin + + \@gfoo = foo + \@lead = u1 + \@dev2 = u2 + \@dev4 = u4 + \@devs = \@dev2 \@dev4 u6 + repo \@gfoo + RW+ = \@lead \@devs + - VREF/COUNT/2 = \@dev2 + - VREF/COUNT/4 = \@dev4 + - VREF/COUNT/3/NEWFILES = u6 + - VREF/COUNT/6 = u6 +"; + +try " + ADMIN_PUSH vr1b + cd ../foo; ok + + # u2 1 file + PUSH u2 origin; ok; /aaf9e8e..1f440d3.*master -. master/ + + # u2 2 files + tc b2 b3; ok; /c3397f7/ + PUSH u2 origin; ok; /1f440d3..c3397f7.*master -. master/ + + # u2 3 files + tc c1 c2 c3; ok; /be242d7/ + PUSH u2 origin; !ok; /W VREF/COUNT/2 foo u2 DENIED by VREF/COUNT/2/ + /too many changed files in this push/ + /hook declined/ + /remote rejected/ + + # u4 3 files + PUSH u4 origin; ok; /c3397f7..be242d7.*master -. master/ + + # u4 4 files + tc d1 d2 d3 d4; ok; /88d80e2/ + PUSH u4 origin; ok; /be242d7..88d80e2.*master -. master/ + + # u4 5 files + tc d5 d6 d7 d8 d9; ok; /e9c60b0/ + PUSH u4 origin; !ok; /W VREF/COUNT/4 foo u4 DENIED by VREF/COUNT/4/ + /too many changed files in this push/ + /hook declined/ + /remote rejected/ + + # u1 all files + PUSH u1 origin; ok; /88d80e2..e9c60b0.*master -. master/ + + # u6 6 old files + test-tick + tc d1 d2 d3 d4 d5 d6 + ok; /2773f0a/ + PUSH u6 origin; ok; /e9c60b0..2773f0a.*master -. master/ + tag six + + # u6 updates 7 old files + test-tick; test-tick + tc d1 d2 d3 d4 d5 d6 d7 + ok; /d3fb574/ + PUSH u6 origin; !ok; /W VREF/COUNT/6 foo u6 DENIED by VREF/COUNT/6/ + /too many changed files in this push/ + /hook declined/ + /remote rejected/ + reset-h six; ok; /HEAD is now at 2773f0a/ + + # u6 4 new 2 old files + test-tick; test-tick + tc d1 d2 n1 n2 n3 n4 + ok; /9e90848/ + PUSH u6 origin; !ok; /W VREF/COUNT/3/NEWFILES foo u6 DENIED by VREF/COUNT/3/NEWFILES/ + /too many new files in this push/ + /hook declined/ + /remote rejected/ + reset-h six; ok; /HEAD is now at 2773f0a/ + + # u6 3 new 3 old files + test-tick; test-tick + tc d1 d2 d3 n1 n2 n3 + ok; /e47ff5d/ + PUSH u6 origin; ok; /2773f0a..e47ff5d.*master -. master/ +"; diff --git a/t/vrefs-2.t b/t/vrefs-2.t new file mode 100755 index 0000000..3ff1037 --- /dev/null +++ b/t/vrefs-2.t @@ -0,0 +1,106 @@ +#!/usr/bin/perl +use strict; +use warnings; + +# this is hardcoded; change it if needed +use lib "src"; +use Gitolite::Test; + +try "plan 74"; + +put "../gitolite-admin/conf/gitolite.conf", " + \@gfoo = foo + \@lead = u1 + \@senior_devs = u2 u3 + \@junior_devs = u4 u5 u6 + repo \@gfoo + + RW+ = \@all + + RW+ VREF/COUNT/2/NO_SIGNOFF = \@lead + - VREF/COUNT/2/NO_SIGNOFF = \@all + + - VREF/COUNT/10/NEWFILES = \@junior_devs + + - VREF/FILETYPE/AUTOGENERATED = \@all +"; + +try " + ADMIN_PUSH vr2a + cd .. + # setup + ls -al foo; !ok; /cannot access foo: No such file or directory/ + CLONE u1 file://foo; ok; /Cloning into/ + /You appear to have cloned an empty/ + cd foo; ok + ls -Al; ok; /\.git/ + + # u1 push 15 new files + tc a b c d e f g h i j k l m n o + ok; /d8c0392/ + PUSH u1 origin master; ok; /new branch.*master -. master/ + + # u2 push 2 new 10 old without signoff + tc a b c d e f g h i j u2a u2b + ok; /6787ac9/ + PUSH u2 origin; ok; /d8c0392..6787ac9.*master -. master/ + + # u2 fail to push 3 new files without signoff + tc u2c u2d u2e; ok; /a74562b/ + PUSH u2 origin; !ok; /W VREF/COUNT/2/NO_SIGNOFF foo u2 DENIED by VREF/COUNT/2/NO_SIGNOFF/ + /top commit message should include the text .3 new files signed-off by: tester.example.com./ + /hook declined/ + /remote rejected/ + # u2 push 15 new files with signoff + tc u2f u2g u2h u2i u2j u2k u2l u2m u2n u2o u2p u2q + ok; /8dd31aa/ + git commit --allow-empty -m '15 new files signed-off by: tester\@example.com' + ok; /.master 6126489. 15 new files signed-off by: tester.example.com/ + PUSH u2 origin; ok; /6787ac9..6126489.*master -. master/ + + # u4 push 2 new 10 old files without signoff + tc u4a u4b a b c d e f g h i j + ok; /76c5593/ + PUSH u4 origin; ok; /6126489..76c5593.*master -. master/ + + # u4 fail push 3 new files withoug signoff + tc u4c u4d u4e; ok; /2a84398/ + PUSH u4 origin; !ok; /W VREF/COUNT/2/NO_SIGNOFF foo u4 DENIED by VREF/COUNT/2/NO_SIGNOFF/ + /top commit message should include the text .3 new files signed-off by: tester.example.com./ + /hook declined/ + /remote rejected/ + + # u4 push 10 new 5 old with signoff + tc u4f u4g u4h u4i u4j u4k u4l a b c d e + ok; /09b646a/ + git commit --allow-empty -m '10 new files signed-off by: tester\@example.com' + ok; /.master 47f84b0. 10 new files signed-off by: tester.example.com/ + PUSH u4 origin; ok; /76c5593..47f84b0.*master -. master/ + + # u4 fail push 11 new files even with signoff + tc u4ab u4ac u4ad u4ae u4af u4ag u4ah u4ai u4aj u4ak u4al + ok; /90e7344/ + git commit --allow-empty -m '11 new files signed-off by: tester\@example.com' + ok; /.master 1f36537. 11 new files signed-off by: tester.example.com/ + PUSH u4 origin; !ok; /W VREF/COUNT/10/NEWFILES foo u4 DENIED by VREF/COUNT/10/NEWFILES/ + /too many new files in this push/ + /hook declined/ + /remote rejected/ + + # test AUTOGENERATED vref + glt fetch u1 origin; ok; + reset-h origin/master; ok; + tc not-really.java; ok; /0f88b2e/ + PUSH u4 origin; ok; /47f84b0..0f88b2e.*master -. master/ +"; + +put "|cat >> not-really.java", " + Generated by the protocol buffer compiler. DO NOT EDIT +"; + +try " + commit -am pbc; ok; /b2df6ef/ + PUSH u4 origin; !ok; /W VREF/FILETYPE/AUTOGENERATED foo u4 DENIED by VREF/FILETYPE/AUTOGENERATED/ + /hook declined/ + /remote rejected/ +"; From c19f75e119df6577473be97d70a71732969f71a9 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Mar 2012 18:30:06 +0530 Subject: [PATCH 314/637] (subconf) add the warning message (not as prominent as in g2 though...) --- src/Gitolite/Conf.pm | 1 + src/Gitolite/Conf/Store.pm | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/Gitolite/Conf.pm b/src/Gitolite/Conf.pm index 6c7dca6..6fcc0cf 100644 --- a/src/Gitolite/Conf.pm +++ b/src/Gitolite/Conf.pm @@ -73,6 +73,7 @@ sub parse { _warn "?? $line"; } } + parse_done(); } 1; diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm index 23d918f..c513669 100644 --- a/src/Gitolite/Conf/Store.pm +++ b/src/Gitolite/Conf/Store.pm @@ -15,6 +15,7 @@ package Gitolite::Conf::Store; new_repo hook_repos store + parse_done ); use Exporter 'import'; @@ -205,6 +206,13 @@ sub store { store_common(); } +sub parse_done { + for my $ig (sort keys %ignored) + { + _warn "$ig.conf attempting to set access for " . join (", ", sort keys %{ $ignored{$ig} }); + } +} + # ---------------------------------------------------------------------- sub check_subconf_repo_disallowed { From ef476f0d32d85529d0bf6a15af5de7a7c0db1f06 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Mar 2012 20:46:31 +0530 Subject: [PATCH 315/637] common: slurp() learns to look at wantarray --- src/Gitolite/Common.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm index d2f2cea..3ac2301 100644 --- a/src/Gitolite/Common.pm +++ b/src/Gitolite/Common.pm @@ -127,7 +127,8 @@ sub _print { } sub slurp { - local $/ = undef; + return unless defined wantarray; + local $/ = undef unless wantarray; my $fh = _open( "<", $_[0] ); return <$fh>; } From 428485086f676bb9a6948b799bb4b369e1ddec8d Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Mar 2012 21:45:07 +0530 Subject: [PATCH 316/637] query-rc learned '-n' to avoid the need to chomp() the result --- src/Gitolite/Rc.pm | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm index 7ed5cef..d716b6d 100644 --- a/src/Gitolite/Rc.pm +++ b/src/Gitolite/Rc.pm @@ -101,7 +101,10 @@ sub glrc { =for usage Usage: gitolite query-rc -a - gitolite query-rc + gitolite query-rc [-n] + + -a print all variables and values + -n do not append a newline Example: @@ -115,6 +118,7 @@ Example: # ---------------------------------------------------------------------- my $all = 0; +my $nonl = 0; sub query_rc { trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename'); @@ -123,14 +127,14 @@ sub query_rc { no strict 'refs'; - if ( $vars[0] eq '-a' ) { + if ( $all ) { for my $e (sort keys %rc) { print "$e=" . ( defined($rc{$e}) ? $rc{$e} : 'undef' ) . "\n"; } return; } - print join( "\t", map { $rc{$_} } @vars ) . "\n" if @vars; + print join( "\t", map { $rc{$_} || '' } @vars ) . ($nonl ? '' : "\n") if @vars; } # ---------------------------------------------------------------------- @@ -140,11 +144,11 @@ sub args { GetOptions( 'all|a' => \$all, + 'nonl|n' => \$nonl, 'help|h' => \$help, ) or usage(); usage("'-a' cannot be combined with other arguments") if $all and @ARGV; - return '-a' if $all; usage() if not $all and not @ARGV or $help; return @ARGV; } From 84422ccf3077930ce2294ccf9447b0a1b20b8e80 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 12 Mar 2012 08:38:50 +0530 Subject: [PATCH 317/637] (rc) prefix GL_BINDIR to PATH Needed when the user didn't actually "install" but is just running it by using the full path to "gitolite". Without this, every time my code runs "gitolite " I have to prefix "gitolite" with $ENV{GL_BINDIR}, which is kinda painful... --- src/Gitolite/Rc.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm index d716b6d..f526fde 100644 --- a/src/Gitolite/Rc.pm +++ b/src/Gitolite/Rc.pm @@ -52,6 +52,9 @@ _die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR); # let values specified in rc file override our internal ones @rc{ keys %RC } = values %RC; +# fix PATH (TODO: do it only if 'gitolite' isn't in PATH) +$ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}"; + # ---------------------------------------------------------------------- use strict; From cbd4d4368783a2990718658cf3f1b19d1abb3f20 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 12 Mar 2012 10:43:33 +0530 Subject: [PATCH 318/637] (minor) usage() sub can handle multiple usage sections in the same script --- src/Gitolite/Common.pm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm index 3ac2301..eb4b6f1 100644 --- a/src/Gitolite/Common.pm +++ b/src/Gitolite/Common.pm @@ -75,10 +75,12 @@ sub _die { } sub usage { - _warn(shift) if @_; + my ($warn, $section) = @_; + _warn($warn) if $warn; + $section ||= 'usage'; my $scriptname = ( caller() )[1]; my $script = slurp($scriptname); - $script =~ /^=for usage(.*?)^=cut/sm; + $script =~ /^=for $section(.*?)^=cut/sm; say2( $1 ? $1 : "...no usage message in $scriptname" ); exit 1; } From 0aeb0cd5e2e37387a7fff5e11ff3012eb5b65646 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 11 Mar 2012 21:33:15 +0530 Subject: [PATCH 319/637] ssh-authkeys done! --- src/Gitolite/Hooks/PostUpdate.pm | 22 +++++- src/Gitolite/Hooks/Update.pm | 4 +- src/Gitolite/Rc.pm | 19 +++-- src/Gitolite/Setup.pm | 1 + src/gitolite | 32 +++++++- src/post-compile/ssh-authkeys | 129 +++++++++++++++++++++++++++++++ t/reset | 4 + t/ssh-authkeys.t | 73 +++++++++++++++++ 8 files changed, 274 insertions(+), 10 deletions(-) create mode 100755 src/post-compile/ssh-authkeys create mode 100755 t/ssh-authkeys.t diff --git a/src/Gitolite/Hooks/PostUpdate.pm b/src/Gitolite/Hooks/PostUpdate.pm index ef8e522..ab94e23 100644 --- a/src/Gitolite/Hooks/PostUpdate.pm +++ b/src/Gitolite/Hooks/PostUpdate.pm @@ -19,7 +19,7 @@ use warnings; # ---------------------------------------------------------------------- sub post_update { - trace(3); + trace(3, @ARGV); # this is the *real* post_update hook for gitolite tsh_try("git ls-tree --name-only master"); @@ -31,6 +31,24 @@ sub post_update { } _system("$ENV{GL_BINDIR}/gitolite compile"); + # now run optional post-compile features + if (exists $rc{POST_COMPILE}) { + if (ref($rc{POST_COMPILE}) ne 'ARRAY') { + _warn "bad syntax for specifying post compile scripts; see docs"; + } else { + for my $s (@{ $rc{POST_COMPILE} }) { + + # perl-ism; apart from keeping the full path separate from the + # simple name, this also protects %rc from change by implicit + # aliasing, which would happen if you touched $s itself + my $sfp = "$ENV{GL_BINDIR}/post-compile/$s"; + + _warn("skipped post-compile script '$s'"), next if not -x $sfp; + _system($sfp, @ARGV); # they better all return with 0 exit codes! + } + } + } + exit 0; } @@ -64,5 +82,5 @@ use Gitolite::Hooks::PostUpdate; # gitolite post-update hook (only for the admin repo) # ---------------------------------------------------------------------- -post_update(@ARGV); # is not expected to return +post_update(); # is not expected to return exit 1; # so if it does, something is wrong diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm index dc94e97..cc13465 100644 --- a/src/Gitolite/Hooks/Update.pm +++ b/src/Gitolite/Hooks/Update.pm @@ -19,7 +19,7 @@ use warnings; # ---------------------------------------------------------------------- sub update { - trace( 3, @_ ); + trace( 3, @ARGV ); # this is the *real* update hook for gitolite my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV); @@ -150,5 +150,5 @@ use Gitolite::Hooks::Update; # gitolite update hook # ---------------------------------------------------------------------- -update(@ARGV); # is not expected to return +update(); # is not expected to return exit 1; # so if it does, something is wrong diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm index f526fde..be82ab2 100644 --- a/src/Gitolite/Rc.pm +++ b/src/Gitolite/Rc.pm @@ -165,19 +165,28 @@ __DATA__ # PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS -# this file is in perl syntax. However, you do NOT need to know perl to edit -# it; it should be fairly self-explanatory and easy to maintain. Just mind -# the commas, make sure the brackets and braces stay matched up! +# This file is in perl syntax. + +# However, you do NOT need to know perl to edit it; it should be fairly +# self-explanatory and easy to maintain. Just mind the commas (perl is quite +# happy to have an extra one at the end of the last item in any list, by the +# way!). And make sure the brackets and braces stay matched up! %RC = ( UMASK => 0077, GL_GITCONFIG_KEYS => "", - # uncomment as needed + # comment out or uncomment as needed SYNTACTIC_SUGAR => [ # 'continuation-lines', - ] + ], + + # comment out or uncomment as needed + POST_COMPILE => + [ + 'ssh-authkeys', + ], ); # ------------------------------------------------------------------------------ diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm index b42e0fc..d335147 100644 --- a/src/Gitolite/Setup.pm +++ b/src/Gitolite/Setup.pm @@ -47,6 +47,7 @@ sub setup { } _system("$ENV{GL_BINDIR}/gitolite compile"); + _system("$ENV{GL_BINDIR}/gitolite post-compile ssh-authkeys") if $pubkey; hook_repos(); # all of them, just to be sure } diff --git a/src/gitolite b/src/gitolite index 0457cc2..d8c82af 100755 --- a/src/gitolite +++ b/src/gitolite @@ -6,7 +6,8 @@ =for usage Usage: gitolite [sub-command] [options] -The following subcommands are available; they should all respond to '-h': +The following subcommands are available; they should all respond to '-h' if +you want further details on each: setup 1st run: initial setup; all runs: hook fixups compile compile gitolite.conf @@ -17,6 +18,7 @@ The following subcommands are available; they should all respond to '-h': list-phy-repos list all repos actually on disk list-memberships list all groups a name is a member of list-members list all members of a group + post-compile run a post-compile command Warnings: - list-users is disk bound and could take a while on sites with 1000s of repos @@ -89,7 +91,35 @@ sub args { require Gitolite::Conf::Load; Gitolite::Conf::Load->import; print "$_\n" for ( @{ list_members() } ); + } elsif ( $command eq 'post-compile' ) { + shift @ARGV; + post_compile(); } else { _die "unknown gitolite sub-command"; } } + +=for post-compile +Usage: gitolite post-compile [-l] [post-compile-scriptname] [script args...] + + -l list currently available post-compile scripts + +Run a post-compile script (which normally runs from the post-update hook in +the gitolite-admin repo). +=cut + +sub post_compile { + usage('', 'post-compile') if (@ARGV and $ARGV[0] eq '-h'); + + if (@ARGV and $ARGV[0] eq '-l') { + _chdir("$ENV{GL_BINDIR}/post-compile"); + map { say2($_) } grep { -x } glob("*"); + exit 0; + } + + my $pgm = shift @ARGV; + my $fullpath = "$ENV{GL_BINDIR}/post-compile/$pgm"; + _die "$pgm not found or not executable" if not -x $fullpath; + _system($fullpath, @ARGV); + exit 0; +} diff --git a/src/post-compile/ssh-authkeys b/src/post-compile/ssh-authkeys new file mode 100755 index 0000000..c45e388 --- /dev/null +++ b/src/post-compile/ssh-authkeys @@ -0,0 +1,129 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use File::Temp qw(tempfile); + +use lib $ENV{GL_BINDIR}; +use Gitolite::Rc; +use Gitolite::Common; + +$|++; + +# can be called directly, or as a post-update hook. Since it ignores +# arguments anyway, it hardly matters. + +my $ab = `gitolite query-rc -n GL_ADMIN_BASE`; +_warn("'keydir' not found in '$ab'"), exit if not -d "$ab/keydir"; +my $akfile = "$ENV{HOME}/.ssh/authorized_keys"; +my $glshell = `gitolite query-rc -n GL_BINDIR` . "/gitolite-shell"; +# XXX gl-time not yet coded (GL_PERFLOGT) +my $auth_options = auth_options(); + +sanity(); + +# ---------------------------------------------------------------------- + +_chdir($ab); + +# old data +my $old_ak = slurp($akfile); +my @non_gl = grep { not /^# gito.*start/ .. /^# gito.*end/ } slurp($akfile); +chomp(@non_gl); +my %seen = map { $_ => 'a non-gitolite key' } ( fp(@non_gl) ); +# die 1; + +# pubkey files +chomp( my @pubkeys = `find keydir -type f -name "*.pub" | sort` ); +my @gl_keys = (); +for my $f (@pubkeys) { + my $fp = fp($f); + if ( $seen{$fp} ) { + _warn "$f duplicates $seen{$fp}, sshd will ignore it"; + } else { + $seen{$fp} = $f; + } + push @gl_keys, grep { /./ } optionise($f); +} + +# dump it out +if (@gl_keys) { + my $out = join( "\n", map { my $_ = $_; chomp($_); $_ } @non_gl, "# gitolite start", @gl_keys, "# gitolite end" ) . "\n"; + + my $ak = slurp($akfile); + _die "$akfile changed between start and end of this program!" if $ak ne $old_ak; + _print( $akfile, $out ); +} + +# ---------------------------------------------------------------------- + +sub sanity { + _warn "$akfile missing; creating a new one" if not -f $akfile; + _die "$glshell not found; this should NOT happen..." if not -f $glshell; + _die "$glshell found but not readable; this should NOT happen..." if not -r $glshell; + _die "$glshell found but not executable; this should NOT happen..." if not -x $glshell; + + if ( not -f $akfile ) { + _print( $akfile, "" ); + chmod 0700, $akfile; + } +} + +sub auth_options { + my $auth_options = `gitolite query-rc AUTH_OPTIONS`; + chomp($auth_options); + $auth_options ||= "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; + + return $auth_options; +} + +sub fp { + # input: see below + # output: a (list of) FPs + my $in = shift || ''; + if ( $in =~ /\.pub$/ ) { + # single pubkey file + return fp_file($in); + } elsif ( -f $in ) { + # an authkeys file + return map { fp_line($_) } grep { !/^#/ and /\S/ } slurp($in); + } else { + # one or more actual keys + return map { fp_line($_) } grep { !/^#/ and /\S/ } ($in, @_); + } +} + +sub fp_file { + my $f = shift; + my $fp = `ssh-keygen -l -f $f`; + chomp($fp); + _die "fingerprinting failed for $f" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/; + $fp = $1; + return $fp; +} + +sub fp_line { + my ( $fh, $fn ) = tempfile(); + print $fh shift; + close $fh; + my $fp = fp_file($fn); + unlink $fn; + return $fp; +} + +sub optionise { + my $f = shift; + + my $user = $f; + $user =~ s(.*/)(); # foo/bar/baz.pub -> baz.pub + $user =~ s/(\@[^.]+)?\.pub$//; # baz.pub, baz@home.pub -> baz + + my @line = slurp($f); + if ( @line != 1 ) { + _warn "$f does not contain exactly 1 line; ignoring"; + return ''; + } + chomp(@line); + return "command=\"$glshell $user\",$auth_options $line[0]"; +} + diff --git a/t/reset b/t/reset index 8c5dcbf..cfdc3db 100755 --- a/t/reset +++ b/t/reset @@ -2,6 +2,10 @@ use strict; use warnings; +BEGIN { + unlink "$ENV{HOME}/.ssh/authorized_keys"; +} + # this is hardcoded; change it if needed use lib "src"; use Gitolite::Test; diff --git a/t/ssh-authkeys.t b/t/ssh-authkeys.t new file mode 100755 index 0000000..64179b4 --- /dev/null +++ b/t/ssh-authkeys.t @@ -0,0 +1,73 @@ +#!/usr/bin/perl +use strict; +use warnings; + +# this is hardcoded; change it if needed +use lib "src"; +use Gitolite::Test; + +$ENV{GL_BINDIR} = "$ENV{PWD}/src"; + +my $ak = "$ENV{HOME}/.ssh/authorized_keys"; +my $kd = `gitolite query-rc -n GL_ADMIN_BASE` . "/keydir"; + +try "plan 50"; + +my $pgm = "gitolite post-compile ssh-authkeys"; + +try " + # prep + rm -rf $ak; ok + + $pgm; ok; /'keydir' not found/ + mkdir $kd; ok + cd $kd; ok + $pgm; ok; /authorized_keys missing/ + /creating/ + wc < $ak; ok; /0 *0 *0/ + # some gl keys + ssh-keygen -N '' -q -f alice -C alice + ssh-keygen -N '' -q -f bob -C bob + ssh-keygen -N '' -q -f carol -C carol + ssh-keygen -N '' -q -f dave -C dave + ssh-keygen -N '' -q -f eve -C eve + rm alice bob carol dave eve + ls -a; ok; /alice.pub/; /bob.pub/; /carol.pub/; /dave.pub/; /eve.pub/ + $pgm; ok; + wc < $ak; ok; /^ *7 .*/; + grep gitolite $ak; ok; /start/ + /end/ + + # some normal keys + mv alice.pub $ak; ok + cat carol.pub >> $ak; ok + $pgm; ok; /carol.pub duplicates.*non-gitolite key/ + wc < $ak; ok; /^ *8 .*/; + + # moving normal keys up + mv dave.pub dave + $pgm; ok + cat dave >> $ak; ok + grep -n dave $ak; ok; /8:ssh-rsa/ + mv dave dave.pub + $pgm; ok; /carol.pub duplicates.*non-gitolite key/ + /dave.pub duplicates.*non-gitolite key/ + grep -n dave $ak; ok; /3:ssh-rsa/ + + # a bad key + ls -al > bad.pub + $pgm; !ok; /fingerprinting failed for keydir/bad.pub/ + wc < $ak; ok; /^ *9 .*/; + # a good key doesn't get added + ssh-keygen -N '' -q -f good + $pgm; !ok; /fingerprinting failed for keydir/bad.pub/ + wc < $ak; ok; /^ *9 .*/; + # till the bad key is removed + rm bad.pub + $pgm; ok; + wc < $ak; ok; /^ *10 .*/; + + # duplicate gl key + cp bob.pub robert.pub + $pgm; ok; /robert.pub duplicates.*bob.pub/ +"; From 7f8020adc5709690e74f0d07838022e43533c272 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 12 Mar 2012 20:54:30 +0530 Subject: [PATCH 320/637] 'info' command, plus lots more changes: - usage() gets a little smarter; it now knows what function it was called from and tries to find a '=for function_name' chunk of data in the script - the various list-* functions now work off a dispatcher in Load.pm - (...and they all use the new usage() magic to print their helps!) - src/gitolite got a lot leaner due to this dispatcher - src/gitolite-shell became a lot more easier to read/flow - rc acquired '{COMMANDS}', which gitolite-shell now refers to - comments in the default rc file changed a bit - rc got a new REMOTE_COMMAND_PATT (in place of ADC_CMD_ARGS_PATT) the rest is perltidy and stuff like that --- src/Gitolite/Common.pm | 33 ++++--- src/Gitolite/Conf.pm | 6 +- src/Gitolite/Conf/Explode.pm | 2 +- src/Gitolite/Conf/Load.pm | 86 +++++++++-------- src/Gitolite/Conf/Store.pm | 5 +- src/Gitolite/Conf/Sugar.pm | 12 +-- src/Gitolite/Hooks/PostUpdate.pm | 10 +- src/Gitolite/Hooks/Update.pm | 22 ++--- src/Gitolite/Rc.pm | 72 ++++++++------ src/Gitolite/Setup.pm | 5 +- src/Gitolite/Test.pm | 4 +- src/Gitolite/Test/Tsh.pm | 3 +- src/VREF/COUNT | 1 + src/VREF/FILETYPE | 1 + src/commands/info | 31 ++++++ src/gitolite | 129 +++++++++++++------------ src/gitolite-shell | 93 ++++++++++++++---- src/post-compile/ssh-authkeys | 2 +- src/syntactic-sugar/continuation-lines | 4 +- t/glt | 5 +- 20 files changed, 317 insertions(+), 209 deletions(-) create mode 100755 src/commands/info diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm index eb4b6f1..2260e41 100644 --- a/src/Gitolite/Common.pm +++ b/src/Gitolite/Common.pm @@ -40,10 +40,10 @@ sub trace { return unless defined( $ENV{D} ); my $level = shift; return if $ENV{D} < $level; - my $args = ''; $args = join( ", ", @_ ) if @_; - my $sub = ( caller 1 )[3] || ''; $sub =~ s/.*://; $sub .= ' ' x ( 32 - length($sub) ); - say2 "TRACE $level $sub", (@_ ? shift : ()); - say2("TRACE $level " . (" " x 32), $_)for @_; + my $args = ''; $args = join( ", ", @_ ) if @_; + my $sub = ( caller 1 )[3] || ''; $sub =~ s/.*://; $sub .= ' ' x ( 32 - length($sub) ); + say2 "TRACE $level $sub", ( @_ ? shift : () ); + say2( "TRACE $level " . ( " " x 32 ), $_ ) for @_; } sub dbg { @@ -75,13 +75,17 @@ sub _die { } sub usage { - my ($warn, $section) = @_; - _warn($warn) if $warn; - $section ||= 'usage'; - my $scriptname = ( caller() )[1]; - my $script = slurp($scriptname); - $script =~ /^=for $section(.*?)^=cut/sm; - say2( $1 ? $1 : "...no usage message in $scriptname" ); + _warn(shift) if @_; + my ( $script, $function ) = ( caller(1) )[ 1, 3 ]; + if (not $script) { + $script = ( caller ) [1]; + $function = 'usage'; + } + dbg( "u s a g e", $script, $function ); + $function =~ s/.*:://; + my $code = slurp($script); + $code =~ /^=for $function(.*?)^=cut/sm; + say2( $1 ? $1 : "...no usage message in $script" ); exit 1; } @@ -154,8 +158,8 @@ sub ln_sf { sub sort_u { my %uniq; my $listref = shift; - return [] unless @{ $listref }; - undef @uniq{ @{ $listref } }; # expect a listref + return [] unless @{$listref}; + undef @uniq{ @{$listref} }; # expect a listref my @sort_u = sort keys %uniq; return \@sort_u; } @@ -177,7 +181,6 @@ sub cleanup_conf_line { my @phy_repos = (); sub list_phy_repos { - _die "'gitolite list_phy_repos' takes no arguments" if @ARGV; trace(3); # use cached value only if it exists *and* no arg was received (i.e., @@ -189,7 +192,7 @@ sub cleanup_conf_line { $repo =~ s(\./(.*)\.git$)($1); push @phy_repos, $repo; } - return sort_u(\@phy_repos); + return sort_u( \@phy_repos ); } } diff --git a/src/Gitolite/Conf.pm b/src/Gitolite/Conf.pm index 6fcc0cf..a93aa10 100644 --- a/src/Gitolite/Conf.pm +++ b/src/Gitolite/Conf.pm @@ -24,12 +24,12 @@ use warnings; sub compile { trace(3); - # XXX assume we're in admin-base/conf + _die "'gitolite compile' does not take any arguments" if @_; _chdir( $rc{GL_ADMIN_BASE} ); _chdir("conf"); - parse(sugar('gitolite.conf')); + parse( sugar('gitolite.conf') ); # the order matters; new repos should be created first, to give store a # place to put the individual gl-conf files @@ -39,7 +39,7 @@ sub compile { sub parse { my $lines = shift; - trace(4, scalar(@$lines) . " lines incoming"); + trace( 4, scalar(@$lines) . " lines incoming" ); for my $line (@$lines) { # user or repo groups diff --git a/src/Gitolite/Conf/Explode.pm b/src/Gitolite/Conf/Explode.pm index a821dc9..f77e89d 100644 --- a/src/Gitolite/Conf/Explode.pm +++ b/src/Gitolite/Conf/Explode.pm @@ -28,7 +28,7 @@ sub explode { # seed the 'seen' list if it's empty $included{ device_inode("conf/gitolite.conf") }++ unless %included; - my $fh = _open( "<", $file ); + my $fh = _open( "<", $file ); while (<$fh>) { my $line = cleanup_conf_line($_); next unless $line =~ /\S/; diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm index 625d7eb..1759214 100644 --- a/src/Gitolite/Conf/Load.pm +++ b/src/Gitolite/Conf/Load.pm @@ -7,12 +7,7 @@ package Gitolite::Conf::Load; load access vrefs - - list_groups - list_users - list_repos - list_memberships - list_members + lister_dispatch ); use Exporter 'import'; @@ -25,8 +20,6 @@ use warnings; # ---------------------------------------------------------------------- -my $subconf = 'master'; - # our variables, because they get loaded by a 'do' our $data_version = ''; our %repos; @@ -36,6 +29,16 @@ our %configs; our %one_config; our %split_conf; +my $subconf = 'master'; + +my %listers = ( + 'list-groups' => \&list_groups, + 'list-users' => \&list_users, + 'list-repos' => \&list_repos, + 'list-memberships' => \&list_memberships, + 'list-members' => \&list_members, +); + # helps maintain the "cache" in both "load_common" and "load_1" my $last_repo = ''; @@ -118,7 +121,7 @@ sub load_1 { my $repo = shift; trace( 4, $repo ); - _chdir( "$rc{GL_REPO_BASE}/$repo.git" ); + _chdir("$rc{GL_REPO_BASE}/$repo.git"); if ( $repo eq $last_repo ) { $repos{$repo} = $one_repo{$repo}; @@ -143,13 +146,13 @@ sub load_1 { { my $lastrepo = ''; my $lastuser = ''; - my @cached = (); + my @cached = (); sub rules { my ( $repo, $user ) = @_; trace( 4, "repo=$repo, user=$user" ); - return @cached if ($lastrepo eq $repo and $lastuser eq $user and @cached); + return @cached if ( $lastrepo eq $repo and $lastuser eq $user and @cached ); my @rules = (); @@ -167,7 +170,7 @@ sub load_1 { $lastrepo = $repo; $lastuser = $user; - @cached = @rules; + @cached = @rules; return @rules; } @@ -175,7 +178,7 @@ sub load_1 { sub vrefs { my ( $repo, $user ) = @_; # fill the cache if needed - rules($repo, $user) unless ($lastrepo eq $repo and $lastuser eq $user and @cached); + rules( $repo, $user ) unless ( $lastrepo eq $repo and $lastuser eq $user and @cached ); my %seen; my @vrefs = grep { /^VREF\// and not $seen{$_}++ } map { $_->[2] } @cached; @@ -200,15 +203,22 @@ sub data_version_mismatch { # api functions # ---------------------------------------------------------------------- -# list all groups -sub list_groups { - die " +sub lister_dispatch { + my $command = shift; + + my $fn = $listers{$command} or _die "unknown gitolite sub-command"; + return $fn; +} + +=for list_groups Usage: gitolite list-groups - lists all group names in conf - no options, no flags +=cut -" if @ARGV; +sub list_groups { + usage() if @_; load_common(); @@ -219,18 +229,18 @@ Usage: gitolite list-groups return ( sort_u( \@g ) ); } -sub list_users { - my $count = 0; - my $total = 0; - - die " +=for list_users Usage: gitolite list-users - lists all users/user groups in conf - no options, no flags - WARNING: may be slow if you have thousands of repos +=cut -" if @ARGV; +sub list_users { + usage() if @_; + my $count = 0; + my $total = 0; load_common(); @@ -242,19 +252,19 @@ Usage: gitolite list-users $count++; print STDERR "$count / $total\r" if not( $count % 100 ) and timer(5); push @u, map { keys %{$_} } values %one_repo; } - print STDERR "\n"; + print STDERR "\n" if $count >= 100; return ( sort_u( \@u ) ); } -sub list_repos { - - die " +=for list_repos Usage: gitolite list-repos - lists all repos/repo groups in conf - no options, no flags +=cut -" if @ARGV; +sub list_repos { + usage() if @_; load_common(); @@ -264,34 +274,34 @@ Usage: gitolite list-repos return ( sort_u( \@r ) ); } -sub list_memberships { - - die " +=for list_memberships Usage: gitolite list-memberships - list all groups a name is a member of - takes one user/repo name +=cut -" if @ARGV and $ARGV[0] eq '-h' or not @ARGV and not @_; +sub list_memberships { + usage() if @_ and $_[0] eq '-h' or not @_; - my $name = ( @_ ? shift @_ : shift @ARGV ); + my $name = shift; load_common(); my @m = memberships($name); return ( sort_u( \@m ) ); } -sub list_members { - - die " +=for list_members Usage: gitolite list-members - list all members of a group - takes one group name +=cut -" if @ARGV and $ARGV[0] eq '-h' or not @ARGV and not @_; +sub list_members { + usage() if @_ and $_[0] eq '-h' or not @_; - my $name = ( @_ ? shift @_ : shift @ARGV ); + my $name = shift; load_common(); diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm index c513669..154b44e 100644 --- a/src/Gitolite/Conf/Store.pm +++ b/src/Gitolite/Conf/Store.pm @@ -207,9 +207,8 @@ sub store { } sub parse_done { - for my $ig (sort keys %ignored) - { - _warn "$ig.conf attempting to set access for " . join (", ", sort keys %{ $ignored{$ig} }); + for my $ig ( sort keys %ignored ) { + _warn "$ig.conf attempting to set access for " . join( ", ", sort keys %{ $ignored{$ig} } ); } } diff --git a/src/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm index 30dcfc0..caea1fb 100644 --- a/src/Gitolite/Conf/Sugar.pm +++ b/src/Gitolite/Conf/Sugar.pm @@ -3,7 +3,7 @@ package SugarBox; sub run_sugar_script { - my ($ss, $lref) = @_; + my ( $ss, $lref ) = @_; do $ss if -x $ss; $lref = sugar_script($lref); return $lref; @@ -35,7 +35,7 @@ sub sugar { # gets a filename, returns a listref my @lines = (); - explode(shift, 'master', \@lines); + explode( shift, 'master', \@lines ); my $lines; $lines = \@lines; @@ -43,11 +43,11 @@ sub sugar { # run through the sugar stack one by one # first, user supplied sugar: - if (exists $rc{SYNTACTIC_SUGAR}) { - if (ref($rc{SYNTACTIC_SUGAR}) ne 'ARRAY') { + if ( exists $rc{SYNTACTIC_SUGAR} ) { + if ( ref( $rc{SYNTACTIC_SUGAR} ) ne 'ARRAY' ) { _warn "bad syntax for specifying sugar scripts; see docs"; } else { - for my $s (@{ $rc{SYNTACTIC_SUGAR} }) { + for my $s ( @{ $rc{SYNTACTIC_SUGAR} } ) { # perl-ism; apart from keeping the full path separate from the # simple name, this also protects %rc from change by implicit @@ -55,7 +55,7 @@ sub sugar { my $sfp = "$ENV{GL_BINDIR}/syntactic-sugar/$s"; _warn("skipped sugar script '$s'"), next if not -x $sfp; - $lines = SugarBox::run_sugar_script($sfp, $lines); + $lines = SugarBox::run_sugar_script( $sfp, $lines ); $lines = [ grep /\S/, map { cleanup_conf_line($_) } @$lines ]; } } diff --git a/src/Gitolite/Hooks/PostUpdate.pm b/src/Gitolite/Hooks/PostUpdate.pm index ab94e23..efd4838 100644 --- a/src/Gitolite/Hooks/PostUpdate.pm +++ b/src/Gitolite/Hooks/PostUpdate.pm @@ -19,7 +19,7 @@ use warnings; # ---------------------------------------------------------------------- sub post_update { - trace(3, @ARGV); + trace( 3, @ARGV ); # this is the *real* post_update hook for gitolite tsh_try("git ls-tree --name-only master"); @@ -32,11 +32,11 @@ sub post_update { _system("$ENV{GL_BINDIR}/gitolite compile"); # now run optional post-compile features - if (exists $rc{POST_COMPILE}) { - if (ref($rc{POST_COMPILE}) ne 'ARRAY') { + if ( exists $rc{POST_COMPILE} ) { + if ( ref( $rc{POST_COMPILE} ) ne 'ARRAY' ) { _warn "bad syntax for specifying post compile scripts; see docs"; } else { - for my $s (@{ $rc{POST_COMPILE} }) { + for my $s ( @{ $rc{POST_COMPILE} } ) { # perl-ism; apart from keeping the full path separate from the # simple name, this also protects %rc from change by implicit @@ -44,7 +44,7 @@ sub post_update { my $sfp = "$ENV{GL_BINDIR}/post-compile/$s"; _warn("skipped post-compile script '$s'"), next if not -x $sfp; - _system($sfp, @ARGV); # they better all return with 0 exit codes! + _system( $sfp, @ARGV ); # they better all return with 0 exit codes! } } } diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm index cc13465..da089b5 100644 --- a/src/Gitolite/Hooks/Update.pm +++ b/src/Gitolite/Hooks/Update.pm @@ -28,32 +28,32 @@ sub update { trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref) -> $ret" ); _die $ret if $ret =~ /DENIED/; - check_vrefs($ref, $oldsha, $newsha, $oldtree, $newtree, $aa); + check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ); exit 0; } sub check_vrefs { - my($ref, $oldsha, $newsha, $oldtree, $newtree, $aa) = @_; + my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = @_; my $name_seen = 0; - for my $vref ( vrefs($ENV{GL_REPO}, $ENV{GL_USER}) ) { - trace(1, "vref=$vref"); - if ($vref =~ m(^VREF/NAME/)) { + for my $vref ( vrefs( $ENV{GL_REPO}, $ENV{GL_USER} ) ) { + trace( 1, "vref=$vref" ); + if ( $vref =~ m(^VREF/NAME/) ) { # this one is special; we process it right here, and only once next if $name_seen++; for my $ref ( map { chomp; s(^)(VREF/NAME/); $_; } `git diff --name-only $oldtree $newtree` ) { - check_vref($aa, $ref); + check_vref( $aa, $ref ); } } else { - my($dummy, $pgm, @args) = split '/', $vref; + my ( $dummy, $pgm, @args ) = split '/', $vref; $pgm = "$ENV{GL_BINDIR}/VREF/$pgm"; -x $pgm or die "$vref: helper program missing or unexecutable\n"; open( my $fh, "-|", $pgm, @_, $vref, @args ) or die "$vref: can't spawn helper program: $!\n"; while (<$fh>) { my ( $ref, $deny_message ) = split( ' ', $_, 2 ); - check_vref($aa, $ref, $deny_message); + check_vref( $aa, $ref, $deny_message ); } close($fh) or die $! ? "Error closing sort pipe: $!" @@ -63,13 +63,13 @@ sub check_vrefs { } sub check_vref { - my ($aa, $ref, $deny_message) = @_; + my ( $aa, $ref, $deny_message ) = @_; my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref ); trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" ); _die "$ret" . ( $deny_message ? "\n$deny_message" : '' ) - if $ret =~ /DENIED/ and $ret !~ /by fallthru/; - trace( 1, "remember, fallthru is success here!") if $ret =~ /by fallthru/; + if $ret =~ /DENIED/ and $ret !~ /by fallthru/; + trace( 1, "remember, fallthru is success here!" ) if $ret =~ /by fallthru/; } { diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm index be82ab2..2a51a55 100644 --- a/src/Gitolite/Rc.pm +++ b/src/Gitolite/Rc.pm @@ -8,7 +8,7 @@ package Gitolite::Rc; glrc query_rc - $ADC_CMD_ARGS_PATT + $REMOTE_COMMAND_PATT $REF_OR_FILENAME_PATT $REPONAME_PATT $REPOPATT_PATT @@ -36,7 +36,7 @@ $rc{GL_REPO_BASE} = "$ENV{HOME}/repositories"; # variables that should probably never be changed # ---------------------------------------------------------------------- -$ADC_CMD_ARGS_PATT = qr(^[0-9a-zA-Z._\@/+:-]*$); +$REMOTE_COMMAND_PATT = qr(^[- 0-9a-zA-Z\@\%_=+:,./]*$); $REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$); $REPONAME_PATT = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$); $REPOPATT_PATT = qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/,-]*$); @@ -101,26 +101,9 @@ sub glrc { # implements 'gitolite query-rc' # ---------------------------------------------------------------------- -=for usage - -Usage: gitolite query-rc -a - gitolite query-rc [-n] - - -a print all variables and values - -n do not append a newline - -Example: - - gitolite query-rc GL_ADMIN_BASE GL_UMASK - # prints "/home/git/.gitolite0077" or similar - - gitolite query-rc -a - # prints all known variables and values, one per line -=cut - # ---------------------------------------------------------------------- -my $all = 0; +my $all = 0; my $nonl = 0; sub query_rc { @@ -130,18 +113,38 @@ sub query_rc { no strict 'refs'; - if ( $all ) { - for my $e (sort keys %rc) { - print "$e=" . ( defined($rc{$e}) ? $rc{$e} : 'undef' ) . "\n"; + if ($all) { + for my $e ( sort keys %rc ) { + print "$e=" . ( defined( $rc{$e} ) ? $rc{$e} : 'undef' ) . "\n"; } - return; + exit 0; } - print join( "\t", map { $rc{$_} || '' } @vars ) . ($nonl ? '' : "\n") if @vars; + my @res = map { $rc{$_} } grep { $rc{$_} } @vars; + print join( "\t", @res ) . ( $nonl ? '' : "\n" ) if @res; + # shell truth + exit 0 if @res; + exit 1; } # ---------------------------------------------------------------------- +=for args +Usage: gitolite query-rc -a + gitolite query-rc [-n] + + -a print all variables and values + -n do not append a newline + +Example: + + gitolite query-rc GL_ADMIN_BASE UMASK + # prints "/home/git/.gitolite0077" or similar + + gitolite query-rc -a + # prints all known variables and values, one per line +=cut + sub args { my $help = 0; @@ -163,30 +166,35 @@ sub args { __DATA__ # configuration variables for gitolite -# PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS +# This file is in perl syntax. But you do NOT need to know perl to edit it -- +# just mind the commas and make sure the brackets and braces stay matched up! -# This file is in perl syntax. - -# However, you do NOT need to know perl to edit it; it should be fairly -# self-explanatory and easy to maintain. Just mind the commas (perl is quite -# happy to have an extra one at the end of the last item in any list, by the -# way!). And make sure the brackets and braces stay matched up! +# (Tip: perl allows a comma after the last item in a list also!) %RC = ( UMASK => 0077, GL_GITCONFIG_KEYS => "", # comment out or uncomment as needed + # these will run in sequence during the conf file parse SYNTACTIC_SUGAR => [ # 'continuation-lines', ], # comment out or uncomment as needed + # these will run in sequence after post-update POST_COMPILE => [ 'ssh-authkeys', ], + + # comment out or uncomment as needed + # these are available to remote users + COMMANDS => + { + 'info' => 1, + }, ); # ------------------------------------------------------------------------------ diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm index d335147..09930bd 100644 --- a/src/Gitolite/Setup.pm +++ b/src/Gitolite/Setup.pm @@ -3,14 +3,15 @@ package Gitolite::Setup; # implements 'gitolite setup' # ---------------------------------------------------------------------- -=for usage +=for args Usage: gitolite setup [] - -a, --admin admin user name -pk --pubkey pubkey file name -f, --fixup-hooks fixup hooks +Setup (first run only), then compile conf and fixup hooks. + First run: -a required -pk required for ssh mode install diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm index f950fb3..f7b4544 100644 --- a/src/Gitolite/Test.pm +++ b/src/Gitolite/Test.pm @@ -17,8 +17,8 @@ use Carp qw(carp cluck croak confess); BEGIN { require Gitolite::Test::Tsh; - *{'try'} = \&Tsh::try; - *{'put'} = \&Tsh::put; + *{'try'} = \&Tsh::try; + *{'put'} = \&Tsh::put; *{'text'} = \&Tsh::text; } diff --git a/src/Gitolite/Test/Tsh.pm b/src/Gitolite/Test/Tsh.pm index b4b3b41..41b4d12 100644 --- a/src/Gitolite/Test/Tsh.pm +++ b/src/Gitolite/Test/Tsh.pm @@ -259,8 +259,7 @@ sub rc_lines { $cmd = shift @cmds; # is the current command a "testing" command? - my $testing_cmd = - ( $cmd =~ m(^ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^!ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$) or $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$) ); + my $testing_cmd = ( $cmd =~ m(^ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^!ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$) or $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$) ); # warn if the previous command failed but rc is not being checked if ( $rc and not $testing_cmd ) { diff --git a/src/VREF/COUNT b/src/VREF/COUNT index f61ab57..d5c3982 100755 --- a/src/VREF/COUNT +++ b/src/VREF/COUNT @@ -1,4 +1,5 @@ #!/bin/bash +# TODO: convert to perl! # gitolite VREF to count number of changed/new files in a push diff --git a/src/VREF/FILETYPE b/src/VREF/FILETYPE index e61acc6..2115a5c 100755 --- a/src/VREF/FILETYPE +++ b/src/VREF/FILETYPE @@ -1,4 +1,5 @@ #!/bin/bash +# TODO: convert to perl! # gitolite VREF to find autogenerated files diff --git a/src/commands/info b/src/commands/info new file mode 100755 index 0000000..fe52837 --- /dev/null +++ b/src/commands/info @@ -0,0 +1,31 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Rc; +use Gitolite::Common; +use Gitolite::Conf::Load; + +=for usage +Usage: gitolite info + + - list all repos/repo groups you can access + - no options, no flags +=cut + +usage() if @ARGV; + +my $user = $ENV{GL_USER} or _die "GL_USER not set"; +my $ref = 'any'; + +my $fn = lister_dispatch('list-repos'); + +for ( @{ $fn->() } ) { + my $perm = ''; + for my $aa (qw(R W ^C)) { + my $ret = access($_, $user, $aa, $ref); + $perm .= ( $ret =~ /DENIED/ ? " " : " $aa" ); + } + print "$perm\t$_\n" if $perm =~ /\S/; +} diff --git a/src/gitolite b/src/gitolite index d8c82af..c265bd5 100755 --- a/src/gitolite +++ b/src/gitolite @@ -3,7 +3,7 @@ # all gitolite CLI tools run as sub-commands of this command # ---------------------------------------------------------------------- -=for usage +=for args Usage: gitolite [sub-command] [options] The following subcommands are available; they should all respond to '-h' if @@ -11,14 +11,16 @@ you want further details on each: setup 1st run: initial setup; all runs: hook fixups compile compile gitolite.conf + query-rc get values of rc variables + post-compile run a post-compile command + list-groups list all group names in conf list-users list all users/user groups in conf list-repos list all repos/repo groups in conf list-phy-repos list all repos actually on disk list-memberships list all groups a name is a member of list-members list all members of a group - post-compile run a post-compile command Warnings: - list-users is disk bound and could take a while on sites with 1000s of repos @@ -40,66 +42,56 @@ use warnings; # ---------------------------------------------------------------------- +my ( $command, @args ) = @ARGV; args(); +# the first two commands need options via @ARGV, as they have their own +# GetOptions calls and older perls don't have 'GetOptionsFromArray' + +if ( $command eq 'setup' ) { + shift @ARGV; + require Gitolite::Setup; + Gitolite::Setup->import; + setup(); + +} elsif ( $command eq 'query-rc' ) { + shift @ARGV; + query_rc(); # doesn't return + +# the rest don't need @ARGV per se + +} elsif ( $command eq 'compile' ) { + require Gitolite::Conf; + Gitolite::Conf->import; + compile(@args); + +} elsif ( $command eq 'post-compile' ) { + post_compile(@args); + +} elsif ( -x "$rc{GL_BINDIR}/commands/$command" ) { + run_command( $command, @args ); + +} elsif ( $command eq 'list-phy-repos' ) { + _chdir( $rc{GL_REPO_BASE} ); + print "$_\n" for ( @{ list_phy_repos(@args) } ); + +} elsif ( $command =~ /^list-/ ) { + require Gitolite::Conf::Load; + Gitolite::Conf::Load->import; + my $fn = lister_dispatch($command); + print "$_\n" for ( @{ $fn->(@args) } ); + +} else { + _die "unknown gitolite sub-command"; +} + +sub args { + usage() if not $command or $command eq '-h'; +} + # ---------------------------------------------------------------------- -sub args { - my ( $command, @args ) = @ARGV; - usage() if not $command or $command eq '-h'; - - if ( $command eq 'setup' ) { - shift @ARGV; - require Gitolite::Setup; - Gitolite::Setup->import; - setup(); - } elsif ( $command eq 'compile' ) { - shift @ARGV; - _die "'gitolite compile' does not take any arguments" if @ARGV; - require Gitolite::Conf; - Gitolite::Conf->import; - compile(); - } elsif ( $command eq 'query-rc' ) { - shift @ARGV; - query_rc(); - } elsif ( $command eq 'list-groups' ) { - shift @ARGV; - require Gitolite::Conf::Load; - Gitolite::Conf::Load->import; - print "$_\n" for ( @{ list_groups() } ); - } elsif ( $command eq 'list-users' ) { - shift @ARGV; - require Gitolite::Conf::Load; - Gitolite::Conf::Load->import; - print "$_\n" for ( @{ list_users() } ); - } elsif ( $command eq 'list-repos' ) { - shift @ARGV; - require Gitolite::Conf::Load; - Gitolite::Conf::Load->import; - print "$_\n" for ( @{ list_repos() } ); - } elsif ( $command eq 'list-phy-repos' ) { - shift @ARGV; - _chdir( $rc{GL_REPO_BASE} ); - print "$_\n" for ( @{ list_phy_repos() } ); - } elsif ( $command eq 'list-memberships' ) { - shift @ARGV; - require Gitolite::Conf::Load; - Gitolite::Conf::Load->import; - print "$_\n" for ( @{ list_memberships() } ); - } elsif ( $command eq 'list-members' ) { - shift @ARGV; - require Gitolite::Conf::Load; - Gitolite::Conf::Load->import; - print "$_\n" for ( @{ list_members() } ); - } elsif ( $command eq 'post-compile' ) { - shift @ARGV; - post_compile(); - } else { - _die "unknown gitolite sub-command"; - } -} - -=for post-compile +=for post_compile Usage: gitolite post-compile [-l] [post-compile-scriptname] [script args...] -l list currently available post-compile scripts @@ -109,17 +101,26 @@ the gitolite-admin repo). =cut sub post_compile { - usage('', 'post-compile') if (@ARGV and $ARGV[0] eq '-h'); + usage() if ( not @_ or $_[0] eq '-h' ); - if (@ARGV and $ARGV[0] eq '-l') { - _chdir("$ENV{GL_BINDIR}/post-compile"); + run_subdir('post-compile', @_); +} + +sub run_command { + run_subdir('commands', @_); +} + +sub run_subdir { + my $subdir = shift; + if ( @_ and $_[0] eq '-l' ) { + _chdir("$ENV{GL_BINDIR}/$subdir"); map { say2($_) } grep { -x } glob("*"); exit 0; } - my $pgm = shift @ARGV; - my $fullpath = "$ENV{GL_BINDIR}/post-compile/$pgm"; + my $pgm = shift; + my $fullpath = "$ENV{GL_BINDIR}/$subdir/$pgm"; _die "$pgm not found or not executable" if not -x $fullpath; - _system($fullpath, @ARGV); + _system( $fullpath, @_ ); exit 0; } diff --git a/src/gitolite-shell b/src/gitolite-shell index d7f6a19..c291bc9 100755 --- a/src/gitolite-shell +++ b/src/gitolite-shell @@ -13,38 +13,89 @@ use Gitolite::Conf::Load; use strict; use warnings; -print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n"; -print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n"; + +# the main() sub expects ssh-ish things; set them up... +if ( exists $ENV{G3T_USER} ) { + in_local(); # file:// masquerading as ssh:// for easy testing +} elsif ( exists $ENV{SSH_CONNECTION} ) { + in_ssh(); +} elsif ( exists $ENV{REQUEST_URI} ) { + in_http(); +} else { + _die "who the *heck* are you?"; +} + +main(); + +exit 0; # ---------------------------------------------------------------------- # XXX lots of stuff from gl-auth-command is missing for now... -# set up the user -my $user = $ENV{GL_USER} = shift; +sub in_local { + print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n"; + print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n"; +} -# set up the repo and the attempted access -my ( $verb, $repo ) = split_soc(); -sanity($repo); -$ENV{GL_REPO} = $repo; -my $aa = ( $verb =~ 'upload' ? 'R' : 'W' ); +sub in_http { + _die 'http not yet implemented...'; +} -# a ref of 'any' signifies that this is a pre-git check, where we don't -# yet know the ref that will be eventually pushed (and even that won't apply -# if it's a read operation). See the matching code in access() for more. -my $ret = access( $repo, $user, $aa, 'any' ); -trace( 1, "access($repo, $user, $aa, 'any') -> $ret" ); -_die $ret if $ret =~ /DENIED/; - -$repo = "'$rc{GL_REPO_BASE}/$repo.git'"; -exec( "git", "shell", "-c", "$verb $repo" ); +sub in_ssh { +} # ---------------------------------------------------------------------- -sub split_soc { +# call this once you are sure arg-1 is the username and SSH_ORIGINAL_COMMAND +# has been setup (even if it's not actually coming via ssh). +sub main { + # set up the user + my $user = $ENV{GL_USER} = shift @ARGV; + + # set up the repo and the attempted access + my ( $verb, $repo ) = parse_soc(); # returns only for git commands + sanity($repo); + $ENV{GL_REPO} = $repo; + my $aa = ( $verb =~ 'upload' ? 'R' : 'W' ); + + # a ref of 'any' signifies that this is a pre-git check, where we don't + # yet know the ref that will be eventually pushed (and even that won't + # apply if it's a read operation). See the matching code in access() for + # more information. + my $ret = access( $repo, $user, $aa, 'any' ); + trace( 1, "access($repo, $user, $aa, 'any') -> $ret" ); + _die $ret if $ret =~ /DENIED/; + + $repo = "'$rc{GL_REPO_BASE}/$repo.git'"; + exec( "git", "shell", "-c", "$verb $repo" ); +} + +# ---------------------------------------------------------------------- + +sub parse_soc { my $soc = $ENV{SSH_ORIGINAL_COMMAND}; - return ( $1, $2 ) if $soc =~ m(^(git-(?:upload|receive)-pack) '/?(.*?)(?:\.git)?'$); - _die "unknown command: $soc"; + $soc ||= 'info'; + + if ( $soc =~ m(^(git-(?:upload|receive)-pack) '/?(.*?)(?:\.git)?'$) ) { + # TODO git archive + my($verb, $repo) = ($1, $2); + _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT; + return ($verb, $repo); + } + + # after this we should not return; caller expects us to handle it all here + # and exit out + + _die "suspicious characters loitering about '$soc'" if $soc !~ $REMOTE_COMMAND_PATT; + + my @words = split ' ', $soc; + if ($rc{COMMANDS}{$words[0]}) { + _system("gitolite", @words); + exit 0; + } + + _die "unknown git/gitolite command: $soc"; } sub sanity { diff --git a/src/post-compile/ssh-authkeys b/src/post-compile/ssh-authkeys index c45e388..5e5ad4b 100755 --- a/src/post-compile/ssh-authkeys +++ b/src/post-compile/ssh-authkeys @@ -89,7 +89,7 @@ sub fp { return map { fp_line($_) } grep { !/^#/ and /\S/ } slurp($in); } else { # one or more actual keys - return map { fp_line($_) } grep { !/^#/ and /\S/ } ($in, @_); + return map { fp_line($_) } grep { !/^#/ and /\S/ } ( $in, @_ ); } } diff --git a/src/syntactic-sugar/continuation-lines b/src/syntactic-sugar/continuation-lines index 1d25379..3c28f20 100755 --- a/src/syntactic-sugar/continuation-lines +++ b/src/syntactic-sugar/continuation-lines @@ -18,10 +18,10 @@ sub sugar_script { my $lines = shift; - my @out = (); + my @out = (); my $keep = ''; for my $l (@$lines) { - if ($l =~ s/\\$//) { + if ( $l =~ s/\\$// ) { $keep .= $l; } else { $l = $keep . $l if $keep; diff --git a/t/glt b/t/glt index 45e7b19..09d4429 100755 --- a/t/glt +++ b/t/glt @@ -12,7 +12,10 @@ my $user = shift or die "need user"; my $rc; $ENV{G3T_USER} = $user; -if ( $cmd eq 'push' ) { +if ($cmd eq 'info' ) { + $ENV{SSH_ORIGINAL_COMMAND} = $cmd; + exec( "$ENV{GL_BINDIR}/../src/gitolite-shell", $user ); +} elsif ( $cmd eq 'push' ) { $rc = system( "git", $cmd, "--receive-pack=$ENV{GL_BINDIR}/gitolite-receive-pack", @ARGV ); } else { $rc = system( "git", $cmd, "--upload-pack=$ENV{GL_BINDIR}/gitolite-upload-pack", @ARGV ); From 356ff2b7572672f315f273116d60c556589331fd Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 14 Mar 2012 14:11:43 +0530 Subject: [PATCH 321/637] store got a few more validations (a full scan of all input data is pending; this is just for diagnostics) --- src/Gitolite/Conf/Store.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm index 154b44e..fef1746 100644 --- a/src/Gitolite/Conf/Store.pm +++ b/src/Gitolite/Conf/Store.pm @@ -49,6 +49,7 @@ my %ignored; sub add_to_group { my ( $lhs, @rhs ) = @_; _die "bad group '$lhs'" unless $lhs =~ $REPONAME_PATT; + map { _die "bad expansion '$_'" unless $_ =~ $REPOPATT_PATT } @rhs; # store the group association, but overload it to keep track of when # the group was *first* created by using $subconf as the *value* @@ -115,6 +116,8 @@ sub parse_users { sub add_rule { my ( $perm, $ref, $user ) = @_; + _die "bad ref '$ref'" unless $ref =~ $REPOPATT_PATT; + _die "bad user '$user'" unless $user =~ $USERNAME_PATT; $ruleseq++; for my $repo (@repolist) { @@ -140,6 +143,7 @@ sub add_rule { sub set_subconf { $subconf = shift; + _die "bad subconf '$subconf'" unless $subconf =~ /^[-\w.]+$/; trace( 1, $subconf ); } From 89cc3a303d1b71beb5075fed55d9ffae053b8954 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 14 Mar 2012 15:29:44 +0530 Subject: [PATCH 322/637] Test.pm learned confreset() and confadd() --- src/Gitolite/Test.pm | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm index f7b4544..e78fe88 100644 --- a/src/Gitolite/Test.pm +++ b/src/Gitolite/Test.pm @@ -9,12 +9,16 @@ package Gitolite::Test; put text dump + confreset + confadd ); #>>> use Exporter 'import'; use File::Path qw(mkpath); use Carp qw(carp cluck croak confess); +use Gitolite::Common; + BEGIN { require Gitolite::Test::Tsh; *{'try'} = \&Tsh::try; @@ -65,4 +69,25 @@ sub dump { } } +sub _confargs { + return @_ if ($_[1]); + return 'gitolite.conf', $_[0]; +} + +sub confreset { + system("rm", "-rf", "conf"); + mkdir("conf"); + put "conf/gitolite.conf", ' + repo gitolite-admin + RW+ = admin + repo testing + RW+ = @all +'; +} + +sub confadd { + my ($file, $string) = _confargs(@_); + put "|cat >> conf/$file", $string; +} + 1; From 141b2ce8970cab434e562b4017ca82beb7438bc5 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Wed, 14 Mar 2012 15:30:05 +0530 Subject: [PATCH 323/637] more tests --- src/Gitolite/Test.pm | 5 +++ t/include-subconf.t | 79 +++++++++++++++++++++++++++++++++ t/info.t | 52 ++++++++++++++++++++++ t/m-explode.t | 102 ------------------------------------------- 4 files changed, 136 insertions(+), 102 deletions(-) create mode 100755 t/include-subconf.t create mode 100755 t/info.t delete mode 100755 t/m-explode.t diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm index e78fe88..df4cc89 100644 --- a/src/Gitolite/Test.pm +++ b/src/Gitolite/Test.pm @@ -77,6 +77,11 @@ sub _confargs { sub confreset { system("rm", "-rf", "conf"); mkdir("conf"); + system("mv ~/repositories/gitolite-admin.git ~/repositories/.ga"); + system("mv ~/repositories/testing.git ~/repositories/.te"); + system("find ~/repositories -name '*.git' |xargs rm -rf"); + system("mv ~/repositories/.ga ~/repositories/gitolite-admin.git"); + system("mv ~/repositories/.te ~/repositories/testing.git "); put "conf/gitolite.conf", ' repo gitolite-admin RW+ = admin diff --git a/t/include-subconf.t b/t/include-subconf.t new file mode 100755 index 0000000..7bb5b04 --- /dev/null +++ b/t/include-subconf.t @@ -0,0 +1,79 @@ +#!/usr/bin/perl +use strict; +use warnings; + +# this is hardcoded; change it if needed +use lib "src"; +use Gitolite::Test; + +try 'plan 37'; + +confreset; confadd ' + include "i1.conf" + @i2 = b1 + subconf "i2.conf" + include "i1.conf" +'; +confadd 'i1.conf', ' + @g1 = a1 a2 + repo foo + RW = u1 + + include "j1.conf" +'; +confadd 'i2.conf', ' + @g2 = b1 b2 + repo bar b1 b2 i1 i2 @i1 @i2 @g2 + RW = u2 +'; +confadd 'j1.conf', ' + @h2 = c1 c2 + repo baz + RW = u3 +'; + +try "ADMIN_PUSH set2; !/FATAL/" or die text(); + +try " + /i1.conf already included/ + /i2.conf attempting to set access for \@i1, b2, bar, i1, locally modified \@g2/ + !/attempting to set access.*i2/ + /Initialized.*empty.*baz.git/ + /Initialized.*empty.*foo.git/ + /Initialized.*empty.*b1.git/ + /Initialized.*empty.*i2.git/ + !/Initialized.*empty.*b2.git/ + !/Initialized.*empty.*i1.git/ + !/Initialized.*empty.*bar.git/ +"; + +confreset;confadd ' + @g2 = i1 i2 i3 + subconf "g2.conf" +'; +confadd 'g2.conf', ' + @g2 = g2 h2 i2 + repo @g2 + RW = u1 +'; + +try "ADMIN_PUSH set3; !/FATAL/" or die text(); +try " + /g2.conf attempting to set access for locally modified \@g2/ + !/Initialized.*empty/ +"; + +confreset;confadd ' + @g2 = i1 i2 i3 + subconf "g2.conf" +'; +confadd 'g2.conf', ' + subconf master + @g2 = g2 h2 i2 + repo @g2 + RW = u1 +'; + +try " + ADMIN_PUSH set3; ok; /FATAL: subconf g2 attempting to run 'subconf'/ +"; diff --git a/t/info.t b/t/info.t new file mode 100755 index 0000000..967f4af --- /dev/null +++ b/t/info.t @@ -0,0 +1,52 @@ +#!/usr/bin/perl +use strict; +use warnings; + +# this is hardcoded; change it if needed +use lib "src"; +use Gitolite::Test; + +try 'plan 35'; + +try "## info"; + +confreset;confadd ' + repo t1 + RW = u1 + R = u2 + repo t2 + RW = u2 + R = u1 + repo t3 + RW = u3 + R = u4 +'; + +try "ADMIN_PUSH info; !/FATAL/" or die text(); +try " + /Initialized.*empty.*t1.git/ + /Initialized.*empty.*t2.git/ + /Initialized.*empty.*t3.git/ +"; +try " + glt info u1; ok; gsh + /R W *\tt1/ + /R *\tt2/ + !/t3/ + / R W *\ttesting/ + glt info u2; ok; gsh + /R *\tt1/ + /R W *\tt2/ + !/t3/ + / R W *\ttesting/ + glt info u3; ok; gsh + /R W *\tt3/ + !/t1/ + !/t2/ + / R W *\ttesting/ + glt info u4; ok; gsh + /R *\tt3/ + !/t1/ + !/t2/ + / R W *\ttesting/ + " or die; diff --git a/t/m-explode.t b/t/m-explode.t deleted file mode 100755 index aef337f..0000000 --- a/t/m-explode.t +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/perl -use strict; -use warnings; -use 5.10.0; - -use Test; -BEGIN { plan tests => - 2 -} - -use lib "$ENV{PWD}/src"; -use Gitolite::Test; -use Gitolite::Conf::Explode; - -my @out; -my @out2; - -warn " - <<< expect a couple of warnings about already included files >>> -"; - -# test 1 -- space normalisation - - put "foo", " - foo line 1 - foo=line 2 - - - foo 3 - "; - @out = (); - explode("foo", 'master', \@out); - @out2 = ( - 'foo line 1', - 'foo = line 2', - 'foo 3', - ); - - ok(@out ~~ @out2); - -# test 2 -- include/subconf processing - - put "foo", " - foo line 1 - \@fog=line 2 - include \"bar.conf\" - - foo line=5 - subconf \"subs/baz.conf\" - include \"bar.conf\" - foo line=7 - include \"bazup.conf\" - "; - - put "bar.conf", " - \@brg=line 1 - - bar line 3 - "; - - mkdir("subs"); - - put "subs/baz.conf", " - \@bzg = line 1 - - include \"subs/baz2.conf\" - - baz=line 3 - "; - - put "subs/baz2.conf", " - baz2 line 1 - baz2 line 2 - include \"bazup.conf\" - baz2 line 4 - "; - - put "bazup.conf", " - whatever... - "; - - @out = (); - explode("foo", 'master', \@out); - - @out2 = ( - 'foo line 1', - '@fog = line 2', - '@brg = line 1', - 'bar line 3', - 'foo line = 5', - 'subconf baz', - '@baz.bzg = line 1', - 'baz2 line 1', - 'baz2 line 2', - 'whatever...', - 'baz2 line 4', - 'baz = line 3', - 'subconf master', - 'foo line = 7' - ); - - ok(@out ~~ @out2); From 4e25a8acd130cc39c2d5eed777feb66b8bb1235f Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 15 Mar 2012 05:07:37 +0530 Subject: [PATCH 324/637] ssh-basic tests (and that's all we will ever do; see below) ssh tests are meant to ensure that basic authENTICATION is happening. AuthORISATION is checked all over the rest of the test suite and these two are quite orthogonal operations so there is no need to test all of authZ with ssh. --- t/keys/admin | 27 +++++++++++++++++++++++++ t/keys/admin.pub | 1 + t/keys/config | 20 +++++++++++++++++++ t/keys/u1 | 27 +++++++++++++++++++++++++ t/keys/u1.pub | 1 + t/keys/u2 | 27 +++++++++++++++++++++++++ t/keys/u2.pub | 1 + t/keys/u3 | 27 +++++++++++++++++++++++++ t/keys/u3.pub | 1 + t/keys/u4 | 27 +++++++++++++++++++++++++ t/keys/u4.pub | 1 + t/keys/u5 | 27 +++++++++++++++++++++++++ t/keys/u5.pub | 1 + t/keys/u6 | 27 +++++++++++++++++++++++++ t/keys/u6.pub | 1 + t/ssh-basic.t | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 16 files changed, 267 insertions(+) create mode 100644 t/keys/admin create mode 100644 t/keys/admin.pub create mode 100644 t/keys/config create mode 100644 t/keys/u1 create mode 100644 t/keys/u1.pub create mode 100644 t/keys/u2 create mode 100644 t/keys/u2.pub create mode 100644 t/keys/u3 create mode 100644 t/keys/u3.pub create mode 100644 t/keys/u4 create mode 100644 t/keys/u4.pub create mode 100644 t/keys/u5 create mode 100644 t/keys/u5.pub create mode 100644 t/keys/u6 create mode 100644 t/keys/u6.pub create mode 100755 t/ssh-basic.t diff --git a/t/keys/admin b/t/keys/admin new file mode 100644 index 0000000..676a711 --- /dev/null +++ b/t/keys/admin @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA0/X7uwd7xOvC3UTZaAnFOR5xqhdgcyc8vk3d1bXXthiuUSmq +5t4uhS9qj0ismcPX0YRNhSDElotG1KSWp8DceJpR2c7GYmELpqoE7ebVPnEBKY0c +PX+G9KZNgbJyyx35QlpJmlO+LimM0oO8XarRphn3kc0jCTLKaI5ndEjGDHH4fsOe +wlryW7a0RR/brMNiJYYzy5ADCOkAnyXQmbrQW4nROCPqFojBFTEBLpe8EuODlJXE +mRHpa1k+k/grFgp/c1xbOrjox127LZT4vkLf5+lSwkhq8oyzWHUrQ+rUJZssi1LC +GEZSudhX5reqksmIlTwX0GPIIwSwZFNVxhj51wIDAQABAoIBAFFLlzE0vZPZmPOk +5H2ywaIWuyGxtZx1ACc9VkgRZprA/JrEkHfb35vVg9lQ1mJjavNA+zqERuI2qQQF +3IKaxfS7u4j+dbhl4EIcE6frUP6R+RAmvx4XO3u6DSAhgUXGSUPZvUEjvV2XMhvL +ywNh8Ob0LrANLdLpWBiiBavj/ZHnsVZj7y+39GKEtLm8BbsH8L7GanPl4uQDNruR +Ef2L2pSEqml+Iva6yk5a22U293JCVZ/aqDdGvrnbjkaxqin5jBgL47D91A3xo4SW +zFZkJR5SDYnFLdwnwTPvxSA9IXj6TZ2ZdnTNvI5Utpd0Wy5N4GlfXS3TBPBD4kP+ ++pS1vgECgYEA/D7k1IyOM2hGamHc0qG+sIxMHge6rJE8pYtW2suB2KX0bJvAeBY8 +eHM+pPFMbjjZHlU7qB9qmGbZ++ZVlEv/SqqHizvwLzth0P4MJ6/UjAcmAdEUclV0 +YSzMRFk/g0J7aMlJb5NXgA5xlArHLsmV3Zc+QwFPECgzz5LHwD5nTMcCgYEA1x2Y +qb7FXwcQPDcs4Qwux12tCUaExQMZbNoLHWrXw7ktHRfR9sI8TcEErLzc2wnZbdyZ +av/GYSTR9UCeq12kUhmTL4SNryktHKNKEfDEvR7wK6sa9iffzZH8FDe5JktLXnu7 +Oe5SNfLDHA5X7qQefz8s2658+xVJmdp+uasNOnECgYEAlzBXVbJ9VQCuG/tWMQVz +VzxwLxuw3tgagprWz0NlK2ak7ygXn6KsUgG5TYG3ruTx9gVeQXG7IWecRiiTqNQ4 +SxeVMHYXiyfLhEmRHYR9IAT02efomnLv04LXWCwqLlF9yJvFIVQuAPonR3WCV1/K +LMwHLIAvVF7UVxkCEw8UOWcCgYEAwiUH/0sZvuYVFQOHEaV5Ip286b4nXdeqPr+b +gHVJPnAF81foO5iZ7GLj4TKi8V02SxzpqdQmKs6cX4huq6LcBuzmFeDALvIusMX+ +t6phJX6irAbFUpwyNMoog+a2x4T1BNUO6P3aXK44wT2AxvSAQb+2sJ4OVl2kC6NS +9CcYzUECgYEAzqbWlJIbjJx5620Q95bu+lpessOUtoVcYryO6dQNZOiis8aDZjJk +RKdB9qhJlcUeFqnvJS5L5gohaJoyx0wMSYVB7MrO6kAIMHo/OriEQ6CTsQOTqa8n +q4cV9Wuk9pRb1b/x5eXCcHe8P7wBt8KKIh/VBB4aDyeIwGbO0NODYr4= +-----END RSA PRIVATE KEY----- diff --git a/t/keys/admin.pub b/t/keys/admin.pub new file mode 100644 index 0000000..b50a5b9 --- /dev/null +++ b/t/keys/admin.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDT9fu7B3vE68LdRNloCcU5HnGqF2BzJzy+Td3Vtde2GK5RKarm3i6FL2qPSKyZw9fRhE2FIMSWi0bUpJanwNx4mlHZzsZiYQumqgTt5tU+cQEpjRw9f4b0pk2BsnLLHflCWkmaU74uKYzSg7xdqtGmGfeRzSMJMspojmd0SMYMcfh+w57CWvJbtrRFH9usw2IlhjPLkAMI6QCfJdCZutBbidE4I+oWiMEVMQEul7wS44OUlcSZEelrWT6T+CsWCn9zXFs6uOjHXbstlPi+Qt/n6VLCSGryjLNYdStD6tQlmyyLUsIYRlK52Ffmt6qSyYiVPBfQY8gjBLBkU1XGGPnX g3@sita-lt.atc.tcs.com diff --git a/t/keys/config b/t/keys/config new file mode 100644 index 0000000..fc75580 --- /dev/null +++ b/t/keys/config @@ -0,0 +1,20 @@ +host * + stricthostkeychecking no +host admin + identityfile ~/.ssh/admin + +host u? admin + user %USER + hostname localhost +host u1 + identityfile ~/.ssh/u1 +host u2 + identityfile ~/.ssh/u2 +host u3 + identityfile ~/.ssh/u3 +host u4 + identityfile ~/.ssh/u4 +host u5 + identityfile ~/.ssh/u5 +host u6 + identityfile ~/.ssh/u6 diff --git a/t/keys/u1 b/t/keys/u1 new file mode 100644 index 0000000..828d1c3 --- /dev/null +++ b/t/keys/u1 @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAuB8HmJ5UX30xFktmvlLgSrzsGIzSiYiAYH8eU6epJGr/xjYD +9GE6G9EcL+/NTc0ziPhIUtVm+h+kFEHtQ1VeOFEuVuWAukPNXRFDrYOMfeR4U1h7 +olT58U3+IlctreKavXr+7bPPhbhvdB7FPM8xusaPfwT6BG4sTqHyOPL5a3eXW7Sp +tuVD87KNHKo3skWcbb78EVQt7MfCMTam6X2O4Y0y1da51kOht6qRaeJHoaAjXB4V +pY9lJSQA+Km5uYu+FIwn89z7P4j2aUNZFq2UOSwuViekSR6lfHvortyh8YFrSRUs +Q4berZUICDp0Ek2BiJirWiPXAp9uxwXms9UbvQIDAQABAoIBAAUTnfMAUpU7b3IM +7C1NPa/x25SltVxjbh67AowN8GT3qku9y4geciq4Lk3ID+IYSVZ6egwGpEs7Ohvw +4Wjc3rcwzdVJiK4aFnx9cF9FZEdIWGT76JTGQQn9O4eY3cKQn/GfhY3qSkuGlVQf +URLnJ5jdxrEa4wXiP8h/QJ1/XY8v9en0uWf/18fMHyzImpd8dqgtTdomSQLjM2ie +1k9Fllh/5Q6e1Kd5mn5QP1QPC65Z78IskOot9Wrg0sk19sWiFMsopgJkQg66BlnM +Fj8A4HPS8Yylt0oXJ5yytUYfvZNDRANZRiAz9mfgQw9oapJLnCLt3Awi5bcKJIVk +/nTccEECgYEA8lSPFeTyzid0sIextXjjHvV4riSZKAHAa5M5DfsPkXLQfbEF4zog +FLZ6c85jUCk7/vTzDRXr38XKaGbAg/1M3UJk7lS9shh1Zdo0+Hshq7rNICNLKz5H +/u1/0gkxqqJcpXaoOHYN1hM7HJrP3uZFDIA5ZrB7JD20YDCQ/2PWrS0CgYEAwoHi +qEKKXOol7FIiG1upTjPaWMTmxtmSs2/pobMVf+ZGDMPts0pbqDegXruaNFUrUQOG +9yvrjA3a2QaKt7R5eDvHAafeMeu0WcHaqUWtIueTtEuduK4fXsCGONBPEGJu+Ct/ +BOCV/W8MAmzIOvffT7Jg1HWsZyAstoikPi1w4tECgYEAuvVmFxw1/7sNGgz2m+2S +PJZh7uipiOYhEF3bTN//mNWd6PskcbSsf45xVttKX9QQR5mv0s6w1koA6R8tNCe+ +n43T1NRoLfkUyenZqENHLPjHvR29prU8Un/ld6REP0NYewfarQTXk+vuVRlTesLp +TsW2g3Vw6/r3KKcPlxntzFkCgYEAqqkH1BY+DHQtPgJ6hoKQNFtuswBgdAymmOYS +mZvlu0iyIbUvNGaDsT7NaRE1pcEstnJf0zMoAsSNRmpk//ZLteDNJXjCjg5/OVnL +n0XROZTylfjatBWi1KIbonGzTW7warLPSdo8ABeU8/O6Y3Lk7qpWJ1PwJrOmR6nw +YdXA/GECgYEAsSokOABGGLQ2zOmDYPDKrRenLho8cSe4g/CF5b6x5MXfJvvBfI/M +hOS/2AFj7UkOSVM9B9QH607F67YJuBPNGvciCatqY/Bcs6ViuuyqG9U4O13OrrmA +oG8Cho3Zy2v3udaLJ6O5JYcpX+hdvkwoG5j/8hLrzkD/3AA12z6QnV0= +-----END RSA PRIVATE KEY----- diff --git a/t/keys/u1.pub b/t/keys/u1.pub new file mode 100644 index 0000000..264c1f0 --- /dev/null +++ b/t/keys/u1.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4HweYnlRffTEWS2a+UuBKvOwYjNKJiIBgfx5Tp6kkav/GNgP0YTob0Rwv781NzTOI+EhS1Wb6H6QUQe1DVV44US5W5YC6Q81dEUOtg4x95HhTWHuiVPnxTf4iVy2t4pq9ev7ts8+FuG90HsU8zzG6xo9/BPoEbixOofI48vlrd5dbtKm25UPzso0cqjeyRZxtvvwRVC3sx8IxNqbpfY7hjTLV1rnWQ6G3qpFp4kehoCNcHhWlj2UlJAD4qbm5i74UjCfz3Ps/iPZpQ1kWrZQ5LC5WJ6RJHqV8e+iu3KHxgWtJFSxDht6tlQgIOnQSTYGImKtaI9cCn27HBeaz1Ru9 g3@sita-lt.atc.tcs.com diff --git a/t/keys/u2 b/t/keys/u2 new file mode 100644 index 0000000..02486a6 --- /dev/null +++ b/t/keys/u2 @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEA4/t3WV4q98MlYV9Jbvbf7gE2Dzit6dD8xHsWBh2wTmggQM9I +2RsPp1tTIoOpt4YdlTzV41577BKLVMvqG7AxnVljC7+m7PrT2YmxEGrrbcrHebHu ++pwh0lkN/CIz6MHjWzLbFiRoHhKh9LyfSiKxCgJ3dyjFgAnA+wrXkwIfPRG8qOLk +sb1KUsMdfYm0OUEC/mHU2dsIS5HIY6xhy5Q5BXWgkfYUK7LL7Eyw6ffLhvq4U9tv +Qv+u64Qowew4BY4B8rvyb73iEGcInIF2JRY0jVC8yJFejTJo2KKlXxu8MyAtAhth +cHE6gA7xmn1d8TF7sxHDNqprvD9mLTjUVdz1owIDAQABAoIBAQDEGaWLZYioHV+l +5gSQQiJT4w7RAPv3RyBlEUrcb+UbTE2R8brDpJdOaSuVYJM3nVEM8Ys5TChj43+d +rNjugBvtMNoVXQEEjqxzThDUAmQHyIjUkMzzHCGrgZaZ7gGgkEY0SAZTgXVdiMFu +dmC9sCGAbqa8BIH9pGYuiiDr/sNIDr3ekyqyuSA0Aa2JIrEx1TKFXF5JtGseU9S6 +bUfesCpoWGyVdulr3NbsLM26eDCsZo45OF5QdpTtU99xc9K4gsOre0ZtqEJMVGlR +2nDQILCroH93GabSIW+fiUZD2lO9BxXAM0NA730ODQWyM8IgoWrqxGUuFz40l7X1 +teB7yRwBAoGBAPkkF//ZaEUUyCX6+a6TjaPQ1atemQCxHtIm1phfIM/u2uANqeOP +Z+N3dM0TL53lRx/xbT0dO4sfF73XcF0sYkQj9rep28Q04W8Z6iC/I+jQAJJ0bYl5 +skUGTECxIPLmKOciQF7N5PUGvxhI4oq/0vgDYFc+NW18mow1Z66H1PkjAoGBAOpC +P9EJXEFKZY1lK6ZL6wvJ6tJaPWAuiQlkOdj9wLvvzywQPlEdBJ1k0q+ndAqpR6WJ +eTlkP/bzDpLRi+l4PEVTsFlpxjcfVn19RRabsKTNIS0usl1el/uQP7u4rRimBCz5 +MO72GD4ARM5CgMZZMGU5AXmxKEM9feTcUNss3RmBAoGBALTYsmMRmVKr5y1KpPtI +OER1TuR6Ym3SJCE/9/3a76KAK3j/8hYw/qRrDenex23CBIL3aOg31AUEqOMxA2te +0GXOBUUEk3Y1PH69POpQVOymMAQfZ3OnVvQrwiYjbVtkHsTIZBltM4l5QDWMkoVN +AQLu0HwDuBylmjm0enKCPuIpAoGBAObd257bprwB4gtzhY0ijMbVfENLA+nictN6 +nzgm/Oc68+XtLD0sZ/vl/W13jnljU2TlEz9oeVGbQOWY9lZlVKDOVaIJCHwSul56 +MriRP4lrUCMDPm2eaBJYmzcaTh1YoAzimUMn7cRM54KPL/JKu9NGVxnjala6J3SB +XH5kvJIBAoGBAITZe45tIbVkGjK3jTjgBfaqFfhyws4L3QgRbjqpy2h9nLEGHMYV +RkIt6bCkw6cy+8TnYB0iG9lcGP1S5E4C6zpTdldi/5tGl7uu9qfEz4hxRpRbJ4Q0 +nwZzUkgBaLxAiLHfntBpwk16UvHkCpo5hiKzSLzxfReXzQPUUudGRnsG +-----END RSA PRIVATE KEY----- diff --git a/t/keys/u2.pub b/t/keys/u2.pub new file mode 100644 index 0000000..916dcf5 --- /dev/null +++ b/t/keys/u2.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDj+3dZXir3wyVhX0lu9t/uATYPOK3p0PzEexYGHbBOaCBAz0jZGw+nW1Mig6m3hh2VPNXjXnvsEotUy+obsDGdWWMLv6bs+tPZibEQauttysd5se76nCHSWQ38IjPoweNbMtsWJGgeEqH0vJ9KIrEKAnd3KMWACcD7CteTAh89Ebyo4uSxvUpSwx19ibQ5QQL+YdTZ2whLkchjrGHLlDkFdaCR9hQrssvsTLDp98uG+rhT229C/67rhCjB7DgFjgHyu/JvveIQZwicgXYlFjSNULzIkV6NMmjYoqVfG7wzIC0CG2FwcTqADvGafV3xMXuzEcM2qmu8P2YtONRV3PWj g3@sita-lt.atc.tcs.com diff --git a/t/keys/u3 b/t/keys/u3 new file mode 100644 index 0000000..163bdef --- /dev/null +++ b/t/keys/u3 @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEArK6uQkgoAnS+w6vs4DmvdOfcGdFUzh3ivHAXHug7sjjKGUB2 +KwcXNwcmJPtyNXM0BJ9ZoOJSqJo5cWLMl7Qn1HBubuyM02Id9EqeJu1Ytn/i1XFY +NMp8fj4wma3LZTdeOy3ockbefkw6VQUq3cheXIeaVpT91jsuyIaE0ejWkjGMu596 +zXFQZYTSO28TIYghRvhXGq5W9hJ3V2k+ZAQ/AsWQqjK4XruICi+RGRpKj59ECI7V +zIrq4MUEti+3LldaY6tyYsTGQLtFJYu+l/ZavkrM979cPOxLeoi8ZHPTPKh6asdr +ZXA2J6++IyIgNy2Q4sNxTmkIlU/EDysvTTGdMwIDAQABAoIBABF+NJr0UlFFYFnU +Hc/tKBAQuORIp22l62Upeb4gyoNYa2i5df8P3dMuPzf53Oz7OabKObspkjQQQ4dv ++cfYcTx9E0LbZby4MM6hjHnnC1iZhfIXZFccuBXV2PiIeZVMUZhvIyAIe9uRf0tD +lb8X4C9BcWoZ98ju/+NCdUwKaUov3jXc32hmsAhK+dtqBE+lwccX9keJQcncf+6j ++2znpDAJdaF70dM0DidbkmXmOTgH6LjjqrYRTAUnVsvUyDsr5YhopwOHs0E95YB4 +RzO7V/DA8H7DQ+XE20Iqp2i6dSbRQQPxbQLvJG4BaITIqb1L8/WJ6N75O/uns4rE +Y8WVwWECgYEA1gDDP70DqvVMu6KDvn18pvfxzxJ3jccYcBZITV/E6xgj1AbH7fdA +0iwvG6jQ26DTUBfLBquZ2fCCyI44OUpZ8GZu/wYBn5CQjYtr1vJ7zHKM3dVf+POT +cGgOVDIHopCCX5Dwb74OpbkTV4g/WxouClvz0Ovobq8NncSkJJkyGEMCgYEAzpIF +oj2AVMWDd6NL+P8e+QXPYEvJ/trAHoAteI83Eof4ZOC/sBr7Gf3c6nM7ZJbCXGhJ +NCCr4teHJMg9DThQ1KxKn9qEf9dWiAE+HCouaZU98Z0UcLQ+tgPeBh/Dw7rTF3M2 +7A/LtLYbg1MoPCZNj6V4qGjmNWCy2lku1ZGBUFECgYBD/4oKvqxjrf3rwP/Lj2QE +SdRzz5JdYl3Jf8sJityvNsRropv0aRQXtCJjz4hNwRRj5quEOxJvxZRI1afXzGA3 +mtS6A9aQNQc5couZiQL9O4i3FA2itQKsPOQQrLTwWqqSYyOC3gkZb21N6uT2taLb +d8xJHiyEvuq8rrbZSjQ4sQKBgEkUDZws58aVrYHYqlrnXny4mnm1tjtMBiWEMRHy +kIgkxDJj9EyH7wdt8QacR4m5b/8jAarIWCbDGtNfZ4HSx33FigztUGytsLYiwmdS +YOMHYkeky4NnsLvRuG0wNaB76ovkPazblbRTrH4UICrPXicQYhQqMC74C64FWPVD +KZ1RAoGAE/vKCHCzPegTT9gr2aaIrhLPUUZboOF4gHajYr9Scr176nJhpIvP/0Y2 +yQtqfas5lID8ouqXb+oL0Q4Yi00hdu+TRYQHm3M+2UL7wgffR5H2vfhk24UUDfV5 +0qQjNKp3pNUWZdiZ2J+RGUszXt0THfWbWI/ntwnG5QchqXCqYEA= +-----END RSA PRIVATE KEY----- diff --git a/t/keys/u3.pub b/t/keys/u3.pub new file mode 100644 index 0000000..e97645c --- /dev/null +++ b/t/keys/u3.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsrq5CSCgCdL7Dq+zgOa9059wZ0VTOHeK8cBce6DuyOMoZQHYrBxc3ByYk+3I1czQEn1mg4lKomjlxYsyXtCfUcG5u7IzTYh30Sp4m7Vi2f+LVcVg0ynx+PjCZrctlN147LehyRt5+TDpVBSrdyF5ch5pWlP3WOy7IhoTR6NaSMYy7n3rNcVBlhNI7bxMhiCFG+Fcarlb2EndXaT5kBD8CxZCqMrheu4gKL5EZGkqPn0QIjtXMiurgxQS2L7cuV1pjq3JixMZAu0Uli76X9lq+Ssz3v1w87Et6iLxkc9M8qHpqx2tlcDYnr74jIiA3LZDiw3FOaQiVT8QPKy9NMZ0z g3@sita-lt.atc.tcs.com diff --git a/t/keys/u4 b/t/keys/u4 new file mode 100644 index 0000000..a669e34 --- /dev/null +++ b/t/keys/u4 @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA2Wg3bl7T0C8VuR8HdbAqmwvQH4/T/maaqlQeJqcATRgWQNDv +VthEasW5Kx8DzcSVRWS0cJ5EpTLGvrs84aXgdvg6TwFZKO/ujrkFDZmw9hd4I/Dv +0dp/Y7himS8vAvnnWfYyqJBiX4plEx9Kg8oWHwy8KK3HHKoO8jAWAHOWO3GMB8BV +128VLovGB6Sp99GrgltzP9UhNRHt/doa3ve8+fDk782SandVTTR9MNLt7qhMSOKr +bmoFWtL36W5hmVjFGTZC1ZNIU6PiqygUqPtyAYblwv/nZ35s72KmxP7Seag3SsuZ +UPnRjM4PZ+7YE7tcPTOUxcct8xuTZXgjh2WiNwIDAQABAoIBADSrR8qIVJ452fRo +LQF49UlsmjYbPQuDxfJ/wHIywSLsM+/t7h3G9QQ89Hga4mwGNPeDxycFYLH41Cc+ +6yfrbK7FwjKDrBr7zXpsHmpGEpX755Ile6QGYBhDgjeEM8pvynmD6I/nsr1cpNH2 +IbI90hAhoK/mMbejB03rEll3pyytCgEvJQsu847dTNTZN+PHsumPr9HERi/dDrbw +JrDSH2tbOvYw4UW9HLFfZAB+IgMo34WXf6kcUY69wWQbxnv1e7KHnYkxIuLCGZ6x +Lc9APM97f4atlGt+mAtqK97CJB1AqoIE3KpPq1x+gpFSBNEE+9U9KnItuSqF3mG4 +vq/VjIkCgYEA+ASubOgzJaAldNz3dinLYK6MLB+V1R+RpkDc3+OgfF5mgg11ZtpE +/DO1Ndpf5dOYB4JYGfhC9+xomHGsQmbOsIUprWaAR/xaO6Bgxw7cxkOBIadsrdwu +MWJ6h3gN72zuhKPERpUVVH7ZH5KQfZwxEMgquUkGdEwumU4rMXeCFqsCgYEA4GdX +qI/5UNXEPnXk3dDWNzNJO7gRoonU228IM18qz5ZKMRIKp6Sq+wIuO8+aQMr2K0rO +RMQk+lLOZ17omaWh9ysDPzTCWRrjOiDOnYWDPCclTeqSnEk8uuWyppDGNXgQ6osM +LnwJSeP8+1EDgkKf5zoiQfcyBc0v8PSRSdTfEqUCgYEA2PqHeqHd9UHY4xdZq1e/ +JLMv0H5Ff/GhY7iFQ54JziRsO8T4e+Xiyl2WYCnPEer+qzseRoIKXInHq+5uzJzS +oF2va5MsEU41xsp1QFDBVvbBpyapDqV9CBlmptOiJV/Af+wiD7nnskdTPqrjm/Ck +gFEOB5Fagy4O6nIXmaw69AcCgYEAhVSBndKlZKUOa7oqmKzLipK7UXNFbxiL0zE+ +Yx+JVTvLqyo4EHFjca5TABCSayrsZr6UngEYo27t2jdm5lumRzBURoq3aq/yEIiL +msZIOkZcANZ988QEBFwT8KmWSxCipGinfTsPXcrLdhslhZDGZ2GAF0ejfhTzBiyZ +4o9LV00CgYBf46Z2M1fWI6Tc8HUKr9WoyUmTmUtHUFPt9IZ2CKm2czoltpku7cC7 +ztlt4LmVPKv1UUavbC/nCz6s1ylOkY0rdm45FSYXKDYPMQqzQix1jG4GZJjM9En+ +M848LupnHBFsmHyQqyyRlsg1gb+wtvAw7Y44wcfAhGbiAT5DVX+zxg== +-----END RSA PRIVATE KEY----- diff --git a/t/keys/u4.pub b/t/keys/u4.pub new file mode 100644 index 0000000..06f3648 --- /dev/null +++ b/t/keys/u4.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZaDduXtPQLxW5Hwd1sCqbC9Afj9P+ZpqqVB4mpwBNGBZA0O9W2ERqxbkrHwPNxJVFZLRwnkSlMsa+uzzhpeB2+DpPAVko7+6OuQUNmbD2F3gj8O/R2n9juGKZLy8C+edZ9jKokGJfimUTH0qDyhYfDLworcccqg7yMBYAc5Y7cYwHwFXXbxUui8YHpKn30auCW3M/1SE1Ee392hre97z58OTvzZJqd1VNNH0w0u3uqExI4qtuagVa0vfpbmGZWMUZNkLVk0hTo+KrKBSo+3IBhuXC/+dnfmzvYqbE/tJ5qDdKy5lQ+dGMzg9n7tgTu1w9M5TFxy3zG5NleCOHZaI3 g3@sita-lt.atc.tcs.com diff --git a/t/keys/u5 b/t/keys/u5 new file mode 100644 index 0000000..ed65131 --- /dev/null +++ b/t/keys/u5 @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA41A0bY6+0akNSJlR2PeRATNtncARXVOUar7CNaxwPqVXQR1+ +TmU9evmIEkRLf0kFAa7L3QDFroFu4sDiSJjvfYIkHdoxO4Fk128PJBhObIXaarKc +UBIZ29/8I3dTedq5CY/YHL/AjaT+0VktWX7YwigvGDamrXAKqjnW1mWKp6TNj8bp +vppBvj3yvdYoOLYvCs/SKfKdlayngiihTbKELPcRu8NzYxHiF2b/u0t31evdP0fA +/apAslKhHdCm8ScZJuyIdztcogBFE5dGm0h3qlbJKvKT8JmTcgQ5TBBcmqPVmUkm +nPvIzd2G7D1smbExG7LnqekHcS9dvaPnF0B3oQIDAQABAoIBAGl1e21crXDN0mjd +INjdOnvpJTDru+KldRT0/VszbjvSL6H5EfFDDPvxqsx2vOQHt3fpZZFZ21yzlgND +Y3g0499BsonbAb5OsL82OjsPv8qfaw7XYKfRTgfxaaP2p1bAP9qMzsG/wJC2fLYZ +fm2n6N5jED5WlIugkIIbJW4AXAycDB2ZU0xtPTHJ8nrSj1otDCsD2VbbVHXN7H6g +HrTqP4RqD+uVDIxSrXllz4Udwe/I1wUrvY9HjH2qS6Liqb5kw8SOf4a2et18+KB2 +NNk6ZEAlmOx1ddC2eZPxm8XxAxdSwTggcwvtixCeoXR53OwTZAZ8BBzwusrMklZm +/n5zPWUCgYEA88Lf4i0WFu9h4FnQ6qxCAKDS1XyRpCYt5cyTpZPMV3LuUV0DPS3K ++EZeU689BSOiwav8omCtrrrOHtE9fHjOH85Gd4IRyOwUQWI7RLpFiI8Lbs4tMbP5 +k1UOzzTji++N47XfTTQVdwHbx4jac80LVavbRd3AKd6oRjc/gQOpgasCgYEA7rnr +uoWqT76xVNLmE+g93x33hzES2HitgjHzvm5B2Cu6fwlXi7cSnLEWVn7W5QXEuhek +6uhq4NC0ahL/qAyJsFhVve32qrR7yacDVlMWZ5iQPfqtvS9CsxyafADBWsgeN4L1 +5oqQofNlh0l+UvMFp8INNbKfxTOPKCMfGxIEd+MCgYEA7UsJky380PrbtwD4JVrn +LaFhXL3VMYyRJaFPIeKNC5wwbzgyjP3lFme6L5Dpv/T+3bZFSvT+XpgvS0S5rFAV +qFSvuGsAUS2wUi4EMFV8lwFZSdafnEDtdgVZU1DTKkhbQg6sgIVxV9aRUt7gedZj +cFTKMms6RAgim6fww/ECs90CgYBdI1pt9jJhVHPZNUMgpy5ke0uUijfhDwwazKRd +OqUj0sO7RojKcM2pJoohivEKf3qmZA0qvSzds2+AJxNpnCKoE364UDw5k5rsLOXn +axlFp8c29zOLqQGr4c//60eExKjNXaHUpWESXmTRKIJJmJkvP01qEtu0043ZygIb +zKbDowKBgHhacDR3aat3kkaTp/4AhekhDhMgZ1u2NIi0ycsA6zRlmFucHMwtKCM0 +VMuu40D9N1lTmcdndNKkTJx6CpbS7g/y0ctkyGwgFdmTjjHYl1nATt9G2oRzKx6I +nQ+sBUwBPieIqEmiFh+KAsDox6plrtvhSSwp8FYLWBJXZHlLJIsd +-----END RSA PRIVATE KEY----- diff --git a/t/keys/u5.pub b/t/keys/u5.pub new file mode 100644 index 0000000..96a0045 --- /dev/null +++ b/t/keys/u5.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDjUDRtjr7RqQ1ImVHY95EBM22dwBFdU5RqvsI1rHA+pVdBHX5OZT16+YgSREt/SQUBrsvdAMWugW7iwOJImO99giQd2jE7gWTXbw8kGE5shdpqspxQEhnb3/wjd1N52rkJj9gcv8CNpP7RWS1ZftjCKC8YNqatcAqqOdbWZYqnpM2Pxum+mkG+PfK91ig4ti8Kz9Ip8p2VrKeCKKFNsoQs9xG7w3NjEeIXZv+7S3fV690/R8D9qkCyUqEd0KbxJxkm7Ih3O1yiAEUTl0abSHeqVskq8pPwmZNyBDlMEFyao9WZSSac+8jN3YbsPWyZsTEbsuep6QdxL129o+cXQHeh g3@sita-lt.atc.tcs.com diff --git a/t/keys/u6 b/t/keys/u6 new file mode 100644 index 0000000..86deee7 --- /dev/null +++ b/t/keys/u6 @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxyRjRT8RoSDnAnbZdrTjXhBMrkfNfolWFqc3qAjAo8Tmp7Ns +n7R+KCl31RDkC9u4ll4AOfF9bIdP9ovDVvXoTMoiOdPTYqsnaMBBpDH04vxID2U5 +rEOhSzbvTazzaSlYNFED2a9bMfULuF6WxchFPBQGhTZaC8cDAkS5YYXvjHXAn1GN +kpv0k5qltD8vjJoM7Mg5JLm84WejPQ5CmwOXqsQj69ZUZEyA8J3oqWVYkEhpYkbK +IuzaHkrTC0PUQJ8MS3wM8358bWp53zYFJMkyL1a/zJQPOMaDiFTXXj1Uue/5ZkIM +/fkkOMLbaCgEFCMbL8HkVEq4Q0dLhJKl1IdzbQIDAQABAoIBACQd91shuyLMAtmx +kHM1D1+J+T5Ki3x9j/1/ylpRbA7HsUWNBxBX/eFu0+ryq0lzSiELX2Mi5yp9yATh +CEaHRuBWcKqoPlhQzk7zP3R2EwHv22nfY/xYL7KiffhKe8MA2pxybQ5X/WQsGzoO +/a1VSylAQIZ8ewxTxbntmOmVDwMcLZdY5X4NAmzUw1lFD2TVOlHyhjvhgIzhO1tp +fBvsX3OL+Pk0tTQebuhoqEy2R01BLc+0ZFAKAFZpZgualqZqjyIU0isQsEo+EK4s +Pn7Ccxy+156aWAEdgD5Aj7oMn3zA9YIt/JoX8muaceUOj3E7B5ZKBtYZkFPVnKO9 +rPMNTIkCgYEA/v5IeFDZjcAMLWqhuPekcUQYMXc+I0ZTn7vT3aksWxhARG96CPdG +HNLMw4y8yZLnCAsdKUJGtrVcn4y4K0FzUYn0tDm9cmtFoFcQvLPp4mR1kB84urIG +7qEwv4djeLhl0cYsqMpyl7qflbTVwKa09Fy7HX9ZFL58nXVX63uRXlMCgYEAx+2o +L/GKkKgRPpCudsGoplECjzqP4SWHXaYVOupVzpnvxFKVcpBl6Un6p6U9V6W16k2C +XUg9XqFdleawfAwGQ2D/Ip/u4r8GKOphv0QCjO88ld15Ky9mr9Sk4/Z2EAMUrmKg +CS4hGBF7VIeA3FnzJwLkYL0+WQKpKBt/zojQLz8CgYEAnmtEgttYDeTeq+iviMbx +9xyjGzhF9oxer8J1oiTUVdP/OYU4gBGAEbA1Xtg1AdauiiS9fUCbxi9u2AEI+naz +OllHGiE1Pby/iRoOX+42xFw9XcjH6dVo0SB7tMJcXkfRmj5QyJzeDL35H301v3bS +vW5PIchYg7bEnN6mPLqMWdkCgYBVWaX1Yb5v5vAFr6prVF11MxxOnQeTbHwPhLmH +f0bGfn0XaNIYKID5SPXS3/4CDuJMdm5y+EYKwgS729H4AwIhfaUt2O0Yq8gra3Pz +PUuBcxiAOh5iS0ghRDxofW0FhOstTzlW8fR62+u0uGxQpa3iN5/blK6rPTGNx7+W +Il4N7QKBgHRTxUZraVt3n0kJ4+tdc+F8XK1wLyVC9PmtW+28tiyx14k8o8H/6W94 +r3lyI13sUnvW6zU0VMAALQujRcJDLac/zSUdFGmgU3P3QatU5yUm0Xpt1uFM49Zw +xZLBTDJxe2Qrc3Z3SKReRqxLz6tTU1soH7TVw/S36tBovKjTw7Cc +-----END RSA PRIVATE KEY----- diff --git a/t/keys/u6.pub b/t/keys/u6.pub new file mode 100644 index 0000000..de5b06b --- /dev/null +++ b/t/keys/u6.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHJGNFPxGhIOcCdtl2tONeEEyuR81+iVYWpzeoCMCjxOans2yftH4oKXfVEOQL27iWXgA58X1sh0/2i8NW9ehMyiI509NiqydowEGkMfTi/EgPZTmsQ6FLNu9NrPNpKVg0UQPZr1sx9Qu4XpbFyEU8FAaFNloLxwMCRLlhhe+MdcCfUY2Sm/STmqW0Py+MmgzsyDkkubzhZ6M9DkKbA5eqxCPr1lRkTIDwneipZViQSGliRsoi7NoeStMLQ9RAnwxLfAzzfnxtannfNgUkyTIvVr/MlA84xoOIVNdePVS57/lmQgz9+SQ4wttoKAQUIxsvweRUSrhDR0uEkqXUh3Nt g3@sita-lt.atc.tcs.com diff --git a/t/ssh-basic.t b/t/ssh-basic.t new file mode 100755 index 0000000..2747185 --- /dev/null +++ b/t/ssh-basic.t @@ -0,0 +1,51 @@ +#!/usr/bin/perl +use strict; +use warnings; + +# this is hardcoded; change it if needed +use lib "src"; +use Gitolite::Common; +use Gitolite::Test; + +my $bd = `gitolite query-rc -n GL_BINDIR`; +my $h = $ENV{HOME}; +my $ab = `gitolite query-rc -n GL_ADMIN_BASE`; +umask 0077; + +try " + plan 26 + + # reset stuff + rm -f $h/.ssh/authorized_keys; ok or die 1 + + cp $bd/../t/keys/u[1-6]* $h/.ssh; ok or die 2 + cp $bd/../t/keys/admin* $h/.ssh; ok or die 3 + cp $bd/../t/keys/config $h/.ssh; ok or die 4 + + mkdir $ab/keydir; ok or die 5 + cp $bd/../t/keys/*.pub $ab/keydir; ok or die 6 +"; + +_system("gitolite post-compile ssh-authkeys"); + +# basic tests +# ---------------------------------------------------------------------- + +confreset; confadd ' + @g1 = u1 + @g2 = u2 + repo foo + RW = @g1 u3 + R = @g2 u4 +'; + +try "ADMIN_PUSH set3; !/FATAL/" or die text(); + +try " + ssh u1 info; ok; /R W \tfoo/ + ssh u2 info; ok; /R \tfoo/ + ssh u3 info; ok; /R W \tfoo/ + ssh u4 info; ok; /R \tfoo/ + ssh u5 info; ok; !/foo/ + ssh u6 info; ok; !/foo/ +" From 5ebb981efabd201b2bffde1edf88d6f19ff806e7 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 15 Mar 2012 05:28:56 +0530 Subject: [PATCH 325/637] new sugar -- keysubdirs as groups. TODO: add appropriate commented entry to Gitolite::Rc.pm also --- src/syntactic-sugar/keysubdirs-as-groups | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100755 src/syntactic-sugar/keysubdirs-as-groups diff --git a/src/syntactic-sugar/keysubdirs-as-groups b/src/syntactic-sugar/keysubdirs-as-groups new file mode 100755 index 0000000..d16dc78 --- /dev/null +++ b/src/syntactic-sugar/keysubdirs-as-groups @@ -0,0 +1,31 @@ +# vim: syn=perl: + +# "sugar script" (syntactic sugar helper) for gitolite3 + +# Enabling this script in the rc file allows you to use subdirectories in +# keydir as group names. The last component other than keydir itself will be +# taken as the group name. + +sub sugar_script { + my $lines = shift; + + my @out = @{ $lines }; + unshift @out, groupnames(); + + return \@out; +} + +sub groupnames { + my @out = (); + my %members = (); + for my $pk (`find ../keydir/ -name "*.pub"`) { + next unless $pk =~ m(.*/([^/]+)/([^/]+)\.pub$); + next if $1 eq 'keydir'; + $members{$1} .= " $2"; + } + for my $m (sort keys %members) { + push @out, "\@$m =" . $members{$m}; + } + + return @out; +} From 6624d35cf99f9471e7d5061ff1bb42077ae8ea0b Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 15 Mar 2012 06:07:41 +0530 Subject: [PATCH 326/637] info command deals with groups --- src/Gitolite/Conf/Load.pm | 1 + src/commands/info | 16 ++++++++++++--- t/info.t | 41 ++++++++++++++++++++++++--------------- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm index 1759214..9b38880 100644 --- a/src/Gitolite/Conf/Load.pm +++ b/src/Gitolite/Conf/Load.pm @@ -119,6 +119,7 @@ sub load_common { sub load_1 { my $repo = shift; + return if $repo =~ /^\@/; trace( 4, $repo ); _chdir("$rc{GL_REPO_BASE}/$repo.git"); diff --git a/src/commands/info b/src/commands/info index fe52837..6672aeb 100755 --- a/src/commands/info +++ b/src/commands/info @@ -19,13 +19,23 @@ usage() if @ARGV; my $user = $ENV{GL_USER} or _die "GL_USER not set"; my $ref = 'any'; -my $fn = lister_dispatch('list-repos'); +my $lr = lister_dispatch('list-repos'); +my $lm = lister_dispatch('list-members'); -for ( @{ $fn->() } ) { +for ( @{ $lr->() } ) { my $perm = ''; for my $aa (qw(R W ^C)) { my $ret = access($_, $user, $aa, $ref); $perm .= ( $ret =~ /DENIED/ ? " " : " $aa" ); } - print "$perm\t$_\n" if $perm =~ /\S/; + next unless $perm =~ /\S/; + if (/^\@/) { + print "\n$perm\t$_\n"; + for ( @{ $lm->($_) } ) { + print "$perm\t$_\n"; + } + print "\n"; + } else { + print "$perm\t$_\n"; + } } diff --git a/t/info.t b/t/info.t index 967f4af..7171fbb 100755 --- a/t/info.t +++ b/t/info.t @@ -6,12 +6,13 @@ use warnings; use lib "src"; use Gitolite::Test; -try 'plan 35'; +try 'plan 45'; try "## info"; confreset;confadd ' - repo t1 + @t1 = t1 + repo @t1 RW = u1 R = u2 repo t2 @@ -30,23 +31,31 @@ try " "; try " glt info u1; ok; gsh - /R W *\tt1/ - /R *\tt2/ + /R W \t\@t1/ + /R W \tt1/ + /R \tt2/ !/t3/ - / R W *\ttesting/ + /R W \ttesting/ glt info u2; ok; gsh - /R *\tt1/ - /R W *\tt2/ + /R \t\@t1/ + /R \tt1/ + /R W \tt2/ !/t3/ - / R W *\ttesting/ + /R W \ttesting/ glt info u3; ok; gsh - /R W *\tt3/ - !/t1/ - !/t2/ - / R W *\ttesting/ + /R W \tt3/ + !/\@t1/ + !/t[12]/ + /R W \ttesting/ glt info u4; ok; gsh - /R *\tt3/ - !/t1/ - !/t2/ - / R W *\ttesting/ + /R \tt3/ + !/\@t1/ + !/t[12]/ + /R W \ttesting/ + glt info u5; ok; gsh + !/t[123]/ + /R W \ttesting/ + glt info u6; ok; gsh + !/t[123]/ + /R W \ttesting/ " or die; From 9a8a86306b9976476ef59564e3db4062fdbbab15 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 15 Mar 2012 09:15:02 +0530 Subject: [PATCH 327/637] _system() is less verbose otherwise things like 'gitolite access' print extra junk that is confusing. --- src/Gitolite/Common.pm | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm index 2260e41..6fcfcc6 100644 --- a/src/Gitolite/Common.pm +++ b/src/Gitolite/Common.pm @@ -105,15 +105,19 @@ sub _chdir { } sub _system { + # run system(), catch errors. Be verbose only if $ENV{D} exists. If not, + # exit with if it applies, else just "exit 1". if ( system(@_) != 0 ) { - say2 "system @_ failed"; + say2 "system @_ failed" if $ENV{D}; if ( $? == -1 ) { - die "failed to execute: $!\n"; + die "failed to execute: $!\n" if $ENV{D}; } elsif ( $? & 127 ) { - die "child died with signal " . ( $? & 127 ) . "\n"; + die "child died with signal " . ( $? & 127 ) . "\n" if $ENV{D}; } else { - die "child exited with value " . ( $? >> 8 ) . "\n"; + die "child exited with value " . ( $? >> 8 ) . "\n" if $ENV{D}; + exit ( $? >> 8 ); } + exit 1; } } From fb332a6c761661373562682afc27882521608be5 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 15 Mar 2012 09:28:07 +0530 Subject: [PATCH 328/637] (!!) neat little 'access' command... ...makes it sooo much eaier to check access rights from external scripts --- src/commands/access | 53 +++++++++++++++ t/access.t | 152 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100755 src/commands/access create mode 100755 t/access.t diff --git a/src/commands/access b/src/commands/access new file mode 100755 index 0000000..f59ac6b --- /dev/null +++ b/src/commands/access @@ -0,0 +1,53 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Rc; +use Gitolite::Common; +use Gitolite::Conf::Load; + +=for usage +Usage: gitolite access [-q] + +Check access rights for arguments given. With '-q', returns only an exit code +(shell truth, not perl truth -- 0 is success, any non-0 is failure). + + - repo: mandatory + - user: mandatory + - perm: defauts to '+'. Valid values: R, W, +, C, D, M + - ref: defauts to 'any'. See notes below + +Notes: + + - ref: Any fully qualified ref ('refs/heads/master', not 'master') is fine. + The 'any' ref is special -- it ignores deny rules (see docs for what this + means and exceptions). +=cut + +# TODO: deal with "C", call it ^C + +usage() if not @ARGV or $ARGV[0] eq '-h'; +my $quiet = 0; +if ( $ARGV[0] eq '-q' ) { $quiet = 1; shift @ARGV; } + +my ( $repo, $user, $aa, $ref ) = @ARGV; +$aa ||= '+'; +$ref ||= 'any'; +# XXX the 4th one below might need fine tuning +_die "invalid repo name" if not( $repo and $repo =~ $REPONAME_PATT ); +_die "invalid user name" if not( $user and $user =~ $USERNAME_PATT ); +_die "invalid perm" if not( $aa and $aa =~ /^(R|W|\+|C|D|M)$/ ); +_die "invalid ref name" if not( $ref and $ref =~ $REPONAME_PATT ); + +my $ret = ''; + +$ret = access( $repo, $user, $aa, $ref ); + +if ($ret =~ /DENIED/) { + print "$ret\n" unless $quiet; + exit 1; +} + +print "$ret\n" unless $quiet; +exit 0; diff --git a/t/access.t b/t/access.t new file mode 100755 index 0000000..d63eb19 --- /dev/null +++ b/t/access.t @@ -0,0 +1,152 @@ +#!/usr/bin/perl +use strict; +use warnings; + +# this is hardcoded; change it if needed +use lib "src"; +use Gitolite::Test; + +# basic tests +# ---------------------------------------------------------------------- + +try "plan 185"; + +confreset;confadd ' + @admins = admin dev1 + repo gitolite-admin + RW+ = admin + + repo testing + RW+ = @all + + @g1 = t1 + repo @g1 + R = u2 + RW = u3 + RW+ = u4 +'; + +try "ADMIN_PUSH set1; !/FATAL/" or die text(); + +try " + + gitolite access -q t1 u1; !ok; !/./ + gitolite access -q t1 u1 R; !ok; !/./ + gitolite access -q t1 u1 W; !ok; !/./ + gitolite access -q t1 u1 +; !ok; !/./ + gitolite access -q t1 u2; !ok; !/./ + gitolite access -q t1 u2 R; ok; !/./ + gitolite access -q t1 u2 W; !ok; !/./ + gitolite access -q t1 u2 +; !ok; !/./ + gitolite access -q t1 u3; !ok; !/./ + gitolite access -q t1 u3 R; ok; !/./ + gitolite access -q t1 u3 W; ok; !/./ + gitolite access -q t1 u3 +; !ok; !/./ + gitolite access -q t1 u4; ok; !/./ + gitolite access -q t1 u4 R; ok; !/./ + gitolite access -q t1 u4 W; ok; !/./ + gitolite access -q t1 u4 +; ok; !/./ + + gitolite access t1 u1; !ok; /\\+ any t1 u1 DENIED by fallthru/ + gitolite access t1 u1 R; !ok; /R any t1 u1 DENIED by fallthru/ + gitolite access t1 u1 W; !ok; /W any t1 u1 DENIED by fallthru/ + gitolite access t1 u1 +; !ok; /\\+ any t1 u1 DENIED by fallthru/ + gitolite access t1 u2; !ok; /\\+ any t1 u2 DENIED by fallthru/ + gitolite access t1 u2 R; ok; /refs/\.\*/ + gitolite access t1 u2 W; !ok; /W any t1 u2 DENIED by fallthru/ + gitolite access t1 u2 +; !ok; /\\+ any t1 u2 DENIED by fallthru/ + gitolite access t1 u3; !ok; /\\+ any t1 u3 DENIED by fallthru/ + gitolite access t1 u3 R; ok; /refs/\.\*/ + gitolite access t1 u3 W; ok; /refs/\.\*/ + gitolite access t1 u3 +; !ok; /\\+ any t1 u3 DENIED by fallthru/ + gitolite access t1 u4; ok; /refs/\.\*/ + gitolite access t1 u4 R; ok; /refs/\.\*/ + gitolite access t1 u4 W; ok; /refs/\.\*/ + gitolite access t1 u4 +; ok; /refs/\.\*/ + +"; + +confreset;confadd ' + @admins = admin dev1 + repo gitolite-admin + RW+ = admin + + @g1 = u1 + @g2 = u2 + @g3 = u3 + @gaa = aa + repo @gaa + RW+ = @g1 + RW = @g2 + RW+ master = @g3 + RW master = u4 + - master = u5 + RW+ dev = u5 + RW = u5 +'; + +try "ADMIN_PUSH set2; !/FATAL/" or die text(); + +try " + gitolite access \@gaa \@g1 + any ; ok; /refs/.*/; !/DENIED/ + gitolite access aa \@g1 + refs/heads/master ; ok; /refs/.*/; !/DENIED/ + gitolite access \@gaa \@g1 + refs/heads/next ; ok; /refs/.*/; !/DENIED/ + gitolite access \@gaa \@g1 W refs/heads/next ; ok; /refs/.*/; !/DENIED/ + gitolite access \@gaa u1 + refs/heads/dev ; ok; /refs/.*/; !/DENIED/ + gitolite access \@gaa u1 + refs/heads/next ; ok; /refs/.*/; !/DENIED/ + gitolite access aa u1 W refs/heads/next ; ok; /refs/.*/; !/DENIED/ + gitolite access \@gaa \@g2 + refs/heads/master ; !ok; /\\+ refs/heads/master \@gaa \@g2 DENIED by fallthru/ + gitolite access \@gaa \@g2 + refs/heads/next ; !ok; /\\+ refs/heads/next \@gaa \@g2 DENIED by fallthru/ + gitolite access aa \@g2 W refs/heads/master ; ok; /refs/.*/; !/DENIED/ + gitolite access aa u2 + any ; !ok; /\\+ any aa u2 DENIED by fallthru/ + gitolite access \@gaa u2 + refs/heads/master ; !ok; /\\+ refs/heads/master \@gaa u2 DENIED by fallthru/ + gitolite access \@gaa u2 W refs/heads/master ; ok; /refs/.*/; !/DENIED/ + gitolite access \@gaa \@g3 + refs/heads/master ; ok; /refs/heads/master/; !/DENIED/ + gitolite access \@gaa \@g3 W refs/heads/next ; !ok; /W refs/heads/next \@gaa \@g3 DENIED by fallthru/ + gitolite access \@gaa \@g3 W refs/heads/dev ; !ok; /W refs/heads/dev \@gaa \@g3 DENIED by fallthru/ + gitolite access aa u3 + refs/heads/dev ; !ok; /\\+ refs/heads/dev aa u3 DENIED by fallthru/ + gitolite access aa u3 + refs/heads/next ; !ok; /\\+ refs/heads/next aa u3 DENIED by fallthru/ + gitolite access \@gaa u4 + refs/heads/master ; !ok; /\\+ refs/heads/master \@gaa u4 DENIED by fallthru/ + gitolite access \@gaa u4 W refs/heads/master ; ok; /refs/heads/master/; !/DENIED/ + gitolite access aa u4 + refs/heads/next ; !ok; /\\+ refs/heads/next aa u4 DENIED by fallthru/ + gitolite access \@gaa u4 W refs/heads/next ; !ok; /W refs/heads/next \@gaa u4 DENIED by fallthru/ + gitolite access \@gaa u5 R any ; ok; /refs/heads/dev/; !/DENIED/ + gitolite access aa u5 R any ; ok; /refs/heads/dev/; !/DENIED/ + gitolite access \@gaa u5 + refs/heads/dev ; ok; /refs/heads/dev/; !/DENIED/ + gitolite access \@gaa u5 + refs/heads/master ; !ok; /\\+ refs/heads/master \@gaa u5 DENIED by refs/heads/master/ + gitolite access aa u5 + refs/heads/next ; !ok; /\\+ refs/heads/next aa u5 DENIED by fallthru/ + gitolite access \@gaa u5 R refs/heads/dev ; ok; /refs/heads/dev/; !/DENIED/ + gitolite access \@gaa u5 R refs/heads/master ; !ok; /R refs/heads/master \@gaa u5 DENIED by refs/heads/master/ + gitolite access \@gaa u5 R refs/heads/next ; ok; /refs/.*/; !/DENIED/ + gitolite access aa u5 W refs/heads/dev ; ok; /refs/heads/dev/; !/DENIED/ + gitolite access aa u5 W refs/heads/master ; !ok; /W refs/heads/master aa u5 DENIED by refs/heads/master/ + gitolite access \@gaa u5 W refs/heads/next ; ok; /refs/.*/; !/DENIED/ +"; + +confreset;confadd ' + @admins = admin dev1 + repo gitolite-admin + RW+ = admin + + @gr1 = r1 + repo @gr1 + RW refs/heads/v[0-9] = u1 + RW refs/heads = tester + + @gr2 = r2 + repo @gr2 + RW refs/heads/v[0-9] = u1 + - refs/heads/v[0-9] = tester + RW refs/heads = tester +'; + +try "ADMIN_PUSH set3; !/FATAL/" or die text(); + +try " + gitolite access \@gr2 tester W refs/heads/v1; !ok; /W refs/heads/v1 \@gr2 tester DENIED by refs/heads/v\\[0-9\\]/ + gitolite access \@gr1 tester W refs/heads/v1; ok; /refs/heads/; !/DENIED/ + gitolite access r1 tester W refs/heads/v1; ok; /refs/heads/; !/DENIED/ + gitolite access r2 tester W refs/heads/v1; !ok; /W refs/heads/v1 r2 tester DENIED by refs/heads/v\\[0-9\\]/ + gitolite access r2 tester W refs/heads/va; ok; /refs/heads/; !/DENIED/ +"; + From 446bd19de733e2abeee91d6357f77b8a13a678c9 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 15 Mar 2012 10:40:27 +0530 Subject: [PATCH 329/637] tsh/test learn the cmp() function to make full output compares easier --- src/Gitolite/Test.pm | 2 ++ src/Gitolite/Test/Tsh.pm | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm index df4cc89..5654477 100644 --- a/src/Gitolite/Test.pm +++ b/src/Gitolite/Test.pm @@ -11,6 +11,7 @@ package Gitolite::Test; dump confreset confadd + cmp ); #>>> use Exporter 'import'; @@ -24,6 +25,7 @@ BEGIN { *{'try'} = \&Tsh::try; *{'put'} = \&Tsh::put; *{'text'} = \&Tsh::text; + *{'cmp'} = \&Tsh::cmp; } use strict; diff --git a/src/Gitolite/Test/Tsh.pm b/src/Gitolite/Test/Tsh.pm index 41b4d12..491ee59 100644 --- a/src/Gitolite/Test/Tsh.pm +++ b/src/Gitolite/Test/Tsh.pm @@ -17,7 +17,7 @@ package Tsh; use Exporter 'import'; @EXPORT = qw( - try run AUTOLOAD + try run cmp AUTOLOAD rc error_count text lines error_list put cd tsh_tempdir @@ -470,6 +470,19 @@ sub fail { exit( $rc || 74 ); } +sub cmp { + # compare input string with text() + my $text = text(); + my $in = shift; + + if ($text eq $in) { + ok(); + } else { + fail('cmp failed', ''); + dbg(4, "\n\ntext = <<<$text>>>, in = <<<$in>>>\n\n"); + } +} + sub expect { my ( $patt, $msg ) = @_; $msg =~ s/^\s+// if $msg; From 8b8d3ef484aee01e73134ae3a6ce7ac4865bd27c Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 15 Mar 2012 10:40:50 +0530 Subject: [PATCH 330/637] new test 'listers' --- t/listers.t | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100755 t/listers.t diff --git a/t/listers.t b/t/listers.t new file mode 100755 index 0000000..84add2f --- /dev/null +++ b/t/listers.t @@ -0,0 +1,147 @@ +#!/usr/bin/perl +use strict; +use warnings; + +# this is hardcoded; change it if needed +use lib "src"; +use Gitolite::Test; + +try 'plan 30'; + +try "## info"; + +confreset;confadd ' + @oss = git gitolite gitolite3 + @prop = cc p4 + @crypto = alice bob carol + @dilbert = alice wally ashok + + repo @oss + RW = u1 @crypto + R = u2 @dilbert + repo @prop + RW = u2 @dilbert + R = u1 + repo t3 + RW = u3 + R = u4 +'; + +try "ADMIN_PUSH info; !/FATAL/" or die text(); +try " + /Initialized.*empty.*cc.git/ + /Initialized.*empty.*p4.git/ + /Initialized.*empty.*git.git/ + /Initialized.*empty.*gitolite.git/ + /Initialized.*empty.*gitolite3.git/ + /Initialized.*empty.*t3.git/ +"; + +try "gitolite list-groups"; cmp +'@crypto +@dilbert +@oss +@prop +'; + +try "gitolite list-users"; cmp +'@all +@crypto +@dilbert +admin +u1 +u2 +u3 +u4 +'; +try "gitolite list-repos"; cmp +'@oss +@prop +gitolite-admin +t3 +testing +'; + +try "gitolite list-phy-repos"; cmp +'cc +git +gitolite +gitolite-admin +gitolite3 +p4 +t3 +testing +'; + +try "gitolite list-memberships alice"; cmp +'@all +@crypto +@dilbert +alice +'; + +try "gitolite list-memberships ashok"; cmp +'@all +@dilbert +ashok +'; + +try "gitolite list-memberships carol"; cmp +'@all +@crypto +carol +'; + +try "gitolite list-memberships git"; cmp +'@all +@oss +git +'; + +try "gitolite list-memberships gitolite"; cmp +'@all +@oss +gitolite +'; + +try "gitolite list-memberships gitolite3"; cmp +'@all +@oss +gitolite3 +'; + +try "gitolite list-memberships cc"; cmp +'@all +@prop +cc +'; + +try "gitolite list-memberships p4"; cmp +'@all +@prop +p4 +'; + +try "gitolite list-members \@crypto"; cmp +'alice +bob +carol +'; + +try "gitolite list-members \@dilbert"; cmp +'alice +ashok +wally +'; + +try "gitolite list-members \@oss"; cmp +'git +gitolite +gitolite3 +'; + +try "gitolite list-members \@prop"; cmp +'cc +p4 +'; + From 876f6517f5a9605ab3ebdfedbe2e2ed93a242acf Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 15 Mar 2012 15:05:51 +0530 Subject: [PATCH 331/637] (testing help) allow a *testing* rc to override the normal one --- src/Gitolite/Rc.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm index 2a51a55..139d6e3 100644 --- a/src/Gitolite/Rc.pm +++ b/src/Gitolite/Rc.pm @@ -52,6 +52,10 @@ _die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR); # let values specified in rc file override our internal ones @rc{ keys %RC } = values %RC; + # testing sometimes requires all of it to be overridden silently; use an + # env var that is highly unlikely to appear in real life :) + do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC}; + # fix PATH (TODO: do it only if 'gitolite' isn't in PATH) $ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}"; From 9780ddab9de2856e005e699671606a78bc45ba8b Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 15 Mar 2012 16:00:15 +0530 Subject: [PATCH 332/637] (!!) personal branches -- 1 line of code, 50 lines of test! (and by the way even in g2 this was not so easy as just ONE line of code!) --- src/Gitolite/Conf/Load.pm | 2 +- t/personal-branches.t | 51 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100755 t/personal-branches.t diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm index 9b38880..3d73ca7 100644 --- a/src/Gitolite/Conf/Load.pm +++ b/src/Gitolite/Conf/Load.pm @@ -68,7 +68,7 @@ sub access { trace( 3, scalar(@rules) . " rules found" ); for my $r (@rules) { my $perm = $r->[1]; - my $refex = $r->[2]; + my $refex = $r->[2]; $refex =~ s(/USER/)(/$user/); trace( 4, "perm=$perm, refex=$refex" ); # skip 'deny' rules if the ref is not (yet) known diff --git a/t/personal-branches.t b/t/personal-branches.t new file mode 100755 index 0000000..04e911f --- /dev/null +++ b/t/personal-branches.t @@ -0,0 +1,51 @@ +#!/usr/bin/perl +use strict; +use warnings; + +# this is hardcoded; change it if needed +use lib "src"; +use Gitolite::Test; + +# basic tests +# ---------------------------------------------------------------------- + +try "plan 39"; + +confreset;confadd ' + @admins = admin dev1 + repo gitolite-admin + RW+ = admin + + repo testing + RW+ = @all + + @g1 = t1 + repo @g1 + R = u2 + RW = u3 + RW+ = u4 + RW a/USER/ = @all + RW+ p/USER/ = u1 u6 +'; + +try "ADMIN_PUSH set1; !/FATAL/" or die text(); + +try " + + gitolite access t1 u1; ok; /refs/heads/p/u1//; !/DENIED/ + gitolite access t1 u5; !ok; /\\+ any t1 u5 DENIED by fallthru/ + gitolite access \@g1 u5 W; ok; /refs/heads/a/u5//; !/DENIED/ + + gitolite access t1 u1 W refs/heads/a/user1/foo; !ok; /W refs/heads/a/user1/foo t1 u1 DENIED by fallthru/ + gitolite access \@g1 u1 + refs/heads/a/user1/foo; !ok; /\\+ refs/heads/a/user1/foo \@g1 u1 DENIED by fallthru/ + gitolite access t1 u1 W refs/heads/p/user1/foo; !ok; /W refs/heads/p/user1/foo t1 u1 DENIED by fallthru/ + gitolite access \@g1 u1 + refs/heads/p/user1/foo; !ok; /\\+ refs/heads/p/user1/foo \@g1 u1 DENIED by fallthru/ + + gitolite access \@g1 u1 W refs/heads/a/u1/foo; ok; /refs/heads/a/u1//; !/DENIED/ + gitolite access t1 u1 + refs/heads/a/u1/foo; !ok; /\\+ refs/heads/a/u1/foo t1 u1 DENIED by fallthru/ + gitolite access \@g1 u1 W refs/heads/p/u1/foo; ok; /refs/heads/p/u1//; !/DENIED/ + gitolite access t1 u1 + refs/heads/p/u1/foo; ok; /refs/heads/p/u1//; !/DENIED/ + + gitolite access \@g1 u1 W refs/heads/p/u2/foo; !ok; /W refs/heads/p/u2/foo \@g1 u1 DENIED by fallthru/ + gitolite access t1 u1 + refs/heads/p/u2/foo; !ok; /\\+ refs/heads/p/u2/foo t1 u1 DENIED by fallthru/ +"; From 5e2563bb8cdc534e520fa07c540a6e40019d163b Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 15 Mar 2012 18:16:16 +0530 Subject: [PATCH 333/637] setup was over-engineered... --- src/Gitolite/Setup.pm | 60 +++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm index 09930bd..20143f0 100644 --- a/src/Gitolite/Setup.pm +++ b/src/Gitolite/Setup.pm @@ -4,20 +4,12 @@ package Gitolite::Setup; # ---------------------------------------------------------------------- =for args -Usage: gitolite setup [] +Usage: gitolite setup [