添加grack
This commit is contained in:
parent
38b97aa558
commit
acd4f33d72
|
@ -0,0 +1,14 @@
|
|||
language: ruby
|
||||
env:
|
||||
- TRAVIS=true
|
||||
branches:
|
||||
only:
|
||||
- 'master'
|
||||
rvm:
|
||||
- 1.9.3-p327
|
||||
- 2.0.0
|
||||
before_script:
|
||||
- "bundle install"
|
||||
- "git submodule init"
|
||||
- "git submodule update"
|
||||
script: "bundle exec rake"
|
|
@ -0,0 +1,16 @@
|
|||
2.0.2
|
||||
- Revert MR that broke smart HTTP clients.
|
||||
|
||||
2.0.1
|
||||
- Make sure child processes get reaped after popen, again.
|
||||
|
||||
2.0.0
|
||||
- Use safer shell commands and avoid Dir.chdir
|
||||
- Restrict the environment for shell commands
|
||||
- Make Grack::Server thread-safe (zimbatm)
|
||||
- Make sure child processes get reaped after popen
|
||||
- Verify requested path is actually a Git directory (Ryan Canty)
|
||||
|
||||
|
||||
1.1.0
|
||||
- Modifies service_rpc to use chunked transfer (https://github.com/gitlabhq/grack/pull/1)
|
|
@ -0,0 +1,10 @@
|
|||
source "http://ruby.taobao.org"
|
||||
|
||||
gemspec
|
||||
|
||||
group :development do
|
||||
gem 'byebug'
|
||||
gem 'rake'
|
||||
gem 'pry'
|
||||
gem 'rack-test'
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
gitlab-grack (2.0.2)
|
||||
rack (~> 1.5.1)
|
||||
|
||||
GEM
|
||||
remote: http://ruby.taobao.org/
|
||||
specs:
|
||||
byebug (4.0.5)
|
||||
columnize (= 0.9.0)
|
||||
coderay (1.1.0)
|
||||
columnize (0.9.0)
|
||||
metaclass (0.0.1)
|
||||
method_source (0.8.2)
|
||||
mocha (0.14.0)
|
||||
metaclass (~> 0.0.1)
|
||||
pry (0.10.1)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
rack (1.5.2)
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
rake (10.1.0)
|
||||
slop (3.6.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
byebug
|
||||
gitlab-grack!
|
||||
mocha (~> 0.11)
|
||||
pry
|
||||
rack-test
|
||||
rake
|
|
@ -0,0 +1,95 @@
|
|||
Grack - Ruby/Rack Git Smart-HTTP Server Handler
|
||||
===============================================
|
||||
|
||||
[![Build Status](https://travis-ci.org/gitlabhq/grack.png)](https://travis-ci.org/gitlabhq/grack)
|
||||
[![Code Climate](https://codeclimate.com/github/gitlabhq/grack.png)](https://codeclimate.com/github/gitlabhq/grack)
|
||||
|
||||
This project aims to replace the builtin git-http-backend CGI handler
|
||||
distributed with C Git with a Rack application. This reason for doing this
|
||||
is to allow far more webservers to be able to handle Git smart http requests.
|
||||
|
||||
The default git-http-backend only runs as a CGI script, and specifically is
|
||||
only targeted for Apache 2.x usage (it requires PATH_INFO to be set and
|
||||
specifically formatted). So, instead of trying to get it to work with
|
||||
other CGI capable webservers (Lighttpd, etc), we can get it running on nearly
|
||||
every major and minor webserver out there by making it Rack capable. Rack
|
||||
applications can run with the following handlers:
|
||||
|
||||
* CGI
|
||||
* FCGI
|
||||
* Mongrel (and EventedMongrel and SwiftipliedMongrel)
|
||||
* WEBrick
|
||||
* SCGI
|
||||
* LiteSpeed
|
||||
* Thin
|
||||
|
||||
These web servers include Rack handlers in their distributions:
|
||||
|
||||
* Ebb
|
||||
* Fuzed
|
||||
* Phusion Passenger (which is mod_rack for Apache and for nginx)
|
||||
* Unicorn
|
||||
* Puma
|
||||
|
||||
With [Warbler](http://caldersphere.rubyforge.org/warbler/classes/Warbler.html),
|
||||
and JRuby, we can also generate a WAR file that can be deployed in any Java
|
||||
web application server (Tomcat, Glassfish, Websphere, JBoss, etc).
|
||||
|
||||
Since the git-http-backend is really just a simple wrapper for the upload-pack
|
||||
and receive-pack processes with the '--stateless-rpc' option, it does not
|
||||
actually re-implement very much.
|
||||
|
||||
Dependencies
|
||||
========================
|
||||
* Ruby - http://www.ruby-lang.org
|
||||
* Rack - http://rack.rubyforge.org
|
||||
* A Rack-compatible web server
|
||||
* Git >= 1.7 (currently the 'pu' branch)
|
||||
* Mocha (only for running the tests)
|
||||
|
||||
Quick Start
|
||||
========================
|
||||
$ gem install rack
|
||||
$ (edit config.ru to set git project path)
|
||||
$ rackup --host 127.0.0.1 -p 8080 config.ru
|
||||
$ git clone http://127.0.0.1:8080/schacon/grit.git
|
||||
|
||||
Contributing
|
||||
========================
|
||||
If you would like to contribute to the Grack project, I prefer to get
|
||||
pull-requests via GitHub. You should include tests for whatever functionality
|
||||
you add. Just fork this project, push your changes to your fork and click
|
||||
the 'pull request' button. To run the tests, you first need to install the
|
||||
'mocha' mocking library and initialize the submodule.
|
||||
|
||||
$ sudo gem install mocha
|
||||
$ git submodule init
|
||||
$ git submodule update
|
||||
|
||||
Then you should be able to run the tests with a 'rake' command. You can also
|
||||
run coverage tests with 'rake rcov' if you have rcov installed.
|
||||
|
||||
License
|
||||
========================
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2009 Scott Chacon <schacon@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env rake
|
||||
require "bundler/gem_tasks"
|
||||
|
||||
task :default => :test
|
||||
|
||||
desc "Run the tests."
|
||||
task :test do
|
||||
Dir.glob("tests/*_test.rb").each do |f|
|
||||
system "ruby #{f}"
|
||||
end
|
||||
end
|
||||
|
||||
desc "Run test coverage."
|
||||
task :rcov do
|
||||
system "rcov tests/*_test.rb -i lib/git_http.rb -x rack -x Library -x tests"
|
||||
system "open coverage/index.html"
|
||||
end
|
||||
|
||||
namespace :grack do
|
||||
desc "Start Grack"
|
||||
task :start do
|
||||
system('./bin/testserver')
|
||||
end
|
||||
end
|
||||
|
||||
desc "Start everything."
|
||||
multitask :start => [ 'grack:start' ]
|
|
@ -0,0 +1,6 @@
|
|||
#! /bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cd $(dirname "$0")/..
|
||||
exec /usr/bin/env bundle exec pry -Ilib -r grack
|
|
@ -0,0 +1,24 @@
|
|||
#! /usr/bin/env ruby
|
||||
libdir = File.absolute_path( File.join( File.dirname(__FILE__), '../lib' ) )
|
||||
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
||||
|
||||
Bundler.require(:default, :development)
|
||||
|
||||
|
||||
require 'grack'
|
||||
require 'rack'
|
||||
root = File.absolute_path( File.join( File.dirname(__FILE__), '../examples' ) )
|
||||
app = Grack::Server.new({
|
||||
project_root: root,
|
||||
upload_pack: true,
|
||||
receive_pack:true
|
||||
})
|
||||
|
||||
app1= Rack::Builder.new do
|
||||
use Grack::Auth do |username, password|
|
||||
[username, password] == ['123', '455']
|
||||
end
|
||||
run app
|
||||
end
|
||||
|
||||
Rack::Server.start app: app1, Port: 3001
|
|
@ -0,0 +1 @@
|
|||
ref: refs/heads/master
|
|
@ -0,0 +1,6 @@
|
|||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
|
@ -0,0 +1 @@
|
|||
Unnamed repository; edit this file 'description' to name the repository.
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to check the commit log message taken by
|
||||
# applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit. The hook is
|
||||
# allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "applypatch-msg".
|
||||
|
||||
. git-sh-setup
|
||||
test -x "$GIT_DIR/hooks/commit-msg" &&
|
||||
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
|
||||
:
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to check the commit log message.
|
||||
# Called by "git commit" with one argument, the name of the file
|
||||
# that has the commit message. The hook should exit with non-zero
|
||||
# status after issuing an appropriate message if it wants to stop the
|
||||
# commit. The hook is allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "commit-msg".
|
||||
|
||||
# Uncomment the below to add a Signed-off-by line to the message.
|
||||
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
||||
# hook is more suited to it.
|
||||
#
|
||||
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||
|
||||
# This example catches duplicate Signed-off-by lines.
|
||||
|
||||
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
||||
echo >&2 Duplicate Signed-off-by lines.
|
||||
exit 1
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to prepare a packed repository for use over
|
||||
# dumb transports.
|
||||
#
|
||||
# To enable this hook, rename this file to "post-update".
|
||||
|
||||
exec git update-server-info
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to verify what is about to be committed
|
||||
# by applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-applypatch".
|
||||
|
||||
. git-sh-setup
|
||||
test -x "$GIT_DIR/hooks/pre-commit" &&
|
||||
exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
|
||||
:
|
|
@ -0,0 +1,49 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to verify what is about to be committed.
|
||||
# Called by "git commit" with no arguments. The hook should
|
||||
# exit with non-zero status after issuing an appropriate message if
|
||||
# it wants to stop the commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-commit".
|
||||
|
||||
if git rev-parse --verify HEAD >/dev/null 2>&1
|
||||
then
|
||||
against=HEAD
|
||||
else
|
||||
# Initial commit: diff against an empty tree object
|
||||
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
|
||||
fi
|
||||
|
||||
# If you want to allow non-ASCII filenames set this variable to true.
|
||||
allownonascii=$(git config --bool hooks.allownonascii)
|
||||
|
||||
# Redirect output to stderr.
|
||||
exec 1>&2
|
||||
|
||||
# Cross platform projects tend to avoid non-ASCII filenames; prevent
|
||||
# them from being added to the repository. We exploit the fact that the
|
||||
# printable range starts at the space character and ends with tilde.
|
||||
if [ "$allownonascii" != "true" ] &&
|
||||
# Note that the use of brackets around a tr range is ok here, (it's
|
||||
# even required, for portability to Solaris 10's /usr/bin/tr), since
|
||||
# the square bracket bytes happen to fall in the designated range.
|
||||
test $(git diff --cached --name-only --diff-filter=A -z $against |
|
||||
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
|
||||
then
|
||||
cat <<\EOF
|
||||
Error: Attempt to add a non-ASCII file name.
|
||||
|
||||
This can cause problems if you want to work with people on other platforms.
|
||||
|
||||
To be portable it is advisable to rename the file.
|
||||
|
||||
If you know what you are doing you can disable this check using:
|
||||
|
||||
git config hooks.allownonascii true
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If there are whitespace errors, print the offending file names and fail.
|
||||
exec git diff-index --check --cached $against --
|
|
@ -0,0 +1,53 @@
|
|||
#!/bin/sh
|
||||
|
||||
# An example hook script to verify what is about to be pushed. Called by "git
|
||||
# push" after it has checked the remote status, but before anything has been
|
||||
# pushed. If this script exits with a non-zero status nothing will be pushed.
|
||||
#
|
||||
# This hook is called with the following parameters:
|
||||
#
|
||||
# $1 -- Name of the remote to which the push is being done
|
||||
# $2 -- URL to which the push is being done
|
||||
#
|
||||
# If pushing without using a named remote those arguments will be equal.
|
||||
#
|
||||
# Information about the commits which are being pushed is supplied as lines to
|
||||
# the standard input in the form:
|
||||
#
|
||||
# <local ref> <local sha1> <remote ref> <remote sha1>
|
||||
#
|
||||
# This sample shows how to prevent push of commits where the log message starts
|
||||
# with "WIP" (work in progress).
|
||||
|
||||
remote="$1"
|
||||
url="$2"
|
||||
|
||||
z40=0000000000000000000000000000000000000000
|
||||
|
||||
while read local_ref local_sha remote_ref remote_sha
|
||||
do
|
||||
if [ "$local_sha" = $z40 ]
|
||||
then
|
||||
# Handle delete
|
||||
:
|
||||
else
|
||||
if [ "$remote_sha" = $z40 ]
|
||||
then
|
||||
# New branch, examine all commits
|
||||
range="$local_sha"
|
||||
else
|
||||
# Update to existing branch, examine new commits
|
||||
range="$remote_sha..$local_sha"
|
||||
fi
|
||||
|
||||
# Check for WIP commit
|
||||
commit=`git rev-list -n 1 --grep '^WIP' "$range"`
|
||||
if [ -n "$commit" ]
|
||||
then
|
||||
echo >&2 "Found WIP commit in $local_ref, not pushing"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,169 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2006, 2008 Junio C Hamano
|
||||
#
|
||||
# The "pre-rebase" hook is run just before "git rebase" starts doing
|
||||
# its job, and can prevent the command from running by exiting with
|
||||
# non-zero status.
|
||||
#
|
||||
# The hook is called with the following parameters:
|
||||
#
|
||||
# $1 -- the upstream the series was forked from.
|
||||
# $2 -- the branch being rebased (or empty when rebasing the current branch).
|
||||
#
|
||||
# This sample shows how to prevent topic branches that are already
|
||||
# merged to 'next' branch from getting rebased, because allowing it
|
||||
# would result in rebasing already published history.
|
||||
|
||||
publish=next
|
||||
basebranch="$1"
|
||||
if test "$#" = 2
|
||||
then
|
||||
topic="refs/heads/$2"
|
||||
else
|
||||
topic=`git symbolic-ref HEAD` ||
|
||||
exit 0 ;# we do not interrupt rebasing detached HEAD
|
||||
fi
|
||||
|
||||
case "$topic" in
|
||||
refs/heads/??/*)
|
||||
;;
|
||||
*)
|
||||
exit 0 ;# we do not interrupt others.
|
||||
;;
|
||||
esac
|
||||
|
||||
# Now we are dealing with a topic branch being rebased
|
||||
# on top of master. Is it OK to rebase it?
|
||||
|
||||
# Does the topic really exist?
|
||||
git show-ref -q "$topic" || {
|
||||
echo >&2 "No such branch $topic"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Is topic fully merged to master?
|
||||
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
|
||||
if test -z "$not_in_master"
|
||||
then
|
||||
echo >&2 "$topic is fully merged to master; better remove it."
|
||||
exit 1 ;# we could allow it, but there is no point.
|
||||
fi
|
||||
|
||||
# Is topic ever merged to next? If so you should not be rebasing it.
|
||||
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
|
||||
only_next_2=`git rev-list ^master ${publish} | sort`
|
||||
if test "$only_next_1" = "$only_next_2"
|
||||
then
|
||||
not_in_topic=`git rev-list "^$topic" master`
|
||||
if test -z "$not_in_topic"
|
||||
then
|
||||
echo >&2 "$topic is already up-to-date with master"
|
||||
exit 1 ;# we could allow it, but there is no point.
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
|
||||
/usr/bin/perl -e '
|
||||
my $topic = $ARGV[0];
|
||||
my $msg = "* $topic has commits already merged to public branch:\n";
|
||||
my (%not_in_next) = map {
|
||||
/^([0-9a-f]+) /;
|
||||
($1 => 1);
|
||||
} split(/\n/, $ARGV[1]);
|
||||
for my $elem (map {
|
||||
/^([0-9a-f]+) (.*)$/;
|
||||
[$1 => $2];
|
||||
} split(/\n/, $ARGV[2])) {
|
||||
if (!exists $not_in_next{$elem->[0]}) {
|
||||
if ($msg) {
|
||||
print STDERR $msg;
|
||||
undef $msg;
|
||||
}
|
||||
print STDERR " $elem->[1]\n";
|
||||
}
|
||||
}
|
||||
' "$topic" "$not_in_next" "$not_in_master"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
################################################################
|
||||
|
||||
This sample hook safeguards topic branches that have been
|
||||
published from being rewound.
|
||||
|
||||
The workflow assumed here is:
|
||||
|
||||
* Once a topic branch forks from "master", "master" is never
|
||||
merged into it again (either directly or indirectly).
|
||||
|
||||
* Once a topic branch is fully cooked and merged into "master",
|
||||
it is deleted. If you need to build on top of it to correct
|
||||
earlier mistakes, a new topic branch is created by forking at
|
||||
the tip of the "master". This is not strictly necessary, but
|
||||
it makes it easier to keep your history simple.
|
||||
|
||||
* Whenever you need to test or publish your changes to topic
|
||||
branches, merge them into "next" branch.
|
||||
|
||||
The script, being an example, hardcodes the publish branch name
|
||||
to be "next", but it is trivial to make it configurable via
|
||||
$GIT_DIR/config mechanism.
|
||||
|
||||
With this workflow, you would want to know:
|
||||
|
||||
(1) ... if a topic branch has ever been merged to "next". Young
|
||||
topic branches can have stupid mistakes you would rather
|
||||
clean up before publishing, and things that have not been
|
||||
merged into other branches can be easily rebased without
|
||||
affecting other people. But once it is published, you would
|
||||
not want to rewind it.
|
||||
|
||||
(2) ... if a topic branch has been fully merged to "master".
|
||||
Then you can delete it. More importantly, you should not
|
||||
build on top of it -- other people may already want to
|
||||
change things related to the topic as patches against your
|
||||
"master", so if you need further changes, it is better to
|
||||
fork the topic (perhaps with the same name) afresh from the
|
||||
tip of "master".
|
||||
|
||||
Let's look at this example:
|
||||
|
||||
o---o---o---o---o---o---o---o---o---o "next"
|
||||
/ / / /
|
||||
/ a---a---b A / /
|
||||
/ / / /
|
||||
/ / c---c---c---c B /
|
||||
/ / / \ /
|
||||
/ / / b---b C \ /
|
||||
/ / / / \ /
|
||||
---o---o---o---o---o---o---o---o---o---o---o "master"
|
||||
|
||||
|
||||
A, B and C are topic branches.
|
||||
|
||||
* A has one fix since it was merged up to "next".
|
||||
|
||||
* B has finished. It has been fully merged up to "master" and "next",
|
||||
and is ready to be deleted.
|
||||
|
||||
* C has not merged to "next" at all.
|
||||
|
||||
We would want to allow C to be rebased, refuse A, and encourage
|
||||
B to be deleted.
|
||||
|
||||
To compute (1):
|
||||
|
||||
git rev-list ^master ^topic next
|
||||
git rev-list ^master next
|
||||
|
||||
if these match, topic has not merged in next at all.
|
||||
|
||||
To compute (2):
|
||||
|
||||
git rev-list master..topic
|
||||
|
||||
if this is empty, it is fully merged to "master".
|
|
@ -0,0 +1,36 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to prepare the commit log message.
|
||||
# Called by "git commit" with the name of the file that has the
|
||||
# commit message, followed by the description of the commit
|
||||
# message's source. The hook's purpose is to edit the commit
|
||||
# message file. If the hook fails with a non-zero status,
|
||||
# the commit is aborted.
|
||||
#
|
||||
# To enable this hook, rename this file to "prepare-commit-msg".
|
||||
|
||||
# This hook includes three examples. The first comments out the
|
||||
# "Conflicts:" part of a merge commit.
|
||||
#
|
||||
# The second includes the output of "git diff --name-status -r"
|
||||
# into the message, just before the "git status" output. It is
|
||||
# commented because it doesn't cope with --amend or with squashed
|
||||
# commits.
|
||||
#
|
||||
# The third example adds a Signed-off-by line to the message, that can
|
||||
# still be edited. This is rarely a good idea.
|
||||
|
||||
case "$2,$3" in
|
||||
merge,)
|
||||
/usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
|
||||
|
||||
# ,|template,)
|
||||
# /usr/bin/perl -i.bak -pe '
|
||||
# print "\n" . `git diff --cached --name-status -r`
|
||||
# if /^#/ && $first++ == 0' "$1" ;;
|
||||
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
|
@ -0,0 +1,128 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to blocks unannotated tags from entering.
|
||||
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
|
||||
#
|
||||
# To enable this hook, rename this file to "update".
|
||||
#
|
||||
# Config
|
||||
# ------
|
||||
# hooks.allowunannotated
|
||||
# This boolean sets whether unannotated tags will be allowed into the
|
||||
# repository. By default they won't be.
|
||||
# hooks.allowdeletetag
|
||||
# This boolean sets whether deleting tags will be allowed in the
|
||||
# repository. By default they won't be.
|
||||
# hooks.allowmodifytag
|
||||
# This boolean sets whether a tag may be modified after creation. By default
|
||||
# it won't be.
|
||||
# hooks.allowdeletebranch
|
||||
# This boolean sets whether deleting branches will be allowed in the
|
||||
# repository. By default they won't be.
|
||||
# hooks.denycreatebranch
|
||||
# This boolean sets whether remotely creating branches will be denied
|
||||
# in the repository. By default this is allowed.
|
||||
#
|
||||
|
||||
# --- Command line
|
||||
refname="$1"
|
||||
oldrev="$2"
|
||||
newrev="$3"
|
||||
|
||||
# --- Safety check
|
||||
if [ -z "$GIT_DIR" ]; then
|
||||
echo "Don't run this script from the command line." >&2
|
||||
echo " (if you want, you could supply GIT_DIR then run" >&2
|
||||
echo " $0 <ref> <oldrev> <newrev>)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
|
||||
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Config
|
||||
allowunannotated=$(git config --bool hooks.allowunannotated)
|
||||
allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
|
||||
denycreatebranch=$(git config --bool hooks.denycreatebranch)
|
||||
allowdeletetag=$(git config --bool hooks.allowdeletetag)
|
||||
allowmodifytag=$(git config --bool hooks.allowmodifytag)
|
||||
|
||||
# check for no description
|
||||
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
|
||||
case "$projectdesc" in
|
||||
"Unnamed repository"* | "")
|
||||
echo "*** Project description file hasn't been set" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Check types
|
||||
# if $newrev is 0000...0000, it's a commit to delete a ref.
|
||||
zero="0000000000000000000000000000000000000000"
|
||||
if [ "$newrev" = "$zero" ]; then
|
||||
newrev_type=delete
|
||||
else
|
||||
newrev_type=$(git cat-file -t $newrev)
|
||||
fi
|
||||
|
||||
case "$refname","$newrev_type" in
|
||||
refs/tags/*,commit)
|
||||
# un-annotated tag
|
||||
short_refname=${refname##refs/tags/}
|
||||
if [ "$allowunannotated" != "true" ]; then
|
||||
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
|
||||
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/tags/*,delete)
|
||||
# delete tag
|
||||
if [ "$allowdeletetag" != "true" ]; then
|
||||
echo "*** Deleting a tag is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/tags/*,tag)
|
||||
# annotated tag
|
||||
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
|
||||
then
|
||||
echo "*** Tag '$refname' already exists." >&2
|
||||
echo "*** Modifying a tag is not allowed in this repository." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/heads/*,commit)
|
||||
# branch
|
||||
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
|
||||
echo "*** Creating a branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/heads/*,delete)
|
||||
# delete branch
|
||||
if [ "$allowdeletebranch" != "true" ]; then
|
||||
echo "*** Deleting a branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/remotes/*,commit)
|
||||
# tracking branch
|
||||
;;
|
||||
refs/remotes/*,delete)
|
||||
# delete tracking branch
|
||||
if [ "$allowdeletebranch" != "true" ]; then
|
||||
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
# Anything else (is there anything else?)
|
||||
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Finished
|
||||
exit 0
|
|
@ -0,0 +1,6 @@
|
|||
# git ls-files --others --exclude-from=.git/info/exclude
|
||||
# Lines that start with '#' are comments.
|
||||
# For a project mostly in C, the following would be a good set of
|
||||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
# *~
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
x•ÍM
|
||||
à @á®=ÅìÁñgœ<67>Rz£Æ5B0÷o®Ðíƒ<C3AD>—Fïmúð˜g)@(†eCâš )‹c‘Í$Y)ÊEÆ$FÅkîã„zÅ£x1“eã>µÇö]Òèo@g½g
|
||||
<EFBFBD>à©Yku×û5ËJµ£Mõíæ/0
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
e1022324d23146d29075a3e7c6f637cb67f091b5
|
|
@ -0,0 +1,9 @@
|
|||
#! /usr/bin/env ruby
|
||||
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/lib')
|
||||
require 'lib/git_http'
|
||||
config = {
|
||||
:project_root => "/opt",
|
||||
:upload_pack => true,
|
||||
:receive_pack => false,
|
||||
}
|
||||
Rack::Handler::FastCGI.run(GitHttp::App.new(config))
|
|
@ -0,0 +1,20 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
require File.expand_path('../lib/grack/version', __FILE__)
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.authors = ["Scott Chacon"]
|
||||
gem.email = ["schacon@gmail.com"]
|
||||
gem.description = %q{Ruby/Rack Git Smart-HTTP Server Handler}
|
||||
gem.summary = %q{Ruby/Rack Git Smart-HTTP Server Handler}
|
||||
gem.homepage = "https://github.com/gitlabhq/grack"
|
||||
|
||||
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
||||
gem.files = `git ls-files`.split("\n")
|
||||
gem.test_files = `git ls-files -- tests/*`.split("\n")
|
||||
gem.name = "grack"
|
||||
gem.require_paths = ["lib"]
|
||||
gem.version = Grack::VERSION
|
||||
|
||||
gem.add_dependency("rack", "~> 1.4.5")
|
||||
gem.add_development_dependency("mocha", "~> 0.11")
|
||||
end
|
|
@ -0,0 +1,60 @@
|
|||
Installation
|
||||
========================
|
||||
|
||||
** This documentation is not finished yet. I haven't tested all of
|
||||
these and it's obviously incomplete - these are currently just notes.
|
||||
|
||||
FastCGI
|
||||
---------------------------------------
|
||||
Here is an example config from lighttpd server:
|
||||
----
|
||||
# main fastcgi entry
|
||||
$HTTP["url"] =~ "^/myapp/.+$" {
|
||||
fastcgi.server = ( "/myapp" =>
|
||||
( "localhost" =>
|
||||
( "bin-path" => "/var/www/localhost/cgi-bin/dispatch.fcgi",
|
||||
"docroot" => "/var/www/localhost/htdocs/myapp",
|
||||
"host" => "127.0.0.1",
|
||||
"port" => 1026,
|
||||
"check-local" => "disable"
|
||||
)
|
||||
)
|
||||
)
|
||||
} # HTTP[url]
|
||||
----
|
||||
You can use the examples/dispatch.fcgi file as your dispatcher.
|
||||
|
||||
(Example Apache setup?)
|
||||
|
||||
Installing in a Java application server
|
||||
---------------------------------------
|
||||
# install Warbler
|
||||
$ sudo gem install warbler
|
||||
$ cd gitsmart
|
||||
$ (edit config.ru)
|
||||
$ warble
|
||||
$ cp gitsmart.war /path/to/java/autodeploy/dir
|
||||
|
||||
Unicorn
|
||||
---------------------------------------
|
||||
With Unicorn (http://unicorn.bogomips.org/) you can just run 'unicorn'
|
||||
in the directory with the config.ru file.
|
||||
|
||||
Thin
|
||||
---------------------------------------
|
||||
thin.yml
|
||||
---
|
||||
pid: /home/deploy/myapp/server/thin.pid
|
||||
log: /home/deploy/myapp/logs/thin.log
|
||||
timeout: 30
|
||||
port: 7654
|
||||
max_conns: 1024
|
||||
chdir: /home/deploy/myapp/site_files
|
||||
rackup: /home/deploy/myapp/server/config.ru
|
||||
max_persistent_conns: 512
|
||||
environment: production
|
||||
address: 127.0.0.1
|
||||
servers: 1
|
||||
daemonize: true
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
require "grack/bundle"
|
||||
|
||||
module Grack
|
||||
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
require 'rack/auth/basic'
|
||||
require 'rack/auth/abstract/handler'
|
||||
require 'rack/auth/abstract/request'
|
||||
|
||||
module Grack
|
||||
class Auth < Rack::Auth::Basic
|
||||
def call(env)
|
||||
@env = env
|
||||
@request = Rack::Request.new(env)
|
||||
@auth = Request.new(env)
|
||||
|
||||
if not @auth.provided?
|
||||
unauthorized
|
||||
elsif not @auth.basic?
|
||||
bad_request
|
||||
else
|
||||
result = if (access = valid?(@auth) and access == true)
|
||||
@env['REMOTE_USER'] = @auth.username
|
||||
@app.call(env)
|
||||
else
|
||||
if access == '404'
|
||||
render_not_found
|
||||
elsif access == '403'
|
||||
render_no_access
|
||||
else
|
||||
unauthorized
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
end# method call
|
||||
|
||||
# def valid?
|
||||
# false
|
||||
# end
|
||||
end# class Auth
|
||||
end# module Grack
|
|
@ -0,0 +1,19 @@
|
|||
require 'rack/builder'
|
||||
require 'grack/auth'
|
||||
require 'grack/server'
|
||||
|
||||
module Grack
|
||||
module Bundle
|
||||
extend self
|
||||
|
||||
def new(config)
|
||||
Rack::Builder.new do
|
||||
use Grack::Auth do |username, password|
|
||||
yield(username, password)
|
||||
end
|
||||
run Grack::Server.new(config)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,72 @@
|
|||
module Grack
|
||||
class Git
|
||||
attr_reader :repo
|
||||
|
||||
def initialize(git_path, repo_path)
|
||||
@git_path = git_path
|
||||
@repo = repo_path
|
||||
end
|
||||
|
||||
def update_server_info
|
||||
execute(%W(update-server-info))
|
||||
end
|
||||
|
||||
def command(cmd)
|
||||
[@git_path || 'git'] + cmd
|
||||
end
|
||||
|
||||
def capture(cmd)
|
||||
# _Not_ the same as `IO.popen(...).read`
|
||||
# By using a block we tell IO.popen to close (wait for) the child process
|
||||
# after we are done reading its output.
|
||||
IO.popen(popen_env, cmd, popen_options) { |p| p.read }
|
||||
end
|
||||
|
||||
def execute(cmd)
|
||||
cmd = command(cmd)
|
||||
if block_given?
|
||||
IO.popen(popen_env, cmd, File::RDWR, popen_options) do |pipe|
|
||||
yield(pipe)
|
||||
end
|
||||
else
|
||||
capture(cmd).chomp
|
||||
end
|
||||
end
|
||||
|
||||
def popen_options
|
||||
{ chdir: repo, unsetenv_others: true }
|
||||
end
|
||||
|
||||
def popen_env
|
||||
{ 'PATH' => ENV['PATH'], 'GL_ID' => ENV['GL_ID'] }
|
||||
end
|
||||
|
||||
def config_setting(service_name)
|
||||
service_name = service_name.gsub('-', '')
|
||||
setting = config("http.#{service_name}")
|
||||
|
||||
if service_name == 'uploadpack'
|
||||
setting != 'false'
|
||||
else
|
||||
setting == 'true'
|
||||
end
|
||||
end
|
||||
|
||||
def config(config_name)
|
||||
execute(%W(config #{config_name}))
|
||||
end
|
||||
|
||||
def valid_repo?
|
||||
return false unless File.exists?(repo) && File.realpath(repo) == repo
|
||||
|
||||
match = execute(%W(rev-parse --git-dir)).match(/\.$|\.git$/)
|
||||
|
||||
if match.to_s == '.git'
|
||||
# Since the parent could be a git repo, we want to make sure the actual repo contains a git dir.
|
||||
return false unless Dir.entries(repo).include?('.git')
|
||||
end
|
||||
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,308 @@
|
|||
require 'zlib'
|
||||
require 'rack/request'
|
||||
require 'rack/response'
|
||||
require 'rack/utils'
|
||||
require 'time'
|
||||
|
||||
require 'grack/git'
|
||||
|
||||
module Grack
|
||||
class Server
|
||||
attr_reader :git
|
||||
|
||||
SERVICES = [
|
||||
["POST", 'service_rpc', "(.*?)/git-upload-pack$", 'upload-pack'],
|
||||
["POST", 'service_rpc', "(.*?)/git-receive-pack$", 'receive-pack'],
|
||||
|
||||
["GET", 'get_info_refs', "(.*?)/info/refs$"],
|
||||
["GET", 'get_text_file', "(.*?)/HEAD$"],
|
||||
["GET", 'get_text_file', "(.*?)/objects/info/alternates$"],
|
||||
["GET", 'get_text_file', "(.*?)/objects/info/http-alternates$"],
|
||||
["GET", 'get_info_packs', "(.*?)/objects/info/packs$"],
|
||||
["GET", 'get_text_file', "(.*?)/objects/info/[^/]*$"],
|
||||
["GET", 'get_loose_object', "(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"],
|
||||
["GET", 'get_pack_file', "(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"],
|
||||
["GET", 'get_idx_file', "(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"],
|
||||
]
|
||||
|
||||
def initialize(config = false)
|
||||
set_config(config)
|
||||
end
|
||||
|
||||
def set_config(config)
|
||||
@config = config || {}
|
||||
end
|
||||
|
||||
def set_config_setting(key, value)
|
||||
@config[key] = value
|
||||
end
|
||||
|
||||
def call(env)
|
||||
dup._call(env)
|
||||
end
|
||||
|
||||
def _call(env)
|
||||
@env = env
|
||||
@req = Rack::Request.new(env)
|
||||
|
||||
cmd, path, @reqfile, @rpc = match_routing
|
||||
|
||||
return render_method_not_allowed if cmd == 'not_allowed'
|
||||
return render_not_found unless cmd
|
||||
|
||||
@git = get_git(path)
|
||||
return render_not_found unless git.valid_repo?
|
||||
|
||||
self.method(cmd).call
|
||||
end
|
||||
|
||||
# ---------------------------------
|
||||
# actual command handling functions
|
||||
# ---------------------------------
|
||||
|
||||
# Uses chunked (streaming) transfer, otherwise response
|
||||
# blocks to calculate Content-Length header
|
||||
# http://en.wikipedia.org/wiki/Chunked_transfer_encoding
|
||||
|
||||
CRLF = "\r\n"
|
||||
|
||||
def service_rpc
|
||||
return render_no_access unless has_access?(@rpc, true)
|
||||
|
||||
input = read_body
|
||||
|
||||
@res = Rack::Response.new
|
||||
@res.status = 200
|
||||
@res["Content-Type"] = "application/x-git-%s-result" % @rpc
|
||||
@res["Transfer-Encoding"] = "chunked"
|
||||
@res["Cache-Control"] = "no-cache"
|
||||
|
||||
@res.finish do
|
||||
git.execute([@rpc, '--stateless-rpc', git.repo]) do |pipe|
|
||||
pipe.write(input)
|
||||
pipe.close_write
|
||||
|
||||
while block = pipe.read(8192) # 8KB at a time
|
||||
@res.write encode_chunk(block) # stream it to the client
|
||||
end
|
||||
|
||||
@res.write terminating_chunk
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def encode_chunk(chunk)
|
||||
size_in_hex = chunk.size.to_s(16)
|
||||
[size_in_hex, CRLF, chunk, CRLF].join
|
||||
end
|
||||
|
||||
def terminating_chunk
|
||||
[0, CRLF, CRLF].join
|
||||
end
|
||||
|
||||
def get_info_refs
|
||||
service_name = get_service_type
|
||||
return dumb_info_refs unless has_access?(service_name)
|
||||
|
||||
refs = git.execute([service_name, '--stateless-rpc', '--advertise-refs', git.repo])
|
||||
|
||||
@res = Rack::Response.new
|
||||
@res.status = 200
|
||||
@res["Content-Type"] = "application/x-git-%s-advertisement" % service_name
|
||||
hdr_nocache
|
||||
|
||||
@res.write(pkt_write("# service=git-#{service_name}\n"))
|
||||
@res.write(pkt_flush)
|
||||
@res.write(refs)
|
||||
|
||||
@res.finish
|
||||
end
|
||||
|
||||
def dumb_info_refs
|
||||
git.update_server_info
|
||||
send_file(@reqfile, "text/plain; charset=utf-8") do
|
||||
hdr_nocache
|
||||
end
|
||||
end
|
||||
|
||||
def get_info_packs
|
||||
# objects/info/packs
|
||||
send_file(@reqfile, "text/plain; charset=utf-8") do
|
||||
hdr_nocache
|
||||
end
|
||||
end
|
||||
|
||||
def get_loose_object
|
||||
send_file(@reqfile, "application/x-git-loose-object") do
|
||||
hdr_cache_forever
|
||||
end
|
||||
end
|
||||
|
||||
def get_pack_file
|
||||
send_file(@reqfile, "application/x-git-packed-objects") do
|
||||
hdr_cache_forever
|
||||
end
|
||||
end
|
||||
|
||||
def get_idx_file
|
||||
send_file(@reqfile, "application/x-git-packed-objects-toc") do
|
||||
hdr_cache_forever
|
||||
end
|
||||
end
|
||||
|
||||
def get_text_file
|
||||
send_file(@reqfile, "text/plain") do
|
||||
hdr_nocache
|
||||
end
|
||||
end
|
||||
|
||||
# ------------------------
|
||||
# logic helping functions
|
||||
# ------------------------
|
||||
|
||||
# some of this borrowed from the Rack::File implementation
|
||||
def send_file(reqfile, content_type)
|
||||
reqfile = File.join(git.repo, reqfile)
|
||||
return render_not_found unless File.exists?(reqfile)
|
||||
|
||||
return render_not_found unless reqfile == File.realpath(reqfile)
|
||||
|
||||
# reqfile looks legit: no path traversal, no leading '|'
|
||||
|
||||
@res = Rack::Response.new
|
||||
@res.status = 200
|
||||
@res["Content-Type"] = content_type
|
||||
@res["Last-Modified"] = File.mtime(reqfile).httpdate
|
||||
|
||||
yield
|
||||
|
||||
if size = File.size?(reqfile)
|
||||
@res["Content-Length"] = size.to_s
|
||||
@res.finish do
|
||||
File.open(reqfile, "rb") do |file|
|
||||
while part = file.read(8192)
|
||||
@res.write part
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
body = [File.read(reqfile)]
|
||||
size = Rack::Utils.bytesize(body.first)
|
||||
@res["Content-Length"] = size
|
||||
@res.write body
|
||||
@res.finish
|
||||
end
|
||||
end
|
||||
|
||||
def get_git(path)
|
||||
root = @config[:project_root] || Dir.pwd
|
||||
path = File.join(root, path)
|
||||
Grack::Git.new(@config[:git_path], path)
|
||||
end
|
||||
|
||||
def get_service_type
|
||||
service_type = @req.params['service']
|
||||
return false unless service_type
|
||||
return false if service_type[0, 4] != 'git-'
|
||||
service_type.gsub('git-', '')
|
||||
end
|
||||
|
||||
def match_routing
|
||||
cmd = nil
|
||||
path = nil
|
||||
|
||||
SERVICES.each do |method, handler, match, rpc|
|
||||
next unless m = Regexp.new(match).match(@req.path_info)
|
||||
|
||||
return ['not_allowed'] unless method == @req.request_method
|
||||
|
||||
cmd = handler
|
||||
path = m[1]
|
||||
file = @req.path_info.sub(path + '/', '')
|
||||
|
||||
return [cmd, path, file, rpc]
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def has_access?(rpc, check_content_type = false)
|
||||
if check_content_type
|
||||
conten_type = "application/x-git-%s-request" % rpc
|
||||
return false unless @req.content_type == conten_type
|
||||
end
|
||||
|
||||
return false unless ['upload-pack', 'receive-pack'].include?(rpc)
|
||||
|
||||
if rpc == 'receive-pack'
|
||||
return @config[:receive_pack] if @config.include?(:receive_pack)
|
||||
end
|
||||
|
||||
if rpc == 'upload-pack'
|
||||
return @config[:upload_pack] if @config.include?(:upload_pack)
|
||||
end
|
||||
|
||||
git.config_setting(rpc)
|
||||
end
|
||||
|
||||
def read_body
|
||||
if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
|
||||
Zlib::GzipReader.new(@req.body).read
|
||||
else
|
||||
@req.body.read
|
||||
end
|
||||
end
|
||||
|
||||
# --------------------------------------
|
||||
# HTTP error response handling functions
|
||||
# --------------------------------------
|
||||
|
||||
PLAIN_TYPE = { "Content-Type" => "text/plain" }
|
||||
|
||||
def render_method_not_allowed
|
||||
if @env['SERVER_PROTOCOL'] == "HTTP/1.1"
|
||||
[405, PLAIN_TYPE, ["Method Not Allowed"]]
|
||||
else
|
||||
[400, PLAIN_TYPE, ["Bad Request"]]
|
||||
end
|
||||
end
|
||||
|
||||
def render_not_found
|
||||
[404, PLAIN_TYPE, ["Not Found"]]
|
||||
end
|
||||
|
||||
def render_no_access
|
||||
[403, PLAIN_TYPE, ["Forbidden"]]
|
||||
end
|
||||
|
||||
|
||||
# ------------------------------
|
||||
# packet-line handling functions
|
||||
# ------------------------------
|
||||
|
||||
def pkt_flush
|
||||
'0000'
|
||||
end
|
||||
|
||||
def pkt_write(str)
|
||||
(str.size + 4).to_s(16).rjust(4, '0') + str
|
||||
end
|
||||
|
||||
# ------------------------
|
||||
# header writing functions
|
||||
# ------------------------
|
||||
|
||||
def hdr_nocache
|
||||
@res["Expires"] = "Fri, 01 Jan 1980 00:00:00 GMT"
|
||||
@res["Pragma"] = "no-cache"
|
||||
@res["Cache-Control"] = "no-cache, max-age=0, must-revalidate"
|
||||
end
|
||||
|
||||
def hdr_cache_forever
|
||||
now = Time.now().to_i
|
||||
@res["Date"] = now.to_s
|
||||
@res["Expires"] = (now + 31536000).to_s;
|
||||
@res["Cache-Control"] = "public, max-age=31536000";
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
module Grack
|
||||
VERSION = "2.0.2"
|
||||
end
|
|
@ -0,0 +1,264 @@
|
|||
require 'rack'
|
||||
require 'rack/test'
|
||||
require 'test/unit'
|
||||
require 'mocha'
|
||||
require 'digest/sha1'
|
||||
|
||||
require_relative '../lib/grack/server.rb'
|
||||
require_relative '../lib/grack/git.rb'
|
||||
require 'pp'
|
||||
|
||||
class GitHttpTest < Test::Unit::TestCase
|
||||
include Rack::Test::Methods
|
||||
|
||||
def example
|
||||
File.expand_path(File.dirname(__FILE__))
|
||||
end
|
||||
|
||||
def app
|
||||
config = {
|
||||
:project_root => example,
|
||||
:upload_pack => true,
|
||||
:receive_pack => true,
|
||||
}
|
||||
Grack::Server.new(config)
|
||||
end
|
||||
|
||||
def test_upload_pack_advertisement
|
||||
get "/example/info/refs?service=git-upload-pack"
|
||||
assert_equal 200, r.status
|
||||
assert_equal "application/x-git-upload-pack-advertisement", r.headers["Content-Type"]
|
||||
assert_equal "001e# service=git-upload-pack", r.body.split("\n").first
|
||||
assert_match 'multi_ack_detailed', r.body
|
||||
end
|
||||
|
||||
def test_no_access_wrong_content_type_up
|
||||
post "/example/git-upload-pack"
|
||||
assert_equal 403, r.status
|
||||
end
|
||||
|
||||
def test_no_access_wrong_content_type_rp
|
||||
post "/example/git-receive-pack"
|
||||
assert_equal 403, r.status
|
||||
end
|
||||
|
||||
def test_no_access_wrong_method_rcp
|
||||
get "/example/git-upload-pack"
|
||||
assert_equal 400, r.status
|
||||
end
|
||||
|
||||
def test_no_access_wrong_command_rcp
|
||||
post "/example/git-upload-packfile"
|
||||
assert_equal 404, r.status
|
||||
end
|
||||
|
||||
def test_no_access_wrong_path_rcp
|
||||
Grack::Git.any_instance.stubs(:valid_repo?).returns(false)
|
||||
post "/example-wrong/git-upload-pack"
|
||||
assert_equal 404, r.status
|
||||
end
|
||||
|
||||
def test_upload_pack_rpc
|
||||
Grack::Git.any_instance.stubs(:valid_repo?).returns(true)
|
||||
IO.stubs(:popen).returns(MockProcess.new)
|
||||
post "/example/git-upload-pack", {}, {"CONTENT_TYPE" => "application/x-git-upload-pack-request"}
|
||||
assert_equal 200, r.status
|
||||
assert_equal "application/x-git-upload-pack-result", r.headers["Content-Type"]
|
||||
end
|
||||
|
||||
def test_receive_pack_advertisement
|
||||
get "/example/info/refs?service=git-receive-pack"
|
||||
assert_equal 200, r.status
|
||||
assert_equal "application/x-git-receive-pack-advertisement", r.headers["Content-Type"]
|
||||
assert_equal "001f# service=git-receive-pack", r.body.split("\n").first
|
||||
assert_match 'report-status', r.body
|
||||
assert_match 'delete-refs', r.body
|
||||
assert_match 'ofs-delta', r.body
|
||||
end
|
||||
|
||||
def test_recieve_pack_rpc
|
||||
Grack::Git.any_instance.stubs(:valid_repo?).returns(true)
|
||||
IO.stubs(:popen).yields(MockProcess.new)
|
||||
post "/example/git-receive-pack", {}, {"CONTENT_TYPE" => "application/x-git-receive-pack-request"}
|
||||
assert_equal 200, r.status
|
||||
assert_equal "application/x-git-receive-pack-result", r.headers["Content-Type"]
|
||||
end
|
||||
|
||||
def test_info_refs_dumb
|
||||
get "/example/.git/info/refs"
|
||||
assert_equal 200, r.status
|
||||
end
|
||||
|
||||
def test_info_packs
|
||||
get "/example/.git/objects/info/packs"
|
||||
assert_equal 200, r.status
|
||||
assert_match /P pack-(.*?).pack/, r.body
|
||||
end
|
||||
|
||||
def test_loose_objects
|
||||
path, content = write_test_objects
|
||||
get "/example/.git/objects/#{path}"
|
||||
assert_equal 200, r.status
|
||||
assert_equal content, r.body
|
||||
remove_test_objects
|
||||
end
|
||||
|
||||
def test_pack_file
|
||||
path, content = write_test_objects
|
||||
get "/example/.git/objects/pack/pack-#{content}.pack"
|
||||
assert_equal 200, r.status
|
||||
assert_equal content, r.body
|
||||
remove_test_objects
|
||||
end
|
||||
|
||||
def test_index_file
|
||||
path, content = write_test_objects
|
||||
get "/example/.git/objects/pack/pack-#{content}.idx"
|
||||
assert_equal 200, r.status
|
||||
assert_equal content, r.body
|
||||
remove_test_objects
|
||||
end
|
||||
|
||||
def test_text_file
|
||||
get "/example/.git/HEAD"
|
||||
assert_equal 200, r.status
|
||||
assert_equal 41, r.body.size # submodules have detached head
|
||||
end
|
||||
|
||||
def test_no_size_avail
|
||||
File.stubs('size?').returns(false)
|
||||
get "/example/.git/HEAD"
|
||||
assert_equal 200, r.status
|
||||
assert_equal 46, r.body.size # submodules have detached head
|
||||
end
|
||||
|
||||
def test_config_upload_pack_off
|
||||
a1 = app
|
||||
a1.set_config_setting(:upload_pack, false)
|
||||
session = Rack::Test::Session.new(a1)
|
||||
session.get "/example/info/refs?service=git-upload-pack"
|
||||
assert_equal 404, session.last_response.status
|
||||
end
|
||||
|
||||
def test_config_receive_pack_off
|
||||
a1 = app
|
||||
a1.set_config_setting(:receive_pack, false)
|
||||
session = Rack::Test::Session.new(a1)
|
||||
session.get "/example/info/refs?service=git-receive-pack"
|
||||
assert_equal 404, session.last_response.status
|
||||
end
|
||||
|
||||
def test_config_bad_service
|
||||
get "/example/info/refs?service=git-receive-packfile"
|
||||
assert_equal 404, r.status
|
||||
end
|
||||
|
||||
def test_git_config_receive_pack
|
||||
app1 = Grack::Server.new({:project_root => example})
|
||||
app1.instance_variable_set(:@git, Grack::Git.new('git', example ))
|
||||
session = Rack::Test::Session.new(app1)
|
||||
git = Grack::Git
|
||||
git.any_instance.stubs(:config).with('http.receivepack').returns('')
|
||||
session.get "/example/info/refs?service=git-receive-pack"
|
||||
assert_equal 404, session.last_response.status
|
||||
|
||||
git.any_instance.stubs(:config).with('http.receivepack').returns('true')
|
||||
session.get "/example/info/refs?service=git-receive-pack"
|
||||
assert_equal 200, session.last_response.status
|
||||
|
||||
git.any_instance.stubs(:config).with('http.receivepack').returns('false')
|
||||
session.get "/example/info/refs?service=git-receive-pack"
|
||||
assert_equal 404, session.last_response.status
|
||||
end
|
||||
|
||||
def test_git_config_upload_pack
|
||||
app1 = Grack::Server.new({:project_root => example})
|
||||
# app1.instance_variable_set(:@git, Grack::Git.new('git', example ))
|
||||
session = Rack::Test::Session.new(app1)
|
||||
git = Grack::Git
|
||||
git.any_instance.stubs(:config).with('http.uploadpack').returns('')
|
||||
session.get "/example/info/refs?service=git-upload-pack"
|
||||
assert_equal 200, session.last_response.status
|
||||
|
||||
git.any_instance.stubs(:config).with('http.uploadpack').returns('true')
|
||||
session.get "/example/info/refs?service=git-upload-pack"
|
||||
assert_equal 200, session.last_response.status
|
||||
|
||||
git.any_instance.stubs(:config).with('http.uploadpack').returns('false')
|
||||
session.get "/example/info/refs?service=git-upload-pack"
|
||||
assert_equal 404, session.last_response.status
|
||||
end
|
||||
|
||||
def test_send_file
|
||||
app1 = app
|
||||
app1.instance_variable_set(:@git, Grack::Git.new('git', Dir.pwd))
|
||||
# Reject path traversal
|
||||
assert_equal 404, app1.send_file('tests/../tests', 'text/plain').first
|
||||
# Reject paths starting with '|', avoid File.read('|touch /tmp/pawned; ls /tmp')
|
||||
assert_equal 404, app1.send_file('|tests', 'text/plain').first
|
||||
end
|
||||
|
||||
def test_get_git
|
||||
# Guard against non-existent directories
|
||||
git1 = Grack::Git.new('git', 'foobar')
|
||||
assert_equal false, git1.valid_repo?
|
||||
# Guard against path traversal
|
||||
git2 = Grack::Git.new('git', '/../tests')
|
||||
assert_equal false, git2.valid_repo?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def r
|
||||
last_response
|
||||
end
|
||||
|
||||
def write_test_objects
|
||||
content = Digest::SHA1.hexdigest('gitrocks')
|
||||
base = File.join(File.expand_path(File.dirname(__FILE__)), 'example', '.git', 'objects')
|
||||
obj = File.join(base, '20')
|
||||
Dir.mkdir(obj) rescue nil
|
||||
file = File.join(obj, content[0, 38])
|
||||
File.open(file, 'w') { |f| f.write(content) }
|
||||
pack = File.join(base, 'pack', "pack-#{content}.pack")
|
||||
File.open(pack, 'w') { |f| f.write(content) }
|
||||
idx = File.join(base, 'pack', "pack-#{content}.idx")
|
||||
File.open(idx, 'w') { |f| f.write(content) }
|
||||
["20/#{content[0,38]}", content]
|
||||
end
|
||||
|
||||
def remove_test_objects
|
||||
content = Digest::SHA1.hexdigest('gitrocks')
|
||||
base = File.join(File.expand_path(File.dirname(__FILE__)), 'example', '.git', 'objects')
|
||||
obj = File.join(base, '20')
|
||||
file = File.join(obj, content[0, 38])
|
||||
pack = File.join(base, 'pack', "pack-#{content}.pack")
|
||||
idx = File.join(base, 'pack', "pack-#{content}.idx")
|
||||
File.unlink(file)
|
||||
File.unlink(pack)
|
||||
File.unlink(idx)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class MockProcess
|
||||
def initialize
|
||||
@counter = 0
|
||||
end
|
||||
|
||||
def write(data)
|
||||
end
|
||||
|
||||
def read(data = nil)
|
||||
''
|
||||
end
|
||||
|
||||
def eof?
|
||||
@counter += 1
|
||||
@counter > 1 ? true : false
|
||||
end
|
||||
|
||||
def close_write
|
||||
true
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue