Commit ee8836a9 authored by Jacob Vosmaer's avatar Jacob Vosmaer

Merge commit '27cf081e' into merge_ce_27cf081e

Conflicts:
	VERSION
	app/models/user.rb
	app/services/merge_requests/build_service.rb
	config/gitlab.yml.example
	db/schema.rb
	doc/integration/README.md
	features/project/merge_requests.feature
	features/steps/project/merge_requests.rb
parents 3d469188 27cf081e
......@@ -17,8 +17,8 @@ env:
- TASK=jasmine:ci DB=postgresql
before_install:
- sudo apt-get install libicu-dev -y
install:
- "bundle install --deployment --without production"
install:
- "travis_retry bundle install --deployment --without production --retry 5"
branches:
only:
- 'master'
......
v 7.2.0
- Explore page
- Add project stars (Ciro Santilli)
- Log Sidekiq arguments
- Fix cpu usage issue in Firefox
- Better labels: colors, ability to rename and remove
- Improve the way merge request collects diffs
- Improve compare page for large diffs
- Expose the full commit message via API
- Fix 500 error on repository rename
- Fix bug when MR download patch return invalid diff
- Test gitlab-shell integration
- Repository import timeout increased from 2 to 4 minutes allowing larger repos to be imported
- API for labels (Robert Schilling)
v 7.1.1
- Fix cpu usage issue in Firefox
- Fix redirect loop when changing password by new user
- Fix 500 error on new merge request page
v 7.1.0
- Remove observers
- Improve MR discussions
......@@ -8,7 +28,7 @@ v 7.1.0
- Dont show reply button if user is not signed in
- Expose more information for issues with webhook
- Add a mention of the merge request into the default merge request commit message
- Imrpove code highlight, introduce support for more languages like Go, Clojure, Erlang etc
- Improve code highlight, introduce support for more languages like Go, Clojure, Erlang etc
- Fix concurrency issue in repository download
- Dont allow repository name start with ?
- Improve email threading (Pierre de La Morinerie)
......
......@@ -80,16 +80,20 @@ gem "six"
gem "seed-fu"
# Markdown to HTML
gem "redcarpet", "~> 2.2.2"
gem "github-markup"
gem "org-ruby" # For rendering .org files
# Required markup gems by github-markdown
gem 'redcarpet', '~> 2.2.2'
gem 'RedCloth'
gem 'rdoc', '~>3.6'
gem 'org-ruby', '= 0.9.1'
gem 'creole', '~>0.3.6'
gem 'wikicloth', '=0.8.1'
gem 'asciidoctor', '= 0.1.4'
# Diffs
gem 'diffy', '~> 3.0.3'
# Asciidoc to HTML
gem "asciidoctor"
# Application server
group :unicorn do
gem "unicorn", '~> 4.6.3'
......@@ -175,6 +179,7 @@ gem "gitlab_emoji", "~> 0.0.1.1"
gem "gon", '~> 5.0.0'
gem 'nprogress-rails'
gem 'request_store'
gem "virtus"
group :development do
gem "annotate", "~> 2.6.0.beta2"
......
GEM
remote: https://rubygems.org/
specs:
RedCloth (4.2.9)
ace-rails-ap (2.0.1)
actionmailer (4.1.1)
actionpack (= 4.1.1)
......@@ -86,6 +87,7 @@ GEM
thor
crack (0.4.1)
safe_yaml (~> 0.9.0)
creole (0.3.8)
d3_rails (3.1.10)
railties (>= 3.1.0)
daemons (1.1.9)
......@@ -121,6 +123,7 @@ GEM
eventmachine (1.0.3)
excon (0.32.1)
execjs (2.0.2)
expression_parser (0.9.0)
factory_girl (4.3.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.3.0)
......@@ -176,12 +179,12 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1)
gitlab_git (6.0.1)
gitlab_git (6.2.1)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-grit (~> 2.6)
gitlab-linguist (~> 3.0)
rugged (~> 0.19.0)
rugged (~> 0.21.0)
gitlab_meta (7.0)
gitlab_omniauth-ldap (1.0.4)
net-ldap (~> 0.3.1)
......@@ -241,7 +244,7 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpauth (0.2.0)
i18n (0.6.9)
i18n (0.6.11)
ice_nine (0.10.0)
jasmine (2.0.2)
jasmine-core (~> 2.0.0)
......@@ -320,7 +323,7 @@ GEM
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
org-ruby (0.9.6)
org-ruby (0.9.1)
rubypants (>= 0.2.0)
orm_adapter (0.5.0)
pg (0.15.1)
......@@ -331,7 +334,7 @@ GEM
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
polyglot (0.3.4)
posix-spawn (0.3.8)
posix-spawn (0.3.9)
pry (0.9.12.4)
coderay (~> 1.0)
method_source (~> 0.8)
......@@ -413,6 +416,7 @@ GEM
require_all (1.3.2)
rest-client (1.6.7)
mime-types (>= 1.16)
rinku (1.7.3)
rouge (1.3.3)
rspec (2.14.1)
rspec-core (~> 2.14.0)
......@@ -432,7 +436,7 @@ GEM
ruby-progressbar (1.2.0)
rubyntlm (0.1.1)
rubypants (0.2.0)
rugged (0.19.0)
rugged (0.21.0)
safe_yaml (0.9.7)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
......@@ -536,7 +540,7 @@ GEM
eventmachine (>= 0.12.8)
http_parser.rb (~> 0.5.1)
simple_oauth (~> 0.1.4)
tzinfo (1.2.1)
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (2.3.2)
execjs (>= 0.3.0)
......@@ -563,6 +567,10 @@ GEM
addressable (>= 2.2.7)
crack (>= 0.3.2)
websocket-driver (0.3.3)
wikicloth (0.8.1)
builder
expression_parser
rinku
xpath (2.0.0)
nokogiri (~> 1.3)
......@@ -570,10 +578,11 @@ PLATFORMS
ruby
DEPENDENCIES
RedCloth
ace-rails-ap
acts-as-taggable-on
annotate (~> 2.6.0.beta2)
asciidoctor
asciidoctor (= 0.1.4)
awesome_print
better_errors
binding_of_caller
......@@ -583,6 +592,7 @@ DEPENDENCIES
coffee-rails
colored
coveralls
creole (~> 0.3.6)
d3_rails (~> 3.1.4)
database_cleaner
default_value_for (~> 3.0.0)
......@@ -633,7 +643,7 @@ DEPENDENCIES
omniauth-github
omniauth-google-oauth2
omniauth-twitter
org-ruby
org-ruby (= 0.9.1)
pg
poltergeist (~> 1.5.1)
pry
......@@ -647,6 +657,7 @@ DEPENDENCIES
raphael-rails (~> 2.1.2)
rb-fsevent
rb-inotify
rdoc (~> 3.6)
redcarpet (~> 2.2.2)
redis-rails
request_store
......@@ -682,4 +693,6 @@ DEPENDENCIES
unicorn (~> 4.6.3)
unicorn-worker-killer
version_sorter
virtus
webmock
wikicloth (= 0.8.1)
......@@ -85,7 +85,7 @@ or by directly calling the script:
sudo /etc/init.d/gitlab start
Please login with `root` / `5iveL!fe`.
Please login with `root` / `5iveL!fe`
## Install a development environment
......
7.1.0-ee
7.2.0-ee.pre
......@@ -53,15 +53,40 @@ window.split = (val) ->
window.extractLast = (term) ->
return split( term ).pop()
window.rstrip = (val) ->
return val.replace(/\s+$/, '')
# Disable button if text field is empty
window.disableButtonIfEmptyField = (field_selector, button_selector) ->
field = $(field_selector)
closest_submit = field.closest("form").find(button_selector)
closest_submit = field.closest('form').find(button_selector)
closest_submit.disable() if rstrip(field.val()) is ""
field.on 'input', ->
if rstrip($(@).val()) is ""
closest_submit.disable()
else
closest_submit.enable()
# Disable button if any input field with given selector is empty
window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) ->
closest_submit = form.find(button_selector)
empty = false
form.find('input').filter(form_selector).each ->
empty = true if rstrip($(this).val()) is ""
if empty
closest_submit.disable()
else
closest_submit.enable()
closest_submit.disable() if field.val() is ""
form.keyup ->
empty = false
form.find('input').filter(form_selector).each ->
empty = true if rstrip($(this).val()) is ""
field.on "input", ->
if $(@).val() is ""
if empty
closest_submit.disable()
else
closest_submit.enable()
......
class Diff
UNFOLD_COUNT = 20
constructor: ->
$(document).on('click', '.js-unfold', (event) =>
target = $(event.target)
unfoldBottom = target.hasClass('js-unfold-bottom')
unfold = true
[old_line, line_number] = @lineNumbers(target.parent())
offset = line_number - old_line
if unfoldBottom
line_number += 1
since = line_number
to = line_number + UNFOLD_COUNT
else
[prev_old_line, prev_new_line] = @lineNumbers(target.parent().prev())
line_number -= 1
to = line_number
if line_number - UNFOLD_COUNT > prev_new_line + 1
since = line_number - UNFOLD_COUNT
else
since = prev_new_line + 1
unfold = false
link = target.parents('.diff-file').attr('data-blob-diff-path')
params =
since: since
to: to
bottom: unfoldBottom
offset: offset
unfold: unfold
$.get(link, params, (response) =>
target.parent().replaceWith(response)
)
)
lineNumbers: (line) ->
return ([0, 0]) unless line.children().length
lines = line.children().slice(0, 2)
line_numbers = ($(l).attr('data-linenumber') for l in lines)
(parseInt(line_number) for line_number in line_numbers)
@Diff = Diff
......@@ -23,13 +23,21 @@ class Dispatcher
new Issue()
when 'projects:milestones:show'
new Milestone()
when 'projects:issues:new', 'projects:merge_requests:new'
when 'projects:issues:new'
GitLab.GfmAutoComplete.setup()
when 'projects:merge_requests:new'
GitLab.GfmAutoComplete.setup()
new Diff()
when 'projects:merge_requests:show'
new Diff()
when "projects:merge_requests:diffs"
new Diff()
when 'dashboard:show'
new Dashboard()
new Activities()
when 'projects:commit:show'
new Commit()
new Diff()
when 'groups:show', 'projects:show'
new Activities()
when 'projects:new', 'projects:edit'
......@@ -42,6 +50,8 @@ class Dispatcher
new TreeView()
when 'projects:blob:show'
new BlobView()
when 'projects:labels:new'
new Labels()
switch path.first()
when 'admin' then new Admin()
......
class Labels
constructor: ->
form = $('.label-form')
@setupLabelForm(form)
@cleanBinding()
@addBinding()
@updateColorPreview
addBinding: ->
$(document).on 'click', '.suggest-colors a', @setSuggestedColor
$(document).on 'input', 'input#label_color', @updateColorPreview
cleanBinding: ->
$(document).off 'click', '.suggest-colors a'
$(document).off 'input', 'input#label_color'
# Initializes the form to disable the save button if no color or title is entered
setupLabelForm: (form) ->
disableButtonIfAnyEmptyField form, '.form-control', form.find('.js-save-button')
# Updates the the preview color with the hex-color input
updateColorPreview: =>
previewColor = $('input#label_color').val()
$('div.label-color-preview').css('background-color', previewColor)
# Updates the preview color with a click on a suggested color
setSuggestedColor: (e) =>
color = $(e.currentTarget).data('color')
$('input#label_color').val(color)
@updateColorPreview()
# Notify the form, that color has changed
$('.label-form').trigger('keyup')
e.preventDefault()
@Labels = Labels
......@@ -20,6 +20,9 @@ $(document).ready ->
$(".div-dropzone-hover").append iconPicture
$(".div-dropzone").append divSpinner
$(".div-dropzone-spinner").append iconSpinner
$(".div-dropzone-spinner").css
"opacity": 0
"display": "none"
dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload
......@@ -66,13 +69,17 @@ $(document).ready ->
return
sending: ->
$(".div-dropzone-spinner").css "opacity", 0.7
$(".div-dropzone-spinner").css
"opacity": 0.7
"display": "inherit"
return
complete: ->
$(".dz-preview").remove()
$(".markdown-area").trigger "input"
$(".div-dropzone-spinner").css "opacity", 0
$(".div-dropzone-spinner").css
"opacity": 0
"display": "none"
return
)
......@@ -163,10 +170,14 @@ $(document).ready ->
val + url + "\n"
showSpinner = (e) ->
$(".div-dropzone-spinner").css "opacity", 0.7
$(".div-dropzone-spinner").css
"opacity": 0.7
"display": "inherit"
closeSpinner = ->
$(".div-dropzone-spinner").css "opacity", 0
$(".div-dropzone-spinner").css
"opacity": 0
"display": "none"
showError = (message) ->
checkIfMsgExists = $(".error-alert").children().length
......
......@@ -321,7 +321,9 @@ class Notes
GitLab.GfmAutoComplete.setup()
form = note.find(".note-edit-form")
form.show()
form.find("textarea").focus()
textarea = form.find("textarea")
textarea.focus()
disableButtonIfEmptyField textarea, form.find(".js-comment-button")
###
Called in response to clicking the edit note link
......
@Pager =
limit: 0
offset: 0
disable: false
init: (limit, preload) ->
@limit = limit
init: (@limit = 0, preload, @disable = false) ->
@loading = $(".loading")
if preload
@offset = 0
@getOld()
else
@offset = limit
@offset = @limit
@initLoadMore()
getOld: ->
$(".loading").show()
@loading.show()
$.ajax
type: "GET"
url: location.href
data: "limit=" + @limit + "&offset=" + @offset
complete: ->
$(".loading").hide()
complete: =>
@loading.hide()
success: (data) ->
Pager.append(data.count, data.html)
dataType: "json"
......@@ -39,6 +36,7 @@
ceaseFire: ->
Pager.disable
callback: (i) ->
$(".loading").show()
Pager.getOld()
callback: (i) =>
unless @loading.is(':visible')
@loading.show()
Pager.getOld()
......@@ -54,3 +54,8 @@ $ ->
$.cookie('hide_no_ssh_message', 'false', { path: path })
$(@).parents('.no-ssh-key-message').hide()
e.preventDefault()
$('.project-side .star').on 'ajax:success', (e, data, status, xhr) ->
$(@).toggleClass('on').find('.count').html(data.star_count)
.on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
......@@ -59,4 +59,4 @@
/**
* Styles for responsive sidebar
*/
@import "semantic-ui/modules/sidebar"
@import "semantic-ui/modules/sidebar";
......@@ -4,3 +4,9 @@
.js-details-container .content.hide { display: block; }
.js-details-container.open .content { display: block; }
.js-details-container.open .content.hide { display: none; }
// Toggle between two states.
.js-toggler-container .turn-on { display: block; }
.js-toggler-container .turn-off { display: none; }
.js-toggler-container.on .turn-on { display: none; }
.js-toggler-container.on .turn-off { display: block; }
......@@ -6,7 +6,7 @@
vertical-align: middle;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
border: $btn-border;
white-space: nowrap;
padding: 6px 12px;
font-size: 13px;
......@@ -19,7 +19,6 @@
user-select: none;
color: #444444;
background-color: #fff;
border-color: #ccc;
text-shadow: none;
&.hover,
......
......@@ -350,3 +350,18 @@ table {
.footer-links a {
margin-right: 15px;
}
.search_box {
position: relative;
padding: 30px;
text-align: center;
background-color: #F9F9F9;
border: 1px solid #DDDDDD;
border-radius: 0px;
}
.search_glyph {
color: #555;
font-size: 42px;
}
......@@ -10,6 +10,8 @@ $hover: #D9EDF7;
$link_color: #446e9b;
$link_hover_color: #2FA0BB;
$btn-border: 1px solid #ccc;
/*
* Success colors (green)
*/
......
......@@ -244,6 +244,7 @@ li.commit {
font-family: inherit;
padding-left: $left;
position: relative;
resize: vertical;
z-index: 2;
}
}
......@@ -40,14 +40,17 @@
font-size: 12px;
.old {
span.idiff {
background-color: #FAA;
background-color: #F99;
}
}
.new {
span.idiff {
background-color: #AFA;
background-color: #8F8;
}
}
.unfold {
cursor: pointer;
}
.file-mode-changed {
padding: 10px;
......
.explore-title {
text-align: center;
h3 {
font-weight: normal;
font-size: 30px;
}
}
......@@ -63,26 +63,10 @@
@media (min-width: 800px) { .issues_bulk_update .select2-container { min-width: 120px; } }
@media (min-width: 1200px) { .issues_bulk_update .select2-container { min-width: 160px; } }
.issues-holder {
.issues_filters {
}
.issues_bulk_update {
margin: 0;
form {
float:left;
}
.update_selected_issues {
margin-left: 4px;
}
.select2-container .select2-choice {
height: 32px;
line-height: 28px;
color: #444 !important;
font-weight: 500;
}
.issues_bulk_update {
.select2-container .select2-choice {
color: #444 !important;
font-weight: 500;
}
}
......@@ -110,7 +94,7 @@
}
}
.issue-show-labels .label {
.issue-show-labels .color-label {
padding: 6px 10px;
}
......
.suggest-colors {
margin-top: 5px;
a {
@include border-radius(4px);
width: 30px;
height: 30px;
display: inline-block;
margin-right: 10px;
}
}
.manage-labels-list {
.label {
padding: 9px;
font-size: 14px;
}
}
.color-label {
padding: 3px 4px;
}
......@@ -13,6 +13,10 @@
max-width: 100%;
margin-bottom: 20px;
}
&.default-brand-image {
margin: 0 80px;
}
}
.login-logo{
......
/**
* MR -> show: Automerge widget
/**
* MR -> show: Automerge widget
*
*/
.automerge_widget {
......@@ -48,10 +48,10 @@
.label-branch {
@include border-radius(4px);
padding: 2px 4px;
padding: 3px 4px;
border: none;
background: #555;
color: #fff;
background: $hover;
color: #333;
font-family: $monospace_font;
font-weight: normal;
overflow: hidden;
......
......@@ -190,28 +190,39 @@ ul.nav.nav-projects-tabs {
.project-side {
.btn-block {
background-image: none;
.btn,
&.btn,
&.btn-group ul.dropdown-menu {
.btn, &.btn {
text-align: left;
padding: 10px 15px;
background-color: #F1f1f1;
border-color: #EEE;
&:hover {
background-color: #eee;
border-color: #DDD;
}
}
&.btn-group-justified {
.btn {
width: 100%;
}
.dropdown-toggle {
width: 26px;
}
.count {
float: right;
font-weight: 500;
text-shadow: 0 1px #FFF;
}
ul {
&.btn-group-justified {
.btn {
width: 100%;
}
.dropdown-toggle {
width: 30px;
padding: 10px;
}
ul {
width: 100%;
}
}
}
.project-fork-icon {
float: left;
font-size: 26px;
......
......@@ -39,12 +39,13 @@ class Admin::UsersController < Admin::ApplicationController
def create
opts = {
force_random_password: true,
password_expires_at: Time.now
password_expires_at: nil
}
@user = User.new(user_params.merge(opts))
@user.created_by_id = current_user.id
@user.generate_password
@user.generate_reset_token
@user.skip_confirmation!
respond_to do |format|
......
......@@ -46,11 +46,11 @@ class DashboardController < ApplicationController
@projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present?
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.includes(:namespace)
@projects = @projects.tagged_with(params[:label]) if params[:label].present?
@projects = @projects.tagged_with(params[:tag]) if params[:tag].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]).per(30)
@labels = current_user.authorized_projects.tags_on(:labels)
@tags = current_user.authorized_projects.tags_on(:tags)
@groups = current_user.authorized_groups
end
......
class Public::ProjectsController < ApplicationController
class Explore::GroupsController < ApplicationController
skip_before_filter :authenticate_user!,
:reject_blocked, :set_current_user_for_observers,
:add_abilities
layout 'public'
layout "explore"
def index
@projects = Project.publicish(current_user)
@projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(20)
@groups = GroupsFinder.new.execute(current_user)
@groups = @groups.search(params[:search]) if params[:search].present?
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.page(params[:page]).per(20)
end
end
class Explore::ProjectsController < ApplicationController
skip_before_filter :authenticate_user!,
:reject_blocked,
:add_abilities
layout 'explore'
def index
@projects = ProjectsFinder.new.execute(current_user)
@projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(20)
end
def trending
@trending_projects = TrendingProjectsFinder.new.execute(current_user)
@trending_projects = @trending_projects.page(params[:page]).per(10)
end
def starred
@starred_projects = ProjectsFinder.new.execute(current_user)
@starred_projects = @starred_projects.order('star_count DESC')
@starred_projects = @starred_projects.page(params[:page]).per(10)
end
end
......@@ -11,6 +11,11 @@ class Profiles::PasswordsController < ApplicationController
end
def create
unless @user.valid_password?(user_params[:current_password])
redirect_to new_profile_password_path, alert: 'You must provide a valid current password'
return
end
new_password = user_params[:password]
new_password_confirmation = user_params[:password_confirmation]
......
......@@ -25,6 +25,21 @@ class Projects::BlobController < Projects::ApplicationController
end
end
def diff
@form = UnfoldForm.new(params)
@lines = @blob.data.lines[@form.since - 1..@form.to - 1]
if @form.bottom?
@match_line = ''
else
lines_length = @lines.length - 1
line = [@form.since, lines_length].join(',')
@match_line = "@@ -#{line}+#{line} @@"
end
render layout: false
end
private
def blob
......
......@@ -8,14 +8,21 @@ class Projects::CompareController < Projects::ApplicationController
end
def show
compare = Gitlab::Git::Compare.new(@repository.raw_repository, params[:from], params[:to], MergeRequestDiff::COMMITS_SAFE_SIZE)
base_ref = params[:from]
head_ref = params[:to]
@commits = compare.commits
@commit = compare.commit
@diffs = compare.diffs
@refs_are_same = compare.same
@line_notes = []
@diff_timeout = compare.timeout
compare_result = CompareService.new.execute(
current_user,
@project,
head_ref,
@project,
base_ref
)
@commits = compare_result.commits
@diffs = compare_result.diffs
@commit = @commits.last
@line_notes = []
end
def create
......
......@@ -24,7 +24,17 @@ class Projects::HooksController < Projects::ApplicationController
end
def test
TestHookService.new.execute(hook, current_user)
if !@project.empty_repo?
status = TestHookService.new.execute(hook, current_user)
if status
flash[:notice] = 'Hook successfully executed.'
else
flash[:alert] = 'Hook execution failed. '\
'Ensure hook URL is correct and service is up.'
end
else
flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
end
redirect_to :back
end
......
......@@ -152,7 +152,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params
params.require(:issue).permit(
:title, :assignee_id, :position, :description,
:milestone_id, :label_list, :state_event
:milestone_id, :state_event, label_ids: []
)
end
end
class Projects::LabelsController < Projects::ApplicationController
before_filter :module_enabled
before_filter :label, only: [:edit, :update, :destroy]
before_filter :authorize_labels!
before_filter :authorize_admin_labels!, except: [:index]
respond_to :js, :html
def index
@labels = @project.issues_labels
@labels = @project.labels.order_by_name.page(params[:page]).per(20)
end
def new
@label = @project.labels.new
end
def create
@label = @project.labels.create(label_params)
if @label.valid?
redirect_to project_labels_path(@project)
else
render 'new'
end
end
def edit
end
def update
if @label.update_attributes(label_params)
redirect_to project_labels_path(@project)
else
render 'edit'
end
end
def generate
......@@ -21,6 +47,15 @@ class Projects::LabelsController < Projects::ApplicationController
end
end
def destroy
@label.destroy
respond_to do |format|
format.html { redirect_to project_labels_path(@project), notice: 'Label was removed' }
format.js { render nothing: true }
end
end
protected
def module_enabled
......@@ -28,4 +63,16 @@ class Projects::LabelsController < Projects::ApplicationController
return render_404
end
end
def label_params
params.require(:label).permit(:title, :color)
end
def label
@label = @project.labels.find(params[:id])
end
def authorize_admin_labels!
return render_404 unless can?(current_user, :admin_label, @project)
end
end
......@@ -70,7 +70,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@target_project = merge_request.target_project
@source_project = merge_request.source_project
@commits = @merge_request.compare_commits
@commit = @merge_request.compare_base_commit
@commit = @merge_request.compare_commits.last
@diffs = @merge_request.compare_diffs
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
......@@ -242,7 +242,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id,
:state_event, :description, :label_list
:state_event, :description, label_ids: []
)
end
end
......@@ -14,11 +14,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
render_404 and return
end
storage_path = Gitlab.config.gitlab.repository_downloads_path
@repository.clean_old_archives
file_path = @repository.archive_repo(params[:ref], storage_path, params[:format].downcase)
file_path = ArchiveRepositoryService.new.execute(@project, params[:ref], params[:format])
if file_path
# Send file to user
......
......@@ -12,12 +12,10 @@ class Projects::WikisController < Projects::ApplicationController
def show
@page = @project_wiki.find_page(params[:id], params[:version_id])
gollum_wiki = @project_wiki.wiki
file = gollum_wiki.file(params[:id], gollum_wiki.ref, true)
if @page
render 'show'
elsif file
elsif file = @project_wiki.find_file(params[:id], params[:version_id])
if file.on_disk?
send_file file.on_disk_path, disposition: 'inline'
else
......
......@@ -60,6 +60,8 @@ class ProjectsController < ApplicationController
@events = event_filter.apply_filter(@events)
@events = @events.limit(limit).offset(params[:offset] || 0)
@show_star = !(current_user && current_user.starred?(@project))
respond_to do |format|
format.html do
if @project.empty_repo?
......@@ -167,6 +169,12 @@ class ProjectsController < ApplicationController
end
end
def toggle_star
current_user.toggle_star(@project)
@project.reload
render json: { star_count: @project.star_count }
end
private
def upload_path
......@@ -188,7 +196,7 @@ class ProjectsController < ApplicationController
def project_params
params.require(:project).permit(
:name, :path, :description, :issues_tracker, :label_list,
:name, :path, :description, :issues_tracker, :tag_list,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :merge_requests_template
)
......
class SessionsController < Devise::SessionsController
def new
redirect_url = if request.referer.present?
redirect_path = if request.referer.present? && (params['redirect_to_referer'] == 'yes')
referer_uri = URI(request.referer)
if referer_uri.host == Gitlab.config.gitlab.host
referer_uri.path
......@@ -12,7 +12,11 @@ class SessionsController < Devise::SessionsController
request.fullpath
end
store_location_for(:redirect, redirect_url)
# Prevent a 'you are already signed in' message directly after signing:
# we should never redirect to '/users/sign_in' after signing in successfully.
unless redirect_path == '/users/sign_in'
store_location_for(:redirect, redirect_path)
end
super
end
......
......@@ -125,7 +125,13 @@ class BaseFinder
def by_label(items)
if params[:label_name].present?
items = items.tagged_with(params[:label_name])
label_names = params[:label_name].split(",")
item_ids = LabelLink.joins(:label).
where('labels.title in (?)', label_names).
where(target_type: klass.name).pluck(:target_id)
items = items.where(id: item_ids)
end
items
......
class TrendingProjectsFinder
def execute(current_user, start_date = nil)
start_date ||= Date.today - 1.month
projects = projects_for(current_user)
# Determine trending projects based on comments count
# for period of time - ex. month
projects.joins(:notes).where('notes.created_at > ?', start_date).
select("projects.*, count(notes.id) as ncount").
group("projects.id").order("ncount DESC")
end
private
def projects_for(current_user)
ProjectsFinder.new.execute(current_user)
end
end
......@@ -221,7 +221,18 @@ module ApplicationHelper
end
def render_markup(file_name, file_content)
GitHub::Markup.render(file_name, file_content).html_safe
GitHub::Markup.render(file_name, file_content).
force_encoding(file_content.encoding).html_safe
rescue RuntimeError
simple_format(file_content)
end
def markup?(filename)
Gitlab::MarkdownHelper.markup?(filename)
end
def gitlab_markdown?(filename)
Gitlab::MarkdownHelper.gitlab_markdown?(filename)
end
def spinner(text = nil, visible = false)
......
......@@ -232,4 +232,16 @@ module CommitsHelper
def diff_file_mode_changed?(diff)
diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
end
def unfold_bottom_class(bottom)
(bottom) ? 'js-unfold-bottom' : ''
end
def view_file_btn(commit_sha, diff, project)
link_to project_blob_path(project, tree_join(commit_sha, diff.new_path)),
class: 'btn btn-small view-file js-view-file' do
raw('View file @') + content_tag(:span, commit_sha[0..6],
class: 'commit-short-id')
end
end
end
......@@ -179,7 +179,11 @@ module GitlabMarkdownHelper
if @commit
@commit.id
elsif @repository && !@repository.empty?
@repository.head_commit.sha
if @ref
@repository.commit(@ref).try(:sha)
else
@repository.head_commit.sha
end
end
end
......
module LabelsHelper
def issue_label_names
@project.issues_labels.map(&:name)
def project_label_names
@project.labels.pluck(:title)
end
def labels_autocomplete_source
labels = @project.issues_labels
labels = labels.map{ |l| { label: l.name, value: l.name } }
labels.to_json
def render_colored_label(label)
label_color = label.color || Label::DEFAULT_COLOR
text_color = text_color_for_bg(label_color)
content_tag :span, class: 'label color-label', style: "background:#{label_color};color:#{text_color}" do
label.name
end
end
def suggested_colors
[
'#d9534f',
'#f0ad4e',
'#428bca',
'#5cb85c',
'#34495e',
'#7f8c8d',
'#8e44ad',
'#FFECDB'
]
end
def label_css_class(name)
klass = Gitlab::IssuesLabels
def text_color_for_bg(bg_color)
r, g, b = bg_color.slice(1,7).scan(/.{2}/).map(&:hex)
case name.downcase
when *klass.warning_labels
'label-warning'
when *klass.neutral_labels
'label-primary'
when *klass.positive_labels
'label-success'
when *klass.important_labels
'label-danger'
if (r + g + b) > 500
"#333"
else
'label-info'
"#FFF"
end
end
end
......@@ -122,6 +122,40 @@ module ProjectsHelper
options_for_select(values, current_tracker)
end
def link_to_toggle_star(title, starred, signed_in)
cls = 'btn btn-block'
cls += ' disabled' unless signed_in
toggle_html = content_tag('span', class: 'toggle') do
toggle_text = if starred
'Unstar'
else
'Star'
end
content_tag('i', ' ', class: 'icon-star') + toggle_text
end
count_html = content_tag('span', class: 'count') do
@project.star_count.to_s
end
link_opts = {
title: title,
class: cls,
method: :post,
remote: true,
data: {type: 'json'}
}
content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do
link_to toggle_star_project_path(@project), link_opts do
toggle_html + count_html
end
end
end
private
def get_project_nav_tabs(project, current_user)
......
......@@ -21,6 +21,16 @@ module TreeHelper
tree.html_safe
end
def render_readme(readme)
if gitlab_markdown?(readme.name)
preserve(markdown(readme.data))
elsif markup?(readme.name)
render_markup(readme.name, readme.data)
else
simple_format(readme.data)
end
end
# Return an image icon depending on the file type
#
# type - String type of the tree item; either 'folder' or 'file'
......@@ -38,24 +48,6 @@ module TreeHelper
"file_#{hexdigest(content.name)}"
end
# Public: Determines if a given filename is compatible with GitHub::Markup.
#
# filename - Filename string to check
#
# Returns boolean
def markup?(filename)
filename.downcase.end_with?(*%w(.textile .rdoc .org .creole
.mediawiki .rst .adoc .asciidoc .pod))
end
def gitlab_markdown?(filename)
filename.downcase.end_with?(*%w(.mdown .md .markdown))
end
def plain_text_readme? filename
filename =~ /^README(.txt)?$/i
end
# Simple shortcut to File.join
def tree_join(*args)
File.join(*args)
......
......@@ -49,9 +49,10 @@ module Emails
@updated_by = User.find updated_by_user_id
@target_url = project_merge_request_url(@project, @merge_request)
set_reference("merge_request_#{merge_request_id}")
mail(from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid}) #{@mr_status}"))
mail_answer_thread(@merge_request,
from: sender(updated_by_user_id),
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid}) #{@mr_status}"))
end
end
......
module Emails
module Profile
def new_user_email(user_id, password)
def new_user_email(user_id, password, token = nil)
@user = User.find(user_id)
@password = password
@target_url = user_url(@user)
@token = token
mail(to: @user.email, subject: subject("Account was created for you"))
end
......
......@@ -142,6 +142,7 @@ class Ability
:write_wiki,
:modify_issue,
:admin_issue,
:admin_label,
:push_code
]
end
......
......@@ -13,6 +13,8 @@ module Issuable
belongs_to :assignee, class_name: "User"
belongs_to :milestone
has_many :notes, as: :noteable, dependent: :destroy
has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
......@@ -131,4 +133,16 @@ module Issuable
object_attributes: self.attributes
}
end
def label_names
labels.order('title ASC').pluck(:title)
end
def add_labels_by_names(label_names)
label_names.each do |label_name|
label = project.labels.create_with(
color: Label::DEFAULT_COLOR).find_or_create_by(title: label_name.strip)
self.labels << label
end
end
end
......@@ -70,6 +70,12 @@ class Event < ActiveRecord::Base
author_id: user.id
)
end
def reset_event_cache_for(target)
Event.where(target_id: target.id, target_type: target.class.to_s).
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end
end
def proper?
......
......@@ -84,4 +84,20 @@ class Group < Namespace
def public_profile?
projects.public_only.any?
end
class << self
def search(query)
where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%")
end
def sort(method)
case method.to_s
when "newest" then reorder("namespaces.created_at DESC")
when "oldest" then reorder("namespaces.created_at ASC")
when "recently_updated" then reorder("namespaces.updated_at DESC")
when "last_updated" then reorder("namespaces.updated_at ASC")
else reorder("namespaces.path, namespaces.name ASC")
end
end
end
end
......@@ -32,9 +32,6 @@ class Issue < ActiveRecord::Base
scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
acts_as_taggable_on :labels
scope :cared, ->(user) { where(assignee_id: user) }
scope :open_for, ->(user) { opened.assigned_to(user) }
......@@ -67,8 +64,6 @@ class Issue < ActiveRecord::Base
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
Event.where(target_id: self.id, target_type: 'Issue').
order('id DESC').limit(100).
update_all(updated_at: Time.now)
Event.reset_event_cache_for(self)
end
end
class Label < ActiveRecord::Base
DEFAULT_COLOR = '#428bca'
belongs_to :project
has_many :label_links, dependent: :destroy
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
validates :color,
format: { with: /\A\#[0-9A-Fa-f]{6}+\Z/ },
allow_blank: false
validates :project, presence: true
# Don't allow '?', '&', and ',' for label titles
validates :title,
presence: true,
format: { with: /\A[^&\?,&]*\z/ },
uniqueness: { scope: :project_id }
scope :order_by_name, -> { reorder("labels.title ASC") }
alias_attribute :name, :title
def open_issues_count
issues.opened.count
end
end
class LabelLink < ActiveRecord::Base
belongs_to :target, polymorphic: true
belongs_to :label
validates :target, presence: true
validates :label, presence: true
end
......@@ -44,12 +44,9 @@ class MergeRequest < ActiveRecord::Base
# Temporary fields to store compare vars
# when creating new merge request
attr_accessor :can_be_created, :compare_failed, :compare_base_commit,
attr_accessor :can_be_created, :compare_failed,
:compare_commits, :compare_diffs
ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :labels
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
......@@ -287,9 +284,7 @@ class MergeRequest < ActiveRecord::Base
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
Event.where(target_id: self.id, target_type: 'MergeRequest').
order('id DESC').limit(100).
update_all(updated_at: Time.now)
Event.reset_event_cache_for(self)
end
def merge_commit_message
......
......@@ -83,11 +83,7 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect array of Git::Commit objects
# between target and source branches
def unmerged_commits
commits = if merge_request.for_fork?
compare_action.commits
else
repository.commits_between(target_branch, source_branch)
end
commits = compare_result.commits
if commits.present?
commits = Commit.decorate(commits).
......@@ -147,12 +143,7 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect array of Git::Diff objects
# between target and source branches
def unmerged_diffs
diffs = if merge_request.for_fork?
compare_action.diffs
else
Gitlab::Git::Diff.between(repository, source_branch, target_branch)
end
diffs = compare_result.diffs
diffs ||= []
diffs
rescue Gitlab::Git::Diff::TimeoutError => ex
......@@ -166,13 +157,13 @@ class MergeRequestDiff < ActiveRecord::Base
private
def compare_action
Gitlab::Satellite::CompareAction.new(
def compare_result
@compare_result ||= CompareService.new.execute(
merge_request.author,
merge_request.source_project,
merge_request.source_branch,
merge_request.target_project,
merge_request.target_branch,
merge_request.source_project,
merge_request.source_branch
)
end
end
......@@ -327,9 +327,7 @@ class Note < ActiveRecord::Base
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
Event.where(target_id: self.id, target_type: 'Note').
order('id DESC').limit(100).
update_all(updated_at: Time.now)
Event.reset_event_cache_for(self)
end
def set_references
......
......@@ -22,6 +22,7 @@
# visibility_level :integer default(0), not null
# archived :boolean default(FALSE), not null
# import_status :string(255)
# star_count :integer
#
class Project < ActiveRecord::Base
......@@ -40,8 +41,7 @@ class Project < ActiveRecord::Base
default_value_for :snippets_enabled, gitlab_config_features.snippets
ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :labels, :issues_default_labels
acts_as_taggable_on :tags
attr_accessor :new_default_branch
......@@ -74,6 +74,7 @@ class Project < ActiveRecord::Base
# Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest
has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy
has_many :labels, dependent: :destroy
has_many :services, dependent: :destroy
has_many :events, dependent: :destroy
has_many :milestones, dependent: :destroy
......@@ -85,6 +86,8 @@ class Project < ActiveRecord::Base
has_many :users, through: :users_projects
has_many :deploy_keys_projects, dependent: :destroy
has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy
has_many :starrers, through: :users_star_projects, source: :user
has_many :project_group_links, dependent: :destroy
has_many :invited_groups, through: :project_group_links, source: :group
......@@ -114,6 +117,7 @@ class Project < ActiveRecord::Base
validates :import_url,
format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" },
if: :import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
# Scopes
......@@ -285,13 +289,6 @@ class Project < ActiveRecord::Base
self.id
end
# Tags are shared by issues and merge requests
def issues_labels
@issues_labels ||= (issues_default_labels +
merge_requests.tags_on(:labels) +
issues.tags_on(:labels)).uniq.sort_by(&:name)
end
def issue_exists?(issue_id)
if used_default_issues_tracker?
self.issues.where(iid: issue_id).first.present?
......@@ -508,6 +505,7 @@ class Project < ActiveRecord::Base
end
def rename_repo
path_was = previous_changes['path'].first
old_path_with_namespace = File.join(namespace_dir, path_was)
new_path_with_namespace = File.join(namespace_dir, path)
......@@ -586,4 +584,12 @@ class Project < ActiveRecord::Base
def update_repository_size
update_attribute(:repository_size, repository.size)
end
def forks_count
ForkedProjectLink.where(forked_from_project_id: self.id).count
end
def find_label(name)
labels.find_by(name: name)
end
end
......@@ -18,6 +18,8 @@
#
class HipchatService < Service
MAX_COMMITS = 3
validates :token, presence: true, if: :activated?
def title
......@@ -57,15 +59,24 @@ class HipchatService < Service
message = ""
message << "#{push[:user_name]} "
if before =~ /000000/
message << "pushed new branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> to <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a>\n"
message << "pushed new branch <a href=\""\
"#{project.web_url}/commits/#{URI.escape(ref)}\">#{ref}</a>"\
" to <a href=\"#{project.web_url}\">"\
"#{project.name_with_namespace.gsub!(/\s/, "")}</a>\n"
elsif after =~ /000000/
message << "removed branch #{ref} from <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> \n"
else
message << "pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> "
message << "pushed to branch <a href=\""\
"#{project.web_url}/commits/#{URI.escape(ref)}\">#{ref}</a> "
message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> "
message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
for commit in push[:commits] do
message << "<br /> - #{commit[:message]} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"
push[:commits].take(MAX_COMMITS).each do |commit|
message << "<br /> - #{commit[:message].lines.first} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"
end
if push[:commits].count > MAX_COMMITS
message << "<br />... #{push[:commits].count - MAX_COMMITS} more commits"
end
end
......
......@@ -72,6 +72,15 @@ class ProjectWiki
end
end
def find_file(name, version = nil, try_on_disk = true)
version = wiki.ref if version.nil? # Gollum::Wiki#file ?
if wiki_file = wiki.file(name, version, try_on_disk)
wiki_file
else
nil
end
end
def create_page(title, content, format = :markdown, message = nil)
commit = commit_details(:created, message, title)
......
......@@ -10,8 +10,11 @@ class Repository
nil
end
# Return absolute path to repository
def path_to_repo
@path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git")
@path_to_repo ||= File.expand_path(
File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git")
)
end
def exists?
......@@ -134,7 +137,7 @@ class Repository
def graph_log
Rails.cache.fetch(cache_key(:graph_log)) do
stats = Gitlab::Git::GitStats.new(raw, root_ref)
stats = Gitlab::Git::GitStats.new(raw, root_ref, Gitlab.config.git.timeout)
stats.parsed_log
end
end
......@@ -263,4 +266,20 @@ class Repository
contributor
end
end
def blob_for_diff(commit, diff)
file = blob_at(commit.id, diff.new_path)
unless file
file = prev_blob_for_diff(commit, diff)
end
file
end
def prev_blob_for_diff(commit, diff)
if commit.parent_id
blob_at(commit.parent_id, diff.old_path)
end
end
end
class Tree
include Gitlab::MarkdownHelper
attr_accessor :entries, :readme, :contribution_guide
def initialize(repository, sha, path = '/')
......@@ -6,7 +8,23 @@ class Tree
git_repo = repository.raw_repository
@entries = Gitlab::Git::Tree.where(git_repo, sha, path)
if readme_tree = @entries.find(&:readme?)
available_readmes = @entries.select(&:readme?)
if available_readmes.count > 0
# If there is more than 1 readme in tree, find readme which is supported
# by markup renderer.
if available_readmes.length > 1
supported_readmes = available_readmes.select do |readme|
gitlab_markdown?(readme.name) || markup?(readme.name)
end
# Take the first supported readme, or the first available readme, if we
# don't support any of them
readme_tree = supported_readmes.first || available_readmes.first
else
readme_tree = available_readmes.first
end
readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name)
@readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path)
end
......
......@@ -91,6 +91,8 @@ class User < ActiveRecord::Base
has_many :personal_projects, through: :namespace, source: :projects
has_many :projects, through: :users_projects
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
has_many :users_star_projects, dependent: :destroy
has_many :starred_projects, through: :users_star_projects, source: :project
has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet"
has_many :users_projects, dependent: :destroy
......@@ -241,6 +243,15 @@ class User < ActiveRecord::Base
end
end
def generate_reset_token
@reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token)
self.reset_password_token = enc
self.reset_password_sent_at = Time.now.utc
@reset_token
end
def namespace_uniq
namespace_name = self.username
if Namespace.find_by(path: namespace_name)
......@@ -492,7 +503,7 @@ class User < ActiveRecord::Base
def post_create_hook
log_info("User \"#{self.name}\" (#{self.email}) was created")
notification_service.new_user(self)
notification_service.new_user(self, @reset_token)
system_hook_service.execute_hooks_for(self, :create)
end
......@@ -516,4 +527,18 @@ class User < ActiveRecord::Base
def admin_unsubscribe!
update_column :admin_email_unsubscribed_at, Time.now
end
def starred?(project)
starred_projects.exists?(project)
end
def toggle_star(project)
user_star_project = users_star_projects.
where(project: project, user: self).take
if user_star_project
user_star_project.destroy
else
UsersStarProject.create!(project: project, user: self)
end
end
end
# == Schema Information
#
# Table name: users_star_projects
#
# id :integer not null, primary key
# starrer_id :integer not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
#
class UsersStarProject < ActiveRecord::Base
belongs_to :project, counter_cache: :star_count
belongs_to :user
validates :user, presence: true
validates :user_id, uniqueness: { scope: [:project_id] }
validates :project, presence: true
end
class ArchiveRepositoryService
def execute(project, ref, format)
storage_path = Gitlab.config.gitlab.repository_downloads_path
unless File.directory?(storage_path)
FileUtils.mkdir_p(storage_path)
end
format ||= 'tar.gz'
repository = project.repository
repository.clean_old_archives
repository.archive_repo(ref, storage_path, format.downcase)
end
end
# Compare 2 branches for one repo or between repositories
# and return Gitlab::CompareResult object that responds to commits and diffs
class CompareService
def execute(current_user, source_project, source_branch, target_project, target_branch)
# Try to compare branches to get commits list and diffs
#
# Note: Use satellite only when need to compare between to repos
# because satellites are slower then operations on bare repo
if target_project == source_project
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
source_branch,
)
)
else
Gitlab::Satellite::CompareAction.new(
current_user,
target_project,
target_branch,
source_project,
source_branch
).result
end
end
end
......@@ -37,7 +37,7 @@ module Files
if created_successfully
success
else
error("Your changes could not be committed, because the file has been changed")
error("Your changes could not be committed. Maybe the file was changed by another process or there was nothing to commit?")
end
end
end
......
module Issues
class CreateService < Issues::BaseService
def execute
issue = project.issues.new(params)
label_params = params[:label_ids]
issue = project.issues.new(params.except(:label_ids))
issue.author = current_user
if issue.save
issue.update_attributes(label_ids: label_params)
notification_service.new_issue(issue, current_user)
event_service.open_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user)
......
......@@ -22,27 +22,25 @@ module MergeRequests
# Set MR description based on project template
merge_request.description = merge_request.target_project.merge_requests_template
# Try to compare branches to get commits list and diffs
compare_action = Gitlab::Satellite::CompareAction.new(
compare_result = CompareService.new.execute(
current_user,
merge_request.source_project,
merge_request.source_branch,
merge_request.target_project,
merge_request.target_branch,
merge_request.source_project,
merge_request.source_branch
)
commits = compare_action.commits
commits = compare_result.commits
# At this point we decide if merge request can be created
# If we have at least one commit to merge -> creation allowed
if commits.present?
merge_request.compare_commits = Commit.decorate(commits)
merge_request.compare_base_commit = Commit.new(commits.first)
merge_request.can_be_created = true
merge_request.compare_failed = false
# Try to collect diff for merge request.
diffs = compare_action.diffs
diffs = compare_result.diffs
if diffs.present?
merge_request.compare_diffs = diffs
......
module MergeRequests
class CreateService < MergeRequests::BaseService
def execute
merge_request = MergeRequest.new(params)
label_params = params[:label_ids]
merge_request = MergeRequest.new(params.except(:label_ids))
merge_request.source_project = project
merge_request.target_project ||= project
merge_request.author = current_user
if merge_request.save
merge_request.update_attributes(label_ids: label_params)
event_service.open_mr(merge_request, current_user)
notification_service.new_merge_request(merge_request, current_user)
merge_request.create_cross_references!(merge_request.project, current_user)
......
......@@ -105,9 +105,9 @@ class NotificationService
end
# Notify new user with email after creation
def new_user(user)
def new_user(user, token = nil)
# Don't email omniauth created users
mailer.new_user_email(user.id, user.password) unless user.extern_uid?
mailer.new_user_email(user.id, user.password, token) unless user.extern_uid?
end
# Notify users on new note in system
......
......@@ -57,7 +57,6 @@ module Projects
:add_repository,
@project.path_with_namespace
)
end
if @project.wiki_enabled?
......
......@@ -12,7 +12,13 @@ module Search
return result unless query.present?
if params[:search_code].present?
blobs = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo?
if !@project.empty_repo?
blobs = project.repository.search_files(query,
params[:repository_ref])
else
blobs = Array.new
end
blobs = Kaminari.paginate_array(blobs).page(params[:page]).per(20)
result[:blobs] = blobs
result[:total_results] = blobs.total_count
......
......@@ -2,5 +2,8 @@ class TestHookService
def execute(hook, current_user)
data = GitPushService.new.sample_data(hook.project, current_user)
hook.execute(data)
true
rescue SocketError
false
end
end
......@@ -31,9 +31,9 @@
= f.label :password, class: 'control-label'
.col-sm-10
%strong
A temporary password will be generated and sent to user.
Reset link will be generated and sent to the user.
%br
User will be forced to change it after first sign in
User will be forced to set the password on first sign in.
- else
%fieldset
%legend Password
......
......@@ -44,12 +44,12 @@
- if @labels.present?
- if @tags.present?
%fieldset
%legend Labels
%legend Tags
%ul.nav.nav-pills.nav-stacked.nav-small
- @labels.each do |label|
%li{ class: (label.name == params[:label]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(scope: params[:scope], label: label.name) do
- @tags.each do |tag|
%li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(scope: params[:scope], tag: tag.name) do
%i.icon-tag
= label.name
= tag.name
......@@ -46,5 +46,5 @@
%br
Public projects are an easy way to allow everyone to have read-only access.
.link_holder
= link_to public_projects_path, class: "btn btn-new" do
= link_to explore_projects_path, class: "btn btn-new" do
Browse public projects »
......@@ -54,10 +54,10 @@
%span.label
%i.icon-archive
Archived
- project.labels.each do |label|
- project.tags.each do |tag|
%span.label.label-info
%i.icon-tag
= label.name
= tag.name
- if project.description.present?
%p= truncate project.description, length: 100
.last-activity
......
.clearfix
.pull-left
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search"
.form-group
= submit_tag 'Search', class: "btn btn-primary wide"
.pull-right
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light sort:
- if @sort.present?
= @sort.humanize
- else
Name
%b.caret
%ul.dropdown-menu
%li
= link_to explore_groups_path(sort: nil) do
Name
= link_to explore_groups_path(sort: 'newest') do
Newest
= link_to explore_groups_path(sort: 'oldest') do
Oldest
= link_to explore_groups_path(sort: 'recently_updated') do
Recently updated
= link_to explore_groups_path(sort: 'last_updated') do
Last updated
%hr
%ul.bordered-list
- @groups.each do |group|
%li
.clearfix
%h4
= link_to group_path(id: group.path) do
%i.icon-group
= group.name
.clearfix
%p
= truncate group.description, length: 150
.clearfix
%p.light
#{pluralize(group.members.size, 'member')}, #{pluralize(group.projects.count, 'project')}
- unless @groups.present?
.nothing-here-block No public groups
= paginate @groups, theme: "gitlab"
%li
%h4.project-title
.project-access-icon
= visibility_level_icon(project.visibility_level)
= link_to project.name_with_namespace, project
- if current_page?(starred_explore_projects_path)
%strong.pull-right
= pluralize project.star_count, 'star'
- if project.description.present?
%p.project-description.str-truncated
= project.description
.repo-info
- unless project.empty_repo?
= link_to pluralize(project.repository.round_commit_count, 'commit'), project_commits_path(project, project.default_branch)
&middot;
= link_to pluralize(project.repository.branch_names.count, 'branch'), project_branches_path(project)
&middot;
= link_to pluralize(project.repository.tag_names.count, 'tag'), project_tags_path(project)
- else
%i.icon-warning-sign
Empty repository
%h3.page-title
Projects (#{@projects.total_count})
.light
You can browse public projects in read-only mode until signed in.
%hr
.clearfix
.pull-left
= form_tag public_projects_path, method: :get, class: 'form-inline form-tiny' do |f|
= form_tag explore_projects_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search"
.form-group
......@@ -22,46 +17,21 @@
%b.caret
%ul.dropdown-menu
%li
= link_to public_projects_path(sort: nil) do
= link_to explore_projects_path(sort: nil) do
Name
= link_to public_projects_path(sort: 'newest') do
= link_to explore_projects_path(sort: 'newest') do
Newest
= link_to public_projects_path(sort: 'oldest') do
= link_to explore_projects_path(sort: 'oldest') do
Oldest
= link_to public_projects_path(sort: 'recently_updated') do
= link_to explore_projects_path(sort: 'recently_updated') do
Recently updated
= link_to public_projects_path(sort: 'last_updated') do
= link_to explore_projects_path(sort: 'last_updated') do
Last updated
%hr
.public-projects
%ul.bordered-list.top-list
- @projects.each do |project|
%li
%h4
= link_to project_path(project) do
= project.name_with_namespace
- if project.internal?
%small.access-icon
= internal_icon
Internal
.pull-right.hidden-sm.hidden-xs
%pre.public-clone git clone #{project.http_url_to_repo}
- if project.description.present?
%p
= project.description
.repo-info
- unless project.empty_repo?
= link_to pluralize(project.repository.round_commit_count, 'commit'), project_commits_path(project, project.default_branch)
&middot;
= link_to pluralize(project.repository.branch_names.count, 'branch'), project_branches_path(project)
&middot;
= link_to pluralize(project.repository.tag_names.count, 'tag'), project_tags_path(project)
- else
%i.icon-warning-sign
Empty repository
= render @projects
- unless @projects.present?
.nothing-here-block No public projects
......
.explore-trending-block
%p.lead
%i.icon-comments-alt
See most starred projects
%hr
.public-projects
%ul.bordered-list
= render @starred_projects
= paginate @starred_projects, theme: 'gitlab'
.explore-trending-block
%p.lead
%i.icon-comments-alt
See most discussed projects for last month
%hr
.public-projects
%ul.bordered-list
= render @trending_projects
.center
= link_to 'Show all projects', explore_projects_path, class: 'btn btn-primary'
......@@ -24,7 +24,7 @@
'data-original-title' => 'Help' do
%i.icon-question-sign
%li
= link_to public_root_path, title: "Public area", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
= link_to explore_root_path, title: "Explore", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
%i.icon-globe
%li
= link_to user_snippets_path(current_user), title: "My snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'My snippets' do
......
......@@ -3,7 +3,7 @@
.container
%div.app_logo
%span.separator
= link_to public_root_path, class: "home" do
= link_to explore_root_path, class: "home" do
%h1 GITLAB
%span.separator
%h1.title= title
......@@ -13,10 +13,10 @@
%i.icon-reorder
.pull-right.hidden-xs
= link_to "Sign in", new_session_path(:user), class: 'btn btn-sign-in btn-new'
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new'
.navbar-collapse.collapse
%ul.nav.navbar-nav
%li.visible-xs
= link_to "Sign in", new_session_path(:user)
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
......@@ -18,7 +18,7 @@
.brand_text
= brand_text
- else
.brand-image.hidden-sm.hidden-xs
.brand-image.default-brand-image.hidden-sm.hidden-xs
= image_tag 'brand_logo.png'
.brand_text.hidden-xs
%h2 Open source software to collaborate on code
......@@ -33,6 +33,6 @@
%hr
.container
.footer-links
= link_to "Explore public projects", public_projects_path
= link_to "Explore", explore_root_path
= link_to "Documentation", "http://doc.gitlab.com/"
= link_to "About GitLab", "https://about.gitlab.com/"
- page_title = 'Explore'
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: page_title
%body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
- if current_user
= render "layouts/head_panel", title: page_title
- else
= render "layouts/public_head_panel", title: page_title
.container.navless-container
.content
.explore-title
%h3
Explore GitLab
%p.lead
Discover projects and groups. Share your projects with others
%ul.nav.nav-tabs
= nav_link(path: 'projects#trending') do
= link_to 'Trending Projects', explore_root_path
= nav_link(path: 'projects#starred') do
= link_to 'Most Starred Projects', starred_explore_projects_path
= nav_link(path: 'projects#index') do
= link_to 'All Projects', explore_projects_path
= nav_link(controller: :groups) do
= link_to 'All Groups', explore_groups_path
= yield
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Public Projects"
%body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
- if current_user
= render "layouts/head_panel", title: "Public Projects"
- else
= render "layouts/public_head_panel", title: "Public Projects"
.container.navless-container
.content= yield
......@@ -11,11 +11,4 @@
- if @user.created_by_id
%p
password..................................
%code= @password
%p
You will be forced to change this password immediately after login.
%p
= link_to "Click here to login", root_url
= link_to "Click here to set your password", edit_password_url(@user, :reset_password_token => @token)
......@@ -4,10 +4,5 @@ The Administrator created an account for you. Now you are a member of the compan
login.................. <%= @user.email %>
<% if @user.created_by_id %>
password............... <%= @password %>
You will be forced to change this password immediately after login.
<%= link_to "Click here to set your password", edit_password_url(@user, :reset_password_token => @token) %>
<% end %>
Click here to login: <%= url_for(root_url) %>
......@@ -25,6 +25,4 @@
%br
- if @compare.timeout
%h5 To prevent performance issues changes are hidden
- elsif @compare.commits_over_limit?
%h5 Changes are not shown due to large amount of commits
%h5 Huge diff. To prevent performance issues changes are hidden
......@@ -23,5 +23,3 @@ Changes:
\
- if @compare.timeout
Huge diff. To prevent performance issues it was hidden
- elsif @compare.commits_over_limit?
Changes are not shown due to large amount of commits
......@@ -11,6 +11,9 @@
- @user.errors.full_messages.each do |msg|
%li= msg
.form-group
= f.label :current_password, class: 'control-label'
.col-sm-10= f.password_field :current_password, required: true, class: 'form-control'
.form-group
= f.label :password, class: 'control-label'
.col-sm-10= f.password_field :password, required: true, class: 'form-control'
......
......@@ -27,9 +27,9 @@
= link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)) do
= readme.name
- unless empty_repo
.col-md-5
.project-home-links
.col-md-5
.project-home-links
- unless empty_repo
= link_to pluralize(number_with_delimiter(@repository.commit_count), 'commit'), project_commits_path(@project, @ref || @repository.root_ref)
= link_to pluralize(number_with_delimiter(@repository.branch_names.count), 'branch'), project_branches_path(@project)
= link_to pluralize(number_with_delimiter(@repository.tag_names.count), 'tag'), project_tags_path(@project)
......
- if @lines.present?
- if @form.unfold? && @form.since != 1 && !@form.bottom?
%tr.line_holder{ id: @form.since }
= render "projects/commits/diffs/match_line", {line: @match_line,
line_old: @form.since, line_new: @form.since, bottom: false}
- @lines.each_with_index do |line, index|
- line_new = index + @form.since
- line_old = line_new - @form.offset
%tr.line_holder
%td.old_line.diff-line-num{data: {linenumber: line_old}}
= link_to raw(line_old), "#"
%td.new_line= link_to raw(line_new) , "#"
%td.line_content.noteable_line= line
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
%tr.line_holder{ id: @form.to }
= render "projects/commits/diffs/match_line", {line: @match_line,
line_old: @form.to, line_new: @form.to, bottom: true}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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