Commit eca823c1 authored by Nihad Abbasov's avatar Nihad Abbasov

Merge branch 'master' into api

parents 024e0348 8b7e404b
......@@ -6,6 +6,7 @@ log/*.log
tmp/
.sass-cache/
coverage/*
backups/*
*.swp
public/uploads/
.rvmrc
......
v 2.7.0
- Issue Labels
- Inline diff
- Git HTTP
- API
- UI improved
- System hooks
- UI improved
- Dashboard events endless scroll
- Source perfomance increased
v 2.6.0
- UI polished
......
......@@ -7,7 +7,7 @@ gem "sqlite3"
gem "mysql2"
# Auth
gem "devise", "~> 1.5"
gem "devise", "~> 2.1.0"
# GITLAB patched libs
gem "grit", :git => "https://github.com/gitlabhq/grit.git", :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837"
......@@ -71,7 +71,6 @@ group :development, :test do
gem "awesome_print"
gem "database_cleaner"
gem "launchy"
gem "webmock"
end
group :test do
......@@ -82,4 +81,5 @@ group :test do
gem "shoulda-matchers"
gem 'email_spec'
gem 'resque_spec'
gem "webmock"
end
......@@ -148,10 +148,11 @@ GEM
nokogiri (>= 1.5.0)
daemons (1.1.8)
database_cleaner (0.8.0)
devise (1.5.3)
devise (2.1.2)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.0.3)
warden (~> 1.1)
orm_adapter (~> 0.1)
railties (~> 3.1)
warden (~> 1.2.1)
diff-lcs (1.1.3)
drapper (0.8.4)
email_spec (1.2.1)
......@@ -225,7 +226,7 @@ GEM
omniauth (1.1.0)
hashie (~> 1.2)
rack
orm_adapter (0.0.7)
orm_adapter (0.3.0)
polyglot (0.3.3)
posix-spawn (0.3.6)
pry (0.9.9.6)
......@@ -356,7 +357,7 @@ GEM
raindrops (~> 0.7)
vegas (0.1.11)
rack (>= 1.0.0)
warden (1.2.0)
warden (1.2.1)
rack (>= 1.0)
webmock (1.8.7)
addressable (>= 2.2.7)
......@@ -383,7 +384,7 @@ DEPENDENCIES
colored
cucumber-rails
database_cleaner
devise (~> 1.5)
devise (~> 2.1.0)
drapper
email_spec
ffaker
......
......@@ -12,6 +12,7 @@
//= require jquery.cookie
//= require jquery.endless-scroll
//= require jquery.highlight
//= require jquery.waitforimages
//= require bootstrap-modal
//= require modernizr
//= require chosen-jquery
......@@ -20,10 +21,26 @@
//= require_tree .
$(document).ready(function(){
$(".one_click_select").live("click", function(){
$(this).select();
});
$('body').on('ajax:complete, ajax:beforeSend, submit', 'form', function(e){
var buttons = $('[type="submit"]', this);
switch( e.type ){
case 'ajax:beforeSend':
case 'submit':
buttons.attr('disabled', 'disabled');
break;
case ' ajax:complete':
default:
buttons.removeAttr('disabled');
break;
}
})
$(".account-box").mouseenter(showMenu);
$(".account-box").mouseleave(resetMenu);
......@@ -97,3 +114,8 @@ function showDiff(link) {
return _chosen.apply(this, [default_options]);
}})
})(jQuery);
function ajaxGet(url) {
$.ajax({type: "GET", url: url, dataType: "script"});
}
......@@ -73,4 +73,25 @@ function issuesPage(){
$("#milestone_id, #assignee_id, #label_name").on("change", function(){
$(this).closest("form").submit();
});
$('body').on('ajax:success', '.close_issue, .reopen_issue, #new_issue', function(){
var t = $(this),
totalIssues,
reopen = t.hasClass('reopen_issue'),
newIssue = false;
if( this.id == 'new_issue' ){
newIssue = true;
}
$('.issue_counter, #new_issue').each(function(){
var issue = $(this);
totalIssues = parseInt( $(this).html(), 10 );
if( newIssue || ( reopen && issue.closest('.main_menu').length ) ){
$(this).html( totalIssues+1 );
}else {
$(this).html( totalIssues-1 );
}
});
});
}
......@@ -25,11 +25,11 @@ init:
$(this).closest('li').fadeOut(); });
$("#new_note").live("ajax:before", function(){
$("#submit_note").attr("disabled", "disabled");
$(".submit_note").attr("disabled", "disabled");
})
$("#new_note").live("ajax:complete", function(){
$("#submit_note").removeAttr("disabled");
$(".submit_note").removeAttr("disabled");
})
$("#note_note").live("focus", function(){
......
......@@ -604,7 +604,11 @@ li.note {
border-style: solid;
border-width: 1px;
@include border-radius(4px);
min-height:42px;
min-height:22px;
.avatar {
width:24px;
}
}
.supp_diff_link,
......
......@@ -202,6 +202,10 @@ a:focus {
color:$style_color;
}
.nav-tabs > .active > a {
font-weight:bold;
}
/** COLORS **/
.cgray { color:gray; }
.cred { color:#D12F19; }
......@@ -209,6 +213,7 @@ a:focus {
.cblack { color:#111; }
.cdark { color:#444 }
.cwhite { color:#fff !important }
.bgred { background: #F2DEDE !important}
/** COMMON STYLES **/
.left {
......@@ -299,9 +304,24 @@ table.no-borders {
}
.event_label {
background: #FCEEC1;
padding: 2px 2px 0;
font-family: monospace;
@extend .label;
background-color: #999;
&.pushed {
background-color: #3A87AD;
}
&.opened {
background-color: #468847;
}
&.closed {
background-color: #B94A48;
}
&.merged {
background-color: #2A2;
}
}
img.avatar {
......@@ -425,9 +445,10 @@ form {
*/
.ui-box {
background:#F9F9F9;
margin-bottom: 40px;
margin-bottom: 25px;
@include round-borders-all(4px);
border-color: #CCC;
@include solid_shade;
ul {
margin:0;
......@@ -443,6 +464,13 @@ form {
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
&.small {
line-height: 28px;
font-size: 14px;
line-height:28px;
text-shadow: 0 1px 1px white;
}
form {
padding:9px 0;
margin:0px;
......@@ -511,6 +539,7 @@ form {
table.admin-table {
@extend .table-bordered;
@extend .zebra-striped;
@include solid_shade;
th {
border-color: #CCC;
border-bottom: 1px solid #bbb;
......@@ -568,6 +597,8 @@ ul.breadcrumb {
@extend .prepend-top-20;
@extend .append-bottom-20;
border-width:1px;
@include solid_shade;
img { max-width: 100%; }
......@@ -624,13 +655,166 @@ p {
h3.page_title {
color:#456;
font-size:20px;
font-weight: 600;
font-weight: normal;
line-height: 28px;
}
pre.logs {
.log {
font-size:12px;
line-height:18px;
/**
* File content holder
*
*/
.file_holder {
border:1px solid #CCC;
margin-bottom:1em;
@include solid_shade;
.file_title {
border-bottom: 1px solid #bbb;
background:#eee;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
margin: 0;
font-weight: normal;
font-weight: bold;
text-align: left;
color: #666;
padding: 9px 10px;
height:18px;
.options {
float:right;
margin-top: -5px;
}
.file_name {
color:$style_color;
font-size:14px;
text-shadow: 0 1px 1px #fff;
small {
color:#999;
font-size:13px;
}
}
}
.file_content {
background:#fff;
font-size: 11px;
&.wiki {
font-size: 13px;
code {
padding:0 4px;
}
padding:20px;
h1, h2 {
line-height: 46px;
}
h3, h4 {
line-height: 40px;
}
}
&.image_file {
background:#eee;
text-align:center;
img {
padding:100px;
max-width:300px;
}
}
&.blob_file {
}
/**
* Blame file
*/
&.blame {
tr {
border-bottom: 1px solid #eee;
}
td {
padding:5px;
}
.author,
.blame_commit {
background:#f5f5f5;
vertical-align:top;
}
.lines {
pre {
padding:0;
margin:0;
background:none;
border:none;
}
}
}
&.logs {
background:#eee;
max-height: 700px;
overflow-y: auto;
ol {
margin-left:40px;
padding: 10px 0;
border-left: 1px solid #CCC;
margin-bottom:0;
background: white;
li {
color:#888;
p {
margin:0;
color:#333;
line-height:24px;
padding-left: 10px;
}
&:hover {
background:$hover;
}
}
}
}
/**
* Code file
*/
&.code {
padding:0;
td.code {
width: 100%;
.highlight {
margin-left: 55px;
overflow:auto;
overflow-y:hidden;
}
}
.highlight pre {
white-space: pre;
word-wrap:normal;
}
table.highlighttable {
border: none;
}
body.project-page table.highlighttable td { border: none }
table.highlighttable tr:hover { background:none;}
table.highlighttable pre{
line-height:16px !important;
font-size:12px !important;
}
table.highlighttable .linenodiv pre {
text-align: right;
padding-right: 4px;
color:#666;
}
}
}
}
......@@ -96,7 +96,7 @@ header {
*/
.search {
float: right;
margin-right: 55px;
margin-right: 50px;
.search-input {
@extend .span2;
......@@ -126,10 +126,10 @@ header {
cursor: pointer;
img {
border-radius: 4px;
right: 0px;
right: 5px;
position: absolute;
width: 33px;
height: 33px;
width: 31px;
height: 31px;
display: block;
top: 0;
&:after {
......
......@@ -31,6 +31,12 @@ $hover: #FDF5D9;
box-shadow: 0 0 3px #ddd;
}
@mixin solid_shade {
-moz-box-shadow: 0 0 0 3px #eee;
-webkit-box-shadow: 0 0 0 3px #eee;
box-shadow: 0 0 0 3px #eee;
}
@mixin border-radius($radius) {
-moz-border-radius: $radius;
-webkit-border-radius: $radius;
......@@ -136,7 +142,7 @@ $hover: #FDF5D9;
/**
* Code (files list) styles. Browsing project files there
*/
@import "tree.scss";
@import "sections/tree.scss";
/**
* This file represent notes(comments) styles
......
......@@ -63,18 +63,22 @@ p.notify_controls span{
tr.line_notes_row {
border-bottom:1px solid #DDD;
border-left: 7px solid #2A79A3;
&.reply {
background:#eee;
border-left: 7px solid #2A79A3;
border-top:1px solid #ddd;
td {
padding:7px 10px;
}
a.line_note_reply_link {
@include round-borders-all(4px);
border-color:#aaa;
background: #bbb;
padding: 3px 20px;
padding: 3px 10px;
margin-left:5px;
color: white;
background: #2A79A3;
border-color: #2A79A3;
}
}
ul {
......@@ -95,6 +99,9 @@ tr.line_notes_row {
td {
border-bottom:1px solid #ddd;
}
.actions {
margin:0;
}
}
td .line_note_link {
......
......@@ -101,18 +101,21 @@
margin:50px;
padding:1px;
max-width:400px;
}
&.diff_image_removed {
img {
&.diff_image_removed {
border: 1px solid #C00;
}
}
&.diff_image_added {
img {
&.diff_image_added {
border: 1px solid #0C0;;
}
}
&.img_compared {
img {
max-width:300px;
}
}
}
}
......
......@@ -82,3 +82,15 @@
}
}
}
li.merge_request {
padding:7px 10px;
img.avatar {
width: 32px;
margin-top: 4px;
}
p {
padding: 0px;
padding-bottom: 2px;
}
}
......@@ -25,103 +25,6 @@
}
}
/** FILE CONTENT VIEW **/
.view_file_content{
.old_line, .new_line {
background:#ECECEC;
color:#777;
width:15px;
float:left;
padding: 0px 10px;
border-right: 1px solid #ccc;
}
.old_line{
display:none;
}
}
.view_file .view_file_header,
.diff_file .diff_file_header {
border-bottom: 1px solid #bbb;
background:#eee;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
margin: 0;
font-weight: normal;
font-weight: bold;
text-align: left;
color: #666;
padding: 9px 10px;
height:18px;
.options {
float:right;
margin-top: -5px;
}
.file_name {
color:$style_color;
font-size:14px;
text-shadow: 0 1px 1px #fff;
small {
color:#999;
font-size:13px;
}
}
}
.view_file {
border:1px solid #CCC;
margin-bottom:1em;
.view_file_content {
background:#fff;
color:#514721;
font-size: 11px;
}
.view_file_content_image {
background:#eee;
text-align:center;
img {
padding:100px;
max-width:300px;
}
}
}
td.code {
width: 100%;
.highlight {
margin-left: 55px;
overflow:auto;
overflow-y:hidden;
}
}
.highlight pre {
white-space: pre;
word-wrap:normal;
}
table.highlighttable {
border: none;
}
body.project-page table.highlighttable td { border: none }
table.highlighttable tr:hover { background:none;}
table.highlighttable pre{
line-height:16px !important;
font-size:12px !important;
}
table.highlighttable .linenodiv pre {
text-align: right;
padding-right: 4px;
color:#666;
}
#tree-slider {
@include border-radius(0);
.tree-item {
......@@ -152,7 +55,7 @@
#tree-slider {
@include shade;
@include solid_shade;
width:100%;
border-color:#ccc;
......@@ -183,21 +86,6 @@
color:#333;
}
#tree-content-holder .view_file{
@include shade;
}
#tree-readme-holder .readme {
@include shade;
margin-bottom:20px;
h1, h2 {
line-height: 56px;
}
h3, h4 {
line-height: 46px;
}
}
a.tree-commit-link {
color: #666;
&:hover {
......@@ -206,27 +94,3 @@
}
}
.blame_file {
.view_file_content {
tr {
border-bottom: 1px solid #eee;
}
td {
padding:5px;
}
.author,
.commit {
background:#f5f5f5;
vertical-align:top;
}
.lines {
pre {
padding:0;
margin:0;
background:none;
border:none;
}
}
}
}
......@@ -70,8 +70,7 @@
}
}
.separator {
border-color:#444;
background:#31363E;
display:none;
}
}
......
class BaseContext
attr_accessor :project, :current_user, :params
def initialize(project, user, params)
@project, @current_user, @params = project, user, params.dup
end
end
class CommitLoad < BaseContext
def execute
result = {
:commit => nil,
:suppress_diff => false,
:line_notes => [],
:notes_count => 0,
:note => nil
}
commit = project.commit(params[:id])
if commit
commit = CommitDecorator.decorate(commit)
line_notes = project.commit_line_notes(commit)
result[:suppress_diff] = true if commit.diffs.size > 200 && !params[:force_show_diff]
result[:commit] = commit
result[:note] = project.build_commit_note(commit)
result[:line_notes] = line_notes
result[:notes_count] = line_notes.count + project.commit_notes(commit).count
end
result
end
end
class MergeRequestsLoad < BaseContext
def execute
type = params[:f].to_i
merge_requests = project.merge_requests
merge_requests = case type
when 1 then merge_requests
when 2 then merge_requests.closed
when 3 then merge_requests.opened.assigned(current_user)
else merge_requests.opened
end.page(params[:page]).per(20)
merge_requests.includes(:author, :project).order("closed, created_at desc")
end
end
class NotesLoad < BaseContext
def execute
target_type = params[:target_type]
target_id = params[:target_id]
first_id = params[:first_id]
last_id = params[:last_id]
@notes = case target_type
when "commit"
then project.commit_notes(project.commit(target_id)).fresh.limit(20)
when "snippet"
then project.snippets.find(target_id).notes
when "wall"
then project.common_notes.order("created_at DESC").fresh.limit(50)
when "issue"
then project.issues.find(target_id).notes.inc_author.order("created_at DESC").limit(20)
when "merge_request"
then project.merge_requests.find(target_id).notes.inc_author.order("created_at DESC").limit(20)
end
@notes = if last_id
@notes.where("id > ?", last_id)
elsif first_id
@notes.where("id < ?", first_id)
else
@notes
end
end
end
class Admin::HooksController < ApplicationController
layout "admin"
before_filter :authenticate_user!
before_filter :authenticate_admin!
def index
@hooks = SystemHook.all
@hook = SystemHook.new
end
def create
@hook = SystemHook.new(params[:hook])
if @hook.save
redirect_to admin_hooks_path, notice: 'Hook was successfully created.'
else
@hooks = SystemHook.all
render :index
end
end
def destroy
@hook = SystemHook.find(params[:id])
@hook.destroy
redirect_to admin_hooks_path
end
def test
@hook = SystemHook.find(params[:hook_id])
data = {
event_name: "project_create",
name: "Ruby",
path: "ruby",
project_id: 1,
owner_name: "Someone",
owner_email: "example@gitlabhq.com"
}
@hook.execute(data)
redirect_to :back
end
end
class Admin::MailerController < ApplicationController
layout "admin"
before_filter :authenticate_user!
before_filter :authenticate_admin!
def preview
end
def preview_note
@note = Note.first
@user = @note.author
@project = @note.project
case params[:type]
when "Commit" then
@commit = @project.commit
render :file => 'notify/note_commit_email', :layout => 'notify'
when "Issue" then
@issue = Issue.first
render :file => 'notify/note_issue_email', :layout => 'notify'
else
render :file => 'notify/note_wall_email', :layout => 'notify'
end
rescue
render :text => "Preview not available"
end
def preview_user_new
@user = User.first
@password = "DHasJKDHAS!"
render :file => 'notify/new_user_email', :layout => 'notify'
rescue
render :text => "Preview not available"
end
def preview_issue_new
@issue = Issue.first
@user = @issue.assignee
@project = @issue.project
render :file => 'notify/new_issue_email', :layout => 'notify'
rescue
render :text => "Preview not available"
end
end
......@@ -6,7 +6,7 @@ class Admin::ProjectsController < ApplicationController
def index
@admin_projects = Project.scoped
@admin_projects = @admin_projects.search(params[:name]) if params[:name].present?
@admin_projects = @admin_projects.page(params[:page])
@admin_projects = @admin_projects.page(params[:page]).per(20)
end
def show
......@@ -72,6 +72,6 @@ class Admin::ProjectsController < ApplicationController
@admin_project = Project.find_by_code(params[:id])
@admin_project.destroy
redirect_to admin_projects_url
redirect_to admin_projects_url, notice: 'Project was successfully deleted.'
end
end
......@@ -52,7 +52,7 @@ class ApplicationController < ActionController::Base
def layout_by_resource
if devise_controller?
"devise"
"devise_layout"
else
"application"
end
......
......@@ -26,43 +26,31 @@ class CommitsController < ApplicationController
end
def show
@commit = project.commit(params[:id])
git_not_found! and return unless @commit
@commit = CommitDecorator.decorate(@commit)
@note = @project.build_commit_note(@commit)
@comments_allowed = true
@line_notes = project.commit_line_notes(@commit)
@notes_count = @line_notes.count + project.commit_notes(@commit).count
if @commit.diffs.size > 200 && !params[:force_show_diff]
@suppress_diff = true
result = CommitLoad.new(project, current_user, params).execute
@commit = result[:commit]
if @commit
@suppress_diff = result[:suppress_diff]
@note = result[:note]
@line_notes = result[:line_notes]
@notes_count = result[:notes_count]
@comments_allowed = true
else
return git_not_found!
end
rescue Grit::Git::GitTimeout
render "huge_commit"
end
def compare
first = project.commit(params[:to].try(:strip))
last = project.commit(params[:from].try(:strip))
result = Commit.compare(project, params[:from], params[:to])
@diffs = []
@commits = []
@commits = result[:commits]
@commit = result[:commit]
@diffs = result[:diffs]
@line_notes = []
if first && last
commits = [first, last].sort_by(&:created_at)
younger = commits.first
older = commits.last
@commits = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)}
@diffs = project.repo.diff(younger.id, older.id) rescue []
@commit = Commit.new(older)
end
end
def patch
......
......@@ -2,15 +2,13 @@ class DashboardController < ApplicationController
respond_to :html
def index
@projects = current_user.projects.includes(:events).order("events.created_at DESC")
@projects = @projects.page(params[:page]).per(40)
@events = Event.where(:project_id => current_user.projects.map(&:id)).recent.limit(20)
@projects = current_user.projects_with_events.page(params[:page]).per(40)
@events = Event.recent_for_user(current_user).limit(20).offset(params[:offset] || 0)
@last_push = current_user.recent_push
respond_to do |format|
format.html
format.js
format.atom { render :layout => false }
end
end
......
......@@ -11,24 +11,24 @@ class HooksController < ApplicationController
respond_to :html
def index
@hooks = @project.web_hooks.all
@hook = WebHook.new
@hooks = @project.hooks.all
@hook = ProjectHook.new
end
def create
@hook = @project.web_hooks.new(params[:hook])
@hook = @project.hooks.new(params[:hook])
@hook.save
if @hook.valid?
redirect_to project_hooks_path(@project)
else
@hooks = @project.web_hooks.all
@hooks = @project.hooks.all
render :index
end
end
def test
@hook = @project.web_hooks.find(params[:id])
@hook = @project.hooks.find(params[:id])
commits = @project.commits(@project.default_branch, nil, 3)
data = @project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{@project.default_branch}", current_user)
@hook.execute(data)
......@@ -37,7 +37,7 @@ class HooksController < ApplicationController
end
def destroy
@hook = @project.web_hooks.find(params[:id])
@hook = @project.hooks.find(params[:id])
@hook.destroy
redirect_to project_hooks_path(@project)
......
......@@ -24,16 +24,7 @@ class MergeRequestsController < ApplicationController
def index
@merge_requests = @project.merge_requests
@merge_requests = case params[:f].to_i
when 1 then @merge_requests
when 2 then @merge_requests.closed
when 3 then @merge_requests.opened.assigned(current_user)
else @merge_requests.opened
end.page(params[:page]).per(20)
@merge_requests = @merge_requests.includes(:author, :project).order("closed, created_at desc")
@merge_requests = MergeRequestsLoad.new(project, current_user, params).execute
end
def show
......
......@@ -40,25 +40,6 @@ class NotesController < ApplicationController
protected
def notes
@notes = case params[:target_type]
when "commit"
then project.commit_notes(project.commit((params[:target_id]))).fresh.limit(20)
when "snippet"
then project.snippets.find(params[:target_id]).notes
when "wall"
then project.common_notes.order("created_at DESC").fresh.limit(50)
when "issue"
then project.issues.find(params[:target_id]).notes.inc_author.order("created_at DESC").limit(20)
when "merge_request"
then project.merge_requests.find(params[:target_id]).notes.inc_author.order("created_at DESC").limit(20)
end
@notes = if params[:last_id]
@notes.where("id > ?", params[:last_id])
elsif params[:first_id]
@notes.where("id < ?", params[:first_id])
else
@notes
end
@notes = NotesLoad.new(project, current_user, params).execute
end
end
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Extend the standard message generation to accept our custom exception
def failure_message
exception = env["omniauth.error"]
if exception.class == OmniAuth::Error
error = exception.message
else
error = exception.error_reason if exception.respond_to?(:error_reason)
error ||= exception.error if exception.respond_to?(:error)
error ||= env["omniauth.error.type"].to_s
end
error.to_s.humanize if error
end
def ldap
# We only find ourselves here if the authentication to LDAP was successful.
......
......@@ -9,7 +9,7 @@ class RefsController < ApplicationController
before_filter :require_non_empty_project
before_filter :ref
before_filter :define_tree_vars, :only => [:tree, :blob, :blame]
before_filter :define_tree_vars, :only => [:tree, :blob, :blame, :logs_tree]
before_filter :render_full_content
layout "project"
......@@ -46,6 +46,18 @@ class RefsController < ApplicationController
end
end
def logs_tree
contents = @tree.contents
@logs = contents.map do |content|
file = params[:path] ? File.join(params[:path], content.name) : content.name
last_commit = @project.commits(@commit.id, file, 1).last
{
:file_name => content.name,
:commit => last_commit
}
end
end
def blob
if @tree.is_blob?
if @tree.text?
......@@ -79,6 +91,15 @@ class RefsController < ApplicationController
@commit = project.commit(@ref)
@tree = Tree.new(@commit.tree, project, @ref, params[:path])
@tree = TreeDecorator.new(@tree)
@hex_path = Digest::SHA1.hexdigest(params[:path] || "/")
if params[:path]
@history_path = tree_file_project_ref_path(@project, @ref, params[:path])
@logs_path = logs_file_project_ref_path(@project, @ref, params[:path])
else
@history_path = tree_project_ref_path(@project, @ref)
@logs_path = logs_tree_project_ref_path(@project, @ref)
end
rescue
return render_404
end
......
class EventDecorator < ApplicationDecorator
decorates :event
def feed_title
if self.issue?
"#{self.author_name} #{self.action_name} issue ##{self.target_id}:" + self.issue_title
elsif self.merge_request?
"#{self.author_name} #{self.action_name} MR ##{self.target_id}:" + self.merge_request_title
elsif self.push?
"#{self.author_name} #{self.push_action_name} #{self.ref_type} " + self.ref_name
else
""
end
end
def feed_url
if self.issue?
h.project_issue_url(self.project, self.issue)
elsif self.merge_request?
h.project_merge_request_url(self.project, self.merge_request)
elsif self.push?
h.project_commits_url(self.project, :ref => self.ref_name)
end
end
end
......@@ -134,4 +134,8 @@ module ApplicationHelper
end
active ? "current" : nil
end
def hexdigest(string)
Digest::SHA1.hexdigest string
end
end
module TreeHelper
def tree_icon(content)
if content.is_a?(Grit::Blob)
if content.text?
image_tag "file_txt.png"
elsif content.image?
image_tag "file_img.png"
else
image_tag "file_bin.png"
end
else
image_tag "file_dir.png"
end
end
def tree_hex_class(content)
"file_#{hexdigest(content.name)}"
end
def tree_full_path(content)
if params[:path]
File.join(params[:path], content.name)
else
content.name
end
end
end
......@@ -80,6 +80,29 @@ class Commit
def commits_between(repo, from, to)
repo.commits_between(from, to).map { |c| Commit.new(c) }
end
def compare(project, from, to)
first = project.commit(to.try(:strip))
last = project.commit(from.try(:strip))
result = {
:commits => [],
:diffs => [],
:commit => nil
}
if first && last
commits = [first, last].sort_by(&:created_at)
younger = commits.first
older = commits.last
result[:commits] = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)}
result[:diffs] = project.repo.diff(younger.id, older.id) rescue []
result[:commit] = Commit.new(older)
end
result
end
end
def persisted?
......
......@@ -28,6 +28,10 @@ class Event < ActiveRecord::Base
end
end
def self.recent_for_user user
where(:project_id => user.projects.map(&:id)).recent
end
# Next events currently enabled for system
# - push
# - new issue
......
......@@ -22,7 +22,6 @@ class MergeRequest < ActiveRecord::Base
:should_remove_source_branch
validates_presence_of :project_id
validates_presence_of :assignee_id
validates_presence_of :author_id
validates_presence_of :source_branch
validates_presence_of :target_branch
......@@ -36,6 +35,7 @@ class MergeRequest < ActiveRecord::Base
delegate :name,
:email,
:to => :assignee,
:allow_nil => true,
:prefix => true
validates :title,
......@@ -128,7 +128,7 @@ class MergeRequest < ActiveRecord::Base
def unmerged_diffs
commits = project.repo.commits_between(target_branch, source_branch).map {|c| Commit.new(c)}
diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id)
diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) rescue []
end
def last_commit
......
......@@ -19,7 +19,7 @@ class Project < ActiveRecord::Base
has_many :notes, :dependent => :destroy
has_many :snippets, :dependent => :destroy
has_many :deploy_keys, :dependent => :destroy, :foreign_key => "project_id", :class_name => "Key"
has_many :web_hooks, :dependent => :destroy
has_many :hooks, :dependent => :destroy, :class_name => "ProjectHook"
has_many :wikis, :dependent => :destroy
has_many :protected_branches, :dependent => :destroy
......@@ -120,7 +120,7 @@ class Project < ActiveRecord::Base
errors.add(:path, " like 'gitolite-admin' is not allowed")
end
end
def self.access_options
UsersProject.access_roles
end
......
class ProjectHook < WebHook
belongs_to :project
end
class SystemHook < WebHook
def async_execute(data)
Resque.enqueue(SystemHookWorker, id, data)
end
def self.all_hooks_fire(data)
SystemHook.all.each do |sh|
sh.async_execute data
end
end
end
class User < ActiveRecord::Base
include Account
devise :database_authenticatable, :token_authenticatable,
devise :database_authenticatable, :token_authenticatable, :lockable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
attr_accessible :email, :password, :password_confirmation, :remember_me, :bio,
:name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme,
:name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme,
:theme_id, :force_random_password
attr_accessor :force_random_password
......@@ -15,6 +16,11 @@ class User < ActiveRecord::Base
has_many :my_own_projects, :class_name => "Project", :foreign_key => :owner_id
has_many :keys, :dependent => :destroy
has_many :events,
:class_name => "Event",
:foreign_key => :author_id,
:dependent => :destroy
has_many :recent_events,
:class_name => "Event",
:foreign_key => :author_id,
......@@ -80,7 +86,8 @@ class User < ActiveRecord::Base
def self.find_for_ldap_auth(omniauth_info)
name = omniauth_info.name.force_encoding("utf-8")
email = omniauth_info.email.downcase
email = omniauth_info.email.downcase unless omniauth_info.email.nil?
raise OmniAuth::Error, "LDAP accounts must provide an email address" if email.nil?
if @user = User.find_by_email(email)
@user
......
......@@ -68,7 +68,7 @@ class UsersProject < ActiveRecord::Base
end
def repo_access_human
""
self.class.access_roles.invert[self.project_access]
end
end
# == Schema Information
......
......@@ -4,8 +4,6 @@ class WebHook < ActiveRecord::Base
# HTTParty timeout
default_timeout 10
belongs_to :project
validates :url,
presence: true,
format: {
......@@ -14,9 +12,8 @@ class WebHook < ActiveRecord::Base
def execute(data)
WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" })
rescue
# There was a problem calling this web hook, let's forget about it.
end
end
# == Schema Information
#
......
......@@ -43,7 +43,7 @@ class MailerObserver < ActiveRecord::Observer
end
def new_merge_request(merge_request)
if merge_request.assignee != current_user
if merge_request.assignee && merge_request.assignee != current_user
Notify.new_merge_request_email(merge_request.id).deliver
end
end
......
class SystemHookObserver < ActiveRecord::Observer
observe :user, :project, :users_project
def after_create(model)
if model.kind_of? Project
SystemHook.all_hooks_fire({
event_name: "project_create",
name: model.name,
path: model.path,
project_id: model.id,
owner_name: model.owner.name,
owner_email: model.owner.email,
created_at: model.created_at
})
elsif model.kind_of? User
SystemHook.all_hooks_fire({
event_name: "user_create",
name: model.name,
email: model.email,
created_at: model.created_at
})
elsif model.kind_of? UsersProject
SystemHook.all_hooks_fire({
event_name: "user_add_to_team",
project_name: model.project.name,
project_path: model.project.path,
project_id: model.project_id,
user_name: model.user.name,
user_email: model.user.email,
project_access: model.repo_access_human,
created_at: model.created_at
})
end
end
def after_destroy(model)
if model.kind_of? Project
SystemHook.all_hooks_fire({
event_name: "project_destroy",
name: model.name,
path: model.path,
project_id: model.id,
owner_name: model.owner.name,
owner_email: model.owner.email,
})
elsif model.kind_of? User
SystemHook.all_hooks_fire({
event_name: "user_destroy",
name: model.name,
email: model.email
})
elsif model.kind_of? UsersProject
SystemHook.all_hooks_fire({
event_name: "user_remove_from_team",
project_name: model.project.name,
project_path: model.project.path,
project_id: model.project_id,
user_name: model.user.name,
user_email: model.user.email,
project_access: model.repo_access_human
})
end
end
end
......@@ -55,4 +55,8 @@ module Account
# Take only latest one
events = events.recent.limit(1).first
end
def projects_with_events
projects.includes(:events).order("events.created_at DESC")
end
end
......@@ -27,7 +27,7 @@ module GitPush
true
end
def execute_web_hooks(oldrev, newrev, ref, user)
def execute_hooks(oldrev, newrev, ref, user)
ref_parts = ref.split('/')
# Return if this is not a push to a branch (e.g. new commits)
......@@ -35,7 +35,7 @@ module GitPush
data = post_receive_data(oldrev, newrev, ref, user)
web_hooks.each { |web_hook| web_hook.execute(data) }
hooks.each { |hook| hook.execute(data) }
end
def post_receive_data(oldrev, newrev, ref, user)
......@@ -97,7 +97,7 @@ module GitPush
self.update_merge_requests(oldrev, newrev, ref, user)
# Execute web hooks
self.execute_web_hooks(oldrev, newrev, ref, user)
self.execute_hooks(oldrev, newrev, ref, user)
# Create satellite
self.satellite.create unless self.satellite.exists?
......
<% data_ex_str = <<eos
1. Project created:
{
"created_at": "2012-07-21T07:30:54Z",
"event_name": "project_create",
"name": "StoreCloud",
"owner_email": "johnsmith@gmail.com",
"owner_name": "John Smith",
"path": "storecloud",
"project_id": 74
}
2. Project destroyed:
{
"event_name": "project_destroy",
"name": "Underscore",
"owner_email": "johnsmith@gmail.com",
"owner_name": "John Smith",
"path": "underscore",
"project_id": 73
}
3. New Team Member:
{
"created_at": "2012-07-21T07:30:56Z",
"event_name": "user_add_to_team",
"project_access": "Master",
"project_id": 74,
"project_name": "StoreCloud",
"project_path": "storecloud",
"owner_email": "johnsmith@gmail.com",
"owner_name": "John Smith",
}
4. Team Member Removed:
{
"created_at": "2012-07-21T07:30:56Z",
"event_name": "user_remove_from_team",
"project_access": "Master",
"project_id": 74,
"project_name": "StoreCloud",
"project_path": "storecloud",
"owner_email": "johnsmith@gmail.com",
"owner_name": "John Smith",
}
5. User created:
{
"created_at": "2012-07-21T07:44:07Z",
"email": "js@gitlabhq.com",
"event_name": "user_create",
"name": "John Smith"
}
6. User removed:
{
"created_at": "2012-07-21T07:44:07Z",
"email": "js@gitlabhq.com",
"event_name": "user_destroy",
"name": "John Smith"
}
eos
%>
<% js_lexer = Pygments::Lexer[:js] %>
<%= raw js_lexer.highlight(data_ex_str) %>
.alert.alert-info
%span
Post receive hooks for binding events.
%br
Read more about system hooks
%strong #{link_to "here", help_system_hooks_path, :class => "vlink"}
= form_for @hook, :as => :hook, :url => admin_hooks_path do |f|
-if @hook.errors.any?
.alert-message.block-message.error
- @hook.errors.full_messages.each do |msg|
%p= msg
.clearfix
= f.label :url, "URL:"
.input
= f.text_field :url, :class => "text_field xxlarge"
&nbsp;
= f.submit "Add System Hook", :class => "btn primary"
%hr
-if @hooks.any?
%h3
Hooks
%small (#{@hooks.count})
%br
%table.admin-table
%tr
%th URL
%th Method
%th
- @hooks.each do |hook|
%tr
%td
= link_to admin_hook_path(hook) do
%strong= hook.url
= link_to 'Test Hook', admin_hook_test_path(hook), :class => "btn small right"
%td POST
%td
= link_to 'Remove', admin_hook_path(hook), :confirm => 'Are you sure?', :method => :delete, :class => "danger btn small right"
%h4
%i.icon-file
githost.log
%pre.logs
- Gitlab::Logger.read_latest.each do |line|
%span.log= line
.file_holder#README
.file_title
%i.icon-file
githost.log
.file_content.logs
%ol
- Gitlab::Logger.read_latest.each do |line|
%li
%p= line
%p This is page with preview for all system emails that are sent to user
%p Email previews built based on existing Project/Commit/Issue base - so some preview maybe unavailable unless object appear in system
#accordion
%h3
%a New user
%div
%iframe{ :src=> admin_mailer_preview_user_new_path, :width=>"100%", :height=>"350"}
%h3
%a New issue
%div
%iframe{ :src=> admin_mailer_preview_issue_new_path, :width=>"100%", :height=>"350"}
%h3
%a Commit note
%div
%iframe{ :src=> admin_mailer_preview_note_path(:type => "Commit"), :width=>"100%", :height=>"350"}
%h3
%a Issue note
%div
%iframe{ :src=> admin_mailer_preview_note_path(:type => "Issue"), :width=>"100%", :height=>"350"}
%h3
%a Wall note
%div
%iframe{ :src=> admin_mailer_preview_note_path(:type => "Wall"), :width=>"100%", :height=>"350"}
:javascript
$(function() {
$("#accordion").accordion(); });
......@@ -13,8 +13,8 @@
%th Team Members
%th Post Receive
%th Last Commit
%th
%th
%th Edit
%th.cred Danger Zone!
- @admin_projects.each do |project|
%tr
......@@ -24,5 +24,5 @@
%td= check_box_tag :post_receive_file, 1, project.has_post_receive_file?, :disabled => true
%td= last_commit(project)
%td= link_to 'Edit', edit_admin_project_path(project), :id => "edit_#{dom_id(project)}", :class => "btn small"
%td= link_to 'Destroy', [:admin, project], :confirm => 'Are you sure?', :method => :delete, :class => "btn small danger"
%td.bgred= link_to 'Destroy', [:admin, project], :confirm => "REMOVE #{project.name}? Are you sure?", :method => :delete, :class => "btn small danger"
= paginate @admin_projects, :theme => "admin"
......@@ -50,7 +50,7 @@
.alert
.clearfix
%p Give user ability to manage application.
%p Make the user a GitLab administrator.
= f.label :admin, :class => "checkbox" do
= f.check_box :admin
%span Administrator
......@@ -59,11 +59,11 @@
- if @admin_user.blocked
%span
= link_to 'Unblock', unblock_admin_user_path(@admin_user), :method => :put, :class => "btn small"
This user is blocked and is not able to login GitLab
This user is blocked and is not able to login to GitLab
- else
%span
= link_to 'Block', block_admin_user_path(@admin_user), :confirm => 'USER WILL BE BLOCKED! Are you sure?', :method => :put, :class => "btn small danger"
Blocked user will removed from all projects &amp; will not be able to login to GitLab.
Blocked users will be removed from all projects &amp; will not be able to login to GitLab.
.actions
= f.submit 'Save', :class => "btn primary"
- if @admin_user.new_record?
......
......@@ -27,7 +27,7 @@
%th Projects
%th Edit
%th Blocked
%th
%th.cred Danger Zone!
- @admin_users.each do |user|
%tr
......@@ -41,6 +41,6 @@
= link_to 'Unblock', unblock_admin_user_path(user), :method => :put, :class => "btn small success"
- else
= link_to 'Block', block_admin_user_path(user), :confirm => 'USER WILL BE BLOCKED! Are you sure?', :method => :put, :class => "btn small danger"
%td= link_to 'Destroy', [:admin, user], :confirm => 'USER WILL BE REMOVED! Are you sure?', :method => :delete, :class => "btn small danger"
%td.bgred= link_to 'Destroy', [:admin, user], :confirm => "USER #{user.name} WILL BE REMOVED! Are you sure?", :method => :delete, :class => "btn small danger"
= paginate @admin_users, :theme => "admin"
- @commits.group_by { |c| c.committed_date.to_date }.each do |day, commits|
%div.ui-box
%h5= day.stamp("28 Aug, 2010")
%h5.small
%i.icon-calendar
= day.stamp("28 Aug, 2010")
%ul.unstyled= render commits
......@@ -35,7 +35,13 @@
- if file.text?
= render "commits/text_file", :diff => diff, :index => i
- elsif file.image?
.diff_file_content_image{:class => image_diff_class(diff)}
%img{:src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
- if diff.renamed_file || diff.new_file || diff.deleted_file
.diff_file_content_image
%img{:class => image_diff_class(diff), :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
- else
- old_file = (@commit.prev_commit.tree / diff.old_path)
.diff_file_content_image.img_compared
%img{:class => "diff_image_removed", :src => "data:#{file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
%img{:class => "diff_image_added", :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
- else
%p.nothing_here_message No preview for this file type
......@@ -13,12 +13,12 @@
%li{:class => "#{branches_tab_class}"}
= link_to project_repository_path(@project) do
Branches
%span.number= @project.repo.branch_count
%span.badge= @project.repo.branch_count
%li{:class => "#{'active' if current_page?(tags_project_repository_path(@project)) }"}
= link_to tags_project_repository_path(@project) do
Tags
%span.number= @project.repo.tag_count
%span.badge= @project.repo.tag_count
- if current_page?(project_commits_path(@project)) && current_user.private_token
......
......@@ -20,7 +20,7 @@
= "..."
= text_field_tag :to, params[:to], :placeholder => "aa8b4ef", :class => "xlarge"
.actions
= submit_tag "Compare", :class => "btn primary"
= submit_tag "Compare", :class => "btn btn-primary"
- unless @commits.empty?
......
......@@ -8,17 +8,10 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
@events.each do |event|
if event.allowed?
event = EventDecorator.decorate(event)
xml.entry do
if event.issue?
event_link = project_issue_url(event.project, event.issue)
event_title = event.issue_title
elsif event.merge_request?
event_link = project_merge_request_url(event.project, event.merge_request)
event_title = event.merge_request_title
elsif event.push?
event_link = project_commits_url(event.project, :ref => event.ref_name)
event_title = event.ref_name
end
event_link = event.feed_url
event_title = event.feed_title
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link :href => event_link
......
......@@ -10,9 +10,10 @@
add new key
to your profile
- if @events.any?
= render @events
.content_list= render @events
- else
%h4.nothing_here_message Projects activity will be displayed here
.loading.hide
.side
= render "events/event_last_push", :event => @last_push
.projects_box
......@@ -54,3 +55,7 @@
New Project »
- else
If you will be added to project - it will be displayed here
:javascript
$(function(){ Pager.init(20); });
:plain
$(".projects .activities").append("#{escape_javascript(render(@events))}");
Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}");
= image_tag gravatar_icon(event.author_email), :class => "avatar"
%strong #{event.author_name}
%span.event_label= event.action_name
&nbsp;issue
%span.event_label{:class => event.action_name}= event.action_name
issue
= link_to project_issue_path(event.project, event.issue) do
%strong= truncate event.issue_title
at
......
......@@ -5,12 +5,9 @@
%span Your pushed to
= event.ref_type
= link_to project_commits_path(event.project, :ref => event.ref_name) do
%strong= event.ref_name
%strong= truncate(event.ref_name, :length => 28)
at
%strong= link_to event.project.name, event.project
%span.cgray
= time_ago_in_words(event.created_at)
ago.
= link_to new_mr_path_from_push_event(event), :title => "New Merge Request", :class => "btn very_small primary" do
Create Merge Request
......@@ -2,8 +2,8 @@
.event_icon= image_tag "event_mr_merged.png"
= image_tag gravatar_icon(event.author_email), :class => "avatar"
%strong #{event.author_name}
%span.event_label= event.action_name
&nbsp;merge request
%span.event_label{:class => event.action_name}= event.action_name
merge request
= link_to project_merge_request_path(event.project, event.merge_request) do
%strong= truncate event.merge_request_title
at
......
......@@ -2,7 +2,7 @@
.event_icon= image_tag "event_push.png"
= image_tag gravatar_icon(event.author_email), :class => "avatar"
%strong #{event.author_name}
%span.event_label= event.push_action_name
%span.event_label.pushed= event.push_action_name
= event.ref_type
= link_to project_commits_path(event.project, :ref => event.ref_name) do
%strong= event.ref_name
......
%h3 API
.back_link
= link_to help_path do
&larr; to index
%hr
%ol
%li
%a{:href => "#README"} README
%li
%a{:href => "#projects"} Projects
%li
%a{:href => "#users"} Users
.file_holder#README
.file_title
%i.icon-file
README
.file_content.wiki
= preserve do
= markdown File.read(Rails.root.join("doc", "api", "README.md"))
%br
.file_holder#projects
.file_title
%i.icon-file
Projects
.file_content.wiki
= preserve do
= markdown File.read(Rails.root.join("doc", "api", "projects.md"))
%br
.file_holder#users
.file_title
%i.icon-file
Users
.file_content.wiki
= preserve do
= markdown File.read(Rails.root.join("doc", "api", "users.md"))
......@@ -22,3 +22,9 @@
%li
%h5= link_to "Web Hooks", help_web_hooks_path
%li
%h5= link_to "System Hooks", help_system_hooks_path
%li
%h5= link_to "API", help_api_path
%h3 System hooks
.back_link
= link_to :back do
&larr; back
%hr
%p.slead
Your Gitlab instance can perform HTTP POST request on next event: create_project, delete_project, create_user, delete_user, change_team_member.
%br
System Hooks can be used for logging or change information in LDAP server.
%br
%h5 Hooks request example:
= render "admin/hooks/data_ex"
......@@ -6,7 +6,9 @@
.row
.span7= paginate @issues, :remote => true, :theme => "gitlab"
.span3.right
%span.cgray.right #{@issues.total_count} issues for this filter
%span.cgray.right
%span.issue_counter #{@issues.total_count}
issues for this filter
- else
%li
%h4.nothing_here_message Nothing to show here
......@@ -12,9 +12,9 @@
= issue.notes.count
- if can? current_user, :modify_issue, issue
- if issue.closed
= link_to 'Reopen', project_issue_path(issue.project, issue, :issue => {:closed => false }, :status_only => true), :method => :put, :class => "btn small grouped", :remote => true
= link_to 'Reopen', project_issue_path(issue.project, issue, :issue => {:closed => false }, :status_only => true), :method => :put, :class => "btn small grouped reopen_issue", :remote => true
- else
= link_to 'Resolve', project_issue_path(issue.project, issue, :issue => {:closed => true }, :status_only => true), :method => :put, :class => "success btn small grouped", :remote => true
= link_to 'Resolve', project_issue_path(issue.project, issue, :issue => {:closed => true }, :status_only => true), :method => :put, :class => "success btn small grouped close_issue", :remote => true
= link_to edit_project_issue_path(issue.project, issue), :class => "btn small edit-issue-link", :remote => true do
%i.icon-edit
Edit
......@@ -35,6 +35,4 @@
&nbsp;
- if issue.upvotes > 0
%span.badge.badge-success= "+#{issue.upvotes}"
%span.badge.badge-success= "+#{issue.upvotes}"
\ No newline at end of file
......@@ -2,7 +2,7 @@
.issues_content
%h3.page_title
Issues
%small (#{@issues.total_count})
%small (<span class=issue_counter>#{@issues.total_count}</span>)
.right
.span5
- if can? current_user, :write_issue, @project
......@@ -45,4 +45,4 @@
:javascript
$(function(){
issuesPage();
})
})
\ No newline at end of file
%h3 New key
%h3.page_title New key
%hr
= render 'form'
......@@ -11,4 +11,4 @@
if( key_mail && key_mail.length > 0 && title.val() == '' ){
$('#key_title').val( key_mail );
}
});
\ No newline at end of file
});
......@@ -17,14 +17,14 @@
%li{:class => tab_class(:issues)}
= link_to project_issues_filter_path(@project) do
Issues
%span.count= @project.issues.opened.count
%span.count.issue_counter= @project.issues.opened.count
- if @project.repo_exists?
- if @project.merge_requests_enabled
%li{:class => tab_class(:merge_requests)}
= link_to project_merge_requests_path(@project) do
Merge Requests
%span.count= @project.merge_requests.opened.count
%span.count.merge_counter= @project.merge_requests.opened.count
- if @project.wall_enabled
%li{:class => tab_class(:wall)}
......
......@@ -15,7 +15,7 @@
%li{:class => tab_class(:admin_logs)}
= link_to "Logs", admin_logs_path
%li{:class => tab_class(:admin_emails)}
= link_to "Emails", admin_emails_path
= link_to "Hooks", admin_hooks_path
%li{:class => tab_class(:admin_resque)}
= link_to "Resque", admin_resque_path
......
......@@ -12,16 +12,17 @@
%li{:class => tab_class(:password)}
= link_to "Password", profile_password_path
%li{:class => tab_class(:ssh_keys)}
= link_to keys_path do
SSH Keys
%span.count= current_user.keys.count
%li{:class => tab_class(:token)}
= link_to "Token", profile_token_path
%li{:class => tab_class(:design)}
= link_to "Design", profile_design_path
%li{:class => tab_class(:ssh_keys)}
= link_to keys_path do
SSH Keys
%span.count= current_user.keys.count
.content
= yield
......@@ -5,7 +5,8 @@
- @merge_request.errors.full_messages.each do |msg|
%li= msg
%h3.padded.cgray 1. Select Branches
%h4.cdark 1. Select Branches
%br
.row
.span6
......@@ -30,14 +31,21 @@
.bottom_commit
.mr_target_commit
%h3.padded.cgray 2. Fill info
%h4.cdark 2. Fill info
.clearfix
= f.label :assignee_id, "Assign to", :class => "control-label"
.controls= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" }, :style => "width:250px")
.main_box
.top_box_content
= f.label :title do
%strong= "Title *"
.input= f.text_field :title, :class => "input-xxlarge pad", :maxlength => 255, :rows => 5
.middle_box_content
= f.label :assignee_id do
%i.icon-user
Assign to
.input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" }, :style => "width:250px")
.control-group
= f.label :title, :class => "control-label"
.controls= f.text_field :title, :class => "input-xxlarge pad", :maxlength => 255, :rows => 5
.form-actions
= f.submit 'Save', :class => "btn-primary btn"
......
......@@ -15,12 +15,14 @@
&rarr;
= merge_request.target_branch
= image_tag gravatar_icon(merge_request.author_email), :class => "avatar"
= link_to project_merge_request_path(merge_request.project, merge_request) do
%p.row_title= truncate(merge_request.title, :length => 80)
%span.update-author
%strong= merge_request.author_name
authored
%small.cdark= "##{merge_request.id}"
authored by #{merge_request.author_name}
= time_ago_in_words(merge_request.created_at)
ago
- if merge_request.upvotes > 0
%span.badge.badge-success= "+#{merge_request.upvotes}"
= link_to project_merge_request_path(merge_request.project, merge_request) do
%p.row_title= truncate(merge_request.title, :length => 80)
%h3
%h3.page_title
= "Edit merge request #{@merge_request.id}"
%hr
= render 'form'
%h3 New Merge Request
%h3.page_title New Merge Request
%hr
= render 'form'
- if @commits.present?
.ui-box
%h5 Commits (#{@commits.count})
%h5
%i.icon-list
Commits (#{@commits.count})
.merge-request-commits
- if @commits.count > 8
%ul.first_mr_commits.unstyled
......
......@@ -13,9 +13,10 @@
= image_tag gravatar_icon(@merge_request.author_email), :width => 16, :class => "lil_av"
%strong.author= link_to_merge_request_author(@merge_request)
%cite.cgray and currently assigned to
= image_tag gravatar_icon(@merge_request.assignee_email), :width => 16, :class => "lil_av"
%strong.author= link_to_merge_request_assignee(@merge_request)
- if @merge_request.assignee
%cite.cgray and currently assigned to
= image_tag gravatar_icon(@merge_request.assignee_email), :width => 16, :class => "lil_av"
%strong.author= link_to_merge_request_assignee(@merge_request)
- if @merge_request.closed
......
......@@ -32,4 +32,4 @@
%span Any file less than 10 MB
= f.submit 'Add Comment', :class => "btn primary", :id => "submit_note"
= f.submit 'Add Comment', :class => "btn primary submit_note", :id => "submit_note"
......@@ -24,7 +24,7 @@
= check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
%span Commit author
.actions
= f.submit 'Add note', :class => "btn primary", :id => "submit_note"
= f.submit 'Add note', :class => "btn primary submit_note", :id => "submit_note"
= link_to "Close", "#", :class => "btn hide-button"
:javascript
......
%tr.line_notes_row.reply
%td{:colspan => 3}
%i.icon-comment
= link_to "Reply", "#", :class => "line_note_reply_link", "line_code" => line_code, :title => "Add note for this line"
......@@ -13,7 +13,7 @@
= render :partial => "refs/tree_file", :locals => { :name => tree.name, :content => tree.data, :file => tree }
- else
- contents = tree.contents
%table#tree-slider.bordered-table.table
%table#tree-slider.bordered-table.table{:class => "table_#{@hex_path}" }
%thead
%th Name
%th Last Update
......@@ -29,34 +29,39 @@
%td
%td
- index = 0
- contents.select{ |i| i.is_a?(Grit::Tree)}.each do |content|
= render :partial => "refs/tree_item", :locals => { :content => content }
= render :partial => "refs/tree_item", :locals => { :content => content, :index => (index += 1) }
- contents.select{ |i| i.is_a?(Grit::Blob)}.each do |content|
= render :partial => "refs/tree_item", :locals => { :content => content }
= render :partial => "refs/tree_item", :locals => { :content => content, :index => (index += 1) }
- contents.select{ |i| i.is_a?(Grit::Submodule)}.each do |content|
= render :partial => "refs/submodule_item", :locals => { :content => content }
= render :partial => "refs/submodule_item", :locals => { :content => content, :index => (index += 1) }
- if content = contents.select{ |c| c.is_a?(Grit::Blob) and c.name =~ /^readme/i }.first
#tree-readme-holder
%h3= content.name
.readme
.file_holder#README
.file_title
%i.icon-file
= content.name
.file_content.wiki
- if content.name =~ /\.(md|markdown)$/i
= preserve do
= markdown(content.data)
- else
= simple_format(content.data)
- if params[:path]
- history_path = tree_file_project_ref_path(@project, @ref, params[:path])
- else
- history_path = tree_project_ref_path(@project, @ref)
:javascript
$(function(){
$('select#branch').selectmenu({style:'popup', width:200});
$('select#tag').selectmenu({style:'popup', width:200});
$('.project-refs-select').chosen();
history.pushState({ path: this.path }, '', "#{history_path}")
history.pushState({ path: this.path }, '', "#{@history_path}");
});
// Load last commit log for each file in tree
$(window).load(function(){
ajaxGet('#{@logs_path}');
});
......
- if tm
%strong= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm)
= link_to truncate(content_commit.safe_message, :length => tm ? 30 : 50), project_commit_path(@project, content_commit.id), :class => "tree-commit-link"
.view_file
.view_file_header
.file_holder
.file_title
%i.icon-file
%span.file_name
= name
......@@ -10,26 +10,28 @@
= link_to "blame", blame_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small"
- if file.text?
- if name =~ /\.(md|markdown)$/i
#tree-readme-holder
.readme
= preserve do
= markdown(file.data)
.file_content.wiki
= preserve do
= markdown(file.data)
- else
.view_file_content
.file_content.code
- unless file.empty?
%div{:class => current_user.dark_scheme ? "black" : "white"}
= preserve do
= raw file.colorize(options: { linenos: 'True'})
- else
%h4.nothing_here_message Empty file
- elsif file.image?
.view_file_content_image
.file_content.image_file
%img{ :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
- else
%center
= link_to blob_project_ref_path(@project, @ref, :path => params[:path]) do
%div.padded
%br
= image_tag "download.png", :width => 64
%h3
Download (#{file.mb_size})
.file_content.blob_file
%center
= link_to blob_project_ref_path(@project, @ref, :path => params[:path]) do
%div.padded
%br
= image_tag "download.png", :width => 64
%h3
Download (#{file.mb_size})
- file = params[:path] ? File.join(params[:path], content.name) : content.name
- content_commit = @project.commits(@commit.id, file, 1).last
- return unless content_commit
%tr{ :class => "tree-item", :url => tree_file_project_ref_path(@project, @ref, file) }
- file = tree_full_path(content)
%tr{ :class => "tree-item #{tree_hex_class(content)}", :url => tree_file_project_ref_path(@project, @ref, file) }
%td.tree-item-file-name
- if content.is_a?(Grit::Blob)
- if content.text?
= image_tag "file_txt.png"
- elsif content.image?
= image_tag "file_img.png"
- else
= image_tag "file_bin.png"
- else
= image_tag "file_dir.png"
= tree_icon(content)
= link_to truncate(content.name, :length => 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), :remote => :true
%td.cgray
= time_ago_in_words(content_commit.committed_date)
ago
%td.commit
- tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name)
- if tm
%strong= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm)
= link_to truncate(content_commit.safe_message, :length => tm ? 30 : 50), project_commit_path(@project, content_commit.id), :class => "tree-commit-link"
%td.tree_time_ago.cgray
- if index == 1
%span.log_loading
Loading commit data..
= image_tag "ajax_loader_tree.gif", :width => 14
%td.tree_commit
......@@ -11,8 +11,8 @@
%li= link
.clear
.view_file.blame_file
.view_file_header
.file_holder
.file_title
%i.icon-file
%span.file_name
= @tree.name
......@@ -21,7 +21,7 @@
= link_to "raw", blob_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small", :target => "_blank"
= link_to "history", project_commits_path(@project, :path => params[:path], :ref => @ref), :class => "btn very_small"
= link_to "source", tree_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small"
.view_file_content
.file_content.blame
%table
- @blame.each do |commit, lines|
- commit = Commit.new(commit)
......@@ -29,7 +29,7 @@
%td.author
= image_tag gravatar_icon(commit.author_email, 16)
= commit.author_name
%td.commit
%td.blame_commit
&nbsp;
= link_to project_commit_path(@project, :id => commit.id) do
%code= commit.id.to_s[0..10]
......@@ -37,8 +37,7 @@
%td.lines
= preserve do
%pre
- lines.each do |line|
= line
= Gitlab::Encode.utf8 lines.join("\n")
:javascript
$(function(){
......
- @logs.each do |content_data|
- file_name = content_data[:file_name]
- content_commit = content_data[:commit]
- tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name)
:plain
var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}");
row.find("td.tree_time_ago").html('#{escape_javascript(time_ago_in_words(content_commit.committed_date))} ago');
row.find("td.tree_commit").html('#{escape_javascript(render("tree_commit", :tm => tm, :content_commit => content_commit))}');
:plain
// Load Files list
$("#tree-holder").html("#{escape_javascript(render(:partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree}))}");
$("#tree-content-holder").show("slide", { direction: "right" }, 150);
$('.project-refs-form #path').val("#{params[:path]}");
// Load last commit log for each file in tree
$('#tree-slider').waitForImages(function() {
ajaxGet('#{@logs_path}');
});
......@@ -7,16 +7,14 @@
= link_to "Edit", edit_project_snippet_path(@project, @snippet), :class => "btn small right"
%br
#tree-holder
#tree-content-holder
.view_file
.view_file_header
%i.icon-file
%strong= @snippet.file_name
%span.options
= link_to "raw", raw_project_snippet_path(@project, @snippet), :class => "btn very_small", :target => "_blank"
.view_file_content
%div{:class => current_user.dark_scheme ? "black" : ""}
= raw @snippet.colorize(options: { linenos: 'True'})
.file_holder
.file_title
%i.icon-file
%strong= @snippet.file_name
%span.options
= link_to "raw", raw_project_snippet_path(@project, @snippet), :class => "btn very_small", :target => "_blank"
.file_content.code
%div{:class => current_user.dark_scheme ? "black" : ""}
= raw @snippet.colorize(options: { linenos: 'True'})
= render "notes/notes", :tid => @snippet.id, :tt => "snippet"
class SystemHookWorker
@queue = :system_hook
def self.perform(hook_id, data)
SystemHook.find(hook_id).execute data
end
end
......@@ -23,7 +23,7 @@ module Gitlab
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# Activate observers that should always be running.
config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer
config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_observer
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
......
......@@ -21,6 +21,8 @@ email:
# Like default project limit for user etc
app:
default_projects_limit: 10
# backup_path: "/vol/backups" # default: Rails.root + backups/
# backup_keep_time: 604800 # default: 0 (forever) (in seconds)
#
......
......@@ -95,11 +95,21 @@ class Settings < Settingslogic
end
def gitolite_admin_uri
git['admin_uri'] || 'git@localhost:gitolite-admin'
git_host['admin_uri'] || 'git@localhost:gitolite-admin'
end
def default_projects_limit
app['default_projects_limit'] || 10
end
def backup_path
t = app['backup_path'] || "backups/"
t = /^\//.match(t) ? t : File.join(Rails.root + t)
t
end
def backup_keep_time
app['backup_keep_time'] || 0
end
end
end
......@@ -93,10 +93,6 @@ Devise.setup do |config|
# If true, extends the user's remember period when remembered via cookie.
# config.extend_remember_period = false
# If true, uses the password salt as remember token. This should be turned
# to false if you are not using database authenticatable.
config.use_salt_as_remember_token = true
# Options to be passed to the created cookie. For instance, you can set
# :secure => true in order to force SSL only cookies.
# config.cookie_options = {}
......@@ -119,7 +115,7 @@ Devise.setup do |config|
# Defines which strategy will be used to lock an account.
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
# :none = No lock strategy. You should handle locking by yourself.
# config.lock_strategy = :failed_attempts
config.lock_strategy = :failed_attempts
# Defines which key will be used when locking and unlocking an account
# config.unlock_keys = [ :email ]
......@@ -129,14 +125,14 @@ Devise.setup do |config|
# :time = Re-enables login after a certain amount of time (see :unlock_in below)
# :both = Enables both strategies
# :none = No unlock strategy. You should handle unlocking by yourself.
# config.unlock_strategy = :both
config.unlock_strategy = :time
# Number of authentication tries before locking an account if lock_strategy
# is failed attempts.
# config.maximum_attempts = 20
config.maximum_attempts = 10
# Time interval to unlock the account if :time is enabled as unlock_strategy.
# config.unlock_in = 1.hour
config.unlock_in = 10.minutes
# ==> Configuration for :recoverable
#
......@@ -160,9 +156,9 @@ Devise.setup do |config|
# Defines name of the authentication token params key
config.token_authentication_key = :private_token
# If true, authentication through token does not store user in session and needs
# Authentication through token does not store user in session and needs
# to be supplied on each request. Useful if you are using the token as API token.
config.stateless_token = true
config.skip_session_storage << :token_auth
# ==> Scopes configuration
# Turn scoped views on. Before rendering "sessions/new", it will first check for
......
......@@ -35,13 +35,11 @@ en:
confirmed: 'Your account was successfully confirmed. You are now signed in.'
registrations:
signed_up: 'Welcome! You have signed up successfully.'
inactive_signed_up: 'You have signed up successfully. However, we could not sign you in because your account is %{reason}.'
updated: 'You updated your account successfully.'
destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.'
reasons:
inactive: 'inactive'
unconfirmed: 'unconfirmed'
locked: 'locked'
signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.'
signed_up_but_inactive: 'You have signed up successfully. However, we could not sign you in because your account is not yet activated.'
signed_up_but_locked: 'You have signed up successfully. However, we could not sign you in because your account is locked.'
unlocks:
send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
unlocked: 'Your account was successfully unlocked. You are now signed in.'
......
......@@ -26,7 +26,9 @@ Gitlab::Application.routes.draw do
get 'help' => 'help#index'
get 'help/permissions' => 'help#permissions'
get 'help/workflow' => 'help#workflow'
get 'help/api' => 'help#api'
get 'help/web_hooks' => 'help#web_hooks'
get 'help/system_hooks' => 'help#system_hooks'
#
# Admin Area
......@@ -46,11 +48,13 @@ Gitlab::Application.routes.draw do
end
end
resources :team_members, :only => [:edit, :update, :destroy]
get 'emails', :to => 'mailer#preview'
get 'mailer/preview_note'
get 'mailer/preview_user_new'
get 'mailer/preview_issue_new'
resources :hooks, :only => [:index, :create, :destroy] do
get :test
end
resource :logs
resource :resque, :controller => 'resque'
root :to => "dashboard#index"
......@@ -116,6 +120,8 @@ Gitlab::Application.routes.draw do
member do
get "tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ }
get "logs_tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ }
get "blob",
:constraints => {
:id => /[a-zA-Z.0-9\/_\-]+/,
......@@ -131,6 +137,14 @@ Gitlab::Application.routes.draw do
:path => /.*/
}
# tree viewer
get "logs_tree/:path" => "refs#logs_tree",
:as => :logs_file,
:constraints => {
:id => /[a-zA-Z.0-9\/_\-]+/,
:path => /.*/
}
# blame
get "blame/:path" => "refs#blame",
:as => :blame_file,
......
class DeviseCreateUsers < ActiveRecord::Migration
def self.up
create_table(:users) do |t|
t.database_authenticatable :null => false
t.recoverable
t.rememberable
t.trackable
# t.encryptable
# t.confirmable
# t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
# t.token_authenticatable
## Database authenticatable
t.string :email, :null => false, :default => ""
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Encryptable
# t.string :password_salt
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
# Token authenticatable
# t.string :authentication_token
## Invitable
# t.string :invitation_token
t.timestamps
end
......
class AddLockableToUsers < ActiveRecord::Migration
def change
add_column :users, :failed_attempts, :integer, :default => 0
add_column :users, :locked_at, :datetime
end
end
class AddTypeToWebHook < ActiveRecord::Migration
def change
add_column :web_hooks, :type, :string, :default => "ProjectHook"
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20120627145613) do
ActiveRecord::Schema.define(:version => 20120712080407) do
create_table "events", :force => true do |t|
t.string "target_type"
......@@ -169,6 +169,8 @@ ActiveRecord::Schema.define(:version => 20120627145613) do
t.integer "theme_id", :default => 1, :null => false
t.string "bio"
t.boolean "blocked", :default => false, :null => false
t.integer "failed_attempts", :default => 0
t.datetime "locked_at"
end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
......@@ -185,8 +187,9 @@ ActiveRecord::Schema.define(:version => 20120627145613) do
create_table "web_hooks", :force => true do |t|
t.string "url"
t.integer "project_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "type", :default => "ProjectHook"
end
create_table "wikis", :force => true do |t|
......
......@@ -60,7 +60,7 @@ Also read the [Read this before you submit an issue](https://github.com/gitlabhq
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install -y wget curl gcc checkinstall libxml2-dev libxslt-dev sqlite3 libsqlite3-dev libcurl4-openssl-dev libreadline-gplv2-dev libc6-dev libssl-dev libmysql++-dev make build-essential zlib1g-dev libicu-dev redis-server openssh-server git-core python-dev python-pip libyaml-dev sendmail
sudo apt-get install -y wget curl gcc checkinstall libxml2-dev libxslt-dev sqlite3 libsqlite3-dev libcurl4-openssl-dev libreadline6-dev libc6-dev libssl-dev libmysql++-dev make build-essential zlib1g-dev libicu-dev redis-server openssh-server git-core python-dev python-pip libyaml-dev sendmail
# If you want to use MySQL:
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
......@@ -107,10 +107,10 @@ Get gitolite source code:
Setup:
sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" > /home/git/.profile'
sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" >> /home/git/.profile'
sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; /home/git/gitolite/src/gl-system-install"
sudo cp /home/gitlab/.ssh/id_rsa.pub /home/git/gitlab.pub
sudo chmod 777 /home/git/gitlab.pub
sudo chmod 0444 /home/git/gitlab.pub
sudo -u git -H sed -i 's/0077/0007/g' /home/git/share/gitolite/conf/example.gitolite.rc
sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gl-setup -q /home/git/gitlab.pub"
......@@ -139,6 +139,8 @@ Permissions:
cd /home/gitlab
sudo -H -u gitlab git clone -b stable git://github.com/gitlabhq/gitlabhq.git gitlab
cd gitlab
sudo -u gitlab mkdir tmp
# Rename config files
sudo -u gitlab cp config/gitlab.yml.example config/gitlab.yml
......@@ -216,15 +218,15 @@ Application can be started with next command:
sudo -u gitlab cp config/unicorn.rb.orig config/unicorn.rb
sudo -u gitlab bundle exec unicorn_rails -c config/unicorn.rb -E production -D
Edit /etc/nginx/nginx.conf. Add in **http** section:
Edit /etc/nginx/nginx.conf. In the *http* section add:
upstream gitlab {
server unix:/home/gitlab/gitlab/tmp/sockets/gitlab.socket;
}
server {
listen YOUR_SERVER_IP:80;
server_name gitlab.YOUR_DOMAIN.com;
listen YOUR_SERVER_IP:80; # e.g., listen 192.168.1.1:80;
server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com;
root /home/gitlab/gitlab/public;
# individual nginx logs for this gitlab vhost
......@@ -232,26 +234,26 @@ Edit /etc/nginx/nginx.conf. Add in **http** section:
error_log /var/log/nginx/gitlab_error.log;
location / {
# serve static files from defined root folder;.
# @gitlab is a named location for the upstream fallback, see below
try_files $uri $uri/index.html $uri.html @gitlab;
# serve static files from defined root folder;.
# @gitlab is a named location for the upstream fallback, see below
try_files $uri $uri/index.html $uri.html @gitlab;
}
# if a file, which is not found in the root folder is requested,
# then the proxy pass the request to the upsteam (gitlab unicorn)
location @gitlab {
proxy_redirect off;
# you need to change this to "https", if you set "ssl" directive to "on"
proxy_set_header X-FORWARDED_PROTO http;
proxy_set_header Host gitlab.YOUR_SUBDOMAIN.com:80;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://gitlab;
}
}
gitlab.YOUR_DOMAIN.com - change to your domain.
Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN** to the IP address and fully-qualified domain name of the host serving GitLab.
Restart nginx:
......
module Gitlab
class Logger
class Logger < ::Logger
def self.error(message)
@@logger ||= ::Logger.new(File.join(Rails.root, "log/githost.log"))
message = Time.now.to_s(:long) + " -> " + message
@@logger.error(message)
build.error(message)
end
def self.info(message)
build.info(message)
end
def self.read_latest
path = Rails.root.join("log/githost.log")
logs = `tail -n 50 #{path}`.split("\n")
logs = File.read(path).split("\n")
end
def self.build
new(File.join(Rails.root, "log/githost.log"))
end
def format_message(severity, timestamp, progname, msg)
"#{timestamp.to_s(:long)} -> #{severity} -> #{msg}\n"
end
end
end
require 'active_record/fixtures'
namespace :gitlab do
namespace :app do
# Create backup of gitlab system
desc "GITLAB | Create a backup of the gitlab system"
task :backup_create => :environment do
Rake::Task["gitlab:app:db_dump"].invoke
Rake::Task["gitlab:app:repo_dump"].invoke
Dir.chdir(Gitlab.config.backup_path)
# saving additional informations
s = Hash.new
s["db_version"] = "#{ActiveRecord::Migrator.current_version}"
s["backup_created_at"] = "#{Time.now}"
s["gitlab_version"] = %x{git rev-parse HEAD}.gsub(/\n/,"")
s["tar_version"] = %x{tar --version | head -1}.gsub(/\n/,"")
File.open("#{Gitlab.config.backup_path}/backup_information.yml", "w+") do |file|
file << s.to_yaml.gsub(/^---\n/,'')
end
# create archive
print "Creating backup archive: #{Time.now.to_i}_gitlab_backup.tar "
if Kernel.system("tar -cf #{Time.now.to_i}_gitlab_backup.tar repositories/ db/ backup_information.yml")
puts "[DONE]".green
else
puts "[FAILED]".red
end
# cleanup: remove tmp files
print "Deletion of tmp directories..."
if Kernel.system("rm -rf repositories/ db/ backup_information.yml")
puts "[DONE]".green
else
puts "[FAILED]".red
end
# delete backups
print "Deleting old backups... "
if Gitlab.config.backup_keep_time > 0
file_list = Dir.glob("*_gitlab_backup.tar").map { |f| f.split(/_/).first.to_i }
file_list.sort.each do |timestamp|
if Time.at(timestamp) < (Time.now - Gitlab.config.backup_keep_time)
%x{rm #{timestamp}_gitlab_backup.tar}
end
end
puts "[DONE]".green
else
puts "[SKIPPING]".yellow
end
end
# Restore backup of gitlab system
desc "GITLAB | Restore a previously created backup"
task :backup_restore => :environment do
Dir.chdir(Gitlab.config.backup_path)
# check for existing backups in the backup dir
file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i }
puts "no backup found" if file_list.count == 0
if file_list.count > 1 && ENV["BACKUP"].nil?
puts "Found more than one backup, please specify which one you want to restore:"
puts "rake gitlab:app:backup_restore BACKUP=timestamp_of_backup"
exit 1;
end
tar_file = ENV["BACKUP"].nil? ? File.join(file_list.first.to_s + "_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar")
unless File.exists?(tar_file)
puts "The specified backup doesn't exist!"
exit 1;
end
print "Unpacking backup... "
unless Kernel.system("tar -xf #{tar_file}")
puts "[FAILED]".red
exit 1
else
puts "[DONE]".green
end
settings = YAML.load_file("backup_information.yml")
ENV["VERSION"] = "#{settings["db_version"]}" if settings["db_version"].to_i > 0
# restoring mismatching backups can lead to unexpected problems
if settings["gitlab_version"] != %x{git rev-parse HEAD}.gsub(/\n/,"")
puts "gitlab_version mismatch:".red
puts " Your current HEAD differs from the HEAD in the backup!".red
puts " Please switch to the following revision and try again:".red
puts " revision: #{settings["gitlab_version"]}".red
exit 1
end
Rake::Task["gitlab:app:db_restore"].invoke
Rake::Task["gitlab:app:repo_restore"].invoke
# cleanup: remove tmp files
print "Deletion of tmp directories..."
if Kernel.system("rm -rf repositories/ db/ backup_information.yml")
puts "[DONE]".green
else
puts "[FAILED]".red
end
end
################################################################################
################################# invoked tasks ################################
################################# REPOSITORIES #################################
task :repo_dump => :environment do
backup_path_repo = File.join(Gitlab.config.backup_path, "repositories")
FileUtils.mkdir_p(backup_path_repo) until Dir.exists?(backup_path_repo)
puts "Dumping repositories:"
project = Project.all.map { |n| [n.name,n.path_to_repo] }
project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")]
project.each do |project|
print "- Dumping repository #{project.first}... "
if Kernel.system("cd #{project.second} > /dev/null 2>&1 && git bundle create #{backup_path_repo}/#{project.first}.bundle --all > /dev/null 2>&1")
puts "[DONE]".green
else
puts "[FAILED]".red
end
end
end
task :repo_restore => :environment do
backup_path_repo = File.join(Gitlab.config.backup_path, "repositories")
puts "Restoring repositories:"
project = Project.all.map { |n| [n.name,n.path_to_repo] }
project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")]
project.each do |project|
print "- Restoring repository #{project.first}... "
FileUtils.rm_rf(project.second) if File.dirname(project.second) # delet old stuff
if Kernel.system("cd #{File.dirname(project.second)} > /dev/null 2>&1 && git clone --bare #{backup_path_repo}/#{project.first}.bundle #{project.first}.git > /dev/null 2>&1")
puts "[DONE]".green
else
puts "[FAILED]".red
end
end
end
###################################### DB ######################################
task :db_dump => :environment do
backup_path_db = File.join(Gitlab.config.backup_path, "db")
FileUtils.mkdir_p(backup_path_db) until Dir.exists?(backup_path_db)
puts "Dumping database tables:"
ActiveRecord::Base.connection.tables.each do |tbl|
print "- Dumping table #{tbl}... "
count = 1
File.open(File.join(backup_path_db, tbl + ".yml"), "w+") do |file|
ActiveRecord::Base.connection.select_all("SELECT * FROM `#{tbl}`").each do |line|
line.delete_if{|k,v| v.blank?}
output = {tbl + '_' + count.to_s => line}
file << output.to_yaml.gsub(/^---\n/,'') + "\n"
count += 1
end
puts "[DONE]".green
end
end
end
task :db_restore=> :environment do
backup_path_db = File.join(Gitlab.config.backup_path, "db")
puts "Restoring database tables:"
Rake::Task["db:reset"].invoke
Dir.glob(File.join(backup_path_db, "*.yml") ).each do |dir|
fixture_file = File.basename(dir, ".*" )
print "- Loading fixture #{fixture_file}..."
if File.size(dir) > 0
ActiveRecord::Fixtures.create_fixtures(backup_path_db, fixture_file)
puts "[DONE]".green
else
puts "[SKIPPING]".yellow
end
end
end
end # namespace end: app
end # namespace end: gitlab
mkdir -p tmp/pids
bundle exec rake environment resque:work QUEUE=post_receive,mailer RAILS_ENV=production PIDFILE=tmp/pids/resque_worker.pid BACKGROUND=yes
bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook RAILS_ENV=production PIDFILE=tmp/pids/resque_worker.pid BACKGROUND=yes
bundle exec rake environment resque:work QUEUE=* VVERBOSE=1
bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1
......@@ -78,7 +78,7 @@ describe Gitlab::API do
end
describe "DELETE /projects/:id/snippets/:snippet_id" do
it "should create a new project snippet" do
it "should delete existing project snippet" do
expect {
delete "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}"
}.should change { Snippet.count }.by(-1)
......
......@@ -7,6 +7,12 @@ Factory.add(:project, Project) do |obj|
obj.code = 'LGT'
end
Factory.add(:project_without_owner, Project) do |obj|
obj.name = Faker::Internet.user_name
obj.path = 'gitlabhq'
obj.code = 'LGT'
end
Factory.add(:public_project, Project) do |obj|
obj.name = Faker::Internet.user_name
obj.path = 'gitlabhq'
......@@ -60,7 +66,11 @@ Factory.add(:key, Key) do |obj|
obj.key = File.read(File.join(Rails.root, "db", "pkey.example"))
end
Factory.add(:web_hook, WebHook) do |obj|
Factory.add(:project_hook, ProjectHook) do |obj|
obj.url = Faker::Internet.uri("http")
end
Factory.add(:system_hook, SystemHook) do |obj|
obj.url = Faker::Internet.uri("http")
end
......
......@@ -13,7 +13,6 @@ describe MergeRequest do
it { should validate_presence_of(:title) }
it { should validate_presence_of(:author_id) }
it { should validate_presence_of(:project_id) }
it { should validate_presence_of(:assignee_id) }
end
describe "Scope" do
......
......@@ -21,44 +21,44 @@ describe Project, "Hooks" do
end
end
describe "Web hooks" do
describe "Project hooks" do
context "with no web hooks" do
it "raises no errors" do
lambda {
project.execute_web_hooks('oldrev', 'newrev', 'ref', @user)
project.execute_hooks('oldrev', 'newrev', 'ref', @user)
}.should_not raise_error
end
end
context "with web hooks" do
before do
@webhook = Factory(:web_hook)
@webhook_2 = Factory(:web_hook)
project.web_hooks << [@webhook, @webhook_2]
@project_hook = Factory(:project_hook)
@project_hook_2 = Factory(:project_hook)
project.hooks << [@project_hook, @project_hook_2]
end
it "executes multiple web hook" do
@webhook.should_receive(:execute).once
@webhook_2.should_receive(:execute).once
@project_hook.should_receive(:execute).once
@project_hook_2.should_receive(:execute).once
project.execute_web_hooks('oldrev', 'newrev', 'refs/heads/master', @user)
project.execute_hooks('oldrev', 'newrev', 'refs/heads/master', @user)
end
end
context "does not execute web hooks" do
before do
@webhook = Factory(:web_hook)
project.web_hooks << [@webhook]
@project_hook = Factory(:project_hook)
project.hooks << [@project_hook]
end
it "when pushing a branch for the first time" do
@webhook.should_not_receive(:execute)
project.execute_web_hooks('00000000000000000000000000000000', 'newrev', 'refs/heads/master', @user)
@project_hook.should_not_receive(:execute)
project.execute_hooks('00000000000000000000000000000000', 'newrev', 'refs/heads/master', @user)
end
it "when pushing tags" do
@webhook.should_not_receive(:execute)
project.execute_web_hooks('oldrev', 'newrev', 'refs/tags/v1.0.0', @user)
@project_hook.should_not_receive(:execute)
project.execute_hooks('oldrev', 'newrev', 'refs/tags/v1.0.0', @user)
end
end
......
......@@ -11,7 +11,7 @@ describe Project do
it { should have_many(:issues).dependent(:destroy) }
it { should have_many(:notes).dependent(:destroy) }
it { should have_many(:snippets).dependent(:destroy) }
it { should have_many(:web_hooks).dependent(:destroy) }
it { should have_many(:hooks).dependent(:destroy) }
it { should have_many(:deploy_keys).dependent(:destroy) }
end
......
require "spec_helper"
describe SystemHook do
describe "execute" do
before(:each) { ActiveRecord::Base.observers.enable(:all) }
before(:each) do
@system_hook = Factory :system_hook
WebMock.stub_request(:post, @system_hook.url)
end
it "project_create hook" do
user = Factory :user
with_resque do
project = Factory :project_without_owner, :owner => user
end
WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once
end
it "project_destroy hook" do
project = Factory :project
with_resque do
project.destroy
end
WebMock.should have_requested(:post, @system_hook.url).with(body: /project_destroy/).once
end
it "user_create hook" do
with_resque do
Factory :user
end
WebMock.should have_requested(:post, @system_hook.url).with(body: /user_create/).once
end
it "user_destroy hook" do
user = Factory :user
with_resque do
user.destroy
end
WebMock.should have_requested(:post, @system_hook.url).with(body: /user_destroy/).once
end
it "project_create hook" do
user = Factory :user
project = Factory :project
with_resque do
project.users << user
end
WebMock.should have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once
end
it "project_destroy hook" do
user = Factory :user
project = Factory :project
project.users << user
with_resque do
project.users_projects.clear
end
WebMock.should have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once
end
end
end
require 'spec_helper'
describe WebHook do
describe ProjectHook do
describe "Associations" do
it { should belong_to :project }
end
......@@ -23,32 +23,32 @@ describe WebHook do
describe "execute" do
before(:each) do
@webhook = Factory :web_hook
@project_hook = Factory :project_hook
@project = Factory :project
@project.web_hooks << [@webhook]
@project.hooks << [@project_hook]
@data = { before: 'oldrev', after: 'newrev', ref: 'ref'}
WebMock.stub_request(:post, @webhook.url)
WebMock.stub_request(:post, @project_hook.url)
end
it "POSTs to the web hook URL" do
@webhook.execute(@data)
WebMock.should have_requested(:post, @webhook.url).once
@project_hook.execute(@data)
WebMock.should have_requested(:post, @project_hook.url).once
end
it "POSTs the data as JSON" do
json = @data.to_json
@webhook.execute(@data)
WebMock.should have_requested(:post, @webhook.url).with(body: json).once
@project_hook.execute(@data)
WebMock.should have_requested(:post, @project_hook.url).with(body: json).once
end
it "catches exceptions" do
WebHook.should_receive(:post).and_raise("Some HTTP Post error")
lambda {
@webhook.execute(@data)
}.should_not raise_error
@project_hook.execute(@data)
}.should raise_error
end
end
end
......
require 'spec_helper'
describe "Admin::Hooks" do
before do
@project = Factory :project,
:name => "LeGiT",
:code => "LGT"
login_as :admin
@system_hook = Factory :system_hook
end
describe "GET /admin/hooks" do
it "should be ok" do
visit admin_root_path
within ".main_menu" do
click_on "Hooks"
end
current_path.should == admin_hooks_path
end
it "should have hooks list" do
visit admin_hooks_path
page.should have_content(@system_hook.url)
end
end
describe "New Hook" do
before do
@url = Faker::Internet.uri("http")
visit admin_hooks_path
fill_in "hook_url", :with => @url
expect { click_button "Add System Hook" }.to change(SystemHook, :count).by(1)
end
it "should open new hook popup" do
page.current_path.should == admin_hooks_path
page.should have_content(@url)
end
end
describe "Test" do
before do
WebMock.stub_request(:post, @system_hook.url)
visit admin_hooks_path
click_link "Test Hook"
end
it { page.current_path.should == admin_hooks_path }
end
end
......@@ -13,9 +13,9 @@ describe "Admin::Projects" do
it { admin_users_path.should be_denied_for :visitor }
end
describe "GET /admin/emails" do
it { admin_emails_path.should be_allowed_for :admin }
it { admin_emails_path.should be_denied_for :user }
it { admin_emails_path.should be_denied_for :visitor }
describe "GET /admin/hooks" do
it { admin_hooks_path.should be_allowed_for :admin }
it { admin_hooks_path.should be_denied_for :user }
it { admin_hooks_path.should be_denied_for :visitor }
end
end
......@@ -9,7 +9,7 @@ describe "Hooks" do
describe "GET index" do
it "should be available" do
@hook = Factory :web_hook, :project => @project
@hook = Factory :project_hook, :project => @project
visit project_hooks_path(@project)
page.should have_content "Hooks"
page.should have_content @hook.url
......@@ -21,7 +21,7 @@ describe "Hooks" do
@url = Faker::Internet.uri("http")
visit project_hooks_path(@project)
fill_in "hook_url", :with => @url
expect { click_button "Add Web Hook" }.to change(WebHook, :count).by(1)
expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1)
end
it "should open new team member popup" do
......@@ -32,7 +32,8 @@ describe "Hooks" do
describe "Test" do
before do
@hook = Factory :web_hook, :project => @project
@hook = Factory :project_hook, :project => @project
stub_request(:post, @hook.url)
visit project_hooks_path(@project)
click_link "Test Hook"
end
......
......@@ -22,14 +22,14 @@ describe PostReceive do
Key.stub(find_by_identifier: nil)
project.should_not_receive(:observe_push)
project.should_not_receive(:execute_web_hooks)
project.should_not_receive(:execute_hooks)
PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id).should be_false
end
it "asks the project to execute web hooks" do
Project.stub(find_by_path: project)
project.should_receive(:execute_web_hooks).with('sha-old', 'sha-new', 'refs/heads/master', project.owner)
project.should_receive(:execute_hooks).with('sha-old', 'sha-new', 'refs/heads/master', project.owner)
PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id)
end
......
/*
* waitForImages 1.4
* -----------------
* Provides a callback when all images have loaded in your given selector.
* http://www.alexanderdickson.com/
*
*
* Copyright (c) 2011 Alex Dickson
* Licensed under the MIT licenses.
* See website for more info.
*
*/
;(function($) {
// Namespace all events.
var eventNamespace = 'waitForImages';
// CSS properties which contain references to images.
$.waitForImages = {
hasImageProperties: [
'backgroundImage',
'listStyleImage',
'borderImage',
'borderCornerImage'
]
};
// Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded.
$.expr[':'].uncached = function(obj) {
// Ensure we are dealing with an `img` element with a valid `src` attribute.
if ( ! $(obj).is('img[src!=""]')) {
return false;
}
// Firefox's `complete` property will always be`true` even if the image has not been downloaded.
// Doing it this way works in Firefox.
var img = document.createElement('img');
img.src = obj.src;
return ! img.complete;
};
$.fn.waitForImages = function(finishedCallback, eachCallback, waitForAll) {
// Handle options object.
if ($.isPlainObject(arguments[0])) {
eachCallback = finishedCallback.each;
waitForAll = finishedCallback.waitForAll;
finishedCallback = finishedCallback.finished;
}
// Handle missing callbacks.
finishedCallback = finishedCallback || $.noop;
eachCallback = eachCallback || $.noop;
// Convert waitForAll to Boolean
waitForAll = !! waitForAll;
// Ensure callbacks are functions.
if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) {
throw new TypeError('An invalid callback was supplied.');
};
return this.each(function() {
// Build a list of all imgs, dependent on what images will be considered.
var obj = $(this),
allImgs = [];
if (waitForAll) {
// CSS properties which may contain an image.
var hasImgProperties = $.waitForImages.hasImageProperties || [],
matchUrl = /url\((['"]?)(.*?)\1\)/g;
// Get all elements, as any one of them could have a background image.
obj.find('*').each(function() {
var element = $(this);
// If an `img` element, add it. But keep iterating in case it has a background image too.
if (element.is('img:uncached')) {
allImgs.push({
src: element.attr('src'),
element: element[0]
});
}
$.each(hasImgProperties, function(i, property) {
var propertyValue = element.css(property);
// If it doesn't contain this property, skip.
if ( ! propertyValue) {
return true;
}
// Get all url() of this element.
var match;
while (match = matchUrl.exec(propertyValue)) {
allImgs.push({
src: match[2],
element: element[0]
});
};
});
});
} else {
// For images only, the task is simpler.
obj
.find('img:uncached')
.each(function() {
allImgs.push({
src: this.src,
element: this
});
});
};
var allImgsLength = allImgs.length,
allImgsLoaded = 0;
// If no images found, don't bother.
if (allImgsLength == 0) {
finishedCallback.call(obj[0]);
};
$.each(allImgs, function(i, img) {
var image = new Image;
// Handle the image loading and error with the same callback.
$(image).bind('load.' + eventNamespace + ' error.' + eventNamespace, function(event) {
allImgsLoaded++;
// If an error occurred with loading the image, set the third argument accordingly.
eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load');
if (allImgsLoaded == allImgsLength) {
finishedCallback.call(obj[0]);
return false;
};
});
image.src = img.src;
});
});
};
})(jQuery);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment