Commit 4226458f authored by miks's avatar miks

Merge branch 'master' into project_users_api

parents 909c8c34 7cc4b3f6
......@@ -22,4 +22,4 @@ config/unicorn.rb
db/data.yml
.idea
.DS_Store
.chef
......@@ -9,6 +9,8 @@ branches:
- 'master'
rvm:
- 1.9.3
services:
- mysql
before_script:
- "cp config/database.yml.$DB config/database.yml"
- "cp config/gitlab.yml.example config/gitlab.yml"
......
......@@ -108,7 +108,7 @@ GEM
bcrypt-ruby (3.0.1)
blankslate (2.1.2.4)
bootstrap-sass (2.0.4.0)
builder (3.0.0)
builder (3.0.2)
capybara (1.1.2)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
......@@ -125,7 +125,7 @@ GEM
charlock_holmes (0.6.8)
childprocess (0.3.2)
ffi (~> 1.0.6)
chosen-rails (0.9.8)
chosen-rails (0.9.8.3)
railties (~> 3.0)
thor (~> 0.14)
coderay (1.0.6)
......
$(document).ready(function(){
$('input#user_force_random_password').on('change', function(elem) {
var elems = $('#user_password, #user_password_confirmation');
if ($(this).attr('checked')) {
elems.val('').attr('disabled', true);
} else {
elems.removeAttr('disabled');
}
});
});
$ ->
$('input#user_force_random_password').on 'change', (elem) ->
elems = $('#user_password, #user_password_confirmation')
if $(@).attr 'checked'
elems.val('').attr 'disabled', true
else
elems.removeAttr 'disabled'
......@@ -17,134 +17,3 @@
//= require raphael
//= require branch-graph
//= 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);
$("#projects-list .project").live('click', function(e){
if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
location.href = $(this).attr("url");
e.stopPropagation();
return false;
}
});
/**
* Focus search field by pressing 's' key
*/
$(document).keypress(function(e) {
if( $(e.target).is(":input") ) return;
switch(e.which) {
case 115: focusSearch();
e.preventDefault();
}
});
/**
* Commit show suppressed diff
*
*/
$(".supp_diff_link").bind("click", function() {
showDiff(this);
});
/**
* Note markdown preview
*
*/
$(document).on('click', '#preview-link', function(e) {
$('#preview-note').text('Loading...');
var previewLinkText = ($(this).text() == 'Preview' ? 'Edit' : 'Preview');
$(this).text(previewLinkText);
var note = $('#note_note').val();
if (note.trim().length === 0) { note = 'Nothing to preview'; }
$.post($(this).attr('href'), {note: note}, function(data) {
$('#preview-note').html(data);
});
$('#preview-note, #note_note').toggle();
e.preventDefault();
});
});
function focusSearch() {
$("#search").focus();
}
function updatePage(data){
$.ajax({type: "GET", url: location.href, data: data, dataType: "script"});
}
function showMenu() {
$(this).toggleClass('hover');
}
function resetMenu() {
$(this).removeClass("hover");
}
function slugify(text) {
return text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase();
}
function showDiff(link) {
$(link).next('table').show();
$(link).remove();
}
(function($){
var _chosen = $.fn.chosen;
$.fn.extend({
chosen: function(options) {
var default_options = {'search_contains' : 'true'};
$.extend(default_options, options);
return _chosen.apply(this, [default_options]);
}})
})(jQuery);
function ajaxGet(url) {
$.ajax({type: "GET", url: url, dataType: "script"});
}
/**
* Disable button if text field is empty
*/
function disableButtonIfEmtpyField(field_selector, button_selector) {
field = $(field_selector);
if(field.val() == "") {
field.closest("form").find(button_selector).attr("disabled", "disabled").addClass("disabled");
}
field.on('keyup', function(){
var field = $(this);
var closest_submit = field.closest("form").find(button_selector);
if(field.val() == "") {
closest_submit.attr("disabled", "disabled").addClass("disabled");
} else {
closest_submit.removeAttr("disabled").removeClass("disabled");
}
})
}
function initGraphNav() {
$(".graph svg").css("position", "relative");
$("body").bind("keyup", function(e) {
if(e.keyCode == 37) { // left
$(".graph svg").animate({ left: "+=400" });
} else if(e.keyCode == 39) { // right
$(".graph svg").animate({ left: "-=400" });
}
});
}
initGraphNav = ->
$('.graph svg').css 'position', 'relative'
$('body').bind 'keyup', (e) ->
if e.keyCode is 37 # left
$('.graph svg').animate left: '+=400'
else if e.keyCode is 39 # right
$('.graph svg').animate left: '-=400'
window.initGraphNav = initGraphNav
......@@ -80,6 +80,10 @@ function issuesPage(){
$(this).closest("form").submit();
});
$("#new_issue_link").click(function(){
updateNewIssueURL();
});
$('body').on('ajax:success', '.close_issue, .reopen_issue, #new_issue', function(){
var t = $(this),
totalIssues,
......@@ -126,3 +130,20 @@ function issuesCheckChanged() {
$('.issues_filters').show();
}
}
function updateNewIssueURL(){
var new_issue_link = $("#new_issue_link");
var milestone_id = $("#milestone_id").val();
var assignee_id = $("#assignee_id").val();
var new_href = "";
if(milestone_id){
new_href = "issue[milestone_id]=" + milestone_id + "&";
}
if(assignee_id){
new_href = new_href + "issue[assignee_id]=" + assignee_id;
}
if(new_href.length){
new_href = new_issue_link.attr("href") + "?" + new_href;
new_issue_link.attr("href", new_href);
}
};
var Loader = {
img_src: "/assets/ajax-loader.gif",
html:
function(width) {
img = $("<img>");
img.attr("width", width);
img.attr("src", this.img_src);
return img;
}
}
Loader =
html: (width) ->
$('<img>').attr src: '/assets/ajax-loader.gif', width: width
window.Loader = Loader
$(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);
$("#projects-list .project").live('click', function(e){
if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
location.href = $(this).attr("url");
e.stopPropagation();
return false;
}
});
/**
* Focus search field by pressing 's' key
*/
$(document).keypress(function(e) {
if( $(e.target).is(":input") ) return;
switch(e.which) {
case 115: focusSearch();
e.preventDefault();
}
});
/**
* Commit show suppressed diff
*
*/
$(".supp_diff_link").bind("click", function() {
showDiff(this);
});
/**
* Note markdown preview
*
*/
$(document).on('click', '#preview-link', function(e) {
$('#preview-note').text('Loading...');
var previewLinkText = ($(this).text() == 'Preview' ? 'Edit' : 'Preview');
$(this).text(previewLinkText);
var note = $('#note_note').val();
if (note.trim().length === 0) { note = 'Nothing to preview'; }
$.post($(this).attr('href'), {note: note}, function(data) {
$('#preview-note').html(data);
});
$('#preview-note, #note_note').toggle();
e.preventDefault();
});
});
function focusSearch() {
$("#search").focus();
}
function updatePage(data){
$.ajax({type: "GET", url: location.href, data: data, dataType: "script"});
}
function showMenu() {
$(this).toggleClass('hover');
}
function resetMenu() {
$(this).removeClass("hover");
}
function slugify(text) {
return text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase();
}
function showDiff(link) {
$(link).next('table').show();
$(link).remove();
}
(function($){
var _chosen = $.fn.chosen;
$.fn.extend({
chosen: function(options) {
var default_options = {'search_contains' : 'true'};
$.extend(default_options, options);
return _chosen.apply(this, [default_options]);
}})
})(jQuery);
function ajaxGet(url) {
$.ajax({type: "GET", url: url, dataType: "script"});
}
/**
* Disable button if text field is empty
*/
function disableButtonIfEmtpyField(field_selector, button_selector) {
field = $(field_selector);
if(field.val() == "") {
field.closest("form").find(button_selector).attr("disabled", "disabled").addClass("disabled");
}
field.on('keyup', function(){
var field = $(this);
var closest_submit = field.closest("form").find(button_selector);
if(field.val() == "") {
closest_submit.attr("disabled", "disabled").addClass("disabled");
} else {
closest_submit.removeAttr("disabled").removeClass("disabled");
}
})
}
window.Projects = ->
$("#project_name").live "change", ->
slug = slugify($(this).val())
$("#project_code").val(slug)
$("#project_path").val(slug)
$('#project_name').on 'change', ->
slug = slugify $(@).val()
$('#project_code, #project_path').val slug
$(".new_project, .edit_project").live "ajax:before", ->
$(".project_new_holder, .project_edit_holder").hide()
$(".save-project-loader").show()
$('.new_project, .edit_project').on 'ajax:before', ->
$('.project_new_holder, .project_edit_holder').hide()
$('.save-project-loader').show()
$("form #project_default_branch").chosen()
disableButtonIfEmtpyField "#project_name", ".project-submit"
$('form #project_default_branch').chosen()
disableButtonIfEmtpyField '#project_name', '.project-submit'
# Git clone panel switcher
$ ->
scope = $('.project_clone_holder')
scope = $ '.project_clone_holder'
if scope.length > 0
$('a, button', scope).click ->
$('a, button', scope).removeClass('active')
$(this).addClass('active')
$('#project_clone', scope).val($(this).data('clone'))
$('a, button', scope).removeClass 'active'
$(@).addClass 'active'
$('#project_clone', scope).val $(@).data 'clone'
$(document).ready(function(){
$("#snippets-table .snippet").live('click', function(e){
if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
location.href = $(this).attr("url");
e.stopPropagation();
return false;
}
});
});
$ ->
$('#snippets-table .snippet').live 'click', (e) ->
if e.target.nodeName isnt 'A' and e.target.nodeName isnt 'INPUT'
location.href = $(@).attr 'url'
e.stopPropagation()
false
function backToMembers(){
$("#new_team_member").hide("slide", { direction: "right" }, 150, function(){
$("#team-table").show("slide", { direction: "left" }, 150, function() {
$("#new_team_member").remove();
$(".add_new").show();
});
});
}
@import "bootstrap";
@import "bootstrap-responsive";
/** GITLAB colors **/
/** GitLab colors **/
$link_color:#3A89A3;
$blue_link: #2fa0bb;
$style_color: #474d57;
$hover: #fdf5d9;
/** GITLAB Fonts **/
/** GitLab Fonts **/
@font-face { font-family: Korolev; src: url('korolev-medium-compressed.otf'); }
/** MIXINS **/
......@@ -113,9 +113,9 @@ $hover: #fdf5d9;
@import "themes/ui_modern.scss";
/**
* Gitlab bootstrap.
* GitLab bootstrap.
* Overrides some styles of twitter bootstrap.
* Also give some common classes for gitlab app
* Also give some common classes for GitLab app
*/
@import "gitlab_bootstrap/common.scss";
@import "gitlab_bootstrap/typography.scss";
......
......@@ -11,15 +11,11 @@ class ApplicationController < ActionController::Base
helper_method :abilities, :can?
rescue_from Gitlab::Gitolite::AccessDenied do |exception|
render "errors/gitolite", layout: "error"
end
rescue_from Gitlab::Gitolite::InvalidKey do |exception|
render "errors/invalid_ssh_key", layout: "error"
render "errors/gitolite", layout: "error", status: 500
end
rescue_from Encoding::CompatibilityError do |exception|
render "errors/encoding", layout: "error", status: 404
render "errors/encoding", layout: "error", status: 500
end
rescue_from ActiveRecord::RecordNotFound do |exception|
......
......@@ -64,19 +64,14 @@ class CommitsController < ApplicationController
@commit.to_patch,
type: "text/plain",
disposition: 'attachment',
filename: (@commit.id.to_s + ".patch")
filename: "#{@commit.id.patch}"
)
end
protected
def load_refs
if params[:ref].blank?
@branch = params[:branch].blank? ? nil : params[:branch]
@tag = params[:tag].blank? ? nil : params[:tag]
@ref = @branch || @tag || @project.try(:default_branch) || 'master'
else
@ref = params[:ref]
end
@ref ||= params[:ref].presence || params[:branch].presence || params[:tag].presence
@ref ||= @ref || @project.try(:default_branch) || 'master'
end
end
......@@ -37,7 +37,7 @@ class IssuesController < ApplicationController
end
def new
@issue = @project.issues.new
@issue = @project.issues.new(params[:issue])
respond_with(@issue)
end
......
......@@ -17,13 +17,12 @@ class TeamMembersController < ApplicationController
end
def create
@team_member = UsersProject.new(params[:team_member])
@team_member.project = project
if @team_member.save
redirect_to team_project_path(@project)
else
render "new"
end
@project.add_users_ids_to_team(
params[:user_ids],
params[:project_access]
)
redirect_to team_project_path(@project)
end
def update
......
module GitlabMarkdownHelper
# Replaces references (i.e. @abc, #123, !456, ...) in the text with links to
# the appropriate items in Gitlab.
#
# text - the source text
# html_options - extra options for the reference links as given to link_to
#
# note: reference links will only be generated if @project is set
#
# see Gitlab::Markdown for details on the supported syntax
def gfm(text, html_options = {})
return text if text.nil?
return text if @project.nil?
# Extract pre blocks so they are not altered
# from http://github.github.com/github-flavored-markdown/
extractions = {}
text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) do |match|
md5 = Digest::MD5.hexdigest(match)
extractions[md5] = match
"{gfm-extraction-#{md5}}"
end
# TODO: add popups with additional information
parser = Gitlab::Markdown.new(@project, html_options)
text = parser.parse(text)
# Insert pre block extractions
text.gsub!(/\{gfm-extraction-(\h{32})\}/) do
extractions[$1]
end
sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class )
end
include Gitlab::Markdown
# Use this in places where you would normally use link_to(gfm(...), ...).
#
......@@ -60,7 +27,7 @@ module GitlabMarkdownHelper
filter_html: true,
with_toc_data: true,
hard_wrap: true)
@markdown ||= Redcarpet::Markdown.new(gitlab_renderer,
@markdown = Redcarpet::Markdown.new(gitlab_renderer,
# see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
no_intra_emphasis: true,
tables: true,
......
module ProjectsHelper
def grouper_project_members(project)
@project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)
end
end
......@@ -111,18 +111,18 @@ class Notify < ActionMailer::Base
# Examples
#
# >> subject('Lorem ipsum')
# => "gitlab | Lorem ipsum"
# => "GitLab | Lorem ipsum"
#
# # Automatically inserts Project name when @project is set
# >> @project = Project.last
# => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# >> subject('Lorem ipsum')
# => "gitlab | Lorem ipsum | Ruby on Rails"
# => "GitLab | Lorem ipsum | Ruby on Rails"
#
# # Accepts multiple arguments
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "gitlab | Lorem ipsum | Dolor sit amet"
# => "GitLab | Lorem ipsum | Dolor sit amet"
def subject(*extra)
"gitlab | " << extra.join(' | ') << (@project ? " | #{@project.name}" : "")
"GitLab | " << extra.join(' | ') << (@project ? " | #{@project.name}" : "")
end
end
......@@ -103,7 +103,7 @@ class Note < ActiveRecord::Base
# Returns true if this is an upvote note,
# otherwise false is returned
def upvote?
note =~ /^\+1/ ? true : false
note.start_with?('+1') || note.start_with?(':+1:')
end
end
# == Schema Information
......
......@@ -14,7 +14,7 @@ class UsersProject < ActiveRecord::Base
after_save :update_repository
after_destroy :update_repository
validates_uniqueness_of :user_id, scope: [:project_id]
validates_uniqueness_of :user_id, scope: [:project_id], message: "already exists in project"
validates_presence_of :user_id
validates_presence_of :project_id
......@@ -65,10 +65,10 @@ class UsersProject < ActiveRecord::Base
def self.access_roles
{
"Guest" => GUEST,
"Reporter" => REPORTER,
"Guest" => GUEST,
"Reporter" => REPORTER,
"Developer" => DEVELOPER,
"Master" => MASTER
"Master" => MASTER
}
end
......
.alert-message.block-message.error
%h3 Encoding Error
%hr
%p
Page can't be loaded because of an encoding error.
%h1 Encoding Error
%hr
%p Page can't be loaded because of an encoding error.
%h1 Git Error
%hr
%h2 Gitlab was unable to access your Gitolite system.
%h2 GitLab was unable to access your Gitolite system.
.git_error_tips
%h4 Tips for Administrator:
......
%h1 Git Error
%hr
%p Seems like SSH Key you provided is not a valid SSH key.
......@@ -30,7 +30,7 @@
%h5= link_to "API", help_api_path
%li
%h5= link_to "Gitlab Markdown", help_markdown_path
%h5= link_to "GitLab Markdown", help_markdown_path
%li
%h5= link_to "SSH keys", help_ssh_path
%h3.page_title Gitlab Flavored Markdown
%h3.page_title GitLab Flavored Markdown
.back_link
= link_to help_path do
&larr; to index
......@@ -7,7 +7,7 @@
.row
.span8
%p
For Gitlab we developed something we call "Gitlab Flavored Markdown" (GFM).
For GitLab we developed something we call "GitLab Flavored Markdown" (GFM).
It extends the standard Markdown in a few significant ways adds some useful functionality.
%p You can use GFM in:
......@@ -20,6 +20,15 @@
%li milestones
%li wiki pages
.span4
.alert.alert-info
%p
If you're not already familiar with Markdown, you should spend 15 minutes and go over the excellent
%strong= link_to "Markdown Syntax Guide", "http://daringfireball.net/projects/markdown/syntax"
at Daring Fireball.
.row
.span8
%h3 Differences from traditional Markdown
%h4 Newlines
......@@ -62,7 +71,30 @@
%p becomes
= markdown %Q{```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new("Hello World!")\nputs markdown.to_html\n```}
%h4 Special Gitlab references
%h4 Emoji
.row
.span8
:ruby
puts markdown %Q{Sometimes you want to be :cool: and add some :sparkles: to your :speech_balloon:. Well we have a :gift: for you:
:exclamation: You can use emoji anywhere GFM is supported. :sunglasses:
You can use it to point out a :bug: or warn about :monkey:patches. And if someone improves your really :snail: code, send them a :bouquet: or some :candy:. People will :heart: you for that.
If you are :new: to this, don't be :fearful:. You can easily join the emoji :circus_tent:. All you need to do is to :book: up on the supported codes.
}
.span4
.alert.alert-info
%p
Consult the
%strong= link_to "Emoji Cheat Sheet", "http://www.emoji-cheat-sheet.com/"
for a list of all supported emoji codes.
.row
.span8
%h4 Special GitLab references
%p
GFM recognizes special references.
......@@ -88,18 +120,10 @@
for commits
-# this example will only be shown if the user has a project with at least one issue
- if project = current_user.projects.first
- if issue = project.issues.first
%p For example in your #{link_to project.name, project_path(project)} project something like
- if @project = current_user.projects.first
- if issue = @project.issues.first
%p For example in your #{link_to @project.name, project_path(@project)} project, writing:
%pre= "This is related to ##{issue.id}. @#{current_user.name} is working on solving it."
%p becomes
%p becomes:
= markdown "This is related to ##{issue.id}. @#{current_user.name} is working on solving it."
.span4.right
.alert.alert-info
%p
If you're not already familiar with Markdown, you should spend 15 minutes and go over the excellent
%strong= link_to "Markdown Syntax Guide", "http://daringfireball.net/projects/markdown/syntax"
at Daring Fireball.
- @project = nil # Prevent this from bubbling up to page title
......@@ -5,7 +5,7 @@
%hr
%p.slead
SSH key allows you to establish a secure connection between your computer and Gitlab
SSH key allows you to establish a secure connection between your computer and GitLab
%p.slead
To generate a new SSH key just open your terminal and use code below.
......@@ -17,7 +17,7 @@
\# Generating public/private rsa key pair...
%p.slead
Next just use code below to dump your public key and add to GITLAB SSH Keys
Next just use code below to dump your public key and add to GitLab SSH Keys
%pre.dark
cat ~/.ssh/id_rsa.pub
......
......@@ -5,7 +5,7 @@
%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.
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
......
......@@ -5,11 +5,11 @@
%hr
%p.slead
Every Gitlab project can trigger a web server whenever the repo is pushed to.
Every GitLab project can trigger a web server whenever the repo is pushed to.
%br
Web Hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server.
%br
GITLAB will send POST request with commits information on every push.
GitLab will send POST request with commits information on every push.
%h5 Hooks request example:
= render "hooks/data_ex"
......@@ -24,7 +24,7 @@
git commit -am "My feature is ready"
%li
%p Push your branch to gitlabhq
%p Push your branch to GitLab
.bash
%pre.dark
git push origin $feature_name
......
......@@ -32,7 +32,7 @@
:timestamp => "2012-01-03T23:36:29+02:00",
:url => "http://localhost/diaspora/commits/da1560886d...",
:author => {
:name => "gitlab dev user",
:name => "GitLab dev user",
:email => "gitlabdev@dv6700.(none)"
}
}
......
......@@ -38,7 +38,7 @@
= f.label :description, "Details"
.input
= f.text_area :description, maxlength: 2000, class: "xxlarge", rows: 14
%p.hint Issues are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
%p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
.actions
......
......@@ -6,7 +6,7 @@
.right
.span5
- if can? current_user, :write_issue, @project
= link_to new_project_issue_path(@project), class: "right btn", title: "New Issue", remote: true do
= link_to new_project_issue_path(@project), class: "right btn", title: "New Issue", remote: true, id: "new_issue_link" do
%i.icon-plus
New Issue
= form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: :right do
......
......@@ -3,8 +3,8 @@
= link_to "Add new", new_key_path, class: "btn right"
%hr
%p.slead
SSH key allows you to establish a secure connection between your computer and Gitlab
%p.slead
SSH key allows you to establish a secure connection between your computer and GitLab
%table#keys-table
......@@ -15,7 +15,7 @@
%th
- @keys.each do |key|
= render(partial: 'show', locals: {key: key})
- if @keys.blank?
- if @keys.blank?
%tr
%td{colspan: 3}
%h3.nothing_here_message There are no SSH keys with access to your account.
......
......@@ -2,7 +2,7 @@
%head
%meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
%title
gitlabhq
GitLab
:css
.header h1 {color: #BBBBBB !important; font: bold 32px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 40px;}
.header p {color: #c6c6c6; font: normal 12px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 18px;}
......@@ -21,7 +21,7 @@
%td{align: "left", style: "padding: 18px 0 10px;", width: "580"}
%h1{style: "color: #BBBBBB; font: normal 32px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 40px;"}
gitlab
GITLAB
- if @project
| #{@project.name}
%table{align: "center", bgcolor: "#fff", border: "0", cellpadding: "0", cellspacing: "0", style: "font-family: Helvetica, Arial, sans-serif; background: #fff;", width: "600"}
......
......@@ -22,7 +22,7 @@
= f.label :description, "Description", class: "control-label"
.controls
= f.text_area :description, maxlength: 2000, class: "input-xlarge", rows: 10
%p.hint Milestones are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
%p.hint Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
.span6
.control-group
= f.label :due_date, "Due Date", class: "control-label"
......
......@@ -11,7 +11,7 @@
= f.text_area :note, size: 255, class: 'note-text'
#preview-note.preview_note.hide
.hint
.right Comments are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
.right Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
.clearfix
.row.note_advanced_opts.hide
......
......@@ -6,7 +6,7 @@
%h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
Hi #{@user['name']}!
%p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
Administrator created account for you. Now you are a member of company gitlab application.
Administrator created account for you. Now you are a member of company GitLab application.
%td{style: "font-size: 1px; line-height: 1px;", width: "21"}
%tr
%td{style: "font-size: 1px; line-height: 1px;", width: "21"}
......
%table
%thead
%tr
%th User
%th Permissions
%tbody
- @project.users_projects.each do |up|
= render(partial: 'team_members/show', locals: {member: up})
- grouper_project_members(@project).each do |access, members|
%table
%thead
%tr
%th.span7
= Project.access_options.key(access).pluralize
%th
%tbody
- members.each do |up|
= render(partial: 'team_members/show', locals: {member: up})
:javascript
......
%h3= "New Team member"
%h3.page_title
= "New Team member(s)"
%hr
= form_for @team_member, as: :team_member, url: project_team_members_path(@project, @team_member) do |f|
-if @team_member.errors.any?
......@@ -7,27 +8,23 @@
- @team_member.errors.full_messages.each do |msg|
%li= msg
%h6 1. Choose people you want in the team
.clearfix
= f.label :user_id, "Name"
.input= f.select(:user_id, User.not_in_project(@project).all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, { style: "width:300px" })
= f.label :user_ids, "Peolpe"
.input= select_tag(:user_ids, options_from_collection_for_select(User.not_in_project(@project).all, :id, :name), { class: "xxlarge", multiple: true })
%h6 2. Set access level for them
.clearfix
= f.label :project_access, "Project Access"
.input= f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select"
.input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select"
.actions
= f.submit 'Save', class: "btn primary"
= link_to "Cancel", team_project_path(@project), class: "btn"
= f.submit 'Save', class: "btn save-btn"
= link_to "Cancel", team_project_path(@project), class: "btn cancel-btn"
:css
form select {
width:300px;
}
:javascript
$('select#team_member_user_id').chosen();
$('select#team_member_project_access').chosen();
//$('select#team_member_repo_access').chosen();
//$('select#team_member_project_access').chosen();
$('select#user_ids').chosen();
$('select#project_access').chosen();
......@@ -2,12 +2,6 @@
- allow_admin = can? current_user, :admin_project, @project
%tr{id: dom_id(member), class: "team_member_row user_#{user.id}"}
%td
.right
- if @project.owner == user
%span.label Project Owner
- if user.blocked
%span.label Blocked
= link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
= image_tag gravatar_icon(user.email, 40), class: "avatar s32"
= link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
......@@ -16,5 +10,11 @@
%div.cgray= user.email
%td
= form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f|
= f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select", disabled: !allow_admin
.right
- if @project.owner == user
%span.btn.disabled.success Project Owner
- if user.blocked
%span.btn.disabled.blocked Blocked
- if allow_admin
= form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f|
= f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select"
......@@ -14,7 +14,7 @@
.middle_box_content
.input
%span.cgray
Wiki content is parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
To link to a (new) page you can just type
%code [Link Title](page-slug)
\.
......
# Gitlab API
# GitLab API
All API requests require authentication. You need to pass a `private_token` parameter to authenticate. You can find or reset your private token in your profile.
......@@ -10,7 +10,7 @@ If no, or an invalid, `private_token` is provided then an error message will be
}
```
API requests should be prefixed with `api` and the API version. The API version is equal to the Gitlab major version number, which is defined in `lib/api.rb`.
API requests should be prefixed with `api` and the API version. The API version is equal to the GitLab major version number, which is defined in `lib/api.rb`.
Example of a valid API request:
......
......@@ -102,6 +102,12 @@ Parameters:
+ `name` (required) - new project name
+ `code` (optional) - new project code, uses project name if not set
+ `path` (optional) - new project path, uses project name if not set
+ `description (optional) - short project description
+ `default_branch` (optional) - 'master' by default
+ `issues_enabled` (optional) - enabled by default
+ `wall_enabled` (optional) - enabled by default
+ `merge_requests_enabled` (optional) - enabled by default
+ `wiki_enabled` (optional) - enabled by default
Will return created project with status `201 Created` on success, or `404 Not
found` on fail.
......
......@@ -167,7 +167,7 @@ and ensure you have followed all of the above steps carefully.
# Login to MySQL
$ mysql -u root -p
# Create the gitlabhq production database
# Create the GitLab production database
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
# Create the MySQL User change $password to a real password
......
......@@ -4,7 +4,7 @@ Feature: Issues
And I own project "Shop"
And project "Shop" have "Release 0.4" open issue
And project "Shop" have "Release 0.3" closed issue
And I visit project "Shop" issues page
And I visit project "Shop" issues page
Scenario: I should see open issues
Given I should see "Release 0.4" in issues
......@@ -36,3 +36,47 @@ Feature: Issues
Given I visit issue page "Release 0.4"
And I leave a comment like "XML attached"
Then I should see comment "XML attached"
@javascript
Scenario: I search issue
Given I fill in issue search with "Release"
Then I should see "Release 0.4" in issues
And I should not see "Release 0.3" in issues
@javascript
Scenario: I search issue that not exist
Given I fill in issue search with "Bug"
Then I should not see "Release 0.4" in issues
And I should not see "Release 0.3" in issues
@javascript
Scenario: I search all issues
Given I click link "All"
And I fill in issue search with "0.3"
Then I should see "Release 0.3" in issues
And I should not see "Release 0.4" in issues
@javascript
Scenario: I clear search
Given I click link "All"
And I fill in issue search with "Something"
And I fill in issue search with ""
Then I should see "Release 0.4" in issues
And I should see "Release 0.3" in issues
@javascript
Scenario: I create Issue with pre-selected milestone
Given project "Shop" has milestone "v2.2"
And project "Shop" has milestone "v3.0"
And I visit project "Shop" issues page
When I select milestone "v3.0"
And I click link "New Issue"
Then I should see selected milestone with title "v3.0"
@javascript
Scenario: I create Issue with pre-selected assignee
When I select first assignee from "Shop" project
And I click link "New Issue"
Then I should see first assignee from "Shop" as selected assignee
include LoginHelpers
Given /^I signin as a user$/ do
login_as :user
end
When /^I click link "(.*?)"$/ do |link|
click_link link
end
When /^I click button "(.*?)"$/ do |button|
click_button button
end
When /^I fill in "(.*?)" with "(.*?)"$/ do |field, value|
fill_in field, :with => value
end
Given /^show me page$/ do
save_and_open_page
end
Given /^I visit dashboard page$/ do
visit dashboard_path
end
Then /^I should see "(.*?)" link$/ do |arg1|
page.should have_link(arg1)
end
......@@ -51,10 +47,10 @@ Then /^I click "(.*?)" link$/ do |arg1|
end
Then /^I see prefilled new Merge Request page$/ do
current_path.should == new_project_merge_request_path(@project)
find("#merge_request_source_branch").value.should == "new_design"
find("#merge_request_target_branch").value.should == "master"
find("#merge_request_title").value.should == "New Design"
current_path.should == new_project_merge_request_path(@project)
find("#merge_request_source_branch").value.should == "new_design"
find("#merge_request_target_branch").value.should == "master"
find("#merge_request_title").value.should == "New Design"
end
Given /^I visit dashboard search page$/ do
......@@ -66,10 +62,6 @@ Given /^I search for "(.*?)"$/ do |arg1|
click_button "Search"
end
Given /^I visit dashboard issues page$/ do
visit dashboard_issues_path
end
Then /^I should see issues assigned to me$/ do
issues = @user.issues
issues.each do |issue|
......@@ -78,10 +70,6 @@ Then /^I should see issues assigned to me$/ do
end
end
Given /^I visit dashboard merge requests page$/ do
visit dashboard_merge_requests_path
end
Then /^I should see my merge requests$/ do
merge_requests = @user.merge_requests
merge_requests.each do |mr|
......
Given /^I visit profile page$/ do
visit profile_path
end
Then /^I should see my profile info$/ do
page.should have_content "Profile"
page.should have_content @user.name
page.should have_content @user.email
end
Given /^I visit profile password page$/ do
visit profile_password_path
end
Then /^I change my password$/ do
fill_in "user_password", :with => "222333"
fill_in "user_password_confirmation", :with => "222333"
......@@ -22,10 +14,6 @@ Then /^I should be redirected to sign in page$/ do
current_path.should == new_user_session_path
end
Given /^I visit profile token page$/ do
visit profile_token_path
end
Then /^I reset my token$/ do
@old_token = @user.private_token
click_button "Reset"
......
Given /^I visit project source page$/ do
visit tree_project_ref_path(@project, @project.root_ref)
end
Then /^I should see files from repository$/ do
page.should have_content("app")
page.should have_content("History")
page.should have_content("Gemfile")
end
Given /^I visit project source page for "(.*?)"$/ do |arg1|
visit tree_project_ref_path(@project, arg1)
end
Then /^I should see files from repository for "(.*?)"$/ do |arg1|
current_path.should == tree_project_ref_path(@project, arg1)
page.should have_content("app")
......@@ -31,10 +23,6 @@ Given /^I click on raw button$/ do
click_link "raw"
end
Given /^I visit blob file from repo$/ do
visit tree_project_ref_path(@project, ValidCommit::ID, :path => ValidCommit::BLOB_FILE_PATH)
end
Then /^I should see raw file content$/ do
page.source.should == ValidCommit::BLOB_FILE
end
......
Given /^I visit project commits page$/ do
visit project_commits_path(@project)
end
Then /^I see project commits$/ do
current_path.should == project_commits_path(@project)
......@@ -23,19 +19,11 @@ Then /^I see commits atom feed$/ do
page.body.should have_selector("entry summary", :text => commit.description)
end
Given /^I click on commit link$/ do
visit project_commit_path(@project, ValidCommit::ID)
end
Then /^I see commit info$/ do
page.should have_content ValidCommit::MESSAGE
page.should have_content "Showing 1 changed file"
end
Given /^I visit compare refs page$/ do
visit compare_project_commits_path(@project)
end
Given /^I fill compare fields with refs$/ do
fill_in "from", :with => "master"
fill_in "to", :with => "stable"
......@@ -48,18 +36,6 @@ Given /^I see compared refs$/ do
page.should have_content "Showing 73 changed files"
end
Given /^I visit project branches page$/ do
visit branches_project_repository_path(@project)
end
Given /^I visit project commit page$/ do
visit project_commit_path(@project, ValidCommit::ID)
end
Given /^I visit project tags page$/ do
visit tags_project_repository_path(@project)
end
Then /^I should see "(.*?)" recent branches list$/ do |arg1|
page.should have_content("Branches")
page.should have_content("master")
......@@ -76,7 +52,7 @@ Then /^I should see "(.*?)" all tags list$/ do |arg1|
end
Then /^I should see "(.*?)" protected branches list$/ do |arg1|
within "table" do
within "table" do
page.should have_content "stable"
page.should_not have_content "master"
end
......
......@@ -8,16 +8,12 @@ Given /^project "(.*?)" have "(.*?)" closed issue$/ do |arg1, arg2|
Factory.create(:issue, :title => arg2, :project => project, :author => project.users.first, :closed => true)
end
Given /^I visit project "(.*?)" issues page$/ do |arg1|
visit project_issues_path(Project.find_by_name(arg1))
end
Given /^I should see "(.*?)" in issues$/ do |arg1|
page.should have_content arg1
page.should have_content arg1
end
Given /^I should not see "(.*?)" in issues$/ do |arg1|
page.should_not have_content arg1
page.should_not have_content arg1
end
Then /^I should see issue "(.*?)"$/ do |arg1|
......@@ -27,11 +23,6 @@ Then /^I should see issue "(.*?)"$/ do |arg1|
page.should have_content issue.project.name
end
Given /^I visit issue page "(.*?)"$/ do |arg1|
issue = Issue.find_by_title(arg1)
visit project_issue_path(issue.project, issue)
end
Given /^I submit new issue "(.*?)"$/ do |arg1|
fill_in "issue_title", with: arg1
click_button "Submit new issue"
......@@ -51,7 +42,40 @@ Given /^I visit project "(.*?)" labels page$/ do |arg1|
end
Then /^I should see label "(.*?)"$/ do |arg1|
within ".labels-table" do
within ".labels-table" do
page.should have_content arg1
end
end
Given /^I fill in issue search with "(.*?)"$/ do |arg1|
# Because fill_in, with: "" triggers nothing
# we need to trigger a keyup event
if arg1 == ''
page.execute_script("$('.issue_search').val('').keyup();");
end
fill_in 'issue_search', with: arg1
end
When /^I select milestone "(.*?)"$/ do |milestone_title|
select milestone_title, from: "milestone_id"
end
Then /^I should see selected milestone with title "(.*?)"$/ do |milestone_title|
issues_milestone_selector = "#issue_milestone_id_chzn/a"
wait_until{ page.has_content?("Details") }
page.find(issues_milestone_selector).should have_content(milestone_title)
end
When /^I select first assignee from "(.*?)" project$/ do |project_name|
project = Project.find_by_name project_name
first_assignee = project.users.first
select first_assignee.name, from: "assignee_id"
end
Then /^I should see first assignee from "(.*?)" as selected assignee$/ do |project_name|
issues_assignee_selector = "#issue_assignee_id_chzn/a"
wait_until{ page.has_content?("Details") }
project = Project.find_by_name project_name
assignee_name = project.users.first.name
page.find(issues_assignee_selector).should have_content(assignee_name)
end
......@@ -8,21 +8,17 @@ Given /^project "(.*?)" have "(.*?)" closed merge request$/ do |arg1, arg2|
Factory.create(:merge_request, :title => arg2, :project => project, :author => project.users.first, :closed => true)
end
Given /^I visit project "(.*?)" merge requests page$/ do |arg1|
visit project_merge_requests_path(Project.find_by_name(arg1))
end
Then /^I should see "(.*?)" in merge requests$/ do |arg1|
page.should have_content arg1
page.should have_content arg1
end
Then /^I should not see "(.*?)" in merge requests$/ do |arg1|
page.should_not have_content arg1
page.should_not have_content arg1
end
Then /^I should see merge request "(.*?)"$/ do |arg1|
merge_request = MergeRequest.find_by_title(arg1)
page.should have_content(merge_request.title[0..10])
page.should have_content(merge_request.title[0..10])
page.should have_content(merge_request.target_branch)
page.should have_content(merge_request.source_branch)
end
......@@ -34,11 +30,6 @@ Given /^I submit new merge request "(.*?)"$/ do |arg1|
click_button "Save"
end
Given /^I visit merge request page "(.*?)"$/ do |arg1|
mr = MergeRequest.find_by_title(arg1)
visit project_merge_request_path(mr.project, mr)
end
Then /^I should see closed merge request "(.*?)"$/ do |arg1|
mr = MergeRequest.find_by_title(arg1)
mr.closed.should be_true
......
......@@ -12,11 +12,6 @@ Given /^project "(.*?)" has milestone "(.*?)"$/ do |arg1, arg2|
end
end
Given /^I visit project "(.*?)" milestones page$/ do |arg1|
@project = Project.find_by_name(arg1)
visit project_milestones_path(@project)
end
Then /^I should see active milestones$/ do
milestone = @project.milestones.first
page.should have_content(milestone.title[0..10])
......
......@@ -8,10 +8,6 @@ Given /^"(.*?)" is "(.*?)" developer$/ do |arg1, arg2|
project.add_access(user, :write)
end
Given /^I visit project "(.*?)" team page$/ do |arg1|
visit team_project_path(Project.find_by_name(arg1))
end
Then /^I should be able to see myself in team$/ do
page.should have_content(@user.name)
page.should have_content(@user.email)
......@@ -23,15 +19,11 @@ Then /^I should see "(.*?)" in team list$/ do |arg1|
page.should have_content(user.email)
end
Given /^I click link "(.*?)"$/ do |arg1|
click_link arg1
end
Given /^I select "(.*?)" as "(.*?)"$/ do |arg1, arg2|
user = User.find_by_name(arg1)
within "#new_team_member" do
select user.name, :from => "team_member_user_id"
select arg2, :from => "team_member_project_access"
within "#new_team_member" do
select user.name, :from => "user_ids"
select arg2, :from => "project_access"
end
click_button "Save"
end
......@@ -44,7 +36,7 @@ end
Given /^I change "(.*?)" role to "(.*?)"$/ do |arg1, arg2|
user = User.find_by_name(arg1)
within ".user_#{user.id}" do
within ".user_#{user.id}" do
select arg2, :from => "team_member_project_access"
end
end
......
Given /^I visit project wiki page$/ do
visit project_wiki_path(@project, :index)
end
Given /^I create Wiki page$/ do
fill_in "Title", :with => 'Test title'
fill_in "Content", :with => '[link test](test)'
......
include LoginHelpers
Given /^I signin as a user$/ do
login_as :user
end
When /^I visit new project page$/ do
visit new_project_path
end
......@@ -65,10 +59,6 @@ Given /^I visit project "(.*?)" network page$/ do |arg1|
visit graph_project_path(project)
end
Given /^show me page$/ do
save_and_open_page
end
Given /^page should have network graph$/ do
page.should have_content "Project Network Graph"
within ".graph" do
......
Given /^I visit project "(.*?)" issues page$/ do |arg1|
visit project_issues_path(Project.find_by_name(arg1))
end
Given /^I visit issue page "(.*?)"$/ do |arg1|
issue = Issue.find_by_title(arg1)
visit project_issue_path(issue.project, issue)
end
Given /^I visit project "(.*?)" merge requests page$/ do |arg1|
visit project_merge_requests_path(Project.find_by_name(arg1))
end
Given /^I visit merge request page "(.*?)"$/ do |arg1|
mr = MergeRequest.find_by_title(arg1)
visit project_merge_request_path(mr.project, mr)
end
Given /^I visit project "(.*?)" milestones page$/ do |arg1|
@project = Project.find_by_name(arg1)
visit project_milestones_path(@project)
end
Given /^I visit project commits page$/ do
visit project_commits_path(@project)
end
Given /^I visit compare refs page$/ do
visit compare_project_commits_path(@project)
end
Given /^I visit project branches page$/ do
visit branches_project_repository_path(@project)
end
Given /^I visit project commit page$/ do
visit project_commit_path(@project, ValidCommit::ID)
end
Given /^I visit project tags page$/ do
visit tags_project_repository_path(@project)
end
Given /^I click on commit link$/ do
visit project_commit_path(@project, ValidCommit::ID)
end
Given /^I visit project source page$/ do
visit tree_project_ref_path(@project, @project.root_ref)
end
Given /^I visit project source page for "(.*?)"$/ do |arg1|
visit tree_project_ref_path(@project, arg1)
end
Given /^I visit blob file from repo$/ do
visit tree_project_ref_path(@project, ValidCommit::ID, :path => ValidCommit::BLOB_FILE_PATH)
end
Given /^I visit project "(.*?)" team page$/ do |arg1|
visit team_project_path(Project.find_by_name(arg1))
end
Given /^I visit project wiki page$/ do
visit project_wiki_path(@project, :index)
end
Given /^I visit profile page$/ do
visit profile_path
end
Given /^I visit profile token page$/ do
visit profile_token_path
end
Given /^I visit profile password page$/ do
visit profile_password_path
end
Given /^I visit dashboard page$/ do
visit dashboard_path
end
Given /^I visit dashboard issues page$/ do
visit dashboard_issues_path
end
Given /^I visit dashboard merge requests page$/ do
visit dashboard_merge_requests_path
end
......@@ -29,14 +29,24 @@ module Gitlab
# name (required) - name for new project
# code (optional) - code for new project, uses project name if not set
# path (optional) - path for new project, uses project name if not set
# description (optional) - short project description
# default_branch (optional) - 'master' by default
# issues_enabled (optional) - enabled by default
# wall_enabled (optional) - enabled by default
# merge_requests_enabled (optional) - enabled by default
# wiki_enabled (optional) - enabled by default
# Example Request
# POST /projects
post do
project = {}
project[:name] = params[:name]
project[:code] = params[:code] || project[:name]
project[:path] = params[:path] || project[:name]
@project = Project.create_by_user(project, current_user)
params[:code] ||= params[:name]
params[:path] ||= params[:name]
project_attrs = {}
params.each_pair do |k ,v|
if Project.attribute_names.include? k
project_attrs[k] = v
end
end
@project = Project.create_by_user(project_attrs, current_user)
if @project.saved?
present @project, with: Entities::Project
else
......
require 'gitolite'
require 'timeout'
require 'fileutils'
require_relative 'gitolite_config'
# TODO: refactor & cleanup
module Gitlab
class Gitolite
class AccessDenied < StandardError; end
class InvalidKey < StandardError; end
def config
Gitlab::GitoliteConfig.new
end
def set_key key_id, key_content, projects
configure do |c|
c.update_keys(key_id, key_content)
c.update_projects(projects)
config.apply do |config|
config.write_key(key_id, key_content)
config.update_projects(projects)
end
end
def remove_key key_id, projects
configure do |c|
c.delete_key(key_id)
c.update_projects(projects)
config.apply do |config|
config.rm_key(key_id)
config.update_projects(projects)
end
end
def update_repository project
configure do |c|
c.update_project(project.path, project)
end
config.update_project!(project.path, project)
end
alias_method :create_repository, :update_repository
def remove_repository project
configure do |c|
c.destroy_project(project)
end
config.destroy_project!(project)
end
def url_to_repo path
Gitlab.config.ssh_path + "#{path}.git"
end
def initialize
# create tmp dir
@local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
end
def enable_automerge
configure do |git|
git.admin_all_repo
end
end
protected
def destroy_project(project)
FileUtils.rm_rf(project.path_to_repo)
ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
conf = ga_repo.config
conf.rm_repo(project.path)
ga_repo.save
end
#update or create
def update_keys(user, key)
File.open(File.join(@local_dir, 'gitolite/keydir',"#{user}.pub"), 'w') {|f| f.write(key.gsub(/\n/,'')) }
end
def delete_key(user)
File.unlink(File.join(@local_dir, 'gitolite/keydir',"#{user}.pub"))
`cd #{File.join(@local_dir,'gitolite')} ; git rm keydir/#{user}.pub`
end
# update or create
def update_project(repo_name, project)
ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
conf = ga_repo.config
repo = update_project_config(project, conf)
conf.add_repo(repo, true)
ga_repo.save
end
# Updates many projects and uses project.path as the repo path
# An order of magnitude faster than update_project
def update_projects(projects)
ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
conf = ga_repo.config
projects.each do |project|
repo = update_project_config(project, conf)
conf.add_repo(repo, true)
end
ga_repo.save
end
def update_project_config(project, conf)
repo_name = project.path
repo = if conf.has_repo?(repo_name)
conf.get_repo(repo_name)
else
::Gitolite::Config::Repo.new(repo_name)
end
name_readers = project.repository_readers
name_writers = project.repository_writers
name_masters = project.repository_masters
pr_br = project.protected_branches.map(&:name).join("$ ")
repo.clean_permissions
# Deny access to protected branches for writers
unless name_writers.blank? || pr_br.blank?
repo.add_permission("-", pr_br.strip + "$ ", name_writers)
end
# Add read permissions
repo.add_permission("R", "", name_readers) unless name_readers.blank?
# Add write permissions
repo.add_permission("RW+", "", name_writers) unless name_writers.blank?
repo.add_permission("RW+", "", name_masters) unless name_masters.blank?
repo
end
def admin_all_repo
ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
conf = ga_repo.config
owner_name = ""
# Read gitolite-admin user
#
begin
repo = conf.get_repo("gitolite-admin")
owner_name = repo.permissions[0]["RW+"][""][0]
raise StandardError if owner_name.blank?
rescue => ex
puts "Can't determine gitolite-admin owner".red
raise StandardError
end
# @ALL repos premission for gitolite owner
repo_name = "@all"
repo = if conf.has_repo?(repo_name)
conf.get_repo(repo_name)
else
::Gitolite::Config::Repo.new(repo_name)
end
repo.add_permission("RW+", "", owner_name)
conf.add_repo(repo, true)
ga_repo.save
config.admin_all_repo!(project)
end
private
def pull
# create tmp dir
@local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
Dir.mkdir @local_dir
`git clone #{Gitlab.config.gitolite_admin_uri} #{@local_dir}/gitolite`
end
def push
Dir.chdir(File.join(@local_dir, "gitolite"))
`git add -A`
`git commit -am "Gitlab"`
`git push`
Dir.chdir(Rails.root)
FileUtils.rm_rf(@local_dir)
end
def configure
Timeout::timeout(30) do
File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f|
begin
f.flock(File::LOCK_EX)
pull
yield(self)
push
ensure
f.flock(File::LOCK_UN)
end
end
end
rescue Exception => ex
if ex.message =~ /is not a valid SSH key string/
raise Gitolite::InvalidKey.new("ssh key is not valid")
else
Gitlab::Logger.error(ex.message)
raise Gitolite::AccessDenied.new("gitolite timeout")
end
end
alias_method :create_repository, :update_repository
end
end
require 'gitolite'
require 'timeout'
require 'fileutils'
module Gitlab
class GitoliteConfig
class PullError < StandardError; end
class PushError < StandardError; end
attr_reader :config_tmp_dir, :ga_repo, :conf
def config_tmp_dir
@config_tmp_dir ||= File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
end
def ga_repo
@ga_repo ||= ::Gitolite::GitoliteAdmin.new(File.join(config_tmp_dir,'gitolite'))
end
def apply
Timeout::timeout(30) do
File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f|
begin
# Set exclusive lock
# to prevent race condition
f.flock(File::LOCK_EX)
# Pull gitolite-admin repo
# in tmp dir before do any changes
pull(config_tmp_dir)
# Build ga_repo object and @conf
# to access gitolite-admin configuration
@conf = ga_repo.config
# Do any changes
# in gitolite-admin
# config here
yield(self)
# Save changes in
# gitolite-admin repo
# before pusht it
ga_repo.save
# Push gitolite-admin repo
# to apply all changes
push(config_tmp_dir)
# Remove tmp dir
# wiith gitolite-admin
FileUtils.rm_rf(config_tmp_dir)
ensure
# unlock so other task cann access
# gitolite configuration
f.flock(File::LOCK_UN)
end
end
end
rescue PullError => ex
Gitlab::Logger.error("Pull error -> " + ex.message)
raise Gitolite::AccessDenied, ex.message
rescue PushError => ex
Gitlab::Logger.error("Push error -> " + " " + ex.message)
raise Gitolite::AccessDenied, ex.message
rescue Exception => ex
Gitlab::Logger.error(ex.class.name + " " + ex.message)
raise Gitolite::AccessDenied.new("gitolite timeout")
end
def destroy_project(project)
FileUtils.rm_rf(project.path_to_repo)
conf.rm_repo(project.path)
end
def destroy_project!(project)
apply do |config|
config.destroy_project(project)
end
end
def write_key(id, key)
File.open(File.join(config_tmp_dir, 'gitolite/keydir',"#{id}.pub"), 'w') do |f|
f.write(key.gsub(/\n/,''))
end
end
def rm_key(user)
File.unlink(File.join(config_tmp_dir, 'gitolite/keydir',"#{user}.pub"))
`cd #{File.join(config_tmp_dir,'gitolite')} ; git rm keydir/#{user}.pub`
end
# update or create
def update_project(repo_name, project)
repo = update_project_config(project, conf)
conf.add_repo(repo, true)
end
def update_project!(repo_name, project)
apply do |config|
config.update_project(repo_name, project)
end
end
# Updates many projects and uses project.path as the repo path
# An order of magnitude faster than update_project
def update_projects(projects)
projects.each do |project|
repo = update_project_config(project, conf)
conf.add_repo(repo, true)
end
end
def update_project_config(project, conf)
repo_name = project.path
repo = if conf.has_repo?(repo_name)
conf.get_repo(repo_name)
else
::Gitolite::Config::Repo.new(repo_name)
end
name_readers = project.repository_readers
name_writers = project.repository_writers
name_masters = project.repository_masters
pr_br = project.protected_branches.map(&:name).join("$ ")
repo.clean_permissions
# Deny access to protected branches for writers
unless name_writers.blank? || pr_br.blank?
repo.add_permission("-", pr_br.strip + "$ ", name_writers)
end
# Add read permissions
repo.add_permission("R", "", name_readers) unless name_readers.blank?
# Add write permissions
repo.add_permission("RW+", "", name_writers) unless name_writers.blank?
repo.add_permission("RW+", "", name_masters) unless name_masters.blank?
repo
end
# Enable access to all repos for gitolite admin.
# We use it for accept merge request feature
def admin_all_repo
owner_name = ""
# Read gitolite-admin user
#
begin
repo = conf.get_repo("gitolite-admin")
owner_name = repo.permissions[0]["RW+"][""][0]
raise StandardError if owner_name.blank?
rescue => ex
puts "Can't determine gitolite-admin owner".red
raise StandardError
end
# @ALL repos premission for gitolite owner
repo_name = "@all"
repo = if conf.has_repo?(repo_name)
conf.get_repo(repo_name)
else
::Gitolite::Config::Repo.new(repo_name)
end
repo.add_permission("RW+", "", owner_name)
conf.add_repo(repo, true)
end
def admin_all_repo!
apply { |config| config.admin_all_repo }
end
private
def pull tmp_dir
Dir.mkdir tmp_dir
`git clone #{Gitlab.config.gitolite_admin_uri} #{tmp_dir}/gitolite`
unless File.exists?(File.join(tmp_dir, 'gitolite', 'conf', 'gitolite.conf'))
raise PullError, "unable to clone gitolite-admin repo"
end
end
def push tmp_dir
Dir.chdir(File.join(tmp_dir, "gitolite"))
system('git add -A')
system('git commit -am "GitLab"')
if system('git push')
Dir.chdir(Rails.root)
else
raise PushError, "unable to push gitolite-admin repo"
end
end
end
end
module Gitlab
# Custom parser for Gitlab-flavored Markdown
# Custom parser for GitLab-flavored Markdown
#
# It replaces references in the text with links to the appropriate items in Gitlab.
# It replaces references in the text with links to the appropriate items in
# GitLab.
#
# Supported reference formats are:
# * @foo for team members
......@@ -10,19 +11,20 @@ module Gitlab
# * $123 for snippets
# * 123456 for commits
#
# Examples
# It also parses Emoji codes to insert images. See
# http://www.emoji-cheat-sheet.com/ for a list of the supported icons.
#
# >> m = Markdown.new(...)
# Examples
#
# >> m.parse("Hey @david, can you fix this?")
# >> gfm("Hey @david, can you fix this?")
# => "Hey <a href="/gitlab/team_members/1">@david</a>, can you fix this?"
#
# >> m.parse("Commit 35d5f7c closes #1234")
# >> gfm("Commit 35d5f7c closes #1234")
# => "Commit <a href="/gitlab/commits/35d5f7c">35d5f7c</a> closes <a href="/gitlab/issues/1234">#1234</a>"
class Markdown
include Rails.application.routes.url_helpers
include ActionView::Helpers
#
# >> gfm(":trollface:")
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
module Markdown
REFERENCE_PATTERN = %r{
([^\w&;])? # Prefix (1)
( # Reference (2)
......@@ -33,15 +35,57 @@ module Gitlab
([^\w&;])? # Suffix (6)
}x.freeze
EMOJI_PATTERN = %r{(:(\S+):)}.freeze
attr_reader :html_options
def initialize(project, html_options = {})
@project = project
# Public: Parse the provided text with GitLab-Flavored Markdown
#
# text - the source text
# html_options - extra options for the reference links as given to link_to
#
# Note: reference links will only be generated if @project is set
def gfm(text, html_options = {})
return text if text.nil?
# prevents the string supplied through the _text_ argument to be altered
text = text.dup
@html_options = html_options
# Extract pre blocks so they are not altered
# from http://github.github.com/github-flavored-markdown/
extractions = {}
text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) do |match|
md5 = Digest::MD5.hexdigest(match)
extractions[md5] = match
"{gfm-extraction-#{md5}}"
end
# TODO: add popups with additional information
text = parse(text)
# Insert pre block extractions
text.gsub!(/\{gfm-extraction-(\h{32})\}/) do
extractions[$1]
end
sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class)
end
private
# Private: Parses text for references and emoji
#
# text - Text to parse
#
# Note: reference links will only be generated if @project is set
#
# Returns parsed text
def parse(text)
text.gsub(REFERENCE_PATTERN) do |match|
# parse reference links
text.gsub!(REFERENCE_PATTERN) do |match|
prefix = $1 || ''
reference = $2
identifier = $3 || $4 || $5
......@@ -52,10 +96,28 @@ module Gitlab
else
match
end
end if @project
# parse emoji
text.gsub!(EMOJI_PATTERN) do |match|
if valid_emoji?($2)
image_tag("emoji/#{$2}.png", size: "20x20", class: 'emoji', title: $1, alt: $1)
else
match
end
end
text
end
private
# Private: Checks if an emoji icon exists in the image asset directory
#
# emoji - Identifier of the emoji as a string (e.g., "+1", "heart")
#
# Returns boolean
def valid_emoji?(emoji)
File.exists?(Rails.root.join('app', 'assets', 'images', 'emoji', "#{emoji}.png"))
end
# Private: Dispatches to a dedicated processing method based on reference
#
......
#!/usr/bin/env bash
# This file was placed here by Gitlab. It makes sure that your pushed commits
# This file was placed here by GitLab. It makes sure that your pushed commits
# will be processed properly.
while read oldrev newrev ref
......
IMPORT_DIRECTORY = 'import_projects'
desc "Imports existing Git repos into new projects from the import_projects folder"
task :import_projects, [:email] => :environment do |t, args|
REPOSITORY_DIRECTORY = Gitlab.config.git_base_path
desc "Imports existing Git repos from a directory into new projects in git_base_path"
task :import_projects, [:directory,:email] => :environment do |t, args|
user_email = args.email
repos_to_import = Dir.glob("#{IMPORT_DIRECTORY}/*")
import_directory = args.directory
repos_to_import = Dir.glob("#{import_directory}/*")
git_base_path = Gitlab.config.git_base_path
puts "Found #{repos_to_import.length} repos to import"
imported_count = 0
......@@ -14,11 +12,9 @@ task :import_projects, [:email] => :environment do |t, args|
failed_count = 0
repos_to_import.each do |repo_path|
repo_name = File.basename repo_path
repo_full_path = File.join(Rails.root, repo_path)
puts " Processing #{repo_name}"
clone_path = "#{REPOSITORY_DIRECTORY}/#{repo_name}.git"
clone_path = "#{git_base_path}#{repo_name}.git"
if Dir.exists? clone_path
if Project.find_by_code(repo_name)
......@@ -30,7 +26,7 @@ task :import_projects, [:email] => :environment do |t, args|
end
else
# Clone the repo
unless clone_bare_repo_as_git(repo_full_path, clone_path)
unless clone_bare_repo_as_git(repo_path, clone_path)
failed_count += 1
next
end
......@@ -48,14 +44,17 @@ task :import_projects, [:email] => :environment do |t, args|
puts "Finished importing #{imported_count} projects (skipped #{skipped_count}, failed #{failed_count})."
end
# Clones a repo as bare git repo using the git user
# Clones a repo as bare git repo using the git_user
def clone_bare_repo_as_git(existing_path, new_path)
git_user = Gitlab.config.ssh_user
begin
sh "sudo -u git -i git clone --bare '#{existing_path}' #{new_path}"
sh "sudo -u #{git_user} -i git clone --bare '#{existing_path}' #{new_path}"
true
rescue
rescue Exception=> msg
puts " ERROR: Faild to clone #{existing_path} to #{new_path}"
false
puts " Make sure #{git_user} can reach #{existing_path}"
puts " Exception-MSG: #{msg}"
false
end
end
......
namespace :gitlab do
namespace :gitolite do
desc "GITLAB | Write GITLAB hook for gitolite"
desc "GITLAB | Write GitLab hook for gitolite"
task :write_hooks => :environment do
gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common")
gitlab_hooks_path = Rails.root.join("lib", "hooks")
......
......@@ -11,6 +11,9 @@ module Factory
def self.new(type, *args)
FactoryGirl.build(type, *args)
end
def self.attributes(type, *args)
FactoryGirl.attributes_for(type, *args)
end
end
FactoryGirl.define do
......
require 'spec_helper'
describe "Factories" do
describe 'User' do
it "builds a valid instance" do
build(:user).should be_valid
end
it "builds a valid admin instance" do
build(:admin).should be_valid
end
end
describe 'Project' do
it "builds a valid instance" do
build(:project).should be_valid
end
end
describe 'Issue' do
it "builds a valid instance" do
build(:issue).should be_valid
end
it "builds a valid closed instance" do
build(:closed_issue).should be_valid
end
end
describe 'MergeRequest' do
it "builds a valid instance" do
build(:merge_request).should be_valid
end
end
describe 'Note' do
it "builds a valid instance" do
build(:note).should be_valid
end
end
describe 'Event' do
it "builds a valid instance" do
build(:event).should be_valid
end
end
describe 'Key' do
it "builds a valid instance" do
build(:key).should be_valid
end
it "builds a valid deploy key instance" do
build(:deploy_key).should be_valid
end
it "builds a valid personal key instance" do
build(:personal_key).should be_valid
end
end
describe 'Milestone' do
it "builds a valid instance" do
build(:milestone).should be_valid
end
end
describe 'SystemHook' do
it "builds a valid instance" do
build(:system_hook).should be_valid
end
end
describe 'ProjectHook' do
it "builds a valid instance" do
build(:project_hook).should be_valid
end
end
describe 'Wiki' do
it "builds a valid instance" do
build(:wiki).should be_valid
end
end
describe 'Snippet' do
it "builds a valid instance" do
build(:snippet).should be_valid
FactoryGirl.factories.map(&:name).each do |factory_name|
describe "#{factory_name} factory" do
it 'should be valid' do
build(factory_name).should be_valid
end
end
end
......@@ -208,6 +208,51 @@ describe GitlabMarkdownHelper do
gfm(actual).should match(expected)
end
end
describe "emoji" do
it "matches at the start of a string" do
gfm(":+1:").should match(/<img/)
end
it "matches at the end of a string" do
gfm("This gets a :-1:").should match(/<img/)
end
it "matches with adjacent text" do
gfm("+1 (:+1:)").should match(/<img/)
end
it "has a title attribute" do
gfm(":-1:").should match(/title=":-1:"/)
end
it "has an alt attribute" do
gfm(":-1:").should match(/alt=":-1:"/)
end
it "has an emoji class" do
gfm(":+1:").should match('class="emoji"')
end
it "sets height and width" do
actual = gfm(":+1:")
actual.should match(/width="20"/)
actual.should match(/height="20"/)
end
it "keeps whitespace intact" do
gfm("This deserves a :+1: big time.").should match(/deserves a <img.+\/> big time/)
end
it "ignores invalid emoji" do
gfm(":invalid-emoji:").should_not match(/<img/)
end
it "should work independet of reference links (i.e. without @project being set)" do
@project = nil
gfm(":+1:").should match(/<img/)
end
end
end
describe "#link_to_gfm" do
......
require 'spec_helper'
describe Gitlab::GitoliteConfig do
let(:gitolite) { Gitlab::GitoliteConfig.new }
it { should respond_to :write_key }
it { should respond_to :rm_key }
it { should respond_to :update_project }
it { should respond_to :update_project! }
it { should respond_to :update_projects }
it { should respond_to :destroy_project }
it { should respond_to :destroy_project! }
it { should respond_to :apply }
it { should respond_to :admin_all_repo }
it { should respond_to :admin_all_repo! }
end
require 'spec_helper'
describe Gitlab::Gitolite do
let(:project) { double('Project', path: 'diaspora') }
let(:gitolite_config) { double('Gitlab::GitoliteConfig') }
let(:gitolite) { Gitlab::Gitolite.new }
before do
gitolite.stub(config: gitolite_config)
end
it { should respond_to :set_key }
it { should respond_to :remove_key }
it { should respond_to :update_repository }
it { should respond_to :create_repository }
it { should respond_to :remove_repository }
it { gitolite.url_to_repo('diaspora').should == Gitlab.config.ssh_path + "diaspora.git" }
it "should call config update" do
gitolite_config.should_receive(:update_project!)
gitolite.update_repository project
end
end
......@@ -24,7 +24,7 @@ describe Notify do
end
it 'has the correct subject' do
should have_subject /^gitlab \| Account was created for you$/
should have_subject /^gitlab \| Account was created for you$/i
end
it 'contains the new user\'s login name' do
......
......@@ -35,6 +35,16 @@ describe Note do
note = Factory(:note, note: "-1 for this")
note.should_not be_upvote
end
it "recognizes a +1 emoji as a vote" do
note = build(:note, note: ":+1: for this")
note.should be_upvote
end
it "recognizes a neutral emoji note" do
note = build(:note, note: "I would :+1: this, but I don't want to")
note.should_not be_upvote
end
end
let(:project) { create(:project) }
......
......@@ -10,7 +10,7 @@ describe UsersProject do
let!(:users_project) { create(:users_project) }
it { should validate_presence_of(:user_id) }
it { should validate_uniqueness_of(:user_id).scoped_to(:project_id) }
it { should validate_uniqueness_of(:user_id).scoped_to(:project_id).with_message(/already exists/) }
it { should validate_presence_of(:project_id) }
end
......
......@@ -30,38 +30,40 @@ describe Gitlab::API do
describe "POST /projects" do
it "should create new project without code and path" do
lambda {
name = "foo"
post api("/projects", user), {
name: name
}
response.status.should == 201
json_response["name"].should == name
json_response["code"].should == name
json_response["path"].should == name
}.should change{Project.count}.by(1)
end
it "should create new project" do
lambda {
name = "foo"
path = "bar"
code = "bazz"
post api("/projects", user), {
code: code,
path: path,
name: name
}
response.status.should == 201
json_response["name"].should == name
json_response["path"].should == path
json_response["code"].should == code
}.should change{Project.count}.by(1)
end
it "should not create project without name" do
lambda {
post api("/projects", user)
response.status.should == 404
}.should_not change{Project.count}
expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1)
end
it "should not create new project without name" do
expect { post api("/projects", user) }.to_not change {Project.count}
end
it "should respond with 201 on success" do
post api("/projects", user), name: 'foo'
response.status.should == 201
end
it "should repsond with 404 on failure" do
post api("/projects", user)
response.status.should == 404
end
it "should assign attributes to project" do
project = Factory.attributes(:project, {
path: 'path',
code: 'code',
description: Faker::Lorem.sentence,
default_branch: 'stable',
issues_enabled: false,
wall_enabled: false,
merge_requests_enabled: false,
wiki_enabled: false
})
post api("/projects", user), project
project.each_pair do |k,v|
json_response[k.to_s].should == v
end
end
end
......
......@@ -17,7 +17,7 @@ module GitoliteStub
)
gitolite_admin = double(
'Gitolite::GitoliteAdmin',
'Gitolite::GitoliteAdmin',
config: gitolite_config,
save: true,
)
......@@ -27,9 +27,21 @@ module GitoliteStub
end
def stub_gitlab_gitolite
gitlab_gitolite = Gitlab::Gitolite.new
Gitlab::Gitolite.stub(new: gitlab_gitolite)
gitlab_gitolite.stub(configure: ->() { yield(self) })
gitlab_gitolite.stub(update_keys: true)
gitolite_config = double('Gitlab::GitoliteConfig')
gitolite_config.stub(
apply: ->() { yield(self) },
write_key: true,
rm_key: true,
update_projects: true,
update_project: true,
update_project!: true,
destroy_project: true,
destroy_project!: true,
admin_all_repo: true,
admin_all_repo!: true,
)
Gitlab::GitoliteConfig.stub(new: gitolite_config)
end
end
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