Support git over ssh: hooks, support gitlab-shell api, UI, refactroing

This commit is contained in:
Alexander Machehin 2013-03-07 14:06:34 +06:00
parent 925281e1ae
commit d85039d5be
20 changed files with 183 additions and 114 deletions

View File

@ -0,0 +1,20 @@
$(document).ready(function() {
var git_protocol_btn = $('.git-protocol-selector.btn');
var http_url_in_help = $('.http_url');
var ssh_url_in_help = $('.ssh_url');
git_protocol_btn.on('click', function (e) {
var text = $('#'+$(this).val()).val();
git_protocol_btn.removeClass('active');
$('#url.name').val(text);
$(this).addClass('active');
if($(this).val() == 'http_url') {
ssh_url_in_help.addClass('hidden');
http_url_in_help.removeClass('hidden');
}
else {
http_url_in_help.addClass('hidden');
ssh_url_in_help.removeClass('hidden');
}
});
});

View File

@ -1836,3 +1836,27 @@ table#myTable thead tr.search th form.button_to div input {
#ui-dialog-title-extra-repos-and-build-lists-dialog {
font-size: 14px;
}
#description-top input.name {
width:280px;
margin-left:0;
}
.btn-group#clone-urls {
float:left;
margin-top:8px;
width:auto;
.git-protocol-selector {
width:auto;
height:23px;
padding: 0 3px;
}
}
#clone-urls > .git-protocol-selector:last-child {
margin: 0;
}
#clone-urls > .git-protocol-selector:first-child {
margin:0 0 0 10px;
}

View File

@ -1,9 +1,9 @@
# -*- encoding : utf-8 -*-
class Users::UsersController < Users::BaseController
skip_before_filter :authenticate_user!, :only => :allowed
skip_before_filter :authenticate_user!
before_filter :find_user_by_key, :only => [:allowed, :discover]
def allowed
key = SshKey.find(params[:key_id])
owner_name, project_name = params[:project].split '/'
project = Project.find_by_owner_and_name!(owner_name, project_name ? project_name : '!')
action = case params[:action_type]
@ -12,6 +12,21 @@ class Users::UsersController < Users::BaseController
when 'git-receive-pack'
then :write
end
render :inline => (!key.user.access_locked? && Ability.new(key.user).can?(action, project)) ? 'true' : 'false'
render :inline => (!@user.access_locked? && Ability.new(@user).can?(action, project)).to_s
end
def check
render :nothing => true
end
def discover
render :json => {:name => @user.name}.to_json
end
protected
def find_user_by_key
key = SshKey.find(params[:key_id])
@user = key.user
end
end

View File

