This commit is contained in:
cxt 2015-10-28 10:18:02 +08:00
commit 86354028ed
197 changed files with 8575 additions and 2125 deletions

View File

@ -7,6 +7,7 @@ unless RUBY_PLATFORM =~ /w32/
end
gem 'grack', path:'./lib/grack'
gem 'gitlab', path: 'lib/gitlab-cli'
gem 'rest-client'
gem "mysql2", "= 0.3.18"
gem 'redis-rails'
@ -45,7 +46,7 @@ group :development, :test do
gem 'pry-stack_explorer'
if RUBY_PLATFORM =~ /darwin/
gem 'puma'
end
end
end
gem 'rspec-rails', '~> 3.0'

View File

@ -38,7 +38,8 @@ class ApplicationController < ActionController::Base
protect_from_forgery
def handle_unverified_request
super
cookies.delete(autologin_cookie_name)
raise(ActionController::InvalidAuthenticityToken)
# cookies.delete(autologin_cookie_name)
end
before_filter :find_first_page

View File

@ -227,6 +227,8 @@ class AttachmentsController < ApplicationController
format.js
elsif @attachment.container.is_a?(Message)
format.html { redirect_to_referer_or new_board_message_path(@attachment.container) }
elsif @attachment.container.is_a?(BlogComment)
format.html { redirect_to_referer_or user_blog_blog_comment_path(:user_id=>@attachment.container.author.id,:blog_id=>@attachment.container.blog_id,:id=>@attachment.container.id)}
elsif @course.nil?
format.html { redirect_to_referer_or forum_memo_path(@attachment.container.forum, @attachment.container) }
else

View File

@ -0,0 +1,121 @@
class BlogCommentsController < ApplicationController
include ApplicationHelper
before_filter :find_user
def index
end
def create
if User.current.logged?
@article = BlogComment.new
@article.author = User.current
@article.blog_id = params[:blog_id]
@article.safe_attributes = params[:blog_comment]
if request.post?
@article.save_attachments(params[:attachments])
if @article.save
# 更新kindeditor上传的图片资源所有者
# if params[:asset_id]
# ids = params[:asset_id].split(',')
# update_kindeditor_assets_owner ids,@article.id,OwnerTypeHelper::BLOGCOMMENT
# end
render_attachment_warning_if_needed(@article)
else
end
redirect_to user_blogs_path(:user_id=>params[:user_id])
else
respond_to do |format|
format.html {
render :layout => 'new_base_user'
}
end
end
else
redirect_to signin_path
end
end
def new
respond_to do |format|
format.html {render :layout=>'new_base_user'}
end
end
def show
@article = BlogComment.find(params[:id])
respond_to do |format|
format.html {render :layout=>'new_base_user'}
end
end
def update
@article = BlogComment.find(params[:id])
@article.safe_attributes = params[:blog_comment]
@article.save_attachments(params[:attachments])
if @article.save
render_attachment_warning_if_needed(@article)
else
end
redirect_to user_blog_blog_comment_path(:user_id=>params[:user_id],:blog_id=>params[:blog_id],:id=>params[:id])
end
def destroy
@article = BlogComment.find(params[:id])
if @article.parent_id.nil? #如果是文章被删,那么跳转到用户博客界面
@article.children.delete
@article.delete
redirect_to user_blogs_path(:user_id=>User.current)
else
root = @article.root
@article.delete
redirect_to user_blog_blog_comment_path(:user_id=>root.author_id,:blog_id=>root.blog_id,:id=>root.id)
end
end
def edit
@article = BlogComment.find(params[:id])
respond_to do |format|
format.html {render :layout=>'new_base_user'}
end
end
def quote
@blogComment = BlogComment.find(params[:id])
@subject = @blogComment.title
@subject = "RE: #{@subject}" unless @subject.starts_with?('RE:')
@content = "> #{ll(Setting.default_language, :text_user_wrote, @blogComment.author.realname)}\n> "
@temp = BlogComment.new
@temp.content = "<blockquote>#{ll(Setting.default_language, :text_user_wrote, @blogComment.author.realname)} <br/>#{@blogComment.content.html_safe}</blockquote>".html_safe
respond_to do | format|
format.js
end
end
#回复
def reply
@article = BlogComment.find(params[:id]).root
@quote = params[:quote][:quote]
@blogComment = BlogComment.new
@blogComment.author = User.current
@blogComment.blog = Blog.find(params[:blog_id])
params[:blog_comment][:sticky] = params[:blog_comment][:sticky] || 0
params[:blog_comment][:locked] = params[:blog_comment][:locked] || 0
@blogComment.safe_attributes = params[:blog_comment]
@blogComment.content = @quote + @blogComment.content
@blogComment.title = "RE: #{@article.title}" unless params[:blog_comment][:title]
@article.children << @blogComment
@user_activity_id = params[:user_activity_id]
attachments = Attachment.attach_files(@blogComment, params[:attachments])
render_attachment_warning_if_needed(@blogComment)
#@article.save
# redirect_to user_blogs_path(:user_id=>params[:user_id])
respond_to do |format|
format.html { redirect_to user_blog_blog_comment_path(:user_id=>@article.author_id,:blog_id=>@article.blog_id,:id=>@article)}
format.js
end
rescue Exception => e #如果上面的代码执行发生异常就捕获
flash[:notice] = e.message
end
private
def find_user
@user = User.find(params[:user_id])
end
end

View File

@ -0,0 +1,47 @@
class BlogsController < ApplicationController
before_filter :find_blog,:except => [:index,:create,:new]
before_filter :find_user
def index
@articls = @user.blog.articles
@article = BlogComment.new
respond_to do |format|
format.html {render :layout=>'new_base_user'}
end
end
def create
end
def new
end
def show
end
def update
end
def destory
end
def edit
end
private
def find_blog
if params[:blog_id]
@blog = Blog.find(params[:blog_id])
else
render_404
end
if @blog.nil?
#如果某个user的blog不存在那么就创建一条
@blog = Blog.create(:name=>User.find(params[:id]).realname ,
:description=>'',
:author_id=>params[:id])
end
end
def find_user
@user = User.find(params[:user_id])
end
end

View File

@ -31,13 +31,11 @@ class CoursesController < ApplicationController
def join
if User.current.logged?
# if params[:role] == 10
cs = CoursesService.new
@user = User.current
join = cs.join_course params,@user
@state = join[:state]
@course = join[:course]
Mailer.run.join_course_request(@course, User.current, params[:role])
# else
# @course = Course.find_by_id params[:object_id]
# CourseMessage.create(:user_id => @course.tea_id, :course_id => @course.id, :viewed => false,:content=> params[:role],:course_message_id=>User.current.id,:course_message_type=>'JoinCourseRequest')
@ -290,14 +288,11 @@ class CoursesController < ApplicationController
def export_course_member_excel
@all_members = student_homework_score(0,0,0,"desc")
filename="#{@course.teacher.lastname.to_s + @course.teacher.firstname.to_s }_#{@course.name}_#{@course.time.to_s + @course.term}#{l(:excel_member_list)}";
# 如果是ie 需要转码
if(/trident/.match(request.env["HTTP_USER_AGENT"]) != nil)
filename= URI::encode(filename)
end
respond_to do |format|
format.xls {
send_data(member_to_xls(@all_members,@course.course_groups), :type => "text/excel;charset=utf-8; header=present",
:filename => "#{filename}.xls")
:filename => filename_for_content_disposition("#{filename}.xls"))
}
end
end

View File

@ -42,8 +42,8 @@ class GanttsController < ApplicationController
respond_to do |format|
format.html { render :action => "show", :layout => 'base_projects' }#by young
format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
format.pdf { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") }
format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => filename_for_content_disposition("#{basename}.png")) } if @gantt.respond_to?('to_image')
format.pdf { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => filename_for_content_disposition("#{basename}.pdf") ) }
end
end
end

View File

@ -33,7 +33,7 @@ class HomeworkAttachController < ApplicationController
format.js
format.xls {
send_data(homework_to_xls(@all_homework_list), :type => "text/excel;charset=utf-8; header=present",
:filename => "#{@course.teacher.lastname.to_s + @course.teacher.firstname}_#{@course.name}_#{@course.time.to_s + @course.term}_#{@bid.name}#{l(:excel_homework_list)}(#{l(:excel_not_rated)}).xls")
:filename => filename_for_content_disposition("#{@course.teacher.lastname.to_s + @course.teacher.firstname}_#{@course.name}_#{@course.time.to_s + @course.term}_#{@bid.name}#{l(:excel_homework_list)}(#{l(:excel_not_rated)}).xls") )
}
end
end
@ -66,7 +66,7 @@ class HomeworkAttachController < ApplicationController
format.js
format.xls {
send_data(homework_to_xls(all_homework_list), :type => "text/excel;charset=utf-8; header=present",
:filename => "#{@course.teacher.lastname.to_s + @course.teacher.firstname}_#{@course.name}_#{@course.time.to_s + @course.term}_#{@bid.name}#{l(:excel_homework_list)}(#{l(:excel_been_rated)}).xls")
:filename => filename_for_content_disposition("#{@course.teacher.lastname.to_s + @course.teacher.firstname}_#{@course.name}_#{@course.time.to_s + @course.term}_#{@bid.name}#{l(:excel_homework_list)}(#{l(:excel_been_rated)}).xls") )
}
end
end
@ -101,7 +101,7 @@ class HomeworkAttachController < ApplicationController
format.js
format.xls {
send_data(homework_to_xls(all_homework_list), :type => "text/excel;charset=utf-8; header=present",
:filename => "#{@course.teacher.lastname.to_s + @course.teacher.firstname}_#{@course.name}_#{@course.time.to_s + @course.term}_#{@bid.name}#{l(:excel_homework_list)}.xls")
:filename => filename_for_content_disposition("#{@course.teacher.lastname.to_s + @course.teacher.firstname}_#{@course.name}_#{@course.time.to_s + @course.term}_#{@bid.name}#{l(:excel_homework_list)}.xls") )
}
end
end

View File

@ -151,7 +151,7 @@ class IssuesController < ApplicationController
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
format.pdf {
pdf = issue_to_pdf(@issue, :journals => @journals)
send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
send_data(pdf, :type => 'application/pdf', :filename => filename_for_content_disposition("#{@project.identifier}-#{@issue.id}.pdf") )
}
end
end

View File

@ -413,7 +413,7 @@ class PollController < ApplicationController
respond_to do |format|
format.xls {
send_data(poll_to_xls(poll_questions), :type => "text/excel;charset=utf-8; header=present",
:filename => "#{@poll.polls_name}.xls")
:filename => filename_for_content_disposition("#{@poll.polls_name}.xls") )
}
end
end

View File

@ -29,12 +29,12 @@ class RepositoriesController < ApplicationController
menu_item :repository
menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers]
default_search_scope :changesets
before_filter :find_project_by_project_id, :only => [:new, :create, :newrepo]
before_filter :find_repository, :only => [:edit, :update, :destroy, :committers]
before_filter :find_project_repository, :except => [:new, :create, :newcreate, :edit, :update, :destroy, :committers, :newrepo]
before_filter :find_project_repository, :except => [:new, :create, :newcreate, :edit, :update, :destroy, :committers, :newrepo,:to_gitlab]
before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
before_filter :authorize , :except => [:newrepo,:newcreate,:fork]
before_filter :authorize , :except => [:newrepo,:newcreate,:fork, :to_gitlab]
accept_rss_auth :revisions
# hidden repositories filter // 隐藏代码过滤器
before_filter :check_hidden_repo, :only => [:show, :stats, :revisions, :revision, :diff ]
@ -42,7 +42,7 @@ class RepositoriesController < ApplicationController
include RepositoriesHelper
helper :project_score
#@root_path = RepositoriesHelper::ROOT_PATH
rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
def new
@ -62,8 +62,8 @@ class RepositoriesController < ApplicationController
end
end
def newrepo
scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
@repository = Repository.factory(scm)
@ -76,23 +76,23 @@ class RepositoriesController < ApplicationController
render :layout => 'base_projects'
end
end
def fork
@repository_url = params[:repository_url]
# @repository.url
# system "htpasswd -mb "+@root_path+"user.passwd "+params[:repository][:identifier]+" "+@upasswd
# system "echo -e '"+params[:project_id]+"-"+params[:repository][:identifier]+"-write:"+
# " "+params[:repository][:identifier]+"' >> "+@root_path+"group.passwd"
system "git clone --bare "+@repository_url
# " "+params[:repository][:identifier]+"' >> "+@root_path+"group.passwd"
system "git clone --bare "+@repository_url
# system "mv "+@project_path+"/hooks/post-update{.sample,}"
# system "chmod a+x "+@project_path+"/hooks/post-update"
# system "."+@project_path+"/hooks/post-update"
# system "echo -e 'Allow from all \n Order Deny,Allow \n "+
# "<Limit PUT POST DELETE PROPPATCH MKCOL COPY MOVE LOCK UNLOCK> \n"+
# "Require group "+params[:project_id]+"-"+params[:repository][:identifier]+"-write \n "+
# "</Limit> \n ' >>"+
# @project_path+"/.htaccess"
# "<Limit PUT POST DELETE PROPPATCH MKCOL COPY MOVE LOCK UNLOCK> \n"+
# "Require group "+params[:project_id]+"-"+params[:repository][:identifier]+"-write \n "+
# "</Limit> \n ' >>"+
# @project_path+"/.htaccess"
flash[:notice] = l(:label_notice_fork_successed)
@repositories = @project.repositories
render :action => 'show', :layout => 'base_projects'
@ -115,77 +115,36 @@ update
}
def create
if params[:repository_scm].to_s == 'Gitlab'
# add by nwb
# 增加对gitlab版本库的支持
attrs = pickup_extra_info
@repository = Repository.factory('Git')
@repository.safe_attributes = params[:repository]
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end
@repository.project = @project
if request.post? && @repository.save
redirect_to settings_project_url(@project, :tab => 'repositories')
else
redirect_to settings_project_url(@project, :tab => 'repositories')
end
else # 原逻辑
##xianbo
@root_path=RepositoriesHelper::ROOT_PATH
@repository_name=User.current.login.to_s+"/"+params[:repository][:identifier]+".git"
@project_path=@root_path+"htdocs/"+@repository_name
@repository_tag=params[:repository][:upassword] || params[:repository][:password]
@repo_name=User.current.login.to_s+"_"+params[:repository][:identifier]
logger.info "htpasswd -mb "+@root_path+"htdocs/user.passwd "+@repo_name+": "+@repository_tag
logger.info "the value of create repository"+@root_path+": "+@repository_name+": "+@project_path+": "+@repo_name
attrs = pickup_extra_info
if((@repository_tag!="")&&params[:repository_scm]=="Git")
params[:repository][:url]=@project_path
end
###xianbo
@repository = Repository.factory(params[:repository_scm])
@repository.safe_attributes = params[:repository]
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end
@repository.project = @project
if request.post? && @repository.save
if(params[:repository_scm]=="Git")
system "htpasswd -mb "+@root_path+"htdocs/user.passwd "+@repo_name+" "+@repository_tag
system "echo -e '"+@repo_name+"-write:"+
" "+@repo_name+"' >> "+@root_path+"htdocs/group.passwd"
system "mkdir "+@root_path+"htdocs/"+User.current.login.to_s
system "git init --bare "+@project_path
system "mv "+@project_path+"/hooks/post-update{.sample,}"
system "chmod a+x "+@project_path+"/hooks/post-update"
system "echo -e 'Allow from all \n Order Deny,Allow \n "+
"<Limit PUT POST DELETE PROPPATCH MKCOL COPY MOVE LOCK UNLOCK> \n"+
"Require group "+@repo_name+"-write \n "+
"</Limit> \n ' >> "+
@root_path+"htdocs/"+ @repository_name+"/.htaccess"
system "cd "+@project_path+" ;git update-server-info"
File.open(@project_path+"/hooks/post-update", "w+") do |f|
f.write(HOOK_TEMPLATE)
end
@repository.update_attributes(:login => User.current.login.to_s)
end
redirect_to settings_project_url(@project, :tab => 'repositories',:repository_error_message=>@repository.errors.full_messages)
else if(@repository_tag.blank?)
#render :action => 'newrepo', :layout =>'base_projects'
redirect_to settings_project_url(@project, :tab => 'repositories',:repository => "pswd_is_null",:repository_error_message=>@repository.errors.full_messages)
else
redirect_to settings_project_url(@project, :tab => 'repositories',:repository => @repository,:repository_error_message=>@repository.errors.full_messages)
end
end
attrs = pickup_extra_info
@repository = Repository.factory('Git')
@repository.safe_attributes = params[:repository]
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end
@repository.project = @project
@repository.type = 'Repository::Gitlab'
@repository.url = @repository.identifier
if request.post? && @repository.save
g = ::Gitlab.client
gid = @project.owner.gid
gproject = g.create_project(@repository.identifier,
path: @repository.identifier,
description: @project.description,
wiki_enabled: false,
wall_enabled: false,
issues_enabled: false,
snippets_enabled: false,
public: false,
user_id: gid
)
@project.gpid = gproject.id
@project.save!
redirect_to settings_project_url(@project, :tab => 'repositories')
else
redirect_to settings_project_url(@project, :tab => 'repositories',:repository_error_message=>@repository.errors.full_messages)
end
end
def edit
end
@ -228,15 +187,17 @@ update
# Build a hash with repository usernames as keys and corresponding user ids as values
@repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_url(@project, :tab => 'repositories')
respond_to do |format|
format.html{
render :layout => "base_projects"
}
end
elsif request.get?
respond_to do |format|
format.html{
render :layout => "base_projects"
}
end
respond_to do |format|
format.html{
render :layout => "base_projects"
}
end
end
end
@ -247,6 +208,16 @@ update
redirect_to settings_project_url(@project, :tab => 'repositories')
end
def to_gitlab
@project = Project.find(params[:project_id])
@repository = Repository.find(params[:id])
s = Trustie::Gitlab::Sync.new
s.sync_project(@project, path: params[:repo_name], import_url: @repository.url)
@repository.type = 'Repository::Gitlab'
@repository.save
redirect_to :controller => 'repositories', :action => 'show', :id => @project.id, to: 'gitlab'
end
def show
## TODO: the below will move to filter, done.
if !User.current.member_of?(@project)
@ -256,19 +227,23 @@ update
end
end
if params[:to] == 'gitlab'
g = Gitlab.client
g.post('/session', body: {email: User.current.mail, password: User.current.hashed_password})
redirect_to "http://192.168.41.130:3000/gitlab-org/gitlab-shell/tree/master"
unless @repository.gitlab?
# redirect_to to_gitlab_project_repository_path(@project, @repository)
render :to_gitlab
return
end
#if( !User.current.member_of?(@project) || @project.hidden_repo)
@repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
# g = Gitlab.client
# project = g.project(20)
# rr = g.trees(project.id, @path)
# r = g.get ("/projects/#{@project}/repository/tree")
# :name, :path, :kind, :size, :lastrev, :changeset
@entries = @repository.entries(@path, @rev)
# @trees = g.trees(project, @path)
@changeset = @repository.find_changeset_by_name(@rev)
#@project_path_cut = RepositoriesHelper::PROJECT_PATH_CUT
#@ip = RepositoriesHelper::REPO_IP_ADDRESS
@ -276,15 +251,22 @@ update
@entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
else
#Modified by young
# (show_error_not_found; return) unless @entries
# (show_error_not_found; return) unless @entries
@changesets = @repository.latest_changesets(@path, @rev)
@changesets_count = @repository.latest_changesets(@path, @rev).count
@changesets_latest_coimmit = @changesets.first
@properties = @repository.properties(@path, @rev)
@repositories = @project.repositories
@course_tag = params[:course]
project_path_cut = RepositoriesHelper::PROJECT_PATH_CUT
ip = RepositoriesHelper::REPO_IP_ADDRESS
@repos_url = "http://"+@repository.login.to_s+"_"+@repository.identifier.to_s+"@"+ip.to_s+
@repository.url.slice(project_path_cut, @repository.url.length).to_s
gitlab_address = RepositoriesHelper::REPO_GITLAB_ADDRESS
if @repository.type.to_s=="Repository::Gitlab"
@repos_url = "http://"+gitlab_address.to_s+"/"+@project.owner.to_s+"/"+@repository.identifier+"."+"git"
else
@repos_url = "http://"+@repository.login.to_s+"_"+@repository.identifier.to_s+"@"+ip.to_s+
@repository.url.slice(project_path_cut, @repository.url.length).to_s
end
if @course_tag == 1
render :action => 'show', :layout => 'base_courses'
else
@ -310,10 +292,10 @@ update
per_page_option,
params['page']
@changesets = @repository.changesets.
limit(@changeset_pages.per_page).
offset(@changeset_pages.offset).
includes(:user, :repository, :parents).
all
limit(@changeset_pages.per_page).
offset(@changeset_pages.offset).
includes(:user, :repository, :parents).
all
respond_to do |format|
format.html { render :layout => 'base_projects' }
@ -327,6 +309,7 @@ update
def entry
entry_and_raw(false)
render :layout => 'base_projects'
end
def entry_and_raw(is_raw)
@ -339,8 +322,8 @@ update
@content = @repository.cat(@path, @rev)
(show_error_not_found; return) unless @content
if is_raw ||
(@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) ||
! is_entry_text_data?(@content, @path)
(@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) ||
! is_entry_text_data?(@content, @path)
# Force the download
send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
send_type = Redmine::MimeType.of(@path)
@ -423,8 +406,8 @@ update
filename = "changeset_r#{@rev}"
filename << "_r#{@rev_to}" if @rev_to
send_data @diff.join, :filename => "#{filename}.diff",
:type => 'text/x-patch',
:disposition => 'attachment'
:type => 'text/x-patch',
:disposition => 'attachment'
else
@diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
@diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
@ -435,7 +418,7 @@ update
User.current.preference.save
end
@cache_key = "repositories/diff/#{@repository.id}/" +
Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
unless read_fragment(@cache_key)
@diff = @repository.diff(@path, @rev, @rev_to)
unless @diff
@ -462,16 +445,16 @@ update
def graph
data = nil
case params[:graph]
when "commits_per_month"
data = graph_commits_per_month(@repository)
when "commits_per_author"
data = graph_commits_per_author(@repository)
when "author_commits_per_month"
data = graph_author_commits_per_month(@repository)
when "author_commits_six_month"
data = author_commits_six_month(@repository)
when "author_code_six_months"
data = author_code_six_month(@repository)
when "commits_per_month"
data = graph_commits_per_month(@repository)
when "commits_per_author"
data = graph_commits_per_author(@repository)
when "author_commits_per_month"
data = graph_author_commits_per_month(@repository)
when "author_commits_six_month"
data = author_commits_six_month(@repository)
when "author_code_six_months"
data = author_code_six_month(@repository)
end
if data
headers["Content-Type"] = "image/svg+xml"
@ -551,14 +534,14 @@ update
@date_from = @date_to << 11
@date_from = Date.civil(@date_from.year, @date_from.month, 1)
commits_by_day = Changeset.count(
:all, :group => :commit_date,
:conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
:all, :group => :commit_date,
:conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
commits_by_month = [0] * 12
commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
changes_by_day = Change.count(
:all, :group => :commit_date, :include => :changeset,
:conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
:all, :group => :commit_date, :include => :changeset,
:conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
changes_by_month = [0] * 12
changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
@ -566,26 +549,26 @@ update
12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
graph = SVG::Graph::Bar.new(
:height => 300,
:width => 600,
:fields => fields.reverse,
:stack => :side,
:scale_integers => true,
:step_x_labels => 2,
:show_data_values => true,
:graph_title => l(:label_commits_per_month),
:show_graph_title => true
:height => 300,
:width => 600,
:fields => fields.reverse,
:stack => :side,
:scale_integers => true,
:step_x_labels => 2,
:show_data_values => true,
:graph_title => l(:label_commits_per_month),
:show_graph_title => true
)
# 具状图
graph.add_data(
:data => commits_by_month[0..11].reverse,
:title => l(:label_revision_plural)
:data => commits_by_month[0..11].reverse,
:title => l(:label_revision_plural)
)
graph.add_data(
:data => changes_by_month[0..11].reverse,
:title => l(:label_change_plural)
:data => changes_by_month[0..11].reverse,
:title => l(:label_change_plural)
)
graph.burn
@ -610,23 +593,23 @@ update
fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
graph = SVG::Graph::BarHorizontal.new(
:height => 400,
:width => 600,
:fields => fields,
:stack => :side,
:scale_integers => true,
:show_data_values => true,
:rotate_y_labels => false,
:graph_title => l(:label_commits_per_author),
:show_graph_title => true
:height => 400,
:width => 600,
:fields => fields,
:stack => :side,
:scale_integers => true,
:show_data_values => true,
:rotate_y_labels => false,
:graph_title => l(:label_commits_per_author),
:show_graph_title => true
)
graph.add_data(
:data => commits_data,
:title => l(:label_revision_plural)
:data => commits_data,
:title => l(:label_revision_plural)
)
graph.add_data(
:data => changes_data,
:title => l(:label_change_plural)
:data => changes_data,
:title => l(:label_change_plural)
)
graph.burn
end
@ -637,7 +620,7 @@ update
@date_from = @date_to << 12
@date_from = Date.civil(@date_from.year, @date_from.month, @date_from.day)
commits_by_author = Changeset.count(:all, :group => :committer,
:conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
:conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
commits_by_author = commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}.last(25)
fields = commits_by_author.collect {|r| r.first}

View File

@ -84,7 +84,7 @@ class StoresController < ApplicationController
respond_to do |format|
format.xls {
send_data(homework_to_xls(attachments), :type => "text/excel;charset=utf-8; header=present",
:filename => "#{l(:label_file_lost_list)}.xls")
:filename => filename_for_content_disposition("#{l(:label_file_lost_list)}.xls") )
}
end
end

View File

@ -82,14 +82,14 @@ class WikiController < ApplicationController
@content = @page.content_for_version(params[:version])
if User.current.allowed_to?(:export_wiki_pages, @project)
if params[:format] == 'pdf'
send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => filename_for_content_disposition("#{@page.title}.pdf") )
return
elsif params[:format] == 'html'
export = render_to_string :action => 'export', :layout => false
send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
send_data(export, :type => 'text/html', :filename => filename_for_content_disposition("#{@page.title}.html"))
return
elsif params[:format] == 'txt'
send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
send_data(@content.text, :type => 'text/plain', :filename => filename_for_content_disposition("#{@page.title}.txt") )
return
end
end
@ -286,7 +286,7 @@ class WikiController < ApplicationController
send_data(export, :type => 'text/html', :filename => "wiki.html")
}
format.pdf {
send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => filename_for_content_disposition("#{@project.identifier}.pdf") )
}
end
end

