Commit 8d47137a authored by Sytse Sijbrandij's avatar Sytse Sijbrandij

Merge branch 'master' into better-link-to-ce-to-ee-upgrade-scripts

Conflicts:
	doc/update/README.md
parents b8ccc67e 52436510
stage:
before:
- cp config/gitlab.teatro.yml config/gitlab.yml
- mkdir /apps/gitlab-satellites
- mkdir /apps/repositories
database:
- RAILS_ENV=development force=yes bundle exec rake db:create gitlab:setup
\ No newline at end of file
v 7.1.0
- Remove observers
- Improve MR discussions
- Filter by description on Issues#index page
- Fix bug with namespace select when create new project page
- Show README link after description for non-master members
- Add @all mention for comments
- Dont show reply button if user is not signed in
- Expose more information for issues with webhook
v 7.0.0
- The CPU no longer overheats when you hold down the spacebar
- Improve edit file UI
......
v 7.1.0
- Synchronize LDAP-enabled GitLab administrators with an LDAP group (Marvin Frick, sponsored by SinnerSchrader)
v 7.0.0
- Fix: empty brand images are displayed as empty image_tag on login page (Marvin Frick, sponsored by SinnerSchrader)
......
# Contribute to GitLab
This guide details how contribute to GitLab.
If you want to know how the GitLab team handles contributions have a look at [the GitLab contributing process](PROCESS.md).
Thank you for your interest in contributing to GitLab.
This guide details how contribute to GitLab in a way that is efficient for everyone.
If you have read this guide and want to know how the GitLab core-team operates please see [the GitLab contributing process](PROCESS.md).
## Contributor license agreement
......@@ -75,9 +75,9 @@ If you can, please submit a merge request with the fix or improvements including
The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features.
Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? The smaller a MR is the more likely it is it will be merged, after that you can send more MR's to enhance it.
Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a mimimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it.
For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). Please ensure that your merge request meets the following contribution acceptance criteria.
For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the following contribution acceptance criteria.
**Please format your merge request description as follows:**
......@@ -97,7 +97,8 @@ For examples of feedback on merge requests please look at already [closed merge
1. Keeps the GitLab code base clean and well structured
1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes
1. Contains a single commit (please use `git rebase -i` to squash commits)
1. Initially contains a single commit (please use `git rebase -i` to squash commits)
1. Changes after submitting the merge request should be in separate commits (no squashing)
1. It conforms to the following style guides
## Style guides
......@@ -113,4 +114,4 @@ For examples of feedback on merge requests please look at already [closed merge
1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
This is also the style used by linting tools such as [Rubocop](https://github.com/bbatsov/rubocop), PullReview[https://www.pullreview.com/] and [Hound CI](https://houndci.com).
This is also the style used by linting tools such as [Rubocop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
......@@ -11,7 +11,6 @@ end
gem "rails", "~> 4.1.0"
gem "protected_attributes"
gem 'rails-observers'
# Make links from text
gem 'rails_autolink', '~> 1.1'
......
......@@ -365,8 +365,6 @@ GEM
bundler (>= 1.3.0, < 2.0)
railties (= 4.1.1)
sprockets-rails (~> 2.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
rails_autolink (1.1.6)
rails (> 3.1)
rails_best_practices (1.14.4)
......@@ -645,7 +643,6 @@ DEPENDENCIES
rack-cors
rack-mini-profiler
rails (~> 4.1.0)
rails-observers
rails_autolink (~> 1.1)
rails_best_practices
raphael-rails (~> 2.1.2)
......
......@@ -15,7 +15,7 @@
## Canonical source
- The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible.
- The source of GitLab Enterprise Edition is [hosted on GitLab.com](https://dev.gitlab.org/gitlab/gitlab-ee/) and acessible only to [subscribers](https://about.gitlab.com/subscription/).
## Code status
......@@ -61,7 +61,11 @@ These applications are maintained by contributors, GitLab B.V. does not offer su
## Installation
Please see [the installation page on the GitLab website](https://www.gitlab.com/installation/).
We recommend the [GitLab Enterprise Edition packages](https://gitlab.com/subscribers/gitlab-ee/blob/master/doc/install/packages.md).
You can also use the [Chef cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md) or the [manual installation](doc/install/installation.md).
Other options are listed on the [installation page on the GitLab website](https://www.gitlab.com/installation/) but most of these assume GitLab Community Edition.
### New versions
......@@ -122,7 +126,7 @@ And surf to [localhost:3000](http://localhost:3000/) and login with `root` / `5i
## Documentation
All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/).
All documentation can be found on [doc.gitlab.com/ee/](http://doc.gitlab.com/ee/).
## Getting help
......
7.0.0-ee
7.1.0.pre-ee
......@@ -143,6 +143,14 @@ $ ->
$(@).next('table').show()
$(@).remove()
# Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) ->
$(@).find('i').
toggleClass('icon-chevron-down').
toggleClass('icon-chevron-up')
$(@).closest(".diff-file").find(".notes_holder").toggle()
e.preventDefault()
(($) ->
# Disable an element and add the 'disabled' Bootstrap class
$.fn.extend disable: ->
......
......@@ -21,7 +21,6 @@ $(document).ready ->
$(".div-dropzone").append divSpinner
$(".div-dropzone-spinner").append iconSpinner
dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload
dictDefaultMessage: ""
......@@ -77,6 +76,107 @@ $(document).ready ->
return
)
child = $(dropzone[0]).children("textarea")
formatLink = (str) ->
"![" + str.alt + "](" + str.url + ")"
handlePaste = (e) ->
e.preventDefault()
my_event = e.originalEvent
if my_event.clipboardData and my_event.clipboardData.items
processItem(my_event)
processItem = (e) ->
image = isImage(e)
if image
filename = getFilename(e) or "image.png"
text = "{{" + filename + "}}"
pasteText(text)
uploadFile image.getAsFile(), filename
else
text = e.clipboardData.getData("text/plain")
pasteText(text)
isImage = (data) ->
i = 0
while i < data.clipboardData.items.length
item = data.clipboardData.items[i]
if item.type.indexOf("image") isnt -1
return item
i++
return false
pasteText = (text) ->
caretStart = $(child)[0].selectionStart
caretEnd = $(child)[0].selectionEnd
textEnd = $(child).val().length
beforeSelection = $(child).val().substring 0, caretStart
afterSelection = $(child).val().substring caretEnd, textEnd
$(child).val beforeSelection + text + afterSelection
$(".markdown-area").trigger "input"
getFilename = (e) ->
if window.clipboardData and window.clipboardData.getData
value = window.clipboardData.getData("Text")
else if e.clipboardData and e.clipboardData.getData
value = e.clipboardData.getData("text/plain")
value = value.split("\r")
value.first()
uploadFile = (item, filename) ->
formData = new FormData()
formData.append "markdown_img", item, filename
$.ajax
url: project_image_path_upload
type: "POST"
data: formData
dataType: "json"
processData: false
contentType: false
headers:
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
beforeSend: ->
showSpinner()
closeAlertMessage()
success: (e, textStatus, response) ->
insertToTextArea(filename, formatLink(response.responseJSON.link))
error: (response) ->
showError(response.responseJSON.message)
complete: ->
closeSpinner()
insertToTextArea = (filename, url) ->
$(child).val (index, val) ->
val.replace("{{" + filename + "}}", url + "\n")
appendToTextArea = (url) ->
$(child).val (index, val) ->
val + url + "\n"
showSpinner = (e) ->
$(".div-dropzone-spinner").css "opacity", 0.7
closeSpinner = ->
$(".div-dropzone-spinner").css "opacity", 0
showError = (message) ->
checkIfMsgExists = $(".error-alert").children().length
if checkIfMsgExists is 0
$(".error-alert").append divAlert
$(".div-dropzone-alert").append btnAlert + message
closeAlertMessage = ->
$(".div-dropzone-alert").alert "close"
$(".markdown-selector").click (e) ->
e.preventDefault()
$(".div-dropzone").click()
......
......@@ -109,10 +109,10 @@ class MergeRequest
type: 'GET'
url: this.$('.merge-request-tabs .diffs-tab a').attr('href')
beforeSend: =>
this.$('.status').addClass 'loading'
this.$('.mr-loading-status .loading').show()
complete: =>
@diffs_loaded = true
this.$('.status').removeClass 'loading'
this.$('.mr-loading-status .loading').hide()
success: (data) =>
this.$(".diffs").html(data.html)
dataType: 'json'
......
......@@ -142,8 +142,15 @@ class Notes
# remove the note (will be added again below)
row.next().find(".note").remove()
# append new note to all matching discussions
$(".notes[rel='" + note.discussion_id + "']").append note.html
# Add note to 'Changes' page discussions
$(".notes[rel='" + note.discussion_id + "']").append note.html
# Init discussion on 'Discussion' page if it is merge request page
if $('body').attr('data-page').indexOf('projects:merge_request') == 0
$('ul.main-notes-list').append(note.discussion_with_diff_html)
else
# append new note to all matching discussions
$(".notes[rel='" + note.discussion_id + "']").append note.html
# cleanup after successfully creating a diff/discussion note
@removeDiscussionNoteForm(form)
......
......@@ -8,8 +8,6 @@
*= require select2
*= require highlightjs.min
*= require_self
*= require nprogress
*= require nprogress-bootstrap
*= require dropzone/basic
*/
......@@ -20,6 +18,12 @@
*/
@import 'gl_bootstrap';
/**
* NProgress load bar css
*/
@import 'nprogress';
@import 'nprogress-bootstrap';
/**
* Font icons
*
......
......@@ -16,6 +16,7 @@
.append-bottom-15 { margin-bottom:15px }
.append-bottom-20 { margin-bottom:20px }
.inline { display: inline-block }
.center { text-align: center }
.underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: #999; }
......@@ -55,7 +56,7 @@ pre {
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background: $bg_style_color;
background: $bg_primary;
color: #FFF
}
......@@ -256,13 +257,6 @@ li.note {
}
}
h1.http_status_code {
font-size: 56px;
line-height: 100px;
font-weight: normal;
color: #456;
}
.control-group {
.controls {
span {
......
......@@ -76,30 +76,10 @@ label {
}
}
.commit-message-container {
background-color: $body-bg;
position: relative;
font-family: $monospace_font;
$left: 12px;
.max-width-marker {
width: 72ch;
color: rgba(0, 0, 0, 0.0);
font-family: inherit;
left: $left;
height: 100%;
border-right: 1px solid mix($input-border, white);
position: absolute;
z-index: 1;
}
> textarea {
background-color: rgba(0, 0, 0, 0.0);
font-family: inherit;
padding-left: $left;
position: relative;
z-index: 2;
}
}
.fieldset-form fieldset {
margin-bottom: 20px;
}
.form-control {
@include box-shadow(none);
}
......@@ -41,8 +41,8 @@
}
.ui-state-active {
border: 1px solid $bg_style_color;
background: $bg_style_color;
border: 1px solid $bg_primary;
background: $bg_primary;
color: #FFF;
}
......
......@@ -42,7 +42,7 @@
.select2-results {
max-height: 350px;
.select2-highlighted {
background: $bg_style_color;
background: $bg_primary;
}
}
}
......
......@@ -9,7 +9,7 @@
$font-size-base: 13px !default;
$nav-pills-active-link-hover-bg: $bg_primary;
$pagination-active-bg: $bg_primary;
$list-group-active-bg: $bg_style_color;
$list-group-active-bg: $bg_primary;
// Core variables and mixins
@import "bootstrap/variables";
......
......@@ -2,7 +2,6 @@
* General Colors
*/
$style_color: #474D57;
$bg_style_color: #2299BB;
$hover: #D9EDF7;
/*
......@@ -40,3 +39,8 @@ $border_warning: #EB9532;
*/
$added: #63c363;
$deleted: #f77;
/**
*
*/
$nprogress-color: #3498db;
......@@ -213,3 +213,27 @@ li.commit {
padding: 4px 12px;
}
}
.commit-message-container {
background-color: $body-bg;
position: relative;
font-family: $monospace_font;
$left: 12px;
.max-width-marker {
width: 72ch;
color: rgba(0, 0, 0, 0.0);
font-family: inherit;
left: $left;
height: 100%;
border-right: 1px solid mix($input-border, white);
position: absolute;
z-index: 1;
}
> textarea {
background-color: rgba(0, 0, 0, 0.0);
font-family: inherit;
padding-left: $left;
position: relative;
z-index: 2;
}
}
.error-page {
max-width: 400px;
margin: 0 auto;
h1, h2, h3 {
text-align: center;
}
h1 {
font-size: 56px;
line-height: 100px;
font-weight: 300;
}
}
......@@ -156,12 +156,13 @@
.filter_icon {
a {
text-align:center;
background: #EEE;
background: $bg_primary;
margin-bottom: 10px;
float: left;
padding: 9px 6px;
font-size: 18px;
width: 40px;
color: #FFF;
@include border-radius(3px);
}
......
......@@ -6,14 +6,12 @@ header {
&.navbar-gitlab {
margin-bottom: 0;
min-height: 40px;
border: none;
.navbar-inner {
background: #F1F1F1;
border-bottom: 1px solid #DDD;
filter: none;
.nav > li > a {
color: $style_color;
font-size: 14px;
line-height: 32px;
padding: 6px 10px;
......@@ -248,8 +246,6 @@ header {
float: left;
height: 46px;
width: 2px;
background: white;
border-left: 1px solid #DDD;
margin-left: 10px;
margin-right: 10px;
}
......
......@@ -3,7 +3,7 @@
margin: 20px 0;
margin-top: 0;
padding-top: 4px;
border-bottom: 1px solid #E1E1E1;
border-bottom: 1px solid #E9E9E9;
ul {
padding: 0;
......
......@@ -43,38 +43,14 @@ ul.notes {
}
.discussion {
padding: 8px 0;
padding: 10px 0;
overflow: hidden;
display: block;
position:relative;
border-bottom: 1px solid #EEE;
.discussion-body {
margin-left: 50px;
.diff-file,
.discussion-hidden,
.notes {
background-color: #F9F9F9;
}
.diff-file .notes {
/* reset */
background: inherit;
border: none;
@include box-shadow(none);
}
.discussion-hidden .note {
@extend .cgray;
padding: 8px;
text-align: center;
}
.notes .note {
border-color: #ddd;
padding: 8px;
}
.reply-btn {
margin-top: 8px;
}
}
}
......@@ -137,10 +113,6 @@ ul.notes {
vertical-align: top;
}
}
.reply-btn {
margin: 5px;
}
}
/**
......@@ -376,3 +348,17 @@ ul.notes {
margin-top: 5px;
margin-bottom: 5px;
}
.discussion-body,
.diff-file {
.notes .note {
border-color: #ddd;
padding: 10px 15px;
}
.discussion-reply-holder {
background: #f9f9f9;
padding: 10px 15px;
border-top: 1px solid #DDD;
}
}
......@@ -56,10 +56,10 @@
text-align: center;
.prev {
@extend .thumbnail;
height: 30px;
width: 175px;
height: 80px;
width: 160px;
margin-bottom: 10px;
@include border-radius(4px);
&.classic {
background: #31363e;
......@@ -92,10 +92,13 @@
text-align: center;
.prev {
@extend .thumbnail;
height: 151px;
width: 220px;
width: 160px;
margin-bottom: 10px;
img {
max-width: 100%;
@include border-radius(4px);
}
}
}
}
......
......@@ -4,11 +4,21 @@
*
*/
.ui_basic {
.separator {
background: #F9F9F9;
border-left: 1px solid #DDD;
header {
&.navbar-gitlab {
.navbar-inner {
background: #F1F1F1;
border-bottom: 1px solid #DDD;
.nav > li > a {
color: $style_color;
}
.separator {
background: #F9F9F9;
border-left: 1px solid #DDD;
}
}
}
}
.main-nav {
background: #FFF;
}
......
......@@ -100,6 +100,16 @@ class Admin::UsersController < Admin::ApplicationController
end
end
def remove_email
email = user.emails.find(params[:email_id])
email.destroy
respond_to do |format|
format.html { redirect_to :back, notice: "Successfully removed email." }
format.js { render nothing: true }
end
end
protected
def user
......
......@@ -48,7 +48,7 @@ class ApplicationController < ActionController::Base
flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
new_user_session_path
else
super
@return_to || root_path
end
end
......
......@@ -33,6 +33,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
def omniauth_error
@provider = params[:provider]
@error = params[:error]
render 'errors/omniauth_error', layout: "errors", status: 422
end
private
def handle_omniauth
......@@ -47,14 +53,19 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Create user if does not exist
# and allow_single_sign_on is true
if Gitlab.config.omniauth['allow_single_sign_on']
@user ||= Gitlab::OAuth::User.create(oauth)
if Gitlab.config.omniauth['allow_single_sign_on'] && !@user
@user, errors = Gitlab::OAuth::User.create(oauth)
end
if @user
if @user && !errors
sign_in_and_redirect(@user)
else
flash[:notice] = "There's no such user!"
if errors
error_message = errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ")
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
else
flash[:notice] = "There's no such user!"
end
redirect_to new_user_session_path
end
end
......
......@@ -13,6 +13,8 @@ class Projects::CommitsController < Projects::ApplicationController
@limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
@commits = @repo.commits(@ref, @path, @limit, @offset)
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
respond_to do |format|
format.html # index.html.erb
......
......@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
terms = params['issue_search']
@issues = issues_filtered
@issues = @issues.where("title LIKE ?", "%#{terms}%") if terms.present?
@issues = @issues.where("title LIKE ? OR description LIKE ?", "%#{terms}%", "%#{terms}%") if terms.present?
@issues = @issues.page(params[:page]).per(20)
assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
......
......@@ -32,6 +32,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def show
@note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)).
group(:commit_id).count
respond_to do |format|
format.html
format.diff { render text: @merge_request.to_diff(current_user) }
......@@ -86,6 +88,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@compare_failed = true
end
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
@diffs = compare_action.diffs
@merge_request.title = @merge_request.source_branch.titleize.humanize
@merge_request.description = @merge_request.target_project.merge_requests_template
......
......@@ -21,7 +21,7 @@ class Projects::NotesController < Projects::ApplicationController
end
def create
@note = Notes::CreateService.new(project, current_user, params).execute
@note = Notes::CreateService.new(project, current_user, params[:note]).execute
respond_to do |format|
format.json { render_note_json(@note) }
......@@ -85,12 +85,24 @@ class Projects::NotesController < Projects::ApplicationController
)
end
def note_to_discussion_with_diff_html(note)
return unless note.for_diff_line?
render_to_string(
"projects/notes/_discussion",
layout: false,
formats: [:html],
locals: { discussion_notes: [note] }
)
end
def render_note_json(note)
render json: {
id: note.id,
discussion_id: note.discussion_id,
html: note_to_html(note),
discussion_html: note_to_discussion_html(note)
discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
}
end
......
......@@ -37,7 +37,7 @@ class Projects::RefsController < Projects::ApplicationController
0
end
@limit = 10
@limit = 25
@path = params[:path]
......
......@@ -98,8 +98,7 @@ class ProjectsController < ApplicationController
def destroy
return access_denied! unless can?(current_user, :remove_project, project)
project.team.truncate
project.destroy
::Projects::DestroyService.new(@project, current_user, {}).execute
respond_to do |format|
format.html { redirect_to root_path }
......@@ -125,18 +124,12 @@ class ProjectsController < ApplicationController
def autocomplete_sources
note_type = params['type']
note_id = params['type_id']
participating = if note_type && note_id
participants_in(note_type, note_id)
else
[]
end
team_members = sorted(@project.team.members)
participants = team_members + participating
participants = ::Projects::ParticipantsService.new(@project).execute(note_type, note_id)
@suggestions = {
emojis: Emoji.names.map { |e| { name: e, path: view_context.image_url("emoji/#{e}.png") } },
issues: @project.issues.select([:iid, :title, :description]),
mergerequests: @project.merge_requests.select([:iid, :title, :description]),
members: participants.uniq
members: participants
}
respond_to do |format|
......@@ -192,25 +185,4 @@ class ProjectsController < ApplicationController
def user_layout
current_user ? "projects" : "public_projects"
end
def participants_in(type, id)
users = case type
when "Issue"
issue = @project.issues.find_by_iid(id)
issue ? issue.participants : []
when "MergeRequest"
merge_request = @project.merge_requests.find_by_iid(id)
merge_request ? merge_request.participants : []
when "Commit"
author_ids = Note.for_commit_id(id).pluck(:author_id).uniq
User.where(id: author_ids)
else
[]
end
sorted(users)
end
def sorted(users)
users.uniq.to_a.compact.sort_by(&:username).map { |user| { username: user.username, name: user.name } }
end
end
class UsersSessionsController < Devise::SessionsController
def create
@return_to = params[:return_to]
super
end
end
......@@ -45,15 +45,15 @@ module EventsHelper
def event_feed_title(event)
if event.issue?
"#{event.author_name} #{event.action_name} issue ##{event.target_id}: #{event.issue_title} at #{event.project_name}"
"#{event.author_name} #{event.action_name} issue ##{event.target_iid}: #{event.issue_title} at #{event.project_name}"
elsif event.merge_request?
"#{event.author_name} #{event.action_name} MR ##{event.target_id}: #{event.merge_request_title} at #{event.project_name}"
"#{event.author_name} #{event.action_name} MR ##{event.target_iid}: #{event.merge_request_title} at #{event.project_name}"
elsif event.push?
"#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}"
elsif event.membership_changed?
"#{event.author_name} #{event.action_name} #{event.project_name}"
elsif event.note?
"#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_id} at #{event.project_name}"
"#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_iid} at #{event.project_name}"
else
""
end
......
......@@ -63,10 +63,14 @@ module GitlabMarkdownHelper
paths = extract_paths(text)
paths.uniq.each do |file_path|
new_path = rebuild_path(file_path)
# Finds quoted path so we don't replace other mentions of the string
# eg. "doc/api" will be replaced and "/home/doc/api/text" won't
text.gsub!("\"#{file_path}\"", "\"/#{new_path}\"")
# If project does not have repository
# its nothing to rebuild
if @repository.exists? && !@repository.empty?
new_path = rebuild_path(file_path)
# Finds quoted path so we don't replace other mentions of the string
# eg. "doc/api" will be replaced and "/home/doc/api/text" won't
text.gsub!("\"#{file_path}\"", "\"/#{new_path}\"")
end
end
text
......@@ -91,7 +95,12 @@ module GitlabMarkdownHelper
end
def link_to_ignore?(link)
ignored_protocols.map{ |protocol| link.include?(protocol) }.any?
if link =~ /\#\w+/
# ignore anchors like <a href="#my-header">
true
else
ignored_protocols.map{ |protocol| link.include?(protocol) }.any?
end
end
def ignored_protocols
......@@ -169,7 +178,7 @@ module GitlabMarkdownHelper
def current_sha
if @commit
@commit.id
else
elsif @repository && !@repository.empty?
@repository.head_commit.sha
end
end
......
......@@ -15,12 +15,6 @@ module NotesHelper
end
end
def link_to_merge_request_diff_line_note(note)
if note.for_merge_request_diff_line? and note.diff
link_to "#{note.diff_file_name}:L#{note.diff_new_line}", diffs_project_merge_request_path(note.project, note.noteable, anchor: note.line_code)
end
end
def note_timestamp(note)
# Shows the created at time and the updated at time if different
ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')}"
......@@ -61,4 +55,23 @@ module NotesHelper
link_to "", "javascript:;", class: "add-diff-note js-add-diff-note-button",
data: data, title: "Add a comment to this line"
end
def link_to_reply_diff(note)
return unless current_user
data = {
noteable_type: note.noteable_type,
noteable_id: note.noteable_id,
commit_id: note.commit_id,
line_code: note.line_code,
discussion_id: note.discussion_id
}
link_to "javascript:;", class: "btn reply-btn js-discussion-reply-button",
data: data, title: "Add a reply" do
link_text = ""
link_text < content_tag(:i, nil, class: 'icon-comment')
link_text << "Reply"
end
end
end
......@@ -49,12 +49,16 @@ module Mentionable
matches = mentionable_text.scan(/@[a-zA-Z][a-zA-Z0-9_\-\.]*/)
matches.each do |match|
identifier = match.delete "@"
if has_project
id = project.team.members.find_by(username: identifier).try(:id)
if identifier == "all"
users += project.team.members.flatten
else
id = User.where(username: identifier).pluck(:id).first
if has_project
id = project.team.members.find_by(username: identifier).try(:id)
else
id = User.find_by(username: identifier).try(:id)
end
users << User.find(id) unless id.blank?
end
users << User.find(id) unless id.blank?
end
users.uniq
end
......
......@@ -41,6 +41,9 @@ class Event < ActiveRecord::Base
# For Hash only
serialize :data
# Callbacks
after_create :reset_project_activity
# Scopes
scope :recent, -> { order("created_at DESC") }
scope :code_push, -> { where(action: PUSHED) }
......@@ -303,4 +306,10 @@ class Event < ActiveRecord::Base
target.respond_to? :title
end
end
def reset_project_activity
if project
project.update_column(:last_activity_at, self.created_at)
end
end
end
......@@ -57,6 +57,7 @@ class Note < ActiveRecord::Base
serialize :st_diff
before_create :set_diff, if: ->(n) { n.line_code.present? }
after_update :set_references
class << self
def create_status_change_note(noteable, project, author, status, source)
......@@ -178,10 +179,28 @@ class Note < ActiveRecord::Base
@diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
end
# Check if such line of code exists in merge request diff
# If exists - its active discussion
# If not - its outdated diff
def active?
# TODO: determine if discussion is outdated
# according to recent MR diff or not
true
return true unless self.diff
noteable.diffs.each do |mr_diff|
next unless mr_diff.new_path == self.diff.new_path
Gitlab::DiffParser.new(mr_diff.diff.lines.to_a, mr_diff.new_path).
each do |full_line, type, line_code, line_new, line_old|
if full_line == diff_line
return true
end
end
end
false
end
def outdated?
!active?
end
def diff_file_index
......@@ -314,4 +333,8 @@ class Note < ActiveRecord::Base
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end
def set_references
notice_added_references(project, author)
end
end
......@@ -128,6 +128,7 @@ class Repository
Rails.cache.delete(cache_key(:commit_count))
Rails.cache.delete(cache_key(:graph_log))
Rails.cache.delete(cache_key(:readme))
Rails.cache.delete(cache_key(:version))
Rails.cache.delete(cache_key(:contribution_guide))
end
......@@ -156,12 +157,24 @@ class Repository
Gitlab::Git::Blob.find(self, sha, path)
end
def blob_by_oid(oid)
Gitlab::Git::Blob.raw(self, oid)
end
def readme
Rails.cache.fetch(cache_key(:readme)) do
tree(:head).readme
end
end
def version
Rails.cache.fetch(cache_key(:version)) do
tree(:head).blobs.find do |file|
file.name.downcase == 'version'
end
end
end
def contribution_guide
Rails.cache.fetch(cache_key(:contribution_guide)) do
tree(:head).contribution_guide
......
......@@ -132,6 +132,10 @@ class User < ActiveRecord::Base
before_validation :sanitize_attrs
before_save :ensure_authentication_token
after_save :ensure_namespace_correct
after_create :post_create_hook
after_destroy :post_destroy_hook
alias_attribute :private_token, :authentication_token
......@@ -369,7 +373,7 @@ class User < ActiveRecord::Base
end
def several_namespaces?
owned_groups.any?
owned_groups.any? || masters_groups.any?
end
def namespace_id
......@@ -491,4 +495,36 @@ class User < ActiveRecord::Base
GravatarService.new.execute(email, size)
end
end
def ensure_namespace_correct
# Ensure user has namespace
self.create_namespace!(path: self.username, name: self.username) unless self.namespace
if self.username_changed?
self.namespace.update_attributes(path: self.username, name: self.username)
end
end
def post_create_hook
log_info("User \"#{self.name}\" (#{self.email}) was created")
notification_service.new_user(self)
system_hook_service.execute_hooks_for(self, :create)
end
def post_destroy_hook
log_info("User \"#{self.name}\" (#{self.email}) was removed")
system_hook_service.execute_hooks_for(self, :destroy)
end
def notification_service
NotificationService.new
end
def log_info message
Gitlab::AppLogger.info message
end
def system_hook_service
SystemHooksService.new
end
end
......@@ -33,6 +33,9 @@ class UsersGroup < ActiveRecord::Base
scope :with_group, ->(group) { where(group_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.id) }
after_create :notify_create
after_update :notify_update
validates :group_access, inclusion: { in: UsersGroup.group_access_roles.values }, presence: true
validates :user_id, presence: true
validates :group_id, presence: true
......@@ -43,4 +46,18 @@ class UsersGroup < ActiveRecord::Base
def access_field
group_access
end
def notify_create
notification_service.new_group_member(self)
end
def notify_update
if group_access_changed?
notification_service.update_group_member(self)
end
end
def notification_service
NotificationService.new
end
end
......@@ -37,6 +37,10 @@ class UsersProject < ActiveRecord::Base
scope :in_projects, ->(projects) { where(project_id: projects.map { |p| p.id }) }
scope :with_user, ->(user) { where(user_id: user.id) }
after_create :post_create_hook
after_update :post_update_hook
after_destroy :post_destroy_hook
class << self
# Add users to project teams with passed access option
......@@ -114,4 +118,37 @@ class UsersProject < ActiveRecord::Base
def owner?
project.owner == user
end
def post_create_hook
Event.create(
project_id: self.project.id,
action: Event::JOINED,
author_id: self.user.id
)
notification_service.new_team_member(self)
system_hook_service.execute_hooks_for(self, :create)
end
def post_update_hook
notification_service.update_team_member(self) if self.project_access_changed?
end
def post_destroy_hook
Event.create(
project_id: self.project.id,
action: Event::LEFT,
author_id: self.user.id
)
system_hook_service.execute_hooks_for(self, :destroy)
end
def notification_service
NotificationService.new
end
def system_hook_service
SystemHooksService.new
end
end
class BaseObserver < ActiveRecord::Observer
def notification
NotificationService.new
end
def event_service
EventCreateService.new
end
def log_info message
Gitlab::AppLogger.info message
end
end
class NoteObserver < BaseObserver
def after_create(note)
notification.new_note(note)
# Skip system notes, like status changes and cross-references.
unless note.system
event_service.leave_note(note, note.author)
# Create a cross-reference note if this Note contains GFM that names an
# issue, merge request, or commit.
note.references.each do |mentioned|
Note.create_cross_reference_note(mentioned, note.noteable, note.author, note.project)
end
end
end
def after_update(note)
note.notice_added_references(note.project, note.author)
end
end
class ProjectActivityCacheObserver < BaseObserver
observe :event
def after_create(event)
event.project.update_column(:last_activity_at, event.created_at) if event.project
end
end
class ProjectObserver < BaseObserver
def after_create(project)
log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"")
end
def after_update(project)
project.send_move_instructions if project.namespace_id_changed?
project.rename_repo if project.path_changed?
end
def before_destroy(project)
project.repository.expire_cache unless project.empty_repo?
end
def after_destroy(project)
GitlabShellWorker.perform_async(
:remove_repository,
project.path_with_namespace
)
GitlabShellWorker.perform_async(
:remove_repository,
project.path_with_namespace + ".wiki"
)
project.satellite.destroy
log_info("Project \"#{project.name}\" was removed")
end
end
class SystemHookObserver < BaseObserver
observe :user, :project, :users_project
def after_create(model)
system_hook_service.execute_hooks_for(model, :create)
end
def after_destroy(model)
system_hook_service.execute_hooks_for(model, :destroy)
end
private
def system_hook_service
SystemHooksService.new
end
end
class UserObserver < BaseObserver
def after_create(user)
log_info("User \"#{user.name}\" (#{user.email}) was created")
notification.new_user(user)
end
def after_destroy user
log_info("User \"#{user.name}\" (#{user.email}) was removed")
end
def after_save user
# Ensure user has namespace
user.create_namespace!(path: user.username, name: user.username) unless user.namespace
if user.username_changed?
user.namespace.update_attributes(path: user.username, name: user.username)
end
end
end
class UsersGroupObserver < BaseObserver
def after_create(membership)
notification.new_group_member(membership)
end
def after_update(membership)
notification.update_group_member(membership) if membership.group_access_changed?
end
end
class UsersProjectObserver < BaseObserver
def after_create(users_project)
Event.create(
project_id: users_project.project.id,
action: Event::JOINED,
author_id: users_project.user.id
)
notification.new_team_member(users_project)
end
def after_update(users_project)
notification.update_team_member(users_project) if users_project.project_access_changed?
end
def after_destroy(users_project)
Event.create(
project_id: users_project.project.id,
action: Event::LEFT,
author_id: users_project.user.id
)
end
end
......@@ -28,4 +28,8 @@ class BaseService
def log_info message
Gitlab::AppLogger.info message
end
def system_hook_service
SystemHooksService.new
end
end
......@@ -7,8 +7,11 @@ module Issues
Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
end
def execute_hooks(issue)
issue.project.execute_hooks(issue.to_hook_data, :issue_hooks)
def execute_hooks(issue, action = 'open')
issue_data = issue.to_hook_data
issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id)
issue_data[:object_attributes].merge!(url: issue_url, action: action)
issue.project.execute_hooks(issue_data, :issue_hooks)
end
def create_milestone_note(issue)
......
......@@ -5,7 +5,7 @@ module Issues
notification_service.close_issue(issue, current_user)
event_service.close_issue(issue, current_user)
create_note(issue, commit)
execute_hooks(issue)
execute_hooks(issue, 'close')
end
issue
......
......@@ -8,7 +8,7 @@ module Issues
notification_service.new_issue(issue, current_user)
event_service.open_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user)
execute_hooks(issue)
execute_hooks(issue, 'open')
end
issue
......
......@@ -4,7 +4,7 @@ module Issues
if issue.reopen
event_service.reopen_issue(issue, current_user)
create_note(issue)
execute_hooks(issue)
execute_hooks(issue, 'reopen')
end
issue
......
......@@ -23,7 +23,7 @@ module Issues
end
issue.notice_added_references(issue.project, current_user)
execute_hooks(issue)
execute_hooks(issue, 'update')
end
issue
......
module Notes
class CreateService < BaseService
def execute
note = project.notes.new(params[:note])
note = project.notes.new(params)
note.author = current_user
note.system = false
note.save
if note.save
notification_service.new_note(note)
# Skip system notes, like status changes and cross-references.
unless note.system
event_service.leave_note(note, note.author)
# Create a cross-reference note if this Note contains GFM that names an
# issue, merge request, or commit.
note.references.each do |mentioned|
Note.create_cross_reference_note(mentioned, note.noteable, note.author, note.project)
end
end
end
note
end
end
......
......@@ -51,6 +51,9 @@ module Projects
@project.creator = current_user
if @project.save
log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
system_hook_service.execute_hooks_for(@project, :create)
unless @project.group
@project.users_projects.create(
project_access: UsersProject::MASTER,
......
module Projects
class DestroyService < BaseService
def execute
return false unless can?(current_user, :remove_project, project)
project.team.truncate
project.repository.expire_cache unless project.empty_repo?
if project.destroy
GitlabShellWorker.perform_async(
:remove_repository,
project.path_with_namespace
)
GitlabShellWorker.perform_async(
:remove_repository,
project.path_with_namespace + ".wiki"
)
project.satellite.destroy
log_info("Project \"#{project.name}\" was removed")
system_hook_service.execute_hooks_for(project, :destroy)
true
end
end
end
end
module Projects
class ParticipantsService < BaseService
def initialize(project)
@project = project
end
def execute(note_type, note_id)
participating = if note_type && note_id
participants_in(note_type, note_id)
else
[]
end
team_members = sorted(@project.team.members)
participants = all_members + team_members + participating
participants.uniq
end
def participants_in(type, id)
users = case type
when "Issue"
issue = @project.issues.find_by_iid(id)
issue ? issue.participants : []
when "MergeRequest"
merge_request = @project.merge_requests.find_by_iid(id)
merge_request ? merge_request.participants : []
when "Commit"
author_ids = Note.for_commit_id(id).pluck(:author_id).uniq
User.where(id: author_ids)
else
[]
end
sorted(users)
end
def sorted(users)
users.uniq.to_a.compact.sort_by(&:username).map { |user| { username: user.username, name: user.name } }
end
def all_members
[{ username: "all", name: "Project and Group Members" }]
end
end
end
......@@ -13,7 +13,15 @@ module Projects
project.change_head(new_branch)
end
project.update_attributes(params[:project], as: role)
if project.update_attributes(params[:project], as: role)
if project.previous_changes.include?('namespace_id')
project.send_move_instructions
end
if project.previous_changes.include?('path')
project.rename_repo
end
end
end
end
end
......@@ -28,6 +28,7 @@ module Search
projects: [],
merge_requests: [],
issues: [],
notes: [],
total_results: 0,
}
end
......
......@@ -18,8 +18,9 @@ module Search
result[:total_results] = blobs.total_count
else
result[:merge_requests] = project.merge_requests.search(query).order('updated_at DESC').limit(20)
result[:issues] = project.issues.search(query).order('updated_at DESC').limit(20)
result[:total_results] = %w(issues merge_requests).sum { |items| result[items.to_sym].size }
result[:issues] = project.issues.where("title like :query OR description like :query ", query: "%#{query}%").order('updated_at DESC').limit(20)
result[:notes] = Note.where(noteable_type: 'issue').where(project_id: project.id).where("note like :query", query: "%#{query}%").order('updated_at DESC').limit(20)
result[:total_results] = %w(issues merge_requests notes).sum { |items| result[items.to_sym].size }
end
result
......@@ -30,6 +31,7 @@ module Search
merge_requests: [],
issues: [],
blobs: [],
notes: [],
total_results: 0,
}
end
......
......@@ -44,6 +44,8 @@
%li
%span.light Secondary email:
%strong= email.email
= link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-tiny btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do
%i.icon-remove
%li
%span.light Can create groups:
......
- if @has_authorized_projects
.dashboard.row
.activities.col-md-8
%section.activities.col-md-8
= render 'activities'
.side.col-md-4.left.responsive-side
%aside.side.col-md-4.left.responsive-side
= render 'sidebar'
.fixed.sidebar-expand-button.hidden-lg.hidden-md
......
......@@ -7,8 +7,8 @@
= f.check_box :remember_me
%span Remember me
%div
= hidden_field_tag 'return_to', params[:return_to]
= f.submit "Sign in", class: "btn-create btn"
.pull-right
= link_to "Forgot your password?", new_password_path(resource_name), class: "btn"
%h1.http_status_code 403
%h3.page-title Access Denied
%h1 403
%h3 Access Denied
%hr
%p You are not allowed to access this page.
%p Read more about project permissions #{link_to "here", help_page_path("permissions", "permissions"), class: "vlink"}
%h1.http_status_code 500
%h3.page-title Encoding Error
%h1 500
%h3 Encoding Error
%hr
%p Page can't be loaded because of an encoding error.
%h1.http_status_code 404
%h3.page-title Git Resource Not found
%h1 404
%h3 Git Resource Not found
%hr
%p
Application can't get access to some branch or commit in your repository. It
......
%h1.http_status_code 404
%h3.page-title The resource you were looking for doesn't exist.
%h1 404
%h3 The resource you were looking for doesn't exist.
%hr
%p You may have mistyped the address or the page may have moved.
%h1 422
%h3 Sign-in using #{@provider} auth failed
%hr
%p Sign-in failed because #{@error}.
%p There are couple of steps you can take:
%ul
%li Try logging in using your email
%li Try logging in using your username
%li If you have forgotten your password, try recovering it using #{ link_to "Password recovery", new_password_path(resource_name) }
%p If none of the options work, try contacting the GitLab administrator.
= form_tag group_filter_path(entity), method: 'get' do
%fieldset
%ul.nav.nav-pills.nav-stacked
%li{class: ("active" if !params[:status])}
= link_to group_filter_path(entity, status: nil) do
Open
%li{class: ("active" if params[:status] == 'closed')}
= link_to group_filter_path(entity, status: 'closed') do
Closed
%li{class: ("active" if params[:status] == 'all')}
= link_to group_filter_path(entity, status: 'all') do
All
%fieldset
%legend Projects:
%ul.nav.nav-pills.nav-stacked
- @projects.each do |project|
- unless entities_per_project(project, entity).zero?
%li{class: ("active" if params[:project_id] == project.id.to_s)}
= link_to group_filter_path(entity, project_id: project.id) do
= project.name_with_namespace
%small.pull-right= entities_per_project(project, entity)
- if @projects.blank?
.nothing-here-block This group has no projects yet
%fieldset
%hr
= link_to "Reset", group_filter_path(entity), class: 'btn pull-right'
......@@ -6,12 +6,12 @@
= f.label :name, class: 'control-label' do
Group name
.col-sm-10
= f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control"
= f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control", tabindex: 1, autofocus: true
.form-group.group-description-holder
= f.label :description, "Details", class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
= f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4, tabindex: 2
.form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label'
......@@ -35,6 +35,4 @@
%li Existing projects may be moved into a group
.form-actions
= f.submit 'Create group', class: "btn btn-create"
= f.submit 'Create group', class: "btn btn-create", tabindex: 3
.dashboard
.activities.col-md-8.hidden-sm.hidden-xs
%section.activities.col-md-8.hidden-sm.hidden-xs
- if current_user
= render "events/event_last_push", event: @last_push
= link_to dashboard_path, class: 'btn btn-tiny' do
......@@ -16,7 +16,7 @@
- else
.nothing-here-block Project activity will be displayed here
= spinner
.side.col-md-4
%aside.side.col-md-4
.light-well.append-bottom-20
= image_tag group_icon(@group.path), class: "avatar s90"
.clearfix.light
......
%head
%meta{charset: "utf-8"}
-# Go repository retrieval support
-# Need to be the fist thing in the head
-# Since Go is using an XML parser to process HTML5
-# https://github.com/gitlabhq/gitlabhq/pull/5958#issuecomment-45397555
- if controller_name == 'projects' && action_name == 'show'
%meta{name: "go-import", content: "#{@project.web_url_without_protocol} git #{@project.web_url}.git"}
%meta{content: "GitLab Enterprise Edition", name: "description"}
%title
= "#{title} | " if defined?(title)
GitLab
......@@ -24,8 +33,3 @@
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, format: :atom, private_token: current_user.private_token), title: "Recent commits to #{@project.name}:#{@ref}")
- if current_controller?(:issues)
= auto_discovery_link_tag(:atom, project_issues_url(@project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
-# Go repository retrieval support.
- if controller_name == 'projects' && action_name == 'show'
%meta{name: "go-import", content: "#{@project.web_url_without_protocol} git #{@project.web_url}.git"}
......@@ -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, return_to: request.fullpath), 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, return_to: request.fullpath)
.search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
= text_field_tag "search", nil, placeholder: search_placeholder, class: "search-input"
= search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input"
= hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
......
......@@ -4,7 +4,6 @@
%body{class: "#{app_theme} application"}
= render "layouts/head_panel", title: "" if current_user
= render "layouts/flash"
.container
.content
%center.padded.prepend-top-20
= yield
.container.navless-container
.error-page
= yield
%ul
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: "Stats" do
%i.icon-home
Overview
= nav_link(controller: :projects) do
= link_to "Projects", admin_projects_path
= nav_link(controller: :groups) do
= link_to "Groups", admin_groups_path
= nav_link(controller: :users) do
= link_to "Users", admin_users_path
= nav_link(controller: :groups) do
= link_to "Groups", admin_groups_path
= nav_link(controller: :logs) do
= link_to "Logs", admin_logs_path
= nav_link(controller: :broadcast_messages) do
......
%ul
= nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
= link_to root_path, title: "Home" do
%i.icon-home
Activity
= nav_link(path: 'dashboard#projects') do
= link_to projects_dashboard_path do
Projects
......
%ul
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: "Home" do
%i.icon-home
Activity
= nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group) do
Issues
......
%ul
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: "Profile" do
%i.icon-home
Profile
= nav_link(controller: :accounts) do
= link_to "Account", profile_account_path
= nav_link(controller: :emails) do
......
%ul
= nav_link(path: 'projects#show', html_options: {class: "home"}) do
= link_to project_path(@project), title: "Project" do
%i.icon-home
Activity
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
......
......@@ -5,7 +5,7 @@
= render 'form'
:javascript
$('#key_key').on('keyup', function(){
$('#key_key').on('focusout', function(){
var title = $('#key_title'),
val = $('#key_key').val(),
key_mail = val.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+|\.[a-zA-Z0-9._-]+)/gi);
......@@ -13,4 +13,4 @@
if( key_mail && key_mail.length > 0 && title.val() == '' ){
$('#key_title').val( key_mail );
}
});
});
\ No newline at end of file
......@@ -68,12 +68,14 @@
%p.light
- if @user.avatar?
You can change your avatar here
%br
or remove the current avatar to revert to #{link_to "gravatar.com", "http://gravatar.com"}
- if Gitlab.config.gravatar.enabled
%br
or remove the current avatar to revert to #{link_to "gravatar.com", "http://gravatar.com"}
- else
You can upload an avatar here
%br
or change it at #{link_to "gravatar.com", "http://gravatar.com"}
- if Gitlab.config.gravatar.enabled
%br
or change it at #{link_to "gravatar.com", "http://gravatar.com"}
%hr
%a.choose-btn.btn.btn-small.js-choose-user-avatar-button
%i.icon-paper-clip
......
......@@ -21,6 +21,11 @@
- if can?(current_user, :admin_project, @project)
&ndash;
%strong= link_to 'Edit', edit_project_path
- elsif !@project.empty_repo? && @repository.readme
- readme = @repository.readme
&ndash;
= link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)) do
= readme.name
- unless empty_repo
.col-md-5
......
......@@ -20,7 +20,7 @@
= render blob_commit, project: @project
%div#tree-content-holder.tree-content-holder
.file-holder
%article.file-holder
.file-title.clearfix
%i.icon-file
%span.file_name
......
.file-content.blob_file.blob-no-preview
%center
.center
= link_to project_raw_path(@project, @id) do
%h1.light
%i.icon-download-alt
......
......@@ -9,11 +9,15 @@
= link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right"
.notes_count
- notes = project.notes.for_commit_id(commit.id)
- if notes.any?
- if @note_counts
- note_count = @note_counts.fetch(commit.id, 0)
- else
- notes = project.notes.for_commit_id(commit.id)
- note_count = notes.count
- if note_count > 0
%span.label.label-gray
%i.icon-comment
= notes.count
%i.icon-comment= note_count
- if commit.description?
.commit-row-description.js-toggle-content
......
......@@ -45,7 +45,7 @@
- file = project.repository.blob_at(@commit.id, diff.new_path)
- file = project.repository.blob_at(@commit.parent_id, diff.old_path) unless file
- next unless file
.diff-file.js-toggle-container{id: "diff-#{i}"}
.diff-file{id: "diff-#{i}"}
.diff-header{id: "file-path-#{hexdigest(diff.new_path || diff.old_path)}"}
- if diff.deleted_file
%span= diff.old_path
......@@ -61,7 +61,7 @@
%span.file-mode= "#{diff.a_mode}#{diff.b_mode}"
.diff-btn-group
= link_to "#", class: "js-toggle-button btn btn-small" do
= link_to "#", class: "js-toggle-diff-comments btn btn-small" do
%i.icon-chevron-down
Diff comments
&nbsp;
......
......@@ -33,7 +33,7 @@
- else
.light-well
%center
.center
%h4
There isn't anything to compare.
%p.slead
......
......@@ -190,7 +190,7 @@
.nothing-here-block Only project owner can remove a project
.save-project-loader.hide
%center
.center
%h2
%i.icon-spinner.icon-spin
Saving project.
......
......@@ -18,7 +18,7 @@
.file-content.code
%pre.js-edit-mode-pane#editor= @blob.data
.js-edit-mode-pane#preview.hide
%center
.center
%h2
%i.icon-spinner.icon-spin
......
......@@ -44,5 +44,10 @@
If this field is empty it allows any commit message.
For example you can require that an issue number is always mentioned in the commit message.
.alert.alert-warning.alert-dismissible{ role: 'alert'}
%button.close{"data-dismiss" => "alert", type: "button"}
%span{"aria-hidden" => "true"} ×
%span.sr-only Close
Would you like to see another Git Hook? If you are a subscriber we'll be glad to implement it without any costs. Please contact your GitLab account manager.
.form-actions
= f.submit "Save Git hooks", class: "btn btn-create"
.loading-graph
%center
.center
%h3.page-title
%i.icon-spinner.icon-spin
Building repository graph.
......
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