- Respect newlines in wall messages
- Generate the Rails secret token on first run
- Rename repo feature
- - Init.d: remove gitlab.scoket on service starta
+ - Init.d: remove gitlab.socket on service start
- Api: added teams api
- Api: Prevent blob content being escaped
+ - Api: Smart deploy key add behaviour
+ - Api: projets/owned.json return user owned project
+ - Fix bug with team assignation on project from #4109
+ - Advanced snippets: public/private, project/personal (Andrew Kulakov)
+ - Repository Graphs (Karlo Nicholas T. Soriano)
+ - Fix dashboard lost if comment on commit
+ - Update gitlab-grack. Fixes issue with --depth option
+ - Fix project events duplicate on project page
+ - Fix postgres error when displaying network graph.
+ - Fix dashboard event filter when navigate via turbolinks
+ - init.d: Ensure socket is removed before starting service
+ - Admin area: Style teams:index, group:show pages
+ - Own page for failed forking
v 5.2.0
- Turbolinks
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem 'gitlab_git', '~> 1.2.1'
+gem 'gitlab_git', '~> 1.3.0'
# Ruby/Rack Git Smart-HTTP Server Handler
-gem 'gitlab-grack', '~> 1.0.0', require: 'grack'
+gem 'gitlab-grack', '~> 1.0.1', require: 'grack'
# LDAP Auth
gem 'gitlab_omniauth-ldap', '1.0.2', require: "omniauth-ldap"
# HipChat integration
gem "hipchat", "~> 0.9.0"
+# d3
+gem "d3_rails", "~> 3.1.4"
+
+# underscore-rails
+gem "underscore-rails", "~> 1.4.4"
+
group :assets do
gem "sass-rails"
gem "coffee-rails"
gem "modernizr", "2.6.2"
gem "raphael-rails", git: "https://github.com/gitlabhq/raphael-rails.git"
gem 'bootstrap-sass'
- gem "font-awesome-sass-rails", "~> 3.0.0"
+ gem "font-awesome-rails", "~> 3.1.1"
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
gem "gon"
end
gem 'poltergeist', '~> 1.3.0'
gem 'spork', '~> 1.0rc'
+ gem 'jasmine'
end
group :test do
celluloid (0.14.0)
timers (>= 1.0.0)
charlock_holmes (0.6.9.4)
+ childprocess (0.3.9)
+ ffi (~> 1.0, >= 1.0.11)
chosen-rails (0.9.8)
railties (~> 3.0)
thor (~> 0.14)
simplecov (>= 0.7)
thor
crack (0.3.2)
+ d3_rails (3.1.4)
+ railties (>= 3.1.0)
daemons (1.1.9)
database_cleaner (1.0.1)
debug_inspector (0.0.2)
eventmachine (>= 0.12.0)
ffaker (1.16.0)
ffi (1.8.1)
- font-awesome-sass-rails (3.0.2.2)
- railties (>= 3.1.1)
- sass-rails (>= 3.1.1)
+ font-awesome-rails (3.1.1.3)
+ railties (>= 3.2, < 5.0)
foreman (0.63.0)
dotenv (>= 0.7)
thor (>= 0.13.6)
pygments.rb (~> 0.4.2)
sanitize (~> 2.0.3)
stringex (~> 1.5.1)
- gitlab-grack (1.0.0)
+ gitlab-grack (1.0.1)
rack (~> 1.4.1)
gitlab-grit (2.5.1)
charlock_holmes (~> 0.6.9)
gitlab-pygments.rb (0.3.2)
posix-spawn (~> 0.3.6)
yajl-ruby (~> 1.1.0)
- gitlab_git (1.2.1)
+ gitlab_git (1.3.0)
activesupport (~> 3.2.13)
github-linguist (~> 2.3.4)
gitlab-grit (~> 2.5.1)
multi_xml (>= 0.5.2)
httpauth (0.2.0)
i18n (0.6.1)
+ jasmine (1.3.2)
+ jasmine-core (~> 1.3.1)
+ rack (~> 1.0)
+ rspec (>= 1.3.1)
+ selenium-webdriver (>= 0.1.3)
+ jasmine-core (1.3.1)
journey (1.0.4)
jquery-atwho-rails (0.3.0)
jquery-rails (2.1.3)
rspec-mocks (~> 2.13.0)
ruby-progressbar (1.0.2)
rubyntlm (0.1.1)
+ rubyzip (0.9.9)
sanitize (2.0.3)
nokogiri (>= 1.4.4, < 1.6)
sass (3.2.9)
select2-rails (3.3.1)
sass-rails (>= 3.2)
thor (~> 0.14)
+ selenium-webdriver (2.32.1)
+ childprocess (>= 0.2.5)
+ multi_json (~> 1.0)
+ rubyzip
+ websocket (~> 1.0.4)
settingslogic (2.0.9)
sexp_processor (4.2.1)
shoulda-matchers (2.1.0)
uglifier (2.0.1)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
+ underscore-rails (1.4.4)
virtus (0.5.4)
backports (~> 2.6.1)
descendants_tracker (~> 0.0.1)
webmock (1.11.0)
addressable (>= 2.2.7)
crack (>= 0.3.2)
+ websocket (1.0.7)
xpath (2.0.0)
nokogiri (~> 1.3)
yajl-ruby (1.1.0)
coffee-rails
colored
coveralls
+ d3_rails (~> 3.1.4)
database_cleaner
devise
email_spec
enumerize
factory_girl_rails
ffaker
- font-awesome-sass-rails (~> 3.0.0)
+ font-awesome-rails (~> 3.1.1)
foreman
gemoji (~> 1.2.1)
github-linguist
github-markup (~> 0.7.4)
gitlab-gollum-lib (~> 1.0.0)
- gitlab-grack (~> 1.0.0)
+ gitlab-grack (~> 1.0.1)
gitlab-pygments.rb (~> 0.3.2)
- gitlab_git (~> 1.2.1)
+ gitlab_git (~> 1.3.0)
gitlab_meta (= 5.0)
gitlab_omniauth-ldap (= 1.0.2)
gon
haml-rails
hipchat (~> 0.9.0)
httparty
+ jasmine
jquery-atwho-rails (= 0.3.0)
jquery-rails (= 2.1.3)
jquery-turbolinks
tinder (~> 1.9.2)
turbolinks
uglifier
+ underscore-rails (~> 1.4.4)
webmock
modal = $('.change-owner-holder')
- $('.change-owner-link').bind "click", ->
+ $('.change-owner-link').bind "click", (e) ->
+ e.preventDefault()
$(this).hide()
modal.show()
- $('.change-owner-cancel-link').bind "click", ->
+ $('.change-owner-cancel-link').bind "click", (e) ->
+ e.preventDefault()
modal.hide()
$('.change-owner-link').show()
//= require branch-graph
//= require ace-src-noconflict/ace
//= require_tree .
+//= require d3
+//= require underscore
@disable = true
@initLoadMore: ->
+ $(document).unbind('scroll')
$(document).endlessScroll
bottomPixels: 400
fireDelay: 1000
--- /dev/null
+class window.StatGraph
+ @log: {}
+ @get_log: ->
+ @log
+ @set_log: (data) ->
+ @log = data
--- /dev/null
+class window.ContributorsStatGraph
+ init: (log) ->
+ @parsed_log = ContributorsStatGraphUtil.parse_log(log)
+ @set_current_field("commits")
+ total_commits = ContributorsStatGraphUtil.get_total_data(@parsed_log, @field)
+ author_commits = ContributorsStatGraphUtil.get_author_data(@parsed_log, @field)
+ @add_master_graph(total_commits)
+ @add_authors_graph(author_commits)
+ @change_date_header()
+ add_master_graph: (total_data) ->
+ @master_graph = new ContributorsMasterGraph(total_data)
+ @master_graph.draw()
+ add_authors_graph: (author_data) ->
+ @authors = []
+ _.each(author_data, (d) =>
+ author_header = @create_author_header(d)
+ $(".contributors-list").append(author_header)
+ @authors[d.author] = author_graph = new ContributorsAuthorGraph(d.dates)
+ author_graph.draw()
+ )
+ format_author_commit_info: (author) ->
+ author.commits + " commits " + author.additions + " ++ / " + author.deletions + " --"
+ create_author_header: (author) ->
+ list_item = $('<li/>', {
+ class: 'person'
+ style: 'display: block;'
+ })
+ author_name = $('<h4>' + author.author + '</h4>')
+ author_commit_info_span = $('<span/>', {
+ class: 'commits'
+ })
+ author_commit_info = @format_author_commit_info(author)
+ author_commit_info_span.text(author_commit_info)
+ list_item.append(author_name)
+ list_item.append(author_commit_info_span)
+ list_item
+ redraw_master: ->
+ total_data = ContributorsStatGraphUtil.get_total_data(@parsed_log, @field)
+ @master_graph.set_data(total_data)
+ @master_graph.redraw()
+ redraw_authors: ->
+ $("ol").html("")
+ x_domain = ContributorsGraph.prototype.x_domain
+ author_commits = ContributorsStatGraphUtil.get_author_data(@parsed_log, @field, x_domain)
+ _.each(author_commits, (d) =>
+ @redraw_author_commit_info(d)
+ $(@authors[d.author].list_item).appendTo("ol")
+ @authors[d.author].set_data(d.dates)
+ @authors[d.author].redraw()
+ )
+ set_current_field: (field) ->
+ @field = field
+ change_date_header: ->
+ x_domain = ContributorsGraph.prototype.x_domain
+ print_date_format = d3.time.format("%B %e %Y");
+ print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
+ $("#date_header").text(print);
+ redraw_author_commit_info: (author) ->
+ author_list_item = $(@authors[author.author].list_item)
+ author_commit_info = @format_author_commit_info(author)
+ author_list_item.find("span").text(author_commit_info)
\ No newline at end of file
--- /dev/null
+class window.ContributorsGraph
+ MARGIN:
+ top: 20
+ right: 20
+ bottom: 30
+ left: 50
+ x_domain: null
+ y_domain: null
+ dates: []
+ @set_x_domain: (data) =>
+ @prototype.x_domain = data
+ @set_y_domain: (data) =>
+ @prototype.y_domain = [0, d3.max(data, (d) ->
+ d.commits = d.commits ? d.additions ? d.deletions
+ )]
+ @init_x_domain: (data) =>
+ @prototype.x_domain = d3.extent(data, (d) ->
+ d.date
+ )
+ @init_y_domain: (data) =>
+ @prototype.y_domain = [0, d3.max(data, (d) ->
+ d.commits = d.commits ? d.additions ? d.deletions
+ )]
+ @init_domain: (data) =>
+ @init_x_domain(data)
+ @init_y_domain(data)
+ @set_dates: (data) =>
+ @prototype.dates = data
+ set_x_domain: ->
+ @x.domain(@x_domain)
+ set_y_domain: ->
+ @y.domain(@y_domain)
+ set_domain: ->
+ @set_x_domain()
+ @set_y_domain()
+ create_scale: (width, height) ->
+ @x = d3.time.scale().range([0, width]).clamp(true)
+ @y = d3.scale.linear().range([height, 0]).nice()
+ draw_x_axis: ->
+ @svg.append("g").attr("class", "x axis").attr("transform", "translate(0, #{@height})")
+ .call(@x_axis);
+ draw_y_axis: ->
+ @svg.append("g").attr("class", "y axis").call(@y_axis)
+ set_data: (data) ->
+ @data = data
+
+class window.ContributorsMasterGraph extends ContributorsGraph
+ constructor: (@data) ->
+ @width = 1100
+ @height = 125
+ @x = null
+ @y = null
+ @x_axis = null
+ @y_axis = null
+ @area = null
+ @svg = null
+ @brush = null
+ @x_max_domain = null
+ process_dates: (data) ->
+ dates = @get_dates(data)
+ @parse_dates(data)
+ ContributorsGraph.set_dates(dates)
+ get_dates: (data) ->
+ _.pluck(data, 'date')
+ parse_dates: (data) ->
+ parseDate = d3.time.format("%Y-%m-%d").parse
+ data.forEach((d) ->
+ d.date = parseDate(d.date)
+ )
+ create_scale: ->
+ super @width, @height
+ create_axes: ->
+ @x_axis = d3.svg.axis().scale(@x).orient("bottom")
+ @y_axis = d3.svg.axis().scale(@y).orient("left")
+ create_svg: ->
+ @svg = d3.select("#contributors-master").append("svg")
+ .attr("width", @width + @MARGIN.left + @MARGIN.right)
+ .attr("height", @height + @MARGIN.top + @MARGIN.bottom)
+ .attr("class", "tint-box")
+ .append("g")
+ .attr("transform", "translate(" + @MARGIN.left + "," + @MARGIN.top + ")")
+ create_area: (x, y) ->
+ @area = d3.svg.area().x((d) ->
+ x(d.date)
+ ).y0(@height).y1((d) ->
+ y(d.commits = d.commits ? d.additions ? d.deletions)
+ ).interpolate("basis")
+ create_brush: ->
+ @brush = d3.svg.brush().x(@x).on("brushend", @update_content);
+ draw_path: (data) ->
+ @svg.append("path").datum(data).attr("class", "area").attr("d", @area);
+ add_brush: ->
+ @svg.append("g").attr("class", "selection").call(@brush).selectAll("rect").attr("height", @height);
+ update_content: =>
+ ContributorsGraph.set_x_domain(if @brush.empty() then @x_max_domain else @brush.extent())
+ $("#brush_change").trigger('change')
+ draw: ->
+ @process_dates(@data)
+ @create_scale()
+ @create_axes()
+ ContributorsGraph.init_domain(@data)
+ @x_max_domain = @x_domain
+ @set_domain()
+ @create_area(@x, @y)
+ @create_svg()
+ @create_brush()
+ @draw_path(@data)
+ @draw_x_axis()
+ @draw_y_axis()
+ @add_brush()
+ redraw: ->
+ @process_dates(@data)
+ ContributorsGraph.set_y_domain(@data)
+ @set_y_domain()
+ @svg.select("path").datum(@data)
+ @svg.select("path").attr("d", @area)
+ @svg.select(".y.axis").call(@y_axis)
+
+class window.ContributorsAuthorGraph extends ContributorsGraph
+ constructor: (@data) ->
+ @width = 490
+ @height = 130
+ @x = null
+ @y = null
+ @x_axis = null
+ @y_axis = null
+ @area = null
+ @svg = null
+ @list_item = null
+ create_scale: ->
+ super @width, @height
+ create_axes: ->
+ @x_axis = d3.svg.axis().scale(@x).orient("bottom").tickFormat(d3.time.format("%m/%d"));
+ @y_axis = d3.svg.axis().scale(@y).orient("left")
+ create_area: (x, y) ->
+ @area = d3.svg.area().x((d) ->
+ parseDate = d3.time.format("%Y-%m-%d").parse
+ x(parseDate(d))
+ ).y0(@height).y1((d) =>
+ if @data[d]? then y(@data[d]) else y(0)
+ ).interpolate("basis")
+ create_svg: ->
+ @list_item = d3.selectAll(".person")[0].pop()
+ @svg = d3.select(@list_item).append("svg")
+ .attr("width", @width + @MARGIN.left + @MARGIN.right)
+ .attr("height", @height + @MARGIN.top + @MARGIN.bottom)
+ .attr("class", "spark")
+ .append("g")
+ .attr("transform", "translate(" + @MARGIN.left + "," + @MARGIN.top + ")")
+ draw_path: (data) ->
+ @svg.append("path").datum(data).attr("class", "area-contributor").attr("d", @area);
+ draw: ->
+ @create_scale()
+ @create_axes()
+ @set_domain()
+ @create_area(@x, @y)
+ @create_svg()
+ @draw_path(@dates)
+ @draw_x_axis()
+ @draw_y_axis()
+ redraw: ->
+ @set_domain()
+ @svg.select("path").datum(@dates)
+ @svg.select("path").attr("d", @area)
+ @svg.select(".x.axis").call(@x_axis)
+ @svg.select(".y.axis").call(@y_axis)
--- /dev/null
+window.ContributorsStatGraphUtil =
+ parse_log: (log) ->
+ total = {}
+ by_author = {}
+ for entry in log
+ @add_date(entry.date, total) unless total[entry.date]?
+ @add_author(entry.author, by_author) unless by_author[entry.author]?
+ @add_date(entry.date, by_author[entry.author]) unless by_author[entry.author][entry.date]
+ @store_data(entry, total[entry.date], by_author[entry.author][entry.date])
+ total = _.toArray(total)
+ by_author = _.toArray(by_author)
+ total: total, by_author: by_author
+
+ add_date: (date, collection) ->
+ collection[date] = {}
+ collection[date].date = date
+
+ add_author: (author, by_author) ->
+ by_author[author] = {}
+ by_author[author].author = author
+
+ store_data: (entry, total, by_author) ->
+ @store_commits(total, by_author)
+ @store_additions(entry, total, by_author)
+ @store_deletions(entry, total, by_author)
+
+ store_commits: (total, by_author) ->
+ @add(total, "commits", 1)
+ @add(by_author, "commits", 1)
+
+ add: (collection, field, value) ->
+ collection[field] ?= 0
+ collection[field] += value
+
+ store_additions: (entry, total, by_author) ->
+ entry.additions ?= 0
+ @add(total, "additions", entry.additions)
+ @add(by_author, "additions", entry.additions)
+
+ store_deletions: (entry, total, by_author) ->
+ entry.deletions ?= 0
+ @add(total, "deletions", entry.deletions)
+ @add(by_author, "deletions", entry.deletions)
+
+ get_total_data: (parsed_log, field) ->
+ log = parsed_log.total
+ total_data = @pick_field(log, field)
+ _.sortBy(total_data, (d) ->
+ d.date
+ )
+ pick_field: (log, field) ->
+ total_data = []
+ _.each(log, (d) ->
+ total_data.push(_.pick(d, [field, 'date']))
+ )
+ total_data
+
+ get_author_data: (parsed_log, field, date_range = null) ->
+ log = parsed_log.by_author
+ author_data = []
+
+ _.each(log, (log_entry) =>
+ parsed_log_entry = @parse_log_entry(log_entry, field, date_range)
+ if not _.isEmpty(parsed_log_entry.dates)
+ author_data.push(parsed_log_entry)
+ )
+
+ _.sortBy(author_data, (d) ->
+ d[field]
+ ).reverse()
+
+ parse_log_entry: (log_entry, field, date_range) ->
+ parsed_entry = {}
+ parsed_entry.author = log_entry.author
+ parsed_entry.dates = {}
+ parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0
+ _.each(_.omit(log_entry, 'author'), (value, key) =>
+ if @in_range(value.date, date_range)
+ parsed_entry.dates[value.date] = value[field]
+ parsed_entry.commits += value.commits
+ parsed_entry.additions += value.additions
+ parsed_entry.deletions += value.deletions
+ )
+ return parsed_entry
+
+ in_range: (date, date_range) ->
+ if date_range is null || date_range[0] <= new Date(date) <= date_range[1]
+ true
+ else
+ false
+
\ No newline at end of file
userFormatSelection = (user) ->
user.name
- $('.ajax-users-select').select2
- placeholder: "Search for a user"
- multiple: $('.ajax-users-select').hasClass('multiselect')
- minimumInputLength: 0
- query: (query) ->
- Api.users query.term, (users) ->
- data = { results: users }
- query.callback(data)
+ $('.ajax-users-select').each (i, select) ->
+ $(select).select2
+ placeholder: "Search for a user"
+ multiple: $(select).hasClass('multiselect')
+ minimumInputLength: 0
+ query: (query) ->
+ Api.users query.term, (users) ->
+ data = { results: users }
+ query.callback(data)
- initSelection: (element, callback) ->
- id = $(element).val()
- if id isnt ""
- Api.user(id, callback)
+ initSelection: (element, callback) ->
+ id = $(element).val()
+ if id isnt ""
+ Api.user(id, callback)
- formatResult: userFormatResult
- formatSelection: userFormatSelection
- dropdownCssClass: "ajax-users-dropdown"
- escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
- m
+ formatResult: userFormatResult
+ formatSelection: userFormatSelection
+ dropdownCssClass: "ajax-users-dropdown"
+ escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
+ m
@import "sections/wiki.scss";
@import "sections/wall.scss";
@import "sections/dashboard.scss";
+@import "sections/stat_graph.scss";
@import "highlight/white.scss";
@import "highlight/dark.scss";
overflow: hidden;
height: 220px;
}
+
+.navless-container {
+ margin-top: 30px;
+}
+
+.description-block {
+ @extend .light-well;
+ @extend .light;
+ margin-bottom: 10px;
+}
-.black .highlight {
+.dark .highlight {
background-color: #333;
--- /dev/null
+.tint-box {
+ background: #f3f3f3;
+ position: relative;
+ margin-bottom: 10px;
+}
+
+.area {
+ fill: #1db34f;
+ fill-opacity: 0.5;
+}
+
+.axis {
+ fill: #aaa;
+ font-size: 10px;
+}
+
+#contributors .person {
+ &:nth-child(even) {
+ float: right;
+ }
+ float: left;
+ margin-top: 10px;
+}
+
+.contributors-list {
+ margin: 0 0 10px 0;
+ list-style: none;
+ padding: 0;
+}
+
+#contributors .person .spark {
+ display: block;
+ background: #f3f3f3;
+}
+
+#contributors .person .area-contributor {
+ fill: #f17f49;
+}
+
+.selection rect {
+ fill: #333;
+ fill-opacity: 0.1;
+ stroke: #333;
+ stroke-width: 1px;
+ stroke-opacity: 0.4;
+ shape-rendering: crispedges;
+ stroke-dasharray: 3 3;
+}
.ajax-users-select {
width: 400px;
+
+ &.input-large {
+ width: 210px;
+ }
}
.user-result {
@projects = @projects.not_in_group(@group) if @group.projects.present?
@projects = @projects.all
@projects.reject!(&:empty_repo?)
-
- @users = User.active
end
def new
end
def project_teams_update
- @group.add_users_to_project_teams(params[:user_ids], params[:project_access])
+ @group.add_users_to_project_teams(params[:user_ids].split(','), params[:project_access])
+
redirect_to [:admin, @group], notice: 'Users were successfully added.'
end
before_filter :project, only: [:edit, :show, :update, :destroy, :team_update]
def index
- @projects = Project.scoped
- @projects = @projects.where(namespace_id: params[:namespace_id]) if params[:namespace_id].present?
+ owner_id = params[:owner_id]
+ user = User.find_by_id(owner_id)
+
+ @projects = user ? user.owned_projects : Project.scoped
@projects = @projects.where(public: true) if params[:public_only].present?
@projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present?
def show
@repository = @project.repository
- @users = User.active
- @users = @users.not_in_project(@project) if @project.users.present?
- @users = @users.all
end
protected
protected
def available_keys
- @available_keys ||= DeployKey.in_projects(current_user.owned_projects).uniq
+ @available_keys ||= current_user.accessible_deploy_keys
end
end
--- /dev/null
+class GraphsController < ProjectResourceController
+ # Authorize
+ before_filter :authorize_read_project!
+ before_filter :authorize_code_access!
+ before_filter :require_non_empty_project
+
+ def show
+ respond_to do |format|
+ format.html
+ format.js do
+ @repo = @project.repository
+ @stats = Gitlab::Git::GitStats.new(@repo.raw, @repo.root_ref)
+ @log = @stats.parsed_log.to_json
+ end
+ end
+ end
+end
class GroupsController < ApplicationController
respond_to :html
- layout 'group', except: [:new, :create]
-
before_filter :group, except: [:new, :create]
# Authorize
# Load group projects
before_filter :projects, except: [:new, :create]
+ layout :determine_layout
+
+ before_filter :set_title, only: [:new, :create]
+
def new
@group = Group.new
end
end
def team_members
- @group.add_users_to_project_teams(params[:user_ids], params[:project_access])
+ @group.add_users_to_project_teams(params[:user_ids].split(','), params[:project_access])
redirect_to people_group_path(@group), notice: 'Users were successfully added.'
end
return render_404
end
end
+
+ def set_title
+ @title = 'New Group'
+ end
+
+ def determine_layout
+ if [:new, :create].include?(action_name.to_sym)
+ 'navless'
+ else
+ 'group'
+ end
+ end
end
class HelpController < ApplicationController
def index
end
+
+ def api
+ @category = params[:category]
+ @category = "README" if @category.blank?
+
+ if File.exists?(Rails.root.join('doc', 'api', @category + '.md'))
+ render 'api'
+ else
+ not_found!
+ end
+ end
end
-class GraphController < ProjectResourceController
+class NetworkController < ProjectResourceController
include ExtractsPath
include ApplicationHelper
class Projects::ApplicationController < ApplicationController
-
- before_filter :authorize_admin_team_member!
-
- protected
-
- def user_team
- @team ||= UserTeam.find_by_path(params[:id])
- end
-
+ before_filter :project
+ before_filter :repository
end
--- /dev/null
+class Projects::SnippetsController < Projects::ApplicationController
+ before_filter :module_enabled
+ before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw]
+
+ # Allow read any snippet
+ before_filter :authorize_read_project_snippet!
+
+ # Allow write(create) snippet
+ before_filter :authorize_write_project_snippet!, only: [:new, :create]
+
+ # Allow modify snippet
+ before_filter :authorize_modify_project_snippet!, only: [:edit, :update]
+
+ # Allow destroy snippet
+ before_filter :authorize_admin_project_snippet!, only: [:destroy]
+
+ layout 'project_resource'
+
+ respond_to :html
+
+ def index
+ @snippets = @project.snippets.fresh.non_expired
+ end
+
+ def new
+ @snippet = @project.snippets.build
+ end
+
+ def create
+ @snippet = @project.snippets.build(params[:project_snippet])
+ @snippet.author = current_user
+
+ if @snippet.save
+ redirect_to project_snippet_path(@project, @snippet)
+ else
+ respond_with(@snippet)
+ end
+ end
+
+ def edit
+ end
+
+ def update
+ if @snippet.update_attributes(params[:project_snippet])
+ redirect_to project_snippet_path(@project, @snippet)
+ else
+ respond_with(@snippet)
+ end
+ end
+
+ def show
+ @note = @project.notes.new(noteable: @snippet)
+ @target_type = :snippet
+ @target_id = @snippet.id
+ end
+
+ def destroy
+ return access_denied! unless can?(current_user, :admin_project_snippet, @snippet)
+
+ @snippet.destroy
+
+ redirect_to project_snippets_path(@project)
+ end
+
+ def raw
+ send_data(
+ @snippet.content,
+ type: "text/plain",
+ disposition: 'inline',
+ filename: @snippet.file_name
+ )
+ end
+
+ protected
+
+ def snippet
+ @snippet ||= @project.snippets.find(params[:id])
+ end
+
+ def authorize_modify_project_snippet!
+ return render_404 unless can?(current_user, :modify_project_snippet, @snippet)
+ end
+
+ def authorize_admin_project_snippet!
+ return render_404 unless can?(current_user, :admin_project_snippet, @snippet)
+ end
+
+ def module_enabled
+ return render_404 unless @project.snippets_enabled
+ end
+end
class Projects::TeamsController < Projects::ApplicationController
+ before_filter :authorize_admin_team_member!
+
def available
@teams = current_user.is_admin? ? UserTeam.scoped : current_user.user_teams
@teams = @teams.without_project(project)
redirect_to project_team_index_path(project)
end
+ protected
+
+ def user_team
+ @team ||= UserTeam.find_by_path(params[:id])
+ end
end
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer]
before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
- layout 'application', only: [:new, :create]
+ layout 'navless', only: [:new, :create, :fork]
+ before_filter :set_title, only: [:new, :create]
def new
@project = Project.new
end
def fork
- @project = ::Projects::ForkContext.new(project, current_user).execute
+ @forked_project = ::Projects::ForkContext.new(project, current_user).execute
respond_to do |format|
format.html do
- if @project.saved? && @project.forked?
- redirect_to(@project, notice: 'Project was successfully forked.')
+ if @forked_project.saved? && @forked_project.forked?
+ redirect_to(@forked_project, notice: 'Project was successfully forked.')
else
- render action: "new"
+ @title = 'Fork project'
+ render action: "fork"
end
end
format.js
format.json { render :json => @suggestions }
end
end
+
+ private
+
+ def set_title
+ @title = 'New Project'
+ end
end
elsif params[:destination] == "blob"
project_blob_path(@project, (@id))
elsif params[:destination] == "graph"
- project_graph_path(@project, @id, @options)
+ project_network_path(@project, @id, @options)
else
project_commits_path(@project, @id)
end
-class SnippetsController < ProjectResourceController
- before_filter :module_enabled
+class SnippetsController < ApplicationController
before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw]
- # Allow read any snippet
- before_filter :authorize_read_snippet!
-
- # Allow write(create) snippet
- before_filter :authorize_write_snippet!, only: [:new, :create]
-
# Allow modify snippet
before_filter :authorize_modify_snippet!, only: [:edit, :update]
respond_to :html
def index
- @snippets = @project.snippets.fresh.non_expired
+ @snippets = Snippet.public.fresh.non_expired.page(params[:page]).per(20)
+ end
+
+ def user_index
+ @user = User.find_by_username(params[:username])
+ @snippets = @user.snippets.fresh.non_expired
+
+ if @user == current_user
+ @snippets = case params[:scope]
+ when 'public' then
+ @snippets.public
+ when 'private' then
+ @snippets.private
+ else
+ @snippets
+ end
+ else
+ @snippets = @snippets.public
+ end
+
+ @snippets = @snippets.page(params[:page]).per(20)
+
+ if @user == current_user
+ render 'current_user_index'
+ else
+ render 'user_index'
+ end
end
def new
- @snippet = @project.snippets.new
+ @snippet = PersonalSnippet.new
end
def create
- @snippet = @project.snippets.new(params[:snippet])
+ @snippet = PersonalSnippet.new(params[:personal_snippet])
@snippet.author = current_user
- @snippet.save
- if @snippet.valid?
- redirect_to [@project, @snippet]
+ if @snippet.save
+ redirect_to snippet_path(@snippet)
else
- respond_with(@snippet)
+ respond_with @snippet
end
end
end
def update
- @snippet.update_attributes(params[:snippet])
-
- if @snippet.valid?
- redirect_to [@project, @snippet]
+ if @snippet.update_attributes(params[:personal_snippet])
+ redirect_to snippet_path(@snippet)
else
- respond_with(@snippet)
+ respond_with @snippet
end
end
def show
- @note = @project.notes.new(noteable: @snippet)
- @target_type = :snippet
- @target_id = @snippet.id
end
def destroy
- return access_denied! unless can?(current_user, :admin_snippet, @snippet)
+ return access_denied! unless can?(current_user, :admin_personal_snippet, @snippet)
@snippet.destroy
- redirect_to project_snippets_path(@project)
+ redirect_to snippets_path
end
def raw
protected
def snippet
- @snippet ||= @project.snippets.find(params[:id])
+ @snippet ||= PersonalSnippet.where('author_id = :user_id or private is false', user_id: current_user.id).find(params[:id])
end
def authorize_modify_snippet!
- return render_404 unless can?(current_user, :modify_snippet, @snippet)
+ return render_404 unless can?(current_user, :modify_personal_snippet, @snippet)
end
def authorize_admin_snippet!
- return render_404 unless can?(current_user, :admin_snippet, @snippet)
- end
-
- def module_enabled
- return render_404 unless @project.snippets_enabled
+ return render_404 unless can?(current_user, :admin_personal_snippet, @snippet)
end
end
before_filter :user_team, except: [:new, :create]
- layout 'user_team', except: [:new, :create]
+ layout :determine_layout
+
+ before_filter :set_title, only: [:new, :create]
def show
projects
def edit
projects
- @avaliable_projects = current_user.admin? ? Project.without_team(user_team) : current_user.owned_projects.without_team(user_team)
+ @avaliable_projects = current_user.owned_projects.without_team(user_team)
end
def update
def user_team
@team ||= current_user.authorized_teams.find_by_path(params[:id])
end
+
+ def set_title
+ @title = 'New Team'
+ end
+
+ def determine_layout
+ if [:new, :create].include?(action_name.to_sym)
+ 'navless'
+ else
+ 'user_team'
+ end
+ end
end
class UsersController < ApplicationController
+ layout 'navless'
+
def show
@user = User.find_by_username!(params[:username])
@projects = @user.authorized_projects.where('projects.id in (?)', current_user.authorized_projects.map(&:id))
@events = @user.recent_events.where(project_id: @projects.map(&:id)).limit(20)
+
+ @title = @user.name
end
end
require 'uri'
module ApplicationHelper
+ COLOR_SCHEMES = {
+ 1 => 'white',
+ 2 => 'dark',
+ 3 => 'solarized-dark',
+ 4 => 'monokai',
+ }
+ COLOR_SCHEMES.default = 'white'
+
+ # Helper method to access the COLOR_SCHEMES
+ #
+ # The keys are the `color_scheme_ids`
+ # The values are the `name` of the scheme.
+ #
+ # The preview images are `name-scheme-preview.png`
+ # The stylesheets should use the css class `.name`
+ def color_schemes
+ COLOR_SCHEMES.freeze
+ end
# Check if a particular controller is the current one
#
end
def user_color_scheme_class
- # in case we dont have current_user (ex. in mailer)
- return 1 unless defined?(current_user)
-
- case current_user.color_scheme_id
- when 1 then 'white'
- when 2 then 'black'
- when 3 then 'solarized-dark'
- when 4 then 'monokai'
- else
- 'white'
- end
+ COLOR_SCHEMES[current_user.try(:color_scheme_id)]
end
# Define whenever show last push event
alias_method :url_to_image, :image_url
def users_select_tag(id, opts = {})
- css_class = "ajax-users-select"
- css_class << " multiselect" if opts[:multiple]
- hidden_field_tag(id, '', class: css_class)
+ css_class = "ajax-users-select "
+ css_class << "multiselect " if opts[:multiple]
+ css_class << (opts[:class] || '')
+ value = opts[:selected] || ''
+
+ hidden_field_tag(id, value, class: css_class)
end
def body_data_page
def extra_config
Gitlab.config.extra
end
+
+ def public_icon
+ content_tag :i, nil, class: 'icon-globe cblue'
+ end
+
+ def private_icon
+ content_tag :i, nil, class: 'icon-lock cgreen'
+ end
+
end
render "events/event_push", event: event
end
end
+
+ def event_note_target_path(event)
+ if event.note? && event.note_commit?
+ project_commit_path(event.project, event.note_target)
+ else
+ url_for([event.project, event.note_target])
+ end
+ end
+
+ def event_note_title_html(event)
+ if event.note_target
+ if event.note_commit?
+ link_to project_commit_path(event.project, event.note_commit_id), class: "commit_short_id" do
+ "#{event.note_target_type} #{event.note_short_commit_id}"
+ end
+ elsif event.note_project_snippet?
+ link_to(project_snippet_path(event.project, event.note_target)) do
+ content_tag :strong do
+ "#{event.note_target_type} ##{truncate event.note_target_id}"
+ end
+ end
+ else
+ link_to event_note_target_path(event) do
+ content_tag :strong do
+ "#{event.note_target_type} ##{truncate event.note_target_id}"
+ end
+ end
+ end
+ elsif event.wall_note?
+ link_to 'wall', project_wall_path(event.project)
+ else
+ content_tag :strong do
+ "(deleted)"
+ end
+ end
+ end
end
module NamespacesHelper
def namespaces_options(selected = :current_user, scope = :default)
- if current_user.admin
- groups = Group.all
- users = Namespace.root
- else
- groups = current_user.owned_groups.select {|n| n.type == 'Group'}
- users = current_user.namespaces.reject {|n| n.type == 'Group'}
- end
-
+ groups = current_user.owned_groups.select {|n| n.type == 'Group'}
+ users = current_user.namespaces.reject {|n| n.type == 'Group'}
global_opts = ["Global", [['/', Namespace.global_id]] ]
group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ]
]
options_for_select(options)
end
+
+ def reliable_snippet_path(snippet)
+ if snippet.project_id?
+ project_snippet_path(snippet.project, snippet)
+ else
+ snippet_path(snippet)
+ end
+ end
end
end
def project_tab_class
- return "active" if current_page?(controller: "projects", action: :edit, id: @project)
+ return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
if ['services', 'hooks', 'deploy_keys', 'team_members'].include? controller.controller_name
"active"
end
def plain_text_readme? filename
- filename == 'README'
+ filename =~ /^README(.txt)?$/i
end
# Simple shortcut to File.join
when "Project" then project_abilities(user, subject)
when "Issue" then issue_abilities(user, subject)
when "Note" then note_abilities(user, subject)
- when "Snippet" then snippet_abilities(user, subject)
+ when "ProjectSnippet" then project_snippet_abilities(user, subject)
+ when "PersonalSnippet" then personal_snippet_abilities(user, subject)
when "MergeRequest" then merge_request_abilities(user, subject)
when "Group", "Namespace" then group_abilities(user, subject)
when "UserTeam" then user_team_abilities(user, subject)
elsif team.reporters.include?(user)
rules << project_report_rules
- elsif team.guests.include?(user) or project.public?
+ elsif team.guests.include?(user)
rules << project_guest_rules
end
+ if project.public?
+ rules << public_project_rules
+ end
+
if project.owner == user || user.admin?
rules << project_admin_rules
end
rules.flatten
end
+ def public_project_rules
+ [
+ :download_code,
+ :fork_project,
+ :read_project,
+ :read_wiki,
+ :read_issue,
+ :read_milestone,
+ :read_project_snippet,
+ :read_team_member,
+ :read_merge_request,
+ :read_note,
+ :write_issue,
+ :write_note
+ ]
+ end
+
def project_guest_rules
[
:read_project,
:read_wiki,
:read_issue,
:read_milestone,
- :read_snippet,
+ :read_project_snippet,
:read_team_member,
:read_merge_request,
:read_note,
def project_report_rules
project_guest_rules + [
:download_code,
- :write_snippet,
- :fork_project
+ :fork_project,
+ :write_project_snippet
]
end
project_dev_rules + [
:push_code_to_protected_branches,
:modify_issue,
- :modify_snippet,
+ :modify_project_snippet,
:modify_merge_request,
:admin_issue,
:admin_milestone,
- :admin_snippet,
+ :admin_project_snippet,
:admin_team_member,
:admin_merge_request,
:admin_note,
rules = []
# Only group owner and administrators can manage team
- if team.owner == user || team.admin?(user) || user.admin?
+ if user.admin? || team.owner == user || team.admin?(user)
rules << [ :manage_user_team ]
end
rules.flatten
end
-
- [:issue, :note, :snippet, :merge_request].each do |name|
+ [:issue, :note, :project_snippet, :personal_snippet, :merge_request].each do |name|
define_method "#{name}_abilities" do |user, subject|
if subject.author == user
[
end
def target_title
- target.try :title
+ if target && target.respond_to?(:title)
+ target.title
+ end
end
def push?
target.noteable_type == "Commit"
end
+ def note_project_snippet?
+ target.noteable_type == "Snippet"
+ end
+
def note_target
target.noteable
end
def collect_notes
h = Hash.new(0)
@project.notes.where('noteable_type = ?' ,"Commit").group('notes.commit_id').select('notes.commit_id, count(notes.id) as note_count').each do |item|
- h[item["commit_id"]] = item["note_count"]
+ h[item.commit_id] = item.note_count.to_i
end
h
end
"wall"
end
end
+
+ # FIXME: Hack for polymorphic associations with STI
+ # For more information wisit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
+ def noteable_type=(sType)
+ super(sType.to_s.classify.constantize.base_class.to_s)
+ end
end
--- /dev/null
+# == Schema Information
+#
+# Table name: snippets
+#
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# author_id :integer not null
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# file_name :string(255)
+# expires_at :datetime
+# type :string(255)
+# private :boolean
+
+class PersonalSnippet < Snippet
+end
has_many :milestones, dependent: :destroy
has_many :users_projects, dependent: :destroy
has_many :notes, dependent: :destroy
- has_many :snippets, dependent: :destroy
+ has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
has_many :protected_branches, dependent: :destroy
has_many :user_team_project_relationships, dependent: :destroy
--- /dev/null
+# == Schema Information
+#
+# Table name: snippets
+#
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# author_id :integer not null
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# file_name :string(255)
+# expires_at :datetime
+# type :string(255)
+# private :boolean
+
+class ProjectSnippet < Snippet
+ belongs_to :project
+ belongs_to :author, class_name: "User"
+
+ validates :project, presence: true
+
+ # Scopes
+ scope :fresh, -> { order("created_at DESC") }
+ scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
+ scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
+end
# updated_at :datetime not null
# file_name :string(255)
# expires_at :datetime
-#
+# type :string(255)
+# private :boolean
class Snippet < ActiveRecord::Base
include Linguist::BlobHelper
- attr_accessible :title, :content, :file_name, :expires_at
+ attr_accessible :title, :content, :file_name, :expires_at, :private
- belongs_to :project
belongs_to :author, class_name: "User"
+
has_many :notes, as: :noteable, dependent: :destroy
delegate :name, :email, to: :author, prefix: true, allow_nil: true
validates :author, presence: true
- validates :project, presence: true
validates :title, presence: true, length: { within: 0..255 }
validates :file_name, presence: true, length: { within: 0..255 }
validates :content, presence: true
# Scopes
- scope :fresh, -> { order("created_at DESC") }
- scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
+ scope :public, -> { where(private: false) }
+ scope :private, -> { where(private: true) }
+ scope :fresh, -> { order("created_at DESC") }
scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
+ scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
def self.content_types
[
has_many :team_projects, through: :user_team_project_relationships
# Projects
+ has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet"
has_many :users_projects, dependent: :destroy
has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :personal_projects, through: :namespace, source: :projects
has_many :projects, through: :users_projects
+ has_many :master_projects, through: :users_projects, source: :project,
+ conditions: { users_projects: { project_access: UsersProject::MASTER } }
has_many :own_projects, foreign_key: :creator_id, class_name: 'Project'
has_many :owned_projects, through: :namespaces, source: :projects
end
def authorized_teams
- @team_ids ||= (user_teams.pluck(:id) + own_teams.pluck(:id)).uniq
- UserTeam.where(id: @team_ids)
+ if admin?
+ UserTeam.scoped
+ else
+ @team_ids ||= (user_teams.pluck(:id) + own_teams.pluck(:id)).uniq
+ UserTeam.where(id: @team_ids)
+ end
end
# Team membership in authorized projects
def ldap_user?
extern_uid && provider == 'ldap'
end
+
+ def accessible_deploy_keys
+ DeployKey.in_projects(self.master_projects).uniq
+ end
end
end
def admin?(member)
- user_team_user_relationships.with_user(member).first.group_admin?
+ user_team_user_relationships.with_user(member).first.try(:group_admin?)
end
end
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,
end
def after_save user
- if user.username_changed?
- if user.namespace
- user.namespace.update_attributes(path: user.username)
- else
- user.create_namespace!(path: user.username, name: user.username)
- end
+ # Ensure user has namespace
+ user.create_namespace!(path: user.username, name: user.username) unless user.namespace
+
+ if user.username_changed? || user.name_changed?
+ user.namespace.update_attributes(path: user.username, name: user.name)
end
end
end
%h4 Stats
%hr
%p
+ Teams
+ %span.light.pull-right
+ = UserTeam.count
+ %p
+ Forks
+ %span.light.pull-right
+ = ForkedProjectLink.count
+ %p
Issues
%span.light.pull-right
= Issue.count
%h3.page_title
Group: #{@group.name}
-%br
-%table.zebra-striped
- %thead
- %tr
- %th Group
- %th
- %tr
- %td
- %b
- Name:
- %td
- = @group.name
-
- = link_to edit_admin_group_path(@group), class: "btn btn-small pull-right" do
- %i.icon-edit
- Edit
- %tr
- %td
- %b
- Description:
- %td
- = @group.description
- %tr
- %td
- %b
- Path:
- %td
- %span.monospace= File.join(Gitlab.config.gitlab_shell.repos_path, @group.path)
- %tr
- %td
- %b
- Owner:
- %td
- = @group.owner_name
- .pull-right
- = link_to "#", class: "btn btn-small change-owner-link" do
- %i.icon-edit
- Change owner
+ = link_to edit_admin_group_path(@group), class: "btn btn-small pull-right" do
+ %i.icon-edit
+ Edit
+%hr
+.row
+ .span6
+ .ui-box
+ %h5.title
+ Group info:
+ %ul.well-list
+ %li
+ %span.light Name:
+ %strong= @group.name
+ %li
+ %span.light Path:
+ %strong
+ = @group.path
- %tr.change-owner-holder.hide
- %td.bgred
- %b.cred
- New Owner:
- %td.bgred
- = form_for [:admin, @group] do |f|
- = f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
- %div
- = f.submit 'Change Owner', class: "btn btn-remove"
- = link_to "Cancel", "#", class: "btn change-owner-cancel-link"
+ %li
+ %span.light Description:
+ %strong
+ = @group.description
-- if @group.projects.any?
- %fieldset
- %legend Projects (#{@group.projects.count})
- %table
- %thead
- %tr
- %th Project name
- %th Path
- %th Users
- %th.cred Danger Zone!
- - @group.projects.each do |project|
- %tr
- %td
- = link_to project.name_with_namespace, [:admin, project]
- %td
- %span.monospace= project.path_with_namespace + ".git"
- %td= project.users.count
- %td.bgred
- = link_to 'Transfer project to global namespace', remove_project_admin_group_path(@group, project_id: project.id), confirm: 'Remove project from group and move to global namespace. Are you sure?', method: :delete, class: "btn btn-remove small"
+ %li
+ %span.light Owned by:
+ %strong
+ - if @group.owner
+ = link_to @group.owner_name, admin_user_path(@group.owner)
+ - else
+ (deleted)
+ .pull-right
+ = link_to "#", class: "btn btn-small change-owner-link" do
+ %i.icon-edit
+ Change owner
+ %li.change-owner-holder.hide.bgred
+ .form-holder
+ %strong.cred New Owner:
+ = form_for [:admin, @group] do |f|
+ = users_select_tag(:"group[owner_id]")
+ .prepend-top-10
+ = f.submit 'Change Owner', class: "btn btn-remove"
+ = link_to "Cancel", "#", class: "btn change-owner-cancel-link"
- = form_tag project_teams_update_admin_group_path(@group), id: "new_team_member", class: "bulk_import", method: :put do
- %table.zebra-striped
- %thead
- %tr
- %th Users
- %th Project Access:
+ %li
+ %span.light Created at:
+ %strong
+ = @group.created_at.stamp("March 1, 1999")
- - @group.users.each do |user|
- - next unless user
- %tr{class: "user_#{user.id}"}
- %td.name= link_to user.name, admin_user_path(user)
- %td.projects_access
- - user.authorized_projects.in_namespace(@group).each do |project|
- - u_p = user.users_projects.in_project(project).first
- - next unless u_p
- %span
- = project.name_with_namespace
- = link_to "(#{ u_p.project_access_human })", edit_admin_project_member_path(project, user)
- %tr
- %td.input= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
- %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"}
- %tr
- %td= submit_tag 'Add user to projects in group', class: "btn btn-create"
- %td
+ .ui-box
+ %h5.title
+ Add user to Group projects:
+ .ui-box-body.form-holder
+ %p.light
Read more about project permissions
%strong= link_to "here", help_permissions_path, class: "vlink"
-- else
- %fieldset
- %legend Group is empty
+ = form_tag project_teams_update_admin_group_path(@group), id: "new_team_member", class: "bulk_import", method: :put do
+ %div
+ = users_select_tag(:user_ids, multiple: true)
+ %div.prepend-top-10
+ = select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span2"}
+ %hr
+ = submit_tag 'Add user to projects in group', class: "btn btn-create"
+ .ui-box
+ %h5.title
+ Users from Group projects
+ %small
+ (#{@group.users.count})
+ %ul.well-list
+ - @group.users.sort_by(&:name).each do |user|
+ %li{class: dom_class(user)}
+ %strong
+ = link_to user.name, admin_user_path(user)
+ %span.pull-right.light
+ = pluralize user.authorized_projects.in_namespace(@group).count, 'project'
-= form_tag project_update_admin_group_path(@group), class: "bulk_import", method: :put do
- %fieldset
- %legend Move projects to group
- .alert
- You can move only projects with existing repos
- %br
- Group projects will be moved in group directory and will not be accessible by old path
- .clearfix
- = label_tag :project_ids do
+ .span6
+ .ui-box
+ %h5.title
Projects
- .input
- = select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
- .form-actions
- = submit_tag 'Move projects', class: "btn btn-create"
-
+ %small
+ (#{@group.projects.count})
+ %ul.well-list
+ - @group.projects.sort_by(&:name).each do |project|
+ %li
+ %strong
+ = link_to project.name_with_namespace, [:admin, project]
+ %span.pull-right.light
+ %span.monospace= project.path_with_namespace + ".git"
= text_field_tag :name, params[:name], class: "span2"
.control-group
- = label_tag :namespace_id, 'Namespace:', class: 'control-label'
+ = label_tag :owner_id, 'Owner:', class: 'control-label'
.controls
- = select_tag :namespace_id, namespaces_options(params[:namespace_id], :all), class: "chosen span2", prompt: "Any"
+ = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large'
.control-group
= label_tag :public_only, 'Public Only', class: 'control-label'
.controls
- @projects.each do |project|
%li
- if project.public
- %i.icon-share
+ = public_icon
- else
- %i.icon-lock.cgreen
+ = private_icon
= link_to project.name_with_namespace, [:admin, project]
.pull-right
= link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
%h3.page_title
- Teams
+ Teams (#{@teams.total_count})
%small
allow you to organize groups of people that have a common focus. Use teams to simplify the process of assigning roles to groups of people.
= link_to 'New Team', new_admin_team_path, class: "btn btn-small pull-right"
- %br
+%br
= form_tag admin_teams_path, method: :get, class: 'form-inline' do
- = text_field_tag :name, params[:name], class: "xlarge"
+ = text_field_tag :name, params[:name], class: "span6"
= submit_tag "Search", class: "btn submit btn-primary"
-%table
- %thead
- %tr
- %th
- Name
- %i.icon-sort-down
- %th Description
- %th Path
- %th Projects
- %th Members
- %th Owner
- %th.cred Danger Zone!
+%hr
+%ul.bordered-list
- @teams.each do |team|
- %tr
- %td
- %strong= link_to team.name, admin_team_path(team)
- %td= truncate team.description
- %td= team.path
- %td= team.projects.count
- %td= team.members.count
- %td
- - if team.owner
- = link_to team.owner.name, admin_user_path(team.owner)
- - else
- (deleted)
- %td.bgred
- = link_to 'Edit', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small"
- = link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
+ %li
+ .clearfix
+ .pull-right.prepend-top-10
+ = link_to 'Edit', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small"
+ = link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
+
+ %h4
+ = link_to admin_team_path(team) do
+ %i.icon-group
+ = team.name
+
+ .clearfix.light.append-bottom-10
+ %span
+ %b Owner:
+ - if team.owner
+ = link_to team.owner.name, admin_user_path(team.owner)
+ - else
+ (deleted)
+ \|
+ %span
+ %b Users:
+ %span.badge= team.members.count
+ \|
+ %span
+ %b Projects:
+ %span.badge= team.projects.count
+
+ .clearfix
+ %p
+ = truncate team.description, length: 150
= paginate @teams, theme: "gitlab"
%h3.page_title
Team: #{@team.name}
+%hr
+= form_tag admin_team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do
+ %h6 Choose Projects you want to assign:
+ .clearfix
+ = label_tag :project_ids, "Projects"
+ .input
+ = select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
-%fieldset
- %legend Projects (#{@team.projects.count})
- = form_tag admin_team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do
- %table#projects_list
- %thead
- %tr
- %th Project name
- %th Max access
- %th
- - @team.projects.each do |project|
- %tr.project
- %td
- = link_to project.name_with_namespace, [:admin, project]
- %td
- %span= @team.human_max_project_access(project)
- %td
- %tr
- %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
- %td= select_tag :greatest_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
- %td= submit_tag 'Add', class: "btn btn-primary", id: :assign_projects_to_team
+ %h6 Choose greatest user acces for your team in this projects:
+ .clearfix
+ = label_tag :greatest_project_access, "Greatest Access"
+ .input
+ = select_tag :greatest_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
+
+ .form-actions
+ = submit_tag 'Add team to projects', class: "btn btn-create", id: :assign_projects_to_team
%h3.page_title
Team: #{@team.name}
-%br
-%table.zebra-striped
- %thead
- %tr
- %th Team
- %th
- %tr
- %td
- %b
- Name:
- %td
- = @team.name
-
- = link_to edit_admin_team_path(@team), class: "btn btn-small pull-right" do
- %i.icon-edit
- Edit
- %tr
- %td
- %b
- Description:
- %td
- = @team.description
- %tr
- %td
- %b
- Owner:
- %td
- = @team.owner.name
- .pull-right
- = link_to "#", class: "btn btn-small change-owner-link" do
- %i.icon-edit
- Change owner
- %tr.change-owner-holder.hide
- %td.bgred
- %b.cred
- New Owner:
- %td.bgred
- = form_for @team, url: admin_team_path(@team) do |f|
- = f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
- %div
- = f.submit 'Change Owner', class: "btn btn-remove"
- = link_to "Cancel", "#", class: "btn change-owner-cancel-link"
+ = link_to edit_admin_team_path(@team), class: "btn btn-small pull-right" do
+ %i.icon-edit
+ Edit
+%hr
-%fieldset
- %legend
- Members (#{@team.members.count})
- %span= link_to 'Add members', new_admin_team_member_path(@team), class: "btn btn-primary btn-small pull-right", id: :add_members_to_team
- - if @team.members.any?
- %table#members_list
- %thead
- %tr
- %th User name
- %th Default project access
- %th Team access
- %th.cred.span3 Danger Zone!
- - @team.members.each do |member|
- %tr.member{ class: "user_#{member.id}"}
- %td
+
+.row
+ .span6
+ .ui-box
+ %h5.title
+ Team info:
+ %ul.well-list
+ %li
+ %span.light Name:
+ %strong= @team.name
+ %li
+ %span.light Path:
+ %strong
+ = @team.path
+
+ %li
+ %span.light Description:
+ %strong
+ = @team.description
+
+ %li
+ %span.light Owned by:
+ %strong
+ - if @team.owner
+ = link_to @team.owner.name, admin_user_path(@team.owner)
+ - else
+ (deleted)
+ .pull-right
+ = link_to "#", class: "btn btn-small change-owner-link" do
+ %i.icon-edit
+ Change owner
+ %li.change-owner-holder.hide.bgred
+ .form-holder
+ %strong.cred New Owner:
+ = form_for @team, url: admin_team_path(@team) do |f|
+ = users_select_tag(:"user_team[owner_id]")
+ .prepend-top-10
+ = f.submit 'Change Owner', class: "btn btn-remove"
+ = link_to "Cancel", "#", class: "btn change-owner-cancel-link"
+
+ %li
+ %span.light Created at:
+ %strong
+ = @team.created_at.stamp("March 1, 1999")
+
+ .span6
+ .ui-box
+ %h5.title
+ Members (#{@team.members.count})
+ .pull-right
+ = link_to 'Add members', new_admin_team_member_path(@team), class: "btn btn-small", id: :add_members_to_team
+ %ul.well-list#members_list
+ - @team.members.each do |member|
+ %li.member{ class: "user_#{member.id}"}
= link_to [:admin, member] do
- = member.name
- %small= "(#{member.email})"
- %td= @team.human_default_projects_access(member)
- %td= @team.admin?(member) ? "Admin" : "Member"
- %td.bgred
- = link_to 'Edit', edit_admin_team_member_path(@team, member), class: "btn btn-small"
-
- = link_to 'Remove', admin_team_member_path(@team, member), confirm: 'Remove member from team. Are you sure?', method: :delete, class: "btn btn-remove btn-small", id: "remove_member_#{member.id}"
+ %strong
+ = member.name
+ .pull-right
+ %span.light
+ = @team.human_default_projects_access(member)
+ - if @team.admin?(member)
+ %span.label.label-info Admin
+
+ = link_to 'Edit', edit_admin_team_member_path(@team, member), class: "btn btn-small"
+
+ = link_to 'Remove', admin_team_member_path(@team, member), confirm: 'Remove member from team. Are you sure?', method: :delete, class: "btn btn-remove btn-small", id: "remove_member_#{member.id}"
+
+
+ .ui-box
+ %h5.title
+ Projects (#{@team.projects.count})
+ .pull-right
+ = link_to 'Add projects', new_admin_team_project_path(@team), class: "btn btn-small", id: :assign_projects_to_team
+ %ul.well-list#projects_list
+ - @team.projects.each do |project|
+ %li.project
+ = link_to [:admin, project] do
+ %strong
+ = project.name_with_namespace
-%fieldset
- %legend
- Projects (#{@team.projects.count})
- %span= link_to 'Add projects', new_admin_team_project_path(@team), class: "btn btn-primary btn-small pull-right", id: :assign_projects_to_team
- - if @team.projects.any?
- %table#projects_list
- %thead
- %tr
- %th Project name
- %th Max access
- %th.cred.span3 Danger Zone!
- - @team.projects.each do |project|
- %tr.project
- %td
- = link_to project.name_with_namespace, [:admin, project]
- %td
- %span= @team.human_max_project_access(project)
- %td.bgred
- = link_to 'Edit', edit_admin_team_project_path(@team, project), class: "btn btn-small"
-
- = link_to 'Relegate', admin_team_project_path(@team, project), confirm: 'Remove project from team. Are you sure?', method: :delete, class: "btn btn-remove small", id: "relegate_project_#{project.id}"
+ .pull-right
+ %span.light
+ = @team.human_max_project_access(project)
+
+ = link_to 'Edit', edit_admin_team_project_path(@team, project), class: "btn btn-small"
+
+ = link_to 'Relegate', admin_team_project_path(@team, project), confirm: 'Remove project from team. Are you sure?', method: :delete, class: "btn btn-remove small", id: "relegate_project_#{project.id}"
.event-title
%span.author_name= link_to_author event
- %span.event_label commented on
- - if event.note_target
- - if event.note_commit?
- = event.note_target_type
- = link_to event.note_short_commit_id, project_commit_path(event.project, event.note_commit_id), class: "commit_short_id"
- - else
- = link_to [event.project, event.note_target] do
- %strong
- #{event.note_target_type} ##{truncate event.note_target_id}
-
- - elsif event.wall_note?
- = link_to 'wall', project_wall_path(event.project)
- - else
- %strong (deleted)
- at
+ %span.event_label commented on #{event_note_title_html(event)} at
- if event.project
= link_to_project event.project
- else
--- /dev/null
+.loading-graph
+ %center
+ .loading
+ %h3.page_title Building repository graph. Please wait a moment.
+
+.stat-graph
+ .header.clearfix
+ .pull-right
+ %select
+ %option{:value => "commits"} Commits
+ %option{:value => "additions"} Additions
+ %option{:value => "deletions"} Deletions
+ %h3#date_header.page_title
+ %input#brush_change{:type => "hidden"}
+ .graphs
+ #contributors-master
+ #contributors.clearfix
+ %ol.contributors-list.clearfix
+
+:javascript
+ $(".stat-graph").hide();
+
+ $.ajax({
+ type: "GET",
+ url: location.href,
+ complete: function() {
+ $(".loading-graph").hide();
+ $(".stat-graph").show();
+ },
+ dataType: "script"
+ });
--- /dev/null
+:plain
+ controller = new ContributorsStatGraph
+ controller.init(#{@log})
+
+ $("select").change( function () {
+ var field = $(this).val()
+ controller.set_current_field(field)
+ controller.redraw_master()
+ controller.redraw_authors()
+ })
+
+ $("#brush_change").change( function () {
+ controller.change_date_header()
+ controller.redraw_authors()
+ })
+
%h6 1. Choose people you want in the team
.clearfix
= f.label :user_ids, "People"
- .input= select_tag(:user_ids, options_from_collection_for_select(User.active.alphabetically, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true})
+ .input= users_select_tag(:user_ids, multiple: true)
%h6 2. Set access level for them
.clearfix
%h6 1. Choose people you want in the team
.clearfix
= f.label :user_ids, "People"
- .input= select_tag(:user_ids, options_from_collection_for_select(User.not_in_project(@project).alphabetically, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true})
+ .input= users_select_tag(:user_ids, multiple: true)
%h6 2. Set access level for them
.clearfix
- @group.projects.each do |project|
%li
- if project.public
- %i.icon-share
+ = public_icon
- else
- %i.icon-lock.cgreen
+ = private_icon
= link_to project.name_with_namespace, project
.pull-right
= link_to 'Team', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
-%h3.page_title New Group
-%hr
= form_for @group do |f|
- if @group.errors.any?
.alert.alert-error
.input
= f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
+ .clearfix
+ .input
+ %ul
+ %li Group is kind of directory for several projects
+ %li All created groups are private
+ %li People within a group see only projects they have access to
+ %li All projects of group will be stored in a group directory
+ %li You will be able to move existing projects into group
+
.form-actions
= f.submit 'Create group', class: "btn btn-create"
- .padded
- %ul
- %li Group is kind of directory for several projects
- %li All created groups are private
- %li People within a group see only projects they have access to
- %li All projects of group will be stored in a group directory
- %li You will be able to move existing projects into group
.loading.hide
.side.span4
- if @group.description.present?
- .description.well.well-small.light
+ .description-block
= @group.description
= render "projects", projects: @projects
.prepend-top-20
--- /dev/null
+.row
+ .span3
+ = link_to help_path, class: 'btn append-bottom-20 btn-small' do
+ %i.icon-angle-left
+ Back to help
+ %br
+ %ul.nav.nav-pills.nav-stacked
+ - %w(README projects project_snippets repositories deploy_keys users session issues milestones notes system_hooks).each do |file|
+ %li{class: file == @category ? 'active' : nil}
+ = link_to file.titleize, help_api_file_path(file)
+
+ .span9.pull-right
+ = yield
-= render layout: 'help/layout' do
- %h3.page_title API
+= render layout: 'help/api_layout' do
+ %h3.page_title
+ %span.light API
+ %span
+ \/
+ = @category.titleize
%br
- %ul.nav.nav-tabs.log-tabs.nav-small-tabs
- %li.active
- = link_to "README", "#README", 'data-toggle' => 'tab'
- %li
- = link_to "Projects", "#projects", 'data-toggle' => 'tab'
- %li
- = link_to "Snippets", "#snippets", 'data-toggle' => 'tab'
- %li
- = link_to "Repositories", "#repositories", 'data-toggle' => 'tab'
- %li
- = link_to "Users", "#users", 'data-toggle' => 'tab'
- %li
- = link_to "Session", "#session", 'data-toggle' => 'tab'
- %li
- = link_to "Issues", "#issues", 'data-toggle' => 'tab'
- %li
- = link_to "Milestones", "#milestones", 'data-toggle' => 'tab'
- %li
- = link_to "Notes", "#notes", 'data-toggle' => 'tab'
- %li
- = link_to "System Hooks", "#system_hooks", 'data-toggle' => 'tab'
-
- .tab-content
- .tab-pane.active#README
- .file_holder
- .file_title
- %i.icon-file
- README
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "README.md"))
-
- .tab-pane#projects
- .file_holder
- .file_title
- %i.icon-file
- Projects
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "projects.md"))
-
- .tab-pane#snippets
- .file_holder
- .file_title
- %i.icon-file
- Projects Snippets
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "snippets.md"))
-
- .tab-pane#repositories
- .file_holder
- .file_title
- %i.icon-file
- Projects
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "repositories.md"))
-
- .tab-pane#users
- .file_holder
- .file_title
- %i.icon-file
- Users
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "users.md"))
-
- .tab-pane#session
- .file_holder
- .file_title
- %i.icon-file
- Session
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "session.md"))
-
- .tab-pane#issues
- .file_holder
- .file_title
- %i.icon-file
- Issues
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "issues.md"))
-
- .tab-pane#milestones
- .file_holder
- .file_title
- %i.icon-file
- Milestones
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "milestones.md"))
-
- .tab-pane#notes
- .file_holder
- .file_title
- %i.icon-file
- Notes
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "notes.md"))
-
- .tab-pane#system_hooks
- .file_holder
- .file_title
- %i.icon-file
- System Hooks
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "system_hooks.md"))
+ .file_holder
+ .file_title
+ %i.icon-file
+ = @category
+ .file_content.wiki
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "api", "#{@category}.md"))
%li Add new team members
%li Push to protected branches
%li Remove protected branches
- %li Push with force option
%li Edit project
%li Add Deploy Keys to project
%li Configure Project Hooks
%fieldset
%legend Owner
%ul
+ %li Switch public mode
%li Transfer project to another namespace
%li Remove project
%i.icon-user
Assign to
.input
- = f.select(:assignee_id, @project.users.alphabetically.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'})
- = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link'
+ .pull-left
+ = f.select(:assignee_id, @project.users.alphabetically.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'})
+ .pull-right
+
+ = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link'
.issue_milestone.pull-left
= f.label :milestone_id do
%i.icon-time
event.preventDefault();
}
})
+ .bind( "click", function( event ) {
+ $( this ).autocomplete("search", "");
+ })
.autocomplete({
minLength: 0,
source: function( request, response ) {
%li
= link_to public_root_path, title: "Public area", 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' => 'Public area' do
+ %i.icon-paste
- if current_user.is_admin?
%li
= link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do
= link_to merge_requests_dashboard_path do
Merge Requests
%span.count= current_user.cared_merge_requests.opened.count
- = nav_link(path: 'search#show') do
- = link_to "Search", search_path
= nav_link(controller: :help) do
= link_to "Help", help_path
= link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref)
= nav_link(controller: %w(commit commits compare repositories protected_branches)) do
= link_to "Commits", project_commits_path(@project, @ref || @repository.root_ref)
- = nav_link(controller: %w(graph)) do
- = link_to "Network", project_graph_path(@project, @ref || @repository.root_ref)
+ = nav_link(controller: %w(network)) do
+ = link_to "Network", project_network_path(@project, @ref || @repository.root_ref)
+ = nav_link(controller: %w(graphs)) do
+ = link_to "Graphs", project_graph_path(@project, @ref || @repository.root_ref)
- if @project.issues_enabled
= nav_link(controller: %w(issues milestones labels)) do
--- /dev/null
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head", title: @title
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/head_panel", title: @title
+ = render "layouts/flash"
+
+ .container.navless-container
+ .content
+ = yield
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Public Projects"
- %body{class: "#{app_theme} application"}
- %header.navbar.navbar-static-top.navbar-gitlab
- .navbar-inner
- .container
- %div.app_logo
- %span.separator
- = link_to root_path, class: "home" do
- %h1 GITLAB
- %span.separator
- %h1.project_name Public Projects
- .container
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ - if current_user
+ = render "layouts/head_panel", title: "Public Projects"
+ - else
+ %header.navbar.navbar-static-top.navbar-gitlab
+ .navbar-inner
+ .container
+ %div.app_logo
+ %span.separator
+ = link_to root_path, class: "home" do
+ %h1 GITLAB
+ %span.separator
+ %h1.project_name Public Projects
+
+ .container.navless-container
.content
- .prepend-top-20
- = yield
+ = yield
--- /dev/null
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head", title: "Search"
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/head_panel", title: "Search"
+ = render "layouts/flash"
+
+ .container.navless-container
+ .content
+ = yield
--- /dev/null
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head", title: "Snipepts"
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/head_panel", title: "Snippets"
+ = render "layouts/flash"
+ %nav.main-nav
+ .container
+ %ul
+ = nav_link(path: 'snippets#user_index', html_options: {class: 'home'}) do
+ = link_to user_snippets_path(current_user), title: "My Snippets" do
+ %i.icon-home
+ = nav_link(path: 'snippets#new') do
+ = link_to new_snippet_path do
+ New snippet
+ = nav_link(path: 'snippets#index') do
+ = link_to snippets_path do
+ Discover snippets
+ .container
+ .content= yield
.pull-left
= render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
.pull-left
- = form_tag project_graph_path(@project, @id), method: :get do |f|
+ = form_tag project_network_path(@project, @id), method: :get do |f|
.control-group
= label_tag :filter_ref, "Show only selected ref", class: 'control-label light'
.controls
= hidden_field_tag(key, value, id: nil) unless key == "filter_ref"
.search.pull-right
- = form_tag project_graph_path(@project, @id), method: :get do |f|
+ = form_tag project_network_path(@project, @id), method: :get do |f|
.control-group
= label_tag :search , "Looking for commit:", class: 'control-label light'
.controls
$(this).closest('form').submit();
});
branch_graph = new BranchGraph($("#holder"), {
- url: '#{project_graph_path(@project, @ref, @options.merge(format: :json))}',
+ url: '#{project_network_path(@project, @ref, @options.merge(format: :json))}',
commit_url: '#{project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")}',
ref: '#{@ref}',
commit_id: '#{@commit.id}'
%i.icon-ok
Saved
.code_highlight_opts
- = label_tag do
- .prev
- = image_tag "white.png"
- = f.radio_button :color_scheme_id, 1
- White
- = label_tag do
- .prev
- = image_tag "dark.png"
- = f.radio_button :color_scheme_id, 2
- Dark
- = label_tag do
- .prev
- = image_tag "solarized_dark.png"
- = f.radio_button :color_scheme_id, 3
- Solarized Dark
- = label_tag do
- .prev
- = image_tag "monokai.png"
- = f.radio_button :color_scheme_id, 4
- Monokai
+ - color_schemes.each do |color_scheme_id, color_scheme|
+ = label_tag do
+ .prev
+ = image_tag "#{color_scheme}-scheme-preview.png"
+ = f.radio_button :color_scheme_id, color_scheme_id
+ = color_scheme.gsub(/[-_]+/, ' ').humanize
- unless @project.empty_repo?
- if can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
- if current_user.already_forked?(@project)
- = link_to project_path(current_user.fork_of(@project)), class: 'btn grouped btn-primary' do
+ = link_to project_path(current_user.fork_of(@project)), class: 'btn grouped disabled' do
+ %i.icon-code-fork
Forked
- else
= link_to fork_project_path(@project), title: "Fork", class: "btn grouped", method: "POST" do
- %i.icon-copy
+ %i.icon-code-fork
Fork
- if can? current_user, :download_code, @project
= link_to archive_project_repository_path(@project), class: "btn grouped" do
--- /dev/null
+.alert.alert-error.alert-block
+ %h4
+ %i.icon-code-fork
+ Fork Error!
+ %p
+ You are trying to fork
+ = link_to_project @project
+ but it fails due to next reason:
+
+
+ - if @forked_project && @forked_project.errors.any?
+ %p
+ –
+ = @forked_project.errors.full_messages.first
+
+ %p
+ = link_to fork_project_path(@project), title: "Fork", class: "btn", method: "POST" do
+ %i.icon-code-fork
+ Try to Fork again
.project-edit-container
- %h3.page_title New Project
- %hr
.project-edit-errors
= render 'projects/errors'
.project-edit-content
.row
.span9
= render "events/event_last_push", event: @last_push
- .content_list= render @events
+ .content_list
.loading.hide
.span3
.light-well
%p Owner: #{link_to @project.owner_name, @project.owner}
- if @project.forked_from_project
%p
+ %i.icon-code-fork
Forked from:
= link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
--- /dev/null
+.file_holder
+ .file_title
+ %i.icon-file
+ %strong= @snippet.file_name
+ %span.options
+ .btn-group.tree-btn-group.pull-right
+ - if can?(current_user, :admin_project_snippet, @project) || @snippet.author == current_user
+ = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-tiny", title: 'Edit Snippet'
+ = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank"
+ .file_content.code
+ - unless @snippet.content.empty?
+ %div{class: user_color_scheme_class}
+ = raw @snippet.colorize(formatter: :gitlab)
+ - else
+ %p.nothing_here_message Empty file
--- /dev/null
+%h3.page_title
+ = @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}"
+%hr
+.snippet-form-holder
+ = form_for [@project, @snippet], as: :project_snippet, url: url do |f|
+ -if @snippet.errors.any?
+ .alert.alert-error
+ %ul
+ - @snippet.errors.full_messages.each do |msg|
+ %li= msg
+
+ .clearfix
+ = f.label :title
+ .input= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true
+ .clearfix
+ = f.label "Lifetime"
+ .input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'}
+ .clearfix
+ .file-editor
+ = f.label :file_name, "File"
+ .input
+ .file_holder.snippet
+ .file_title
+ = f.text_field :file_name, placeholder: "example.rb", class: 'snippet-file-name', required: true
+ .file_content.code
+ %pre#editor= @snippet.content
+ = f.hidden_field :content, class: 'snippet-file-content'
+
+ .form-actions
+ = f.submit 'Save', class: "btn-save btn"
+ = link_to "Cancel", project_snippets_path(@project), class: " btn"
+ - unless @snippet.new_record?
+ .pull-right= link_to 'Destroy', project_snippet_path(@project, @snippet), confirm: 'Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}"
+
+
+:javascript
+ var editor = ace.edit("editor");
+ $(".snippet-form-holder form").submit(function(){
+ $(".snippet-file-content").val(editor.getValue());
+ });
+
--- /dev/null
+%li
+ .snippet-title
+ - if snippet.private?
+ %i.icon-lock.cgreen
+ - else
+ %i.icon-globe.cblue
+ = link_to reliable_snippet_path(snippet) do
+ %h5.inline
+ = truncate(snippet.title, length: 60)
+ %span.cgray
+ = snippet.file_name
+
+ %small.pull-right.cgray
+ Expires:
+ - if snippet.expires_at
+ = snippet.expires_at.to_date.to_s(:short)
+ - else
+ Never
+
+ .snippet-info.prepend-left-20
+ = "##{snippet.id}"
+ %span.light
+ by
+ = image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16"
+ = snippet.author_name
--- /dev/null
+= render "projects/snippets/form", url: project_snippet_path(@project, @snippet)
--- /dev/null
+%h3.page_title
+ Snippets
+ %small share code pastes with others out of git repository
+
+ - if can? current_user, :write_project_snippet, @project
+ = link_to new_project_snippet_path(@project), class: "btn btn-small add_new pull-right", title: "New Snippet" do
+ Add new snippet
+%hr
+%ul.bordered-list
+ = render partial: "projects/snippets/snippet", collection: @snippets
+ - if @snippets.empty?
+ %li
+ %h3.nothing_here_message Nothing here.
--- /dev/null
+= render "projects/snippets/form", url: project_snippets_path(@project, @snippet)
--- /dev/null
+%h3.page_title
+ %i.icon-lock.cgreen
+ = @snippet.title
+
+ %small.pull-right
+ = "##{@snippet.id}"
+ %span.light
+ by
+ = image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
+ = @snippet.author_name
+%br
+%div= render 'projects/snippets/blob'
+%div#notes= render "notes/notes_with_form"
= text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span7", readonly: true
%span.add-on
- if @project.public
- .cblue
- %i.icon-share
- public
+ = public_icon
+ %span.cblue public
- else
- .cgreen
- %i.icon-lock
- private
+ = private_icon
+ %span.cgreen private
%i.icon-file
%strong= @snippet.file_name
%span.options
- = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank"
+ .btn-group.tree-btn-group.pull-right
+ - if @snippet.author == current_user
+ = link_to "Edit", edit_snippet_path(@snippet), class: "btn btn-tiny", title: 'Edit Snippet'
+ = link_to "Raw", raw_snippet_path(@snippet), class: "btn btn-tiny", target: "_blank"
.file_content.code
- unless @snippet.content.empty?
%div{class: user_color_scheme_class}
= @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}"
%hr
.snippet-form-holder
- = form_for [@project, @snippet] do |f|
+ = form_for @snippet, as: :personal_snippet, url: url do |f|
-if @snippet.errors.any?
.alert.alert-error
%ul
= f.label :title
.input= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true
.clearfix
+ = f.label "Private?"
+ .input= f.check_box :private, {class: ''}
+ .clearfix
= f.label "Lifetime"
.input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'}
.clearfix
.form-actions
= f.submit 'Save', class: "btn-save btn"
- = link_to "Cancel", project_snippets_path(@project), class: " btn"
+ = link_to "Cancel", snippets_path(@project), class: " btn"
- unless @snippet.new_record?
- .pull-right= link_to 'Destroy', [@project, @snippet], confirm: 'Removed snippet cannot be restored! Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}"
+ .pull-right= link_to 'Destroy', snippet_path(@snippet), confirm: 'Removed snippet cannot be restored! Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}"
:javascript
-%tr
- %td
- = image_tag gravatar_icon(snippet.author_email), class: "avatar s24"
- %a{href: project_snippet_path(snippet.project, snippet)}
- %strong= truncate(snippet.title, length: 60)
- %td
- = snippet.file_name
- %td
+%li
+ .snippet-title
+ - if snippet.private?
+ = private_icon
+ - else
+ = public_icon
+ = link_to reliable_snippet_path(snippet) do
+ %h5.inline
+ = truncate(snippet.title, length: 60)
%span.cgray
- - if snippet.expires_at
- = snippet.expires_at.to_date.to_s(:short)
- - else
- Never
+ = snippet.file_name
+
+ %small.pull-right.cgray
+ - if snippet.project_id?
+ = link_to snippet.project.name_with_namespace, project_path(snippet.project)
+ %span
+ \|
+ Expires:
+ - if snippet.expires_at
+ = snippet.expires_at.to_date.to_s(:short)
+ - else
+ Never
+
+ .snippet-info.prepend-left-20
+ = "##{snippet.id}"
+ %span.light
+ by
+ = image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16"
+ = snippet.author_name
+
--- /dev/null
+%ul.bordered-list
+ = render partial: 'snippet', collection: @snippets
+ - if @snippets.empty?
+ %li
+ %h3.nothing_here_message Nothing here.
+
+= paginate @snippets
--- /dev/null
+%h3.page_title
+ My Snippets
+ %small share code pastes with others out of git repository
+ = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
+ Add new snippet
+
+%hr
+
+.row
+ .span3
+ %ul.nav.nav-pills.nav-stacked
+ = nav_tab :scope, nil do
+ = link_to "All", user_snippets_path(@user)
+ = nav_tab :scope, 'private' do
+ = link_to "Private", user_snippets_path(@user, scope: 'private')
+ = nav_tab :scope, 'public' do
+ = link_to "Public", user_snippets_path(@user, scope: 'public')
+
+ .span9
+ = render 'snippets'
+
-= render "snippets/form"
+= render "snippets/form", url: snippet_path(@snippet)
%h3.page_title
- Snippets
+ Public snippets
%small share code pastes with others out of git repository
+ = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
+ Add new snippet
+
+%hr
+.row
+ .span12
+ = render 'snippets'
- - if can? current_user, :write_snippet, @project
- = link_to new_project_snippet_path(@project), class: "btn btn-small add_new pull-right", title: "New Snippet" do
- Add new snippet
-%br
-%table
- %thead
- %tr
- %th Title
- %th File Name
- %th Expires At
- = render @snippets
- - if @snippets.empty?
- %tr
- %td{colspan: 3}
- %h3.nothing_here_message Nothing here.
-= render "snippets/form"
+= render "snippets/form", url: snippets_path(@snippet)
%h3.page_title
+ - if @snippet.private?
+ %i.icon-lock.cgreen
+ - else
+ %i.icon-globe.cblue
+
= @snippet.title
- %small= @snippet.file_name
- - if can?(current_user, :admin_snippet, @project) || @snippet.author == current_user
- = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-small pull-right", title: 'Edit Snippet'
+ %small.pull-right
+ = "##{@snippet.id}"
+ %span.light
+ by
+ = image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
+ = @snippet.author_name
%br
%div= render 'blob'
-%div#notes= render "notes/notes_with_form"
--- /dev/null
+%h3.page_title
+ = image_tag gravatar_icon(@user.email), class: "avatar s24"
+ = @user.name
+ %span
+ \/
+ Snippets
+ %small share code pastes with others out of git repository
+ = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
+ Add new snippet
+
+%hr
+
+= render 'snippets'
+- can_admin_project = (can? current_user, :admin_project, @project)
- team.each do |access, members|
- role = Project.access_options.key(access).pluralize
.ui-box{class: role.downcase}
%span.light (#{members.size})
%ul.well-list
- members.sort_by(&:user_name).each do |team_member|
- = render 'team_members/team_member', member: team_member
+ = render 'team_members/team_member', member: team_member, current_user_can_admin_project: can_admin_project
- user = member.user
-- allow_admin = can? current_user, :admin_project, @project
+- allow_admin = current_user_can_admin_project
%li{id: dom_id(user), class: "team_member_row user_#{user.id}"}
.row
.span4
-%h3.page_title New Team
-%hr
= form_for @team, url: teams_path do |f|
- if @team.errors.any?
.alert.alert-error
.input
= f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
+
+ .clearfix
+ .input
+ %ul
+ %li All created teams are public (users can view who enter into team and which project are assigned for this team)
+ %li People within a team see only projects they have access to
+ %li You will be able to assign existing projects for team
.form-actions
= f.submit 'Create team', class: "btn btn-create"
- .padded
- %ul
- %li All created teams are public (users can view who enter into team and which project are assigned for this team)
- %li People within a team see only projects they have access to
- %li You will be able to assign existing projects for team
- %hr
-
- if current_user.can_create_group?
.clearfix
.input.light
.loading.hide
.side.span4
- if @team.description.present?
- .description.well.well-small.light
+ .description-block
= @team.description
= render "projects", projects: @projects
.prepend-top-20
# To enable smtp email delivery for your GitLab instance do next:
# 1. Change config/environments/production.rb to use smtp
# config.action_mailer.delivery_method = :smtp
-# 2. Rename this file to smpt_settings.rb
+# 2. Rename this file to smtp_settings.rb
# 3. Edit settings inside this file
# 4. Restart GitLab instance
#
#
get 'help' => 'help#index'
get 'help/api' => 'help#api'
+ get 'help/api/:category' => 'help#api', as: 'help_api_file'
get 'help/markdown' => 'help#markdown'
get 'help/permissions' => 'help#permissions'
get 'help/public_access' => 'help#public_access'
get 'help/workflow' => 'help#workflow'
#
+ # Global snippets
+ #
+ resources :snippets do
+ member do
+ get "raw"
+ end
+ end
+ get "/s/:username" => "snippets#user_index", as: :user_snippets, constraints: { username: /.*/ }
+
+ #
# Public namespace
#
namespace :public do
resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
resources :compare, only: [:index, :create]
resources :blame, only: [:show], constraints: {id: /.+/}
- resources :graph, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
+ resources :network, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
+ resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
+ scope module: :projects do
+ resources :snippets do
+ member do
+ get "raw"
+ end
+ end
+ end
+
resources :wikis, only: [:show, :edit, :destroy, :create] do
collection do
get :pages
member do
# tree viewer logs
- get "logs_tree", constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }
+ get "logs_tree", constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }
get "logs_tree/:path" => "refs#logs_tree",
as: :logs_file,
constraints: {
- id: /[a-zA-Z.0-9\/_\-]+/,
+ id: /[a-zA-Z.0-9\/_\-#%+]+/,
path: /.*/
}
end
end
end
- resources :snippets do
- member do
- get "raw"
- end
- end
-
resources :hooks, only: [:index, :create, :destroy] do
member do
get :test
end
end
-
resources :team, controller: 'team_members', only: [:index]
resources :milestones, except: [:destroy]
--- /dev/null
+class AddPrivateToSnippets < ActiveRecord::Migration
+ def change
+ add_column :snippets, :private, :boolean, null: false, default: true
+ end
+end
--- /dev/null
+class AddTypeToSnippets < ActiveRecord::Migration
+ def change
+ add_column :snippets, :type, :string
+ end
+end
--- /dev/null
+class ChangeProjectIdToNullInSnipepts < ActiveRecord::Migration
+ def up
+ change_column :snippets, :project_id, :integer, :null => true
+ end
+
+ def down
+ change_column :snippets, :project_id, :integer, :null => false
+ end
+end
--- /dev/null
+class AddTypeValueForSnippets < ActiveRecord::Migration
+ def up
+ Snippet.where("project_id IS NOT NULL").update_all(type: 'ProjectSnippet')
+ end
+
+ def down
+ end
+end
create_table "snippets", :force => true do |t|
t.string "title"
t.text "content"
- t.integer "author_id", :null => false
- t.integer "project_id", :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.integer "author_id", :null => false
+ t.integer "project_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.string "file_name"
t.datetime "expires_at"
+ t.boolean "private", :default => true, :null => false
+ t.string "type"
end
add_index "snippets", ["created_at"], :name => "index_snippets_on_created_at"
+ [Users](doc/api/users.md)
+ [Session](doc/api/session.md)
+ [Projects](doc/api/projects.md)
-+ [Groups](doc/api/groups.md)
-+ [Snippets](doc/api/snippets.md)
++ [Project Snippets](doc/api/project_snippets.md)
+ [Repositories](doc/api/repositories.md)
+ [Issues](doc/api/issues.md)
+ [Milestones](doc/api/milestones.md)
+ [Notes](doc/api/notes.md)
++ [Deploy Keys](doc/api/deploy_keys.md)
+ [System Hooks](doc/api/system_hooks.md)
++ [Groups](doc/api/groups.md)
+ [User Teams](doc/api/user_teams.md)
--- /dev/null
+## Deploy Keys
+
+### List deploy keys
+
+Get a list of a project's deploy keys.
+
+```
+GET /projects/:id/keys
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project
+
+```json
+[
+ {
+ "id": 1,
+ "title" : "Public key"
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
+ 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
+ soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ },
+ {
+ "id": 3,
+ "title" : "Another Public key"
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
+ 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
+ soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ }
+]
+```
+
+
+### Single deploy key
+
+Get a single key.
+
+```
+GET /projects/:id/keys/:key_id
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project
++ `key_id` (required) - The ID of the deploy key
+
+```json
+{
+ "id": 1,
+ "title" : "Public key"
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
+ 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
+ soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+}
+```
+
+
+### Add deploy key
+
+Creates a new deploy key for a project.
+If deploy key already exists in another project - it will be joined to project but only if original one was is accessible by same user
+
+```
+POST /projects/:id/keys
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project
++ `title` (required) - New deploy key's title
++ `key` (required) - New deploy key
+
+
+### Delete deploy key
+
+Delete a deploy key from a project
+
+```
+DELETE /projects/:id/keys/:key_id
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project
++ `key_id` (required) - The ID of the deploy key
+
"blocked": false,
"created_at": "2012-05-23T08:00:58Z"
},
- "closed": true,
+ "state": 'closed',
"updated_at": "2012-07-02T17:53:12Z",
"created_at": "2012-07-02T17:53:12Z"
},
"title": "v1.0",
"description": "",
"due_date": "2012-07-20",
- "closed": false,
+ "state": 'reopenend',
"updated_at": "2012-07-04T13:42:48Z",
"created_at": "2012-07-04T13:42:48Z"
},
"blocked": false,
"created_at": "2012-05-23T08:00:58Z"
},
- "closed": false,
+ "state": 'opened',
"updated_at": "2012-07-12T13:43:19Z",
"created_at": "2012-06-28T12:58:06Z"
}
"title": "v1.0",
"description": "",
"due_date": "2012-07-20",
- "closed": false,
+ "state": 'closed',
"updated_at": "2012-07-04T13:42:48Z",
"created_at": "2012-07-04T13:42:48Z"
},
"blocked": false,
"created_at": "2012-05-23T08:00:58Z"
},
- "closed": false,
+ "state": 'opened',
"updated_at": "2012-07-12T13:43:19Z",
"created_at": "2012-06-28T12:58:06Z"
}
+ `assignee_id` (optional) - The ID of a user to assign issue
+ `milestone_id` (optional) - The ID of a milestone to assign issue
+ `labels` (optional) - Comma-separated label names for an issue
-+ `closed` (optional) - The state of an issue (0 = false, 1 = true)
++ `state_event` (optional) - The state event of an issue ('close' to close issue and 'reopen' to reopen it)
## Delete existing issue (**Deprecated**)
+ `title` (optional) - The title of a milestone
+ `description` (optional) - The description of a milestone
+ `due_date` (optional) - The due date of the milestone
-+ `closed` (optional) - The status of the milestone
++ `state_event` (optional) - The state event of the milestone (close|activate)
"merge_requests_enabled": false,
"wall_enabled": true,
"wiki_enabled": true,
- "created_at": "2012-05-23T08:05:02Z"
+ "created_at": "2012-05-23T08:05:02Z",
+ "last_activity_at": "2012-05-23T08:05:02Z"
},
{
"id": 5,
"merge_requests_enabled": true,
"wall_enabled": true,
"wiki_enabled": true,
- "created_at": "2012-05-30T12:49:20Z"
+ "created_at": "2012-05-30T12:49:20Z",
+ "last_activity_at": "2012-05-23T08:05:02Z"
}
]
```
{
"id": 5,
"name": "gitlab",
+ "name_with_namespace": "GitLab / gitlabhq",
"description": null,
"default_branch": "api",
"owner": {
"merge_requests_enabled": true,
"wall_enabled": true,
"wiki_enabled": true,
- "created_at": "2012-05-30T12:49:20Z"
+ "created_at": "2012-05-30T12:49:20Z",
+ "last_activity_at": "2012-05-23T08:05:02Z"
}
```
+### Get project events
+
+Get a project events for specific project.
+Sorted from newest to latest
+
+```
+GET /projects/:id/events
+```
+
+Parameters:
+
++ `id` (required) - The ID or NAME of a project
+
+```json
+
+[{
+ "title": null,
+ "project_id": 15,
+ "action_name": "closed",
+ "target_id": 830,
+ "target_type": "Issue",
+ "author_id": 1,
+ "data": null,
+ "target_title": "Public project search field"
+}, {
+ "title": null,
+ "project_id": 15,
+ "action_name": "opened",
+ "target_id": null,
+ "target_type": null,
+ "author_id": 1,
+ "data": {
+ "before": "50d4420237a9de7be1304607147aec22e4a14af7",
+ "after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
+ "ref": "refs/heads/master",
+ "user_id": 1,
+ "user_name": "Dmitriy Zaporozhets",
+ "repository": {
+ "name": "gitlabhq",
+ "url": "git@dev.gitlab.org:gitlab/gitlabhq.git",
+ "description": "GitLab: self hosted Git management software. \r\nDistributed under the MIT License.",
+ "homepage": "https://dev.gitlab.org/gitlab/gitlabhq"
+ },
+ "commits": [{
+ "id": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
+ "message": "Add simple search to projects in public area",
+ "timestamp": "2013-05-13T18:18:08+00:00",
+ "url": "https://dev.gitlab.org/gitlab/gitlabhq/commit/c5feabde2d8cd023215af4d2ceeb7a64839fc428",
+ "author": {
+ "name": "Dmitriy Zaporozhets",
+ "email": "dmitriy.zaporozhets@gmail.com"
+ }
+ }],
+ "total_commits_count": 1
+ },
+ "target_title": null
+}, {
+ "title": null,
+ "project_id": 15,
+ "action_name": "closed",
+ "target_id": 840,
+ "target_type": "Issue",
+ "author_id": 1,
+ "data": null,
+ "target_title": "Finish & merge Code search PR"
+}]
+```
+
### Create project
+ `id` (required) - The ID of the project.
+ `branch` (required) - The name of the branch.
-
-### List tags
-
-Lists all tags of a project.
-
-```
-GET /projects/:id/repository/tags
-```
-
-Parameters:
-
-+ `id` (required) - The ID of the project
-
-
-### List commits
-
-Lists all commits with pagination. If the optional `ref_name` name is not given the commits of
-the default branch (usually master) are returned.
-
-```
-GET /projects/:id/repository/commits
-```
-
-Parameters:
-
-+ `id` (required) - The Id of the project
-+ `ref_name` (optional) - The name of a repository branch or tag
-+ `page` (optional) - The page of commits to return (`0` default)
-+ `per_page` (optional) - The number of commits per page (`20` default)
-
-Returns values:
-
-+ `200 Ok` on success and a list with commits
-+ `404 Not Found` if project with id or the branch with `ref_name` not found
-
-
-
-## Deploy Keys
-
-### List deploy keys
-
-Get a list of a project's deploy keys.
-
-```
-GET /projects/:id/keys
-```
-
-Parameters:
-
-+ `id` (required) - The ID of the project
-
-```json
-[
- {
- "id": 1,
- "title" : "Public key"
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
- 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
- soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
- },
- {
- "id": 3,
- "title" : "Another Public key"
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
- 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
- soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
- }
-]
-```
-
-
-### Single deploy key
-
-Get a single key.
-
-```
-GET /projects/:id/keys/:key_id
-```
-
-Parameters:
-
-+ `id` (required) - The ID of the project
-+ `key_id` (required) - The ID of the deploy key
-
-```json
-{
- "id": 1,
- "title" : "Public key"
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
- 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
- soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
-}
-```
-
-
-### Add deploy key
-
-Creates a new deploy key for a project.
-
-```
-POST /projects/:id/keys
-```
-
-Parameters:
-
-+ `id` (required) - The ID of the project
-+ `title` (required) - New deploy key's title
-+ `key` (required) - New deploy key
-
-
-### Delete deploy key
-
-Delete a deploy key from a project
-
-```
-DELETE /projects/:id/keys/:key_id
-```
-
-Parameters:
-
-+ `id` (required) - The ID of the project
-+ `key_id` (required) - The ID of the deploy key
-
]
```
+## List repository tree
+
+Get a list of repository files and directories in a project.
+
+```
+GET /projects/:id/repository/tree
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `path` (optional) - The path inside repository. Used to get contend of subdirectories
++ `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
+
+```json
+
+[{
+ "name": "assets",
+ "type": "tree",
+ "mode": "040000",
+ "id": "6229c43a7e16fcc7e95f923f8ddadb8281d9c6c6"
+}, {
+ "name": "contexts",
+ "type": "tree",
+ "mode": "040000",
+ "id": "faf1cdf33feadc7973118ca42d35f1e62977e91f"
+}, {
+ "name": "controllers",
+ "type": "tree",
+ "mode": "040000",
+ "id": "95633e8d258bf3dfba3a5268fb8440d263218d74"
+}, {
+ "name": "Rakefile",
+ "type": "blob",
+ "mode": "100644",
+ "id": "35b2f05cbb4566b71b34554cf184a9d0bd9d46d6"
+}, {
+ "name": "VERSION",
+ "type": "blob",
+ "mode": "100644",
+ "id": "803e4a4f3727286c3093c63870c2b6524d30ec4f"
+}, {
+ "name": "config.ru",
+ "type": "blob",
+ "mode": "100644",
+ "id": "dfd2d862237323aa599be31b473d70a8a817943b"
+}]
+
+```
+
## Raw blob content
# Copy the example Puma config
sudo -u git -H cp config/puma.rb.example config/puma.rb
+ # Enable cluster mode if you expect to have a high load instance
+ # Ex. change amount of workers to 3 for 2GB RAM server
+ sudo -u git -H vim config/puma.rb
+
# Configure Git global settings for git user, useful when editing via web
# Edit user.email according to what is set in gitlab.yml
sudo -u git -H git config --global user.name "GitLab"
--- /dev/null
+# Things to do when creating new release
+NOTE: This is a developer guide. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update).
+## Install guide up to date?
+
+* References correct GitLab branch `x-x-stable` and correct GitLab shell tag?
+
+## Make upgrade guide
+
+### From x.x to x.x
+
+#### 0. Any major changes? Database updates? Web server change? File strucuture changes?
+
+#### 1. Make backup
+
+#### 2. Stop server
+
+#### 3. Do users need to update dependencies like `git`?
+
+#### 4. Get latest code
+
+#### 5. Does GitLab shell need to be updated?
+
+#### 6. Install libs, migrations, etc.
+
+#### 7. Any config files updated since last release?
+
+Check if any of these changed since last release (~22nd of last month depending on when last release branch was created):
+
+* https://github.com/gitlabhq/gitlabhq/commits/master/lib/support/nginx/gitlab
+* https://github.com/gitlabhq/gitlab-shell/commits/master/config.yml.example
+* https://github.com/gitlabhq/gitlabhq/commits/master/config/gitlab.yml.example
+* https://github.com/gitlabhq/gitlabhq/commits/master/config/puma.rb.example
+* https://github.com/gitlabhq/gitlabhq/commits/master/config/database.yml.mysql
+* https://github.com/gitlabhq/gitlabhq/commits/master/config/database.yml.postgresql
+
+#### 8. Need to update init script?
+
+Check if changed since last release (~22nd of last month depending on when last release branch was created): https://github.com/gitlabhq/gitlabhq/commits/master/lib/support/init.d/gitlab
+
+#### 9. Start application
+
+#### 10. Check application status
+
+## Make sure code status is good
+
+* [![build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
+
+* [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) on travis-ci.org (master branch)
+
+* [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
+
+* [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available)
+
+* [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
+
+## Make release branch
# From 5.1 to 5.2
+### 0. Backup
+
+It's useful to make a backup just in case things go south:
+(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create
+```
+
### 1. Stop server
sudo service gitlab stop
sudo -u git -H git checkout v1.4.0
```
-### 4. Install libs, migrations etc
+### 4. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
+
+# MySQL
sudo -u git -H bundle install --without development test postgres --deployment
+
+#PostgreSQL
+sudo -u git -H bundle install --without development test mysql --deployment
+
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
```
-### 5. Start application
+### 5. Update config files
+
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/puma.rb.example but with your settings.
+
+### 6. Update Init script
+
+```bash
+sudo rm /etc/init.d/gitlab
+sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/5-2-stable/lib/support/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab
+```
+
+### 6. Start application
sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade complete!
+
+## Things went south? Revert to previous version (5.1)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 5.0 to 5.1`](5.0-to-5.1.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:restore
+```
Then I should be redirected to group page
And I should see newly created group
+ @javascript
Scenario: Add user into projects in group
When I visit admin group page
When I select user "John" from user list as "Reporter"
Then the active main tab should be Merge Requests
And no other main tabs should be active
- Scenario: On Dashboard Search
- Given I visit dashboard search page
- Then the active main tab should be Search
- And no other main tabs should be active
-
Scenario: On Dashboard Help
Given I visit dashboard help page
Then the active main tab should be Help
When I visit group merge requests page
Then I should see merge requests from this group assigned to me
+ @javascript
Scenario: I should add user to projects in Group
Given I have new user "John"
When I visit group people page
--- /dev/null
+Feature: Project Graph
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And I visit project "Shop" graph page
+
+ @javascript
+ Scenario: I should see project graphs
+ Then page should have graphs
And project "Shop" has push event
And I visit project "Shop" page
+ @javascript
Scenario: I should see project activity
When I visit project "Shop" page
Then I should see project "Shop" activity feed
--- /dev/null
+Feature: Project Snippets
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And project "Shop" have "Snippet one" snippet
+ And project "Shop" have no "Snippet two" snippet
+ And I visit project "Shop" snippets page
+
+ Scenario: I should see snippets
+ Given I visit project "Shop" snippets page
+ Then I should see "Snippet one" in snippets
+ And I should not see "Snippet two" in snippets
+
+ Scenario: I create new project snippet
+ Given I click link "New Snippet"
+ And I submit new snippet "Snippet three"
+ Then I should see snippet "Snippet three"
+
+ @javascript
+ Scenario: I comment on a snippet "Snippet one"
+ Given I visit snippet page "Snippet one"
+ And I leave a comment like "Good snippet!"
+ Then I should see comment "Good snippet!"
+
+ Scenario: I update "Snippet one"
+ Given I visit snippet page "Snippet one"
+ And I click link "Edit"
+ And I submit new title "Snippet new title"
+ Then I should see "Snippet new title"
+
+ Scenario: I destroy "Snippet one"
+ Given I visit snippet page "Snippet one"
+ And I click link "Edit"
+ And I click link "Destroy"
+ Then I should not see "Snippet one" in snippets
--- /dev/null
+Feature: Discover Snippets
+ Background:
+ Given I sign in as a user
+ And I have public "Personal snippet one" snippet
+ And I have private "Personal snippet private" snippet
+
+ Scenario: I should see snippets
+ Given I visit snippets page
+ Then I should see "Personal snippet one" in snippets
+ And I should not see "Personal snippet private" in snippets
--- /dev/null
+Feature: Snippets Feature
+ Background:
+ Given I sign in as a user
+ And I have public "Personal snippet one" snippet
+ And I have private "Personal snippet private" snippet
+
+ Scenario: I create new snippet
+ Given I visit new snippet page
+ And I submit new snippet "Personal snippet three"
+ Then I should see snippet "Personal snippet three"
+
+ Scenario: I update "Personal snippet one"
+ Given I visit snippet page "Personal snippet one"
+ And I click link "Edit"
+ And I submit new title "Personal snippet new title"
+ Then I should see "Personal snippet new title"
+
+ Scenario: Set "Personal snippet one" public
+ Given I visit snippet page "Personal snippet one"
+ And I click link "Edit"
+ And I uncheck "Private" checkbox
+ Then I should see "Personal snippet one" public
+
+ Scenario: I destroy "Personal snippet one"
+ Given I visit snippet page "Personal snippet one"
+ And I click link "Edit"
+ And I click link "Destroy"
+ Then I should not see "Personal snippet one" in snippets
--- /dev/null
+Feature: User Snippets
+ Background:
+ Given I sign in as a user
+ And I have public "Personal snippet one" snippet
+ And I have private "Personal snippet private" snippet
+
+ Scenario: I should see all my snippets
+ Given I visit my snippets page
+ Then I should see "Personal snippet one" in snippets
+ And I should see "Personal snippet private" in snippets
+
+ Scenario: I can see only my private snippets
+ Given I visit my snippets page
+ And I click "Private" filter
+ Then I should not see "Personal snippet one" in snippets
+ And I should see "Personal snippet private" in snippets
+
+ Scenario: I can see only my public snippets
+ Given I visit my snippets page
+ And I click "Public" filter
+ Then I should see "Personal snippet one" in snippets
+ And I should not see "Personal snippet private" in snippets
include SharedAuthentication
include SharedPaths
include SharedActiveTab
+ include Select2Helper
When 'I visit admin group page' do
visit admin_group_path(current_group)
When 'I select user "John" from user list as "Reporter"' do
user = User.find_by_name("John")
+ select2(user.id, from: "#user_ids", multiple: true)
within "#new_team_member" do
- select user.name, from: "user_ids"
select "Reporter", from: "project_access"
end
click_button "Add user to projects in group"
Then 'I should see "John" in team list in every project as "Reporter"' do
user = User.find_by_name("John")
- projects_with_access = find(".user_#{user.id} .projects_access")
- projects_with_access.should have_link("Reporter")
end
protected
end
Then 'I should see empty projects table' do
- page.has_no_css?("#projects_list").must_equal true
+ page.should have_content "Projects (0)"
end
When 'I select project "Shop" with max access "Reporter"' do
ensure_active_main_tab('Merge Requests')
end
- Then 'the active main tab should be Search' do
- ensure_active_main_tab('Search')
- end
-
Then 'the active main tab should be Help' do
ensure_active_main_tab('Help')
end
class Groups < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
+ include Select2Helper
Then 'I should see projects list' do
current_user.authorized_projects.each do |project|
And 'I select user "John" from list with role "Reporter"' do
user = User.find_by_name("John")
within "#new_team_member" do
- select user.name, from: "user_ids"
+ select2(user.id, from: "#user_ids", multiple: true)
select "Reporter", from: "project_access"
end
click_button "Add"
When "I change my code preview theme" do
within '.code-preview-theme' do
- choose "Solarized Dark"
+ choose "Solarized dark"
end
end
step 'other project has deploy key' do
@second_project = create :project, namespace: current_user.namespace
+ @second_project.team << [current_user, :master]
create(:deploy_keys_project, project: @second_project)
end
--- /dev/null
+class ProjectGraph < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+
+ Then 'page should have graphs' do
+ page.should have_selector ".stat-graph"
+ end
+
+ When 'I visit project "Shop" graph page' do
+ project = Project.find_by_name("Shop")
+ visit project_graph_path(project, "master")
+ end
+end
Network::Graph.stub(max_count: 10)
project = Project.find_by_name("Shop")
- visit project_graph_path(project, "master")
+ visit project_network_path(project, "master")
end
And 'page should select "master" in select box' do
--- /dev/null
+class ProjectSnippets < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedNote
+ include SharedPaths
+
+ And 'project "Shop" have "Snippet one" snippet' do
+ create(:project_snippet,
+ title: "Snippet one",
+ content: "Test content",
+ file_name: "snippet.rb",
+ project: project,
+ author: project.users.first)
+ end
+
+ And 'project "Shop" have no "Snippet two" snippet' do
+ create(:snippet,
+ title: "Snippet two",
+ content: "Test content",
+ file_name: "snippet.rb",
+ author: project.users.first)
+ end
+
+ Given 'I click link "New Snippet"' do
+ click_link "Add new snippet"
+ end
+
+ Given 'I click link "Snippet one"' do
+ click_link "Snippet one"
+ end
+
+ Then 'I should see "Snippet one" in snippets' do
+ page.should have_content "Snippet one"
+ end
+
+ And 'I should not see "Snippet two" in snippets' do
+ page.should_not have_content "Snippet two"
+ end
+
+ And 'I should not see "Snippet one" in snippets' do
+ page.should_not have_content "Snippet one"
+ end
+
+ And 'I click link "Edit"' do
+ within ".file_title" do
+ click_link "Edit"
+ end
+ end
+
+ And 'I click link "Destroy"' do
+ click_link "Destroy"
+ end
+
+ And 'I submit new snippet "Snippet three"' do
+ fill_in "project_snippet_title", :with => "Snippet three"
+ select "forever", :from => "project_snippet_expires_at"
+ fill_in "project_snippet_file_name", :with => "my_snippet.rb"
+ within('.file-editor') do
+ find(:xpath, "//input[@id='project_snippet_content']").set 'Content of snippet three'
+ end
+ click_button "Save"
+ end
+
+ Then 'I should see snippet "Snippet three"' do
+ page.should have_content "Snippet three"
+ page.should have_content "Content of snippet three"
+ end
+
+ And 'I submit new title "Snippet new title"' do
+ fill_in "project_snippet_title", :with => "Snippet new title"
+ click_button "Save"
+ end
+
+ Then 'I should see "Snippet new title"' do
+ page.should have_content "Snippet new title"
+ end
+
+ And 'I leave a comment like "Good snippet!"' do
+ within('.js-main-target-form') do
+ fill_in "note_note", with: "Good snippet!"
+ click_button "Add Comment"
+ end
+ end
+
+ Then 'I should see comment "Good snippet!"' do
+ page.should have_content "Good snippet!"
+ end
+
+ And 'I visit snippet page "Snippet one"' do
+ visit project_snippet_path(project, project_snippet)
+ end
+
+ def project
+ @project ||= Project.find_by_name!("Shop")
+ end
+
+ def project_snippet
+ @project_snippet ||= ProjectSnippet.find_by_title!("Snippet One")
+ end
+end
# Stub Graph max_size to speed up test (10 commits vs. 650)
Network::Graph.stub(max_count: 10)
- visit project_graph_path(@project, root_ref)
+ visit project_network_path(@project, root_ref)
end
step "I visit my project's issues page" do
visit public_root_path
end
+ # ----------------------------------------
+ # Snippets
+ # ----------------------------------------
+
+ Given 'I visit project "Shop" snippets page' do
+ visit project_snippets_path(project)
+ end
+
+ Given 'I visit snippets page' do
+ visit snippets_path
+ end
+
+ Given 'I visit new snippet page' do
+ visit new_snippet_path
+ end
+
def root_ref
@project.repository.root_ref
end
--- /dev/null
+module SharedSnippet
+ include Spinach::DSL
+
+ And 'I have public "Personal snippet one" snippet' do
+ create(:personal_snippet,
+ title: "Personal snippet one",
+ content: "Test content",
+ file_name: "snippet.rb",
+ private: false,
+ author: current_user)
+ end
+
+ And 'I have private "Personal snippet private" snippet' do
+ create(:personal_snippet,
+ title: "Personal snippet private",
+ content: "Provate content",
+ file_name: "private_snippet.rb",
+ private: true,
+ author: current_user)
+ end
+end
--- /dev/null
+class DiscoverSnippets < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedSnippet
+
+ Then 'I should see "Personal snippet one" in snippets' do
+ page.should have_content "Personal snippet one"
+ end
+
+ And 'I should not see "Personal snippet private" in snippets' do
+ page.should_not have_content "Personal snippet private"
+ end
+
+ def snippet
+ @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one")
+ end
+end
--- /dev/null
+class SnippetsFeature < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+ include SharedSnippet
+
+ Given 'I click link "Personal snippet one"' do
+ click_link "Personal snippet one"
+ end
+
+ And 'I should not see "Personal snippet one" in snippets' do
+ page.should_not have_content "Personal snippet one"
+ end
+
+ And 'I click link "Edit"' do
+ within ".file_title" do
+ click_link "Edit"
+ end
+ end
+
+ And 'I click link "Destroy"' do
+ click_link "Destroy"
+ end
+
+ And 'I submit new snippet "Personal snippet three"' do
+ fill_in "personal_snippet_title", :with => "Personal snippet three"
+ select "forever", :from => "personal_snippet_expires_at"
+ fill_in "personal_snippet_file_name", :with => "my_snippet.rb"
+ within('.file-editor') do
+ find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of snippet three'
+ end
+ click_button "Save"
+ end
+
+ Then 'I should see snippet "Personal snippet three"' do
+ page.should have_content "Personal snippet three"
+ page.should have_content "Content of snippet three"
+ end
+
+ And 'I submit new title "Personal snippet new title"' do
+ fill_in "personal_snippet_title", :with => "Personal snippet new title"
+ click_button "Save"
+ end
+
+ Then 'I should see "Personal snippet new title"' do
+ page.should have_content "Personal snippet new title"
+ end
+
+ And 'I uncheck "Private" checkbox' do
+ find(:xpath, "//input[@id='personal_snippet_private']").set true
+ click_button "Save"
+ end
+
+ Then 'I should see "Personal snippet one" public' do
+ page.should have_no_xpath("//i[@class='public-snippet']")
+ end
+
+ And 'I visit snippet page "Personal snippet one"' do
+ visit snippet_path(snippet)
+ end
+
+ def snippet
+ @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one")
+ end
+end
--- /dev/null
+class UserSnippets < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedSnippet
+
+ Given 'I visit my snippets page' do
+ visit user_snippets_path(current_user)
+ end
+
+ Then 'I should see "Personal snippet one" in snippets' do
+ page.should have_content "Personal snippet one"
+ end
+
+ And 'I should see "Personal snippet private" in snippets' do
+ page.should have_content "Personal snippet private"
+ end
+
+ Then 'I should not see "Personal snippet one" in snippets' do
+ page.should_not have_content "Personal snippet one"
+ end
+
+ And 'I should not see "Personal snippet private" in snippets' do
+ page.should_not have_content "Personal snippet private"
+ end
+
+ Given 'I click "Public" filter' do
+ within('.nav-stacked') do
+ click_link "Public"
+ end
+ end
+
+ Given 'I click "Private" filter' do
+ within('.nav-stacked') do
+ click_link "Private"
+ end
+ end
+
+ def snippet
+ @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one")
+ end
+end
mount Internal
mount SystemHooks
mount UserTeams
+ mount ProjectSnippets
+ mount DeployKeys
+ mount ProjectHooks
end
end
--- /dev/null
+module API
+ # Projects API
+ class DeployKeys < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+
+ # Get a specific project's keys
+ #
+ # Example Request:
+ # GET /projects/:id/keys
+ get ":id/keys" do
+ present user_project.deploy_keys, with: Entities::SSHKey
+ end
+
+ # Get single key owned by currently authenticated user
+ #
+ # Example Request:
+ # GET /projects/:id/keys/:id
+ get ":id/keys/:key_id" do
+ key = user_project.deploy_keys.find params[:key_id]
+ present key, with: Entities::SSHKey
+ end
+
+ # Add new ssh key to currently authenticated user
+ # If deploy key already exists - it will be joined to project
+ # but only if original one was is accessible by same user
+ #
+ # Parameters:
+ # key (required) - New SSH Key
+ # title (required) - New SSH Key's title
+ # Example Request:
+ # POST /projects/:id/keys
+ post ":id/keys" do
+ attrs = attributes_for_keys [:title, :key]
+
+ if attrs[:key].present?
+ attrs[:key].strip!
+
+ # check if key already exist in project
+ key = user_project.deploy_keys.find_by_key(attrs[:key])
+ if key
+ present key, with: Entities::SSHKey
+ return
+ end
+
+ # Check for available deploy keys in other projects
+ key = current_user.accessible_deploy_keys.find_by_key(attrs[:key])
+ if key
+ user_project.deploy_keys << key
+ present key, with: Entities::SSHKey
+ return
+ end
+ end
+
+ key = DeployKey.new attrs
+
+ if key.valid? && user_project.deploy_keys << key
+ present key, with: Entities::SSHKey
+ else
+ not_found!
+ end
+ end
+
+ # Delete existed ssh key of currently authenticated user
+ #
+ # Example Request:
+ # DELETE /projects/:id/keys/:id
+ delete ":id/keys/:key_id" do
+ key = user_project.deploy_keys.find params[:key_id]
+ key.destroy
+ end
+ end
+ end
+end
end
class Project < Grape::Entity
- expose :id, :name, :description, :default_branch
+ expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url
expose :owner, using: Entities::UserBasic
- expose :public
+ expose :name, :name_with_namespace
expose :path, :path_with_namespace
- expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at
+ expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at, :last_activity_at
expose :namespace
end
expose :note
expose :author, using: Entities::UserBasic
end
+
+ class Event < Grape::Entity
+ expose :title, :project_id, :action_name
+ expose :target_id, :target_type, :author_id
+ expose :data, :target_title
+ end
end
end
# assignee_id (optional) - The ID of a user to assign issue
# milestone_id (optional) - The ID of a milestone to assign issue
# labels (optional) - The labels of an issue
- # state (optional) - The state of an issue (close|reopen)
+ # state_event (optional) - The state event of an issue (close|reopen)
# Example Request:
# PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do
# title (optional) - The title of a milestone
# description (optional) - The description of a milestone
# due_date (optional) - The due date of a milestone
- # state (optional) - The status of the milestone (close|activate)
+ # state_event (optional) - The state event of the milestone (close|activate)
# Example Request:
# PUT /projects/:id/milestones/:milestone_id
put ":id/milestones/:milestone_id" do
--- /dev/null
+module API
+ # Projects API
+ class ProjectHooks < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+ # Get project hooks
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/hooks
+ get ":id/hooks" do
+ authorize! :admin_project, user_project
+ @hooks = paginate user_project.hooks
+ present @hooks, with: Entities::Hook
+ end
+
+ # Get a project hook
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # hook_id (required) - The ID of a project hook
+ # Example Request:
+ # GET /projects/:id/hooks/:hook_id
+ get ":id/hooks/:hook_id" do
+ authorize! :admin_project, user_project
+ @hook = user_project.hooks.find(params[:hook_id])
+ present @hook, with: Entities::Hook
+ end
+
+
+ # Add hook to project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # url (required) - The hook URL
+ # Example Request:
+ # POST /projects/:id/hooks
+ post ":id/hooks" do
+ authorize! :admin_project, user_project
+ required_attributes! [:url]
+
+ @hook = user_project.hooks.new({"url" => params[:url]})
+ if @hook.save
+ present @hook, with: Entities::Hook
+ else
+ if @hook.errors[:url].present?
+ error!("Invalid url given", 422)
+ end
+ not_found!
+ end
+ end
+
+ # Update an existing project hook
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # hook_id (required) - The ID of a project hook
+ # url (required) - The hook URL
+ # Example Request:
+ # PUT /projects/:id/hooks/:hook_id
+ put ":id/hooks/:hook_id" do
+ @hook = user_project.hooks.find(params[:hook_id])
+ authorize! :admin_project, user_project
+ required_attributes! [:url]
+
+ attrs = attributes_for_keys [:url]
+ if @hook.update_attributes attrs
+ present @hook, with: Entities::Hook
+ else
+ if @hook.errors[:url].present?
+ error!("Invalid url given", 422)
+ end
+ not_found!
+ end
+ end
+
+ # Deletes project hook. This is an idempotent function.
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # hook_id (required) - The ID of hook to delete
+ # Example Request:
+ # DELETE /projects/:id/hooks/:hook_id
+ delete ":id/hooks/:hook_id" do
+ authorize! :admin_project, user_project
+ required_attributes! [:hook_id]
+
+ begin
+ @hook = ProjectHook.find(params[:hook_id])
+ @hook.destroy
+ rescue
+ # ProjectHook can raise Error if hook_id not found
+ end
+ end
+ end
+ end
+end
--- /dev/null
+module API
+ # Projects API
+ class ProjectSnippets < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+ # Get a project snippets
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/snippets
+ get ":id/snippets" do
+ present paginate(user_project.snippets), with: Entities::ProjectSnippet
+ end
+
+ # Get a project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # Example Request:
+ # GET /projects/:id/snippets/:snippet_id
+ get ":id/snippets/:snippet_id" do
+ @snippet = user_project.snippets.find(params[:snippet_id])
+ present @snippet, with: Entities::ProjectSnippet
+ end
+
+ # Create a new project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # title (required) - The title of a snippet
+ # file_name (required) - The name of a snippet file
+ # lifetime (optional) - The expiration date of a snippet
+ # code (required) - The content of a snippet
+ # Example Request:
+ # POST /projects/:id/snippets
+ post ":id/snippets" do
+ authorize! :write_project_snippet, user_project
+ required_attributes! [:title, :file_name, :code]
+
+ attrs = attributes_for_keys [:title, :file_name]
+ attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
+ attrs[:content] = params[:code] if params[:code].present?
+ @snippet = user_project.snippets.new attrs
+ @snippet.author = current_user
+
+ if @snippet.save
+ present @snippet, with: Entities::ProjectSnippet
+ else
+ not_found!
+ end
+ end
+
+ # Update an existing project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # title (optional) - The title of a snippet
+ # file_name (optional) - The name of a snippet file
+ # lifetime (optional) - The expiration date of a snippet
+ # code (optional) - The content of a snippet
+ # Example Request:
+ # PUT /projects/:id/snippets/:snippet_id
+ put ":id/snippets/:snippet_id" do
+ @snippet = user_project.snippets.find(params[:snippet_id])
+ authorize! :modify_project_snippet, @snippet
+
+ attrs = attributes_for_keys [:title, :file_name]
+ attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
+ attrs[:content] = params[:code] if params[:code].present?
+
+ if @snippet.update_attributes attrs
+ present @snippet, with: Entities::ProjectSnippet
+ else
+ not_found!
+ end
+ end
+
+ # Delete a project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # Example Request:
+ # DELETE /projects/:id/snippets/:snippet_id
+ delete ":id/snippets/:snippet_id" do
+ begin
+ @snippet = user_project.snippets.find(params[:snippet_id])
+ authorize! :modify_project_snippet, @snippet
+ @snippet.destroy
+ rescue
+ end
+ end
+
+ # Get a raw project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # Example Request:
+ # GET /projects/:id/snippets/:snippet_id/raw
+ get ":id/snippets/:snippet_id/raw" do
+ @snippet = user_project.snippets.find(params[:snippet_id])
+
+ env['api.format'] = :txt
+ content_type 'text/plain'
+ present @snippet.content
+ end
+ end
+ end
+end
present @projects, with: Entities::Project
end
+ # Get an owned projects list for authenticated user
+ #
+ # Example Request:
+ # GET /projects/owned
+ get '/owned' do
+ @projects = paginate current_user.owned_projects
+ present @projects, with: Entities::Project
+ end
+
# Get a single project
#
# Parameters:
present user_project, with: Entities::Project
end
+ # Get a single project events
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id
+ get ":id/events" do
+ limit = (params[:per_page] || 20).to_i
+ offset = (params[:page] || 0).to_i * limit
+ events = user_project.events.recent.limit(limit).offset(offset)
+
+ present events, with: Entities::Event
+ end
+
# Create new project
#
# Parameters:
{message: "Access revoked", id: params[:user_id].to_i}
end
end
-
- # Get project hooks
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/hooks
- get ":id/hooks" do
- authorize! :admin_project, user_project
- @hooks = paginate user_project.hooks
- present @hooks, with: Entities::Hook
- end
-
- # Get a project hook
- #
- # Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of a project hook
- # Example Request:
- # GET /projects/:id/hooks/:hook_id
- get ":id/hooks/:hook_id" do
- authorize! :admin_project, user_project
- @hook = user_project.hooks.find(params[:hook_id])
- present @hook, with: Entities::Hook
- end
-
-
- # Add hook to project
- #
- # Parameters:
- # id (required) - The ID of a project
- # url (required) - The hook URL
- # Example Request:
- # POST /projects/:id/hooks
- post ":id/hooks" do
- authorize! :admin_project, user_project
- required_attributes! [:url]
-
- @hook = user_project.hooks.new({"url" => params[:url]})
- if @hook.save
- present @hook, with: Entities::Hook
- else
- if @hook.errors[:url].present?
- error!("Invalid url given", 422)
- end
- not_found!
- end
- end
-
- # Update an existing project hook
- #
- # Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of a project hook
- # url (required) - The hook URL
- # Example Request:
- # PUT /projects/:id/hooks/:hook_id
- put ":id/hooks/:hook_id" do
- @hook = user_project.hooks.find(params[:hook_id])
- authorize! :admin_project, user_project
- required_attributes! [:url]
-
- attrs = attributes_for_keys [:url]
- if @hook.update_attributes attrs
- present @hook, with: Entities::Hook
- else
- if @hook.errors[:url].present?
- error!("Invalid url given", 422)
- end
- not_found!
- end
- end
-
- # Deletes project hook. This is an idempotent function.
- #
- # Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of hook to delete
- # Example Request:
- # DELETE /projects/:id/hooks/:hook_id
- delete ":id/hooks/:hook_id" do
- authorize! :admin_project, user_project
- required_attributes! [:hook_id]
-
- begin
- @hook = ProjectHook.find(params[:hook_id])
- @hook.destroy
- rescue
- # ProjectHook can raise Error if hook_id not found
- end
- end
-
- # Get a project snippets
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/snippets
- get ":id/snippets" do
- present paginate(user_project.snippets), with: Entities::ProjectSnippet
- end
-
- # Get a project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # Example Request:
- # GET /projects/:id/snippets/:snippet_id
- get ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- present @snippet, with: Entities::ProjectSnippet
- end
-
- # Create a new project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # title (required) - The title of a snippet
- # file_name (required) - The name of a snippet file
- # lifetime (optional) - The expiration date of a snippet
- # code (required) - The content of a snippet
- # Example Request:
- # POST /projects/:id/snippets
- post ":id/snippets" do
- authorize! :write_snippet, user_project
- required_attributes! [:title, :file_name, :code]
-
- attrs = attributes_for_keys [:title, :file_name]
- attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
- attrs[:content] = params[:code] if params[:code].present?
- @snippet = user_project.snippets.new attrs
- @snippet.author = current_user
-
- if @snippet.save
- present @snippet, with: Entities::ProjectSnippet
- else
- not_found!
- end
- end
-
- # Update an existing project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # title (optional) - The title of a snippet
- # file_name (optional) - The name of a snippet file
- # lifetime (optional) - The expiration date of a snippet
- # code (optional) - The content of a snippet
- # Example Request:
- # PUT /projects/:id/snippets/:snippet_id
- put ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- authorize! :modify_snippet, @snippet
-
- attrs = attributes_for_keys [:title, :file_name]
- attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
- attrs[:content] = params[:code] if params[:code].present?
-
- if @snippet.update_attributes attrs
- present @snippet, with: Entities::ProjectSnippet
- else
- not_found!
- end
- end
-
- # Delete a project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # Example Request:
- # DELETE /projects/:id/snippets/:snippet_id
- delete ":id/snippets/:snippet_id" do
- begin
- @snippet = user_project.snippets.find(params[:snippet_id])
- authorize! :modify_snippet, user_project
- @snippet.destroy
- rescue
- end
- end
-
- # Get a raw project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # Example Request:
- # GET /projects/:id/snippets/:snippet_id/raw
- get ":id/snippets/:snippet_id/raw" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- content_type 'text/plain'
- present @snippet.content
- end
-
- # Get a specific project's keys
- #
- # Example Request:
- # GET /projects/:id/keys
- get ":id/keys" do
- present user_project.deploy_keys, with: Entities::SSHKey
- end
-
- # Get single key owned by currently authenticated user
- #
- # Example Request:
- # GET /projects/:id/keys/:id
- get ":id/keys/:key_id" do
- key = user_project.deploy_keys.find params[:key_id]
- present key, with: Entities::SSHKey
- end
-
- # Add new ssh key to currently authenticated user
- #
- # Parameters:
- # key (required) - New SSH Key
- # title (required) - New SSH Key's title
- # Example Request:
- # POST /projects/:id/keys
- post ":id/keys" do
- attrs = attributes_for_keys [:title, :key]
- key = DeployKey.new attrs
- if key.valid? && user_project.deploy_keys << key
- present key, with: Entities::SSHKey
- else
- not_found!
- end
- end
-
- # Delete existed ssh key of currently authenticated user
- #
- # Example Request:
- # DELETE /projects/:id/keys/:id
- delete ":id/keys/:key_id" do
- key = user_project.deploy_keys.find params[:key_id]
- key.destroy
- end
end
end
end
get ":id/repository/commits" do
authorize! :download_code, user_project
- page = params[:page] || 0
+ page = (params[:page] || 0).to_i
per_page = (params[:per_page] || 20).to_i
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
present commits, with: Entities::RepoCommit
end
+ # Get a project repository tree
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
+ # Example Request:
+ # GET /projects/:id/repository/tree
+ get ":id/repository/tree" do
+ authorize! :download_code, user_project
+
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ path = params[:path] || nil
+
+ commit = user_project.repository.commit(ref)
+ tree = Tree.new(user_project.repository, commit.id, ref, path)
+
+ trees = []
+
+ %w(trees blobs submodules).each do |type|
+ trees += tree.send(type).map { |t| { name: t.name, type: type.singularize, mode: t.mode, id: t.id } }
+ end
+
+ trees
+ end
+
# Get a raw file contents
#
# Parameters:
print 'Put GitLab hooks in repositories dirs'.yellow
gitlab_shell_user_home = File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
- if system("#{gitlab_shell_user_home}/gitlab-shell/support/rewrite-hooks.sh")
+ if system("#{gitlab_shell_user_home}/gitlab-shell/support/rewrite-hooks.sh #{Gitlab.config.gitlab_shell.repos_path}")
puts " [DONE]".green
else
puts " [FAILED]".red
attr_reader :major, :minor, :patch
def self.parse(str)
- if m = str.match(/(\d+)\.(\d+)\.(\d+)/)
+ if str && m = str.match(/(\d+)\.(\d+)\.(\d+)/)
VersionInfo.new(m[1].to_i, m[2].to_i, m[3].to_i)
else
VersionInfo.new
def block_code(code, language)
options = { options: {encoding: 'utf-8'} }
- options.merge!(lexer: language.downcase) if Pygments::Lexer.find(language)
+ lexer = Pygments::Lexer.find(language) # language can be an alias
+ options.merge!(lexer: lexer.name.downcase) if lexer # downcase is required
# New lines are placed to fix an rendering issue
# with code wrapped inside <h1> tag for next case:
server {
listen YOUR_SERVER_IP:80 default_server; # e.g., listen 192.168.1.1:80; In most cases *:80 is a good idea
server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com;
+ server_tokens off; # don't show the version number, a security best practice
root /home/git/gitlab/public;
# individual nginx logs for this gitlab vhost
settings = YAML.load_file("backup_information.yml")
ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
- # restoring mismatching backups can lead to unexpected problems
- if settings[:gitlab_version] != %x{git rev-parse HEAD}.gsub(/\n/,"")
- puts "GitLab version mismatch:".red
- puts " Your current HEAD differs from the HEAD in the backup!".red
- puts " Please switch to the following revision and try again:".red
- puts " revision: #{settings[:gitlab_version]}".red
- exit 1
+ # backups directory is not always sub of Rails root and able to execute the git rev-parse below
+ begin
+ Dir.chdir(Rails.root)
+
+ # restoring mismatching backups can lead to unexpected problems
+ if settings[:gitlab_version] != %x{git rev-parse HEAD}.gsub(/\n/, "")
+ puts "GitLab version mismatch:".red
+ puts " Your current HEAD differs from the HEAD in the backup!".red
+ puts " Please switch to the following revision and try again:".red
+ puts " revision: #{settings[:gitlab_version]}".red
+ exit 1
+ end
+ ensure
+ # chdir back to original intended dir
+ Dir.chdir(Gitlab.config.backup.path)
end
Rake::Task["gitlab:backup:db:restore"].invoke
def check_init_script_up_to_date
print "Init script up-to-date? ... "
+ recipe_path = Rails.root.join("lib/support/init.d/", "gitlab")
script_path = "/etc/init.d/gitlab"
+
unless File.exists?(script_path)
puts "can't check because of previous errors".magenta
return
end
- recipe_content = `curl https://raw.github.com/gitlabhq/gitlab-recipes/master/init.d/gitlab 2>/dev/null`
+ recipe_content = File.read(recipe_path)
script_content = File.read(script_path)
if recipe_content == script_content
current_version = Gitlab::VersionInfo.parse(gitlab_shell_version)
print "GitLab Shell version >= #{required_version} ? ... "
- if required_version <= current_version
+ if current_version.valid? && required_version <= current_version
puts "OK (#{current_version})".green
else
puts "FAIL. Please update gitlab-shell to #{required_version} from #{current_version}".red
puts "Your git bin path is \"#{Gitlab.config.git.bin_path}\""
print "Git version >= #{required_version} ? ... "
- if required_version <= current_version
+ if current_version.valid? && required_version <= current_version
puts "yes (#{current_version})".green
else
puts "no".red
warn_user_is_not_gitlab
gitlab_shell_authorized_keys = File.join(File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}"),'.ssh/authorized_keys')
- puts "This will rebuild an authorized_keys file."
- puts "You will lose any data stored in #{gitlab_shell_authorized_keys}."
- ask_to_continue
- puts ""
+ unless ENV['force'] == 'yes'
+ puts "This will rebuild an authorized_keys file."
+ puts "You will lose any data stored in #{gitlab_shell_authorized_keys}."
+ ask_to_continue
+ puts ""
+ end
system("echo '# Managed by gitlab-shell' > #{gitlab_shell_authorized_keys}")
#!/bin/sh
-sudo -u gitlab -H bundle exec rake gitlab:check RAILS_ENV=production
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
url
end
- factory :snippet do
+ factory :project_snippet do
project
author
title
file_name
end
+ factory :personal_snippet do
+ author
+ title
+ content
+ file_name
+ end
+
+ factory :snippet do
+ author
+ title
+ content
+ file_name
+ end
+
factory :protected_branch do
name
project
+++ /dev/null
-require 'spec_helper'
-
-describe "Snippets" do
- let(:project) { create(:project) }
-
- before do
- login_as :user
- project.team << [@user, :developer]
- end
-
- describe "GET /snippets" do
- before do
- @snippet = create(:snippet,
- author: @user,
- project: project)
-
- visit project_snippets_path(project)
- end
-
- subject { page }
-
- it { should have_content(@snippet.title[0..10]) }
- it { should have_content(@snippet.project.name) }
-
- describe "Destroy" do
- before do
- # admin access to remove snippet
- @user.users_projects.destroy_all
- project.team << [@user, :master]
- visit edit_project_snippet_path(project, @snippet)
- end
-
- it "should remove entry" do
- expect {
- click_link "destroy_snippet_#{@snippet.id}"
- }.to change { Snippet.count }.by(-1)
- end
- end
- end
-
- describe "New snippet" do
- before do
- visit project_snippets_path(project)
- click_link "New Snippet"
- end
-
- it "should open new snippet popup" do
- page.current_path.should == new_project_snippet_path(project)
- end
-
- describe "fill in", js: true do
- before do
- fill_in "snippet_title", with: "login function"
- fill_in "snippet_file_name", with: "test.rb"
- page.execute_script("editor.insert('def login; end');")
- end
-
- it { expect { click_button "Save" }.to change {Snippet.count}.by(1) }
-
- it "should add new snippet to table" do
- click_button "Save"
- page.current_path.should == project_snippet_path(project, Snippet.last)
- page.should have_content "login function"
- page.should have_content "test.rb"
- end
- end
- end
-
- describe "Edit snippet" do
- before do
- @snippet = create(:snippet,
- author: @user,
- project: project)
- visit project_snippet_path(project, @snippet)
- click_link "Edit Snippet"
- end
-
- it "should open edit page" do
- page.current_path.should == edit_project_snippet_path(project, @snippet)
- end
-
- describe "fill in" do
- before do
- fill_in "snippet_title", with: "login function"
- fill_in "snippet_file_name", with: "test.rb"
- end
-
- it { expect { click_button "Save" }.to_not change {Snippet.count} }
-
- it "should update snippet fields" do
- click_button "Save"
-
- page.current_path.should == project_snippet_path(project, @snippet)
- page.should have_content "login function"
- page.should have_content "test.rb"
- end
- end
- end
-end
end
end
+
+ describe "user_color_scheme_class" do
+ context "with current_user is nil" do
+ it "should return a string" do
+ stub!(:current_user).and_return(nil)
+ user_color_scheme_class.should be_kind_of(String)
+ end
+ end
+
+ context "with a current_user" do
+ (1..5).each do |color_scheme_id|
+ context "with color_scheme_id == #{color_scheme_id}" do
+ it "should return a string" do
+ current_user = double(:color_scheme_id => color_scheme_id)
+ stub!(:current_user).and_return(current_user)
+ user_color_scheme_class.should be_kind_of(String)
+ end
+ end
+ end
+ end
+ end
+
end
let(:commit) { project.repository.commit }
let(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, project: project) }
- let(:snippet) { create(:snippet, project: project) }
+ let(:snippet) { create(:project_snippet, project: project) }
let(:member) { project.users_projects.where(user_id: user).first }
before do
describe "referencing a snippet" do
let(:object) { snippet }
let(:reference) { "$#{snippet.id}" }
+ let(:actual) { "Reference to #{reference}" }
+ let(:expected) { project_snippet_path(project, object) }
+
+ it "should link using a valid id" do
+ gfm(actual).should match(expected)
+ end
+
+ it "should link with adjacent text" do
+ # Wrap the reference in parenthesis
+ gfm(actual.gsub(reference, "(#{reference})")).should match(expected)
+
+ # Append some text to the end of the reference
+ gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected)
+ end
+
+ it "should keep whitespace intact" do
+ actual = "Referenced #{reference} already."
+ expected = /Referenced <a.+>[^\s]+<\/a> already/
+ gfm(actual).should match(expected)
+ end
+
+ it "should not link with an invalid id" do
+ # Modify the reference string so it's still parsed, but is invalid
+ reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
+ gfm(actual).should == actual
+ end
+
+ it "should include a title attribute" do
+ title = "Snippet: #{object.title}"
+ gfm(actual).should match(/title="#{title}"/)
+ end
+
+ it "should include standard gfm classes" do
+ css = object.class.to_s.underscore
+ gfm(actual).should match(/class="\s?gfm gfm-snippet\s?"/)
+ end
- include_examples 'referenced object'
end
describe "referencing multiple objects" do
--- /dev/null
+describe("ContributorsGraph", function () {
+ describe("#set_x_domain", function () {
+ it("set the x_domain", function () {
+ ContributorsGraph.set_x_domain(20)
+ expect(ContributorsGraph.prototype.x_domain).toEqual(20)
+ })
+ })
+
+ describe("#set_y_domain", function () {
+ it("sets the y_domain", function () {
+ ContributorsGraph.set_y_domain([{commits: 30}])
+ expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30])
+ })
+ })
+
+ describe("#init_x_domain", function () {
+ it("sets the initial x_domain", function () {
+ ContributorsGraph.init_x_domain([{date: "2013-01-31"}, {date: "2012-01-31"}])
+ expect(ContributorsGraph.prototype.x_domain).toEqual(["2012-01-31", "2013-01-31"])
+ })
+ })
+
+ describe("#init_y_domain", function () {
+ it("sets the initial y_domain", function () {
+ ContributorsGraph.init_y_domain([{commits: 30}])
+ expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30])
+ })
+ })
+
+ describe("#init_domain", function () {
+ it("calls init_x_domain and init_y_domain", function () {
+ spyOn(ContributorsGraph, "init_x_domain")
+ spyOn(ContributorsGraph, "init_y_domain")
+ ContributorsGraph.init_domain()
+ expect(ContributorsGraph.init_x_domain).toHaveBeenCalled()
+ expect(ContributorsGraph.init_y_domain).toHaveBeenCalled()
+ })
+ })
+
+ describe("#set_dates", function () {
+ it("sets the dates", function () {
+ ContributorsGraph.set_dates("2013-12-01")
+ expect(ContributorsGraph.prototype.dates).toEqual("2013-12-01")
+ })
+ })
+
+ describe("#set_x_domain", function () {
+ it("sets the instance's x domain using the prototype's x_domain", function () {
+ ContributorsGraph.prototype.x_domain = 20
+ var instance = new ContributorsGraph()
+ instance.x = d3.time.scale().range([0, 100]).clamp(true)
+ spyOn(instance.x, 'domain')
+ instance.set_x_domain()
+ expect(instance.x.domain).toHaveBeenCalledWith(20)
+ })
+ })
+
+ describe("#set_y_domain", function () {
+ it("sets the instance's y domain using the prototype's y_domain", function () {
+ ContributorsGraph.prototype.y_domain = 30
+ var instance = new ContributorsGraph()
+ instance.y = d3.scale.linear().range([100, 0]).nice()
+ spyOn(instance.y, 'domain')
+ instance.set_y_domain()
+ expect(instance.y.domain).toHaveBeenCalledWith(30)
+ })
+ })
+
+ describe("#set_domain", function () {
+ it("calls set_x_domain and set_y_domain", function () {
+ var instance = new ContributorsGraph()
+ spyOn(instance, 'set_x_domain')
+ spyOn(instance, 'set_y_domain')
+ instance.set_domain()
+ expect(instance.set_x_domain).toHaveBeenCalled()
+ expect(instance.set_y_domain).toHaveBeenCalled()
+ })
+ })
+
+ describe("#set_data", function () {
+ it("sets the data", function () {
+ var instance = new ContributorsGraph()
+ instance.set_data("20")
+ expect(instance.data).toEqual("20")
+ })
+ })
+})
+
+describe("ContributorsMasterGraph", function () {
+
+ describe("#process_dates", function () {
+ it("gets and parses dates", function () {
+ var graph = new ContributorsMasterGraph()
+ var data = 'random data here'
+ spyOn(graph, 'parse_dates')
+ spyOn(graph, 'get_dates').andReturn("get")
+ spyOn(ContributorsGraph,'set_dates').andCallThrough()
+ graph.process_dates(data)
+ expect(graph.parse_dates).toHaveBeenCalledWith(data)
+ expect(graph.get_dates).toHaveBeenCalledWith(data)
+ expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get")
+ })
+ })
+
+ describe("#get_dates", function () {
+ it("plucks the date field from data collection", function () {
+ var graph = new ContributorsMasterGraph()
+ var data = [{date: "2013-01-01"}, {date: "2012-12-15"}]
+ expect(graph.get_dates(data)).toEqual(["2013-01-01", "2012-12-15"])
+ })
+ })
+
+ describe("#parse_dates", function () {
+ it("parses the dates", function () {
+ var graph = new ContributorsMasterGraph()
+ var parseDate = d3.time.format("%Y-%m-%d").parse
+ var data = [{date: "2013-01-01"}, {date: "2012-12-15"}]
+ var correct = [{date: parseDate(data[0].date)}, {date: parseDate(data[1].date)}]
+ graph.parse_dates(data)
+ expect(data).toEqual(correct)
+ })
+ })
+
+
+})
--- /dev/null
+describe("ContributorsStatGraphUtil", function () {
+
+ describe("#parse_log", function () {
+ it("returns a correctly parsed log", function () {
+ var fake_log = [
+ {author: "Karlo Soriano", date: "2013-05-09", additions: 471},
+ {author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1},
+ {author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3},
+ {author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3}]
+
+ var correct_parsed_log = {
+ total: [
+ {date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
+ {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
+ by_author:
+ [
+ {
+ author: "Karlo Soriano",
+ "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
+ },
+ {
+ author: "Dmitriy Zaporozhets",
+ "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
+ }
+ ]
+ }
+ expect(ContributorsStatGraphUtil.parse_log(fake_log)).toEqual(correct_parsed_log)
+ })
+ })
+
+ describe("#store_data", function () {
+
+ var fake_entry = {author: "Karlo Soriano", date: "2013-05-09", additions: 471}
+ var fake_total = {}
+ var fake_by_author = {}
+
+ it("calls #store_commits", function () {
+ spyOn(ContributorsStatGraphUtil, 'store_commits')
+ ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author)
+ expect(ContributorsStatGraphUtil.store_commits).toHaveBeenCalled()
+ })
+
+ it("calls #store_additions", function () {
+ spyOn(ContributorsStatGraphUtil, 'store_additions')
+ ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author)
+ expect(ContributorsStatGraphUtil.store_additions).toHaveBeenCalled()
+ })
+
+ it("calls #store_deletions", function () {
+ spyOn(ContributorsStatGraphUtil, 'store_deletions')
+ ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author)
+ expect(ContributorsStatGraphUtil.store_deletions).toHaveBeenCalled()
+ })
+
+ })
+
+ describe("#store_commits", function () {
+ var fake_total = "fake_total"
+ var fake_by_author = "fake_by_author"
+
+ it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
+ spyOn(ContributorsStatGraphUtil, 'add')
+ ContributorsStatGraphUtil.store_commits(fake_total, fake_by_author)
+ expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "commits", 1], ["fake_by_author", "commits", 1]])
+ })
+ })
+
+ describe("#add", function () {
+ it("adds 1 to current test_field in collection", function () {
+ var fake_collection = {test_field: 10}
+ ContributorsStatGraphUtil.add(fake_collection, "test_field", 1)
+ expect(fake_collection.test_field).toEqual(11)
+ })
+
+ it("inits and adds 1 if test_field in collection is not defined", function () {
+ var fake_collection = {}
+ ContributorsStatGraphUtil.add(fake_collection, "test_field", 1)
+ expect(fake_collection.test_field).toEqual(1)
+ })
+ })
+
+ describe("#store_additions", function () {
+ var fake_entry = {additions: 10}
+ var fake_total= "fake_total"
+ var fake_by_author = "fake_by_author"
+ it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
+ spyOn(ContributorsStatGraphUtil, 'add')
+ ContributorsStatGraphUtil.store_additions(fake_entry, fake_total, fake_by_author)
+ expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "additions", 10], ["fake_by_author", "additions", 10]])
+ })
+ })
+
+ describe("#store_deletions", function () {
+ var fake_entry = {deletions: 10}
+ var fake_total= "fake_total"
+ var fake_by_author = "fake_by_author"
+ it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
+ spyOn(ContributorsStatGraphUtil, 'add')
+ ContributorsStatGraphUtil.store_deletions(fake_entry, fake_total, fake_by_author)
+ expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "deletions", 10], ["fake_by_author", "deletions", 10]])
+ })
+ })
+
+ describe("#add_date", function () {
+ it("adds a date field to the collection", function () {
+ var fake_date = "2013-10-02"
+ var fake_collection = {}
+ ContributorsStatGraphUtil.add_date(fake_date, fake_collection)
+ expect(fake_collection[fake_date].date).toEqual("2013-10-02")
+ })
+ })
+
+ describe("#add_author", function () {
+ it("adds an author field to the collection", function () {
+ var fake_author = "Author"
+ var fake_collection = {}
+ ContributorsStatGraphUtil.add_author(fake_author, fake_collection)
+ expect(fake_collection[fake_author].author).toEqual("Author")
+ })
+ })
+
+ describe("#get_total_data", function () {
+ it("returns the collection sorted via specified field", function () {
+ var fake_parsed_log = {
+ total: [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
+ {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
+ by_author:[
+ {
+ author: "Karlo Soriano",
+ "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
+ },
+ {
+ author: "Dmitriy Zaporozhets",
+ "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
+ }
+ ]};
+ var correct_total_data = [{date: "2013-05-08", commits: 3},
+ {date: "2013-05-09", commits: 1}];
+ expect(ContributorsStatGraphUtil.get_total_data(fake_parsed_log, "commits")).toEqual(correct_total_data)
+ })
+ })
+
+ describe("#pick_field", function () {
+ it("returns the collection with only the specified field and date", function () {
+ var fake_parsed_log_total = [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
+ {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}];
+ ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits")
+ var correct_pick_field_data = [{date: "2013-05-09", commits: 1},{date: "2013-05-08", commits: 3}];
+ expect(ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits")).toEqual(correct_pick_field_data)
+ })
+ })
+
+ describe("#get_author_data", function () {
+ it("returns the log by author sorted by specified field", function () {
+ var fake_parsed_log = {
+ total: [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
+ {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
+ by_author:[
+ {
+ author: "Karlo Soriano",
+ "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
+ },
+ {
+ author: "Dmitriy Zaporozhets",
+ "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
+ }
+ ]}
+ var correct_author_data = [{author:"Dmitriy Zaporozhets",dates:{"2013-05-08":3},deletions:7,additions:54,"commits":3},
+ {author:"Karlo Soriano",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1}]
+ expect(ContributorsStatGraphUtil.get_author_data(fake_parsed_log, "commits")).toEqual(correct_author_data)
+ })
+ })
+
+ describe("#parse_log_entry", function () {
+ it("adds the corresponding info from the log entry to the author", function () {
+ var fake_log_entry = { author: "Karlo Soriano",
+ "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
+ }
+ var correct_parsed_log = {author:"Karlo Soriano",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1}
+ expect(ContributorsStatGraphUtil.parse_log_entry(fake_log_entry, 'commits', null)).toEqual(correct_parsed_log)
+ })
+ })
+
+ describe("#in_range", function () {
+ var date = "2013-05-09"
+ it("returns true if date_range is null", function () {
+ expect(ContributorsStatGraphUtil.in_range(date, null)).toEqual(true)
+ })
+ it("returns true if date is in range", function () {
+ var date_range = [new Date("2013-01-01"), new Date("2013-12-12")]
+ expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(true)
+ })
+ it("returns false if date is not in range", function () {
+ var date_range = [new Date("1999-12-01"), new Date("2000-12-01")]
+ expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(false)
+ })
+ })
+
+
+})
\ No newline at end of file
--- /dev/null
+describe("StatGraph", function () {
+
+ describe("#get_log", function () {
+ it("returns log", function () {
+ StatGraph.log = "test";
+ expect(StatGraph.get_log()).toBe("test");
+ });
+ });
+
+ describe("#set_log", function () {
+ it("sets the log", function () {
+ StatGraph.set_log("test");
+ expect(StatGraph.log).toBe("test");
+ })
+ })
+
+});
\ No newline at end of file
--- /dev/null
+# src_files
+#
+# Return an array of filepaths relative to src_dir to include before jasmine specs.
+# Default: []
+#
+# EXAMPLE:
+#
+# src_files:
+# - lib/source1.js
+# - lib/source2.js
+# - dist/**/*.js
+#
+src_files:
+ - assets/application.js
+
+# stylesheets
+#
+# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
+# Default: []
+#
+# EXAMPLE:
+#
+# stylesheets:
+# - css/style.css
+# - stylesheets/*.css
+#
+stylesheets:
+ - stylesheets/**/*.css
+
+# helpers
+#
+# Return an array of filepaths relative to spec_dir to include before jasmine specs.
+# Default: ["helpers/**/*.js"]
+#
+# EXAMPLE:
+#
+# helpers:
+# - helpers/**/*.js
+#
+helpers:
+ - helpers/**/*.js
+
+# spec_files
+#
+# Return an array of filepaths relative to spec_dir to include.
+# Default: ["**/*[sS]pec.js"]
+#
+# EXAMPLE:
+#
+# spec_files:
+# - **/*[sS]pec.js
+#
+spec_files:
+ - '**/*[sS]pec.js'
+
+# src_dir
+#
+# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
+# Default: project root
+#
+# EXAMPLE:
+#
+# src_dir: public
+#
+src_dir:
+
+# spec_dir
+#
+# Spec directory path. Your spec_files must be returned relative to this path.
+# Default: spec/javascripts
+#
+# EXAMPLE:
+#
+# spec_dir: spec/javascripts
+#
+spec_dir: spec/javascripts
--- /dev/null
+#Use this file to set/override Jasmine configuration options
+#You can remove it if you don't need it.
+#This file is loaded *after* jasmine.yml is interpreted.
+#
+#Example: using a different boot file.
+#Jasmine.configure do |config|
+# @config.boot_dir = '/absolute/path/to/boot_dir'
+# @config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] }
+#end
+#
+
--- /dev/null
+# == Schema Information
+#
+# Table name: snippets
+#
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# author_id :integer not null
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# file_name :string(255)
+# expires_at :datetime
+#
+
+require 'spec_helper'
+
+describe ProjectSnippet do
+ describe "Associations" do
+ it { should belong_to(:project) }
+ end
+
+ describe "Mass assignment" do
+ it { should_not allow_mass_assignment_of(:project_id) }
+ end
+
+ describe "Validation" do
+ it { should validate_presence_of(:project) }
+ end
+end
it { should have_many(:milestones).dependent(:destroy) }
it { should have_many(:users_projects).dependent(:destroy) }
it { should have_many(:notes).dependent(:destroy) }
- it { should have_many(:snippets).dependent(:destroy) }
+ it { should have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) }
it { should have_many(:deploy_keys_projects).dependent(:destroy) }
it { should have_many(:deploy_keys) }
it { should have_many(:hooks).dependent(:destroy) }
describe Snippet do
describe "Associations" do
- it { should belong_to(:project) }
it { should belong_to(:author).class_name('User') }
it { should have_many(:notes).dependent(:destroy) }
end
describe "Mass assignment" do
it { should_not allow_mass_assignment_of(:author_id) }
- it { should_not allow_mass_assignment_of(:project_id) }
end
describe "Validation" do
it { should validate_presence_of(:author) }
- it { should validate_presence_of(:project) }
it { should validate_presence_of(:title) }
it { should ensure_length_of(:title).is_within(0..255) }
describe User do
describe "Associations" do
it { should have_one(:namespace) }
+ it { should have_many(:snippets).class_name('Snippet').dependent(:destroy) }
it { should have_many(:users_projects).dependent(:destroy) }
it { should have_many(:groups) }
it { should have_many(:keys).dependent(:destroy) }
ActiveRecord::Base.observers.enable(:user_observer)
@user = create :user
@project = create :project, namespace: @user.namespace
+ @project_2 = create :project # Grant MASTER access to the user
+ @project_3 = create :project # Grant DEVELOPER access to the user
+
+ UsersProject.add_users_into_projects(
+ [@project_2.id], [@user.id], UsersProject::MASTER
+ )
+ UsersProject.add_users_into_projects(
+ [@project_3.id], [@user.id], UsersProject::DEVELOPER
+ )
end
it { @user.authorized_projects.should include(@project) }
+ it { @user.authorized_projects.should include(@project_2) }
+ it { @user.authorized_projects.should include(@project_3) }
it { @user.owned_projects.should include(@project) }
+ it { @user.owned_projects.should_not include(@project_2) }
+ it { @user.owned_projects.should_not include(@project_3) }
it { @user.personal_projects.should include(@project) }
+ it { @user.personal_projects.should_not include(@project_2) }
+ it { @user.personal_projects.should_not include(@project_3) }
+
+ # master_projects doesn't check creator/namespace.
+ # In real case the users_projects relation will certainly be assigned
+ # when the project is created.
+ it { @user.master_projects.should_not include(@project) }
+ it { @user.master_projects.should include(@project_2) }
+ it { @user.master_projects.should_not include(@project_3) }
end
describe 'groups' do
it { @user.owned_groups.should == [@group] }
end
+ describe 'teams' do
+ before do
+ ActiveRecord::Base.observers.enable(:user_observer)
+ @admin = create :user, admin: true
+ @user1 = create :user
+ @user2 = create :user
+ @team = create :user_team, owner: @user1
+ end
+
+ it { @admin.authorized_teams.should == [@team] }
+ it { @user1.authorized_teams.should == [@team] }
+ it { @user2.authorized_teams.should be_empty }
+ it { @admin.should be_can(:manage_user_team, @team) }
+ it { @user1.should be_can(:manage_user_team, @team) }
+ it { @user2.should_not be_can(:manage_user_team, @team) }
+ end
+
describe 'namespaced' do
before do
ActiveRecord::Base.observers.enable(:user_observer)
let!(:project) { create(:project, namespace: user.namespace ) }
let!(:issue) { create(:issue, project: project, author: user) }
let!(:merge_request) { create(:merge_request, project: project, author: user) }
- let!(:snippet) { create(:snippet, project: project, author: user) }
+ let!(:snippet) { create(:project_snippet, project: project, author: user) }
let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) }
let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) }
let(:admin) { create(:admin) }
let!(:project) { create(:project_with_code, creator_id: user.id) }
let!(:hook) { create(:project_hook, project: project, url: "http://example.com") }
- let!(:snippet) { create(:snippet, author: user, project: project, title: 'example') }
+ let!(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
end
end
+ describe "GET /projects/:id/events" do
+ it "should return a project events" do
+ get api("/projects/#{project.id}/events", user)
+ response.status.should == 200
+ json_event = json_response.first
+
+ json_event['action_name'].should == 'joined'
+ json_event['project_id'].to_i.should == project.id
+ end
+
+ it "should return a 404 error if not found" do
+ get api("/projects/42/events", user)
+ response.status.should == 404
+ json_response['message'].should == '404 Not Found'
+ end
+
+ it "should return a 404 error if user is not a member" do
+ other_user = create(:user)
+ get api("/projects/#{project.id}/events", other_user)
+ response.status.should == 404
+ end
+ end
+
describe "GET /projects/:id/members" do
it "should return project team members" do
get api("/projects/#{project.id}/members", user)
end
end
+ describe "GET /projects/:id/repository/tree" do
+ context "authorized user" do
+ before { project.team << [user2, :reporter] }
+
+ it "should return project commits" do
+ get api("/projects/#{project.id}/repository/tree", user)
+ response.status.should == 200
+
+ json_response.should be_an Array
+ json_response.first['name'].should == 'app'
+ json_response.first['type'].should == 'tree'
+ json_response.first['mode'].should == '040000'
+ end
+ end
+
+ context "unauthorized user" do
+ it "should not return project commits" do
+ get api("/projects/#{project.id}/repository/tree")
+ response.status.should == 401
+ end
+ end
+ end
+
describe "GET /projects/:id/repository/commits/:sha/blob" do
it "should get the raw file contents" do
get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.md", user)
it "to #logs_tree" do
get("/gitlabhq/refs/stable/logs_tree").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable')
+ get("/gitlabhq/refs/feature%2345/logs_tree").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'feature#45')
+ get("/gitlabhq/refs/feature%2B45/logs_tree").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'feature+45')
get("/gitlabhq/refs/stable/logs_tree/foo/bar/baz").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz')
+ get("/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'feature#45', path: 'foo/bar/baz')
+ get("/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'feature+45', path: 'foo/bar/baz')
get("/gitlab/gitlabhq/refs/stable/logs_tree/files.scss").should route_to('refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'files.scss')
end
end
# project_snippet GET /:project_id/snippets/:id(.:format) snippets#show
# PUT /:project_id/snippets/:id(.:format) snippets#update
# DELETE /:project_id/snippets/:id(.:format) snippets#destroy
-describe SnippetsController, "routing" do
+describe Project::SnippetsController, "routing" do
it "to #raw" do
- get("/gitlabhq/snippets/1/raw").should route_to('snippets#raw', project_id: 'gitlabhq', id: '1')
+ get("/gitlabhq/snippets/1/raw").should route_to('projects/snippets#raw', project_id: 'gitlabhq', id: '1')
end
- it_behaves_like "RESTful project resources" do
- let(:controller) { 'snippets' }
+ it "to #index" do
+ get("/gitlabhq/snippets").should route_to("projects/snippets#index", project_id: 'gitlabhq')
+ end
+
+ it "to #create" do
+ post("/gitlabhq/snippets").should route_to("projects/snippets#create", project_id: 'gitlabhq')
+ end
+
+ it "to #new" do
+ get("/gitlabhq/snippets/new").should route_to("projects/snippets#new", project_id: 'gitlabhq')
+ end
+
+ it "to #edit" do
+ get("/gitlabhq/snippets/1/edit").should route_to("projects/snippets#edit", project_id: 'gitlabhq', id: '1')
+ end
+
+ it "to #show" do
+ get("/gitlabhq/snippets/1").should route_to("projects/snippets#show", project_id: 'gitlabhq', id: '1')
+ end
+
+ it "to #update" do
+ put("/gitlabhq/snippets/1").should route_to("projects/snippets#update", project_id: 'gitlabhq', id: '1')
+ end
+
+ it "to #destroy" do
+ delete("/gitlabhq/snippets/1").should route_to("projects/snippets#destroy", project_id: 'gitlabhq', id: '1')
end
end
end
end
-describe GraphController, "routing" do
+describe NetworkController, "routing" do
+ it "to #show" do
+ get("/gitlabhq/network/master").should route_to('network#show', project_id: 'gitlabhq', id: 'master')
+ get("/gitlabhq/network/master.json").should route_to('network#show', project_id: 'gitlabhq', id: 'master', format: "json")
+ end
+end
+
+describe GraphsController, "routing" do
it "to #show" do
- get("/gitlabhq/graph/master").should route_to('graph#show', project_id: 'gitlabhq', id: 'master')
- get("/gitlabhq/graph/master.json").should route_to('graph#show', project_id: 'gitlabhq', id: 'master', format: "json")
+ get("/gitlabhq/graphs/master").should route_to('graphs#show', project_id: 'gitlabhq', id: 'master')
end
end
end
end
+# snippets GET /snippets(.:format) snippets#index
+# POST /snippets(.:format) snippets#create
+# new_snippet GET /snippets/new(.:format) snippets#new
+# edit_snippet GET /snippets/:id/edit(.:format) snippets#edit
+# snippet GET /snippets/:id(.:format) snippets#show
+# PUT /snippets/:id(.:format) snippets#update
+# DELETE /snippets/:id(.:format) snippets#destroy
+describe SnippetsController, "routing" do
+ it "to #user_index" do
+ get("/s/User").should route_to('snippets#user_index', username: 'User')
+ end
+
+ it "to #raw" do
+ get("/snippets/1/raw").should route_to('snippets#raw', id: '1')
+ end
+
+ it "to #index" do
+ get("/snippets").should route_to('snippets#index')
+ end
+
+ it "to #create" do
+ post("/snippets").should route_to('snippets#create')
+ end
+
+ it "to #new" do
+ get("/snippets/new").should route_to('snippets#new')
+ end
+
+ it "to #edit" do
+ get("/snippets/1/edit").should route_to('snippets#edit', id: '1')
+ end
+
+ it "to #show" do
+ get("/snippets/1").should route_to('snippets#show', id: '1')
+ end
+
+ it "to #update" do
+ put("/snippets/1").should route_to('snippets#update', id: '1')
+ end
+
+ it "to #destroy" do
+ delete("/snippets/1").should route_to('snippets#destroy', id: '1')
+ end
+end
+
# help GET /help(.:format) help#index
# help_permissions GET /help/permissions(.:format) help#permissions
# help_workflow GET /help/workflow(.:format) help#workflow