View File

@ -0,0 +1,2 @@
module BlogCommentsHelper
end

View File

@ -0,0 +1,2 @@
module BlogsHelper
end

View File

@ -7,4 +7,5 @@ module OwnerTypeHelper
BID = 6
JOURNALSFORMESSAGE = 7
HOMEWORKCOMMON = 8
BLOGCOMMENT=9
end

View File

@ -25,6 +25,7 @@ module RepositoriesHelper
end
PROJECT_PATH_CUT = 40
REPO_IP_ADDRESS = Setting.host_repository
REPO_GITLAB_ADDRESS = "git.trustie.net"
def format_revision(revision)
if revision.respond_to? :format_identifier
@ -34,6 +35,10 @@ module RepositoriesHelper
end
end
def repository_creater rep
repository_creater = User.find_by_login(rep.login) unless rep.login.nil?
end
def truncate_at_line_break(text, length = 255)
if text
text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...')

16
app/models/blog.rb Normal file
View File

@ -0,0 +1,16 @@
class Blog < ActiveRecord::Base
# attr_accessible :title, :body
include Redmine::SafeAttributes
belongs_to :user
has_many :articles, :class_name => 'BlogComment', :conditions => "#{BlogComment.table_name}.parent_id IS NULL ", :order => "#{BlogComment.table_name}.created_on DESC"
has_many :blog_comments, :dependent => :destroy, :order => "#{BlogComment.table_name}.created_on DESC"
belongs_to :last_comment, :class_name => 'BlogComment', :foreign_key => :last_comment_id
acts_as_tree :dependent => :nullify
#acts_as_list :scope => '(user_id = #{user_id} AND parent_id #{user_id ? = "#{parent_id}" : "IS NULL"})'
acts_as_watchable
validates :name, presence: true, length: {maximum: 30}
validates :description, length: {maximum: 255}
safe_attributes 'name', 'description'
end

View File

@ -0,0 +1,24 @@
class BlogComment < ActiveRecord::Base
# attr_accessible :title, :body
include Redmine::SafeAttributes
belongs_to :blog
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
acts_as_tree :counter_cache => :comments_count, :order => "#{BlogComment.table_name}.sticky desc ,#{BlogComment.table_name}.created_on ASC"
acts_as_attachable
belongs_to :last_reply, :class_name => 'BlogComment', :foreign_key => 'last_comment_id'
acts_as_watchable
validates_presence_of :title, :content
validates_length_of :title, :maximum => 255
#validate :cannot_reply_to_locked_comment, :on => :create
safe_attributes 'title', 'content',"sticky", "locked"
def deleted_attach_able_by? user
(user && user.logged? && (self.author == user) ) || user.admin?
end
def project
end
end

View File

@ -34,6 +34,7 @@ class Member < ActiveRecord::Base
after_destroy :delete_ivite_list
def role
end

View File

@ -35,8 +35,11 @@ class MemberRole < ActiveRecord::Base
!inherited_from.nil?
end
include Trustie::Gitlab::ManageMember
private
def remove_member_if_empty
if member.roles.empty?
member.destroy

View File

@ -770,7 +770,8 @@ class Project < ActiveRecord::Base
'project_type',
'dts_test',
'attachmenttype',
'enterprise_name'
'enterprise_name',
'gpid'
@ -853,6 +854,10 @@ class Project < ActiveRecord::Base
end
end
def owner
User.find(self.user_id)
end
private
def after_parent_changed(parent_was)
@ -1167,5 +1172,7 @@ class Project < ActiveRecord::Base
:forge_act_id => self.id,:forge_act_type => "ProjectCreateInfo")
fa.save!
end
end

View File

@ -57,6 +57,11 @@ class Repository < ActiveRecord::Base
safe_attributes 'url',
:if => lambda {|repository, user| repository.new_record?}
def gitlab?
self.type == 'Repository::Gitlab'
end
def repo_create_validation
unless Setting.enabled_scm.include?(self.class.name.demodulize)
errors.add(:type, :invalid)

View File

@ -0,0 +1,259 @@
#coding=utf-8
require 'redmine/scm/adapters/git_adapter'
class Repository::Gitlab < Repository
attr_protected :root_url
validates_presence_of :url
def self.human_attribute_name(attribute_key_name, *args)
attr_name = attribute_key_name.to_s
if attr_name == "url"
attr_name = "path_to_repository"
end
super(attr_name, *args)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::GitlabAdapter
end
def self.scm_name
'Gitlab'
end
def commits(authors, start_date, end_date, branch='master')
scm.commits(authors, start_date, end_date,branch).map {|commit|
[commit[:author], commit[:num]]
}
end
def report_last_commit
extra_report_last_commit
end
def extra_report_last_commit
return false if extra_info.nil?
v = extra_info["extra_report_last_commit"]
return false if v.nil?
v.to_s != '0'
end
def supports_directory_revisions?
true
end
def supports_revision_graph?
true
end
def repo_log_encoding
'UTF-8'
end
# Returns the identifier for the given git changeset
def self.changeset_identifier(changeset)
changeset.scmid
end
# Returns the readable identifier for the given git changeset
def self.format_changeset_identifier(changeset)
changeset.revision[0, 8]
end
def branches
scm.branches
end
def tags
scm.tags
end
def default_branch
scm.default_branch
rescue Exception => e
logger.error "git: error during get default branch: #{e.message}"
nil
end
def find_changeset_by_name(name)
if name.present?
changesets.where(:revision => name.to_s).first ||
changesets.where('scmid LIKE ?', "#{name}%").first
end
end
def entries(path=nil, identifier=nil)
entries = scm.entries(path, identifier, :report_last_commit => extra_report_last_commit)
load_entries_changesets(entries)
entries
end
# With SCMs that have a sequential commit numbering,
# such as Subversion and Mercurial,
# Redmine is able to be clever and only fetch changesets
# going forward from the most recent one it knows about.
#
# However, Git does not have a sequential commit numbering.
#
# In order to fetch only new adding revisions,
# Redmine needs to save "heads".
#
# In Git and Mercurial, revisions are not in date order.
# Redmine Mercurial fixed issues.
# * Redmine Takes Too Long On Large Mercurial Repository
# http://www.redmine.org/issues/3449
# * Sorting for changesets might go wrong on Mercurial repos
# http://www.redmine.org/issues/3567
#
# Database revision column is text, so Redmine can not sort by revision.
# Mercurial has revision number, and revision number guarantees revision order.
# Redmine Mercurial model stored revisions ordered by database id to database.
# So, Redmine Mercurial model can use correct ordering revisions.
#
# Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
# to get limited revisions from old to new.
# But, Git 1.7.3.4 does not support --reverse with -n or --skip.
#
# The repository can still be fully reloaded by calling #clear_changesets
# before fetching changesets (eg. for offline resync)
def fetch_changesets
scm_brs = branches
return if scm_brs.nil? || scm_brs.empty?
h1 = extra_info || {}
h = h1.dup
repo_heads = scm_brs.map{ |br| br.scmid }
h["heads"] ||= []
prev_db_heads = h["heads"].dup
if prev_db_heads.empty?
prev_db_heads += heads_from_branches_hash
end
return if prev_db_heads.sort == repo_heads.sort
h["db_consistent"] ||= {}
if changesets.count == 0
h["db_consistent"]["ordering"] = 1
merge_extra_info(h)
self.save
elsif ! h["db_consistent"].has_key?("ordering")
h["db_consistent"]["ordering"] = 0
merge_extra_info(h)
self.save
end
save_revisions(prev_db_heads, repo_heads)
end
def save_revisions(prev_db_heads, repo_heads)
h = {}
opts = {}
opts[:reverse] = true
opts[:excludes] = prev_db_heads
opts[:includes] = repo_heads
revisions = scm.revisions('', nil, nil, opts)
return if revisions.blank?
# Make the search for existing revisions in the database in a more sufficient manner
#
# Git branch is the reference to the specific revision.
# Git can *delete* remote branch and *re-push* branch.
#
# $ git push remote :branch
# $ git push remote branch
#
# After deleting branch, revisions remain in repository until "git gc".
# On git 1.7.2.3, default pruning date is 2 weeks.
# So, "git log --not deleted_branch_head_revision" return code is 0.
#
# After re-pushing branch, "git log" returns revisions which are saved in database.
# So, Redmine needs to scan revisions and database every time.
#
# This is replacing the one-after-one queries.
# Find all revisions, that are in the database, and then remove them from the revision array.
# Then later we won't need any conditions for db existence.
# Query for several revisions at once, and remove them from the revisions array, if they are there.
# Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits).
# If there are no revisions (because the original code's algorithm filtered them),
# then this part will be stepped over.
# We make queries, just if there is any revision.
limit = 100
offset = 0
revisions_copy = revisions.clone # revisions will change
while offset < revisions_copy.size
recent_changesets_slice = changesets.find(
:all,
:conditions => [
'scmid IN (?)',
revisions_copy.slice(offset, limit).map{|x| x.scmid}
]
)
# Subtract revisions that redmine already knows about
recent_revisions = recent_changesets_slice.map{|c| c.scmid}
revisions.reject!{|r| recent_revisions.include?(r.scmid)}
offset += limit
end
revisions.each do |rev|
transaction do
# There is no search in the db for this revision, because above we ensured,
# that it's not in the db.
save_revision(rev)
end
end
h["heads"] = repo_heads.dup
merge_extra_info(h)
self.save
end
private :save_revisions
def save_revision(rev)
parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
changeset = Changeset.create(
:repository => self,
:revision => rev.identifier,
:scmid => rev.scmid,
:committer => rev.author,
:committed_on => rev.time,
:comments => rev.message,
:parents => parents
)
unless changeset.new_record?
rev.paths.each { |change| changeset.create_change(change) }
end
changeset
end
private :save_revision
def heads_from_branches_hash
h1 = extra_info || {}
h = h1.dup
h["branches"] ||= {}
h['branches'].map{|br, hs| hs['last_scmid']}
end
def latest_changesets(path,rev,limit=10)
revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
return [] if revisions.nil? || revisions.empty?
changesets.find(
:all,
:conditions => [
"scmid IN (?)",
revisions.map!{|c| c.scmid}
]
)
end
def clear_extra_info_of_changesets
return if extra_info.nil?
v = extra_info["extra_report_last_commit"]
write_attribute(:extra_info, nil)
h = {}
h["extra_report_last_commit"] = v
merge_extra_info(h)
self.save
end
private :clear_extra_info_of_changesets
end

View File

@ -77,6 +77,27 @@ class Role < ActiveRecord::Base
self.givable[3..5]
end
GUEST = 10
REPORTER = 20
DEVELOPER = 30
MASTER = 40
OWNER = 50
def to_gitlab_role
case self.position
when 1,2
GUEST
when 5
REPORTER
when 4
DEVELOPER
when 3
MASTER
else
GUEST
end
end
# Copies attributes from another role, arg can be an id or a Role
def copy_from(arg, options={})
return unless arg.present?

View File

@ -93,6 +93,7 @@ class User < Principal
has_many :changesets, :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
has_one :blog, :class_name => 'Blog', :foreign_key => "author_id"
has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
belongs_to :auth_source
belongs_to :ucourse, :class_name => 'Course', :foreign_key => :id #huang
@ -208,7 +209,7 @@ class User < Principal
validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
validate :validate_password_length
# validates_email_realness_of :mail
before_create :set_mail_notification
before_create :set_mail_notification, :sync_gitlab_user
before_save :update_hashed_password
before_destroy :remove_references_before_destroy
# added by fq
@ -255,6 +256,18 @@ class User < Principal
# count = self.journals_for_messages(:conditions => ["status=? and is_readed = ? " ,1, 0]).count
end
def blog
@blog = Blog.where("author_id = #{self.id}").all[0]
if @blog.nil?
#如果某个user的blog不存在那么就创建一条并且跳转
@blog = Blog.create(:name=>(User.find(self.id).realname),
:description=>'',
:author_id=>self.id)
@blog.save
end
@blog
end
# 查询指派给我的缺陷记录
def count_new_issue_assign_to
self.issue_assigns
@ -345,7 +358,7 @@ class User < Principal
name
end
## end
#added by nie
def count_new_journal_reply
count = self.journal_reply.count
@ -1066,6 +1079,17 @@ class User < Principal
end
private
def sync_gitlab_user
user = self
g = Gitlab.client
u = g.get("/users?search=#{user.mail}").first
unless u
u = g.create_user(user.mail, user.password, name: user.show_name, username: user.login, confirm: "true")
self.gid = u.id
puts "create user #{user.login}"
end
end
end

View File

@ -326,26 +326,26 @@ class CoursesService
@state = 1
end
else
if params[:course_password] == course.password
if params[:role] == "10"
members = []
members << Member.new(:role_ids => [10], :user_id => current_user.id)
course.members << members
StudentsForCourse.create(:student_id => current_user.id, :course_id => params[:object_id])
@state = 0
if params[:course_password] == course.password
if params[:role] == "10"
members = []
members << Member.new(:role_ids => [10], :user_id => current_user.id)
course.members << members
StudentsForCourse.create(:student_id => current_user.id, :course_id => params[:object_id])
@state = 0
else
#如果已经发送过消息了,那么就要给个提示
if CourseMessage.where("course_message_type = 'JoinCourseRequest' and user_id = #{course.tea_id} and content = #{params[:role]} and course_message_id = #{User.current.id} and course_id = #{course.id} and status = 0").count != 0
@state = 7
else
Mailer.run.join_course_request(course, User.current, params[:role])
CourseMessage.create(:user_id => course.tea_id, :course_id => course.id, :viewed => false,:content=> params[:role],:course_message_id=>User.current.id,:course_message_type=>'JoinCourseRequest',:status=>0)
@state = 6
end
end
else
#如果已经发送过消息了,那么就要给个提示
if CourseMessage.where("course_message_type = 'JoinCourseRequest' and user_id = #{course.tea_id} and content = #{params[:role]} and course_message_id = #{User.current.id} and course_id = #{course.id} and status = 0").count != 0
@state = 7
else
Mailer.run.join_course_request(course, User.current, params[:role])
CourseMessage.create(:user_id => course.tea_id, :course_id => course.id, :viewed => false,:content=> params[:role],:course_message_id=>User.current.id,:course_message_type=>'JoinCourseRequest',:status=>0)
@state = 6
end
@state = 1
end
else
@state = 1
end
end
end
else

View File