@ -28,6 +28,10 @@ module ProjectsHelper
end
end
def git_ssh_repo_url(name)
"git@#{request.host}:#{name}.git"
end
def options_for_collaborators_roles_select
options_for_select(
Relation::ROLES.collect { |role|

View File

@ -74,29 +74,32 @@ class ActivityFeedObserver < ActiveRecord::Observer
change_type = record.change_type
branch_name = record.refname.split('/').last
if record.user # online update
#FIXME using oldrev is a hack (only for online edit).
last_commits, first_commiter = [[record.oldrev, record.message]], record.user
else
last_commits = record.project.repo.log(branch_name, nil).first(3)
first_commiter = User.find_by_email(last_commits[0].author.email) unless last_commits.blank?
last_commits = last_commits.collect do |commit| #:author => 'author'
[commit.sha, commit.message]
end
end
if change_type == 'delete'
kind = 'git_delete_branch_notification'
options = {:project_id => record.project.id, :project_name => record.project.name, :branch_name => branch_name,
:change_type => change_type, :project_owner => record.project.owner.uname}
else
if record.message # online update
#FIXME using oldrev is a hack (only for online edit).
last_commits, commits = [[record.newrev, record.message]], []
else
commits = record.project.repo.commits_between(record.oldrev, record.newrev)
last_commits = commits.last(3).collect { |commit| [commit.sha, commit.message] }
end
kind = 'git_new_push_notification'
options = {:project_id => record.project.id, :project_name => record.project.name, :last_commits => last_commits, :branch_name => branch_name,
:change_type => change_type, :user_email => record.project.repo.log(branch_name, nil).first.author.email,
:project_owner => record.project.owner.uname}
options.merge!({:user_id => first_commiter.id, :user_name => first_commiter.name}) if first_commiter
options = {:project_id => record.project.id, :project_name => record.project.name, :last_commits => last_commits.reverse,
:branch_name => branch_name, :change_type => change_type, :project_owner => record.project.owner.uname}
if commits.count > 3
commits = commits[0...-3]
options.merge!({:other_commits_count => commits.count, :other_commits => "#{commits[0].sha[0..9]}...#{commits[-1].sha[0..9]}"})
end
end
options.merge!({:user_id => record.user.id, :user_name => record.user.name, :user_email => record.user.email}) if record.user
record.project.admins.each do |recipient|
next if record.user && record.user.id == recipient.id
ActivityFeed.create!(
:user => recipient,
:kind => kind,

View File

@ -1,24 +1,31 @@
# -*- encoding : utf-8 -*-
class GitHook
ZERO = '0000000000000000000000000000000000000000'
@queue = :hook
attr_reader :repo, :newrev, :oldrev, :newrev_type, :oldrev_type, :refname,
:change_type, :rev, :rev_type, :refname_type, :owner, :project, :user, :message
include Resque::Plugins::Status
def initialize(owner_uname, repo, newrev, oldrev, ref, newrev_type, oldrev_type = nil, user = nil, message = nil)
@repo, @newrev, @oldrev, @refname, @newrev_type, @oldrev_type, @user, @message = repo, newrev, oldrev, ref, newrev_type, oldrev_type, user, message
def self.perform(*options)
self.process(*options)
end
def initialize(owner_uname, repo, newrev, oldrev, ref, newrev_type, user = nil, message = nil)
@repo, @newrev, @oldrev, @refname, @newrev_type, @user, @message = repo, newrev, oldrev, ref, newrev_type, user, message
if @owner = User.where(:uname => owner_uname).first || Group.where(:uname => owner_uname).first!
@project = @owner.own_projects.where(:name => repo).first!
end
@change_type = git_change_type
@change_type, @user = git_change_type, find_user(user)
git_revision_types
commit_type
end
def git_change_type
if @oldrev =~ /0+$/
if oldrev == ZERO
return 'create'
elsif @newrev =~ /0+$/
elsif newrev == ZERO
return 'delete'
else
return 'update'
@ -26,32 +33,31 @@ class GitHook
end
def git_revision_types
case @change_type
case change_type
when 'create', 'update'
@rev = @newrev
@rev_type = @newrev_type
@rev = newrev
when 'delete'
@rev = @oldrev
@rev_type = @oldrev_type
end
@rev = oldrev
end
@rev_type = newrev_type
end
def commit_type
if @refname =~ /refs\/tags\/*/ && @rev_type == 'commit'
if refname =~ /refs\/tags\/*/ && rev_type == 'commit'
# un-annotated tag
@refname_type= 'tag'
#~ short_refname=refname + '##refs/tags/'
elsif @refname =~ /refs\/tags\/*/ && @rev_type == 'tag'
elsif refname =~ /refs\/tags\/*/ && rev_type == 'tag'
# annotated tag
@refname_type="annotated tag"
#~ short_refname= refname + '##refs/tags/'
elsif @refname =~ /refs\/heads\/*/ && @rev_type == 'commit'
elsif refname =~ /refs\/heads\/*/ && rev_type == 'commit'
# branch
@refname_type= 'branch'
elsif @refname =~ /refs\/remotes\/*'/ && @rev_type == 'commit'
elsif refname =~ /refs\/remotes\/*'/ && rev_type == 'commit'
# tracking branch
@refname_type="tracking branch"
@short_refname= @refname + '##refs/remotes/'
@short_refname= refname + '##refs/remotes/'
else
# Anything else (is there anything else?)
@refname_type= "*** Unknown type of update to $refname (#{rev_type})"
@ -61,4 +67,17 @@ class GitHook
def self.process(*args)
ActivityFeedObserver.instance.after_create(args.size > 1 ? GitHook.new(*args) : args.first)
end
def find_user(user)
if user.blank?
# Local push
User.find_by_email(project.repo.commit(newrev).author.email) rescue nil
elsif user =~ /\Auser-\d+\Z/
# git push over http
User.find(user.gsub('user-', ''))
elsif user =~ /\Akey-\d+\Z/
# git push over ssh
SshKey.find_by_id(user.gsub('key-', '')).try(:user)
end
end
end

View File

@ -1,7 +1,10 @@
-user= User.where(:email => user_email).first || User.new(:email => user_email) if defined?(user_email)
.top
.image= link_to(image_tag(avatar_url(user, :small), :alt => 'avatar'), user_path(user)) if user.try(:persisted?)
.text
%span
= t('notifications.bodies.delete_branch', :branch_name => branch_name)
-_user_link = defined?(user_email) ? user_link(user, defined?(user_name) ? user_name : user_email) : nil
= t('notifications.bodies.delete_branch', :branch_name => branch_name, :user_link => _user_link).html_safe
= raw t("notifications.bodies.project", :project_link => link_to("#{project_owner}/#{project_name}", project_path(project_owner, project_name)) )
.both
%span.date= activity_feed.created_at

View File

@ -1,9 +1,9 @@
-user= User.where(:email => user_email).first || User.new(:email => user_email) if defined?(user_email)
.top
.image= link_to(image_tag(avatar_url(user, :small), :alt => 'avatar'), user_path(user)) if user.persisted?
.image= link_to(image_tag(avatar_url(user, :small), :alt => 'avatar'), user_path(user)) if user.try(:persisted?)
.text
%span
-_user_link = user_link(user, defined?(user_name) ? user_name : user_email)
-_user_link = defined?(user_email) ? user_link(user, defined?(user_name) ? user_name : user_email) : nil
= raw t("notifications.bodies.#{change_type}_branch", {:branch_name => branch_name, :user_link => _user_link})
= raw t("notifications.bodies.project", :project_link => link_to("#{project_owner}/#{project_name}", project_path(project_owner, project_name)) )
.both
@ -14,3 +14,7 @@
= link_to shortest_hash_id(commit[0]), commit_path(project_owner, project_name, commit[0])
= commit[1]
%br
- if defined? other_commits
-pluralize = t('layout.commits.pluralize').map {|base, title| title.to_s}
%br
=link_to t('notifications.bodies.more_commits', :count => other_commits_count, :commits => Russian.p(other_commits_count, *pluralize)), diff_path(project_owner, project_name, :diff => other_commits)

View File

@ -1,4 +1,4 @@
json.(project, :id, :name)
json.fullname project.name_with_owner
json.url api_v1_project_path(project.id, :format => :json)
json.git_url git_repo_url(project.git_repo_name)
json.git_url git_repo_url(project.name_with_owner)

View File

@ -1,4 +1,6 @@
- act = action_name.to_sym; contr = controller_name.to_sym; treeish = project.default_head(params[:treeish]); branch = @branch.try(:name) || project.default_head
-http_url = git_repo_url(project.name_with_owner)
-ssh_url = git_ssh_repo_url(project.name_with_owner)
#description-top
-if @commit
%ul.nav.zip
@ -10,8 +12,12 @@
- file_name = "#{@project.name}-#{treeish}"
%li=link_to "tar.gz", archive_path(project, file_name, 'tar.gz')
%li=link_to "zip", archive_path(project, file_name, 'zip')
= text_field_tag :url, git_repo_url(project.git_repo_name), :class => 'name', :spellcheck => 'false', :readonly => true
.btn-group#clone-urls
%button.git-protocol-selector.btn{:value => 'http_url', :class => current_user ? '' : 'active'} HTTP
%button.git-protocol-selector.btn{:value => 'ssh_url', :class => current_user ? 'active' : ''} SSH
=hidden_field_tag :http_url, http_url
=hidden_field_tag :ssh_url, ssh_url
= text_field_tag :url, (current_user ? ssh_url : http_url), :class => 'name', :spellcheck => 'false', :readonly => true
.git_help ?
.role= can?(:write, project) ? t("layout.read_write_access") : t("layout.read_access")
= render 'branch_select', :project => project if act != :tags
@ -19,12 +25,14 @@
#git_help_data
%p= t("layout.projects.git_help.cloning") + ":"
%p
%p~ "git clone #{git_repo_url(project.git_repo_name)} #{project.name}"
%p.http_url{:class => current_user ? 'hidden' : ''}~ "git clone #{http_url} #{project.name}"
%p.ssh_url{:class => current_user ? '' : 'hidden'}~ "git clone #{ssh_url} #{project.name}"
%p~ "cd #{project.name}"
%p
%p= t("layout.projects.git_help.remote") + ":"
%p
%p~ "git remote add #{project.name} #{git_repo_url(project.git_repo_name)}"
%p.http_url{:class => current_user ? 'hidden' : ''}~ "git remote add #{project.name} #{http_url}"
%p.ssh_url{:class => current_user ? '' : 'hidden'}~ "git remote add #{project.name} #{ssh_url}"
%p~ "git fetch #{project.name}"
%p~ "git checkout -b my-local-tracking-branch #{project.name}/master_or_other_branch"
.project-tabnav

View File

@ -39,7 +39,7 @@
%h3= t("layout.projects.create_repository")
%p
%code
= "git clone #{git_repo_url(@project.git_repo_name)}"
= "git clone #{git_repo_url(@project.name_with_owner)}"
%br/
= "cd #{@project.name}"
%br/
@ -58,7 +58,7 @@
%code
cd existing_git_repo
%br/
= "git remote add origin #{git_repo_url(@project.git_repo_name)}"
= "git remote add origin #{git_repo_url(@project.name_with_owner)}"
%br/
git push -u origin master

View File

@ -1,12 +0,0 @@
#!/bin/bash
# This file was placed here by rosa-team. It makes sure that your pushed commits will be processed properly.
pwd=`pwd`
reponame=`basename $pwd .git`
owner=`basename \`dirname $pwd\``
while read oldrev newrev ref
do
newrev_type=$(git cat-file -t $newrev 2> /dev/null)
oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null)

View File

@ -3,3 +3,7 @@ en:
commits:
subscribe_btn: Enable notifications.
unsubscribe_btn: Disable notifications.
pluralize:
commit: commit
commits: commits
commits2: commits

View File

@ -3,3 +3,7 @@ ru:
commits:
subscribe_btn: Включить уведомления.
unsubscribe_btn: Выключить уведомления.
pluralize:
commit: коммит
commits: коммита
commits2: коммитов

View File

@ -35,7 +35,7 @@ en:
wiki_new_commit_notification: '%{user_link} has been updated %{history_link}'
invite_approve_notification: Invite to ABF
project: in project %{project_link}
delete_branch: Branch %{branch_name} has been deleted
delete_branch: '%{user_link} deleted a %{branch_name}'
create_branch: '%{user_link} created a new branch %{branch_name}'
update_branch: '%{user_link} pushed to branch %{branch_name}'
build_task: 'Build <a href="%{task_link}">task #%{task_num}</a>'
@ -44,6 +44,7 @@ en:
success: completed successfully
failed: completed with error "%{error}"
pending: build is pending
more_commits: '%{count} more %{commits}'
footers:
support_team: Support team «ROSA Build System».

View File

@ -36,7 +36,7 @@ ru:
invite_approve_notification: Приглашение в ABF
project: в проекте %{project_link}
delete_branch: Удалена ветка %{branch_name}
delete_branch: '%{user_link} удалил ветку %{branch_name}'
create_branch: '%{user_link} создал новую ветку %{branch_name}'
update_branch: '%{user_link} внес изменения в ветку %{branch_name}'
build_task: 'Сборочное <a href="%{task_link}">задание №%{task_num}</a>'
@ -45,6 +45,7 @@ ru:
success: успешно собрано
failed: завершилось с ошибкой "%{error}"
pending: ожидает сборки
more_commits: 'еще %{count} %{commits} '
footers:
support_team: Команда поддержки «ROSA Build System».

View File

@ -81,7 +81,7 @@ Rosa::Application.routes.draw do
resources :product_build_lists, :only => [:index, :show, :destroy, :create] do
put :cancel, :on => :member
end
resources :ssh_keys, :only => [:index, :create, :destroy]
#resources :ssh_keys, :only => [:index, :create, :destroy]
end
end
@ -185,6 +185,10 @@ Rosa::Application.routes.draw do
end
scope :module => 'users' do
get '/settings/ssh_keys' => 'ssh_keys#index', :as => :ssh_keys
post '/settings/ssh_keys' => 'ssh_keys#create'
delete '/settings/ssh_keys/:id' => 'ssh_keys#destroy', :as => :ssh_key
resources :settings, :only => [] do
collection do
get :profile
@ -196,8 +200,10 @@ Rosa::Application.routes.draw do
end
end
resources :register_requests, :only => [:new, :create], :format => /ru|en/ #view support only two languages
resources :ssh_keys, :only => [:index, :create, :destroy]
get '/allowed' => 'users#allowed'
get '/allowed' => 'users#allowed'
get '/check' => 'users#check'
get '/discover' => 'users#discover'
end
scope :module => 'groups' do

View File

@ -26,11 +26,7 @@ module Modules
end
def path
build_path(git_repo_name)
end
def git_repo_name
File.join owner.uname, name
build_path(name_with_owner)
end
def versions
@ -57,7 +53,7 @@ module Modules
index.add(path, data)
if sha1 = index.commit(message, :parents => [parent], :actor => actor, :last_tree => parent.tree.id, :head => head)
Project.process_hook(owner.uname, name, "refs/heads/#{sha1}", sha1, head, 'commit', 'commit', options[:actor], message)
Resque.enqueue(GitHook, owner.uname, name, sha1, sha1, "refs/heads/#{head}", 'commit', "user-#{options[:actor].id}", message)
end
sha1
end
@ -134,22 +130,9 @@ module Modules
end
def write_hook
is_production = Rails.env == "production"
hook = File.join(::Rails.root.to_s, 'tmp', "post-receive-hook")
FileUtils.cp(File.join(::Rails.root.to_s, 'bin', "post-receive-hook.partial"), hook)
File.open(hook, 'a') do |f|
s = "\n /bin/bash -l -c \"cd #{is_production ? '/srv/rosa_build/current' : Rails.root.to_s} && #{is_production ? 'RAILS_ENV=production' : ''} bundle exec rake hook:enqueue[$owner,$reponame,$newrev,$oldrev,$ref,$newrev_type,$oldrev_type]\""
s << " > /dev/null 2>&1" if is_production
s << "\ndone\n"
f.write(s)
f.chmod(0755)
end
hook = "/home/#{APP_CONFIG['shell_user']}/gitlab-shell/hooks/post-receive"
hook_file = File.join(path, 'hooks', 'post-receive')
FileUtils.cp(hook, hook_file)
FileUtils.rm_rf(hook)
rescue Exception # FIXME
FileUtils.ln_sf hook, hook_file
end
def get_actor(actor = nil)
@ -170,8 +153,8 @@ module Modules
end
module ClassMethods
def process_hook(owner_uname, repo, newrev, oldrev, ref, newrev_type, oldrev_type, user = nil, message = nil)
rec = GitHook.new(owner_uname, repo, newrev, oldrev, ref, newrev_type, oldrev_type, user, message)
def process_hook(owner_uname, repo, newrev, oldrev, ref, newrev_type, user = nil, message = nil)
rec = GitHook.new(owner_uname, repo, newrev, oldrev, ref, newrev_type, user, message)
ActivityFeedObserver.instance.after_create rec
end
end

View File

@ -13,7 +13,8 @@ module Grack
return ::Rack::Auth::Basic.new(@app) do |u, p|
user = User.auth_by_token_or_login_pass(u, p) and
ability = ::Ability.new(user) and ability.can?(action, project) # project.members.include?(user)
ability = ::Ability.new(user) and ability.can?(action, project) and
ENV['GL_ID'] = "user-#{user.id}"
end.call(env) unless project.public? and read? # need auth
end
@app.call(env) # next app in stack

View File

@ -1,25 +1,16 @@
namespace :hook do
desc "Inserting hook to all repos"
task :install => :environment do
is_production = ENV['RAILS_ENV'] == 'production'
say "Generate temporary file..."
hook = File.join(::Rails.root.to_s, 'tmp', "post-receive-hook")
FileUtils.cp(File.join(::Rails.root.to_s, 'bin', "post-receive-hook.partial"), hook)
File.open(hook, 'a') do |f|
s = "\n /bin/bash -l -c \"cd #{is_production ? '/srv/rosa_build/current' : Rails.root.to_s} && #{is_production ? 'RAILS_ENV=production' : ''} bundle exec rake hook:enqueue[$owner,$reponame,$newrev,$oldrev,$ref,$newrev_type,$oldrev_type]\""
s << " > /dev/null 2>&1" if is_production
s << "\ndone\n"
f.write(s)
f.chmod(0755)
end
hook = "/home/#{APP_CONFIG['shell_user']}/gitlab-shell/hooks/post-receive"
say "Install process.."
count = 0
projects = ENV['project_id'] ? Project.where(:id => eval(ENV['project_id'])) : Project
projects.where('created_at >= ?', Time.now.ago(ENV['period'] ? eval(ENV['period']) : 100.years)).each do |project|
count, projects = 0, Project.scoped
projects = projects.where(:id => ENV['PROJECT_ID']) if ENV['PROJECT_ID']
projects.each do |project|
next unless Dir.exist? project.path
hook_file = File.join(project.path, 'hooks', 'post-receive')
FileUtils.rm_rf hook_file
begin
FileUtils.copy_entry(hook, hook_file, false, false, true)
FileUtils.ln_sf(hook, hook_file)
count = count + 1
rescue Exception => e
say "----\nCatching exception with project #{project.id}"
@ -28,24 +19,14 @@ namespace :hook do
end
end
say "Writing to #{count.to_s} repo(s)"
say "Removing temporary file"
FileUtils.rm_rf(hook)
end
desc 'Enqueue hook process'
task :enqueue, :owner, :reponame, :newrev, :oldrev, :ref, :newrev_type, :oldrev_type do |t, args|
# require 'resque'
require './app/models/git_hook'
PerformLater.config.enabled = true unless Rails.env.test?
GitHook.perform_later!(:hook, :process, *args.to_hash.values)
end
desc "remove git hook from all repos"
task :remove => :environment do
say "process.."
count = 0
projects = ENV['project_id'] ? Project.where(:id => eval(ENV['project_id'])) : Project
projects.where('created_at >= ?', Time.now.ago(ENV['period'] ? eval(ENV['period']) : 100.years)).each do |project|
count, projects = 0, Project.scoped
projects = projects.where(:id => ENV['PROJECT_ID']) if ENV['PROJECT_ID']
projects.each do |project|
FileUtils.rm_rf File.join(project.path, 'hooks', 'post-receive')
count = count + 1
end