@ -0,0 +1,41 @@
<div class="mt10 fl" style="width: 600px">
<% for attachment in attachments %>
<!--<p style="width: 100%;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;">-->
<!--<div style="max-width:55%;white-space: nowrap; overflow: hidden; text-overflow: ellipsis;float: left;">-->
<!--<span title="<%#= attachment.filename%>" id = "attachment_">-->
<% if options[:length] %>
<span class="pic_files fl "></span>
<%= link_to_short_attachment attachment, :class => 'fl FilesName02', :download => true,:length => options[:length] -%>
<a class="fl FilesName02"> (<%= number_to_human_size attachment.filesize , :precision => 0 %>)</a>
<% if options[:deletable] %>
<%#= link_to image_tag('delete.png'), attachment_path(attachment),
:data => {:confirm => l(:text_are_you_sure)},
:method => :delete,
:class => 'delete',
#:remote => true,
#:id => "attachments_" + attachment.id.to_s,
:title => l(:button_delete) %>
<span class="pic_del fl "> <a href="<%=attachment_path(attachment) %>" onclick="confirm(<%=l(:text_are_you_sure) %>)" data-method="delete"> </a></span>
<% end %>
<div class="cl"></div>
<% else %>
<span class="pic_files fl "></span>
<%= link_to_short_attachment attachment, :class => 'fl FilesName02', :download => true, :length => 45 -%>
<a href="javascript:void(0);" class="fl FilesName02"> (<%= number_to_human_size attachment.filesize , :precision => 0 %>)</a>
<% if options[:deletable] %>
<a href="<%=attachment_path(attachment) %>" onclick="confirm(<%=l(:text_are_you_sure) %>)" data-method="delete"> <span class="pic_del fl "></span> </a>
<% end %>
<div class="cl"></div>
<% end %>
<% end %>
<% if defined?(thumbnails) && thumbnails %>
<% images = attachments.select(&:thumbnailable?) %>
<% if images.any? %>
<div class="pro_pic mb10" width="100" height="73">
<% images.each do |attachment| %>
<div><%= thumbnail_tag(attachment) %></div>
<% end %>
</div>
<% end %>
<% end %>
</div>

View File

@ -0,0 +1,78 @@
<style type="text/css">
input.is_public,input.is_public_checkbox{height:12px;}
input.is_public_checkbox{margin-left:4px;margin-right:4px;}
</style>
<div class="fl">
<span id="attachments_fields" xmlns="http://www.w3.org/1999/html">
<% if defined?(container) && container && container.saved_attachments %>
<% container.attachments.each_with_index do |attachment, i| %>
<span id="attachments_p<%= i %>" class="attachment">
<%= text_field_tag("attachments[p#{i}][filename]", attachment.filename, :class => 'filename readonly', :readonly => 'readonly') %><%= text_field_tag("attachments[p#{i}][description]", attachment.description, :maxlength => 254, :placeholder => l(:label_optional_description), :class => 'description', :style => "display: inline-block;") %><span class="ispublic-label"><%= l(:field_is_public) %>:</span>
<%= check_box_tag("attachments[p#{i}][is_public_checkbox]", attachment.is_public, attachment.is_public == 1 ? true : false, :class => 'is_public') %>
<%= if attachment.id.nil?
#待补充代码
else
link_to('&nbsp;'.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload')
end
%>
<%#= render :partial => 'tags/tag', :locals => {:obj => attachment, :object_flag => "6"} %>
<%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.token}" %>
</span>
<div class="cl"></div>
<% end %>
<% container.saved_attachments.each_with_index do |attachment, i| %>
<span id="attachments_p<%= i %>" class="attachment">
<%= text_field_tag("attachments[p#{i}][filename]", attachment.filename, :class => 'filename readonly', :readonly => 'readonly') %>
<%= text_field_tag("attachments[p#{i}][description]", attachment.description, :maxlength => 254, :placeholder => l(:label_optional_description), :class => 'description', :style => "display: inline-block;") %>
<span class="ispublic-label"><%= l(:field_is_public) %>:</span>
<%= check_box_tag("attachments[p#{i}][is_public_checkbox]", attachment.is_public, attachment.is_public == 1 ? true : false, :class => 'is_public') %>
<%= if attachment.id.nil?
#待补充代码
else
link_to('&nbsp;'.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload')
end
%>
<%#= render :partial => 'tags/tag', :locals => {:obj => attachment, :object_flag => "6"} %>
<%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.token}" %>
</span>
<div class="cl"></div>
<% end %>
<% end %>
</span>
<div class="cl"></div>
<span class="add_attachment" style="font-weight:normal;">
<%#= button_tag "浏览", :type=>"button", :onclick=>"CompatibleSend();" %>
<!--%= link_to image_tag(),"javascript:void(0)", :onclick => "_file.click()"%-->
<%#= button_tag "文件浏览", :type=>"button", :onclick=>"$('#_file').click();",:onmouseover => 'this.focus()',:class => 'sub_btn' %>
<a href="javascript:void(0);" onclick="_file.click();" class="AnnexBtn fl mr15">上传附件</a>
<%= file_field_tag 'attachments[dummy][file]',
:id => '_file',
:class => 'file_selector',
:multiple => true,
:onchange => 'addInputFiles(this);',
:style => ie8? ? '' : 'display:none',
:data => {
:max_file_size => Setting.attachment_max_size.to_i.kilobytes,
:max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)),
:max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i,
:upload_path => uploads_path(:format => 'js', :project => container),
:description_placeholder => l(:label_optional_description),
:field_is_public => l(:field_is_public),
:are_you_sure => l(:text_are_you_sure),
:file_count => l(:label_file_count),
:delete_all_files => l(:text_are_you_sure_all)
} %>
<span id="upload_file_count">
<%= l(:label_no_file_uploaded) %>
</span>
(<%= l(:label_max_size) %>:
<%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)
</span>
<% content_for :header_tags do %>
<%= javascript_include_tag 'attachments' %>
<% end %>
</div>

View File

@ -0,0 +1,53 @@
<%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg' ,'blog' %>
<div class="resources mt10">
<div id="new_course_topic">
<div class="homepagePostBrief c_grey">
<div>
<input type="text" value="<%= article.title%>" name="blog_comment[title]" id="message_subject" class="InputBox w713" style="width: 720px !important;" maxlength="255" onkeyup="regexTopicSubject();" placeholder="发布文章,请先输入文章标题" " />
<p id="subjectmsg"></p>
</div>
<div id="topic_editor" style="display: block;">
<%if User.current.id == user.id%>
<div class="mt10">
<%= f.check_box :sticky%>
<%= label_tag 'message_sticky', l(:label_board_sticky) %>
<%= f.check_box :locked%>
<%= label_tag 'message_locked', l(:label_board_locked) %>
<div class="cl"></div>
</div>
<% end %>
<div class="mt10">
<div id="message_quote" class="wiki" style="width: 92%;word-break: break-all;word-wrap: break-word;margin-left: 40px;"></div>
<%= text_area :quote,:quote,:style => 'display:none' %>
<%= hidden_field_tag :asset_id,params[:asset_id],:required => false,:style => 'display:none' %>
<%= f.kindeditor :content,:editor_id => 'message_content_editor',
:owner_id => article.nil? ? 0: article.id,
:owner_type => OwnerTypeHelper::BLOGCOMMENT,
:width => '100%',
:height => 300,
:minHeight=>300,
:class => 'talk_text fl',
:input_html => { :id => 'message_content',
:class => 'talk_text fl',
:maxlength => 5000 }%>
<div class="cl"></div>
<p id="message_content_span"></p>
</div>
<div class="cl"></div>
<div class="mt10">
<div class="fl" id="topic_attachments">
<%= render :partial => 'blog_comments/blog_attachments', :locals => {:container => article} %>
</div>
</div>
<div class="cl"></div>
<div class="mt5">
<a href="javascript:void(0);" class="BlueCirBtnMini fr" onclick="submit_article();">确定</a>
<span class="fr mr10 mt3">或</span>
<a href="<%= user_blog_blog_comment_path(:user_id=>article.author.id,:blog_id=>article.blog_id,:id=>article.id)%>" class="fr mr10 mt3" >取消</a>
</div>
<div class="cl"></div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,62 @@
<%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg' ,'blog' %>
<div class="resources mt10">
<div id="new_course_topic">
<div class="homepagePostBrief c_grey">
<div>
<input type="text" name="blog_comment[title]" id="message_subject" class="InputBox w713" style="width: 720px !important;" maxlength="255" onfocus="$('#topic_editor').show()" onkeyup="regexTopicSubject();" placeholder="发布文章,请先输入文章标题" " >
<p id="subjectmsg"></p>
</div>
<div id="topic_editor" style="display: none;">
<%if User.current.id == user.id%>
<div class="mt10">
<%= f.check_box :sticky%>
<%= label_tag 'message_sticky', l(:label_board_sticky) %>
<%= f.check_box :locked%>
<%= label_tag 'message_locked', l(:label_board_locked) %>
<div class="cl"></div>
</div>
<% end %>
<div class="mt10">
<div id="message_quote" class="wiki" style="width: 92%;word-break: break-all;word-wrap: break-word;margin-left: 40px;"></div>
<%= text_area :quote,:quote,:style => 'display:none' %>
<%= hidden_field_tag :asset_id,params[:asset_id],:required => false,:style => 'display:none' %>
<%= f.kindeditor :content, :editor_id => 'message_content_editor',
:owner_id => article.nil? ? 0: article.id,
:owner_type => OwnerTypeHelper::BLOGCOMMENT,
:width => '100%',
:height => 300,
:minHeight=>300,
:class => 'talk_text fl',
:input_html => { :id => 'message_content',
:class => 'talk_text fl',
:maxlength => 5000 }%>
<div class="cl"></div>
<p id="message_content_span"></p>
</div>
<div class="cl"></div>
<div class="mt10">
<div class="fl" id="topic_attachments">
<%= render :partial => 'blog_comments/blog_attachments', :locals => {:container => article} %>
</div>
</div>
<div class="cl"></div>
<div class="mt5">
<a href="javascript:void(0);" class="BlueCirBtnMini fr" onclick="submit_article();">确定</a>
<span class="fr mr10 mt3">或</span>
<a href="javascript:void(0);" class="fr mr10 mt3" onclick="reset_article();">取消</a>
</div>
<div class="cl"></div>
</div>
</div>
<%#= render :partial => 'course_new_topic', :locals => {:f => f, :topic => @message} %>
<!--<li>
<div class="ml55 fl" nhname="toolbar_container"></div>
<a href="javascript:void(0);" nhname="cancelbtn" class="grey_btn fr ml10"><%#= l(:button_cancel) %></a>
<a href="javascript:void(0);" nhname="submitbtn" class="blue_btn fr " style="margin-left: 55px">
<%#= l(:button_submit)%>
</a>
<div class="cl"></div>
</li>-->
</div>
</div>

View File

@ -0,0 +1,35 @@
<%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg' %>
<li>
<div style="display: none ;" class="fl"><label><span class="c_red">*</span>&nbsp;<%= l(:field_subject) %>&nbsp;&nbsp;</label></div>
<div style="display: none;"><%= f.text_field :title, { size: 60, id: "message_subject",:class=>"talk_input w585 fl" }.merge({ hidden: "hidden"}) %></div>
<div class="cl"></div>
</li>
<li class="ml60 mb5">
<div class="cl"></div>
</li>
<li>
<div id="message_quote" class="wiki" style="width: 92%;word-break: break-all;word-wrap: break-word;margin-left: 40px;"></div>
<!--<label class="fl" >
<span class="c_red">*</span>&nbsp;
<%#= l(:field_description) %>&nbsp;&nbsp;
</label>-->
<%= text_area :quote,:quote,:style => 'display:none' %>
<%= hidden_field_tag :asset_id,params[:asset_id],:required => false,:style => 'display:none' %>
<input type="hidden" name="blog_comment[title]" value="RE:<%= article.title%>">
<input type="hidden" name="blog_comment[sticky]" value="0">
<input type="hidden" name="blog_comment[locked]" value="0">
<%= f.kindeditor :content, :editor_id => 'message_content_editor',
:width => '99%',
:height => 100,
:minHeight=>100,
:input_html => { :id => 'message_content',
:class => 'talk_text fl',
:maxlength => 5000 }%>
<div class="cl"></div>
<p id="message_content_span"></p>
</li>
<div class="cl"></div>
<li >
<div class="cl"></div>
</li>

View File

@ -0,0 +1,31 @@
<style type="text/css">
/*回复框*/
.ReplyToMessageInputContainer .ke-toolbar{display:none;width:400px;border:none;background:none;padding:0px 0px;}
.ReplyToMessageInputContainer .ke-toolbar-icon{line-height:26px;font-size:14px;padding-left:26px;}
.ReplyToMessageInputContainer .ke-toolbar-icon-url{background-image:url( /images/public_icon.png )}
.ReplyToMessageInputContainer .ke-outline{padding:0px 0px;line-height:26px;font-size:14px;}
.ReplyToMessageInputContainer .ke-icon-emoticons{background-position:0px -671px;width:50px;height:26px;}
.ReplyToMessageInputContainer .ke-icon-emoticons:hover{background-position:-79px -671px;width:50px;height:26px;}
.ReplyToMessageInputContainer .ke-outline{border:none;}
.ReplyToMessageInputContainer .ke-inline-block{display: none;}
.ReplyToMessageInputContainer .ke-container{float:left;}
</style>
<div class="ReplyToMessageContainer borderBottomNone"id="reply_to_message_<%= reply.id%>">
<div class="homepagePostReplyPortrait mr15 imageFuzzy" id="reply_image_<%= reply.id%>"><%= link_to image_tag(url_to_avatar(User.current), :width => "33", :height => "33"), user_path(User.current), :alt => "用户头像" %></div>
<div class="ReplyToMessageInputContainer mb10">
<div nhname='new_message_<%= reply.id%>'>
<%= form_for @blog_comment, :as => :reply, :url => {:controller => 'blog_comments',:action => 'reply', :id => @blogComment.id}, :html => {:multipart => true, :id => 'new_form'} do |f| %>
<input type="hidden" name="quote[quote]" id="quote_quote">
<input type="hidden" name="blog_comment[title]" id="reply_subject">
<textarea placeholder="有问题或有建议,请直接给我留言吧!" style="display: none" nhname='new_message_textarea_<%= reply.id%>' name="blog_comment[content]"></textarea>
<div nhname='toolbar_container_<%= reply.id%>' style="float:left; margin-left: 5px; padding-top:3px;"></div>
<a id="new_message_submit_btn_<%= reply.id%>" href="javascript:void(0)" class="blue_n_btn fr" style="display:none;margin-top:6px;">发送</a>
<div class="cl"></div>
<p nhname='contentmsg_<%= reply.id%>'></p>
<% end%>
</div>
<div class="cl"></div>
</div>
<div class="cl"></div>
</div>

View File

@ -0,0 +1,6 @@
<% if User.current.logged? && User.current.id == @user.id %>
<%= form_for @article, :url =>{:controller=>'blog_comments',:action => 'update',:user_id=>@user.id , :blog_id => @article.id},:method=>'PUT',
:html => {:nhname=>'form',:multipart => true, :id => 'message-form'} do |f| %>
<%= render :partial => 'blog_comments/edit', :locals => {:f => f, :article => @article, :edit_mode => true, :user => @user} %>
<% end %>
<% end %>

View File

@ -0,0 +1,10 @@
if($("#reply_message_<%= @blogComment.id%>").length > 0) {
$("#reply_message_<%= @blogComment.id%>").replaceWith("<%= escape_javascript(render :partial => 'simple_ke_reply_form', :locals => {:reply => @blogComment,:temp =>@temp,:subject =>@subject}) %>");
$(function(){
$('#reply_subject').val("<%= raw escape_javascript(@subject) %>");
$('#quote_quote').val("<%= raw escape_javascript(@temp.content.html_safe) %>");
init_activity_KindEditor_data(<%= @blogComment.id%>,null,"85%");
});
}else if($("#reply_to_message_<%= @blogComment.id%>").length >0) {
$("#reply_to_message_<%= @blogComment.id%>").replaceWith("<p id='reply_message_<%= @blogComment.id%>'></p>");
}

View File

@ -0,0 +1,2 @@
$("#user_activity_<%= @user_activity_id%>").replaceWith("<%= escape_javascript(render :partial => 'blogs/article', :locals => {:activity => @article,:user_activity_id =>@user_activity_id,:first_user_activity =>@first_user_activity,:page => @page}) %>");
init_activity_KindEditor_data(<%= @user_activity_id%>,"","87%");

View File

@ -0,0 +1,175 @@
<%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg',"init_activity_KindEditor",'blog' %>
<script type="text/javascript">
$(function(){
$("#RSide").removeAttr("id");
$("#Container").css("width","1000px");
});
</script>
<script>
function expand_reply(container,btnid){
var target = $(container).children();
var btn = $(btnid);
if(btn.data('init')=='0'){
btn.data('init',1);
btn.html('收起回复');
target.show();
}else{
btn.data('init',0);
btn.html('展开更多');
target.hide();
target.eq(0).show();
target.eq(1).show();
target.eq(2).show();
}
}
$(function() {
init_activity_KindEditor_data(<%= @article.id%>,null,"85%");
showNormalImage('message_description_<%= @article.id %>');
});
</script>
<div class="postRightContainer ml10" onmouseover="$('#message_setting_<%= @article.id%>').show();" onmouseout="$('#message_setting_<%= @article.id%>').hide();">
<div class="postThemeContainer">
<div class="postDetailPortrait">
<%= link_to image_tag(url_to_avatar(@article.author),:width=>50,:height => 50,:alt=>'图像' ),user_path(@article.author) %>
</div>
<div class="postThemeWrap">
<% if @article.author.id == User.current.id%>
<div class="homepagePostSetting" id="message_setting_<%= @article.id%>" style="display: none">
<ul>
<li class="homepagePostSettingIcon">
<ul class="homepagePostSettiongText">
<li>
<%= link_to(
l(:button_edit),
{:action => 'edit', :id => @article.id},
:class => 'postOptionLink'
) if User.current && User.current.id == @article.author.id %>
</li>
<li>
<%= link_to(
l(:button_delete),
{:action => 'destroy', :id => @article.id},
:method => :delete,
:data => {:confirm => l(:text_are_you_sure)},
:class => 'postOptionLink'
) if User.current && User.current.id == @article.author.id %>
</li>
</ul>
</li>
</ul>
</div>
<%end%>
<div class="postDetailTitle fl">
<a href="javascript:void(0);" class="f14 linkGrey4 fb" style="overflow:hidden;">主题: <%= @article.title%></a>
</div>
<div class="cl"></div>
<div class="postDetailCreater">
<% if @article.try(:author).try(:realname) == ' ' %>
<%= link_to @article.try(:author), user_path(@article.author,:host=>Setting.host_user), :class => "linkBlue2", :target=> "_blank" %>
<% else %>
<%= link_to @article.try(:author).try(:realname), user_path(@article.author,:host=>Setting.host_user), :class => "linkBlue2", :target=> "_blank" %>
<% end %>
</div>
<div class="postDetailDate mb5"><%= format_time( @article.created_on)%></div>
<div class="cl"></div>
<div class="memo-content upload_img break_word" id="message_description_<%= @article.id %>" style="word-break: break-all; word-wrap:break-word;margin-bottom: 0px !important;" >
<%= @article.content.html_safe%>
</div>
<div class="cl"></div>
<div class=" fl" style="width: 600px">
<%#= link_to_attachments_course @topic, :author => false %>
<% if @article.attachments.any?%>
<% options = {:author => true, :deletable => false} %>
<%= render :partial => 'blog_comments/attachments_links', :locals => {:attachments => @article.attachments, :options => options, :is_float => true} %>
<% end %>
</div>
</div>
<div class="cl"></div>
</div>
<div class="cl"></div>
<% count=0 %>
<% if @article.parent %>
<% count=@article.parent.children.count%>
<% else %>
<% count=@article.children.count%>
<% end %>
<div class="homepagePostReply">
<% unless count == 0 %>
<div class="homepagePostReplyBanner">
<div class="homepagePostReplyBannerCount">回复(<%=count %></div>
<div class="homepagePostReplyBannerTime"></div>
<!-- <div class="homepagePostReplyBannerMore">
<%# if @reply_count > 2%>
<a href="javascript:void(0);" class="replyGrey" id="reply_btn_<%#= @topic.id%>" onclick="expand_reply('#reply_div_<%#= @topic.id %>','#reply_btn_<%#= @topic.id%>')" data-count="<%#= @reply_count %>" data-init="0" >点击展开更多回复</a>
<%# end %>
</div>-->
</div>
<div class="" id="reply_div_<%= @article.id %>">
<%@article.children.reorder('created_on desc').each_with_index do |reply,i| %>
<script type="text/javascript">
$(function(){
showNormalImage('reply_message_description_<%= reply.id %>');
});
</script>
<div class="homepagePostReplyContainer" onmouseover="$('#reply_edit_menu_<%= reply.id%>').show();" onmouseout="$('#reply_edit_menu_<%= reply.id%>').hide();">
<div class="homepagePostReplyPortrait">
<%= link_to image_tag(url_to_avatar(reply.author), :width => 33,:height => 33), user_path(reply.author) %>
</div>
<div class="homepagePostReplyDes">
<div class="homepagePostReplyPublisher">
<% if reply.try(:author).try(:realname) == ' ' %>
<%= link_to reply.try(:author), user_path(reply.author_id,:host=>Setting.host_user), :class => "newsBlue mr10 f14" %>
<% else %>
<%= link_to reply.try(:author).try(:realname), user_path(reply.author_id,:host=>Setting.host_user), :class => "newsBlue mr10 f14" %>
<% end %>
</div>
<div class="homepagePostReplyContent upload_img break_word" id="reply_message_description_<%= reply.id %>">
<%= reply.content.html_safe%>
</div>
<div style="margin-top: -7px; margin-bottom: 5px">
<%= format_time(reply.created_on) %>
<div class="fr" id="reply_edit_menu_<%= reply.id%>" style="display: none">
<%= link_to(
l(:button_reply),
{:controller => 'blog_comments',:action => 'quote',:user_id=>reply.author_id,:blog_id=>reply.blog_id, :id => reply.id},
:remote => true,
:method => 'get',
:class => 'fr newsBlue',
:title => l(:button_reply)) if !@article.locked? && User.current.logged? %>
<%= link_to(
l(:button_delete),
{:controller => 'blog_comments',:action => 'destroy', :id => reply.id},
:method => :delete,
:class => 'fr newsGrey mr10',
:data => {:confirm => l(:text_are_you_sure)},
:title => l(:button_delete)
) if reply.author.id == User.current.id %>
</div>
</div>
<p id="reply_message_<%= reply.id%>"></p>
</div>
<div class="cl"></div>
</div>
<% end %>
</div>
<% end %>
<div class="cl"></div>
<% if !@article.locked? && User.current.logged?%>
<div class="talkWrapMsg" nhname="about_talk_reply">
<em class="talkWrapArrow"></em>
<div class="cl"></div>
<div class="talkConIpt ml5 mb10" id="reply<%= @article.id %>">
<%= form_for :blog_comment, :url => {:action => 'reply',:controller => 'blog_comments',:user_id=>@article.author.id,:blog_id=>@article.blog_id, :id => @article.id}, :html => {:multipart => true, :id => 'message_form'} do |f| %>
<%= render :partial => 'blog_comments/reply_form', :locals => {:f => f,:user=>@user,:article=>@article} %>
<%= link_to l(:button_cancel), "javascript:void(0)", :onclick => 'canel_message_replay();', :class => " grey_btn fr c_white mt10 mr5" %>
<%= link_to l(:button_submit), "javascript:void(0)", :onclick => 'submit_message_replay();', :class => "blue_btn fr c_white mt10", :style => "margin-right: 5px;" %>
<% end %>
<div class="cl"></div>
</div>
</div>
<% end %>
</div>
</div>

View File

@ -0,0 +1,145 @@
<div class="resources mt10" id="user_activity_<%= user_activity_id%>">
<div class="homepagePostBrief">
<div class="homepagePostPortrait">
<%= link_to image_tag(url_to_avatar(activity.author), :width => "50", :height => "50"), user_path(activity.author_id,:host=>Setting.host_user), :alt => "用户头像" %>
</div>
<div class="homepagePostDes">
<div class="homepagePostTo break_word mt-4">
<% if activity.try(:author).try(:realname) == ' ' %>
<%= link_to activity.try(:author), user_path(activity.author_id,:host=>Setting.host_user), :class => "newsBlue mr15" %>
<% else %>
<%= link_to activity.try(:author).try(:realname), user_path(activity.author_id,:host=>Setting.host_user), :class => "newsBlue mr15" %>
<% end %>
TO
<%= link_to activity.blog.name+" | 博客", user_blogs_path(:user_id=>activity.author_id,:host=>Setting.host_user), :class => "newsBlue ml15 mr5"%>
</div>
<div class="homepagePostTitle hidden m_w530 fl">
<% if activity.parent_id.nil? %> <!--+"(帖子标题)"-->
<%= link_to activity.title.to_s.html_safe, user_blog_blog_comment_path(:user_id=>activity.author_id, :blog_id=>activity.blog.id,:id=>activity), :class=> "postGrey" %>
<% else %>
<%= link_to activity.title.subject.to_s.html_safe, user_blog_blog_comment_path(:user_id=>activity.author_id, :blog_id=>activity.blog.id,:id=>activity), :class=> "postGrey"%>
<% end %>
</div>
<% if activity.sticky == 1%>
<span class="sticky_btn_cir ml10">置顶</span>
<% end%>
<% if activity.locked%>
<span class="locked_btn_cir ml10 fl" title="已锁定">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<% end%>
<div class="cl"></div>
<div class="homepagePostDate">
发帖时间:<%= format_time(activity.created_on) %>
</div>
<div class="homepagePostIntro break_word upload_img list_style" id="activity_description_<%= user_activity_id%>">
<% if activity.parent_id.nil? %>
<%= activity.content.to_s.html_safe%>
<% else %>
<%= activity.parent.content.to_s.html_safe%>
<% end %>
</div>
<div class="cl"></div>
<div class=" fl" style="width: 600px">
<% if activity.attachments.any?%>
<% options = {:author => true, :deletable => false } %>
<%= render :partial => 'blog_comments/attachments_links', :locals => {:attachments => activity.attachments, :options => options, :is_float => true} %>
<% end %>
</div>
<div class="homepagePostSetting" id="act-<%= user_activity_id %>" style="visibility: hidden">
<ul>
<li class="homepagePostSettingIcon">
<ul class="homepagePostSettiongText">
<li><a href="javascript:void(0);" class="postOptionLink">编辑</a></li>
<li><a href="javascript:void(0);" class="postOptionLink">复制</a></li>
<li><a href="javascript:void(0);" class="postOptionLink">删除</a></li>
</ul>
</li>
</ul>
</div>
</div>
<div class="cl"></div>
</div>
<% count=0 %>
<% if activity.parent %>
<% count=activity.parent.children.count%>
<% else %>
<% count=activity.children.count%>
<% end %>
<div class="homepagePostReply">
<div class="topBorder" style="display: <%= count<=0 && !activity.locked ? '': 'none' %>"></div>
<div class="homepagePostReplyBanner" style="display: <%= count>0 ? '': 'none' %>">
<div class="homepagePostReplyBannerCount" onclick="expand_reply_input('#reply_input_<%= user_activity_id %>');">回复(
<%= count %>
)</div>
<div class="homepagePostReplyBannerTime"><%#=format_date(activity.updated_on)%></div>
<%if count > 3 %>
<div class="homepagePostReplyBannerMore">
<a id="reply_btn_<%=user_activity_id%>" onclick="expand_reply('#reply_div_<%= user_activity_id %> li','#reply_btn_<%=user_activity_id%>')" data-count="<%= count %>" data-init="0" class=" replyGrey" href="javascript:void(0)" value="show_help" >
展开更多
</a>
</div>
<% end %>
</div>
<% activity= activity.parent ? activity.parent : activity%>
<% replies_all_i = 0 %>
<% if count > 0 %>
<div class="" id="reply_div_<%= user_activity_id %>">
<ul>
<% activity.children.reorder("created_on desc").each do |reply|%>
<script type="text/javascript">
$(function(){
showNormalImage('reply_content_<%= reply.id %>');
});
</script>
<% replies_all_i=replies_all_i+1 %>
<li class="homepagePostReplyContainer" nhname="reply_rec" style="display:<%= replies_all_i>3 ? 'none' : '' %>">
<div class="homepagePostReplyPortrait">
<%= link_to image_tag(url_to_avatar(reply.author), :width => "33", :height => "33"), user_path(reply.author_id,:host=>Setting.host_user), :alt => "用户头像" %>
</div>
<div class="homepagePostReplyDes">
<div class="homepagePostReplyPublisher mt-4">
<% if reply.try(:author).try(:realname) == ' ' %>
<%= link_to reply.try(:author), user_path(reply.author_id,:host=>Setting.host_user), :class => "newsBlue mr10 f14" %>
<% else %>
<%= link_to reply.try(:author).try(:realname), user_path(reply.author_id,:host=>Setting.host_user), :class => "newsBlue mr10 f14" %>
<% end %>
<%= format_time(reply.created_on) %>
</div>
<div class="homepagePostReplyContent break_word list_style upload_img" id="reply_content_<%= reply.id %>">
<%= reply.content.html_safe %>
</div>
</div>
<div class="cl"></div>
</li>
<% end %>
</ul>
</div>
<% end %>
<% if !activity.locked? %>
<div class="homepagePostReplyContainer borderBottomNone minHeight48">
<div class="homepagePostReplyPortrait mr15 imageFuzzy" id="reply_image_<%= user_activity_id%>"><%= link_to image_tag(url_to_avatar(User.current), :width => "33", :height => "33"), user_path(activity.author_id), :alt => "用户头像" %></div>
<div class="homepagePostReplyInputContainer mb10">
<div nhname='new_message_<%= user_activity_id%>' style="display:none;">
<%= form_for('new_form',:url => {:controller=>'blog_comments',:action => 'reply', :id => activity.id, :blog_id => activity.blog.id, :user_id => activity.author_id},:method => "post",:remote=>true) do |f|%>
<input type="hidden" name="quote[quote]" value="">
<input type="hidden" name="blog_comment[sticky]" value="0">
<input type="hidden" name="blog_comment[locked]" value="0">
<input type="hidden" name="blog_comment[title]" value="RE:<%= activity.title%>">
<input type="hidden" name="user_activity_id" value="<%=user_activity_id%>">
<textarea placeholder="有问题或有建议,请直接给我留言吧!" style="display: none" nhname='new_message_textarea_<%= user_activity_id%>' name="blog_comment[content]"></textarea>
<div nhname='toolbar_container_<%= user_activity_id%>' style="float:left; margin-left: 5px; padding-top:3px;"></div>
<a id="new_message_submit_btn_<%= user_activity_id%>" href="javascript:void(0)" class="blue_n_btn fr" style="display:none;margin-top:6px;">发送</a>
<div class="cl"></div>
<p nhname='contentmsg_<%= user_activity_id%>'></p>
<% end%>
<div class="cl"></div>
</div>
<div class="cl"></div>
</div>
<div class="cl"></div>
</div>
<% end %>
</div>
</div>

View File

@ -0,0 +1,101 @@
<%= javascript_include_tag "/assets/kindeditor/kindeditor", '/assets/kindeditor/pasteimg', "init_activity_KindEditor" %>
<style type="text/css">
/*回复框*/
.homepagePostReplyInputContainer .ke-toolbar {display: none; width: 400px; border: none; background: none; padding: 0px 0px;}
.homepagePostReplyInputContainer .ke-toolbar-icon {line-height: 26px; font-size: 14px; padding-left: 26px;}
.homepagePostReplyInputContainer .ke-toolbar-icon-url {background-image: url(/images/public_icon.png)}
.homepagePostReplyInputContainer .ke-outline {padding: 0px 0px; line-height: 26px; font-size: 14px;}
.homepagePostReplyInputContainer .ke-icon-emoticons {background-position: 0px -671px; width: 50px; height: 26px;}
.homepagePostReplyInputContainer .ke-icon-emoticons:hover {background-position: -79px -671px; width: 50px; height: 26px;}
.homepagePostReplyInputContainer .ke-outline {border: none;}
.homepagePostReplyInputContainer .ke-inline-block {display: none;}
.homepagePostReplyInputContainer .ke-container {float: left;}
</style>
<script type="text/javascript">
$(function(){
$("#RSide").removeAttr("id");
$("#Container").css("width","1000px");
});
function reset_article(){
$("#message_subject").val("");
$("#subjectmsg").text("");
document.getElementById("blog_comment_sticky").checked=false;
document.getElementById("blog_comment_locked").checked=false;
$("#topic_attachments").html("<%= escape_javascript(render :partial => 'blog_comments/blog_attachments', :locals => {:container => BlogComment.new})%>");
message_content_editor.html("");
$("#topic_editor").slideToggle();
}
<%# if @is_new%>
// $(function(){
// $("#message_subject").focus();
// });
<%#end%>
</script>
<div class="homepageRight mt0 ml10">
<div class="homepageRightBanner">
<div class="NewsBannerName">
<%= @user.name%>的博客
</div>
</div>
<% if User.current.logged? && User.current.id == @user.id %>
<%= labelled_form_for @article, :url =>{:controller=>'blog_comments',:action => 'create',:user_id=>user.id , :blog_id => blog.id},
:html => {:nhname=>'form',:multipart => true, :id => 'message-form'} do |f| %>
<%= render :partial => 'blog_comments/new', :locals => {:f => f, :article => @article, :edit_mode => false, :user => @user} %>
<% end %>
<% end %>
<% if topics%>
<% topics.each do |topic| %>
<script>
function expand_reply(container, btnid) {
var target = $(container);
var btn = $(btnid);
if (btn.data('init') == '0') {
btn.data('init', 1);
btn.html('收起回复');
target.show();
} else {
btn.data('init', 0);
btn.html('展开更多');
target.hide();
target.eq(0).show();
target.eq(1).show();
target.eq(2).show();
}
}
function expand_reply_input(id) {
$(id).toggle();
}
$(function () {
init_activity_KindEditor_data(<%= topic.id%>, null, "87%");
showNormalImage('activity_description_<%= topic.id %>');
/*var description_images=$("div#activity_description_<%#= topic.id %>").find("img");
if (description_images.length>0) {
for (var i=0; i<description_images.length; i++){
var image=$(description_images[i]);
var element=$("<a></a>").attr("href",image.attr('src'));
image.wrap(element);
}
}
$('#activity_description_<%#= topic.id %> a').colorbox({rel:'nofollow', close: "关闭", returnFocus: false});*/
});
</script>
<% if topic %>
<%= render :partial => 'blogs/article', :locals => {:activity => topic, :user_activity_id => topic.id} %>
<% end %>
<% end %>
<%# if topics.count == 10 %>
<!--<div id="show_more_course_topic" class="loadMore mt10 f_grey">展开更多<%#= link_to "", boards_topic_path(@board, :course_id => @board.course.id ,:page => page), :id => "more_topic_link", :remote => "true", :class => "none" %></div>-->
<%# end %>
<% end%>
</div>
<script type="text/javascript">
$("#show_more_course_topic").mouseover(function () {
$("#more_topic_link").click();
});
</script>

View File

@ -0,0 +1,187 @@
<style type="text/css">
div.talk_new .ke-container{margin-left:2px;}
.break_word {width:100%;}
</style>
<script type="text/javascript">
//头部导航
var menuids=["TopUserNav"] //Enter id(s) of SuckerTree UL menus, separated by commas
function buildsubmenus(){
for (var i=0; i<menuids.length; i++){
var div = document.getElementById(menuids[i]);
if(div == undefined)continue;
var ultags=div.getElementsByTagName("ul");
for (var t=0; t<ultags.length; t++){
ultags[t].parentNode.getElementsByTagName("a")[0].className="subfolderstyle";
ultags[t].parentNode.onmouseover=function(){
this.getElementsByTagName("ul")[0].style.display="block";
}
ultags[t].parentNode.onmouseout=function(){
this.getElementsByTagName("ul")[0].style.display="none";
}
}
}
}
if (window.addEventListener)
window.addEventListener("load", buildsubmenus, false)
else if (window.attachEvent)
window.attachEvent("onload", buildsubmenus)
</script>
<%= javascript_include_tag "/assets/kindeditor/kindeditor",'/assets/kindeditor/pasteimg' %>
<%#= javascript_include_tag "/assets/kindeditor/kindeditor-min" %>
<%= render :partial => 'blogs/article_list', :locals => {:blog=>@user.blog,:topics => @user.blog.articles.reorder("#{BlogComment.table_name}.sticky desc,#{BlogComment.table_name}.created_on desc"), :page => 0, :user => @user} %>
<script type="text/javascript">//侧导航
function nh_check_field(params){
var result=true;
if(params.subject!=undefined){
if($.trim(params.subject.val()) == ""){
params.subjectmsg.html('主题不能为空');
params.subjectmsg.css({color:'#ff0000'});
result=false;
}else{
params.subjectmsg.html('填写正确');
params.subjectmsg.css({color:'#008000'});
}
params.subjectmsg.show();
}
if(params.content!=undefined){
if(params.content.isEmpty()){
result=false;
}
if(params.content.html()!=params.textarea.html() || params.issubmit==true){
params.textarea.html(params.content.html());
params.content.sync(); //用上面那句ie11提交到服务器居然木有值
if(params.content.isEmpty()){
params.contentmsg.html('内容不能为空');
params.contentmsg.css({color:'#ff0000'});
}else{
params.contentmsg.html('填写正确');
params.contentmsg.css({color:'#008000'});
}
params.contentmsg.show();
}
}
return result;
}
function nh_init_board(params){
//发帖/编辑/回复按钮的click
params.showbtn.click(function(){
params.textarea.removeAttr('placeholder');
if(params.textarea.data('init') == undefined){
//初始化编辑器
var editor = params.kindutil.create(params.textarea, {
// allowPreviewEmoticons : false,
// allowImageUpload : false,
autoHeightMode : true,
resizeType : 1,minWidth:"1px",width:"560px",height:"150px",
allowFileManager:true,uploadJson:"/kindeditor/upload",
fileManagerJson:"/kindeditor/filemanager",
afterChange:function(){//按键事件
nh_check_field({content:this,contentmsg:params.contentmsg,textarea:params.textarea});
// var edit = this.edit;
// var body = edit.doc.body;
// edit.iframe.height(minHeight);
// this.resize(null, Math.max((params.kindutil.IE ? body.scrollHeight : body.offsetHeight) + 30, minHeight));
},
afterCreate:function(){
this.loadPlugin("autoheight");
var userAgent = navigator.userAgent.toLowerCase();
if(/trident/.test(userAgent)){
$("div.talk_new .ke-container").css({'margin-left':'0px'});
}
// var toolbar = $("div[class='ke-toolbar']",params.about_talk);
// $(".ke-outline>.ke-toolbar-icon",toolbar).append('表情');
// params.toolbar_container.append(toolbar);
}
}).loadPlugin('paste');
//主题输入框按键事件
params.inputsubject.keyup(function(){
nh_check_field({subject:params.inputsubject,subjectmsg:params.subjectmsg});
})
//表单提交
params.form.submit(function(){
var is_checked = nh_check_field({
issubmit:true,
subject:params.inputsubject,
subjectmsg:params.subjectmsg,
content:editor,
contentmsg:params.contentmsg,
textarea:params.textarea
});
if(is_checked){
//return true 居然不提交 fuck your sister
$(this)[0].submit();
// return true;
}
return false;
});
//提交按钮click
params.submitbtn.click(function(){
params.form.submit();
});
//取消按钮click
params.cancelbtn.click(function(){
params.about_talk.toggle();//显示/隐藏编辑区
if(params.about_talk.is(':hidden')){//隐藏时reset表单数据
params.form[0].reset();
if(params.type=='reply'){
params.textarea.empty();
}else{
params.textarea.html(params.init_content_val.val());
}
var str = params.textarea.html();
str=str.replace(new RegExp(/&lt;/g),'<');
str=str.replace(new RegExp(/&gt;/g),'>');
editor.html(str);
params.subjectmsg.hide();
params.contentmsg.hide();
if(params.quote_show!=undefined)params.quote_show.empty();
if(params.quote_input!=undefined)params.quote_input.empty();
}else{
if(params.type=='reply'){
params.textarea.show();
params.textarea.focus();
params.textarea.hide();
//params.jumphref.attr('href','#'+params.form.attr('id'));
//params.jumphref[0].click();
}else{
params.textarea.show();
params.textarea.focus();
params.textarea.hide();
// params.inputsubject.focus();
}
}
});
params.textarea.data('init','1');//标记为已经初始化
}
params.cancelbtn.click();//显示/隐藏编辑区
});
if(params.type == 'reply'){
params.showbtn_child.click(function(){
if(params.textarea.data('init') == undefined){
params.showbtn.click();
}else{
params.cancelbtn.click();
if(params.about_talk.is(':hidden')){
params.cancelbtn.click();
}
}
var parent_topic_id = $(this).data('topic-id');
if(parent_topic_id!=undefined)$("input[name='parent_topic']",params.form).val(parent_topic_id);
var ref_str = params.get_ref_str_call($(this));
params.quote_show.html(ref_str);
params.quote_input.html(ref_str);
});
}
}
</script>

View File

View File

@ -8,7 +8,7 @@
<a href="#L<%= line_num %>" style="padding-top: 0px;"><%= line_num %></a>
</th>
<td class="line-code">
<pre style="width:880px;word-wrap: break-word; word-break: normal; "><%= line.html_safe %></pre>
<pre style="width:auto;white-space: nowrap; "><%= line.html_safe %></pre>
</td>
</tr>
<% line_num += 1 %>

View File

@ -62,13 +62,13 @@
<li>
<input type="text" style="display: none"/> <!--阻止表单自动填充 -->
<input type="password" style="display: none"/> <!--阻止表单自动填充 -->
<span class="tips" style="width: 72px; display: inline-block;">课&nbsp;程&nbsp;ID</span>
<span class="tips" style="width: 68px; display: inline-block;">课&nbsp;程&nbsp;ID</span>
<input class=" width190" name="object_id" id="object_id" type="text" value="" >
<input type="text" style="display: none"/>
</li>
<li class="mB5">课程ID是所在课程网址中显示的序号</li>
<li>
<span class="tips">密&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;码:</span>
<span class="tips" style="width: 68px; display: inline-block;">密&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;码:</span>
<input class=" width190" type="password" name="course_password" id="course_password" value="" >
</li>
<li style="margin-top: 30px;">

View File

@ -12,7 +12,7 @@
<%= favicon %>
<%= javascript_heads %>
<%= heads_for_theme %>
<%= stylesheet_link_tag 'public', 'pleft', 'project','prettify','jquery/jquery-ui-1.9.2','header' %>
<%= stylesheet_link_tag 'public', 'pleft', 'project','prettify','jquery/jquery-ui-1.9.2','header','repository' %>
<%= javascript_include_tag 'cookie','project', 'header','prettify','select_list_move' %>
<%= call_hook :view_layouts_base_html_head %>
<!-- page specific tags -->

View File

@ -71,27 +71,38 @@
<textarea class="homepageSignatureTextarea none" placeholder="请编辑签名" id="user_brief_introduction_edit" onblur="edit_user_introduction('<%= edit_brief_introduction_user_path(@user.id)%>');"><%= @user.user_extensions.brief_introduction %></textarea>
</div>
<div>
<div class="homepageImageBlock">
<div>
<%= link_to(@user.blog.blog_comments.where("#{BlogComment.table_name}.parent_id is null").count,
{:controller => 'blogs', :action => 'index', :user_id => @user.id }, :class => 'homepageImageNumber',:id => 'user_score') %>
</div>
<div class="homepageImageText">
<%= link_to('博客',
{:controller => 'blogs', :action => 'index', :user_id => @user.id }, :class => 'homepageImageNumber',:id => 'user_score') %>
</div>
</div>
<div class="homepageVerDiv"></div>
<div class="homepageImageBlock">
<div id="watch_user_number_div">
<%= link_to User.watched_by(@user.id).count.to_s, {:controller=>"users", :action=>"user_watchlist",:id=>@user.id},:class=>"homepageImageNumber" %>
</div>
<div class="homepageImageText">关注</div>
<div class="homepageImageText">
<%= link_to '关注', {:controller=>"users", :action=>"user_watchlist",:id=>@user.id},:class=>"homepageImageNumber" %>
</div>
</div>
<div class="homepageVerDiv"></div>
<div class="homepageImageBlock">
<div id="fans_user_number_div">
<%= link_to @user.watcher_users.count.to_s, {:controller=>"users", :action=>"user_fanslist",:id=>@user.id},:class=>"homepageImageNumber", :id => "user_fans_number"%>
</div>
<div class="homepageImageText">粉丝</div>
</div>
<div class="homepageVerDiv"></div>
<div class="homepageImageBlock">
<div>
<%= link_to(format("%.2f" ,get_option_number(@user,1).total_score ).to_i,
{:controller => 'users', :action => 'show_new_score', :remote => true, :id => @user.id }, :class => 'homepageImageNumber',:id => 'user_score') %>
<div class="homepageImageText">
<%= link_to '粉丝', {:controller=>"users", :action=>"user_fanslist",:id=>@user.id},:class=>"homepageImageNumber", :id => "user_fans_number"%>
</div>
<div class="homepageImageText">积分</div>
</div>
<div class="cl"></div>
</div>
</div>

View File

@ -1,4 +1,14 @@
<p>
<%= @user.show_name %>申请成为课程<%= @course.name %>的<%= @role.eql?('9') ? '老师': '教辅' %>
<%=link_to user_message_url(@receive),user_message_url(@receive)%>
</p>
<div class="mail_box" style="border:1px solid #c8c8c8; width:570px; height: auto; padding:15px; margin-top:10px; margin-bottom:10px;">
<ul style="list-style-type:none; margin:0; padding:0;">
<li style="list-style-type:none; margin:0; padding:0;"><span style="float: left;"><strong><%= l(:mail_issue_content)%></strong></span>
<span style="float: left; width: 526px">
<p><span style="color:#1b55a7; font-weight:bold;"><%= @user.show_name %></span> 请求成为课程:<span style="color:#1b55a7; font-weight:bold;"><%=link_to @course.name, course_url(@course) %></span>的<%= @role.eql?('9') ? '老师': '教辅' %> </p>
<p><%=link_to user_message_url(@receive),user_message_url(@receive)%></p>
</span>
</li>
</ul>
<div class="cl" style="margin-top: 30px; clear:both; overflow:hidden;"></div>
</div>

View File

@ -41,7 +41,7 @@
<%# --版本库被设置成私有、module中设置不显示、没有创建版本库 三种情况不显示-- %>
<% if visible_repository?(@project) %>
<div class="subNav">
<%= link_to l(:project_module_repository), {:controller => 'repositories', :action => 'show', :id => @project.id}, :class => "f14 c_blue02" %>
<%= link_to l(:project_module_repository), {:controller => 'repositories', :action => 'show', :id => @project.id, to: 'gitlab'}, :class => "f14 c_blue02" %>
<a class="subnav_num">(<%= @project.repositories.count %>)</a>
</div>
<% end %>

View File

@ -18,6 +18,13 @@
<script type="text/javascript">
function clickCanel(){hideModal("#popbox02");}
function clickSure()
{
if ($('#new_join_course').submit())
{
hideModal("#popbox02");
}
}
</script>
</head>
@ -33,7 +40,7 @@
<label>请输入课程密码:</label>
<%= text_field_tag 'course_password', nil, :style=>'width:300px;'%>
<div class="ni_btn">
<a href="javascript:" class="tijiao" onclick="$('#new_join_course').submit();" >
<a href="javascript:" class="tijiao" onclick="clickSure();" >
确&nbsp;&nbsp;定
</a>
<a href="javascript:" class="tijiao" onclick="clickCanel();">

View File

@ -73,13 +73,6 @@
<span class="c_grey"><%= l(:text_scm_command_not_available) %></span>
<% end %>
</li>
<% unless judge_main_repository(@project) %>
<li>
<label class="label02"><%=l(:field_repository_is_default)%></label>
<%= f.check_box :is_default, :label => "", :no_label => true %></p>
</li>
<% end %>
<li >
<input type="text" style="display: none"/> <!--阻止表单自动填充 -->
<input type="password" style="display: none"/> <!--阻止表单自动填充 -->
@ -89,11 +82,6 @@
<span class="c_grey"><%=l(:text_length_between,:min=>1,:max=>254)<<l(:text_project_identifier_info) %></span>
<% end %>
</li>
<li >
<label class="label02"><span class="c_red">*</span><%=l(:label_password)%></label>
<%= f.password_field :upassword, :label=> "", :no_label => true%>
<span class="c_grey"><%= l(:label_upassword_info)%></span>
</li>
<div class="cl"></div>
</ul>
<a href="#" onclick="$('#repository-form').submit();" class="blue_btn fl ml110"><%=l(:button_save)%></a>

View File

@ -1,32 +1,13 @@
<%= link_to @repository.identifier.present? ? h(@repository.identifier) : 'root',
{:action => 'show', :id => @project,
:repository_id => @repository.identifier_param,
:path => nil, :rev => @rev },
:class=>"fl c_blue f14 fb" %>
<%
dirs = path.split('/')
if 'file' == kind
filename = dirs.pop
end
link_path = ''
dirs.each do |dir|
next if dir.blank?
link_path << '/' unless link_path.empty?
link_path << "#{dir}"
%>
/ <%= link_to h(dir), :action => 'show', :id => @project, :repository_id => @repository.identifier_param,
:path => to_path_param(link_path), :rev => @rev %>
<% end %>
<% if filename %>
/ <%= link_to h(filename),
:action => 'changes', :id => @project, :repository_id => @repository.identifier_param,
:path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %>
<% end %>
<%
# @rev is revsion or Git and Mercurial branch or tag.
# For Mercurial *tip*, @rev and @changeset are nil.
rev_text = @changeset.nil? ? @rev : format_revision(@changeset)
%>
<p class="fl f14 fb c_grey02"><%= "@ #{h rev_text}" unless rev_text.blank? %></p>
<div class="git_usr_title">
<span><%= link_to @repository.identifier.present? ? h(@repository.identifier) : 'root',
{:action => 'show', :id => @project,
:repository_id => @repository.identifier_param,
:path => nil, :rev => @rev },
:class => "repository-title-dec"
%>
/
<%=link_to @project.owner, user_path(@project.owner), :class => "repository-title-dec" %>
</span>
</div>
<% html_title(with_leading_slash(path)) -%>

View File

@ -1,17 +1,6 @@
<div class="autoscroll">
<table class="list entries" id="browser">
<thead>
<tr id="root">
<th><%= l(:field_name) %></th>
<th><%= l(:field_filesize) %></th>
<% if @repository.report_last_commit %>
<th><%= l(:label_revision) %></th>
<th><%= l(:label_age) %></th>
<th><%= l(:field_author) %></th>
<th><%= l(:field_comments) %></th>
<% end %>
</tr>
</thead>
<tbody>
<%= render :partial => 'dir_list_content' %>
</tbody>

View File

@ -7,6 +7,7 @@
<td style="padding-left: <%=18 * depth%>px;" class="<%=
@repository.report_last_commit ? "filename" : "filename_no_report" %>">
<% if entry.is_dir? %>
<%# 展开文件目录 %>
<span class="expander" onclick="scmEntryClick('<%= tr_id %>', '<%= escape_javascript(url_for(
:action => 'show',
:id => @project,
@ -17,10 +18,11 @@
:parent_id => tr_id)) %>');">&nbsp;</span>
<% end %>
<%= link_to h(ent_name),
{:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(ent_path), :rev => @rev},
{:action => (entry.is_dir? ? 'show' : 'entry'), :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(ent_path), :rev => @rev},
:class => (entry.is_dir? ? 'icon icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(ent_name)}")%>
</td>
<td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
<!--<td class="size"><%#= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>-->
<% if @repository.report_last_commit %>
<td class="revision"><%= link_to_revision(entry.changeset, @repository) if entry.changeset %></td>
<td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>

View File

@ -31,12 +31,7 @@
<%= f.text_field :login, :size => 30 %>
<input type="text" hidden="hidden">
</p>
<p>
<%= f.password_field :password, :size => 30, :name => 'ignore',
:value => ((@repository.new_record? || @repository.password.blank?) ? '' : ('x'*15)),
:onfocus => "this.value=''; this.name='repository[password]';",
:onchange => "this.name='repository[password]';" %>
</p>
</div>
<!--Ended by tanxianbo-->
<p>

View File

@ -26,7 +26,6 @@ border:none
<em class="info error"><%= l(:text_scm_command_not_available) %></em>
<% end %>
</p>
<p><%= f.check_box :is_default, :label => :field_repository_is_default %></p>
<p>
<input id="googleinputcache" size="30" type="text">
@ -36,8 +35,6 @@ border:none
<%= l(:text_repository_identifier_info).html_safe %></em>
<% end %></p>
<!-- <p><%#= f.text_field :url, :size => 60, :required => true,:readonly=>true, :class=>'textbg'%></p> -->
<p><%= f.password_field :upassword, :required =>true, :label=> :field_password %>
<em class="info"><%= l(:label_upassword_info)%></em></p>
</div>
<p>
<%= submit_tag(@repository.new_record? ? l(:button_create) : l(:button_save)) %>

View File

@ -1,34 +1,33 @@
<% content_for :header_tags do %>
<%= javascript_include_tag 'repository_navigation' %>
<% end %>
<a href="javascript:void(0);" class="pic_stats fl ml20 mt3"></a>
<%= link_to l(:label_statistics),
<!--<a href="javascript:void(0);" class="pic_stats fl ml20 mt3"></a>-->
<%#= link_to l(:label_statistics),
{:action => 'stats', :id => @project, :repository_id => @repository.identifier_param},
:class => 'mt3 c_blue fl' if @repository.supports_all_revisions? %>
<div class="repositorytitle mr15">
<% content_for :header_tags do %>
<%= javascript_include_tag 'repository_navigation' %>
<% end %>
<%= form_tag({:action => controller.action_name,
:id => @project,
:repository_id => @repository.identifier_param,
:path => to_path_param(@path),
:rev => nil},
{:method => :get, :id => 'revision_selector', :class => "fl c_grey02 ml5"}) do -%>
<!-- Branches Dropdown -->
<% if !@repository.branches.nil? && @repository.branches.length > 0 -%>
| <%= l(:label_branch) %>:
<%= select_tag :branch,
options_for_select([''] + @repository.branches, @rev),
:id => 'branch' %>
<% end -%>
<% if !@repository.tags.nil? && @repository.tags.length > 0 -%>
| <%= l(:label_tag) %>:
<%= select_tag :tag,
options_for_select([''] + @repository.tags, @rev),
:id => 'tag' %>
<% end -%>
<% if @repository.supports_all_revisions? %>
| <%= l(:label_revision) %>:
<%= text_field_tag 'rev', @rev, :size => 8 %>
<% end %>
<% end -%>
<%= form_tag({:action => controller.action_name,
:id => @project,
:repository_id => @repository.identifier_param,
:path => to_path_param(@path),
:rev => nil},
{:method => :get, :id => 'revision_selector'}) do -%>
<!-- Branches Dropdown -->
<% if !@repository.branches.nil? && @repository.branches.length > 0 -%>
<%= l(:label_branch) %>:
<%= select_tag :branch, options_for_select([''] + @repository.branches, @rev), :id => 'branch' %>
<% end -%>
<% if !@repository.tags.nil? && @repository.tags.length > 0 -%>
<%= select_tag :tag, options_for_select([''] + @repository.tags, @rev), :id => 'tag', :style=>" display:none" %>
<% end -%>
<% if @repository.supports_all_revisions? %>
<%= hidden_field_tag 'rev', @rev, :size => 8 %>
<% end %>
<% end -%>
</div>

View File

@ -0,0 +1,37 @@
<div class="overall-summary overall-summary-bottomless">
<div class="stats-switcher-viewport js-stats-switcher-viewport">
<div class="stats-switcher-wrapper">
<ul class="numbers-summary">
<li class="commits">
<a data-pjax="" href="/redmine/redmine/commits/0.6-stable">
<span class="octicon octicon-history"></span>
<span class="num text-emphasized">
<%=link_to @changesets.count, {:action => 'changes', :path => to_path_param(@path), :id => @project, :repository_id => @repository.identifier_param, :rev => @rev}, :class => "num text-emphasized c_blue" %>
</span>
commits
</a>
</li>
<li>
<span class="octicon image-type"></span>
<span class="num text-emphasized" style="color: #269AC9">
<%= @repository.branches.count %>
</span>
branches
</li>
<li>
<span class="octicon octicon-organization"></span>
<span class="num text-emphasized">
<%=link_to @repository.committers.count, committers_repository_path(@repository), :class => "c_blue" %>
</span>
contributors
</li>
</ul>
</div>
</div>
</div>

View File

@ -1,18 +1,20 @@
<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %>
<div class="contextual">
<%= render :partial => 'navigation' %>
<div class="project_r_h">
<div class="fl"><h2 class="project_h2_repository"><%= render :partial => 'breadcrumbs', :locals => {:path => @path, :kind => 'dir', :revision => @rev} %></h2></div>
</div>
<h3><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h3>
<div class="repository_con " style="line-height:1.9;">
<%= render :partial => 'navigation' %>
<div class="cl"></div>
</div>
<%= render :partial => 'link_to_functions' %>
<%= render_properties(@properties) %>
<div class="mt40">
<%= render(:partial => 'revisions',
:locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %>
<div class="mt10">
<%= render(:partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %>
</div>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "scm" %>

View File

@ -1,4 +1,8 @@
<h3><%= l(:label_repository) %></h3>
<div class="project_r_h">
<div class="fl">
<h2 class="project_h2_repository"><%= render :partial => 'breadcrumbs', :locals => {:path => @path, :kind => 'dir', :revision => @rev} %></h2>
</div>
</div>
<%= simple_format(l(:text_repository_usernames_mapping)) %>

View File

@ -1,4 +1,7 @@
<h3><%= l(:label_revision) %> <%= @diff_format_revisions %> <%=h @path %></h3>
<div class="project_r_h">
<div class="fl"><h2 class="project_h2_repository"><%= render :partial => 'breadcrumbs', :locals => {:path => @path, :kind => 'dir', :revision => @rev} %></h2></div>
</div>
<h3><%= l(:label_revision_path) %> <%=h @path %></h3>
<!-- Choose view type -->
<%= form_tag({:action => 'diff', :id => @project,

View File

@ -1,15 +1,17 @@
<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %>
<div style="padding-left: 8px; padding-top: 10px;">
<div class="contextual">
<%= render :partial => 'navigation' %>
</div>
<div class="contextual">
<%= render :partial => 'navigation' %>
<h3><%= l(:label_revision_path) %> <%= @path %></h3>
<%= render :partial => 'link_to_functions' %>
<%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "scm" %>
<% end %>
</div>
<h3><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h3>
<%= render :partial => 'link_to_functions' %>
<%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "scm" %>
<% end %>

View File

@ -1,83 +1,74 @@
<%= call_hook(:view_repositories_show_contextual, {:repository => @repository, :project => @project}) %>
<div class="project_r_h">
<h2 class="project_h2">版本库</h2>
<div class="fl"><h2 class="project_h2_repository"><%= render :partial => 'breadcrumbs', :locals => {:path => @path, :kind => 'dir', :revision => @rev} %></h2></div>
</div>
<div class="repository_con" style="line-height:1.9;">
<div class="repositorytitle" style="float:left;">
<%= render :partial => 'breadcrumbs',
:locals => {:path => @path, :kind => 'dir', :revision => @rev} %>
<%= render :partial => 'navigation' %>
</div>
<!--contextual end-->
<div class="cl"></div>
<div class=" c_dark f14">
<p>
<% if @repository.type.to_s=="Repository::Git" %>
<%= @repos_url %>
<% else %>
<%= h @repository.url %>
<% end %>
</p>
<% if @entries.nil? %>
<%# 未提交代码提示 %>
<div class=" repository-url light-well">
<% if @entries.nil? && authorize_for('repositories', 'browse') %>
<div class="page-title">
该版本库还没有上传代码!
</div>
<% end %>
<% if @repository.type.to_s=="Repository::Gitlab" %>
版本库地址:<%= @repos_url %>
<% else %>
版本库地址:<%= h @repository.url %>
<% end %>
<!-- added by bai -->
<div class="fb"><a href="http://<%=Setting.host_name %>/forums/1/memos/1232" class=" c_blue ">点击查看如何提交代码</a></div>
<div class="cl"></div>
</div>
<% else %>
<%= render :partial => 'navigation' %>
<div class="fl c_grey02 mt5 mr5">克隆网址:</div>
<textarea id="copy_rep_content" class="cloneUrl mt5 fl" type="input" placeholder="http://xianbo_trustie2@repository.trustie.net/xianbo/trustie2.git"><%=@repository.type.to_s=="Repository::Gitlab" ? @repos_url.to_s.lstrip : @repository.url %></textarea>
<a href="javascript:void(0);" class="clone_btn mt5" onclick="jsCopy()"><span class="vl_copy" title="点击复制版本库地址"></span></a>
<div class="fl mt5 ml15"><a href="javascript:void(0);" class="vl_btn fb" onclick="zip()"><span class="vl_zip"></span>ZIP</a> </div>
<div class="fr mt5"><a href="javascript:void(0);" class="vl_btn fb" onclick="zip()"><span class="vl_fork"></span>Fork</a> <span href="javascript:void(0);" class="vl_btn_2 fb">0</span> </div>
<div class="cl"></div>
<div class="recordBanner mt10">
<% if @changesets && !@changesets.empty? %>
<%= image_tag(url_to_avatar(@changesets_latest_coimmit.user), :width => "25", :height => "25", :class => "fl portraitRadius mt2 ml4 mr5") %>
<span class="fl"><div class="fb fontGrey3 mr5 fl"><%=link_to @changesets_latest_coimmit.user, user_path(@changesets_latest_coimmit.user) %></div>
<div class="fl">提交于<%= time_tag(@changesets_latest_coimmit.committed_on) %></div>
<div class="commit_content_dec fl" title="<%= @changesets_latest_coimmit.comments %>"><%= @changesets_latest_coimmit.comments %></div>
</span>
<% end %>
<span class="fr mr5 "><font class="fb ml2 mr2 vl_branch mt2">
<%= @repository.branches.count %></font> 个分支
</span>
<p class="mb10 break_word">
(<%= l(:label_all_revisions) %><%= @repositories.sort.collect { |repo|
link_to h(repo.name),
{:controller => 'repositories', :action => 'show',
:id => @project, :repository_id => repo.identifier_param, :rev => nil, :path => nil},
:class => 'repository' + (repo == @repository ? ' selected' : ''),
:class => "mb10 break_word c_orange" }.join('&nbsp|&nbsp').html_safe %>)
</p>
</div>
<span class="fr mr5"><font class="fb ml2 mr2 vl_commit">
<%=link_to @changesets_count, {:action => 'changes', :path => to_path_param(@path), :id => @project, :repository_id => @repository.identifier_param, :rev => @rev} %></font> 提交
</span>
</div>
<% end %>
</div>
<!--contextual end-->
<% if !@entries.nil? && authorize_for('repositories', 'browse') %>
<%# 数据统计 %>
<%#= render :partial => 'summary' %>
<%# end %>
<%= render :partial => 'dir_list' %>
<% end %>
<%= render_properties(@properties) %>
<!-- 代码修订 -->
<% if authorize_for('repositories', 'revisions') %>
<%# if @changesets && !@changesets.empty? %>
<h3>
<%= l(:label_latest_revision_plural) %>
</h3>
<%= render :partial => 'revisions',
:locals => {:project => @project, :path => @path,
:revisions => @changesets, :entry => nil} %>
<%# end %>
<%= render_properties(@properties) %>
<p style="padding-top: 10px;">
<% if authorize_for('repositories', 'revisions') %>
<% if @changesets && !@changesets.empty? %>
<% has_branches = (!@repository.branches.nil? && @repository.branches.length > 0)
sep = '' %>
<% if @repository.supports_all_revisions? && @path.blank? %>
<%= link_to l(:label_view_all_revisions), {:action => 'revisions', :id => @project,
:repository_id => @repository.identifier_param},
:class => "orange_u_btn" %>
<% sep = '|' %>
<% end %>
<% if @repository.supports_directory_revisions? && (has_branches || !@path.blank? || !@rev.blank?) %>
<%= sep %>
<%= link_to l(:label_view_revisions),
{:action => 'changes',
:path => to_path_param(@path),
:id => @project,
:repository_id => @repository.identifier_param,
:rev => @rev},
:class => "orange_u_btn" %>
<% end %>
</p>
<% if @repository.supports_all_revisions? %>
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(
:atom, params.merge(
{:format => 'atom', :action => 'revisions',
:id => @project, :page => nil, :key => User.current.rss_key})) %>
<% end %>
<% end %>
<% if @repository.supports_all_revisions? && @path.blank? %>
<%= link_to l(:label_view_all_revisions_commits), :action => 'revisions', :id => @project, :repository_id => @repository.identifier_param %>
<% end %> |
<% end %>
<% end %>
<!-- added by bai -->
<p class="fb mt10"><a href="http://<%=Setting.host_name %>/forums/1/memos/1232" class=" c_blue ">点击查看如何提交代码</a></p>
<div class="cl"></div>
<a href="https://<%=Setting.host_name %>/forums/1/memos/1232" >如何提交代码</a>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "scm" %>

View File

@ -0,0 +1,12 @@
<div class="repository-update-dec">
<p class="c_orange">
<%= l(:label_repository_migrate_dec) %>
</p>
<%= form_for(@repository, url: to_gitlab_project_repository_path(@project, @repository)) do |f| %>
<input type="text" name="repo_name"/>
<button type="submit">转换到新版本</button>
<% end %>
<span class="c_grey">
<%= l(:label_repository_name_dec) %>
</span>
</div>

View File

@ -69,6 +69,12 @@ module RedmineApp
config.action_view.sanitized_allowed_tags = 'div', 'p', 'span', 'img', 'embed'
config.before_initialize do
end
config.after_initialize do
end
if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb'))
instance_eval File.read(File.join(File.dirname(__FILE__), 'additional_environment.rb'))
end

View File

@ -0,0 +1,9 @@
Gitlab.configure do |config|
# config.endpoint = 'http://192.168.41.130:3000/trustie/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT']
# config.private_token = 'cK15gUDwvt8EEkzwQ_63' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN']
config.endpoint = 'http://git.trustie.net/trustie/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT']
config.private_token = 'fPc_gBmEiSANve8TCfxW' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN']
# Optional
# config.user_agent = 'Custom User Agent' # user agent, default: 'Gitlab Ruby Gem [version]'
# config.sudo = 'user' # username for sudo mode, default: nil
end

View File

@ -481,6 +481,7 @@ en:
label_attribute_plural: Attributes
label_change_status: Change status
label_history: History
label_commit_history: Commit History
label_attachment: Files
label_attachment_delete: Delete file

View File

@ -322,6 +322,7 @@ zh:
label_latest_revision_plural: 最近的修订版本
label_view_revisions: 查看修订
label_view_all_revisions: 查看所有修订
label_view_all_revisions_commits: 查看所有提交记录
#
# 项目托管平台
#
@ -440,4 +441,8 @@ zh:
#
field_sharing: 共享
label_title_code_review: 代码评审
label_home_non_project: 您还没有创建项目,您可以查看系统的其它项目!
label_home_non_project: 您还没有创建项目,您可以查看系统的其它项目!
# 版本库迁移
label_repository_migrate_dec: 注意Trustie版本库近期进行了一次大的改造历史版本需要转换成新的版本输入新的版本库名即可完成转换。 转换过程可能需要等待一段时间。
label_repository_name_dec: 版本库名仅小写字母a-z、数字、破折号-和下划线_可以使用长度必须在 1 到 254 个字符之间,一旦保存,标识无法修改。

View File

@ -580,6 +580,7 @@ zh:
label_change_status: 变更状态
label_history: 历史记录
label_commit_history: 历史变更记录
label_attachment: 文件
label_file_upload: 上传资料
@ -674,6 +675,7 @@ zh:
label_branch: 分支
label_tag: 标签
label_revision: 修订
label_revision_path: 当前路径
label_revision_plural: 修订
lable_revision_code_count: 代码量
label_revision_commit_count: 提交次数
@ -924,7 +926,7 @@ zh:
button_change_password: 修改密码
button_copy: 复制
button_copy_and_follow: 复制并转到新问题
button_annotate: 追溯
button_annotate: 代码定位
button_configure: 配置
button_quote: 引用
@ -987,7 +989,7 @@ zh:
text_enumeration_destroy_question: "%{count} 个对象被关联到了这个枚举值。"
text_enumeration_category_reassign_to: '将它们关联到新的枚举值:'
text_email_delivery_not_configured: "邮件参数尚未配置,因此邮件通知功能已被禁用。\n请在config/configuration.yml中配置您的SMTP服务器信息并重新启动以使其生效。"
text_repository_usernames_mapping: "选择或更新与版本库中的用户名对应的Trustie用户。\n版本库中与Trustie中的同名用户将被自动对应。"
text_repository_usernames_mapping: "选择或更新与版本库中的用户名对应的Trustie用户版本库中与Trustie中的同名用户将被自动对应。"
text_diff_truncated: '... 差别内容超过了可显示的最大行数并已被截断'
text_custom_field_possible_values_info: '每项数值一行'
text_wiki_page_destroy_question: 此页面有 %{descendants} 个子页面和下级页面。您想进行那种操作?

View File

@ -392,6 +392,15 @@ RedmineApp::Application.routes.draw do
get 'store_selected_resource'
# end
end
#resources :blogs
resources :blogs do
resources :blog_comments do
member do
post 'reply'
get 'quote'
end
end
end
end
match 'users/:id/user_newfeedback', :to => 'users#user_newfeedback', :via => :get, :as => "feedback"
match 'users/:id/user_projects', :to => 'users#user_projects', :via => :get
@ -535,6 +544,7 @@ RedmineApp::Application.routes.draw do
resources :repositories, :except => [:index, :show] do
member do
get 'newrepo', :via => [:get, :post]
put 'to_gitlab'
# get 'create', :via=>[:get, :post]
end
end

View File

@ -0,0 +1,5 @@
class AddGitlabUserIdToUsers < ActiveRecord::Migration
def change
add_column :users, :gid, :integer
end
end

View File

@ -0,0 +1,5 @@
class AddGpidToProject < ActiveRecord::Migration
def change
add_column :projects, :gpid, :integer
end
end

View File

@ -0,0 +1,15 @@
class CreateBlogs < ActiveRecord::Migration
def change
create_table :blogs do |t|
t.string "name", :default => "", :null => false
t.text "description"
t.integer "position", :default => 1
t.integer "article_count", :default => 0, :null => false
t.integer "comments_count", :default => 0, :null => false
t.integer "last_comments_id"
t.integer "parent_id"
t.integer "author_id"
t.timestamps
end
end
end

View File

@ -0,0 +1,19 @@
class CreateBlogComments < ActiveRecord::Migration
def change
create_table :blog_comments do |t|
t.integer "blog_id", :null => false
t.integer "parent_id"
t.string "title", :default => "", :null => false
t.text "content"
t.integer "author_id"
t.integer "comments_count", :default => 0, :null => false
t.integer "last_comment_id"
t.datetime "created_on", :null => false
t.datetime "updated_on", :null => false
t.boolean "locked", :default => false
t.integer "sticky", :default => 0
t.integer "reply_id"
t.timestamps
end
end
end

File diff suppressed because it is too large Load Diff

4
lib/gitlab-cli/Gemfile Normal file
View File

@ -0,0 +1,4 @@
source 'https://rubygems.org'
# Specify your gem's dependencies in gitlab.gemspec
gemspec

View File

@ -0,0 +1,24 @@
Copyright (c) 2012-2014 Nihad Abbasov <mail@narkoz.me>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

121
lib/gitlab-cli/README.md Normal file
View File

@ -0,0 +1,121 @@
# Gitlab
[![Build Status](https://travis-ci.org/NARKOZ/gitlab.png)](http://travis-ci.org/NARKOZ/gitlab)
[website](http://narkoz.github.io/gitlab) |
[documentation](http://rubydoc.info/gems/gitlab/frames)
Gitlab is a Ruby wrapper and CLI for the [GitLab API](https://github.com/gitlabhq/gitlabhq/tree/master/doc/api#gitlab-api).
## Installation
Install it from rubygems:
```sh
gem install gitlab
```
Or add to a Gemfile:
```ruby
gem 'gitlab'
# gem 'gitlab', :git => 'git://github.com/NARKOZ/gitlab.git'
```
## Usage
Configuration example:
```ruby
Gitlab.configure do |config|
config.endpoint = 'https://example.net/api/v3' # API endpoint URL, default: ENV['GITLAB_API_ENDPOINT']
config.private_token = 'qEsq1pt6HJPaNciie3MG' # user's private token, default: ENV['GITLAB_API_PRIVATE_TOKEN']
# Optional
# config.user_agent = 'Custom User Agent' # user agent, default: 'Gitlab Ruby Gem [version]'
# config.sudo = 'user' # username for sudo mode, default: nil
end
```
(Note: If you are using Gitlab.com's hosted service, your endpoint will be `https://gitlab.com/api/v3`)
Usage examples:
```ruby
# set an API endpoint
Gitlab.endpoint = 'http://example.net/api/v3'
# => "http://example.net/api/v3"
# set a user private token
Gitlab.private_token = 'qEsq1pt6HJPaNciie3MG'
# => "qEsq1pt6HJPaNciie3MG"
# list projects
Gitlab.projects(:per_page => 5)
# => [#<Gitlab::ObjectifiedHash:0x000000023326e0 @data={"id"=>1, "code"=>"brute", "name"=>"Brute", "description"=>nil, "path"=>"brute", "default_branch"=>nil, "owner"=>#<Gitlab::ObjectifiedHash:0x00000002331600 @data={"id"=>1, "email"=>"john@example.com", "name"=>"John Smith", "blocked"=>false, "created_at"=>"2012-09-17T09:41:56Z"}>, "private"=>true, "issues_enabled"=>true, "merge_requests_enabled"=>true, "wall_enabled"=>true, "wiki_enabled"=>true, "created_at"=>"2012-09-17T09:41:56Z"}>, #<Gitlab::ObjectifiedHash:0x000000023450d8 @data={"id"=>2, "code"=>"mozart", "name"=>"Mozart", "description"=>nil, "path"=>"mozart", "default_branch"=>nil, "owner"=>#<Gitlab::ObjectifiedHash:0x00000002344ca0 @data={"id"=>1, "email"=>"john@example.com", "name"=>"John Smith", "blocked"=>false, "created_at"=>"2012-09-17T09:41:56Z"}>, "private"=>true, "issues_enabled"=>true, "merge_requests_enabled"=>true, "wall_enabled"=>true, "wiki_enabled"=>true, "created_at"=>"2012-09-17T09:41:57Z"}>, #<Gitlab::ObjectifiedHash:0x00000002344958 @data={"id"=>3, "code"=>"gitlab", "name"=>"Gitlab", "description"=>nil, "path"=>"gitlab", "default_branch"=>nil, "owner"=>#<Gitlab::ObjectifiedHash:0x000000023447a0 @data={"id"=>1, "email"=>"john@example.com", "name"=>"John Smith", "blocked"=>false, "created_at"=>"2012-09-17T09:41:56Z"}>, "private"=>true, "issues_enabled"=>true, "merge_requests_enabled"=>true, "wall_enabled"=>true, "wiki_enabled"=>true, "created_at"=>"2012-09-17T09:41:58Z"}>]
# initialize a new client
g = Gitlab.client(:endpoint => 'https://api.example.com', :private_token => 'qEsq1pt6HJPaNciie3MG')
# => #<Gitlab::Client:0x00000001e62408 @endpoint="https://api.example.com", @private_token="qEsq1pt6HJPaNciie3MG", @user_agent="Gitlab Ruby Gem 2.0.0">
# get a user
user = g.user
# => #<Gitlab::ObjectifiedHash:0x00000002217990 @data={"id"=>1, "email"=>"john@example.com", "name"=>"John Smith", "bio"=>nil, "skype"=>"", "linkedin"=>"", "twitter"=>"john", "dark_scheme"=>false, "theme_id"=>1, "blocked"=>false, "created_at"=>"2012-09-17T09:41:56Z"}>
# get a user's email
user.email
# => "john@example.com"
# set a sudo mode to perform API calls as another user
Gitlab.sudo = 'other_user'
# => "other_user"
# disable a sudo mode
Gitlab.sudo = nil
# => nil
```
For more information, refer to [documentation](http://rubydoc.info/gems/gitlab/frames).
## CLI
Usage examples:
```sh
# list users
gitlab users
# get current user
gitlab user
# get a user
gitlab user 2
# filter output
gitlab user --only=id,username
gitlab user --except=email,bio
```
## CLI Shell
Usage examples:
```sh
# start shell session
gitlab shell
# list available commands
gitlab> help
# list groups
gitlab> groups
# protect a branch
gitlab> protect_branch 1 master
```
For more information, refer to [website](http://narkoz.github.io/gitlab).
## License
Released under the BSD 2-clause license. See LICENSE.txt for details.

9
lib/gitlab-cli/Rakefile Normal file
View File

@ -0,0 +1,9 @@
require "bundler/gem_tasks"
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |spec|
spec.pattern = FileList['spec/**/*_spec.rb']
spec.rspec_opts = ['--color', '--format d']
end
task :default => :spec

View File

@ -0,0 +1,7 @@
#!/usr/bin/env ruby
$:.unshift File.expand_path('../../lib', __FILE__)
require 'gitlab/cli'
Gitlab::CLI.start(ARGV)

View File

@ -0,0 +1,26 @@
# -*- encoding: utf-8 -*-
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'gitlab/version'
Gem::Specification.new do |gem|
gem.name = "gitlab"
gem.version = Gitlab::VERSION
gem.authors = ["Nihad Abbasov"]
gem.email = ["mail@narkoz.me"]
gem.description = %q{Ruby client and CLI for GitLab API}
gem.summary = %q{A Ruby wrapper and CLI for the GitLab API}
gem.homepage = "https://github.com/narkoz/gitlab"
gem.files = `git ls-files`.split($/)
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.require_paths = ["lib"]
gem.add_runtime_dependency 'httparty'
gem.add_runtime_dependency 'terminal-table'
gem.add_development_dependency 'rake'
gem.add_development_dependency 'rspec'
gem.add_development_dependency 'webmock'
end

View File

@ -0,0 +1,37 @@
require 'gitlab/version'
require 'gitlab/objectified_hash'
require 'gitlab/configuration'
require 'gitlab/error'
require 'gitlab/request'
require 'gitlab/api'
require 'gitlab/client'
module Gitlab
extend Configuration
# Alias for Gitlab::Client.new
#
# @return [Gitlab::Client]
def self.client(options={})
Gitlab::Client.new(options)
end
# Delegate to Gitlab::Client
def self.method_missing(method, *args, &block)
return super unless client.respond_to?(method)
client.send(method, *args, &block)
end
# Delegate to Gitlab::Client
def self.respond_to?(method)
return client.respond_to?(method) || super
end
# Returns an unsorted array of available client methods.
#
# @return [Array<Symbol>]
def self.actions
hidden = /endpoint|private_token|user_agent|sudo|get|post|put|\Adelete\z|validate|set_request_defaults/
(Gitlab::Client.instance_methods - Object.methods).reject {|e| e[hidden]}
end
end

View File

@ -0,0 +1,17 @@
module Gitlab
# @private
class API < Request
# @private
attr_accessor(*Configuration::VALID_OPTIONS_KEYS)
# Creates a new API.
# @raise [Error:MissingCredentials]
def initialize(options={})
options = Gitlab.options.merge(options)
Configuration::VALID_OPTIONS_KEYS.each do |key|
send("#{key}=", options[key])
end
set_request_defaults @endpoint, @private_token, @sudo
end
end
end

View File

@ -0,0 +1,47 @@
require 'gitlab'
require 'terminal-table/import'
require_relative 'cli_helpers'
require_relative 'shell'
class Gitlab::CLI
extend Helpers
def self.start(args)
command = args.shift.strip rescue 'help'
run(command, args)
end
def self.run(cmd, args=[])
case cmd
when 'help'
puts actions_table
when 'info'
endpoint = Gitlab.endpoint ? Gitlab.endpoint : 'not set'
private_token = Gitlab.private_token ? Gitlab.private_token : 'not set'
puts "Gitlab endpoint is #{endpoint}"
puts "Gitlab private token is #{private_token}"
puts "Ruby Version is #{RUBY_VERSION}"
puts "Gitlab Ruby Gem #{Gitlab::VERSION}"
when '-v', '--version'
puts "Gitlab Ruby Gem #{Gitlab::VERSION}"
when 'shell'
Gitlab::Shell.start
else
unless valid_command?(cmd)
puts "Unknown command. Run `gitlab help` for a list of available commands."
exit(1)
end
if args.any? && (args.last.start_with?('--only=') || args.last.start_with?('--except='))
command_args = args[0..-2]
else
command_args = args
end
confirm_command(cmd)
data = gitlab_helper(cmd, command_args) { exit(1) }
output_table(cmd, args, data)
end
end
end

View File

@ -0,0 +1,175 @@
class Gitlab::CLI
# Defines methods related to CLI output and formatting.
module Helpers
extend self
# Returns filtered required fields.
#
# @return [Array]
def required_fields(args)
if args.any? && args.last.start_with?('--only=')
args.last.gsub('--only=', '').split(',')
else
[]
end
end
# Returns filtered excluded fields.
#
# @return [Array]
def excluded_fields(args)
if args.any? && args.last.start_with?('--except=')
args.last.gsub('--except=', '').split(',')
else
[]
end
end
# Confirms command is valid.
#
# @return [Boolean]
def valid_command?(cmd)
command = cmd.is_a?(Symbol) ? cmd : cmd.to_sym
Gitlab.actions.include?(command)
end
# Confirms command with a desctructive action.
#
# @return [String]
def confirm_command(cmd)
if cmd.start_with?('remove_') || cmd.start_with?('delete_')
puts "Are you sure? (y/n)"
if %w(y yes).include?($stdin.gets.to_s.strip.downcase)
puts 'Proceeding..'
else
puts 'Command aborted.'
exit(1)
end
end
end
# Table with available commands.
#
# @return [String]
def actions_table
client = Gitlab::Client.new(endpoint: '')
actions = Gitlab.actions
methods = []
actions.each do |action|
methods << {
name: action,
owner: client.method(action).owner.to_s.gsub('Gitlab::Client::', '')
}
end
owners = methods.map {|m| m[:owner]}.uniq.sort
methods_c = methods.group_by {|m| m[:owner]}
methods_c = methods_c.map {|_, v| [_, v.sort_by {|hv| hv[:name]}] }
methods_c = Hash[methods_c.sort_by(&:first).map {|k, v| [k, v]}]
max_column_length = methods_c.values.max_by(&:size).size
rows = max_column_length.times.map do |i|
methods_c.keys.map do |key|
methods_c[key][i] ? methods_c[key][i][:name] : ''
end
end
table do |t|
t.title = "Available commands (#{actions.size} total)"
t.headings = owners
rows.each do |row|
t.add_row row
end
end
end
# Decides which table to use.
#
# @return [String]
def output_table(cmd, args, data)
case data
when Gitlab::ObjectifiedHash
puts single_record_table(data, cmd, args)
when Array
puts multiple_record_table(data, cmd, args)
else
puts data.inspect
end
end
# Table for a single record.
#
# @return [String]
def single_record_table(data, cmd, args)
hash = data.to_h
keys = hash.keys.sort {|x, y| x.to_s <=> y.to_s }
keys = keys & required_fields(args) if required_fields(args).any?
keys = keys - excluded_fields(args)
table do |t|
t.title = "Gitlab.#{cmd} #{args.join(', ')}"
keys.each_with_index do |key, index|
case value = hash[key]
when Hash
value = 'Hash'
when nil
value = 'null'
end
t.add_row [key, value]
t.add_separator unless keys.size - 1 == index
end
end
end
# Table for multiple records.
#
# @return [String]
def multiple_record_table(data, cmd, args)
return 'No data' if data.empty?
arr = data.map(&:to_h)
keys = arr.first.keys.sort {|x, y| x.to_s <=> y.to_s }
keys = keys & required_fields(args) if required_fields(args).any?
keys = keys - excluded_fields(args)
table do |t|
t.title = "Gitlab.#{cmd} #{args.join(', ')}"
t.headings = keys
arr.each_with_index do |hash, index|
values = []
keys.each do |key|
case value = hash[key]
when Hash
value = 'Hash'
when nil
value = 'null'
end
values << value
end
t.add_row values
t.add_separator unless arr.size - 1 == index
end
end
end
# Helper function to call Gitlab commands with args.
def gitlab_helper(cmd, args=[])
begin
data = args.any? ? Gitlab.send(cmd, *args) : Gitlab.send(cmd)
rescue => e
puts e.message
yield if block_given?
end
data
end
end
end

View File

@ -0,0 +1,18 @@
module Gitlab
# Wrapper for the Gitlab REST API.
class Client < API
Dir[File.expand_path('../client/*.rb', __FILE__)].each {|f| require f}
include Branches
include Groups
include Issues
include MergeRequests
include Milestones
include Notes
include Projects
include Repositories
include Snippets
include SystemHooks
include Users
end
end

View File

@ -0,0 +1,79 @@
class Gitlab::Client
# Defines methods related to repositories.
module Branches
# Gets a list of project repositiory branches.
#
# @example
# Gitlab.branches(42)
#
# @param [Integer] project The ID of a project.
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def branches(project, options={})
get("/projects/#{project}/repository/branches", :query => options)
end
alias_method :repo_branches, :branches
# Gets information about a repository branch.
#
# @example
# Gitlab.branch(3, 'api')
# Gitlab.repo_branch(5, 'master')
#
# @param [Integer] project The ID of a project.
# @param [String] branch The name of the branch.
# @return [Gitlab::ObjectifiedHash]
def branch(project, branch)
get("/projects/#{project}/repository/branches/#{branch}")
end
alias_method :repo_branch, :branch
# Protects a repository branch.
#
# @example
# Gitlab.protect_branch(3, 'api')
# Gitlab.repo_protect_branch(5, 'master')
#
# @param [Integer] project The ID of a project.
# @param [String] branch The name of the branch.
# @return [Gitlab::ObjectifiedHash]
def protect_branch(project, branch)
put("/projects/#{project}/repository/branches/#{branch}/protect")
end
alias_method :repo_protect_branch, :protect_branch
# Unprotects a repository branch.
#
# @example
# Gitlab.unprotect_branch(3, 'api')
# Gitlab.repo_unprotect_branch(5, 'master')
#
# @param [Integer] project The ID of a project.
# @param [String] branch The name of the branch.
# @return [Gitlab::ObjectifiedHash]
def unprotect_branch(project, branch)
put("/projects/#{project}/repository/branches/#{branch}/unprotect")
end
alias_method :repo_unprotect_branch, :unprotect_branch
# Creates a repository branch. Requires Gitlab >= 6.8.x
#
# @example
# Gitlab.create_branch(3, 'api')
# Gitlab.repo_create_branch(5, 'master')
#
# @param [Integer] project The ID of a project.
# @param [String] branch The name of the new branch.
# @param [String] ref Create branch from commit sha or existing branch
# @return [Gitlab::ObjectifiedHash]
def create_branch(project, branch, ref)
post("/projects/#{project}/repository/branches",:body => {:branch_name => branch, :ref => ref})
end
alias_method :repo_create_branch, :create_branch
end
end

View File

@ -0,0 +1,88 @@
class Gitlab::Client
# Defines methods related to groups.
module Groups
# Gets a list of groups.
#
# @example
# Gitlab.groups
# Gitlab.groups(:per_page => 40)
#
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def groups(options={})
get("/groups", :query => options)
end
# Gets a single group.
#
# @example
# Gitlab.group(42)
#
# @param [Integer] id The ID of a group.
# @return [Gitlab::ObjectifiedHash]
def group(id)
get("/groups/#{id}")
end
# Creates a new group.
#
# @param [String] name The name of a group.
# @param [String] path The path of a group.
# @return [Gitlab::ObjectifiedHash] Information about created group.
def create_group(name, path)
body = {:name => name, :path => path}
post("/groups", :body => body)
end
# Get a list of group members.
#
# @example
# Gitlab.group_members(1)
# Gitlab.group_members(1, :per_page => 40)
#
# @param [Integer] id The ID of a group.
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def group_members(id, options={})
get("/groups/#{id}/members", :query => options)
end
# Adds a user to group.
#
# @example
# Gitlab.add_group_member(1, 2, 40)
#
# @param [Integer] team_id The group id to add a member to.
# @param [Integer] user_id The user id of the user to add to the team.
# @param [Integer] access_level Project access level.
# @return [Gitlab::ObjectifiedHash] Information about added team member.
def add_group_member(team_id, user_id, access_level)
post("/groups/#{team_id}/members", :body => {:user_id => user_id, :access_level => access_level})
end
# Removes user from user group.
#
# @example
# Gitlab.remove_group_member(1, 2)
#
# @param [Integer] team_id The group ID.
# @param [Integer] user_id The ID of a user.
# @return [Gitlab::ObjectifiedHash] Information about removed team member.
def remove_group_member(team_id, user_id)
delete("/groups/#{team_id}/members/#{user_id}")
end
# Transfers a project to a group
#
# @param [Integer] id The ID of a group.
# @param [Integer] project_id The ID of a project.
def transfer_project_to_group(id, project_id)
body = {:id => id, :project_id => project_id}
post("/groups/#{id}/projects/#{project_id}", :body => body)
end
end
end

View File

@ -0,0 +1,92 @@
class Gitlab::Client
# Defines methods related to issues.
module Issues
# Gets a list of user's issues.
# Will return a list of project's issues if project ID passed.
#
# @example
# Gitlab.issues
# Gitlab.issues(5)
# Gitlab.issues(:per_page => 40)
#
# @param [Integer] project The ID of a project.
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def issues(project=nil, options={})
if project.to_i.zero?
get("/issues", :query => options)
else
get("/projects/#{project}/issues", :query => options)
end
end
# Gets a single issue.
#
# @example
# Gitlab.issue(5, 42)
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of an issue.
# @return [Gitlab::ObjectifiedHash]
def issue(project, id)
get("/projects/#{project}/issues/#{id}")
end
# Creates a new issue.
#
# @param [Integer] project The ID of a project.
# @param [String] title The title of an issue.
# @param [Hash] options A customizable set of options.
# @option options [String] :description The description of an issue.
# @option options [Integer] :assignee_id The ID of a user to assign issue.
# @option options [Integer] :milestone_id The ID of a milestone to assign issue.
# @option options [String] :labels Comma-separated label names for an issue.
# @return [Gitlab::ObjectifiedHash] Information about created issue.
def create_issue(project, title, options={})
body = {:title => title}.merge(options)
post("/projects/#{project}/issues", :body => body)
end
# Updates an issue.
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of an issue.
# @param [Hash] options A customizable set of options.
# @option options [String] :title The title of an issue.
# @option options [String] :description The description of an issue.
# @option options [Integer] :assignee_id The ID of a user to assign issue.
# @option options [Integer] :milestone_id The ID of a milestone to assign issue.
# @option options [String] :labels Comma-separated label names for an issue.
# @option options [String] :state_event The state event of an issue ('close' or 'reopen').
# @return [Gitlab::ObjectifiedHash] Information about updated issue.
def edit_issue(project, id, options={})
put("/projects/#{project}/issues/#{id}", :body => options)
end
# Closes an issue.
#
# @example
# Gitlab.close_issue(3, 42)
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of an issue.
# @return [Gitlab::ObjectifiedHash] Information about closed issue.
def close_issue(project, id)
put("/projects/#{project}/issues/#{id}", :body => {:state_event => 'close'})
end
# Reopens an issue.
#
# @example
# Gitlab.reopen_issue(3, 42)
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of an issue.
# @return [Gitlab::ObjectifiedHash] Information about reopened issue.
def reopen_issue(project, id)
put("/projects/#{project}/issues/#{id}", :body => {:state_event => 'reopen'})
end
end
end

View File

@ -0,0 +1,107 @@
class Gitlab::Client
# Defines methods related to merge requests.
module MergeRequests
# Gets a list of project merge requests.
#
# @example
# Gitlab.merge_requests(5)
# Gitlab.merge_requests(:per_page => 40)
#
# @param [Integer] project The ID of a project.
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def merge_requests(project, options={})
get("/projects/#{project}/merge_requests", :query => options)
end
# Gets a single merge request.
#
# @example
# Gitlab.merge_request(5, 36)
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of a merge request.
# @return <Gitlab::ObjectifiedHash]
def merge_request(project, id)
get("/projects/#{project}/merge_request/#{id}")
end
# Creates a merge request.
#
# @example
# Gitlab.create_merge_request(5, 'New merge request',
# :source_branch => 'source_branch', :target_branch => 'target_branch')
# Gitlab.create_merge_request(5, 'New merge request',
# :source_branch => 'source_branch', :target_branch => 'target_branch', :assignee_id => 42)
#
# @param [Integer] project The ID of a project.
# @param [String] title The title of a merge request.
# @param [Hash] options A customizable set of options.
# @option options [String] :source_branch (required) The source branch name.
# @option options [String] :target_branch (required) The target branch name.
# @option options [Integer] :assignee_id (optional) The ID of a user to assign merge request.
# @return [Gitlab::ObjectifiedHash] Information about created merge request.
def create_merge_request(project, title, options={})
check_attributes!(options, [:source_branch, :target_branch])
body = {:title => title}.merge(options)
post("/projects/#{project}/merge_requests", :body => body)
end
# Updates a merge request.
#
# @example
# Gitlab.update_merge_request(5, 42, :title => 'New title')
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of a merge request.
# @param [Hash] options A customizable set of options.
# @option options [String] :title The title of a merge request.
# @option options [String] :source_branch The source branch name.
# @option options [String] :target_branch The target branch name.
# @option options [Integer] :assignee_id The ID of a user to assign merge request.
# @option options [String] :state_event New state (close|reopen|merge).
# @return [Gitlab::ObjectifiedHash] Information about updated merge request.
def update_merge_request(project, id, options={})
put("/projects/#{project}/merge_request/#{id}", :body => options)
end
# Adds a comment to a merge request.
#
# @example
# Gitlab.create_merge_request_comment(5, 1, "Awesome merge!")
# Gitlab.create_merge_request_comment('gitlab', 1, "Awesome merge!")
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of a merge request.
# @param [String] note The content of a comment.
# @return [Gitlab::ObjectifiedHash] Information about created merge request comment.
def create_merge_request_comment(project, id, note)
post("/projects/#{project}/merge_request/#{id}/comments", :body => {:note => note})
end
# Gets the comments on a merge request.
#
# @example
# Gitlab.merge_request_comments(5, 1)
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of a merge request.
# @return [Gitlab::ObjectifiedHash] The merge request's comments.
def merge_request_comments(project, id)
get("/projects/#{project}/merge_request/#{id}/comments")
end
private
def check_attributes!(options, attrs)
attrs.each do |attr|
unless options.has_key?(attr) || options.has_key?(attr.to_s)
raise Gitlab::Error::MissingAttributes.new("Missing '#{attr}' parameter")
end
end
end
end
end

View File

@ -0,0 +1,57 @@
class Gitlab::Client
# Defines methods related to milestones.
module Milestones
# Gets a list of project's milestones.
#
# @example
# Gitlab.milestones(5)
#
# @param [Integer] project The ID of a project.
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def milestones(project, options={})
get("/projects/#{project}/milestones", :query => options)
end
# Gets a single milestone.
#
# @example
# Gitlab.milestone(5, 36)
#
# @param [Integer, String] project The ID of a project.
# @param [Integer] id The ID of a milestone.
# @return [Gitlab::ObjectifiedHash]
def milestone(project, id)
get("/projects/#{project}/milestones/#{id}")
end
# Creates a new milestone.
#
# @param [Integer] project The ID of a project.
# @param [String] title The title of a milestone.
# @param [Hash] options A customizable set of options.
# @option options [String] :description The description of a milestone.
# @option options [String] :due_date The due date of a milestone.
# @return [Gitlab::ObjectifiedHash] Information about created milestone.
def create_milestone(project, title, options={})
body = {:title => title}.merge(options)
post("/projects/#{project}/milestones", :body => body)
end
# Updates a milestone.
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of a milestone.
# @param [Hash] options A customizable set of options.
# @option options [String] :title The title of a milestone.
# @option options [String] :description The description of a milestone.
# @option options [String] :due_date The due date of a milestone.
# @option options [String] :state_event The state of a milestone ('close' or 'activate').
# @return [Gitlab::ObjectifiedHash] Information about updated milestone.
def edit_milestone(project, id, options={})
put("/projects/#{project}/milestones/#{id}", :body => options)
end
end
end

View File

@ -0,0 +1,106 @@
class Gitlab::Client
# Defines methods related to notes.
module Notes
# Gets a list of projects notes.
#
# @example
# Gitlab.notes(5)
#
# @param [Integer] project The ID of a project.
# @return [Array<Gitlab::ObjectifiedHash>]
def notes(project)
get("/projects/#{project}/notes")
end
# Gets a list of notes for a issue.
#
# @example
# Gitlab.issue_notes(5, 10)
#
# @param [Integer] project The ID of a project.
# @param [Integer] issue The ID of an issue.
# @return [Array<Gitlab::ObjectifiedHash>]
def issue_notes(project, issue)
get("/projects/#{project}/issues/#{issue}/notes")
end
# Gets a list of notes for a snippet.
#
# @example
# Gitlab.snippet_notes(5, 1)
#
# @param [Integer] project The ID of a project.
# @param [Integer] snippet The ID of a snippet.
# @return [Array<Gitlab::ObjectifiedHash>]
def snippet_notes(project, snippet)
get("/projects/#{project}/snippets/#{snippet}/notes")
end
# Gets a single wall note.
#
# @example
# Gitlab.note(5, 15)
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of a note.
# @return [Gitlab::ObjectifiedHash]
def note(project, id)
get("/projects/#{project}/notes/#{id}")
end
# Gets a single issue note.
#
# @example
# Gitlab.issue_note(5, 10, 1)
#
# @param [Integer] project The ID of a project.
# @param [Integer] issue The ID of an issue.
# @param [Integer] id The ID of a note.
# @return [Gitlab::ObjectifiedHash]
def issue_note(project, issue, id)
get("/projects/#{project}/issues/#{issue}/notes/#{id}")
end
# Gets a single snippet note.
#
# @example
# Gitlab.snippet_note(5, 11, 3)
#
# @param [Integer] project The ID of a project.
# @param [Integer] snippet The ID of a snippet.
# @param [Integer] id The ID of an note.
# @return [Gitlab::ObjectifiedHash]
def snippet_note(project, snippet, id)
get("/projects/#{project}/snippets/#{snippet}/notes/#{id}")
end
# Creates a new wall note.
#
# @param [Integer] project The ID of a project.
# @param [String] body The body of a note.
# @return [Gitlab::ObjectifiedHash] Information about created note.
def create_note(project, body)
post("/projects/#{project}/notes", :body => {:body => body})
end
# Creates a new issue note.
#
# @param [Integer] project The ID of a project.
# @param [Integer] issue The ID of an issue.
# @param [String] body The body of a note.
# @return [Gitlab::ObjectifiedHash] Information about created note.
def create_issue_note(project, issue, body)
post("/projects/#{project}/issues/#{issue}/notes", :body => {:body => body})
end
# Creates a new snippet note.
#
# @param [Integer] project The ID of a project.
# @param [Integer] snippet The ID of a snippet.
# @param [String] body The body of a note.
# @return [Gitlab::ObjectifiedHash] Information about created note.
def create_snippet_note(project, snippet, body)
post("/projects/#{project}/snippets/#{snippet}/notes", :body => {:body => body})
end
end
end

View File

@ -0,0 +1,300 @@
class Gitlab::Client
# Defines methods related to projects.
module Projects
# Gets a list of projects owned by the authenticated user.
#
# @example
# Gitlab.projects
#
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @option options [String] :scope Scope of projects. 'owned' for list of projects owned by the authenticated user, 'all' to get all projects (admin only)
# @return [Array<Gitlab::ObjectifiedHash>]
def projects(options={})
if (options[:scope])
get("/projects/#{options[:scope]}", :query => options)
else
get("/projects", :query => options)
end
end
# Gets information about a project.
#
# @example
# Gitlab.project(3)
# Gitlab.project('gitlab')
#
# @param [Integer, String] id The ID or name of a project.
# @return [Gitlab::ObjectifiedHash]
def project(id)
get("/projects/#{id}")
end
# Gets a list of project events.
#
# @example
# Gitlab.project_events(42)
# Gitlab.project_events('gitlab')
#
# @param [Integer, String] project The ID or name of a project.
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def project_events(project, options={})
get("/projects/#{project}/events", :query => options)
end
# Creates a new project.
#
# @example
# Gitlab.create_project('gitlab')
# Gitlab.create_project('viking', :description => 'Awesome project')
# Gitlab.create_project('Red', :wall_enabled => false)
#
# @param [String] name The name of a project.
# @param [Hash] options A customizable set of options.
# @option options [String] :description The description of a project.
# @option options [String] :default_branch The default branch of a project.
# @option options [String] :group_id The group in which to create a project.
# @option options [String] :namespace_id The namespace in which to create a project.
# @option options [Boolean] :wiki_enabled The wiki integration for a project (0 = false, 1 = true).
# @option options [Boolean] :wall_enabled The wall functionality for a project (0 = false, 1 = true).
# @option options [Boolean] :issues_enabled The issues integration for a project (0 = false, 1 = true).
# @option options [Boolean] :snippets_enabled The snippets integration for a project (0 = false, 1 = true).
# @option options [Boolean] :merge_requests_enabled The merge requests functionality for a project (0 = false, 1 = true).
# @option options [Boolean] :public The setting for making a project public (0 = false, 1 = true).
# @option options [Integer] :user_id The user/owner id of a project.
# @return [Gitlab::ObjectifiedHash] Information about created project.
def create_project(name, options={})
url = options[:user_id] ? "/projects/user/#{options[:user_id]}" : "/projects"
post(url, :body => {:name => name}.merge(options))
end
# Deletes a project.
#
# @example
# Gitlab.delete_project(4)
#
# @param [Integer, String] id The ID or name of a project.
# @return [Gitlab::ObjectifiedHash] Information about deleted project.
def delete_project(id)
delete("/projects/#{id}")
end
# Gets a list of project team members.
#
# @example
# Gitlab.team_members(42)
# Gitlab.team_members('gitlab')
#
# @param [Integer, String] project The ID or name of a project.
# @param [Hash] options A customizable set of options.
# @option options [String] :query The search query.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def team_members(project, options={})
get("/projects/#{project}/members", :query => options)
end
# Gets a project team member.
#
# @example
# Gitlab.team_member('gitlab', 2)
#
# @param [Integer, String] project The ID or name of a project.
# @param [Integer] id The ID of a project team member.
# @return [Gitlab::ObjectifiedHash]
def team_member(project, id)
get("/projects/#{project}/members/#{id}")
end
# Adds a user to project team.
#
# @example
# Gitlab.add_team_member('gitlab', 2, 40)
#
# @param [Integer, String] project The ID or name of a project.
# @param [Integer] id The ID of a user.
# @param [Integer] access_level The access level to project.
# @param [Hash] options A customizable set of options.
# @return [Gitlab::ObjectifiedHash] Information about added team member.
def add_team_member(project, id, access_level)
post("/projects/#{project}/members", :body => {:user_id => id, :access_level => access_level})
end
# Updates a team member's project access level.
#
# @example
# Gitlab.edit_team_member('gitlab', 3, 20)
#
# @param [Integer, String] project The ID or name of a project.
# @param [Integer] id The ID of a user.
# @param [Integer] access_level The access level to project.
# @param [Hash] options A customizable set of options.
# @return [Array<Gitlab::ObjectifiedHash>] Information about updated team member.
def edit_team_member(project, id, access_level)
put("/projects/#{project}/members/#{id}", :body => {:access_level => access_level})
end
# Removes a user from project team.
#
# @example
# Gitlab.remove_team_member('gitlab', 2)
#
# @param [Integer, String] project The ID or name of a project.
# @param [Integer] id The ID of a user.
# @param [Hash] options A customizable set of options.
# @return [Gitlab::ObjectifiedHash] Information about removed team member.
def remove_team_member(project, id)
delete("/projects/#{project}/members/#{id}")
end
# Gets a list of project hooks.
#
# @example
# Gitlab.project_hooks(42)
# Gitlab.project_hooks('gitlab')
#
# @param [Integer, String] project The ID or name of a project.
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def project_hooks(project, options={})
get("/projects/#{project}/hooks", :query => options)
end
# Gets a project hook.
#
# @example
# Gitlab.project_hook(42, 5)
# Gitlab.project_hook('gitlab', 5)
#
# @param [Integer, String] project The ID or name of a project.
# @param [Integer] id The ID of a hook.
# @return [Gitlab::ObjectifiedHash]
def project_hook(project, id)
get("/projects/#{project}/hooks/#{id}")
end
# Adds a new hook to the project.
#
# @example
# Gitlab.add_project_hook(42, 'https://api.example.net/v1/webhooks/ci')
#
# @param [Integer, String] project The ID or name of a project.
# @param [String] url The hook URL.
# @param [Hash] options Events list (`{push_events: true, merge_requests_events: false}`).
# @return [Gitlab::ObjectifiedHash] Information about added hook.
def add_project_hook(project, url, options = {})
available_events = [:push_events, :merge_requests_events, :issues_events]
passed_events = available_events.select { |event| options[event] }
events = Hash[passed_events.map { |event| [event, options[event]] }]
post("/projects/#{project}/hooks", :body => {:url => url}.merge(events))
end
# Updates a project hook URL.
#
# @example
# Gitlab.edit_project_hook(42, 1, 'https://api.example.net/v1/webhooks/ci')
#
# @param [Integer, String] project The ID or name of a project.
# @param [Integer] id The ID of the hook.
# @param [String] url The hook URL.
# @return [Gitlab::ObjectifiedHash] Information about updated hook.
def edit_project_hook(project, id, url)
put("/projects/#{project}/hooks/#{id}", :body => {:url => url})
end
# Deletes a hook from project.
#
# @example
# Gitlab.delete_project_hook('gitlab', 4)
#
# @param [Integer, String] project The ID or name of a project.
# @param [String] id The ID of the hook.
# @return [Gitlab::ObjectifiedHash] Information about deleted hook.
def delete_project_hook(project, id)
delete("/projects/#{project}/hooks/#{id}")
end
# Mark this project as forked from the other
#
# @example
# Gitlab.make_forked(42, 24)
#
# @param [Integer, String] project The ID or name of a project.
# @param [Integer] id The ID of the project it is forked from.
# @return [Gitlab::ObjectifiedHash] Information about the forked project.
def make_forked_from(project, id)
post("/projects/#{project}/fork/#{id}")
end
# Remove a forked_from relationship for a project.
#
# @example
# Gitlab.remove_forked(42)
#
# @param [Integer, String] project The ID or name of a project.
# @param [Integer] project The ID of the project it is forked from
# @return [Gitlab::ObjectifiedHash] Information about the forked project.
def remove_forked(project)
delete("/projects/#{project}/fork")
end
# Gets a project deploy keys.
#
# @example
# Gitlab.deploy_keys(42)
#
# @param [Integer] project The ID of a project.
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def deploy_keys(project, options={})
get("/projects/#{project}/keys", :query => options)
end
# Gets a single project deploy key.
#
# @example
# Gitlab.deploy_key(42, 1)
#
# @param [Integer, String] project The ID of a project.
# @param [Integer] id The ID of a deploy key.
# @return [Gitlab::ObjectifiedHash]
def deploy_key(project, id)
get("/projects/#{project}/keys/#{id}")
end
# Creates a new deploy key.
#
# @example
# Gitlab.create_deploy_key(42, 'My Key', 'Key contents')
#
# @param [Integer] project The ID of a project.
# @param [String] title The title of a deploy key.
# @param [String] key The content of a deploy key.
# @return [Gitlab::ObjectifiedHash] Information about created deploy key.
def create_deploy_key(project, title, key)
post("/projects/#{project}/keys", body: {title: title, key: key})
end
# Deletes a deploy key from project.
#
# @example
# Gitlab.delete_deploy_key(42, 1)
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of a deploy key.
# @return [Gitlab::ObjectifiedHash] Information about deleted deploy key.
def delete_deploy_key(project, id)
delete("/projects/#{project}/keys/#{id}")
end
end
end

View File

@ -0,0 +1,89 @@
class Gitlab::Client
# Defines methods related to repositories.
module Repositories
def trees(project, options={})
get "/projects/#{project}/repository/tree", query: options
end
alias_method :repo_trees, :trees
def files(project, file_path, ref)
get "/projects/#{project}/repository/files", query: {file_path: file_path, ref: ref}
end
alias_method :repo_files, :files
# Gets a list of project repository tags.
#
# @example
# Gitlab.tags(42)
#
# @param [Integer] project The ID of a project.
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def tags(project, options={})
get("/projects/#{project}/repository/tags", :query => options)
end
alias_method :repo_tags, :tags
# Creates a new project repository tag.
#
# @example
# Gitlab.create_tag(42,'new_tag','master')
#
# @param [Integer] project The ID of a project.
# @param [String] tag_name The name of the new tag.
# @param [String] ref The ref (commit sha, branch name, or another tag) the tag will point to.
# @return [Gitlab::ObjectifiedHash]
def create_tag(project, tag_name, ref)
post("/projects/#{project}/repository/tags", body: {tag_name: tag_name, ref: ref})
end
alias_method :repo_create_tag, :create_tag
# Gets a list of project commits.
#
# @example
# Gitlab.commits('viking')
# Gitlab.repo_commits('gitlab', :ref_name => 'api')
#
# @param [Integer] project The ID of a project.
# @param [Hash] options A customizable set of options.
# @option options [String] :ref_name The branch or tag name of a project repository.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def commits(project, options={})
get("/projects/#{project}/repository/commits", :query => options)
end
alias_method :repo_commits, :commits
# Gets a specific commit identified by the commit hash or name of a branch or tag.
#
# @example
# Gitlab.commit(42, '6104942438c14ec7bd21c6cd5bd995272b3faff6')
# Gitlab.repo_commit(3, 'ed899a2f4b50b4370feeea94676502b42383c746')
#
# @param [Integer] project The ID of a project.
# @param [String] sha The commit hash or name of a repository branch or tag
# @return [Gitlab::ObjectifiedHash]
def commit(project, sha)
get("/projects/#{project}/repository/commits/#{sha}")
end
alias_method :repo_commit, :commit
# Get the diff of a commit in a project.
#
# @example
# Gitlab.commit_diff(42, '6104942438c14ec7bd21c6cd5bd995272b3faff6')
# Gitlab.repo_commit_diff(3, 'ed899a2f4b50b4370feeea94676502b42383c746')
#
# @param [Integer] project The ID of a project.
# @param [String] sha The name of a repository branch or tag or if not given the default branch.
# @return [Gitlab::ObjectifiedHash]
def commit_diff(project, sha)
get("/projects/#{project}/repository/commits/#{sha}/diff")
end
alias_method :repo_commit_diff, :commit_diff
end
end

View File

@ -0,0 +1,86 @@
class Gitlab::Client
# Defines methods related to snippets.
module Snippets
# Gets a list of project's snippets.
#
# @example
# Gitlab.snippets(42)
#
# @param [Integer] project The ID of a project.
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Gitlab::ObjectifiedHash]
def snippets(project, options={})
get("/projects/#{project}/snippets", :query => options)
end
# Gets information about a snippet.
#
# @example
# Gitlab.snippet(2, 14)
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of a snippet.
# @return [Gitlab::ObjectifiedHash]
def snippet(project, id)
get("/projects/#{project}/snippets/#{id}")
end
# Creates a new snippet.
#
# @example
# Gitlab.create_snippet(42, {:title => 'REST', :file_name => 'api.rb', :code => 'some code'})
#
# @param [Integer] project The ID of a project.
# @param [Hash] options A customizable set of options.
# @option options [String] :title (required) The title of a snippet.
# @option options [String] :file_name (required) The name of a snippet file.
# @option options [String] :code (required) The content of a snippet.
# @option options [String] :lifetime (optional) The expiration date of a snippet.
# @return [Gitlab::ObjectifiedHash] Information about created snippet.
def create_snippet(project, options={})
check_attributes!(options, [:title, :file_name, :code])
post("/projects/#{project}/snippets", :body => options)
end
# Updates a snippet.
#
# @example
# Gitlab.edit_snippet(42, 34, :file_name => 'README.txt')
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of a snippet.
# @param [Hash] options A customizable set of options.
# @option options [String] :title The title of a snippet.
# @option options [String] :file_name The name of a snippet file.
# @option options [String] :code The content of a snippet.
# @option options [String] :lifetime The expiration date of a snippet.
# @return [Gitlab::ObjectifiedHash] Information about updated snippet.
def edit_snippet(project, id, options={})
put("/projects/#{project}/snippets/#{id}", :body => options)
end
# Deletes a snippet.
#
# @example
# Gitlab.delete_snippet(2, 14)
#
# @param [Integer] project The ID of a project.
# @param [Integer] id The ID of a snippet.
# @return [Gitlab::ObjectifiedHash] Information about deleted snippet.
def delete_snippet(project, id)
delete("/projects/#{project}/snippets/#{id}")
end
private
def check_attributes!(options, attrs)
attrs.each do |attr|
unless options.has_key?(attr) || options.has_key?(attr.to_s)
raise Gitlab::Error::MissingAttributes.new("Missing '#{attr}' parameter")
end
end
end
end
end

View File

@ -0,0 +1,58 @@
class Gitlab::Client
# Defines methods related to system hooks.
module SystemHooks
# Gets a list of system hooks.
#
# @example
# Gitlab.hooks
# Gitlab.system_hooks
#
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def hooks(options={})
get("/hooks", query: options)
end
alias_method :system_hooks, :hooks
# Adds a new system hook.
#
# @example
# Gitlab.add_hook('http://example.com/hook')
# Gitlab.add_system_hook('https://api.example.net/v1/hook')
#
# @param [String] url The hook URL.
# @return [Gitlab::ObjectifiedHash]
def add_hook(url)
post("/hooks", :body => {:url => url})
end
alias_method :add_system_hook, :add_hook
# Tests a system hook.
#
# @example
# Gitlab.hook(3)
# Gitlab.system_hook(12)
#
# @param [Integer] id The ID of a system hook.
# @return [Array<Gitlab::ObjectifiedHash>]
def hook(id)
get("/hooks/#{id}")
end
alias_method :system_hook, :hook
# Deletes a new system hook.
#
# @example
# Gitlab.delete_hook(3)
# Gitlab.delete_system_hook(12)
#
# @param [Integer] id The ID of a system hook.
# @return [Gitlab::ObjectifiedHash]
def delete_hook(id)
delete("/hooks/#{id}")
end
alias_method :delete_system_hook, :delete_hook
end
end

View File

@ -0,0 +1,123 @@
class Gitlab::Client
# Defines methods related to users.
module Users
# Gets a list of users.
#
# @example
# Gitlab.users
#
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def users(options={})
get("/users", :query => options)
end
# Gets information about a user.
# Will return information about an authorized user if no ID passed.
#
# @example
# Gitlab.user
# Gitlab.user(2)
#
# @param [Integer] id The ID of a user.
# @return [Gitlab::ObjectifiedHash]
def user(id=nil)
id.to_i.zero? ? get("/user") : get("/users/#{id}")
end
# Creates a new user.
# Requires authentication from an admin account.
#
# @param [String] email The email of a user.
# @param [String] password The password of a user.
# @param [Hash] options A customizable set of options.
# @option options [String] :name The name of a user. Defaults to email.
# @option options [String] :skype The skype of a user.
# @option options [String] :linkedin The linkedin of a user.
# @option options [String] :twitter The twitter of a user.
# @option options [Integer] :projects_limit The limit of projects for a user.
# @return [Gitlab::ObjectifiedHash] Information about created user.
def create_user(email, password, options={})
body = {:email => email, :password => password, :name => email}.merge(options)
post("/users", :body => body)
end
# Updates a user.
#
# @param [Integer] id The ID of a user.
# @param [Hash] options A customizable set of options.
# @option options [String] email The email of a user.
# @option options [String] password The password of a user.
# @option options [String] :name The name of a user. Defaults to email.
# @option options [String] :skype The skype of a user.
# @option options [String] :linkedin The linkedin of a user.
# @option options [String] :twitter The twitter of a user.
# @option options [Integer] :projects_limit The limit of projects for a user.
# @return [Gitlab::ObjectifiedHash] Information about created user.
def edit_user(user_id, options={})
put("/users/#{user_id}", :body => options)
end
# Creates a new user session.
#
# @example
# Gitlab.session('jack@example.com', 'secret12345')
#
# @param [String] email The email of a user.
# @param [String] password The password of a user.
# @return [Gitlab::ObjectifiedHash]
# @note This method doesn't require private_token to be set.
def session(email, password)
post("/session", :body => {:email => email, :password => password})
end
# Gets a list of user's SSH keys.
#
# @example
# Gitlab.ssh_keys
#
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @return [Array<Gitlab::ObjectifiedHash>]
def ssh_keys(options={})
get("/user/keys", :query => options)
end
# Gets information about SSH key.
#
# @example
# Gitlab.ssh_key(1)
#
# @param [Integer] id The ID of a user's SSH key.
# @return [Gitlab::ObjectifiedHash]
def ssh_key(id)
get("/user/keys/#{id}")
end
# Creates a new SSH key.
#
# @example
# Gitlab.create_ssh_key('key title', 'key body')
#
# @param [String] title The title of an SSH key.
# @param [String] key The SSH key body.
# @return [Gitlab::ObjectifiedHash] Information about created SSH key.
def create_ssh_key(title, key)
post("/user/keys", :body => {:title => title, :key => key})
end
# Deletes an SSH key.
#
# @example
# Gitlab.delete_ssh_key(1)
#
# @param [Integer] id The ID of a user's SSH key.
# @return [Gitlab::ObjectifiedHash] Information about deleted SSH key.
def delete_ssh_key(id)
delete("/user/keys/#{id}")
end
end
end

View File

@ -0,0 +1,39 @@
module Gitlab
# Defines constants and methods related to configuration.
module Configuration
# An array of valid keys in the options hash when configuring a Gitlab::API.
VALID_OPTIONS_KEYS = [:endpoint, :private_token, :user_agent, :sudo, :httparty].freeze
# The user agent that will be sent to the API endpoint if none is set.
DEFAULT_USER_AGENT = "Gitlab Ruby Gem #{Gitlab::VERSION}".freeze
# @private
attr_accessor(*VALID_OPTIONS_KEYS)
# Sets all configuration options to their default values
# when this module is extended.
def self.extended(base)
base.reset
end
# Convenience method to allow configuration options to be set in a block.
def configure
yield self
end
# Creates a hash of options and their values.
def options
VALID_OPTIONS_KEYS.inject({}) do |option, key|
option.merge!(key => send(key))
end
end
# Resets all configuration options to the defaults.
def reset
self.endpoint = ENV['GITLAB_API_ENDPOINT']
self.private_token = ENV['GITLAB_API_PRIVATE_TOKEN']
self.sudo = nil
self.user_agent = DEFAULT_USER_AGENT
end
end
end

View File

@ -0,0 +1,42 @@
module Gitlab
module Error
# Custom error class for rescuing from all Gitlab errors.
class Error < StandardError; end
# Raise when attributes are missing.
class MissingAttributes < Error; end
# Raised when API endpoint credentials not configured.
class MissingCredentials < Error; end
# Raised when impossible to parse response body.
class Parsing < Error; end
# Raised when API endpoint returns the HTTP status code 400.
class BadRequest < Error; end
# Raised when API endpoint returns the HTTP status code 401.
class Unauthorized < Error; end
# Raised when API endpoint returns the HTTP status code 403.
class Forbidden < Error; end
# Raised when API endpoint returns the HTTP status code 404.
class NotFound < Error; end
# Raised when API endpoint returns the HTTP status code 405.
class MethodNotAllowed < Error; end
# Raised when API endpoint returns the HTTP status code 409.
class Conflict < Error; end
# Raised when API endpoint returns the HTTP status code 500.
class InternalServerError < Error; end
# Raised when API endpoint returns the HTTP status code 502.
class BadGateway < Error; end
# Raised when API endpoint returns the HTTP status code 503.
class ServiceUnavailable < Error; end
end
end

View File

@ -0,0 +1,44 @@
require 'gitlab'
require 'gitlab/cli_helpers'
module Gitlab::Help
extend Gitlab::CLI::Helpers
def self.get_help(methods,cmd=nil)
help = ''
if cmd.nil? || cmd == 'help'
help = actions_table
else
ri_cmd = `which ri`.chomp
if $? == 0
namespace = methods.select {|m| m[:name] === cmd }.map {|m| m[:owner]+'.'+m[:name] }.shift
if namespace
begin
ri_output = `#{ri_cmd} -T #{namespace} 2>&1`.chomp
if $? == 0
ri_output.gsub!(/#{cmd}\((.*?)\)/, cmd+' \1')
ri_output.gsub!(/Gitlab\./, 'gitlab> ')
ri_output.gsub!(/Gitlab\..+$/, '')
ri_output.gsub!(/\,\s?/, ' ')
help = ri_output
else
help = "Ri docs not found for #{namespace}, please install the docs to use 'help'"
end
rescue => e
puts e.message
end
else
help = "Unknown command: #{cmd}"
end
else
help = "'ri' tool not found in your PATH, please install it to use the help."
end
end
puts help
end
end

View File

@ -0,0 +1,24 @@
module Gitlab
# Converts hashes to the objects.
class ObjectifiedHash
# Creates a new ObjectifiedHash object.
def initialize(hash)
@hash = hash
@data = hash.inject({}) do |data, (key,value)|
value = ObjectifiedHash.new(value) if value.is_a? Hash
data[key.to_s] = value
data
end
end
def to_hash
@hash
end
alias_method :to_h, :to_hash
# Delegate to ObjectifiedHash.
def method_missing(key)
@data.key?(key.to_s) ? @data[key.to_s] : nil
end
end
end

View File

@ -0,0 +1,113 @@
require 'httparty'
require 'json'
module Gitlab
# @private
class Request
include HTTParty
format :json
headers 'Accept' => 'application/json'
parser Proc.new { |body, _| parse(body) }
attr_accessor :private_token
# Converts the response body to an ObjectifiedHash.
def self.parse(body)
body = decode(body)
if body.is_a? Hash
ObjectifiedHash.new body
elsif body.is_a? Array
body.collect! { |e| ObjectifiedHash.new(e) }
else
raise Error::Parsing.new "Couldn't parse a response body"
end
end
# Decodes a JSON response into Ruby object.
def self.decode(response)
begin
JSON.load response
rescue JSON::ParserError
raise Error::Parsing.new "The response is not a valid JSON"
end
end
def get(path, options={})
set_httparty_config(options)
set_private_token_header(options)
validate self.class.get(path, options)
end
def post(path, options={})
set_httparty_config(options)
set_private_token_header(options, path)
validate self.class.post(path, options)
end
def put(path, options={})
set_httparty_config(options)
set_private_token_header(options)
validate self.class.put(path, options)
end
def delete(path, options={})
set_httparty_config(options)
set_private_token_header(options)
validate self.class.delete(path, options)
end
# Checks the response code for common errors.
# Returns parsed response for successful requests.
def validate(response)
case response.code
when 400; raise Error::BadRequest.new error_message(response)
when 401; raise Error::Unauthorized.new error_message(response)
when 403; raise Error::Forbidden.new error_message(response)
when 404; raise Error::NotFound.new error_message(response)
when 405; raise Error::MethodNotAllowed.new error_message(response)
when 409; raise Error::Conflict.new error_message(response)
when 500; raise Error::InternalServerError.new error_message(response)
when 502; raise Error::BadGateway.new error_message(response)
when 503; raise Error::ServiceUnavailable.new error_message(response)
end
response.parsed_response
end
# Sets a base_uri and default_params for requests.
# @raise [Error::MissingCredentials] if endpoint not set.
def set_request_defaults(endpoint, private_token, sudo=nil)
raise Error::MissingCredentials.new("Please set an endpoint to API") unless endpoint
@private_token = private_token
self.class.base_uri endpoint
self.class.default_params :sudo => sudo
self.class.default_params.delete(:sudo) if sudo.nil?
end
private
# Sets a PRIVATE-TOKEN header for requests.
# @raise [Error::MissingCredentials] if private_token not set.
def set_private_token_header(options, path=nil)
unless path == '/session'
raise Error::MissingCredentials.new("Please set a private_token for user") unless @private_token
options[:headers] = {'PRIVATE-TOKEN' => @private_token}
end
end
# Set HTTParty configuration
# @see https://github.com/jnunemaker/httparty
def set_httparty_config(options)
if self.httparty
options.merge!(self.httparty)
end
end
def error_message(response)
"Server responded with code #{response.code}, message: #{response.parsed_response.message}. " \
"Request URI: #{response.request.base_uri}#{response.request.path}"
end
end
end

Some files were not shown because too many files have changed in this diff Show More