-<%= render(:partial => 'revisions',
- :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %>
+
+ <%= render(:partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "scm" %>
diff --git a/app/views/repositories/committers.html.erb b/app/views/repositories/committers.html.erb
index ccb037e09..93350889d 100644
--- a/app/views/repositories/committers.html.erb
+++ b/app/views/repositories/committers.html.erb
@@ -1,4 +1,8 @@
-
<%= simple_format(l(:text_repository_usernames_mapping)) %>
diff --git a/app/views/repositories/diff.html.erb b/app/views/repositories/diff.html.erb
index a3afebfff..4e40bd5e8 100644
--- a/app/views/repositories/diff.html.erb
+++ b/app/views/repositories/diff.html.erb
@@ -1,4 +1,7 @@
-
<%= form_tag({:action => 'diff', :id => @project,
diff --git a/app/views/repositories/entry.html.erb b/app/views/repositories/entry.html.erb
index 5aea99dcc..bb2fa6dae 100644
--- a/app/views/repositories/entry.html.erb
+++ b/app/views/repositories/entry.html.erb
@@ -1,15 +1,17 @@
<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %>
+
+
+ <%= render :partial => 'navigation' %>
+
-
- <%= render :partial => 'navigation' %>
+
<%= l(:label_revision_path) %> :<%= @path %>
+
+ <%= render :partial => 'link_to_functions' %>
+
+ <%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %>
+
+ <% content_for :header_tags do %>
+ <%= stylesheet_link_tag "scm" %>
+ <% end %>
-
<%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %>
-
-<%= render :partial => 'link_to_functions' %>
-
-<%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %>
-
-<% content_for :header_tags do %>
-<%= stylesheet_link_tag "scm" %>
-<% end %>
diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb
index 28a260178..a1dd44cb7 100644
--- a/app/views/repositories/show.html.erb
+++ b/app/views/repositories/show.html.erb
@@ -1,83 +1,74 @@
<%= call_hook(:view_repositories_show_contextual, {:repository => @repository, :project => @project}) %>
-
版本库
+
<%= render :partial => 'breadcrumbs', :locals => {:path => @path, :kind => 'dir', :revision => @rev} %>
-
- <%= render :partial => 'breadcrumbs',
- :locals => {:path => @path, :kind => 'dir', :revision => @rev} %>
- <%= render :partial => 'navigation' %>
-
-
-
-
-
- <% if @repository.type.to_s=="Repository::Git" %>
- <%= @repos_url %>
- <% else %>
- <%= h @repository.url %>
- <% end %>
-
+ <% if @entries.nil? %>
+ <%# 未提交代码提示 %>
+
+ <% if @entries.nil? && authorize_for('repositories', 'browse') %>
+
+ 该版本库还没有上传代码!
+
+ <% end %>
+ <% if @repository.type.to_s=="Repository::Gitlab" %>
+ 版本库地址:<%= @repos_url %>
+ <% else %>
+ 版本库地址:<%= h @repository.url %>
+ <% end %>
+
+
+
+
+ <% else %>
+ <%= render :partial => 'navigation' %>
+
克隆网址:
+
+
+
+
+
+
+ <% if @changesets && !@changesets.empty? %>
+ <%= image_tag(url_to_avatar(@changesets_latest_coimmit.user), :width => "25", :height => "25", :class => "fl portraitRadius mt2 ml4 mr5") %>
+
<%=link_to @changesets_latest_coimmit.user, user_path(@changesets_latest_coimmit.user) %>
+ 提交于<%= time_tag(@changesets_latest_coimmit.committed_on) %>:
+ <%= @changesets_latest_coimmit.comments %>
+
+ <% end %>
+
+ <%= @repository.branches.count %> 个分支
+
-
- (<%= 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(' | ').html_safe %>)
-
-
+
+ <%=link_to @changesets_count, {:action => 'changes', :path => to_path_param(@path), :id => @project, :repository_id => @repository.identifier_param, :rev => @rev} %> 提交
+
+
+ <% 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? %>
-
- <%= l(:label_latest_revision_plural) %>
-
- <%= render :partial => 'revisions',
- :locals => {:project => @project, :path => @path,
- :revisions => @changesets, :entry => nil} %>
- <%# end %>
+<%= render_properties(@properties) %>
-
+<% 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 %>
-
- <% 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 %>
-
-
-
点击查看如何提交代码
-
+
如何提交代码
<% content_for :header_tags do %>
<%= stylesheet_link_tag "scm" %>
diff --git a/app/views/repositories/to_gitlab.html.erb b/app/views/repositories/to_gitlab.html.erb
new file mode 100644
index 000000000..d06c087ed
--- /dev/null
+++ b/app/views/repositories/to_gitlab.html.erb
@@ -0,0 +1,12 @@
+
+
+ <%= l(:label_repository_migrate_dec) %>
+
+<%= form_for(@repository, url: to_gitlab_project_repository_path(@project, @repository)) do |f| %>
+
+
+<% end %>
+
+ <%= l(:label_repository_name_dec) %>
+
+
\ No newline at end of file
diff --git a/config/application.rb b/config/application.rb
index 83ba21b05..90cc299c6 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -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
diff --git a/config/initializers/gitlab_config.rb b/config/initializers/gitlab_config.rb
new file mode 100644
index 000000000..75edc8eff
--- /dev/null
+++ b/config/initializers/gitlab_config.rb
@@ -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
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 2071529f0..2bed45103 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -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
diff --git a/config/locales/projects/zh.yml b/config/locales/projects/zh.yml
index dbce93219..bd31c4d79 100644
--- a/config/locales/projects/zh.yml
+++ b/config/locales/projects/zh.yml
@@ -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: 您还没有创建项目,您可以查看系统的其它项目!
\ No newline at end of file
+ label_home_non_project: 您还没有创建项目,您可以查看系统的其它项目!
+
+ # 版本库迁移
+ label_repository_migrate_dec: 注意:Trustie版本库近期进行了一次大的改造,历史版本需要转换成新的版本,输入新的版本库名,即可完成转换。 转换过程可能需要等待一段时间。
+ label_repository_name_dec: 版本库名仅小写字母(a-z)、数字、破折号(-)和下划线(_)可以使用,长度必须在 1 到 254 个字符之间,一旦保存,标识无法修改。
\ No newline at end of file
diff --git a/config/locales/zh.yml b/config/locales/zh.yml
index 5efe4a2cc..a7274c3c2 100644
--- a/config/locales/zh.yml
+++ b/config/locales/zh.yml
@@ -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} 个子页面和下级页面。您想进行那种操作?
diff --git a/config/routes.rb b/config/routes.rb
index fbce06e02..04dd05271 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -544,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
diff --git a/db/migrate/20150712063406_add_gitlab_user_id_to_users.rb b/db/migrate/20150712063406_add_gitlab_user_id_to_users.rb
new file mode 100644
index 000000000..f6e2d60cd
--- /dev/null
+++ b/db/migrate/20150712063406_add_gitlab_user_id_to_users.rb
@@ -0,0 +1,5 @@
+class AddGitlabUserIdToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :gid, :integer
+ end
+end
diff --git a/db/migrate/20151013023237_add_gpid_to_project.rb b/db/migrate/20151013023237_add_gpid_to_project.rb
new file mode 100644
index 000000000..bfa535253
--- /dev/null
+++ b/db/migrate/20151013023237_add_gpid_to_project.rb
@@ -0,0 +1,5 @@
+class AddGpidToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :gpid, :integer
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b18ffb544..7dab1b3a7 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1,1759 +1,1754 @@
-# encoding: UTF-8
-# This file is auto-generated from the current state of the database. Instead
-# of editing this file, please use the migrations feature of Active Record to
-# incrementally modify your database, and then regenerate this schema definition.
-#
-# Note that this schema.rb definition is the authoritative source for your
-# database schema. If you need to create the application database on another
-# system, you should be using db:schema:load, not running all the migrations
-# from scratch. The latter is a flawed and unsustainable approach (the more migrations
-# you'll amass, the slower it'll run and the greater likelihood for issues).
-#
-# It's strongly recommended to check this file into your version control system.
-
-ActiveRecord::Schema.define(:version => 20151022071804) do
-
- create_table "activities", :force => true do |t|
- t.integer "act_id", :null => false
- t.string "act_type", :null => false
- t.integer "user_id", :null => false
- t.integer "activity_container_id"
- t.string "activity_container_type", :default => ""
- t.datetime "created_at"
- end
-
- add_index "activities", ["act_id", "act_type"], :name => "index_activities_on_act_id_and_act_type"
- add_index "activities", ["user_id", "act_type"], :name => "index_activities_on_user_id_and_act_type"
- add_index "activities", ["user_id"], :name => "index_activities_on_user_id"
-
- create_table "activity_notifies", :force => true do |t|
- t.integer "activity_container_id"
- t.string "activity_container_type"
- t.integer "activity_id"
- t.string "activity_type"
- t.integer "notify_to"
- t.datetime "created_on"
- t.integer "is_read"
- end
-
- add_index "activity_notifies", ["activity_container_id", "activity_container_type"], :name => "index_an_activity_container_id"
- add_index "activity_notifies", ["created_on"], :name => "index_an_created_on"
- add_index "activity_notifies", ["notify_to"], :name => "index_an_notify_to"
-
- create_table "api_keys", :force => true do |t|
- t.string "access_token"
- t.datetime "expires_at"
- t.integer "user_id"
- t.boolean "active", :default => true
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- add_index "api_keys", ["access_token"], :name => "index_api_keys_on_access_token"
- add_index "api_keys", ["user_id"], :name => "index_api_keys_on_user_id"
-
- create_table "applied_projects", :force => true do |t|
- t.integer "project_id", :null => false
- t.integer "user_id", :null => false
- end
-
- create_table "apply_project_masters", :force => true do |t|
- t.integer "user_id"
- t.string "apply_type"
- t.integer "apply_id"
- t.integer "status"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "attachments", :force => true do |t|
- t.integer "container_id"
- t.string "container_type", :limit => 30
- t.string "filename", :default => "", :null => false
- t.string "disk_filename", :default => "", :null => false
- t.integer "filesize", :default => 0, :null => false
- t.string "content_type", :default => ""
- t.string "digest", :limit => 40, :default => "", :null => false
- t.integer "downloads", :default => 0, :null => false
- t.integer "author_id", :default => 0, :null => false
- t.datetime "created_on"
- t.string "description"
- t.string "disk_directory"
- t.integer "attachtype", :default => 1
- t.integer "is_public", :default => 1
- t.integer "copy_from"
- t.integer "quotes"
- end
-
- add_index "attachments", ["author_id"], :name => "index_attachments_on_author_id"
- add_index "attachments", ["container_id", "container_type"], :name => "index_attachments_on_container_id_and_container_type"
- add_index "attachments", ["created_on"], :name => "index_attachments_on_created_on"
-
- create_table "attachmentstypes", :force => true do |t|
- t.integer "typeId", :null => false
- t.string "typeName", :limit => 50
- end
-
- create_table "auth_sources", :force => true do |t|
- t.string "type", :limit => 30, :default => "", :null => false
- t.string "name", :limit => 60, :default => "", :null => false
- t.string "host", :limit => 60
- t.integer "port"
- t.string "account"
- t.string "account_password", :default => ""
- t.string "base_dn"
- t.string "attr_login", :limit => 30
- t.string "attr_firstname", :limit => 30
- t.string "attr_lastname", :limit => 30
- t.string "attr_mail", :limit => 30
- t.boolean "onthefly_register", :default => false, :null => false
- t.boolean "tls", :default => false, :null => false
- t.string "filter"
- t.integer "timeout"
- end
-
- add_index "auth_sources", ["id", "type"], :name => "index_auth_sources_on_id_and_type"
-
- create_table "biding_projects", :force => true do |t|
- t.integer "project_id"
- t.integer "bid_id"
- t.integer "user_id"
- t.string "description"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "reward"
- end
-
- create_table "bids", :force => true do |t|
- t.string "name"
- t.string "budget", :null => false
- t.integer "author_id"
- t.date "deadline"
- t.text "description"
- t.datetime "created_on", :null => false
- t.datetime "updated_on", :null => false
- t.integer "commit"
- t.integer "reward_type"
- t.integer "homework_type"
- t.integer "parent_id"
- t.string "password"
- t.integer "is_evaluation"
- t.integer "proportion", :default => 60
- t.integer "comment_status", :default => 0
- t.integer "evaluation_num", :default => 3
- t.integer "open_anonymous_evaluation", :default => 1
- end
-
- create_table "blog_comments", :force => true 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.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "blogs", :force => true 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.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "boards", :force => true do |t|
- t.integer "project_id", :null => false
- t.string "name", :default => "", :null => false
- t.string "description"
- t.integer "position", :default => 1
- t.integer "topics_count", :default => 0, :null => false
- t.integer "messages_count", :default => 0, :null => false
- t.integer "last_message_id"
- t.integer "parent_id"
- t.integer "course_id"
- end
-
- add_index "boards", ["last_message_id"], :name => "index_boards_on_last_message_id"
- add_index "boards", ["project_id"], :name => "boards_project_id"
-
- create_table "bug_to_osps", :force => true do |t|
- t.integer "osp_id"
- t.integer "relative_memo_id"
- t.string "description"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "changes", :force => true do |t|
- t.integer "changeset_id", :null => false
- t.string "action", :limit => 1, :default => "", :null => false
- t.text "path", :null => false
- t.text "from_path"
- t.string "from_revision"
- t.string "revision"
- t.string "branch"
- end
-
- add_index "changes", ["changeset_id"], :name => "changesets_changeset_id"
-
- create_table "changeset_parents", :id => false, :force => true do |t|
- t.integer "changeset_id", :null => false
- t.integer "parent_id", :null => false
- end
-
- add_index "changeset_parents", ["changeset_id"], :name => "changeset_parents_changeset_ids"
- add_index "changeset_parents", ["parent_id"], :name => "changeset_parents_parent_ids"
-
- create_table "changesets", :force => true do |t|
- t.integer "repository_id", :null => false
- t.string "revision", :null => false
- t.string "committer"
- t.datetime "committed_on", :null => false
- t.text "comments"
- t.date "commit_date"
- t.string "scmid"
- t.integer "user_id"
- end
-
- add_index "changesets", ["committed_on"], :name => "index_changesets_on_committed_on"
- add_index "changesets", ["repository_id", "revision"], :name => "changesets_repos_rev", :unique => true
- add_index "changesets", ["repository_id", "scmid"], :name => "changesets_repos_scmid"
- add_index "changesets", ["repository_id"], :name => "index_changesets_on_repository_id"
- add_index "changesets", ["user_id"], :name => "index_changesets_on_user_id"
-
- create_table "changesets_issues", :id => false, :force => true do |t|
- t.integer "changeset_id", :null => false
- t.integer "issue_id", :null => false
- end
-
- add_index "changesets_issues", ["changeset_id", "issue_id"], :name => "changesets_issues_ids", :unique => true
-
- create_table "code_review_assignments", :force => true do |t|
- t.integer "issue_id"
- t.integer "change_id"
- t.integer "attachment_id"
- t.string "file_path"
- t.string "rev"
- t.string "rev_to"
- t.string "action_type"
- t.integer "changeset_id"
- end
-
- create_table "code_review_project_settings", :force => true do |t|
- t.integer "project_id"
- t.integer "tracker_id"
- t.datetime "created_at"
- t.datetime "updated_at"
- t.integer "updated_by"
- t.boolean "hide_code_review_tab", :default => false
- t.integer "auto_relation", :default => 1
- t.integer "assignment_tracker_id"
- t.text "auto_assign"
- t.integer "lock_version", :default => 0, :null => false
- t.boolean "tracker_in_review_dialog", :default => false
- end
-
- create_table "code_review_user_settings", :force => true do |t|
- t.integer "user_id", :default => 0, :null => false
- t.integer "mail_notification", :default => 0, :null => false
- t.datetime "created_at"
- t.datetime "updated_at"
- end
-
- create_table "code_reviews", :force => true do |t|
- t.integer "project_id"
- t.integer "change_id"
- t.datetime "created_at"
- t.datetime "updated_at"
- t.integer "line"
- t.integer "updated_by_id"
- t.integer "lock_version", :default => 0, :null => false
- t.integer "status_changed_from"
- t.integer "status_changed_to"
- t.integer "issue_id"
- t.string "action_type"
- t.string "file_path"
- t.string "rev"
- t.string "rev_to"
- t.integer "attachment_id"
- t.integer "file_count", :default => 0, :null => false
- t.boolean "diff_all"
- end
-
- create_table "comments", :force => true do |t|
- t.string "commented_type", :limit => 30, :default => "", :null => false
- t.integer "commented_id", :default => 0, :null => false
- t.integer "author_id", :default => 0, :null => false
- t.text "comments"
- t.datetime "created_on", :null => false
- t.datetime "updated_on", :null => false
- end
-
- add_index "comments", ["author_id"], :name => "index_comments_on_author_id"
- add_index "comments", ["commented_id", "commented_type"], :name => "index_comments_on_commented_id_and_commented_type"
-
- create_table "contest_notifications", :force => true do |t|
- t.text "title"
- t.text "content"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "contesting_projects", :force => true do |t|
- t.integer "project_id"
- t.string "contest_id"
- t.integer "user_id"
- t.string "description"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "reward"
- end
-
- create_table "contesting_softapplications", :force => true do |t|
- t.integer "softapplication_id"
- t.integer "contest_id"
- t.integer "user_id"
- t.string "description"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "reward"
- end
-
- create_table "contestnotifications", :force => true do |t|
- t.integer "contest_id"
- t.string "title"
- t.string "summary"
- t.text "description"
- t.integer "author_id"
- t.integer "notificationcomments_count"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "contests", :force => true do |t|
- t.string "name"
- t.string "budget", :default => ""
- t.integer "author_id"
- t.date "deadline"
- t.string "description"
- t.integer "commit"
- t.string "password"
- t.datetime "created_on", :null => false
- t.datetime "updated_on", :null => false
- end
-
- create_table "course_activities", :force => true do |t|
- t.integer "user_id"
- t.integer "course_id"
- t.integer "course_act_id"
- t.string "course_act_type"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "course_attachments", :force => true do |t|
- t.string "filename"
- t.string "disk_filename"
- t.integer "filesize"
- t.string "content_type"
- t.string "digest"
- t.integer "downloads"
- t.string "author_id"
- t.string "integer"
- t.string "description"
- t.string "disk_directory"
- t.integer "attachtype"
- t.integer "is_public"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "container_id", :default => 0
- end
-
- create_table "course_groups", :force => true do |t|
- t.string "name"
- t.integer "course_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "course_infos", :force => true do |t|
- t.integer "course_id"
- t.integer "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "course_messages", :force => true do |t|
- t.integer "user_id"
- t.integer "course_id"
- t.integer "course_message_id"
- t.string "course_message_type"
- t.integer "viewed"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "content"
- t.integer "status"
- end
-
- create_table "course_statuses", :force => true do |t|
- t.integer "changesets_count"
- t.integer "watchers_count"
- t.integer "course_id"
- t.float "grade", :default => 0.0
- t.integer "course_ac_para", :default => 0
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "courses", :force => true do |t|
- t.integer "tea_id"
- t.string "name"
- t.integer "state"
- t.string "code"
- t.integer "time"
- t.string "extra"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "location"
- t.string "term"
- t.string "string"
- t.string "password"
- t.string "setup_time"
- t.string "endup_time"
- t.string "class_period"
- t.integer "school_id"
- t.text "description"
- t.integer "status", :default => 1
- t.integer "attachmenttype", :default => 2
- t.integer "lft"
- t.integer "rgt"
- t.integer "is_public", :limit => 1, :default => 1
- t.integer "inherit_members", :limit => 1, :default => 1
- t.integer "open_student", :default => 0
- end
-
- create_table "custom_fields", :force => true do |t|
- t.string "type", :limit => 30, :default => "", :null => false
- t.string "name", :limit => 30, :default => "", :null => false
- t.string "field_format", :limit => 30, :default => "", :null => false
- t.text "possible_values"
- t.string "regexp", :default => ""
- t.integer "min_length", :default => 0, :null => false
- t.integer "max_length", :default => 0, :null => false
- t.boolean "is_required", :default => false, :null => false
- t.boolean "is_for_all", :default => false, :null => false
- t.boolean "is_filter", :default => false, :null => false
- t.integer "position", :default => 1
- t.boolean "searchable", :default => false
- t.text "default_value"
- t.boolean "editable", :default => true
- t.boolean "visible", :default => true, :null => false
- t.boolean "multiple", :default => false
- end
-
- add_index "custom_fields", ["id", "type"], :name => "index_custom_fields_on_id_and_type"
-
- create_table "custom_fields_projects", :id => false, :force => true do |t|
- t.integer "custom_field_id", :default => 0, :null => false
- t.integer "project_id", :default => 0, :null => false
- end
-
- add_index "custom_fields_projects", ["custom_field_id", "project_id"], :name => "index_custom_fields_projects_on_custom_field_id_and_project_id", :unique => true
-
- create_table "custom_fields_trackers", :id => false, :force => true do |t|
- t.integer "custom_field_id", :default => 0, :null => false
- t.integer "tracker_id", :default => 0, :null => false
- end
-
- add_index "custom_fields_trackers", ["custom_field_id", "tracker_id"], :name => "index_custom_fields_trackers_on_custom_field_id_and_tracker_id", :unique => true
-
- create_table "custom_values", :force => true do |t|
- t.string "customized_type", :limit => 30, :default => "", :null => false
- t.integer "customized_id", :default => 0, :null => false
- t.integer "custom_field_id", :default => 0, :null => false
- t.text "value"
- end
-
- add_index "custom_values", ["custom_field_id"], :name => "index_custom_values_on_custom_field_id"
- add_index "custom_values", ["customized_type", "customized_id"], :name => "custom_values_customized"
-
- create_table "delayed_jobs", :force => true do |t|
- t.integer "priority", :default => 0, :null => false
- t.integer "attempts", :default => 0, :null => false
- t.text "handler", :null => false
- t.text "last_error"
- t.datetime "run_at"
- t.datetime "locked_at"
- t.datetime "failed_at"
- t.string "locked_by"
- t.string "queue"
- t.datetime "created_at"
- t.datetime "updated_at"
- end
-
- add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority"
-
- create_table "discuss_demos", :force => true do |t|
- t.string "title"
- t.text "body"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "documents", :force => true do |t|
- t.integer "project_id", :default => 0, :null => false
- t.integer "category_id", :default => 0, :null => false
- t.string "title", :limit => 60, :default => "", :null => false
- t.text "description"
- t.datetime "created_on"
- t.integer "user_id", :default => 0
- t.integer "is_public", :default => 1
- end
-
- add_index "documents", ["category_id"], :name => "index_documents_on_category_id"
- add_index "documents", ["created_on"], :name => "index_documents_on_created_on"
- add_index "documents", ["project_id"], :name => "documents_project_id"
-
- create_table "dts", :force => true do |t|
- t.string "IPLineCode"
- t.string "Description"
- t.string "Num"
- t.string "Variable"
- t.string "TraceInfo"
- t.string "Method"
- t.string "File"
- t.string "IPLine"
- t.string "Review"
- t.string "Category"
- t.string "Defect"
- t.string "PreConditions"
- t.string "StartLine"
- t.integer "project_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "enabled_modules", :force => true do |t|
- t.integer "project_id"
- t.string "name", :null => false
- t.integer "course_id"
- end
-
- add_index "enabled_modules", ["project_id"], :name => "enabled_modules_project_id"
-
- create_table "enumerations", :force => true do |t|
- t.string "name", :limit => 30, :default => "", :null => false
- t.integer "position", :default => 1
- t.boolean "is_default", :default => false, :null => false
- t.string "type"
- t.boolean "active", :default => true, :null => false
- t.integer "project_id"
- t.integer "parent_id"
- t.string "position_name", :limit => 30
- end
-
- add_index "enumerations", ["id", "type"], :name => "index_enumerations_on_id_and_type"
- add_index "enumerations", ["project_id"], :name => "index_enumerations_on_project_id"
-
- create_table "first_pages", :force => true do |t|
- t.string "web_title"
- t.string "title"
- t.text "description"
- t.string "page_type"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "sort_type"
- t.integer "image_width", :default => 107
- t.integer "image_height", :default => 63
- t.integer "show_course", :default => 1
- t.integer "show_contest", :default => 1
- end
-
- create_table "forge_activities", :force => true do |t|
- t.integer "user_id"
- t.integer "project_id"
- t.integer "forge_act_id"
- t.string "forge_act_type"
- t.integer "org_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- add_index "forge_activities", ["forge_act_id"], :name => "index_forge_activities_on_forge_act_id"
-
- create_table "forge_messages", :force => true do |t|
- t.integer "user_id"
- t.integer "project_id"
- t.integer "forge_message_id"
- t.string "forge_message_type"
- t.integer "viewed"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "secret_key"
- t.integer "status"
- end
-
- create_table "forums", :force => true do |t|
- t.string "name", :null => false
- t.text "description"
- t.integer "topic_count", :default => 0
- t.integer "memo_count", :default => 0
- t.integer "last_memo_id", :default => 0
- t.integer "creator_id", :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "sticky"
- t.integer "locked"
- end
-
- create_table "groups_users", :id => false, :force => true do |t|
- t.integer "group_id", :null => false
- t.integer "user_id", :null => false
- end
-
- add_index "groups_users", ["group_id", "user_id"], :name => "groups_users_ids", :unique => true
-
- create_table "homework_attaches", :force => true do |t|
- t.integer "bid_id"
- t.integer "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "reward"
- t.string "name"
- t.text "description"
- t.integer "state"
- t.integer "project_id", :default => 0
- t.float "score", :default => 0.0
- t.integer "is_teacher_score", :default => 0
- end
-
- add_index "homework_attaches", ["bid_id"], :name => "index_homework_attaches_on_bid_id"
-
- create_table "homework_commons", :force => true do |t|
- t.string "name"
- t.integer "user_id"
- t.text "description"
- t.date "publish_time"
- t.date "end_time"
- t.integer "homework_type", :default => 1
- t.string "late_penalty"
- t.integer "course_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "teacher_priority", :default => 1
- end
-
- create_table "homework_detail_manuals", :force => true do |t|
- t.float "ta_proportion"
- t.integer "comment_status"
- t.date "evaluation_start"
- t.date "evaluation_end"
- t.integer "evaluation_num"
- t.integer "absence_penalty", :default => 1
- t.integer "homework_common_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "homework_detail_programings", :force => true do |t|
- t.string "language"
- t.text "standard_code", :limit => 2147483647
- t.integer "homework_common_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.float "ta_proportion", :default => 0.1
- t.integer "question_id"
- end
-
- create_table "homework_evaluations", :force => true do |t|
- t.string "user_id"
- t.string "homework_attach_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "homework_for_courses", :force => true do |t|
- t.integer "course_id"
- t.integer "bid_id"
- end
-
- add_index "homework_for_courses", ["bid_id"], :name => "index_homework_for_courses_on_bid_id"
- add_index "homework_for_courses", ["course_id"], :name => "index_homework_for_courses_on_course_id"
-
- create_table "homework_tests", :force => true do |t|
- t.text "input"
- t.text "output"
- t.integer "homework_common_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "result"
- t.text "error_msg"
- end
-
- create_table "homework_users", :force => true do |t|
- t.string "homework_attach_id"
- t.string "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "invite_lists", :force => true do |t|
- t.integer "project_id"
- t.integer "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "mail"
- end
-
- create_table "issue_categories", :force => true do |t|
- t.integer "project_id", :default => 0, :null => false
- t.string "name", :limit => 30, :default => "", :null => false
- t.integer "assigned_to_id"
- end
-
- add_index "issue_categories", ["assigned_to_id"], :name => "index_issue_categories_on_assigned_to_id"
- add_index "issue_categories", ["project_id"], :name => "issue_categories_project_id"
-
- create_table "issue_relations", :force => true do |t|
- t.integer "issue_from_id", :null => false
- t.integer "issue_to_id", :null => false
- t.string "relation_type", :default => "", :null => false
- t.integer "delay"
- end
-
- add_index "issue_relations", ["issue_from_id", "issue_to_id"], :name => "index_issue_relations_on_issue_from_id_and_issue_to_id", :unique => true
- add_index "issue_relations", ["issue_from_id"], :name => "index_issue_relations_on_issue_from_id"
- add_index "issue_relations", ["issue_to_id"], :name => "index_issue_relations_on_issue_to_id"
-
- create_table "issue_statuses", :force => true do |t|
- t.string "name", :limit => 30, :default => "", :null => false
- t.boolean "is_closed", :default => false, :null => false
- t.boolean "is_default", :default => false, :null => false
- t.integer "position", :default => 1
- t.integer "default_done_ratio"
- end
-
- add_index "issue_statuses", ["is_closed"], :name => "index_issue_statuses_on_is_closed"
- add_index "issue_statuses", ["is_default"], :name => "index_issue_statuses_on_is_default"
- add_index "issue_statuses", ["position"], :name => "index_issue_statuses_on_position"
-
- create_table "issues", :force => true do |t|
- t.integer "tracker_id", :null => false
- t.integer "project_id", :null => false
- t.string "subject", :default => "", :null => false
- t.text "description"
- t.date "due_date"
- t.integer "category_id"
- t.integer "status_id", :null => false
- t.integer "assigned_to_id"
- t.integer "priority_id", :null => false
- t.integer "fixed_version_id"
- t.integer "author_id", :null => false
- t.integer "lock_version", :default => 0, :null => false
- t.datetime "created_on"
- t.datetime "updated_on"
- t.date "start_date"
- t.integer "done_ratio", :default => 0, :null => false
- t.float "estimated_hours"
- t.integer "parent_id"
- t.integer "root_id"
- t.integer "lft"
- t.integer "rgt"
- t.boolean "is_private", :default => false, :null => false
- t.datetime "closed_on"
- t.integer "project_issues_index"
- end
-
- add_index "issues", ["assigned_to_id"], :name => "index_issues_on_assigned_to_id"
- add_index "issues", ["author_id"], :name => "index_issues_on_author_id"
- add_index "issues", ["category_id"], :name => "index_issues_on_category_id"
- add_index "issues", ["created_on"], :name => "index_issues_on_created_on"
- add_index "issues", ["fixed_version_id"], :name => "index_issues_on_fixed_version_id"
- add_index "issues", ["priority_id"], :name => "index_issues_on_priority_id"
- add_index "issues", ["project_id"], :name => "issues_project_id"
- add_index "issues", ["root_id", "lft", "rgt"], :name => "index_issues_on_root_id_and_lft_and_rgt"
- add_index "issues", ["status_id"], :name => "index_issues_on_status_id"
- add_index "issues", ["tracker_id"], :name => "index_issues_on_tracker_id"
-
- create_table "join_in_competitions", :force => true do |t|
- t.integer "user_id"
- t.integer "competition_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "join_in_contests", :force => true do |t|
- t.integer "user_id"
- t.integer "bid_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "journal_details", :force => true do |t|
- t.integer "journal_id", :default => 0, :null => false
- t.string "property", :limit => 30, :default => "", :null => false
- t.string "prop_key", :limit => 30, :default => "", :null => false
- t.text "old_value"
- t.text "value"
- end
-
- add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id"
-
- create_table "journal_details_copy", :force => true do |t|
- t.integer "journal_id", :default => 0, :null => false
- t.string "property", :limit => 30, :default => "", :null => false
- t.string "prop_key", :limit => 30, :default => "", :null => false
- t.text "old_value"
- t.text "value"
- end
-
- add_index "journal_details_copy", ["journal_id"], :name => "journal_details_journal_id"
-
- create_table "journal_replies", :id => false, :force => true do |t|
- t.integer "journal_id"
- t.integer "user_id"
- t.integer "reply_id"
- end
-
- add_index "journal_replies", ["journal_id"], :name => "index_journal_replies_on_journal_id"
- add_index "journal_replies", ["reply_id"], :name => "index_journal_replies_on_reply_id"
- add_index "journal_replies", ["user_id"], :name => "index_journal_replies_on_user_id"
-
- create_table "journals", :force => true do |t|
- t.integer "journalized_id", :default => 0, :null => false
- t.string "journalized_type", :limit => 30, :default => "", :null => false
- t.integer "user_id", :default => 0, :null => false
- t.text "notes"
- t.datetime "created_on", :null => false
- t.boolean "private_notes", :default => false, :null => false
- end
-
- add_index "journals", ["created_on"], :name => "index_journals_on_created_on"
- add_index "journals", ["journalized_id", "journalized_type"], :name => "journals_journalized_id"
- add_index "journals", ["journalized_id"], :name => "index_journals_on_journalized_id"
- add_index "journals", ["user_id"], :name => "index_journals_on_user_id"
-
- create_table "journals_for_messages", :force => true do |t|
- t.integer "jour_id"
- t.string "jour_type"
- t.integer "user_id"
- t.text "notes"
- t.integer "status"
- t.integer "reply_id"
- t.datetime "created_on", :null => false
- t.datetime "updated_on", :null => false
- t.string "m_parent_id"
- t.boolean "is_readed"
- t.integer "m_reply_count"
- t.integer "m_reply_id"
- t.integer "is_comprehensive_evaluation"
- end
-
- create_table "kindeditor_assets", :force => true do |t|
- t.string "asset"
- t.integer "file_size"
- t.string "file_type"
- t.integer "owner_id"
- t.string "asset_type"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "owner_type", :default => 0
- end
-
- create_table "member_roles", :force => true do |t|
- t.integer "member_id", :null => false
- t.integer "role_id", :null => false
- t.integer "inherited_from"
- end
-
- add_index "member_roles", ["member_id"], :name => "index_member_roles_on_member_id"
- add_index "member_roles", ["role_id"], :name => "index_member_roles_on_role_id"
-
- create_table "members", :force => true do |t|
- t.integer "user_id", :default => 0, :null => false
- t.integer "project_id", :default => 0
- t.datetime "created_on"
- t.boolean "mail_notification", :default => false, :null => false
- t.integer "course_id", :default => -1
- t.integer "course_group_id", :default => 0
- end
-
- add_index "members", ["project_id"], :name => "index_members_on_project_id"
- add_index "members", ["user_id", "project_id", "course_id"], :name => "index_members_on_user_id_and_project_id", :unique => true
- add_index "members", ["user_id"], :name => "index_members_on_user_id"
-
- create_table "memo_messages", :force => true do |t|
- t.integer "user_id"
- t.integer "forum_id"
- t.integer "memo_id"
- t.string "memo_type"
- t.integer "viewed"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "memos", :force => true do |t|
- t.integer "forum_id", :null => false
- t.integer "parent_id"
- t.string "subject", :null => false
- t.text "content", :null => false
- t.integer "author_id", :null => false
- t.integer "replies_count", :default => 0
- t.integer "last_reply_id"
- t.boolean "lock", :default => false
- t.boolean "sticky", :default => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "viewed_count", :default => 0
- end
-
- create_table "message_alls", :force => true do |t|
- t.integer "user_id"
- t.integer "message_id"
- t.string "message_type"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "messages", :force => true do |t|
- t.integer "board_id", :null => false
- t.integer "parent_id"
- t.string "subject", :default => "", :null => false
- t.text "content"
- t.integer "author_id"
- t.integer "replies_count", :default => 0, :null => false
- t.integer "last_reply_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"
- end
-
- add_index "messages", ["author_id"], :name => "index_messages_on_author_id"
- add_index "messages", ["board_id"], :name => "messages_board_id"
- add_index "messages", ["created_on"], :name => "index_messages_on_created_on"
- add_index "messages", ["last_reply_id"], :name => "index_messages_on_last_reply_id"
- add_index "messages", ["parent_id"], :name => "messages_parent_id"
-
- create_table "news", :force => true do |t|
- t.integer "project_id"
- t.string "title", :limit => 60, :default => "", :null => false
- t.string "summary", :default => ""
- t.text "description"
- t.integer "author_id", :default => 0, :null => false
- t.datetime "created_on"
- t.integer "comments_count", :default => 0, :null => false
- t.integer "course_id"
- end
-
- add_index "news", ["author_id"], :name => "index_news_on_author_id"
- add_index "news", ["created_on"], :name => "index_news_on_created_on"
- add_index "news", ["project_id"], :name => "news_project_id"
-
- create_table "no_uses", :force => true do |t|
- t.integer "user_id", :null => false
- t.string "no_use_type"
- t.integer "no_use_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "notificationcomments", :force => true do |t|
- t.string "notificationcommented_type"
- t.integer "notificationcommented_id"
- t.integer "author_id"
- t.text "notificationcomments"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "onclick_times", :force => true do |t|
- t.integer "user_id"
- t.datetime "onclick_time"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "open_id_authentication_associations", :force => true do |t|
- t.integer "issued"
- t.integer "lifetime"
- t.string "handle"
- t.string "assoc_type"
- t.binary "server_url"
- t.binary "secret"
- end
-
- create_table "open_id_authentication_nonces", :force => true do |t|
- t.integer "timestamp", :null => false
- t.string "server_url"
- t.string "salt", :null => false
- end
-
- create_table "open_source_projects", :force => true do |t|
- t.string "name"
- t.text "description"
- t.integer "commit_count", :default => 0
- t.integer "code_line", :default => 0
- t.integer "users_count", :default => 0
- t.date "last_commit_time"
- t.string "url"
- t.date "date_collected"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "option_numbers", :force => true do |t|
- t.integer "user_id"
- t.integer "memo"
- t.integer "messages_for_issues"
- t.integer "issues_status"
- t.integer "replay_for_message"
- t.integer "replay_for_memo"
- t.integer "follow"
- t.integer "tread"
- t.integer "praise_by_one"
- t.integer "praise_by_two"
- t.integer "praise_by_three"
- t.integer "tread_by_one"
- t.integer "tread_by_two"
- t.integer "tread_by_three"
- t.integer "changeset"
- t.integer "document"
- t.integer "attachment"
- t.integer "issue_done_ratio"
- t.integer "post_issue"
- t.integer "score_type"
- t.integer "total_score"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "project_id"
- end
-
- create_table "organizations", :force => true do |t|
- t.string "name"
- t.string "logo_link"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "phone_app_versions", :force => true do |t|
- t.string "version"
- t.text "description"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "poll_answers", :force => true do |t|
- t.integer "poll_question_id"
- t.text "answer_text"
- t.integer "answer_position"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "poll_questions", :force => true do |t|
- t.string "question_title"
- t.integer "question_type"
- t.integer "is_necessary"
- t.integer "poll_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "question_number"
- end
-
- create_table "poll_users", :force => true do |t|
- t.integer "user_id"
- t.integer "poll_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "poll_votes", :force => true do |t|
- t.integer "user_id"
- t.integer "poll_question_id"
- t.integer "poll_answer_id"
- t.text "vote_text"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "polls", :force => true do |t|
- t.string "polls_name"
- t.string "polls_type"
- t.integer "polls_group_id"
- t.integer "polls_status"
- t.integer "user_id"
- t.datetime "published_at"
- t.datetime "closed_at"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.text "polls_description"
- t.integer "show_result", :default => 1
- end
-
- create_table "praise_tread_caches", :force => true do |t|
- t.integer "object_id", :null => false
- t.string "object_type"
- t.integer "praise_num"
- t.integer "tread_num"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "praise_treads", :force => true do |t|
- t.integer "user_id", :null => false
- t.integer "praise_tread_object_id"
- t.string "praise_tread_object_type"
- t.integer "praise_or_tread"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "principal_activities", :force => true do |t|
- t.integer "user_id"
- t.integer "principal_id"
- t.integer "principal_act_id"
- t.string "principal_act_type"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "project_infos", :force => true do |t|
- t.integer "project_id"
- t.integer "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "project_scores", :force => true do |t|
- t.string "project_id"
- t.integer "score"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "issue_num", :default => 0
- t.integer "issue_journal_num", :default => 0
- t.integer "news_num", :default => 0
- t.integer "documents_num", :default => 0
- t.integer "changeset_num", :default => 0
- t.integer "board_message_num", :default => 0
- end
-
- create_table "project_statuses", :force => true do |t|
- t.integer "changesets_count"
- t.integer "watchers_count"
- t.integer "project_id"
- t.integer "project_type"
- t.float "grade", :default => 0.0
- t.integer "course_ac_para", :default => 0
- end
-
- add_index "project_statuses", ["grade"], :name => "index_project_statuses_on_grade"
-
- create_table "projecting_softapplictions", :force => true do |t|
- t.integer "user_id"
- t.integer "softapplication_id"
- t.integer "project_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "projects", :force => true do |t|
- t.string "name", :default => "", :null => false
- t.text "description"
- t.string "homepage", :default => ""
- t.boolean "is_public", :default => true, :null => false
- t.integer "parent_id"
- t.datetime "created_on"
- t.datetime "updated_on"
- t.string "identifier"
- t.integer "status", :default => 1, :null => false
- t.integer "lft"
- t.integer "rgt"
- t.boolean "inherit_members", :default => false, :null => false
- t.integer "project_type"
- t.boolean "hidden_repo", :default => false, :null => false
- t.integer "attachmenttype", :default => 1
- t.integer "user_id"
- t.integer "dts_test", :default => 0
- t.string "enterprise_name"
- t.integer "organization_id"
- t.integer "project_new_type"
- end
-
- add_index "projects", ["lft"], :name => "index_projects_on_lft"
- add_index "projects", ["rgt"], :name => "index_projects_on_rgt"
-
- create_table "projects_trackers", :id => false, :force => true do |t|
- t.integer "project_id", :default => 0, :null => false
- t.integer "tracker_id", :default => 0, :null => false
- end
-
- add_index "projects_trackers", ["project_id", "tracker_id"], :name => "projects_trackers_unique", :unique => true
- add_index "projects_trackers", ["project_id"], :name => "projects_trackers_project_id"
-
- create_table "queries", :force => true do |t|
- t.integer "project_id"
- t.string "name", :default => "", :null => false
- t.text "filters"
- t.integer "user_id", :default => 0, :null => false
- t.boolean "is_public", :default => false, :null => false
- t.text "column_names"
- t.text "sort_criteria"
- t.string "group_by"
- t.string "type"
- end
-
- add_index "queries", ["project_id"], :name => "index_queries_on_project_id"
- add_index "queries", ["user_id"], :name => "index_queries_on_user_id"
-
- create_table "relative_memo_to_open_source_projects", :force => true do |t|
- t.integer "osp_id"
- t.integer "relative_memo_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "relative_memos", :force => true do |t|
- t.integer "osp_id"
- t.integer "parent_id"
- t.string "subject", :null => false
- t.text "content", :limit => 16777215, :null => false
- t.integer "author_id"
- t.integer "replies_count", :default => 0
- t.integer "last_reply_id"
- t.boolean "lock", :default => false
- t.boolean "sticky", :default => false
- t.boolean "is_quote", :default => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "viewed_count_crawl", :default => 0
- t.integer "viewed_count_local", :default => 0
- t.string "url"
- t.string "username"
- t.string "userhomeurl"
- t.date "date_collected"
- t.string "topic_resource"
- end
-
- create_table "repositories", :force => true do |t|
- t.integer "project_id", :default => 0, :null => false
- t.string "url", :default => "", :null => false
- t.string "login", :limit => 60, :default => ""
- t.string "password", :default => ""
- t.string "root_url", :default => ""
- t.string "type"
- t.string "path_encoding", :limit => 64
- t.string "log_encoding", :limit => 64
- t.text "extra_info"
- t.string "identifier"
- t.boolean "is_default", :default => false
- t.boolean "hidden", :default => false
- end
-
- add_index "repositories", ["project_id"], :name => "index_repositories_on_project_id"
-
- create_table "rich_rich_files", :force => true do |t|
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "rich_file_file_name"
- t.string "rich_file_content_type"
- t.integer "rich_file_file_size"
- t.datetime "rich_file_updated_at"
- t.string "owner_type"
- t.integer "owner_id"
- t.text "uri_cache"
- t.string "simplified_type", :default => "file"
- end
-
- create_table "roles", :force => true do |t|
- t.string "name", :limit => 30, :default => "", :null => false
- t.integer "position", :default => 1
- t.boolean "assignable", :default => true
- t.integer "builtin", :default => 0, :null => false
- t.text "permissions"
- t.string "issues_visibility", :limit => 30, :default => "default", :null => false
- end
-
- create_table "schools", :force => true do |t|
- t.string "name"
- t.string "province"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "logo_link"
- t.string "pinyin"
- end
-
- create_table "seems_rateable_cached_ratings", :force => true do |t|
- t.integer "cacheable_id", :limit => 8
- t.string "cacheable_type"
- t.float "avg", :null => false
- t.integer "cnt", :null => false
- t.string "dimension"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "seems_rateable_rates", :force => true do |t|
- t.integer "rater_id", :limit => 8
- t.integer "rateable_id"
- t.string "rateable_type"
- t.float "stars", :null => false
- t.string "dimension"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "is_teacher_score", :default => 0
- end
-
- create_table "settings", :force => true do |t|
- t.string "name", :default => "", :null => false
- t.text "value"
- t.datetime "updated_on"
- end
-
- add_index "settings", ["name"], :name => "index_settings_on_name"
-
- create_table "shares", :force => true do |t|
- t.date "created_on"
- t.string "url"
- t.string "title"
- t.integer "share_type"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "project_id"
- t.integer "user_id"
- t.string "description"
- end
-
- create_table "softapplications", :force => true do |t|
- t.string "name"
- t.text "description"
- t.integer "app_type_id"
- t.string "app_type_name"
- t.string "android_min_version_available"
- t.integer "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "contest_id"
- t.integer "softapplication_id"
- t.integer "is_public"
- t.string "application_developers"
- t.string "deposit_project_url"
- t.string "deposit_project"
- t.integer "project_id"
- end
-
- create_table "student_work_tests", :force => true do |t|
- t.integer "student_work_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "status", :default => 9
- t.text "results"
- t.text "src"
- end
-
- create_table "student_works", :force => true do |t|
- t.string "name"
- t.text "description", :limit => 2147483647
- t.integer "homework_common_id"
- t.integer "user_id"
- t.float "final_score"
- t.float "teacher_score"
- t.float "student_score"
- t.float "teaching_asistant_score"
- t.integer "project_id", :default => 0
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "late_penalty", :default => 0
- t.integer "absence_penalty", :default => 0
- t.float "system_score", :default => 0.0
- t.boolean "is_test", :default => false
- end
-
- create_table "student_works_evaluation_distributions", :force => true do |t|
- t.integer "student_work_id"
- t.integer "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "student_works_scores", :force => true do |t|
- t.integer "student_work_id"
- t.integer "user_id"
- t.integer "score"
- t.text "comment"
- t.integer "reviewer_role"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "students_for_courses", :force => true do |t|
- t.integer "student_id"
- t.integer "course_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- add_index "students_for_courses", ["course_id"], :name => "index_students_for_courses_on_course_id"
- add_index "students_for_courses", ["student_id"], :name => "index_students_for_courses_on_student_id"
-
- create_table "system_messages", :force => true do |t|
- t.integer "user_id"
- t.string "content"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.text "description"
- t.string "subject"
- end
-
- create_table "taggings", :force => true do |t|
- t.integer "tag_id"
- t.integer "taggable_id"
- t.string "taggable_type"
- t.integer "tagger_id"
- t.string "tagger_type"
- t.string "context", :limit => 128
- t.datetime "created_at"
- end
-
- add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
- add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
- add_index "taggings", ["taggable_type"], :name => "index_taggings_on_taggable_type"
-
- create_table "tags", :force => true do |t|
- t.string "name"
- end
-
- create_table "teachers", :force => true do |t|
- t.string "tea_name"
- t.string "location"
- t.integer "couurse_time"
- t.integer "course_code"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "extra"
- end
-
- create_table "time_entries", :force => true do |t|
- t.integer "project_id", :null => false
- t.integer "user_id", :null => false
- t.integer "issue_id"
- t.float "hours", :null => false
- t.string "comments"
- t.integer "activity_id", :null => false
- t.date "spent_on", :null => false
- t.integer "tyear", :null => false
- t.integer "tmonth", :null => false
- t.integer "tweek", :null => false
- t.datetime "created_on", :null => false
- t.datetime "updated_on", :null => false
- end
-
- add_index "time_entries", ["activity_id"], :name => "index_time_entries_on_activity_id"
- add_index "time_entries", ["created_on"], :name => "index_time_entries_on_created_on"
- add_index "time_entries", ["issue_id"], :name => "time_entries_issue_id"
- add_index "time_entries", ["project_id"], :name => "time_entries_project_id"
- add_index "time_entries", ["user_id"], :name => "index_time_entries_on_user_id"
-
- create_table "tokens", :force => true do |t|
- t.integer "user_id", :default => 0, :null => false
- t.string "action", :limit => 30, :default => "", :null => false
- t.string "value", :limit => 40, :default => "", :null => false
- t.datetime "created_on", :null => false
- end
-
- add_index "tokens", ["user_id"], :name => "index_tokens_on_user_id"
- add_index "tokens", ["value"], :name => "tokens_value", :unique => true
-
- create_table "trackers", :force => true do |t|
- t.string "name", :limit => 30, :default => "", :null => false
- t.boolean "is_in_chlog", :default => false, :null => false
- t.integer "position", :default => 1
- t.boolean "is_in_roadmap", :default => true, :null => false
- t.integer "fields_bits", :default => 0
- end
-
- create_table "user_activities", :force => true do |t|
- t.string "act_type"
- t.integer "act_id"
- t.string "container_type"
- t.integer "container_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "user_id"
- end
-
- create_table "user_extensions", :force => true do |t|
- t.integer "user_id", :null => false
- t.date "birthday"
- t.string "brief_introduction"
- t.integer "gender"
- t.string "location"
- t.string "occupation"
- t.integer "work_experience"
- t.integer "zip_code"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "technical_title"
- t.integer "identity"
- t.string "student_id"
- t.string "teacher_realname"
- t.string "student_realname"
- t.string "location_city"
- t.integer "school_id"
- t.string "description", :default => ""
- end
-
- create_table "user_feedback_messages", :force => true do |t|
- t.integer "user_id"
- t.integer "journals_for_message_id"
- t.string "journals_for_message_type"
- t.integer "viewed"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "user_grades", :force => true do |t|
- t.integer "user_id", :null => false
- t.integer "project_id", :null => false
- t.float "grade", :default => 0.0
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- add_index "user_grades", ["grade"], :name => "index_user_grades_on_grade"
- add_index "user_grades", ["project_id"], :name => "index_user_grades_on_project_id"
- add_index "user_grades", ["user_id"], :name => "index_user_grades_on_user_id"
-
- create_table "user_levels", :force => true do |t|
- t.integer "user_id"
- t.integer "level"
- end
-
- create_table "user_preferences", :force => true do |t|
- t.integer "user_id", :default => 0, :null => false
- t.text "others"
- t.boolean "hide_mail", :default => false
- t.string "time_zone"
- end
-
- add_index "user_preferences", ["user_id"], :name => "index_user_preferences_on_user_id"
-
- create_table "user_score_details", :force => true do |t|
- t.integer "current_user_id"
- t.integer "target_user_id"
- t.string "score_type"
- t.string "score_action"
- t.integer "user_id"
- t.integer "old_score"
- t.integer "new_score"
- t.integer "current_user_level"
- t.integer "target_user_level"
- t.integer "score_changeable_obj_id"
- t.string "score_changeable_obj_type"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "user_scores", :force => true do |t|
- t.integer "user_id", :null => false
- t.integer "collaboration"
- t.integer "influence"
- t.integer "skill"
- t.integer "active"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "user_statuses", :force => true do |t|
- t.integer "changesets_count"
- t.integer "watchers_count"
- t.integer "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.float "grade", :default => 0.0
- end
-
- add_index "user_statuses", ["changesets_count"], :name => "index_user_statuses_on_changesets_count"
- add_index "user_statuses", ["grade"], :name => "index_user_statuses_on_grade"
- add_index "user_statuses", ["watchers_count"], :name => "index_user_statuses_on_watchers_count"
-
- create_table "users", :force => true do |t|
- t.string "login", :default => "", :null => false
- t.string "hashed_password", :limit => 40, :default => "", :null => false
- t.string "firstname", :limit => 30, :default => "", :null => false
- t.string "lastname", :default => "", :null => false
- t.string "mail", :limit => 60, :default => "", :null => false
- t.boolean "admin", :default => false, :null => false
- t.integer "status", :default => 1, :null => false
- t.datetime "last_login_on"
- t.string "language", :limit => 5, :default => ""
- t.integer "auth_source_id"
- t.datetime "created_on"
- t.datetime "updated_on"
- t.string "type"
- t.string "identity_url"
- t.string "mail_notification", :default => "", :null => false
- t.string "salt", :limit => 64
- end
-
- add_index "users", ["auth_source_id"], :name => "index_users_on_auth_source_id"
- add_index "users", ["id", "type"], :name => "index_users_on_id_and_type"
- add_index "users", ["type"], :name => "index_users_on_type"
-
- create_table "versions", :force => true do |t|
- t.integer "project_id", :default => 0, :null => false
- t.string "name", :default => "", :null => false
- t.string "description", :default => ""
- t.date "effective_date"
- t.datetime "created_on"
- t.datetime "updated_on"
- t.string "wiki_page_title"
- t.string "status", :default => "open"
- t.string "sharing", :default => "none", :null => false
- end
-
- add_index "versions", ["project_id"], :name => "versions_project_id"
- add_index "versions", ["sharing"], :name => "index_versions_on_sharing"
-
- create_table "visitors", :force => true do |t|
- t.integer "user_id"
- t.integer "master_id"
- t.datetime "updated_on"
- t.datetime "created_on"
- end
-
- add_index "visitors", ["master_id"], :name => "index_visitors_master_id"
- add_index "visitors", ["updated_on"], :name => "index_visitors_updated_on"
- add_index "visitors", ["user_id"], :name => "index_visitors_user_id"
-
- create_table "watchers", :force => true do |t|
- t.string "watchable_type", :default => "", :null => false
- t.integer "watchable_id", :default => 0, :null => false
- t.integer "user_id"
- end
-
- add_index "watchers", ["user_id", "watchable_type"], :name => "watchers_user_id_type"
- add_index "watchers", ["user_id"], :name => "index_watchers_on_user_id"
- add_index "watchers", ["watchable_id", "watchable_type"], :name => "index_watchers_on_watchable_id_and_watchable_type"
-
- create_table "web_footer_companies", :force => true do |t|
- t.string "name"
- t.string "logo_size"
- t.string "url"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "web_footer_oranizers", :force => true do |t|
- t.string "name"
- t.text "description"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "wiki_content_versions", :force => true do |t|
- t.integer "wiki_content_id", :null => false
- t.integer "page_id", :null => false
- t.integer "author_id"
- t.binary "data", :limit => 2147483647
- t.string "compression", :limit => 6, :default => ""
- t.string "comments", :default => ""
- t.datetime "updated_on", :null => false
- t.integer "version", :null => false
- end
-
- add_index "wiki_content_versions", ["updated_on"], :name => "index_wiki_content_versions_on_updated_on"
- add_index "wiki_content_versions", ["wiki_content_id"], :name => "wiki_content_versions_wcid"
-
- create_table "wiki_contents", :force => true do |t|
- t.integer "page_id", :null => false
- t.integer "author_id"
- t.text "text", :limit => 2147483647
- t.string "comments", :default => ""
- t.datetime "updated_on", :null => false
- t.integer "version", :null => false
- end
-
- add_index "wiki_contents", ["author_id"], :name => "index_wiki_contents_on_author_id"
- add_index "wiki_contents", ["page_id"], :name => "wiki_contents_page_id"
-
- create_table "wiki_pages", :force => true do |t|
- t.integer "wiki_id", :null => false
- t.string "title", :null => false
- t.datetime "created_on", :null => false
- t.boolean "protected", :default => false, :null => false
- t.integer "parent_id"
- end
-
- add_index "wiki_pages", ["parent_id"], :name => "index_wiki_pages_on_parent_id"
- add_index "wiki_pages", ["wiki_id", "title"], :name => "wiki_pages_wiki_id_title"
- add_index "wiki_pages", ["wiki_id"], :name => "index_wiki_pages_on_wiki_id"
-
- create_table "wiki_redirects", :force => true do |t|
- t.integer "wiki_id", :null => false
- t.string "title"
- t.string "redirects_to"
- t.datetime "created_on", :null => false
- end
-
- add_index "wiki_redirects", ["wiki_id", "title"], :name => "wiki_redirects_wiki_id_title"
- add_index "wiki_redirects", ["wiki_id"], :name => "index_wiki_redirects_on_wiki_id"
-
- create_table "wikis", :force => true do |t|
- t.integer "project_id", :null => false
- t.string "start_page", :null => false
- t.integer "status", :default => 1, :null => false
- end
-
- add_index "wikis", ["project_id"], :name => "wikis_project_id"
-
- create_table "workflows", :force => true do |t|
- t.integer "tracker_id", :default => 0, :null => false
- t.integer "old_status_id", :default => 0, :null => false
- t.integer "new_status_id", :default => 0, :null => false
- t.integer "role_id", :default => 0, :null => false
- t.boolean "assignee", :default => false, :null => false
- t.boolean "author", :default => false, :null => false
- t.string "type", :limit => 30
- t.string "field_name", :limit => 30
- t.string "rule", :limit => 30
- end
-
- add_index "workflows", ["new_status_id"], :name => "index_workflows_on_new_status_id"
- add_index "workflows", ["old_status_id"], :name => "index_workflows_on_old_status_id"
- add_index "workflows", ["role_id", "tracker_id", "old_status_id"], :name => "wkfs_role_tracker_old_status"
- add_index "workflows", ["role_id"], :name => "index_workflows_on_role_id"
-
- create_table "works_categories", :force => true do |t|
- t.string "category"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "zip_packs", :force => true do |t|
- t.integer "user_id"
- t.integer "homework_id"
- t.string "file_digest"
- t.string "file_path"
- t.integer "pack_times", :default => 1
- t.integer "pack_size", :default => 0
- t.text "file_digests"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
-end
+# encoding: UTF-8
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended to check this file into your version control system.
+
+ActiveRecord::Schema.define(:version => 20151022071804) do
+
+ create_table "activities", :force => true do |t|
+ t.integer "act_id", :null => false
+ t.string "act_type", :null => false
+ t.integer "user_id", :null => false
+ t.integer "activity_container_id"
+ t.string "activity_container_type", :default => ""
+ t.datetime "created_at"
+ end
+
+ add_index "activities", ["act_id", "act_type"], :name => "index_activities_on_act_id_and_act_type"
+ add_index "activities", ["user_id", "act_type"], :name => "index_activities_on_user_id_and_act_type"
+ add_index "activities", ["user_id"], :name => "index_activities_on_user_id"
+
+ create_table "activity_notifies", :force => true do |t|
+ t.integer "activity_container_id"
+ t.string "activity_container_type"
+ t.integer "activity_id"
+ t.string "activity_type"
+ t.integer "notify_to"
+ t.datetime "created_on"
+ t.integer "is_read"
+ end
+
+ add_index "activity_notifies", ["activity_container_id", "activity_container_type"], :name => "index_an_activity_container_id"
+ add_index "activity_notifies", ["created_on"], :name => "index_an_created_on"
+ add_index "activity_notifies", ["notify_to"], :name => "index_an_notify_to"
+
+ create_table "api_keys", :force => true do |t|
+ t.string "access_token"
+ t.datetime "expires_at"
+ t.integer "user_id"
+ t.boolean "active", :default => true
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "api_keys", ["access_token"], :name => "index_api_keys_on_access_token"
+ add_index "api_keys", ["user_id"], :name => "index_api_keys_on_user_id"
+
+ create_table "applied_projects", :force => true do |t|
+ t.integer "project_id", :null => false
+ t.integer "user_id", :null => false
+ end
+
+ create_table "apply_project_masters", :force => true do |t|
+ t.integer "user_id"
+ t.string "apply_type"
+ t.integer "apply_id"
+ t.integer "status"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "attachments", :force => true do |t|
+ t.integer "container_id"
+ t.string "container_type", :limit => 30
+ t.string "filename", :default => "", :null => false
+ t.string "disk_filename", :default => "", :null => false
+ t.integer "filesize", :default => 0, :null => false
+ t.string "content_type", :default => ""
+ t.string "digest", :limit => 40, :default => "", :null => false
+ t.integer "downloads", :default => 0, :null => false
+ t.integer "author_id", :default => 0, :null => false
+ t.datetime "created_on"
+ t.string "description"
+ t.string "disk_directory"
+ t.integer "attachtype", :default => 1
+ t.integer "is_public", :default => 1
+ t.integer "copy_from"
+ t.integer "quotes"
+ end
+
+ add_index "attachments", ["author_id"], :name => "index_attachments_on_author_id"
+ add_index "attachments", ["container_id", "container_type"], :name => "index_attachments_on_container_id_and_container_type"
+ add_index "attachments", ["created_on"], :name => "index_attachments_on_created_on"
+
+ create_table "attachmentstypes", :force => true do |t|
+ t.integer "typeId", :null => false
+ t.string "typeName", :limit => 50
+ end
+
+ create_table "auth_sources", :force => true do |t|
+ t.string "type", :limit => 30, :default => "", :null => false
+ t.string "name", :limit => 60, :default => "", :null => false
+ t.string "host", :limit => 60
+ t.integer "port"
+ t.string "account"
+ t.string "account_password", :default => ""
+ t.string "base_dn"
+ t.string "attr_login", :limit => 30
+ t.string "attr_firstname", :limit => 30
+ t.string "attr_lastname", :limit => 30
+ t.string "attr_mail", :limit => 30
+ t.boolean "onthefly_register", :default => false, :null => false
+ t.boolean "tls", :default => false, :null => false
+ t.string "filter"
+ t.integer "timeout"
+ end
+
+ add_index "auth_sources", ["id", "type"], :name => "index_auth_sources_on_id_and_type"
+
+ create_table "biding_projects", :force => true do |t|
+ t.integer "project_id"
+ t.integer "bid_id"
+ t.integer "user_id"
+ t.string "description"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "reward"
+ end
+
+ create_table "bids", :force => true do |t|
+ t.string "name"
+ t.string "budget", :null => false
+ t.integer "author_id"
+ t.date "deadline"
+ t.text "description"
+ t.datetime "created_on", :null => false
+ t.datetime "updated_on", :null => false
+ t.integer "commit"
+ t.integer "reward_type"
+ t.integer "homework_type"
+ t.integer "parent_id"
+ t.string "password"
+ t.integer "is_evaluation"
+ t.integer "proportion", :default => 60
+ t.integer "comment_status", :default => 0
+ t.integer "evaluation_num", :default => 3
+ t.integer "open_anonymous_evaluation", :default => 1
+ end
+
+ create_table "blog_comments", :force => true 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.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "blogs", :force => true 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.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "boards", :force => true do |t|
+ t.integer "project_id", :null => false
+ t.string "name", :default => "", :null => false
+ t.string "description"
+ t.integer "position", :default => 1
+ t.integer "topics_count", :default => 0, :null => false
+ t.integer "messages_count", :default => 0, :null => false
+ t.integer "last_message_id"
+ t.integer "parent_id"
+ t.integer "course_id"
+ end
+
+ add_index "boards", ["last_message_id"], :name => "index_boards_on_last_message_id"
+ add_index "boards", ["project_id"], :name => "boards_project_id"
+
+ create_table "bug_to_osps", :force => true do |t|
+ t.integer "osp_id"
+ t.integer "relative_memo_id"
+ t.string "description"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "changes", :force => true do |t|
+ t.integer "changeset_id", :null => false
+ t.string "action", :limit => 1, :default => "", :null => false
+ t.text "path", :null => false
+ t.text "from_path"
+ t.string "from_revision"
+ t.string "revision"
+ t.string "branch"
+ end
+
+ add_index "changes", ["changeset_id"], :name => "changesets_changeset_id"
+
+ create_table "changeset_parents", :id => false, :force => true do |t|
+ t.integer "changeset_id", :null => false
+ t.integer "parent_id", :null => false
+ end
+
+ add_index "changeset_parents", ["changeset_id"], :name => "changeset_parents_changeset_ids"
+ add_index "changeset_parents", ["parent_id"], :name => "changeset_parents_parent_ids"
+
+ create_table "changesets", :force => true do |t|
+ t.integer "repository_id", :null => false
+ t.string "revision", :null => false
+ t.string "committer"
+ t.datetime "committed_on", :null => false
+ t.text "comments"
+ t.date "commit_date"
+ t.string "scmid"
+ t.integer "user_id"
+ end
+
+ add_index "changesets", ["committed_on"], :name => "index_changesets_on_committed_on"
+ add_index "changesets", ["repository_id", "revision"], :name => "changesets_repos_rev", :unique => true
+ add_index "changesets", ["repository_id", "scmid"], :name => "changesets_repos_scmid"
+ add_index "changesets", ["repository_id"], :name => "index_changesets_on_repository_id"
+ add_index "changesets", ["user_id"], :name => "index_changesets_on_user_id"
+
+ create_table "changesets_issues", :id => false, :force => true do |t|
+ t.integer "changeset_id", :null => false
+ t.integer "issue_id", :null => false
+ end
+
+ add_index "changesets_issues", ["changeset_id", "issue_id"], :name => "changesets_issues_ids", :unique => true
+
+ create_table "code_review_assignments", :force => true do |t|
+ t.integer "issue_id"
+ t.integer "change_id"
+ t.integer "attachment_id"
+ t.string "file_path"
+ t.string "rev"
+ t.string "rev_to"
+ t.string "action_type"
+ t.integer "changeset_id"
+ end
+
+ create_table "code_review_project_settings", :force => true do |t|
+ t.integer "project_id"
+ t.integer "tracker_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "updated_by"
+ t.boolean "hide_code_review_tab", :default => false
+ t.integer "auto_relation", :default => 1
+ t.integer "assignment_tracker_id"
+ t.text "auto_assign"
+ t.integer "lock_version", :default => 0, :null => false
+ t.boolean "tracker_in_review_dialog", :default => false
+ end
+
+ create_table "code_review_user_settings", :force => true do |t|
+ t.integer "user_id", :default => 0, :null => false
+ t.integer "mail_notification", :default => 0, :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "code_reviews", :force => true do |t|
+ t.integer "project_id"
+ t.integer "change_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "line"
+ t.integer "updated_by_id"
+ t.integer "lock_version", :default => 0, :null => false
+ t.integer "status_changed_from"
+ t.integer "status_changed_to"
+ t.integer "issue_id"
+ t.string "action_type"
+ t.string "file_path"
+ t.string "rev"
+ t.string "rev_to"
+ t.integer "attachment_id"
+ t.integer "file_count", :default => 0, :null => false
+ t.boolean "diff_all"
+ end
+
+ create_table "comments", :force => true do |t|
+ t.string "commented_type", :limit => 30, :default => "", :null => false
+ t.integer "commented_id", :default => 0, :null => false
+ t.integer "author_id", :default => 0, :null => false
+ t.text "comments"
+ t.datetime "created_on", :null => false
+ t.datetime "updated_on", :null => false
+ end
+
+ add_index "comments", ["author_id"], :name => "index_comments_on_author_id"
+ add_index "comments", ["commented_id", "commented_type"], :name => "index_comments_on_commented_id_and_commented_type"
+
+ create_table "contest_notifications", :force => true do |t|
+ t.text "title"
+ t.text "content"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "contesting_projects", :force => true do |t|
+ t.integer "project_id"
+ t.string "contest_id"
+ t.integer "user_id"
+ t.string "description"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "reward"
+ end
+
+ create_table "contesting_softapplications", :force => true do |t|
+ t.integer "softapplication_id"
+ t.integer "contest_id"
+ t.integer "user_id"
+ t.string "description"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "reward"
+ end
+
+ create_table "contestnotifications", :force => true do |t|
+ t.integer "contest_id"
+ t.string "title"
+ t.string "summary"
+ t.text "description"
+ t.integer "author_id"
+ t.integer "notificationcomments_count"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "contests", :force => true do |t|
+ t.string "name"
+ t.string "budget", :default => ""
+ t.integer "author_id"
+ t.date "deadline"
+ t.string "description"
+ t.integer "commit"
+ t.string "password"
+ t.datetime "created_on", :null => false
+ t.datetime "updated_on", :null => false
+ end
+
+ create_table "course_activities", :force => true do |t|
+ t.integer "user_id"
+ t.integer "course_id"
+ t.integer "course_act_id"
+ t.string "course_act_type"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "course_attachments", :force => true do |t|
+ t.string "filename"
+ t.string "disk_filename"
+ t.integer "filesize"
+ t.string "content_type"
+ t.string "digest"
+ t.integer "downloads"
+ t.string "author_id"
+ t.string "integer"
+ t.string "description"
+ t.string "disk_directory"
+ t.integer "attachtype"
+ t.integer "is_public"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "container_id", :default => 0
+ end
+
+ create_table "course_groups", :force => true do |t|
+ t.string "name"
+ t.integer "course_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "course_infos", :force => true do |t|
+ t.integer "course_id"
+ t.integer "user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "course_messages", :force => true do |t|
+ t.integer "user_id"
+ t.integer "course_id"
+ t.integer "course_message_id"
+ t.string "course_message_type"
+ t.integer "viewed"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "content"
+ t.integer "status"
+ end
+
+ create_table "course_statuses", :force => true do |t|
+ t.integer "changesets_count"
+ t.integer "watchers_count"
+ t.integer "course_id"
+ t.float "grade", :default => 0.0
+ t.integer "course_ac_para", :default => 0
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "courses", :force => true do |t|
+ t.integer "tea_id"
+ t.string "name"
+ t.integer "state"
+ t.string "code"
+ t.integer "time"
+ t.string "extra"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "location"
+ t.string "term"
+ t.string "string"
+ t.string "password"
+ t.string "setup_time"
+ t.string "endup_time"
+ t.string "class_period"
+ t.integer "school_id"
+ t.text "description"
+ t.integer "status", :default => 1
+ t.integer "attachmenttype", :default => 2
+ t.integer "lft"
+ t.integer "rgt"
+ t.integer "is_public", :limit => 1, :default => 1
+ t.integer "inherit_members", :limit => 1, :default => 1
+ t.integer "open_student", :default => 0
+ end
+
+ create_table "custom_fields", :force => true do |t|
+ t.string "type", :limit => 30, :default => "", :null => false
+ t.string "name", :limit => 30, :default => "", :null => false
+ t.string "field_format", :limit => 30, :default => "", :null => false
+ t.text "possible_values"
+ t.string "regexp", :default => ""
+ t.integer "min_length", :default => 0, :null => false
+ t.integer "max_length", :default => 0, :null => false
+ t.boolean "is_required", :default => false, :null => false
+ t.boolean "is_for_all", :default => false, :null => false
+ t.boolean "is_filter", :default => false, :null => false
+ t.integer "position", :default => 1
+ t.boolean "searchable", :default => false
+ t.text "default_value"
+ t.boolean "editable", :default => true
+ t.boolean "visible", :default => true, :null => false
+ t.boolean "multiple", :default => false
+ end
+
+ add_index "custom_fields", ["id", "type"], :name => "index_custom_fields_on_id_and_type"
+
+ create_table "custom_fields_projects", :id => false, :force => true do |t|
+ t.integer "custom_field_id", :default => 0, :null => false
+ t.integer "project_id", :default => 0, :null => false
+ end
+
+ add_index "custom_fields_projects", ["custom_field_id", "project_id"], :name => "index_custom_fields_projects_on_custom_field_id_and_project_id", :unique => true
+
+ create_table "custom_fields_trackers", :id => false, :force => true do |t|
+ t.integer "custom_field_id", :default => 0, :null => false
+ t.integer "tracker_id", :default => 0, :null => false
+ end
+
+ add_index "custom_fields_trackers", ["custom_field_id", "tracker_id"], :name => "index_custom_fields_trackers_on_custom_field_id_and_tracker_id", :unique => true
+
+ create_table "custom_values", :force => true do |t|
+ t.string "customized_type", :limit => 30, :default => "", :null => false
+ t.integer "customized_id", :default => 0, :null => false
+ t.integer "custom_field_id", :default => 0, :null => false
+ t.text "value"
+ end
+
+ add_index "custom_values", ["custom_field_id"], :name => "index_custom_values_on_custom_field_id"
+ add_index "custom_values", ["customized_type", "customized_id"], :name => "custom_values_customized"
+
+ create_table "delayed_jobs", :force => true do |t|
+ t.integer "priority", :default => 0, :null => false
+ t.integer "attempts", :default => 0, :null => false
+ t.text "handler", :null => false
+ t.text "last_error"
+ t.datetime "run_at"
+ t.datetime "locked_at"
+ t.datetime "failed_at"
+ t.string "locked_by"
+ t.string "queue"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority"
+
+ create_table "discuss_demos", :force => true do |t|
+ t.string "title"
+ t.text "body"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "documents", :force => true do |t|
+ t.integer "project_id", :default => 0, :null => false
+ t.integer "category_id", :default => 0, :null => false
+ t.string "title", :limit => 60, :default => "", :null => false
+ t.text "description"
+ t.datetime "created_on"
+ t.integer "user_id", :default => 0
+ t.integer "is_public", :default => 1
+ end
+
+ add_index "documents", ["category_id"], :name => "index_documents_on_category_id"
+ add_index "documents", ["created_on"], :name => "index_documents_on_created_on"
+ add_index "documents", ["project_id"], :name => "documents_project_id"
+
+ create_table "dts", :primary_key => "Num", :force => true do |t|
+ t.string "Defect", :limit => 50
+ t.string "Category", :limit => 50
+ t.string "File"
+ t.string "Method"
+ t.string "Module", :limit => 20
+ t.string "Variable", :limit => 50
+ t.integer "StartLine"
+ t.integer "IPLine"
+ t.string "IPLineCode", :limit => 200
+ t.string "Judge", :limit => 15
+ t.integer "Review", :limit => 1
+ t.string "Description"
+ t.text "PreConditions", :limit => 2147483647
+ t.text "TraceInfo", :limit => 2147483647
+ t.text "Code", :limit => 2147483647
+ t.integer "project_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "id", :null => false
+ end
+
+ create_table "enabled_modules", :force => true do |t|
+ t.integer "project_id"
+ t.string "name", :null => false
+ t.integer "course_id"
+ end
+
+ add_index "enabled_modules", ["project_id"], :name => "enabled_modules_project_id"
+
+ create_table "enumerations", :force => true do |t|
+ t.string "name", :limit => 30, :default => "", :null => false
+ t.integer "position", :default => 1
+ t.boolean "is_default", :default => false, :null => false
+ t.string "type"
+ t.boolean "active", :default => true, :null => false
+ t.integer "project_id"
+ t.integer "parent_id"
+ t.string "position_name", :limit => 30
+ end
+
+ add_index "enumerations", ["id", "type"], :name => "index_enumerations_on_id_and_type"
+ add_index "enumerations", ["project_id"], :name => "index_enumerations_on_project_id"
+
+ create_table "first_pages", :force => true do |t|
+ t.string "web_title"
+ t.string "title"
+ t.text "description"
+ t.string "page_type"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "sort_type"
+ t.integer "image_width", :default => 107
+ t.integer "image_height", :default => 63
+ t.integer "show_course", :default => 1
+ t.integer "show_contest", :default => 1
+ end
+
+ create_table "forge_activities", :force => true do |t|
+ t.integer "user_id"
+ t.integer "project_id"
+ t.integer "forge_act_id"
+ t.string "forge_act_type"
+ t.integer "org_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "forge_activities", ["forge_act_id"], :name => "index_forge_activities_on_forge_act_id"
+
+ create_table "forge_messages", :force => true do |t|
+ t.integer "user_id"
+ t.integer "project_id"
+ t.integer "forge_message_id"
+ t.string "forge_message_type"
+ t.integer "viewed"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "secret_key"
+ t.integer "status"
+ end
+
+ create_table "forums", :force => true do |t|
+ t.string "name", :null => false
+ t.text "description"
+ t.integer "topic_count", :default => 0
+ t.integer "memo_count", :default => 0
+ t.integer "last_memo_id", :default => 0
+ t.integer "creator_id", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "sticky"
+ t.integer "locked"
+ end
+
+ create_table "groups_users", :id => false, :force => true do |t|
+ t.integer "group_id", :null => false
+ t.integer "user_id", :null => false
+ end
+
+ add_index "groups_users", ["group_id", "user_id"], :name => "groups_users_ids", :unique => true
+
+ create_table "homework_attaches", :force => true do |t|
+ t.integer "bid_id"
+ t.integer "user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "reward"
+ t.string "name"
+ t.text "description"
+ t.integer "state"
+ t.integer "project_id", :default => 0
+ t.float "score", :default => 0.0
+ t.integer "is_teacher_score", :default => 0
+ end
+
+ add_index "homework_attaches", ["bid_id"], :name => "index_homework_attaches_on_bid_id"
+
+ create_table "homework_commons", :force => true do |t|
+ t.string "name"
+ t.integer "user_id"
+ t.text "description"
+ t.date "publish_time"
+ t.date "end_time"
+ t.integer "homework_type", :default => 1
+ t.string "late_penalty"
+ t.integer "course_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "teacher_priority", :default => 1
+ end
+
+ create_table "homework_detail_manuals", :force => true do |t|
+ t.float "ta_proportion"
+ t.integer "comment_status"
+ t.date "evaluation_start"
+ t.date "evaluation_end"
+ t.integer "evaluation_num"
+ t.integer "absence_penalty", :default => 1
+ t.integer "homework_common_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "homework_detail_programings", :force => true do |t|
+ t.string "language"
+ t.text "standard_code", :limit => 2147483647
+ t.integer "homework_common_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.float "ta_proportion", :default => 0.1
+ t.integer "question_id"
+ end
+
+ create_table "homework_evaluations", :force => true do |t|
+ t.string "user_id"
+ t.string "homework_attach_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "homework_for_courses", :force => true do |t|
+ t.integer "course_id"
+ t.integer "bid_id"
+ end
+
+ add_index "homework_for_courses", ["bid_id"], :name => "index_homework_for_courses_on_bid_id"
+ add_index "homework_for_courses", ["course_id"], :name => "index_homework_for_courses_on_course_id"
+
+ create_table "homework_tests", :force => true do |t|
+ t.text "input"
+ t.text "output"
+ t.integer "homework_common_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "result"
+ t.text "error_msg"
+ end
+
+ create_table "homework_users", :force => true do |t|
+ t.string "homework_attach_id"
+ t.string "user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "invite_lists", :force => true do |t|
+ t.integer "project_id"
+ t.integer "user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "mail"
+ end
+
+ create_table "issue_categories", :force => true do |t|
+ t.integer "project_id", :default => 0, :null => false
+ t.string "name", :limit => 30, :default => "", :null => false
+ t.integer "assigned_to_id"
+ end
+
+ add_index "issue_categories", ["assigned_to_id"], :name => "index_issue_categories_on_assigned_to_id"
+ add_index "issue_categories", ["project_id"], :name => "issue_categories_project_id"
+
+ create_table "issue_relations", :force => true do |t|
+ t.integer "issue_from_id", :null => false
+ t.integer "issue_to_id", :null => false
+ t.string "relation_type", :default => "", :null => false
+ t.integer "delay"
+ end
+
+ add_index "issue_relations", ["issue_from_id", "issue_to_id"], :name => "index_issue_relations_on_issue_from_id_and_issue_to_id", :unique => true
+ add_index "issue_relations", ["issue_from_id"], :name => "index_issue_relations_on_issue_from_id"
+ add_index "issue_relations", ["issue_to_id"], :name => "index_issue_relations_on_issue_to_id"
+
+ create_table "issue_statuses", :force => true do |t|
+ t.string "name", :limit => 30, :default => "", :null => false
+ t.boolean "is_closed", :default => false, :null => false
+ t.boolean "is_default", :default => false, :null => false
+ t.integer "position", :default => 1
+ t.integer "default_done_ratio"
+ end
+
+ add_index "issue_statuses", ["is_closed"], :name => "index_issue_statuses_on_is_closed"
+ add_index "issue_statuses", ["is_default"], :name => "index_issue_statuses_on_is_default"
+ add_index "issue_statuses", ["position"], :name => "index_issue_statuses_on_position"
+
+ create_table "issues", :force => true do |t|
+ t.integer "tracker_id", :null => false
+ t.integer "project_id", :null => false
+ t.string "subject", :default => "", :null => false
+ t.text "description"
+ t.date "due_date"
+ t.integer "category_id"
+ t.integer "status_id", :null => false
+ t.integer "assigned_to_id"
+ t.integer "priority_id", :null => false
+ t.integer "fixed_version_id"
+ t.integer "author_id", :null => false
+ t.integer "lock_version", :default => 0, :null => false
+ t.datetime "created_on"
+ t.datetime "updated_on"
+ t.date "start_date"
+ t.integer "done_ratio", :default => 0, :null => false
+ t.float "estimated_hours"
+ t.integer "parent_id"
+ t.integer "root_id"
+ t.integer "lft"
+ t.integer "rgt"
+ t.boolean "is_private", :default => false, :null => false
+ t.datetime "closed_on"
+ t.integer "project_issues_index"
+ end
+
+ add_index "issues", ["assigned_to_id"], :name => "index_issues_on_assigned_to_id"
+ add_index "issues", ["author_id"], :name => "index_issues_on_author_id"
+ add_index "issues", ["category_id"], :name => "index_issues_on_category_id"
+ add_index "issues", ["created_on"], :name => "index_issues_on_created_on"
+ add_index "issues", ["fixed_version_id"], :name => "index_issues_on_fixed_version_id"
+ add_index "issues", ["priority_id"], :name => "index_issues_on_priority_id"
+ add_index "issues", ["project_id"], :name => "issues_project_id"
+ add_index "issues", ["root_id", "lft", "rgt"], :name => "index_issues_on_root_id_and_lft_and_rgt"
+ add_index "issues", ["status_id"], :name => "index_issues_on_status_id"
+ add_index "issues", ["tracker_id"], :name => "index_issues_on_tracker_id"
+
+ create_table "join_in_competitions", :force => true do |t|
+ t.integer "user_id"
+ t.integer "competition_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "join_in_contests", :force => true do |t|
+ t.integer "user_id"
+ t.integer "bid_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "journal_details", :force => true do |t|
+ t.integer "journal_id", :default => 0, :null => false
+ t.string "property", :limit => 30, :default => "", :null => false
+ t.string "prop_key", :limit => 30, :default => "", :null => false
+ t.text "old_value"
+ t.text "value"
+ end
+
+ add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id"
+
+ create_table "journal_replies", :id => false, :force => true do |t|
+ t.integer "journal_id"
+ t.integer "user_id"
+ t.integer "reply_id"
+ end
+
+ add_index "journal_replies", ["journal_id"], :name => "index_journal_replies_on_journal_id"
+ add_index "journal_replies", ["reply_id"], :name => "index_journal_replies_on_reply_id"
+ add_index "journal_replies", ["user_id"], :name => "index_journal_replies_on_user_id"
+
+ create_table "journals", :force => true do |t|
+ t.integer "journalized_id", :default => 0, :null => false
+ t.string "journalized_type", :limit => 30, :default => "", :null => false
+ t.integer "user_id", :default => 0, :null => false
+ t.text "notes"
+ t.datetime "created_on", :null => false
+ t.boolean "private_notes", :default => false, :null => false
+ end
+
+ add_index "journals", ["created_on"], :name => "index_journals_on_created_on"
+ add_index "journals", ["journalized_id", "journalized_type"], :name => "journals_journalized_id"
+ add_index "journals", ["journalized_id"], :name => "index_journals_on_journalized_id"
+ add_index "journals", ["user_id"], :name => "index_journals_on_user_id"
+
+ create_table "journals_for_messages", :force => true do |t|
+ t.integer "jour_id"
+ t.string "jour_type"
+ t.integer "user_id"
+ t.text "notes"
+ t.integer "status"
+ t.integer "reply_id"
+ t.datetime "created_on", :null => false
+ t.datetime "updated_on", :null => false
+ t.string "m_parent_id"
+ t.boolean "is_readed"
+ t.integer "m_reply_count"
+ t.integer "m_reply_id"
+ t.integer "is_comprehensive_evaluation"
+ end
+
+ create_table "kindeditor_assets", :force => true do |t|
+ t.string "asset"
+ t.integer "file_size"
+ t.string "file_type"
+ t.integer "owner_id"
+ t.string "asset_type"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "owner_type", :default => 0
+ end
+
+ create_table "member_roles", :force => true do |t|
+ t.integer "member_id", :null => false
+ t.integer "role_id", :null => false
+ t.integer "inherited_from"
+ end
+
+ add_index "member_roles", ["member_id"], :name => "index_member_roles_on_member_id"
+ add_index "member_roles", ["role_id"], :name => "index_member_roles_on_role_id"
+
+ create_table "members", :force => true do |t|
+ t.integer "user_id", :default => 0, :null => false
+ t.integer "project_id", :default => 0
+ t.datetime "created_on"
+ t.boolean "mail_notification", :default => false, :null => false
+ t.integer "course_id", :default => -1
+ t.integer "course_group_id", :default => 0
+ end
+
+ add_index "members", ["project_id"], :name => "index_members_on_project_id"
+ add_index "members", ["user_id", "project_id", "course_id"], :name => "index_members_on_user_id_and_project_id", :unique => true
+ add_index "members", ["user_id"], :name => "index_members_on_user_id"
+
+ create_table "memo_messages", :force => true do |t|
+ t.integer "user_id"
+ t.integer "forum_id"
+ t.integer "memo_id"
+ t.string "memo_type"
+ t.integer "viewed"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "memos", :force => true do |t|
+ t.integer "forum_id", :null => false
+ t.integer "parent_id"
+ t.string "subject", :null => false
+ t.text "content", :null => false
+ t.integer "author_id", :null => false
+ t.integer "replies_count", :default => 0
+ t.integer "last_reply_id"
+ t.boolean "lock", :default => false
+ t.boolean "sticky", :default => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "viewed_count", :default => 0
+ end
+
+ create_table "message_alls", :force => true do |t|
+ t.integer "user_id"
+ t.integer "message_id"
+ t.string "message_type"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "messages", :force => true do |t|
+ t.integer "board_id", :null => false
+ t.integer "parent_id"
+ t.string "subject", :default => "", :null => false
+ t.text "content"
+ t.integer "author_id"
+ t.integer "replies_count", :default => 0, :null => false
+ t.integer "last_reply_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"
+ end
+
+ add_index "messages", ["author_id"], :name => "index_messages_on_author_id"
+ add_index "messages", ["board_id"], :name => "messages_board_id"
+ add_index "messages", ["created_on"], :name => "index_messages_on_created_on"
+ add_index "messages", ["last_reply_id"], :name => "index_messages_on_last_reply_id"
+ add_index "messages", ["parent_id"], :name => "messages_parent_id"
+
+ create_table "news", :force => true do |t|
+ t.integer "project_id"
+ t.string "title", :limit => 60, :default => "", :null => false
+ t.string "summary", :default => ""
+ t.text "description"
+ t.integer "author_id", :default => 0, :null => false
+ t.datetime "created_on"
+ t.integer "comments_count", :default => 0, :null => false
+ t.integer "course_id"
+ end
+
+ add_index "news", ["author_id"], :name => "index_news_on_author_id"
+ add_index "news", ["created_on"], :name => "index_news_on_created_on"
+ add_index "news", ["project_id"], :name => "news_project_id"
+
+ create_table "no_uses", :force => true do |t|
+ t.integer "user_id", :null => false
+ t.string "no_use_type"
+ t.integer "no_use_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "notificationcomments", :force => true do |t|
+ t.string "notificationcommented_type"
+ t.integer "notificationcommented_id"
+ t.integer "author_id"
+ t.text "notificationcomments"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "onclick_times", :force => true do |t|
+ t.integer "user_id"
+ t.datetime "onclick_time"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "open_id_authentication_associations", :force => true do |t|
+ t.integer "issued"
+ t.integer "lifetime"
+ t.string "handle"
+ t.string "assoc_type"
+ t.binary "server_url"
+ t.binary "secret"
+ end
+
+ create_table "open_id_authentication_nonces", :force => true do |t|
+ t.integer "timestamp", :null => false
+ t.string "server_url"
+ t.string "salt", :null => false
+ end
+
+ create_table "open_source_projects", :force => true do |t|
+ t.string "name"
+ t.text "description"
+ t.integer "commit_count", :default => 0
+ t.integer "code_line", :default => 0
+ t.integer "users_count", :default => 0
+ t.date "last_commit_time"
+ t.string "url"
+ t.date "date_collected"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "option_numbers", :force => true do |t|
+ t.integer "user_id"
+ t.integer "memo"
+ t.integer "messages_for_issues"
+ t.integer "issues_status"
+ t.integer "replay_for_message"
+ t.integer "replay_for_memo"
+ t.integer "follow"
+ t.integer "tread"
+ t.integer "praise_by_one"
+ t.integer "praise_by_two"
+ t.integer "praise_by_three"
+ t.integer "tread_by_one"
+ t.integer "tread_by_two"
+ t.integer "tread_by_three"
+ t.integer "changeset"
+ t.integer "document"
+ t.integer "attachment"
+ t.integer "issue_done_ratio"
+ t.integer "post_issue"
+ t.integer "score_type"
+ t.integer "total_score"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "project_id"
+ end
+
+ create_table "organizations", :force => true do |t|
+ t.string "name"
+ t.string "logo_link"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "phone_app_versions", :force => true do |t|
+ t.string "version"
+ t.text "description"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "poll_answers", :force => true do |t|
+ t.integer "poll_question_id"
+ t.text "answer_text"
+ t.integer "answer_position"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "poll_questions", :force => true do |t|
+ t.string "question_title"
+ t.integer "question_type"
+ t.integer "is_necessary"
+ t.integer "poll_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "question_number"
+ end
+
+ create_table "poll_users", :force => true do |t|
+ t.integer "user_id"
+ t.integer "poll_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "poll_votes", :force => true do |t|
+ t.integer "user_id"
+ t.integer "poll_question_id"
+ t.integer "poll_answer_id"
+ t.text "vote_text"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "polls", :force => true do |t|
+ t.string "polls_name"
+ t.string "polls_type"
+ t.integer "polls_group_id"
+ t.integer "polls_status"
+ t.integer "user_id"
+ t.datetime "published_at"
+ t.datetime "closed_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.text "polls_description"
+ t.integer "show_result", :default => 1
+ end
+
+ create_table "praise_tread_caches", :force => true do |t|
+ t.integer "object_id", :null => false
+ t.string "object_type"
+ t.integer "praise_num"
+ t.integer "tread_num"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "praise_treads", :force => true do |t|
+ t.integer "user_id", :null => false
+ t.integer "praise_tread_object_id"
+ t.string "praise_tread_object_type"
+ t.integer "praise_or_tread"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "principal_activities", :force => true do |t|
+ t.integer "user_id"
+ t.integer "principal_id"
+ t.integer "principal_act_id"
+ t.string "principal_act_type"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "project_infos", :force => true do |t|
+ t.integer "project_id"
+ t.integer "user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "project_scores", :force => true do |t|
+ t.string "project_id"
+ t.integer "score"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "issue_num", :default => 0
+ t.integer "issue_journal_num", :default => 0
+ t.integer "news_num", :default => 0
+ t.integer "documents_num", :default => 0
+ t.integer "changeset_num", :default => 0
+ t.integer "board_message_num", :default => 0
+ end
+
+ create_table "project_statuses", :force => true do |t|
+ t.integer "changesets_count"
+ t.integer "watchers_count"
+ t.integer "project_id"
+ t.integer "project_type"
+ t.float "grade", :default => 0.0
+ t.integer "course_ac_para", :default => 0
+ end
+
+ add_index "project_statuses", ["grade"], :name => "index_project_statuses_on_grade"
+
+ create_table "projecting_softapplictions", :force => true do |t|
+ t.integer "user_id"
+ t.integer "softapplication_id"
+ t.integer "project_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "projects", :force => true do |t|
+ t.string "name", :default => "", :null => false
+ t.text "description"
+ t.string "homepage", :default => ""
+ t.boolean "is_public", :default => true, :null => false
+ t.integer "parent_id"
+ t.datetime "created_on"
+ t.datetime "updated_on"
+ t.string "identifier"
+ t.integer "status", :default => 1, :null => false
+ t.integer "lft"
+ t.integer "rgt"
+ t.boolean "inherit_members", :default => false, :null => false
+ t.integer "project_type"
+ t.boolean "hidden_repo", :default => false, :null => false
+ t.integer "attachmenttype", :default => 1
+ t.integer "user_id"
+ t.integer "dts_test", :default => 0
+ t.string "enterprise_name"
+ t.integer "organization_id"
+ t.integer "project_new_type"
+ t.integer "gpid"
+ end
+
+ add_index "projects", ["lft"], :name => "index_projects_on_lft"
+ add_index "projects", ["rgt"], :name => "index_projects_on_rgt"
+
+ create_table "projects_trackers", :id => false, :force => true do |t|
+ t.integer "project_id", :default => 0, :null => false
+ t.integer "tracker_id", :default => 0, :null => false
+ end
+
+ add_index "projects_trackers", ["project_id", "tracker_id"], :name => "projects_trackers_unique", :unique => true
+ add_index "projects_trackers", ["project_id"], :name => "projects_trackers_project_id"
+
+ create_table "queries", :force => true do |t|
+ t.integer "project_id"
+ t.string "name", :default => "", :null => false
+ t.text "filters"
+ t.integer "user_id", :default => 0, :null => false
+ t.boolean "is_public", :default => false, :null => false
+ t.text "column_names"
+ t.text "sort_criteria"
+ t.string "group_by"
+ t.string "type"
+ end
+
+ add_index "queries", ["project_id"], :name => "index_queries_on_project_id"
+ add_index "queries", ["user_id"], :name => "index_queries_on_user_id"
+
+ create_table "relative_memo_to_open_source_projects", :force => true do |t|
+ t.integer "osp_id"
+ t.integer "relative_memo_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "relative_memos", :force => true do |t|
+ t.integer "osp_id"
+ t.integer "parent_id"
+ t.string "subject", :null => false
+ t.text "content", :limit => 16777215, :null => false
+ t.integer "author_id"
+ t.integer "replies_count", :default => 0
+ t.integer "last_reply_id"
+ t.boolean "lock", :default => false
+ t.boolean "sticky", :default => false
+ t.boolean "is_quote", :default => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "viewed_count_crawl", :default => 0
+ t.integer "viewed_count_local", :default => 0
+ t.string "url"
+ t.string "username"
+ t.string "userhomeurl"
+ t.date "date_collected"
+ t.string "topic_resource"
+ end
+
+ create_table "repositories", :force => true do |t|
+ t.integer "project_id", :default => 0, :null => false
+ t.string "url", :default => "", :null => false
+ t.string "login", :limit => 60, :default => ""
+ t.string "password", :default => ""
+ t.string "root_url", :default => ""
+ t.string "type"
+ t.string "path_encoding", :limit => 64
+ t.string "log_encoding", :limit => 64
+ t.text "extra_info"
+ t.string "identifier"
+ t.boolean "is_default", :default => false
+ t.boolean "hidden", :default => false
+ end
+
+ add_index "repositories", ["project_id"], :name => "index_repositories_on_project_id"
+
+ create_table "rich_rich_files", :force => true do |t|
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "rich_file_file_name"
+ t.string "rich_file_content_type"
+ t.integer "rich_file_file_size"
+ t.datetime "rich_file_updated_at"
+ t.string "owner_type"
+ t.integer "owner_id"
+ t.text "uri_cache"
+ t.string "simplified_type", :default => "file"
+ end
+
+ create_table "roles", :force => true do |t|
+ t.string "name", :limit => 30, :default => "", :null => false
+ t.integer "position", :default => 1
+ t.boolean "assignable", :default => true
+ t.integer "builtin", :default => 0, :null => false
+ t.text "permissions"
+ t.string "issues_visibility", :limit => 30, :default => "default", :null => false
+ end
+
+ create_table "schools", :force => true do |t|
+ t.string "name"
+ t.string "province"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "logo_link"
+ t.string "pinyin"
+ end
+
+ create_table "seems_rateable_cached_ratings", :force => true do |t|
+ t.integer "cacheable_id", :limit => 8
+ t.string "cacheable_type"
+ t.float "avg", :null => false
+ t.integer "cnt", :null => false
+ t.string "dimension"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "seems_rateable_rates", :force => true do |t|
+ t.integer "rater_id", :limit => 8
+ t.integer "rateable_id"
+ t.string "rateable_type"
+ t.float "stars", :null => false
+ t.string "dimension"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "is_teacher_score", :default => 0
+ end
+
+ create_table "settings", :force => true do |t|
+ t.string "name", :default => "", :null => false
+ t.text "value"
+ t.datetime "updated_on"
+ end
+
+ add_index "settings", ["name"], :name => "index_settings_on_name"
+
+ create_table "shares", :force => true do |t|
+ t.date "created_on"
+ t.string "url"
+ t.string "title"
+ t.integer "share_type"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "project_id"
+ t.integer "user_id"
+ t.string "description"
+ end
+
+ create_table "softapplications", :force => true do |t|
+ t.string "name"
+ t.text "description"
+ t.integer "app_type_id"
+ t.string "app_type_name"
+ t.string "android_min_version_available"
+ t.integer "user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "contest_id"
+ t.integer "softapplication_id"
+ t.integer "is_public"
+ t.string "application_developers"
+ t.string "deposit_project_url"
+ t.string "deposit_project"
+ t.integer "project_id"
+ end
+
+ create_table "student_work_tests", :force => true do |t|
+ t.integer "student_work_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "status", :default => 9
+ t.text "results"
+ t.text "src"
+ end
+
+ create_table "student_works", :force => true do |t|
+ t.string "name"
+ t.text "description", :limit => 2147483647
+ t.integer "homework_common_id"
+ t.integer "user_id"
+ t.float "final_score"
+ t.float "teacher_score"
+ t.float "student_score"
+ t.float "teaching_asistant_score"
+ t.integer "project_id", :default => 0
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "late_penalty", :default => 0
+ t.integer "absence_penalty", :default => 0
+ t.float "system_score", :default => 0.0
+ t.boolean "is_test", :default => false
+ end
+
+ create_table "student_works_evaluation_distributions", :force => true do |t|
+ t.integer "student_work_id"
+ t.integer "user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "student_works_scores", :force => true do |t|
+ t.integer "student_work_id"
+ t.integer "user_id"
+ t.integer "score"
+ t.text "comment"
+ t.integer "reviewer_role"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "students_for_courses", :force => true do |t|
+ t.integer "student_id"
+ t.integer "course_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "students_for_courses", ["course_id"], :name => "index_students_for_courses_on_course_id"
+ add_index "students_for_courses", ["student_id"], :name => "index_students_for_courses_on_student_id"
+
+ create_table "system_messages", :force => true do |t|
+ t.integer "user_id"
+ t.string "content"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.text "description"
+ t.string "subject"
+ end
+
+ create_table "taggings", :force => true do |t|
+ t.integer "tag_id"
+ t.integer "taggable_id"
+ t.string "taggable_type"
+ t.integer "tagger_id"
+ t.string "tagger_type"
+ t.string "context", :limit => 128
+ t.datetime "created_at"
+ end
+
+ add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
+ add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
+ add_index "taggings", ["taggable_type"], :name => "index_taggings_on_taggable_type"
+
+ create_table "tags", :force => true do |t|
+ t.string "name"
+ end
+
+ create_table "teachers", :force => true do |t|
+ t.string "tea_name"
+ t.string "location"
+ t.integer "couurse_time"
+ t.integer "course_code"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "extra"
+ end
+
+ create_table "time_entries", :force => true do |t|
+ t.integer "project_id", :null => false
+ t.integer "user_id", :null => false
+ t.integer "issue_id"
+ t.float "hours", :null => false
+ t.string "comments"
+ t.integer "activity_id", :null => false
+ t.date "spent_on", :null => false
+ t.integer "tyear", :null => false
+ t.integer "tmonth", :null => false
+ t.integer "tweek", :null => false
+ t.datetime "created_on", :null => false
+ t.datetime "updated_on", :null => false
+ end
+
+ add_index "time_entries", ["activity_id"], :name => "index_time_entries_on_activity_id"
+ add_index "time_entries", ["created_on"], :name => "index_time_entries_on_created_on"
+ add_index "time_entries", ["issue_id"], :name => "time_entries_issue_id"
+ add_index "time_entries", ["project_id"], :name => "time_entries_project_id"
+ add_index "time_entries", ["user_id"], :name => "index_time_entries_on_user_id"
+
+ create_table "tokens", :force => true do |t|
+ t.integer "user_id", :default => 0, :null => false
+ t.string "action", :limit => 30, :default => "", :null => false
+ t.string "value", :limit => 40, :default => "", :null => false
+ t.datetime "created_on", :null => false
+ end
+
+ add_index "tokens", ["user_id"], :name => "index_tokens_on_user_id"
+ add_index "tokens", ["value"], :name => "tokens_value", :unique => true
+
+ create_table "trackers", :force => true do |t|
+ t.string "name", :limit => 30, :default => "", :null => false
+ t.boolean "is_in_chlog", :default => false, :null => false
+ t.integer "position", :default => 1
+ t.boolean "is_in_roadmap", :default => true, :null => false
+ t.integer "fields_bits", :default => 0
+ end
+
+ create_table "user_activities", :force => true do |t|
+ t.string "act_type"
+ t.integer "act_id"
+ t.string "container_type"
+ t.integer "container_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "user_id"
+ end
+
+ create_table "user_extensions", :force => true do |t|
+ t.integer "user_id", :null => false
+ t.date "birthday"
+ t.string "brief_introduction"
+ t.integer "gender"
+ t.string "location"
+ t.string "occupation"
+ t.integer "work_experience"
+ t.integer "zip_code"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "technical_title"
+ t.integer "identity"
+ t.string "student_id"
+ t.string "teacher_realname"
+ t.string "student_realname"
+ t.string "location_city"
+ t.integer "school_id"
+ t.string "description", :default => ""
+ end
+
+ create_table "user_feedback_messages", :force => true do |t|
+ t.integer "user_id"
+ t.integer "journals_for_message_id"
+ t.string "journals_for_message_type"
+ t.integer "viewed"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "user_grades", :force => true do |t|
+ t.integer "user_id", :null => false
+ t.integer "project_id", :null => false
+ t.float "grade", :default => 0.0
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "user_grades", ["grade"], :name => "index_user_grades_on_grade"
+ add_index "user_grades", ["project_id"], :name => "index_user_grades_on_project_id"
+ add_index "user_grades", ["user_id"], :name => "index_user_grades_on_user_id"
+
+ create_table "user_levels", :force => true do |t|
+ t.integer "user_id"
+ t.integer "level"
+ end
+
+ create_table "user_preferences", :force => true do |t|
+ t.integer "user_id", :default => 0, :null => false
+ t.text "others"
+ t.boolean "hide_mail", :default => false
+ t.string "time_zone"
+ end
+
+ add_index "user_preferences", ["user_id"], :name => "index_user_preferences_on_user_id"
+
+ create_table "user_score_details", :force => true do |t|
+ t.integer "current_user_id"
+ t.integer "target_user_id"
+ t.string "score_type"
+ t.string "score_action"
+ t.integer "user_id"
+ t.integer "old_score"
+ t.integer "new_score"
+ t.integer "current_user_level"
+ t.integer "target_user_level"
+ t.integer "score_changeable_obj_id"
+ t.string "score_changeable_obj_type"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "user_scores", :force => true do |t|
+ t.integer "user_id", :null => false
+ t.integer "collaboration"
+ t.integer "influence"
+ t.integer "skill"
+ t.integer "active"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "user_statuses", :force => true do |t|
+ t.integer "changesets_count"
+ t.integer "watchers_count"
+ t.integer "user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.float "grade", :default => 0.0
+ end
+
+ add_index "user_statuses", ["changesets_count"], :name => "index_user_statuses_on_changesets_count"
+ add_index "user_statuses", ["grade"], :name => "index_user_statuses_on_grade"
+ add_index "user_statuses", ["watchers_count"], :name => "index_user_statuses_on_watchers_count"
+
+ create_table "users", :force => true do |t|
+ t.string "login", :default => "", :null => false
+ t.string "hashed_password", :limit => 40, :default => "", :null => false
+ t.string "firstname", :limit => 30, :default => "", :null => false
+ t.string "lastname", :default => "", :null => false
+ t.string "mail", :limit => 60, :default => "", :null => false
+ t.boolean "admin", :default => false, :null => false
+ t.integer "status", :default => 1, :null => false
+ t.datetime "last_login_on"
+ t.string "language", :limit => 5, :default => ""
+ t.integer "auth_source_id"
+ t.datetime "created_on"
+ t.datetime "updated_on"
+ t.string "type"
+ t.string "identity_url"
+ t.string "mail_notification", :default => "", :null => false
+ t.string "salt", :limit => 64
+ t.integer "gid"
+ end
+
+ add_index "users", ["auth_source_id"], :name => "index_users_on_auth_source_id"
+ add_index "users", ["id", "type"], :name => "index_users_on_id_and_type"
+ add_index "users", ["type"], :name => "index_users_on_type"
+
+ create_table "versions", :force => true do |t|
+ t.integer "project_id", :default => 0, :null => false
+ t.string "name", :default => "", :null => false
+ t.string "description", :default => ""
+ t.date "effective_date"
+ t.datetime "created_on"
+ t.datetime "updated_on"
+ t.string "wiki_page_title"
+ t.string "status", :default => "open"
+ t.string "sharing", :default => "none", :null => false
+ end
+
+ add_index "versions", ["project_id"], :name => "versions_project_id"
+ add_index "versions", ["sharing"], :name => "index_versions_on_sharing"
+
+ create_table "visitors", :force => true do |t|
+ t.integer "user_id"
+ t.integer "master_id"
+ t.datetime "updated_on"
+ t.datetime "created_on"
+ end
+
+ add_index "visitors", ["master_id"], :name => "index_visitors_master_id"
+ add_index "visitors", ["updated_on"], :name => "index_visitors_updated_on"
+ add_index "visitors", ["user_id"], :name => "index_visitors_user_id"
+
+ create_table "watchers", :force => true do |t|
+ t.string "watchable_type", :default => "", :null => false
+ t.integer "watchable_id", :default => 0, :null => false
+ t.integer "user_id"
+ end
+
+ add_index "watchers", ["user_id", "watchable_type"], :name => "watchers_user_id_type"
+ add_index "watchers", ["user_id"], :name => "index_watchers_on_user_id"
+ add_index "watchers", ["watchable_id", "watchable_type"], :name => "index_watchers_on_watchable_id_and_watchable_type"
+
+ create_table "web_footer_companies", :force => true do |t|
+ t.string "name"
+ t.string "logo_size"
+ t.string "url"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "web_footer_oranizers", :force => true do |t|
+ t.string "name"
+ t.text "description"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "wiki_content_versions", :force => true do |t|
+ t.integer "wiki_content_id", :null => false
+ t.integer "page_id", :null => false
+ t.integer "author_id"
+ t.binary "data", :limit => 2147483647
+ t.string "compression", :limit => 6, :default => ""
+ t.string "comments", :default => ""
+ t.datetime "updated_on", :null => false
+ t.integer "version", :null => false
+ end
+
+ add_index "wiki_content_versions", ["updated_on"], :name => "index_wiki_content_versions_on_updated_on"
+ add_index "wiki_content_versions", ["wiki_content_id"], :name => "wiki_content_versions_wcid"
+
+ create_table "wiki_contents", :force => true do |t|
+ t.integer "page_id", :null => false
+ t.integer "author_id"
+ t.text "text", :limit => 2147483647
+ t.string "comments", :default => ""
+ t.datetime "updated_on", :null => false
+ t.integer "version", :null => false
+ end
+
+ add_index "wiki_contents", ["author_id"], :name => "index_wiki_contents_on_author_id"
+ add_index "wiki_contents", ["page_id"], :name => "wiki_contents_page_id"
+
+ create_table "wiki_pages", :force => true do |t|
+ t.integer "wiki_id", :null => false
+ t.string "title", :null => false
+ t.datetime "created_on", :null => false
+ t.boolean "protected", :default => false, :null => false
+ t.integer "parent_id"
+ end
+
+ add_index "wiki_pages", ["parent_id"], :name => "index_wiki_pages_on_parent_id"
+ add_index "wiki_pages", ["wiki_id", "title"], :name => "wiki_pages_wiki_id_title"
+ add_index "wiki_pages", ["wiki_id"], :name => "index_wiki_pages_on_wiki_id"
+
+ create_table "wiki_redirects", :force => true do |t|
+ t.integer "wiki_id", :null => false
+ t.string "title"
+ t.string "redirects_to"
+ t.datetime "created_on", :null => false
+ end
+
+ add_index "wiki_redirects", ["wiki_id", "title"], :name => "wiki_redirects_wiki_id_title"
+ add_index "wiki_redirects", ["wiki_id"], :name => "index_wiki_redirects_on_wiki_id"
+
+ create_table "wikis", :force => true do |t|
+ t.integer "project_id", :null => false
+ t.string "start_page", :null => false
+ t.integer "status", :default => 1, :null => false
+ end
+
+ add_index "wikis", ["project_id"], :name => "wikis_project_id"
+
+ create_table "workflows", :force => true do |t|
+ t.integer "tracker_id", :default => 0, :null => false
+ t.integer "old_status_id", :default => 0, :null => false
+ t.integer "new_status_id", :default => 0, :null => false
+ t.integer "role_id", :default => 0, :null => false
+ t.boolean "assignee", :default => false, :null => false
+ t.boolean "author", :default => false, :null => false
+ t.string "type", :limit => 30
+ t.string "field_name", :limit => 30
+ t.string "rule", :limit => 30
+ end
+
+ add_index "workflows", ["new_status_id"], :name => "index_workflows_on_new_status_id"
+ add_index "workflows", ["old_status_id"], :name => "index_workflows_on_old_status_id"
+ add_index "workflows", ["role_id", "tracker_id", "old_status_id"], :name => "wkfs_role_tracker_old_status"
+ add_index "workflows", ["role_id"], :name => "index_workflows_on_role_id"
+
+ create_table "works_categories", :force => true do |t|
+ t.string "category"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "zip_packs", :force => true do |t|
+ t.integer "user_id"
+ t.integer "homework_id"
+ t.string "file_digest"
+ t.string "file_path"
+ t.integer "pack_times", :default => 1
+ t.integer "pack_size", :default => 0
+ t.text "file_digests"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+end
diff --git a/lib/gitlab-cli/Gemfile b/lib/gitlab-cli/Gemfile
new file mode 100644
index 000000000..54fbdf177
--- /dev/null
+++ b/lib/gitlab-cli/Gemfile
@@ -0,0 +1,4 @@
+source 'https://rubygems.org'
+
+# Specify your gem's dependencies in gitlab.gemspec
+gemspec
diff --git a/lib/gitlab-cli/LICENSE.txt b/lib/gitlab-cli/LICENSE.txt
new file mode 100644
index 000000000..2c289176d
--- /dev/null
+++ b/lib/gitlab-cli/LICENSE.txt
@@ -0,0 +1,24 @@
+Copyright (c) 2012-2014 Nihad Abbasov
+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.
diff --git a/lib/gitlab-cli/README.md b/lib/gitlab-cli/README.md
new file mode 100644
index 000000000..1aa73996c
--- /dev/null
+++ b/lib/gitlab-cli/README.md
@@ -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)
+# => [#1, "code"=>"brute", "name"=>"Brute", "description"=>nil, "path"=>"brute", "default_branch"=>nil, "owner"=>#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"}>, #2, "code"=>"mozart", "name"=>"Mozart", "description"=>nil, "path"=>"mozart", "default_branch"=>nil, "owner"=>#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"}>, #3, "code"=>"gitlab", "name"=>"Gitlab", "description"=>nil, "path"=>"gitlab", "default_branch"=>nil, "owner"=>#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')
+# => #
+
+# get a user
+user = g.user
+# => #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.
diff --git a/lib/gitlab-cli/Rakefile b/lib/gitlab-cli/Rakefile
new file mode 100644
index 000000000..bf451bdcb
--- /dev/null
+++ b/lib/gitlab-cli/Rakefile
@@ -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
diff --git a/lib/gitlab-cli/bin/gitlab b/lib/gitlab-cli/bin/gitlab
new file mode 100644
index 000000000..af0fd0cc0
--- /dev/null
+++ b/lib/gitlab-cli/bin/gitlab
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path('../../lib', __FILE__)
+
+require 'gitlab/cli'
+
+Gitlab::CLI.start(ARGV)
diff --git a/lib/gitlab-cli/gitlab.gemspec b/lib/gitlab-cli/gitlab.gemspec
new file mode 100644
index 000000000..7d5f30862
--- /dev/null
+++ b/lib/gitlab-cli/gitlab.gemspec
@@ -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
diff --git a/lib/gitlab-cli/lib/gitlab.rb b/lib/gitlab-cli/lib/gitlab.rb
new file mode 100644
index 000000000..95c382678
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab.rb
@@ -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]
+ 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
diff --git a/lib/gitlab-cli/lib/gitlab/api.rb b/lib/gitlab-cli/lib/gitlab/api.rb
new file mode 100644
index 000000000..62b3f47dc
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/api.rb
@@ -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
diff --git a/lib/gitlab-cli/lib/gitlab/cli.rb b/lib/gitlab-cli/lib/gitlab/cli.rb
new file mode 100644
index 000000000..57c2aa5b4
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/cli.rb
@@ -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
diff --git a/lib/gitlab-cli/lib/gitlab/cli_helpers.rb b/lib/gitlab-cli/lib/gitlab/cli_helpers.rb
new file mode 100644
index 000000000..d1c777dc0
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/cli_helpers.rb
@@ -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
diff --git a/lib/gitlab-cli/lib/gitlab/client.rb b/lib/gitlab-cli/lib/gitlab/client.rb
new file mode 100644
index 000000000..5cb01ce2b
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/client.rb
@@ -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
diff --git a/lib/gitlab-cli/lib/gitlab/client/branches.rb b/lib/gitlab-cli/lib/gitlab/client/branches.rb
new file mode 100644
index 000000000..0ba3afb29
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/client/branches.rb
@@ -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]
+ 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
+
diff --git a/lib/gitlab-cli/lib/gitlab/client/groups.rb b/lib/gitlab-cli/lib/gitlab/client/groups.rb
new file mode 100644
index 000000000..1ea4bf5bc
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/client/groups.rb
@@ -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]
+ 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]
+ 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
diff --git a/lib/gitlab-cli/lib/gitlab/client/issues.rb b/lib/gitlab-cli/lib/gitlab/client/issues.rb
new file mode 100644
index 000000000..7248df848
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/client/issues.rb
@@ -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]
+ 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
diff --git a/lib/gitlab-cli/lib/gitlab/client/merge_requests.rb b/lib/gitlab-cli/lib/gitlab/client/merge_requests.rb
new file mode 100644
index 000000000..1ccc25081
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/client/merge_requests.rb
@@ -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]
+ 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 '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
diff --git a/lib/gitlab-cli/lib/gitlab/client/milestones.rb b/lib/gitlab-cli/lib/gitlab/client/milestones.rb
new file mode 100644
index 000000000..4ba4d5e87
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/client/milestones.rb
@@ -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]
+ 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
diff --git a/lib/gitlab-cli/lib/gitlab/client/notes.rb b/lib/gitlab-cli/lib/gitlab/client/notes.rb
new file mode 100644
index 000000000..7d782e8e2
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/client/notes.rb
@@ -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]
+ 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]
+ 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]
+ 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
diff --git a/lib/gitlab-cli/lib/gitlab/client/projects.rb b/lib/gitlab-cli/lib/gitlab/client/projects.rb
new file mode 100644
index 000000000..04ea682aa
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/client/projects.rb
@@ -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]
+ 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]
+ 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]
+ 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] 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]
+ 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]
+ 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
diff --git a/lib/gitlab-cli/lib/gitlab/client/repositories.rb b/lib/gitlab-cli/lib/gitlab/client/repositories.rb
new file mode 100644
index 000000000..f489e5009
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/client/repositories.rb
@@ -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]
+ 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]
+ 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
diff --git a/lib/gitlab-cli/lib/gitlab/client/snippets.rb b/lib/gitlab-cli/lib/gitlab/client/snippets.rb
new file mode 100644
index 000000000..594d37402
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/client/snippets.rb
@@ -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
diff --git a/lib/gitlab-cli/lib/gitlab/client/system_hooks.rb b/lib/gitlab-cli/lib/gitlab/client/system_hooks.rb
new file mode 100644
index 000000000..59db4f924
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/client/system_hooks.rb
@@ -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]
+ 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]
+ 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
diff --git a/lib/gitlab-cli/lib/gitlab/client/users.rb b/lib/gitlab-cli/lib/gitlab/client/users.rb
new file mode 100644
index 000000000..3fc83cd1b
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/client/users.rb
@@ -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]
+ 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]
+ 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
diff --git a/lib/gitlab-cli/lib/gitlab/configuration.rb b/lib/gitlab-cli/lib/gitlab/configuration.rb
new file mode 100644
index 000000000..b36ed5a7d
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/configuration.rb
@@ -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
diff --git a/lib/gitlab-cli/lib/gitlab/error.rb b/lib/gitlab-cli/lib/gitlab/error.rb
new file mode 100644
index 000000000..96a8a39d6
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/error.rb
@@ -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
diff --git a/lib/gitlab-cli/lib/gitlab/help.rb b/lib/gitlab-cli/lib/gitlab/help.rb
new file mode 100644
index 000000000..6ead8af94
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/help.rb
@@ -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
diff --git a/lib/gitlab-cli/lib/gitlab/objectified_hash.rb b/lib/gitlab-cli/lib/gitlab/objectified_hash.rb
new file mode 100644
index 000000000..87ca2fc77
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/objectified_hash.rb
@@ -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
diff --git a/lib/gitlab-cli/lib/gitlab/request.rb b/lib/gitlab-cli/lib/gitlab/request.rb
new file mode 100644
index 000000000..64a8f6b4c
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/request.rb
@@ -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
diff --git a/lib/gitlab-cli/lib/gitlab/shell.rb b/lib/gitlab-cli/lib/gitlab/shell.rb
new file mode 100644
index 000000000..9253b61e3
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/shell.rb
@@ -0,0 +1,51 @@
+require 'gitlab'
+require 'gitlab/help'
+require 'gitlab/cli_helpers'
+require 'readline'
+
+class Gitlab::Shell
+ extend Gitlab::CLI::Helpers
+
+ def self.start
+ actions = Gitlab.actions
+
+ comp = proc { |s| actions.map(&:to_s).grep(/^#{Regexp.escape(s)}/) }
+
+ Readline.completion_proc = comp
+ Readline.completion_append_character = ' '
+
+ client = Gitlab::Client.new(endpoint: '')
+
+ while buf = Readline.readline('gitlab> ', true)
+ next if buf.nil? || buf.empty?
+ break if buf == 'exit'
+
+ buf = buf.scan(/["][^"]+["]|\S+/).map { |word| word.gsub(/^['"]|['"]$/,'') }
+ cmd = buf.shift
+ args = buf.count > 0 ? buf : []
+
+ if cmd == 'help'
+ methods = []
+
+ actions.each do |action|
+ methods << {
+ name: action.to_s,
+ owner: client.method(action).owner.to_s
+ }
+ end
+
+ args[0].nil? ? Gitlab::Help.get_help(methods) : Gitlab::Help.get_help(methods, args[0])
+ next
+ end
+
+ data = if actions.include?(cmd.to_sym)
+ confirm_command(cmd)
+ gitlab_helper(cmd, args)
+ else
+ "'#{cmd}' is not a valid command. See the 'help' for a list of valid commands."
+ end
+
+ output_table(cmd, args, data)
+ end
+ end
+end
diff --git a/lib/gitlab-cli/lib/gitlab/version.rb b/lib/gitlab-cli/lib/gitlab/version.rb
new file mode 100644
index 000000000..42ba291e0
--- /dev/null
+++ b/lib/gitlab-cli/lib/gitlab/version.rb
@@ -0,0 +1,3 @@
+module Gitlab
+ VERSION = "3.2.0"
+end
diff --git a/lib/gitlab-cli/spec/fixtures/branch.json b/lib/gitlab-cli/spec/fixtures/branch.json
new file mode 100644
index 000000000..34a02081f
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/branch.json
@@ -0,0 +1 @@
+{"name":"api","commit":{"id":"f7dd067490fe57505f7226c3b54d3127d2f7fd46","parents":[{"id":"949b1df930bedace1dbd755aaa4a82e8c451a616"}],"tree":"f8c4b21c036339f92fcc5482aa28a41250553b27","message":"API: expose issues project id","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-07-25T04:22:21-07:00","committed_date":"2012-07-25T04:22:21-07:00"},"protected": true}
diff --git a/lib/gitlab-cli/spec/fixtures/branches.json b/lib/gitlab-cli/spec/fixtures/branches.json
new file mode 100644
index 000000000..05a39447d
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/branches.json
@@ -0,0 +1 @@
+[{"name":"api","commit":{"id":"f7dd067490fe57505f7226c3b54d3127d2f7fd46","parents":[{"id":"949b1df930bedace1dbd755aaa4a82e8c451a616"}],"tree":"f8c4b21c036339f92fcc5482aa28a41250553b27","message":"API: expose issues project id","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-07-25T04:22:21-07:00","committed_date":"2012-07-25T04:22:21-07:00"}},{"name":"dashboard-feed","commit":{"id":"f8f6ff065eccc6ede4d35ed87a09bb962b84ca25","parents":[{"id":"2cf8010792c3075824ee27d0f037aeb178cbbf7e"}],"tree":"e17f2157143d550891d4669c10b7446e4739bc6d","message":"add projects atom feed","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-05-31T23:42:02-07:00","committed_date":"2012-05-31T23:42:02-07:00"}},{"name":"master","commit":{"id":"2cf8010792c3075824ee27d0f037aeb178cbbf7e","parents":[{"id":"af226ae9c9af406c8a0e0bbdf364563495c2f432"},{"id":"e851cb07762aa464aae10e8b4b28de87c1a6f925"}],"tree":"6c6845838039f01723d91f395a1d2fa1dcc82522","message":"Merge pull request #868 from SaitoWu/bugfix/encoding\n\nBugfix/encoding","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-05-30T00:24:43-07:00","committed_date":"2012-05-30T00:24:43-07:00"}},{"name":"preview_notes","commit":{"id":"3749e0d99ac6bfbc65889b1b7a5310e14e7fe89a","parents":[{"id":"2483181f2c3d4ea7d2c68147b19bc07fc3937b0c"}],"tree":"f8c56161b0d6561568f088df9961362eb1ece88b","message":"pass project_id to notes preview path","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-08-09T23:46:27-07:00","committed_date":"2012-08-09T23:46:27-07:00"}},{"name":"refactoring","commit":{"id":"7c7761099cae83f59fe5780340e100be890847b2","parents":[{"id":"058d80b3363dd4fc4417ca4f60f76119188a2470"}],"tree":"d7d4a94c700dc0e84ee943019213d2358a49c413","message":"fix deprecation warnings","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-05-29T07:16:28-07:00","committed_date":"2012-05-29T07:16:28-07:00"}}]
diff --git a/lib/gitlab-cli/spec/fixtures/comment_merge_request.json b/lib/gitlab-cli/spec/fixtures/comment_merge_request.json
new file mode 100644
index 000000000..742f33377
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/comment_merge_request.json
@@ -0,0 +1 @@
+{"note":"Cool Merge Request!","author":{"id":1,"username":"jsmith","email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-07-11T01:32:18Z"}}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/create_branch.json b/lib/gitlab-cli/spec/fixtures/create_branch.json
new file mode 100644
index 000000000..0b011abdb
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/create_branch.json
@@ -0,0 +1 @@
+{"name":"api","commit":{ "id":"f7dd067490fe57505f7226c3b54d3127d2f7fd46","message":"API: expose issues project id","parent_ids":["949b1df930bedace1dbd755aaa4a82e8c451a616"],"authored_date":"2012-07-25T04:22:21-07:00","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","committed_date":"2012-07-25T04:22:21-07:00","committer_name":"Nihad Abbasov","committer_email":"narkoz.2008@gmail.com"},"protected": false}
diff --git a/lib/gitlab-cli/spec/fixtures/create_merge_request.json b/lib/gitlab-cli/spec/fixtures/create_merge_request.json
new file mode 100644
index 000000000..c27435168
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/create_merge_request.json
@@ -0,0 +1 @@
+{"id":2,"target_branch":"master","source_branch":"api","project_id":3,"title":"New feature","closed":false,"merged":false,"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-10-19T05:56:05Z"},"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-10-19T05:56:14Z"}}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/error_already_exists.json b/lib/gitlab-cli/spec/fixtures/error_already_exists.json
new file mode 100644
index 000000000..d1070f560
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/error_already_exists.json
@@ -0,0 +1 @@
+{"message": "409 Already exists"}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/group.json b/lib/gitlab-cli/spec/fixtures/group.json
new file mode 100644
index 000000000..bce3581d8
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/group.json
@@ -0,0 +1,60 @@
+{"id": 10, "name": "GitLab-Group", "path": "gitlab-group", "owner_id": 6, "projects": [
+ {
+ "id": 9,
+ "name": "mojito",
+ "description": null,
+ "default_branch": "master",
+ "owner": {
+ "id": 6,
+ "username": "jose",
+ "email": "jose@abc.com",
+ "name": "Jose Jose",
+ "blocked": false,
+ "created_at": "2013-02-06T06:54:06Z"
+ },
+ "path": "mojito",
+ "path_with_namespace": "gitlab-group/mojito",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wall_enabled": true,
+ "wiki_enabled": true,
+ "created_at": "2013-02-06T16:59:15Z",
+ "namespace": {
+ "created_at": "2013-02-06T16:58:22Z",
+ "id": 10,
+ "name": "GitLab-Group",
+ "owner_id": 6,
+ "path": "gitlab-group",
+ "updated_at": "2013-02-06T16:58:22Z"
+ }
+ },
+ {
+ "id": 10,
+ "name": "gitlabhq",
+ "description": null,
+ "default_branch": null,
+ "owner": {
+ "id": 6,
+ "username": "randx",
+ "email": "randx@github.com",
+ "name": "Dmitry Z",
+ "blocked": false,
+ "created_at": "2013-02-06T06:54:06Z"
+ },
+ "path": "gitlabhq",
+ "path_with_namespace": "gitlab-group/gitlabhq",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wall_enabled": true,
+ "wiki_enabled": true,
+ "created_at": "2013-02-06T17:02:31Z",
+ "namespace": {
+ "created_at": "2013-02-06T16:58:22Z",
+ "id": 10,
+ "name": "GitLab-Group",
+ "owner_id": 6,
+ "path": "gitlab-group",
+ "updated_at": "2013-02-06T16:58:22Z"
+ }
+ }
+]}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/group_create.json b/lib/gitlab-cli/spec/fixtures/group_create.json
new file mode 100644
index 000000000..67445f68b
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/group_create.json
@@ -0,0 +1 @@
+{"id":3,"name":"Gitlab-Group","path":"gitlab-group","owner_id":1}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/group_member.json b/lib/gitlab-cli/spec/fixtures/group_member.json
new file mode 100644
index 000000000..feef54322
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/group_member.json
@@ -0,0 +1 @@
+{"id":2,"username":"jsmith","email":"jsmith@local.host","name":"John Smith","state":"active","created_at":"2013-09-04T18:15:30Z","access_level":10}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/group_member_delete.json b/lib/gitlab-cli/spec/fixtures/group_member_delete.json
new file mode 100644
index 000000000..ff052edf6
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/group_member_delete.json
@@ -0,0 +1 @@
+{"created_at":"2013-09-04T18:18:15Z","group_access":10,"group_id":3,"id":2,"notification_level":3,"updated_at":"2013-09-04T18:18:15Z","user_id":2}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/group_members.json b/lib/gitlab-cli/spec/fixtures/group_members.json
new file mode 100644
index 000000000..02ddc1089
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/group_members.json
@@ -0,0 +1 @@
+[{"id":1,"username":"eraymond","email":"eraymond@local.host","name":"Edward Raymond","state":"active","created_at":"2013-08-30T16:16:22Z","access_level":50},{"id":1,"username":"jsmith","email":"jsmith@local.host","name":"John Smith","state":"active","created_at":"2013-08-30T16:16:22Z","access_level":50}]
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/groups.json b/lib/gitlab-cli/spec/fixtures/groups.json
new file mode 100644
index 000000000..7d8b426a4
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/groups.json
@@ -0,0 +1,2 @@
+[{"id": 3,"name": "ThreeGroup","path": "threegroup","owner_id": 1},{"id": 5,"name": "Five-Group","path": "five-group","owner_id": 2},{"id": 8,"name": "Eight Group","path": "eight-group","owner_id": 6}
+]
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/issue.json b/lib/gitlab-cli/spec/fixtures/issue.json
new file mode 100644
index 000000000..9f70318a7
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/issue.json
@@ -0,0 +1 @@
+{"id":33,"project_id":3,"title":"Beatae possimus nostrum nihil reiciendis laboriosam nihil delectus alias accusantium dolor unde.","description":null,"labels":[],"milestone":null,"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/issues.json b/lib/gitlab-cli/spec/fixtures/issues.json
new file mode 100644
index 000000000..62e4cadd2
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/issues.json
@@ -0,0 +1 @@
+[{"id":1,"project_id":1,"title":"Culpa eius recusandae suscipit autem distinctio dolorum.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":6,"project_id":2,"title":"Ut in dolorum omnis sed sit aliquam.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":12,"project_id":3,"title":"Veniam et tempore quidem eum reprehenderit cupiditate non aut velit eaque.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":21,"project_id":1,"title":"Vitae ea aliquam et quo eligendi sapiente voluptatum labore hic nihil culpa.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":26,"project_id":2,"title":"Quo enim est nihil atque placeat voluptas neque eos voluptas.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":32,"project_id":3,"title":"Deserunt tenetur impedit est beatae voluptas voluptas quaerat quisquam.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"}]
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/key.json b/lib/gitlab-cli/spec/fixtures/key.json
new file mode 100644
index 000000000..6595c8ceb
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/key.json
@@ -0,0 +1 @@
+{"id":1,"title":"narkoz@helium","key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkUsh42Nh1yefGd1jbSELn5XsY8p5Oxmau0/1HqHnjuYOaj5X+kHccFDwtmtg9Ox8ua/+WptNsiE8IUwsD3zKgEjajgwq3gMeeFdxfXwM+tEvHOOMV9meRrgRWGYCToPbT6sR7/YMAYa7cPqWSpx/oZhYfz4XtoMv3ZZT1fZMmx3MY3HwXwW8j+obJyN2K4LN0TFi9RPgWWYn0DCyb9OccmABimt3i74WoJ/OT8r6/7swce8+OSe0Q2wBhyTtvxg2vtUcoek8Af+EZaUMBwSEzEsocOCzwQvjF5XUk5o7dJ8nP8W3RE60JWX57t16eQm7lBmumLYfszpn2isd6W7a1 narkoz@helium"}
diff --git a/lib/gitlab-cli/spec/fixtures/keys.json b/lib/gitlab-cli/spec/fixtures/keys.json
new file mode 100644
index 000000000..d81fca6ad
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/keys.json
@@ -0,0 +1 @@
+[{"id":1,"title":"narkoz@helium","key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkUsh42Nh1yefGd1jbSELn5XsY8p5Oxmau0/1HqHnjuYOaj5X+kHccFDwtmtg9Ox8ua/+WptNsiE8IUwsD3zKgEjajgwq3gMeeFdxfXwM+tEvHOOMV9meRrgRWGYCToPbT6sR7/YMAYa7cPqWSpx/oZhYfz4XtoMv3ZZT1fZMmx3MY3HwXwW8j+obJyN2K4LN0TFi9RPgWWYn0DCyb9OccmABimt3i74WoJ/OT8r6/7swce8+OSe0Q2wBhyTtvxg2vtUcoek8Af+EZaUMBwSEzEsocOCzwQvjF5XUk5o7dJ8nP8W3RE60JWX57t16eQm7lBmumLYfszpn2isd6W7a1 narkoz@helium"}]
diff --git a/lib/gitlab-cli/spec/fixtures/merge_request.json b/lib/gitlab-cli/spec/fixtures/merge_request.json
new file mode 100644
index 000000000..5278f4664
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/merge_request.json
@@ -0,0 +1 @@
+{"id":1,"target_branch":"master","source_branch":"api","project_id":3,"title":"New feature","closed":false,"merged":false,"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-10-19T05:56:05Z"},"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-10-19T05:56:14Z"}}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/merge_request_comments.json b/lib/gitlab-cli/spec/fixtures/merge_request_comments.json
new file mode 100644
index 000000000..3b9733ef3
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/merge_request_comments.json
@@ -0,0 +1 @@
+[{"note":"this is the 1st comment on the 2merge merge request","author":{"id":11,"username":"admin","email":"admin@example.com","name":"A User","state":"active","created_at":"2014-03-06T08:17:35.000Z"}},{"note":"another discussion point on the 2merge request","author":{"id":12,"username":"admin","email":"admin@example.com","name":"A User","state":"active","created_at":"2014-03-06T08:17:35.000Z"}}]
diff --git a/lib/gitlab-cli/spec/fixtures/merge_requests.json b/lib/gitlab-cli/spec/fixtures/merge_requests.json
new file mode 100644
index 000000000..ea32ac4e6
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/merge_requests.json
@@ -0,0 +1 @@
+[{"id":1,"target_branch":"master","source_branch":"api","project_id":3,"title":"New feature","closed":false,"merged":false,"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-10-19T05:56:05Z"},"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-10-19T05:56:14Z"}}]
diff --git a/lib/gitlab-cli/spec/fixtures/milestone.json b/lib/gitlab-cli/spec/fixtures/milestone.json
new file mode 100644
index 000000000..94ea3d360
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/milestone.json
@@ -0,0 +1 @@
+{"id":1,"project_id":3,"title":"3.0","description":"","due_date":"2012-10-22","closed":false,"updated_at":"2012-09-17T10:15:31Z","created_at":"2012-09-17T10:15:31Z"}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/milestones.json b/lib/gitlab-cli/spec/fixtures/milestones.json
new file mode 100644
index 000000000..f9e309af6
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/milestones.json
@@ -0,0 +1 @@
+[{"id":1,"project_id":3,"title":"3.0","description":"","due_date":"2012-10-22","closed":false,"updated_at":"2012-09-17T10:15:31Z","created_at":"2012-09-17T10:15:31Z"}]
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/note.json b/lib/gitlab-cli/spec/fixtures/note.json
new file mode 100644
index 000000000..3f575aed3
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/note.json
@@ -0,0 +1 @@
+{"id":1201,"body":"The solution is rather tricky","author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"created_at":"2012-11-27T19:16:44Z"}
diff --git a/lib/gitlab-cli/spec/fixtures/notes.json b/lib/gitlab-cli/spec/fixtures/notes.json
new file mode 100644
index 000000000..15c0d8ca2
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/notes.json
@@ -0,0 +1 @@
+[{"id":1201,"body":"The solution is rather tricky","author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"created_at":"2012-11-27T19:16:44Z"},{"id":1207,"body":"I know, right?","author":{"id":1,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"created_at":"2012-11-27T19:58:21Z"}]
diff --git a/lib/gitlab-cli/spec/fixtures/project.json b/lib/gitlab-cli/spec/fixtures/project.json
new file mode 100644
index 000000000..1f4f96028
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project.json
@@ -0,0 +1 @@
+{"id":3,"code":"gitlab","name":"Gitlab","description":null,"path":"gitlab","default_branch":null,"owner":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"public":false,"issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"created_at":"2012-09-17T09:41:58Z"}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/project_commit.json b/lib/gitlab-cli/spec/fixtures/project_commit.json
new file mode 100644
index 000000000..ace52b700
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_commit.json
@@ -0,0 +1,13 @@
+{
+ "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
+ "short_id": "6104942438c",
+ "title": "Sanitize for network graph",
+ "author_name": "randx",
+ "author_email": "dmitriy.zaporozhets@gmail.com",
+ "created_at": "2012-09-20T09:06:12+03:00",
+ "committed_date": "2012-09-20T09:06:12+03:00",
+ "authored_date": "2012-09-20T09:06:12+03:00",
+ "parent_ids": [
+ "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
+ ]
+}
diff --git a/lib/gitlab-cli/spec/fixtures/project_commit_diff.json b/lib/gitlab-cli/spec/fixtures/project_commit_diff.json
new file mode 100644
index 000000000..ad3dfde86
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_commit_diff.json
@@ -0,0 +1,10 @@
+{
+ "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files",
+ "new_path": "doc/update/5.4-to-6.0.md",
+ "old_path": "doc/update/5.4-to-6.0.md",
+ "a_mode": null,
+ "b_mode": "100644",
+ "new_file": false,
+ "renamed_file": false,
+ "deleted_file": false
+}
diff --git a/lib/gitlab-cli/spec/fixtures/project_commits.json b/lib/gitlab-cli/spec/fixtures/project_commits.json
new file mode 100644
index 000000000..58cb5020d
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_commits.json
@@ -0,0 +1 @@
+[{"id":"f7dd067490fe57505f7226c3b54d3127d2f7fd46","short_id":"f7dd067490f","title":"API: expose issues project id","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-25T04:22:21-07:00"},{"id":"949b1df930bedace1dbd755aaa4a82e8c451a616","short_id":"949b1df930b","title":"API: update docs","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-25T02:35:41-07:00"},{"id":"1b95c8bff351f6718ec31ac1de1e48c57bc95d44","short_id":"1b95c8bff35","title":"API: ability to get project by id","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-25T02:18:30-07:00"},{"id":"92d98f5a0c28bffd7b070cda190b07ab72667d58","short_id":"92d98f5a0c2","title":"Merge pull request #1118 from patthoyts/pt/ldap-missing-password","author_name":"Dmitriy Zaporozhets","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-25T01:51:06-07:00"},{"id":"60d3e94874964a626f105d3598e1c122addcf43e","short_id":"60d3e948749","title":"Merge pull request #1122 from patthoyts/pt/missing-log","author_name":"Dmitriy Zaporozhets","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-25T01:50:34-07:00"},{"id":"b683a71aa1230f17f9df47661c77dfeae27027de","short_id":"b683a71aa12","title":"Merge pull request #1135 from NARKOZ/api","author_name":"Dmitriy Zaporozhets","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-25T01:48:00-07:00"},{"id":"fbb41100db35cf2def2c8b4d896b7015d56bd15b","short_id":"fbb41100db3","title":"update help section with issues API docs","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-24T05:52:43-07:00"},{"id":"eca823c1c7cef45cc18c6ab36d2327650c85bfc3","short_id":"eca823c1c7c","title":"Merge branch 'master' into api","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-24T05:46:36-07:00"},{"id":"024e0348904179a8dea81c01e27a5f014cf57499","short_id":"024e0348904","title":"update API docs","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-24T05:25:01-07:00"},{"id":"7b33d8cbcab3b0ee5789ec607455ab62130db69f","short_id":"7b33d8cbcab","title":"add issues API","author_name":"Nihad Abbasov","author_email":"narkoz.2008@gmail.com","created_at":"2012-07-24T05:19:51-07:00"},{"id":"6035ad7e1fe519d0c6a42731790183889e3ba31d","short_id":"6035ad7e1fe","title":"Create the githost.log file if necessary.","author_name":"Pat Thoyts","author_email":"patthoyts@users.sourceforge.net","created_at":"2012-07-21T07:32:04-07:00"},{"id":"a2d244ec062f3348f6cd1c5218c6097402c5f562","short_id":"a2d244ec062","title":"Handle LDAP missing credentials error with a flash message.","author_name":"Pat Thoyts","author_email":"patthoyts@users.sourceforge.net","created_at":"2012-07-21T01:04:05-07:00"},{"id":"8b7e404b5b6944e9c92cc270b2e5d0005781d49d","short_id":"8b7e404b5b6","title":"Up to 2.7.0","author_name":"randx","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-21T00:53:55-07:00"},{"id":"11721b0dbe82c35789be3e4fa8e14663934b2ff5","short_id":"11721b0dbe8","title":"Help section for system hooks completed","author_name":"randx","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-21T00:47:57-07:00"},{"id":"9c8a1e651716212cf50a623d98e03b8dbdb2c64a","short_id":"9c8a1e65171","title":"Fix system hook example","author_name":"randx","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-21T00:32:42-07:00"},{"id":"4261acda90ff4c61326d80cba026ae76e8551f8f","short_id":"4261acda90f","title":"move SSH keys tab closer to begining","author_name":"randx","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-21T00:27:09-07:00"},{"id":"a69fc5dd23bd502fd36892a80eec21a4c53891f8","short_id":"a69fc5dd23b","title":"Endless event loading for dsahboard","author_name":"randx","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-21T00:23:05-07:00"},{"id":"860fa1163a5fbdfec2bb01ff2d584351554dee29","short_id":"860fa1163a5","title":"Merge pull request #1117 from patthoyts/pt/user-form","author_name":"Dmitriy Zaporozhets","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-20T14:23:49-07:00"},{"id":"787e5e94acf5e20280416c9fda105ef5b77576b3","short_id":"787e5e94acf","title":"Fix english on the edit user form.","author_name":"Pat Thoyts","author_email":"patthoyts@users.sourceforge.net","created_at":"2012-07-20T14:18:42-07:00"},{"id":"9267cb04b0b3fdf127899c4b7e636dc27fac06d3","short_id":"9267cb04b0b","title":"Merge branch 'refactoring_controllers' of dev.gitlabhq.com:gitlabhq","author_name":"Dmitriy Zaporozhets","author_email":"dmitriy.zaporozhets@gmail.com","created_at":"2012-07-20T07:24:56-07:00"}]
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/project_delete_key.json b/lib/gitlab-cli/spec/fixtures/project_delete_key.json
new file mode 100644
index 000000000..ed4141599
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_delete_key.json
@@ -0,0 +1,8 @@
+{
+ "created_at": "2013-10-05T15:05:26Z",
+ "fingerprint": "5c:b5:e6:b0:f5:31:65:3f:a6:b5:59:86:32:cc:15:e1",
+ "id": 2,
+ "key": "ssh-rsa ...",
+ "updated_at": "2013-10-05T15:05:26Z",
+ "user_id": null
+}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/project_events.json b/lib/gitlab-cli/spec/fixtures/project_events.json
new file mode 100644
index 000000000..4d5afc04e
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_events.json
@@ -0,0 +1 @@
+[{"title":null,"project_id":2,"action_name":"opened","target_id":null,"target_type":null,"author_id":1,"data":{"before":"ac0c1aa3898d6dea54d7868ea6f9c45fd5e30c59","after":"66350dbb62a221bc619b665aef3e1e7d3b306747","ref":"refs/heads/master","user_id":1,"user_name":"Administrator","project_id":2,"repository":{"name":"gitlab-ci","url":"git@demo.gitlab.com:gitlab/gitlab-ci.git","description":"Continuous integration server for gitlabhq | Coordinator","homepage":"http://demo.gitlab.com/gitlab/gitlab-ci"},"commits":[{"id":"8cf469b039931bab37bbf025e6b69287ea3cfb0e","message":"Modify screenshot\n\nSigned-off-by: Dmitriy Zaporozhets \u003Cdummy@gmail.com\u003E","timestamp":"2014-05-20T10:34:27+00:00","url":"http://demo.gitlab.com/gitlab/gitlab-ci/commit/8cf469b039931bab37bbf025e6b69287ea3cfb0e","author":{"name":"Dummy","email":"dummy@gmail.com"}},{"id":"66350dbb62a221bc619b665aef3e1e7d3b306747","message":"Edit some code\n\nSigned-off-by: Dmitriy Zaporozhets \u003Cdummy@gmail.com\u003E","timestamp":"2014-05-20T10:35:15+00:00","url":"http://demo.gitlab.com/gitlab/gitlab-ci/commit/66350dbb62a221bc619b665aef3e1e7d3b306747","author":{"name":"Dummy","email":"dummy@gmail.com"}}],"total_commits_count":2},"target_title":null,"created_at":"2014-05-20T10:35:26.240Z"},{"title":null,"project_id":2,"action_name":"opened","target_id":2,"target_type":"MergeRequest","author_id":1,"data":null,"target_title":" Morbi et cursus leo. Sed eget vestibulum sapien","created_at":"2014-05-20T10:24:11.917Z"}]
diff --git a/lib/gitlab-cli/spec/fixtures/project_for_user.json b/lib/gitlab-cli/spec/fixtures/project_for_user.json
new file mode 100644
index 000000000..e2835d865
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_for_user.json
@@ -0,0 +1 @@
+{"id":1,"code":"brute","name":"Brute","description":null,"path":"brute","default_branch":null,"owner":{"id":1,"email":"john@example.com","name":"John Owner","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"}
diff --git a/lib/gitlab-cli/spec/fixtures/project_fork_link.json b/lib/gitlab-cli/spec/fixtures/project_fork_link.json
new file mode 100644
index 000000000..f1490dfa7
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_fork_link.json
@@ -0,0 +1 @@
+{"created_at":"2013-07-03T13:51:48Z","forked_from_project_id":24,"forked_to_project_id":42,"id":1,"updated_at":"2013-07-03T13:51:48Z"}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/project_hook.json b/lib/gitlab-cli/spec/fixtures/project_hook.json
new file mode 100644
index 000000000..180dd4555
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_hook.json
@@ -0,0 +1 @@
+{"id":1,"url":"https://api.example.net/v1/webhooks/ci"}
diff --git a/lib/gitlab-cli/spec/fixtures/project_hooks.json b/lib/gitlab-cli/spec/fixtures/project_hooks.json
new file mode 100644
index 000000000..e70d4122c
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_hooks.json
@@ -0,0 +1 @@
+[{"id":1,"url":"https://api.example.net/v1/webhooks/ci"}]
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/project_issues.json b/lib/gitlab-cli/spec/fixtures/project_issues.json
new file mode 100644
index 000000000..87fb2fb18
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_issues.json
@@ -0,0 +1 @@
+[{"id":36,"project_id":3,"title":"Eos ut modi et laudantium quasi porro voluptas sed.","description":null,"labels":[],"milestone":null,"assignee":{"id":5,"email":"aliza_stark@schmeler.info","name":"Michale Von","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":5,"email":"aliza_stark@schmeler.info","name":"Michale Von","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":35,"project_id":3,"title":"Ducimus illo in iure voluptatem dolores labore.","description":null,"labels":[],"milestone":null,"assignee":{"id":4,"email":"nicole@mertz.com","name":"Felipe Davis","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":4,"email":"nicole@mertz.com","name":"Felipe Davis","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":34,"project_id":3,"title":"Rem tempora voluptatum atque eum sit nihil neque.","description":null,"labels":[],"milestone":null,"assignee":{"id":3,"email":"wilma@mayerblanda.ca","name":"Beatrice Jewess","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":3,"email":"wilma@mayerblanda.ca","name":"Beatrice Jewess","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":33,"project_id":3,"title":"Beatae possimus nostrum nihil reiciendis laboriosam nihil delectus alias accusantium dolor unde.","description":null,"labels":[],"milestone":null,"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":32,"project_id":3,"title":"Deserunt tenetur impedit est beatae voluptas voluptas quaerat quisquam.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":16,"project_id":3,"title":"Numquam earum aut laudantium reprehenderit voluptatem aut.","description":null,"labels":[],"milestone":null,"assignee":{"id":5,"email":"aliza_stark@schmeler.info","name":"Michale Von","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":5,"email":"aliza_stark@schmeler.info","name":"Michale Von","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":15,"project_id":3,"title":"Qui veritatis voluptas fuga voluptate voluptas cupiditate.","description":null,"labels":[],"milestone":null,"assignee":{"id":4,"email":"nicole@mertz.com","name":"Felipe Davis","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":4,"email":"nicole@mertz.com","name":"Felipe Davis","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":14,"project_id":3,"title":"In assumenda et ipsa qui debitis voluptatem incidunt.","description":null,"labels":[],"milestone":null,"assignee":{"id":3,"email":"wilma@mayerblanda.ca","name":"Beatrice Jewess","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":3,"email":"wilma@mayerblanda.ca","name":"Beatrice Jewess","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":13,"project_id":3,"title":"Illo eveniet consequatur enim iste provident facilis rerum voluptatem et architecto aut.","description":null,"labels":[],"milestone":null,"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"author":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":12,"project_id":3,"title":"Veniam et tempore quidem eum reprehenderit cupiditate non aut velit eaque.","description":null,"labels":[],"milestone":null,"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"}]
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/project_key.json b/lib/gitlab-cli/spec/fixtures/project_key.json
new file mode 100644
index 000000000..d917f9466
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_key.json
@@ -0,0 +1,6 @@
+{
+ "id": 2,
+ "title": "Key Title",
+ "key": "ssh-rsa ...",
+ "created_at": "2013-09-22T18:34:32Z"
+}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/project_keys.json b/lib/gitlab-cli/spec/fixtures/project_keys.json
new file mode 100644
index 000000000..dd22f9668
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_keys.json
@@ -0,0 +1,6 @@
+[{
+ "id": 2,
+ "title": "Key Title",
+ "key": "ssh-rsa ...",
+ "created_at": "2013-09-22T18:34:32Z"
+}]
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/project_tags.json b/lib/gitlab-cli/spec/fixtures/project_tags.json
new file mode 100644
index 000000000..1e2fb96cb
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/project_tags.json
@@ -0,0 +1 @@
+[{"name":"v2.8.2","commit":{"id":"a502f67c0b358cc6b391df0c5dca48375c21fcad","parents":[{"id":"4381084af341684240b1a671d368511afcf5774a"}],"tree":"1612068bdd20de5d14b3096cfa4c621e2051ed4c","message":"Up to 2.8.2","author":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-08-24T02:03:48-07:00","committed_date":"2012-08-24T02:03:48-07:00"}},{"name":"v2.8.1","commit":{"id":"ed2b53cd1c34c421b23208eeb502a141a6829f9d","parents":[{"id":"7ab587a47791e371f5c109c14097a5d1d7776ea5"}],"tree":"b7393b0b33b777583b285e85b423c4e5ab7f859f","message":"Up to 2.8.1","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-08-22T23:17:18-07:00","committed_date":"2012-08-22T23:17:18-07:00"}},{"name":"v2.8.0pre","commit":{"id":"b2c6ba97a25d299e83c51493d7bc770c13b8ed1a","parents":[{"id":"05da3801f53f06fdc529b4f3820af1380039f245"},{"id":"66399d558da45fb9cd3ea972a47a4f7bb12bfc8d"}],"tree":"36ad53f35bce1fe3f2a4a5f840e7b1bdbfed9c82","message":"Merge pull request #1230 from tsigo/hooray_apostrophes\n\nCorrect usage of \"Can't\"","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-08-16T14:11:08-07:00","committed_date":"2012-08-16T14:11:08-07:00"}},{"name":"v2.8.0","commit":{"id":"5c7ed6fa26b47ac71ff6ba04720d85df6d74b200","parents":[{"id":"d1daeba1736ba145fe525ce08a91f29495a3abf1"}],"tree":"4fc230ff2dbc0e75f27321eac2976aba5a6d323d","message":"Up to 2.8","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-08-21T15:15:26-07:00","committed_date":"2012-08-21T15:15:26-07:00"}},{"name":"v2.7.0pre","commit":{"id":"72a571724d84d112f98a5543c971e9b3b9da1383","parents":[{"id":"3ac840ff06e0ee5b349c52b5a8c02e803a17eec3"},{"id":"990b9217d9a55e26a53d4143d4a3c89123384327"}],"tree":"64b104df5d956e21e0749dc8e70849d1989de36f","message":"Merge pull request #1096 from moregeek/show-flash-note-when-destroying-a-project\n\nshow flash notice after deletion of a project","author":{"name":"Valeriy Sizov","email":"vsv2711@gmail.com"},"committer":{"name":"Valeriy Sizov","email":"vsv2711@gmail.com"},"authored_date":"2012-07-18T05:35:42-07:00","committed_date":"2012-07-18T05:35:42-07:00"}},{"name":"v2.7.0","commit":{"id":"8b7e404b5b6944e9c92cc270b2e5d0005781d49d","parents":[{"id":"11721b0dbe82c35789be3e4fa8e14663934b2ff5"}],"tree":"89fe8c5ff58daaedea07a910cffb14b04ebcc828","message":"Up to 2.7.0","author":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-07-21T00:53:55-07:00","committed_date":"2012-07-21T00:53:55-07:00"}},{"name":"v2.6.3","commit":{"id":"666cdb22792dd955a286b9993d6235b4cdd68b4b","parents":[{"id":"d92446df1fdba87101c92c90b1c34eb2f1eebef4"}],"tree":"888173aa4c12a4920d318c35b950095d3505673d","message":"up to 2.6.3","author":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-06-26T09:20:47-07:00","committed_date":"2012-06-26T09:21:28-07:00"}},{"name":"v2.6.2","commit":{"id":"39fecb554f172a0c8ea00316e612e1d37efc7200","parents":[{"id":"68389588d664100590b1a6ca7eedd50860b7e9bc"}],"tree":"53accb25e0b9d038d550cf387753bde15fe4ad19","message":"Up to 2.6.2","author":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-06-22T13:50:58-07:00","committed_date":"2012-06-22T13:50:58-07:00"}},{"name":"v2.6.1","commit":{"id":"d92a22c9e627268eca697bbd9b660d8c335df953","parents":[{"id":"193804516b8b0783c850981456e947f888ff51bb"}],"tree":"4ac1b5225f597ab55372cb5e950b121d6f55e386","message":"Up to 2.6.1","author":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-06-22T12:49:03-07:00","committed_date":"2012-06-22T12:49:03-07:00"}},{"name":"v2.6.0","commit":{"id":"b32465712becfbcf83d63b1e6eff7d1483fdabea","parents":[{"id":"1903f6ade027df0f10ef96b9439495eeda07482c"}],"tree":"ffbc05fd0f1771c1602c956df9556260048c7167","message":"Up to 2.6","author":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"randx","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-06-21T10:25:23-07:00","committed_date":"2012-06-21T10:25:23-07:00"}},{"name":"v2.5.0","commit":{"id":"cc8369144db2147d2956e8dd7d314e9a7dfd4fbb","parents":[{"id":"1b2068eaa91e5002d01a220c65da21dad8ccb071"}],"tree":"666a442e00689911169e8cc336c5ce60d014854c","message":"Prevent app crash in case if encoding failed","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-05-22T04:57:04-07:00","committed_date":"2012-05-22T04:57:04-07:00"}},{"name":"v2.4.2","commit":{"id":"f18339c26d673c5f8b4c19776036fd42a0de30aa","parents":[{"id":"c937d06c3c98e9ffce8ec1132203eaff6bf7b231"},{"id":"35e602f19c83585d64aa2043ed26eeb8cd7b40e2"}],"tree":"5101f0cd8e395fee1996764298a202437757e85b","message":"Merge branch 'master' of github.com:gitlabhq/gitlabhq","author":{"name":"Zevs","email":"vsv2711@gmail.com"},"committer":{"name":"Zevs","email":"vsv2711@gmail.com"},"authored_date":"2012-04-29T14:24:59-07:00","committed_date":"2012-04-29T14:24:59-07:00"}},{"name":"v2.4.1","commit":{"id":"d97a9aa4a44ff9f452144fad348fd9d7e3b48260","parents":[{"id":"21f3da23589d50038728393f0badc6255b5762ca"}],"tree":"905c33874b064778199f806749d5688e33d64be3","message":"fixed email markdown","author":{"name":"gitlabhq","email":"m@gitlabhq.com"},"committer":{"name":"gitlabhq","email":"m@gitlabhq.com"},"authored_date":"2012-04-23T05:32:56-07:00","committed_date":"2012-04-23T05:32:56-07:00"}},{"name":"v2.4.0pre","commit":{"id":"1845429268364e75bffdeb1075de8f1606e157ec","parents":[{"id":"45b18365d5f409f196a02a4e6e2b77b8ebef909b"}],"tree":"423c70246fa7ffd8804b26628fea34bdb2b22846","message":"Use try for commit prev_commit_id detection","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-04-19T13:35:35-07:00","committed_date":"2012-04-19T13:35:35-07:00"}},{"name":"v2.4.0","commit":{"id":"204c66461ed519eb0078be7e8ac8a6cb56834753","parents":[{"id":"511d07c47c9bf3a18bfa276d452c899369432a22"}],"tree":"9416c777cccf87d348f5705078e82f3f97485e19","message":"corrected exception for automerge","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-04-22T06:49:45-07:00","committed_date":"2012-04-22T06:49:45-07:00"}},{"name":"v2.3.1","commit":{"id":"fa8219e0a753e642a6f1dbdfc010d01ae8a949ee","parents":[{"id":"81da8e46f24913ccf42d3e2644962cbcbc0f9c2e"}],"tree":"5debfcd6d17f9d582aace6ac9b80db27d5c1fe36","message":"better MR list, dashboard pollished","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-03-22T13:57:04-07:00","committed_date":"2012-03-22T13:57:04-07:00"}},{"name":"v2.3.0pre","commit":{"id":"cadf12c60cc27c5b0b8273c1de4b190a0e88bd7d","parents":[{"id":"724ea16c348bc61cf7cb3dbe362c6f30cff1b2c7"}],"tree":"6f4c22761fd2dee405d3fbf38f9dd835bb3c8694","message":"Merged activities & projects pages","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-03-19T15:05:35-07:00","committed_date":"2012-03-19T15:05:35-07:00"}},{"name":"v2.3.0","commit":{"id":"b57faf9282d7df6cdd62953d474652a0ae2e6896","parents":[{"id":"cadf12c60cc27c5b0b8273c1de4b190a0e88bd7d"}],"tree":"f0d5b826df373191b4681452fc2ae4c5970cef4a","message":"Push events polished","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-03-20T14:59:35-07:00","committed_date":"2012-03-20T14:59:35-07:00"}},{"name":"v2.2.0pre","commit":{"id":"6a445b42003007cbb6c06f477c4d7a0b175688c1","parents":[{"id":"22f4c1908d0fc2dbce02e74ed03bf65f028d78d6"}],"tree":"9c60577833f6ca717acdebfa66140124c88e8471","message":"fixed forgot password form","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-02-20T10:37:37-08:00","committed_date":"2012-02-20T10:37:37-08:00"}},{"name":"v2.2.0","commit":{"id":"9e6d0710e927aa8ea834b8a9ae9f277be617ac7d","parents":[{"id":"8c40aab120dbc5507ab9cc8d7ad8e2519d6e9f25"},{"id":"6ea87c47f0f8a24ae031c3fff17bc913889ecd00"}],"tree":"86c831ab21236f21ffa5b97c752369612ce41b39","message":"Merge pull request #443 from CedricGatay/fix/incorrectLineNumberingInDiff\n\nIncorrect line numbering in diff","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-02-22T07:14:54-08:00","committed_date":"2012-02-22T07:14:54-08:00"}},{"name":"v2.1.0","commit":{"id":"98d6492582d232ed86525aa31ccbf280f4cbdaef","parents":[{"id":"611c5a87ab0c083a43785323b09cc47f554c3ba4"}],"tree":"1689b3cad580a18fd9b429ee0b33dac21c9f5a48","message":"removed broken migration","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2012-01-22T10:52:06-08:00","committed_date":"2012-01-22T10:52:06-08:00"}},{"name":"v2.0.0","commit":{"id":"9a2a8612769d472503b367fa35e99f6fb2876704","parents":[{"id":"2f7b67161952fc9ab322eba6878511b5f2dd5cf1"}],"tree":"26cdb4e66b5e664fe4bcd57d011c54c9c9c26ded","message":"Design tab for profile. Colorscheme as db value","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2011-12-20T12:47:09-08:00","committed_date":"2011-12-20T12:47:09-08:00"}},{"name":"v1.2.2","commit":{"id":"139a332293b9d8c4e5436619036e093483d8347f","parents":[{"id":"ade12da9488bea19d12505c371ead35686a1436e"}],"tree":"365d57f4df5c5dcac69b666cf6d2bfd8ef058008","message":"updated readme","author":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"authored_date":"2011-11-25T14:30:51-08:00","committed_date":"2011-11-25T14:30:51-08:00"}},{"name":"v1.2.1","commit":{"id":"7ebba27db21719c0035bab65fea92a4780051c73","parents":[{"id":"b56024100d40457a998f83adae3cdc830c997cda"},{"id":"a4fbe13fce87cb6ff2a27a2574ae25bf1dad145c"}],"tree":"b121a7576af1503a96954ce9a94598a68579e053","message":"Merge branch 'master' of dev.gitlabhq.com:gitlabhq","author":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"authored_date":"2011-11-22T13:15:09-08:00","committed_date":"2011-11-22T13:15:09-08:00"}},{"name":"v1.2.0pre","commit":{"id":"86829cae50857b5edf74b935380c6f68a19c2282","parents":[{"id":"a6b99319381c2d62ec4b92d64805e8de8965859e"}],"tree":"6aab9d13000584fa96fb3cb34d94f3b122bd1143","message":"fixed min height for menu","author":{"name":"gitlabhq","email":"m@gitlabhq.com"},"committer":{"name":"gitlabhq","email":"m@gitlabhq.com"},"authored_date":"2011-11-22T06:03:27-08:00","committed_date":"2011-11-22T06:03:27-08:00"}},{"name":"v1.2.0","commit":{"id":"b56024100d40457a998f83adae3cdc830c997cda","parents":[{"id":"4451b8df8ad6d4b6d79fbce77687c6c2fd37d0a9"}],"tree":"f402cbb6d54526a32b30968c98410bae97b27c8d","message":"lil style fixes","author":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"authored_date":"2011-11-22T09:57:25-08:00","committed_date":"2011-11-22T09:57:25-08:00"}},{"name":"v1.1.0pre","commit":{"id":"6b030fd41d697e327d2935b406cba70b6a460504","parents":[{"id":"3a2b273316fb29d63b489906f85d9b5329377258"}],"tree":"63b1fdb2a0f135f7074f6a94da14543b8450dd71","message":"1.1pre1","author":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"authored_date":"2011-10-21T10:04:41-07:00","committed_date":"2011-10-21T10:04:41-07:00"}},{"name":"v1.1.0","commit":{"id":"ba8048d71019b5aaa1f92ee5c3415bfddaa9babb","parents":[{"id":"6b030fd41d697e327d2935b406cba70b6a460504"}],"tree":"4db2b5f4f9b374dd1be3579459bc5947c225c9ba","message":"v1.1.0","author":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"authored_date":"2011-10-22T06:07:26-07:00","committed_date":"2011-10-22T06:07:26-07:00"}},{"name":"v1.0.2","commit":{"id":"3a2b273316fb29d63b489906f85d9b5329377258","parents":[{"id":"757ea634665e475bf69c1ec962040a0511ee8aeb"},{"id":"c374eb80ff9fb71d37faffc15714bf98b632d3e5"}],"tree":"e0d8170e61a9468a7bb5d4e63305171ec1efa6bf","message":"Merge pull request #40 from vslinko/patch-1\n\nIncrease max key length. Some keys has comment after key string.","author":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dmitriy.zaporozhets@gmail.com"},"authored_date":"2011-10-18T23:30:06-07:00","committed_date":"2011-10-18T23:30:06-07:00"}},{"name":"v1.0.1","commit":{"id":"7b5799a97998b68416f1b6233ce427135c99165a","parents":[{"id":"0541b3f3c5dcd291d144c83d9731c75ee811b4e0"},{"id":"7b67480c76db8b9a9ccdc80015cc500dc6d26892"}],"tree":"e052185e9dd72a1b1a04d59a5f9efbf3c0369601","message":"Merge branch '1x' of github.com:gitlabhq/gitlabhq into 1x","author":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"committer":{"name":"Dmitriy Zaporozhets","email":"dzaporozhets@sphereconsultinginc.com"},"authored_date":"2011-10-14T15:16:44-07:00","committed_date":"2011-10-14T15:16:44-07:00"}}]
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/projects.json b/lib/gitlab-cli/spec/fixtures/projects.json
new file mode 100644
index 000000000..deab4c5f3
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/projects.json
@@ -0,0 +1 @@
+[{"id":1,"code":"brute","name":"Brute","description":null,"path":"brute","default_branch":null,"owner":{"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"},{"id":2,"code":"mozart","name":"Mozart","description":null,"path":"mozart","default_branch":null,"owner":{"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"},{"id":3,"code":"gitlab","name":"Gitlab","description":null,"path":"gitlab","default_branch":null,"owner":{"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"}]
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/protect_branch.json b/lib/gitlab-cli/spec/fixtures/protect_branch.json
new file mode 100644
index 000000000..752bc2389
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/protect_branch.json
@@ -0,0 +1 @@
+{"name":"api","commit":{"id":"f7dd067490fe57505f7226c3b54d3127d2f7fd46","parents":[{"id":"949b1df930bedace1dbd755aaa4a82e8c451a616"}],"tree":"f8c4b21c036339f92fcc5482aa28a41250553b27","message":"API: expose issues project id","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-07-25T04:22:21-07:00","committed_date":"2012-07-25T04:22:21-07:00"},"protected":true}
diff --git a/lib/gitlab-cli/spec/fixtures/session.json b/lib/gitlab-cli/spec/fixtures/session.json
new file mode 100644
index 000000000..e4f5ba35f
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/session.json
@@ -0,0 +1 @@
+{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z","private_token":"qEsq1pt6HJPaNciie3MG"}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/snippet.json b/lib/gitlab-cli/spec/fixtures/snippet.json
new file mode 100644
index 000000000..34e9d994d
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/snippet.json
@@ -0,0 +1 @@
+{"id":1,"title":"Rails Console ActionMailer","file_name":"mailer_test.rb","author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"expires_at":"2012-09-24T00:00:00Z","updated_at":"2012-09-17T09:51:42Z","created_at":"2012-09-17T09:51:42Z"}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/snippets.json b/lib/gitlab-cli/spec/fixtures/snippets.json
new file mode 100644
index 000000000..26457995c
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/snippets.json
@@ -0,0 +1 @@
+[{"id":1,"title":"Rails Console ActionMailer","file_name":"mailer_test.rb","author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"expires_at":"2012-09-24T00:00:00Z","updated_at":"2012-09-17T09:51:42Z","created_at":"2012-09-17T09:51:42Z"}]
diff --git a/lib/gitlab-cli/spec/fixtures/system_hook.json b/lib/gitlab-cli/spec/fixtures/system_hook.json
new file mode 100644
index 000000000..0028b7a52
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/system_hook.json
@@ -0,0 +1 @@
+{"id": 3, "url": "http://example.com/hook", "created_at": "2013-10-02T10:15:31Z"}
diff --git a/lib/gitlab-cli/spec/fixtures/system_hook_test.json b/lib/gitlab-cli/spec/fixtures/system_hook_test.json
new file mode 100644
index 000000000..cc79044ff
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/system_hook_test.json
@@ -0,0 +1 @@
+{ "event_name": "project_create", "name": "Ruby", "path": "ruby", "project_id": 1, "owner_name": "Someone", "owner_email": "example@gitlabhq.com" }
diff --git a/lib/gitlab-cli/spec/fixtures/system_hooks.json b/lib/gitlab-cli/spec/fixtures/system_hooks.json
new file mode 100644
index 000000000..2b58791c3
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/system_hooks.json
@@ -0,0 +1 @@
+[{"id": 3, "url": "http://example.com/hook", "created_at": "2013-10-02T10:15:31Z"}]
diff --git a/lib/gitlab-cli/spec/fixtures/tag.json b/lib/gitlab-cli/spec/fixtures/tag.json
new file mode 100644
index 000000000..a56a09262
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/tag.json
@@ -0,0 +1 @@
+{"name": "v1.0.0","commit": {"id": "2695effb5807a22ff3d138d593fd856244e155e7","parents": [],"message": "Initial commit","authored_date": "2012-05-28T04:42:42-07:00","author_name": "John Smith","author email": "john@example.com","committer_name": "Jack Smith","committed_date": "2012-05-28T04:42:42-07:00","committer_email": "jack@example.com"},"protected": false}
diff --git a/lib/gitlab-cli/spec/fixtures/team_member.json b/lib/gitlab-cli/spec/fixtures/team_member.json
new file mode 100644
index 000000000..fd3ac3852
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/team_member.json
@@ -0,0 +1 @@
+{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z","access_level":40}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/team_members.json b/lib/gitlab-cli/spec/fixtures/team_members.json
new file mode 100644
index 000000000..a2fe19e3b
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/team_members.json
@@ -0,0 +1 @@
+[{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z","access_level":40},{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-09-17T09:42:03Z","access_level":20},{"id":3,"email":"wilma@mayerblanda.ca","name":"Beatrice Jewess","blocked":false,"created_at":"2012-09-17T09:42:03Z","access_level":40},{"id":5,"email":"aliza_stark@schmeler.info","name":"Michale Von","blocked":false,"created_at":"2012-09-17T09:42:03Z","access_level":40},{"id":6,"email":"faye.watsica@rohanwalter.com","name":"Ambrose Hansen","blocked":false,"created_at":"2012-09-17T09:42:03Z","access_level":40},{"id":7,"email":"maida@walshtorp.name","name":"Alana Hahn","blocked":false,"created_at":"2012-09-17T09:42:03Z","access_level":20}]
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/unprotect_branch.json b/lib/gitlab-cli/spec/fixtures/unprotect_branch.json
new file mode 100644
index 000000000..854c8274a
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/unprotect_branch.json
@@ -0,0 +1 @@
+{"name":"api","commit":{"id":"f7dd067490fe57505f7226c3b54d3127d2f7fd46","parents":[{"id":"949b1df930bedace1dbd755aaa4a82e8c451a616"}],"tree":"f8c4b21c036339f92fcc5482aa28a41250553b27","message":"API: expose issues project id","author":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"committer":{"name":"Nihad Abbasov","email":"narkoz.2008@gmail.com"},"authored_date":"2012-07-25T04:22:21-07:00","committed_date":"2012-07-25T04:22:21-07:00"},"protected":false}
diff --git a/lib/gitlab-cli/spec/fixtures/update_merge_request.json b/lib/gitlab-cli/spec/fixtures/update_merge_request.json
new file mode 100644
index 000000000..735819ff3
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/update_merge_request.json
@@ -0,0 +1 @@
+{"id":1,"target_branch":"master","source_branch":"api","project_id":3,"title":"A different new feature","closed":false,"merged":false,"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-10-19T05:56:05Z"},"assignee":{"id":2,"email":"jack@example.com","name":"Jack Smith","blocked":false,"created_at":"2012-10-19T05:56:14Z"}}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/user.json b/lib/gitlab-cli/spec/fixtures/user.json
new file mode 100644
index 000000000..4e0daca50
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/user.json
@@ -0,0 +1 @@
+{"id":1,"email":"john@example.com","name":"John Smith","bio":null,"skype":"","linkedin":"","twitter":"john","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:41:56Z"}
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/fixtures/users.json b/lib/gitlab-cli/spec/fixtures/users.json
new file mode 100644
index 000000000..14c6388bf
--- /dev/null
+++ b/lib/gitlab-cli/spec/fixtures/users.json
@@ -0,0 +1 @@
+[{"id":1,"email":"john@example.com","name":"John Smith","bio":null,"skype":"","linkedin":"","twitter":"john","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:41:56Z"},{"id":2,"email":"jack@example.com","name":"Jack Smith","bio":null,"skype":"","linkedin":"","twitter":"","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:42:03Z"},{"id":3,"email":"wilma@mayerblanda.ca","name":"Beatrice Jewess","bio":null,"skype":"","linkedin":"","twitter":"","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:42:03Z"},{"id":4,"email":"nicole@mertz.com","name":"Felipe Davis","bio":null,"skype":"","linkedin":"","twitter":"","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:42:03Z"},{"id":5,"email":"aliza_stark@schmeler.info","name":"Michale Von","bio":null,"skype":"","linkedin":"","twitter":"","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:42:03Z"},{"id":6,"email":"faye.watsica@rohanwalter.com","name":"Ambrose Hansen","bio":null,"skype":"","linkedin":"","twitter":"","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:42:03Z"},{"id":7,"email":"maida@walshtorp.name","name":"Alana Hahn","bio":null,"skype":"","linkedin":"","twitter":"","dark_scheme":false,"theme_id":1,"blocked":false,"created_at":"2012-09-17T09:42:03Z"}]
\ No newline at end of file
diff --git a/lib/gitlab-cli/spec/gitlab/cli_spec.rb b/lib/gitlab-cli/spec/gitlab/cli_spec.rb
new file mode 100644
index 000000000..8b002ec64
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/cli_spec.rb
@@ -0,0 +1,80 @@
+require 'spec_helper'
+
+describe Gitlab::CLI do
+ describe ".run" do
+ context "when command is version" do
+ it "should show gem version" do
+ output = capture_output { Gitlab::CLI.run('-v') }
+ expect(output).to eq("Gitlab Ruby Gem #{Gitlab::VERSION}\n")
+ end
+ end
+
+ context "when command is info" do
+ it "should show environment info" do
+ output = capture_output { Gitlab::CLI.run('info') }
+ expect(output).to include("Gitlab endpoint is")
+ expect(output).to include("Gitlab private token is")
+ expect(output).to include("Ruby Version is")
+ expect(output).to include("Gitlab Ruby Gem")
+ end
+ end
+
+ context "when command is help" do
+ it "should show available actions" do
+ output = capture_output { Gitlab::CLI.run('help') }
+ expect(output).to include('Available commands')
+ expect(output).to include('MergeRequests')
+ expect(output).to include('team_members')
+ end
+ end
+
+ context "when command is user" do
+ before do
+ stub_get("/user", "user")
+ @output = capture_output { Gitlab::CLI.run('user') }
+ end
+
+ it "should show executed command" do
+ expect(@output).to include('Gitlab.user')
+ end
+
+ it "should show user data" do
+ expect(@output).to include('name')
+ expect(@output).to include('John Smith')
+ end
+ end
+ end
+
+ describe ".start" do
+ context "when command with excluded fields" do
+ before do
+ stub_get("/user", "user")
+ args = ['user', '--except=id,email,name']
+ @output = capture_output { Gitlab::CLI.start(args) }
+ end
+
+ it "should show user data with excluded fields" do
+ expect(@output).to_not include('John Smith')
+ expect(@output).to include('bio')
+ expect(@output).to include('created_at')
+ end
+ end
+
+ context "when command with required fields" do
+ before do
+ stub_get("/user", "user")
+ args = ['user', '--only=id,email,name']
+ @output = capture_output { Gitlab::CLI.start(args) }
+ end
+
+ it "should show user data with required fields" do
+ expect(@output).to include('id')
+ expect(@output).to include('name')
+ expect(@output).to include('email')
+ expect(@output).to include('John Smith')
+ expect(@output).to_not include('bio')
+ expect(@output).to_not include('created_at')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/gitlab/client/branches_spec.rb b/lib/gitlab-cli/spec/gitlab/client/branches_spec.rb
new file mode 100644
index 000000000..80c18ccb5
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/client/branches_spec.rb
@@ -0,0 +1,103 @@
+require 'spec_helper'
+
+describe Gitlab::Client do
+ it { should respond_to :repo_branches }
+ it { should respond_to :repo_branch }
+ it { should respond_to :repo_protect_branch }
+ it { should respond_to :repo_unprotect_branch }
+
+ describe ".branches" do
+ before do
+ stub_get("/projects/3/repository/branches", "branches")
+ @branches = Gitlab.branches(3)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/repository/branches")).to have_been_made
+ end
+
+ it "should return an array of repository branches" do
+ expect(@branches).to be_an Array
+ expect(@branches.first.name).to eq("api")
+ end
+ end
+
+ describe ".branch" do
+ before do
+ stub_get("/projects/3/repository/branches/api", "branch")
+ @branch = Gitlab.branch(3, "api")
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/repository/branches/api")).to have_been_made
+ end
+
+ it "should return information about a repository branch" do
+ expect(@branch.name).to eq("api")
+ end
+ end
+
+ describe ".protect_branch" do
+ before do
+ stub_put("/projects/3/repository/branches/api/protect", "protect_branch")
+ @branch = Gitlab.protect_branch(3, "api")
+ end
+
+ it "should get the correct resource" do
+ expect(a_put("/projects/3/repository/branches/api/protect")).to have_been_made
+ end
+
+ it "should return information about a protected repository branch" do
+ expect(@branch.name).to eq("api")
+ expect(@branch.protected).to eq(true)
+ end
+ end
+
+ describe ".unprotect_branch" do
+ before do
+ stub_put("/projects/3/repository/branches/api/unprotect", "unprotect_branch")
+ @branch = Gitlab.unprotect_branch(3, "api")
+ end
+
+ it "should get the correct resource" do
+ expect(a_put("/projects/3/repository/branches/api/unprotect")).to have_been_made
+ end
+
+ it "should return information about an unprotected repository branch" do
+ expect(@branch.name).to eq("api")
+ expect(@branch.protected).to eq(false)
+ end
+ end
+
+ describe ".create_branch" do
+ context "with branch name" do
+ before do
+ stub_post("/projects/3/repository/branches", "create_branch")
+ @branch = Gitlab.create_branch(3, "api","master")
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/projects/3/repository/branches")).to have_been_made
+ end
+
+ it "should return information about a new repository branch" do
+ expect(@branch.name).to eq("api")
+ end
+ end
+ context "with commit hash" do
+ before do
+ stub_post("/projects/3/repository/branches", "create_branch")
+ @branch = Gitlab.create_branch(3, "api","949b1df930bedace1dbd755aaa4a82e8c451a616")
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/projects/3/repository/branches")).to have_been_made
+ end
+
+ it "should return information about a new repository branch" do
+ expect(@branch.name).to eq("api")
+ end
+ end
+ end
+
+end
diff --git a/lib/gitlab-cli/spec/gitlab/client/groups_spec.rb b/lib/gitlab-cli/spec/gitlab/client/groups_spec.rb
new file mode 100644
index 000000000..ad17aedaa
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/client/groups_spec.rb
@@ -0,0 +1,111 @@
+require 'spec_helper'
+
+describe Gitlab::Client do
+ describe ".groups" do
+ before do
+ stub_get("/groups", "groups")
+ stub_get("/groups/3", "group")
+ @group = Gitlab.group(3)
+ @groups = Gitlab.groups
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/groups")).to have_been_made
+ expect(a_get("/groups/3")).to have_been_made
+ end
+
+ it "should return an array of Groups" do
+ expect(@groups).to be_an Array
+ expect(@groups.first.path).to eq("threegroup")
+ end
+ end
+
+ describe ".create_group" do
+ before do
+ stub_post("/groups", "group_create")
+ @group = Gitlab.create_group('GitLab-Group', 'gitlab-path')
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/groups").
+ with(:body => {:path => 'gitlab-path', :name => 'GitLab-Group'})).to have_been_made
+ end
+
+ it "should return information about a created group" do
+ expect(@group.name).to eq("Gitlab-Group")
+ expect(@group.path).to eq("gitlab-group")
+ end
+ end
+
+ describe ".transfer_project_to_group" do
+ before do
+ stub_post("/projects", "project")
+ @project = Gitlab.create_project('Gitlab')
+ stub_post("/groups", "group_create")
+ @group = Gitlab.create_group('GitLab-Group', 'gitlab-path')
+
+ stub_post("/groups/#{@group.id}/projects/#{@project.id}", "group_create")
+ @group_transfer = Gitlab.transfer_project_to_group(@group.id,@project.id)
+ end
+
+ it "should post to the correct resource" do
+ expect(a_post("/groups/#{@group.id}/projects/#{@project.id}").with(:body => {:id => @group.id.to_s, :project_id => @project.id.to_s})).to have_been_made
+ end
+
+ it "should return information about the group" do
+ expect(@group_transfer.name).to eq(@group.name)
+ expect(@group_transfer.path).to eq(@group.path)
+ expect(@group_transfer.id).to eq(@group.id)
+ end
+ end
+
+ describe ".group_members" do
+ before do
+ stub_get("/groups/3/members", "group_members")
+ @members = Gitlab.group_members(3)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/groups/3/members")).to have_been_made
+ end
+
+ it "should return information about a group members" do
+ expect(@members).to be_an Array
+ expect(@members.size).to eq(2)
+ expect(@members[1].name).to eq("John Smith")
+ end
+ end
+
+ describe ".add_group_member" do
+ before do
+ stub_post("/groups/3/members", "group_member")
+ @member = Gitlab.add_group_member(3, 1, 40)
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/groups/3/members").
+ with(:body => {:user_id => '1', :access_level => '40'})).to have_been_made
+ end
+
+ it "should return information about an added member" do
+ expect(@member.name).to eq("John Smith")
+ end
+ end
+
+ describe ".remove_group_member" do
+ before do
+ stub_delete("/groups/3/members/1", "group_member_delete")
+ @group = Gitlab.remove_group_member(3, 1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_delete("/groups/3/members/1")).to have_been_made
+ end
+
+ it "should return information about the group the member was removed from" do
+ expect(@group.group_id).to eq(3)
+ end
+ end
+
+
+end
diff --git a/lib/gitlab-cli/spec/gitlab/client/issues_spec.rb b/lib/gitlab-cli/spec/gitlab/client/issues_spec.rb
new file mode 100644
index 000000000..e5a506b94
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/client/issues_spec.rb
@@ -0,0 +1,122 @@
+require 'spec_helper'
+
+describe Gitlab::Client do
+ describe ".issues" do
+ context "with project ID passed" do
+ before do
+ stub_get("/projects/3/issues", "project_issues")
+ @issues = Gitlab.issues(3)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/issues")).to have_been_made
+ end
+
+ it "should return an array of project's issues" do
+ expect(@issues).to be_an Array
+ expect(@issues.first.project_id).to eq(3)
+ end
+ end
+
+ context "without project ID passed" do
+ before do
+ stub_get("/issues", "issues")
+ @issues = Gitlab.issues
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/issues")).to have_been_made
+ end
+
+ it "should return an array of user's issues" do
+ expect(@issues).to be_an Array
+ expect(@issues.first.closed).to be_falsey
+ expect(@issues.first.author.name).to eq("John Smith")
+ end
+ end
+ end
+
+ describe ".issue" do
+ before do
+ stub_get("/projects/3/issues/33", "issue")
+ @issue = Gitlab.issue(3, 33)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/issues/33")).to have_been_made
+ end
+
+ it "should return information about an issue" do
+ expect(@issue.project_id).to eq(3)
+ expect(@issue.assignee.name).to eq("Jack Smith")
+ end
+ end
+
+ describe ".create_issue" do
+ before do
+ stub_post("/projects/3/issues", "issue")
+ @issue = Gitlab.create_issue(3, 'title')
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/projects/3/issues").
+ with(:body => {:title => 'title'})).to have_been_made
+ end
+
+ it "should return information about a created issue" do
+ expect(@issue.project_id).to eq(3)
+ expect(@issue.assignee.name).to eq("Jack Smith")
+ end
+ end
+
+ describe ".edit_issue" do
+ before do
+ stub_put("/projects/3/issues/33", "issue")
+ @issue = Gitlab.edit_issue(3, 33, :title => 'title')
+ end
+
+ it "should get the correct resource" do
+ expect(a_put("/projects/3/issues/33").
+ with(:body => {:title => 'title'})).to have_been_made
+ end
+
+ it "should return information about an edited issue" do
+ expect(@issue.project_id).to eq(3)
+ expect(@issue.assignee.name).to eq("Jack Smith")
+ end
+ end
+
+ describe ".close_issue" do
+ before do
+ stub_put("/projects/3/issues/33", "issue")
+ @issue = Gitlab.close_issue(3, 33)
+ end
+
+ it "should get the correct resource" do
+ expect(a_put("/projects/3/issues/33").
+ with(:body => {:state_event => 'close'})).to have_been_made
+ end
+
+ it "should return information about an closed issue" do
+ expect(@issue.project_id).to eq(3)
+ expect(@issue.assignee.name).to eq("Jack Smith")
+ end
+ end
+
+ describe ".reopen_issue" do
+ before do
+ stub_put("/projects/3/issues/33", "issue")
+ @issue = Gitlab.reopen_issue(3, 33)
+ end
+
+ it "should get the correct resource" do
+ expect(a_put("/projects/3/issues/33").
+ with(:body => {:state_event => 'reopen'})).to have_been_made
+ end
+
+ it "should return information about an reopened issue" do
+ expect(@issue.project_id).to eq(3)
+ expect(@issue.assignee.name).to eq("Jack Smith")
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/gitlab/client/merge_requests_spec.rb b/lib/gitlab-cli/spec/gitlab/client/merge_requests_spec.rb
new file mode 100644
index 000000000..a336da9f5
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/client/merge_requests_spec.rb
@@ -0,0 +1,124 @@
+require 'spec_helper'
+
+describe Gitlab::Client do
+ describe ".merge_requests" do
+ before do
+ stub_get("/projects/3/merge_requests", "merge_requests")
+ @merge_requests = Gitlab.merge_requests(3)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/merge_requests")).to have_been_made
+ end
+
+ it "should return an array of project's merge requests" do
+ expect(@merge_requests).to be_an Array
+ expect(@merge_requests.first.project_id).to eq(3)
+ end
+ end
+
+ describe ".merge_request" do
+ before do
+ stub_get("/projects/3/merge_request/1", "merge_request")
+ @merge_request = Gitlab.merge_request(3, 1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/merge_request/1")).to have_been_made
+ end
+
+ it "should return information about a merge request" do
+ expect(@merge_request.project_id).to eq(3)
+ expect(@merge_request.assignee.name).to eq("Jack Smith")
+ end
+ end
+
+ describe ".create_merge_request" do
+ before do
+ stub_post("/projects/3/merge_requests", "create_merge_request")
+ end
+
+ it "should fail if it doesn't have a source_branch" do
+ expect {
+ Gitlab.create_merge_request(3, 'New merge request', :target_branch => 'master')
+ }.to raise_error Gitlab::Error::MissingAttributes
+ end
+
+ it "should fail if it doesn't have a target_branch" do
+ expect {
+ Gitlab.create_merge_request(3, 'New merge request', :source_branch => 'dev')
+ }.to raise_error Gitlab::Error::MissingAttributes
+ end
+
+ it "should return information about a merge request" do
+ @merge_request = Gitlab.create_merge_request(3, 'New feature',
+ :source_branch => 'api',
+ :target_branch => 'master'
+ )
+ expect(@merge_request.project_id).to eq(3)
+ expect(@merge_request.assignee.name).to eq("Jack Smith")
+ expect(@merge_request.title).to eq('New feature')
+ end
+ end
+
+ describe ".update_merge_request" do
+ before do
+ stub_put("/projects/3/merge_request/2", "update_merge_request")
+ @merge_request = Gitlab.update_merge_request(3, 2,
+ :assignee_id => '1',
+ :target_branch => 'master',
+ :title => 'A different new feature'
+ )
+ end
+
+ it "should return information about a merge request" do
+ expect(@merge_request.project_id).to eq(3)
+ expect(@merge_request.assignee.name).to eq("Jack Smith")
+ expect(@merge_request.title).to eq('A different new feature')
+ end
+ end
+
+ describe ".merge_request_comments" do
+ before do
+ stub_get("/projects/3/merge_request/2/comments", "merge_request_comments")
+ @merge_request = Gitlab.merge_request_comments(3, 2)
+ end
+
+ it "should return merge request's comments" do
+ expect(@merge_request).to be_an Array
+ expect(@merge_request.length).to eq(2)
+ expect(@merge_request[0].note).to eq("this is the 1st comment on the 2merge merge request")
+ expect(@merge_request[0].author.id).to eq(11)
+ expect(@merge_request[1].note).to eq("another discussion point on the 2merge request")
+ expect(@merge_request[1].author.id).to eq(12)
+ end
+ end
+
+ describe ".merge_request_comments" do
+ before do
+ stub_get("/projects/3/merge_request/2/comments", "merge_request_comments")
+ @merge_request = Gitlab.merge_request_comments(3, 2)
+ end
+
+ it "should return merge request's comments" do
+ expect(@merge_request).to be_an Array
+ expect(@merge_request.length).to eq(2)
+ expect(@merge_request[0].note).to eq("this is the 1st comment on the 2merge merge request")
+ expect(@merge_request[0].author.id).to eq(11)
+ expect(@merge_request[1].note).to eq("another discussion point on the 2merge request")
+ expect(@merge_request[1].author.id).to eq(12)
+ end
+ end
+
+ describe ".create_merge_request_comment" do
+ before do
+ stub_post("/projects/3/merge_request/2/comments", "comment_merge_request")
+ end
+
+ it "should return information about a merge request" do
+ @merge_request = Gitlab.create_merge_request_comment(3, 2, 'Cool Merge Request!')
+ expect(@merge_request.note).to eq('Cool Merge Request!')
+ @merge_request.author.id == 1
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/gitlab/client/milestones_spec.rb b/lib/gitlab-cli/spec/gitlab/client/milestones_spec.rb
new file mode 100644
index 000000000..aa1e66b14
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/client/milestones_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Gitlab::Client do
+ describe ".milestones" do
+ before do
+ stub_get("/projects/3/milestones", "milestones")
+ @milestones = Gitlab.milestones(3)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/milestones")).to have_been_made
+ end
+
+ it "should return an array of project's milestones" do
+ expect(@milestones).to be_an Array
+ expect(@milestones.first.project_id).to eq(3)
+ end
+ end
+
+ describe ".milestone" do
+ before do
+ stub_get("/projects/3/milestones/1", "milestone")
+ @milestone = Gitlab.milestone(3, 1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/milestones/1")).to have_been_made
+ end
+
+ it "should return information about a milestone" do
+ expect(@milestone.project_id).to eq(3)
+ end
+ end
+
+ describe ".create_milestone" do
+ before do
+ stub_post("/projects/3/milestones", "milestone")
+ @milestone = Gitlab.create_milestone(3, 'title')
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/projects/3/milestones").
+ with(:body => {:title => 'title'})).to have_been_made
+ end
+
+ it "should return information about a created milestone" do
+ expect(@milestone.project_id).to eq(3)
+ end
+ end
+
+ describe ".edit_milestone" do
+ before do
+ stub_put("/projects/3/milestones/33", "milestone")
+ @milestone = Gitlab.edit_milestone(3, 33, :title => 'title')
+ end
+
+ it "should get the correct resource" do
+ expect(a_put("/projects/3/milestones/33").
+ with(:body => {:title => 'title'})).to have_been_made
+ end
+
+ it "should return information about an edited milestone" do
+ expect(@milestone.project_id).to eq(3)
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/gitlab/client/notes_spec.rb b/lib/gitlab-cli/spec/gitlab/client/notes_spec.rb
new file mode 100644
index 000000000..d80cad476
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/client/notes_spec.rb
@@ -0,0 +1,156 @@
+require 'spec_helper'
+
+describe Gitlab::Client do
+ describe "notes" do
+ context "when wall notes" do
+ before do
+ stub_get("/projects/3/notes", "notes")
+ @notes = Gitlab.notes(3)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/notes")).to have_been_made
+ end
+
+ it "should return an array of notes" do
+ expect(@notes).to be_an Array
+ expect(@notes.first.author.name).to eq("John Smith")
+ end
+ end
+
+ context "when issue notes" do
+ before do
+ stub_get("/projects/3/issues/7/notes", "notes")
+ @notes = Gitlab.issue_notes(3, 7)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/issues/7/notes")).to have_been_made
+ end
+
+ it "should return an array of notes" do
+ expect(@notes).to be_an Array
+ expect(@notes.first.author.name).to eq("John Smith")
+ end
+ end
+
+ context "when snippet notes" do
+ before do
+ stub_get("/projects/3/snippets/7/notes", "notes")
+ @notes = Gitlab.snippet_notes(3, 7)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/snippets/7/notes")).to have_been_made
+ end
+
+ it "should return an array of notes" do
+ expect(@notes).to be_an Array
+ expect(@notes.first.author.name).to eq("John Smith")
+ end
+ end
+ end
+
+ describe "note" do
+ context "when wall note" do
+ before do
+ stub_get("/projects/3/notes/1201", "note")
+ @note = Gitlab.note(3, 1201)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/notes/1201")).to have_been_made
+ end
+
+ it "should return information about a note" do
+ expect(@note.body).to eq("The solution is rather tricky")
+ expect(@note.author.name).to eq("John Smith")
+ end
+ end
+
+ context "when issue note" do
+ before do
+ stub_get("/projects/3/issues/7/notes/1201", "note")
+ @note = Gitlab.issue_note(3, 7, 1201)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/issues/7/notes/1201")).to have_been_made
+ end
+
+ it "should return information about a note" do
+ expect(@note.body).to eq("The solution is rather tricky")
+ expect(@note.author.name).to eq("John Smith")
+ end
+ end
+
+ context "when snippet note" do
+ before do
+ stub_get("/projects/3/snippets/7/notes/1201", "note")
+ @note = Gitlab.snippet_note(3, 7, 1201)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/snippets/7/notes/1201")).to have_been_made
+ end
+
+ it "should return information about a note" do
+ expect(@note.body).to eq("The solution is rather tricky")
+ expect(@note.author.name).to eq("John Smith")
+ end
+ end
+ end
+
+ describe "create note" do
+ context "when wall note" do
+ before do
+ stub_post("/projects/3/notes", "note")
+ @note = Gitlab.create_note(3, "The solution is rather tricky")
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/projects/3/notes").
+ with(:body => {:body => 'The solution is rather tricky'})).to have_been_made
+ end
+
+ it "should return information about a created note" do
+ expect(@note.body).to eq("The solution is rather tricky")
+ expect(@note.author.name).to eq("John Smith")
+ end
+ end
+
+ context "when issue note" do
+ before do
+ stub_post("/projects/3/issues/7/notes", "note")
+ @note = Gitlab.create_issue_note(3, 7, "The solution is rather tricky")
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/projects/3/issues/7/notes").
+ with(:body => {:body => 'The solution is rather tricky'})).to have_been_made
+ end
+
+ it "should return information about a created note" do
+ expect(@note.body).to eq("The solution is rather tricky")
+ expect(@note.author.name).to eq("John Smith")
+ end
+ end
+
+ context "when snippet note" do
+ before do
+ stub_post("/projects/3/snippets/7/notes", "note")
+ @note = Gitlab.create_snippet_note(3, 7, "The solution is rather tricky")
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/projects/3/snippets/7/notes").
+ with(:body => {:body => 'The solution is rather tricky'})).to have_been_made
+ end
+
+ it "should return information about a created note" do
+ expect(@note.body).to eq("The solution is rather tricky")
+ expect(@note.author.name).to eq("John Smith")
+ end
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/gitlab/client/projects_spec.rb b/lib/gitlab-cli/spec/gitlab/client/projects_spec.rb
new file mode 100644
index 000000000..5096f8f66
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/client/projects_spec.rb
@@ -0,0 +1,357 @@
+require 'spec_helper'
+
+describe Gitlab::Client do
+ describe ".projects" do
+ before do
+ stub_get("/projects", "projects")
+ @projects = Gitlab.projects
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects")).to have_been_made
+ end
+
+ it "should return an array of projects" do
+ expect(@projects).to be_an Array
+ expect(@projects.first.name).to eq("Brute")
+ expect(@projects.first.owner.name).to eq("John Smith")
+ end
+ end
+
+ describe ".project" do
+ before do
+ stub_get("/projects/3", "project")
+ @project = Gitlab.project(3)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3")).to have_been_made
+ end
+
+ it "should return information about a project" do
+ expect(@project.name).to eq("Gitlab")
+ expect(@project.owner.name).to eq("John Smith")
+ end
+ end
+
+ describe ".project_events" do
+ before do
+ stub_get("/projects/2/events", "project_events")
+ @events = Gitlab.project_events(2)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/2/events")).to have_been_made
+ end
+
+ it "should return an array of events" do
+ expect(@events).to be_an Array
+ expect(@events.size).to eq(2)
+ end
+
+ it "should return the action name of the event" do
+ expect(@events.first.action_name).to eq("opened")
+ end
+ end
+
+ describe ".create_project" do
+ before do
+ stub_post("/projects", "project")
+ @project = Gitlab.create_project('Gitlab')
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/projects")).to have_been_made
+ end
+
+ it "should return information about a created project" do
+ expect(@project.name).to eq("Gitlab")
+ expect(@project.owner.name).to eq("John Smith")
+ end
+ end
+
+ describe ".create_project for user" do
+ before do
+ stub_post("/users", "user")
+ @owner = Gitlab.create_user("john@example.com", "pass", {name: 'John Owner'})
+ stub_post("/projects/user/#{@owner.id}", "project_for_user")
+ @project = Gitlab.create_project('Brute', {:user_id => @owner.id})
+ end
+
+ it "should return information about a created project" do
+ expect(@project.name).to eq("Brute")
+ expect(@project.owner.name).to eq("John Owner")
+ end
+ end
+
+ describe ".delete_project" do
+ before do
+ stub_delete("/projects/Gitlab", "project")
+ @project = Gitlab.delete_project('Gitlab')
+ end
+
+ it "should get the correct resource" do
+ expect(a_delete("/projects/Gitlab")).to have_been_made
+ end
+
+ it "should return information about a deleted project" do
+ expect(@project.name).to eq("Gitlab")
+ expect(@project.owner.name).to eq("John Smith")
+ end
+ end
+
+ describe ".team_members" do
+ before do
+ stub_get("/projects/3/members", "team_members")
+ @team_members = Gitlab.team_members(3)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/members")).to have_been_made
+ end
+
+ it "should return an array of team members" do
+ expect(@team_members).to be_an Array
+ expect(@team_members.first.name).to eq("John Smith")
+ end
+ end
+
+ describe ".team_member" do
+ before do
+ stub_get("/projects/3/members/1", "team_member")
+ @team_member = Gitlab.team_member(3, 1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/members/1")).to have_been_made
+ end
+
+ it "should return information about a team member" do
+ expect(@team_member.name).to eq("John Smith")
+ end
+ end
+
+ describe ".add_team_member" do
+ before do
+ stub_post("/projects/3/members", "team_member")
+ @team_member = Gitlab.add_team_member(3, 1, 40)
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/projects/3/members").
+ with(:body => {:user_id => '1', :access_level => '40'})).to have_been_made
+ end
+
+ it "should return information about an added team member" do
+ expect(@team_member.name).to eq("John Smith")
+ end
+ end
+
+ describe ".edit_team_member" do
+ before do
+ stub_put("/projects/3/members/1", "team_member")
+ @team_member = Gitlab.edit_team_member(3, 1, 40)
+ end
+
+ it "should get the correct resource" do
+ expect(a_put("/projects/3/members/1").
+ with(:body => {:access_level => '40'})).to have_been_made
+ end
+
+ it "should return information about an edited team member" do
+ expect(@team_member.name).to eq("John Smith")
+ end
+ end
+
+ describe ".remove_team_member" do
+ before do
+ stub_delete("/projects/3/members/1", "team_member")
+ @team_member = Gitlab.remove_team_member(3, 1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_delete("/projects/3/members/1")).to have_been_made
+ end
+
+ it "should return information about a removed team member" do
+ expect(@team_member.name).to eq("John Smith")
+ end
+ end
+
+ describe ".project_hooks" do
+ before do
+ stub_get("/projects/1/hooks", "project_hooks")
+ @hooks = Gitlab.project_hooks(1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/1/hooks")).to have_been_made
+ end
+
+ it "should return an array of hooks" do
+ expect(@hooks).to be_an Array
+ expect(@hooks.first.url).to eq("https://api.example.net/v1/webhooks/ci")
+ end
+ end
+
+ describe ".project_hook" do
+ before do
+ stub_get("/projects/1/hooks/1", "project_hook")
+ @hook = Gitlab.project_hook(1, 1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/1/hooks/1")).to have_been_made
+ end
+
+ it "should return information about a hook" do
+ expect(@hook.url).to eq("https://api.example.net/v1/webhooks/ci")
+ end
+ end
+
+ describe ".add_project_hook" do
+ context "without specified events" do
+ before do
+ stub_post("/projects/1/hooks", "project_hook")
+ @hook = Gitlab.add_project_hook(1, "https://api.example.net/v1/webhooks/ci")
+ end
+
+ it "should get the correct resource" do
+ body = {:url => "https://api.example.net/v1/webhooks/ci"}
+ expect(a_post("/projects/1/hooks").with(:body => body)).to have_been_made
+ end
+
+ it "should return information about an added hook" do
+ expect(@hook.url).to eq("https://api.example.net/v1/webhooks/ci")
+ end
+ end
+
+ context "with specified events" do
+ before do
+ stub_post("/projects/1/hooks", "project_hook")
+ @hook = Gitlab.add_project_hook(1, "https://api.example.net/v1/webhooks/ci", push_events: true, merge_requests_events: true)
+ end
+
+ it "should get the correct resource" do
+ body = {:url => "https://api.example.net/v1/webhooks/ci", push_events: "true", merge_requests_events: "true"}
+ expect(a_post("/projects/1/hooks").with(:body => body)).to have_been_made
+ end
+
+ it "should return information about an added hook" do
+ expect(@hook.url).to eq("https://api.example.net/v1/webhooks/ci")
+ end
+ end
+ end
+
+ describe ".edit_project_hook" do
+ before do
+ stub_put("/projects/1/hooks/1", "project_hook")
+ @hook = Gitlab.edit_project_hook(1, 1, "https://api.example.net/v1/webhooks/ci")
+ end
+
+ it "should get the correct resource" do
+ body = {:url => "https://api.example.net/v1/webhooks/ci"}
+ expect(a_put("/projects/1/hooks/1").with(:body => body)).to have_been_made
+ end
+
+ it "should return information about an edited hook" do
+ expect(@hook.url).to eq("https://api.example.net/v1/webhooks/ci")
+ end
+ end
+
+ describe ".delete_project_hook" do
+ before do
+ stub_delete("/projects/1/hooks/1", "project_hook")
+ @hook = Gitlab.delete_project_hook(1, 1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_delete("/projects/1/hooks/1")).to have_been_made
+ end
+
+ it "should return information about a deleted hook" do
+ expect(@hook.url).to eq("https://api.example.net/v1/webhooks/ci")
+ end
+ end
+
+ describe ".make_forked_from" do
+ before do
+ stub_post("/projects/42/fork/24", "project_fork_link")
+ @forked_project_link = Gitlab.make_forked_from(42, 24)
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/projects/42/fork/24")).to have_been_made
+ end
+
+ it "should return information about a forked project" do
+ expect(@forked_project_link.forked_from_project_id).to eq(24)
+ expect(@forked_project_link.forked_to_project_id).to eq(42)
+ end
+ end
+
+ describe ".remove_forked" do
+ before do
+ stub_delete("/projects/42/fork", "project_fork_link")
+ @forked_project_link = Gitlab.remove_forked(42)
+ end
+
+ it "should be sent to correct resource" do
+ expect(a_delete("/projects/42/fork")).to have_been_made
+ end
+
+ it "should return information about an unforked project" do
+ expect(@forked_project_link.forked_to_project_id).to eq(42)
+ end
+ end
+
+ describe ".deploy_keys" do
+ before do
+ stub_get("/projects/42/keys", "project_keys")
+ @deploy_keys = Gitlab.deploy_keys(42)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/42/keys")).to have_been_made
+ end
+
+ it "should return project deploy keys" do
+ expect(@deploy_keys).to be_an Array
+ expect(@deploy_keys.first.id).to eq 2
+ expect(@deploy_keys.first.title).to eq "Key Title"
+ expect(@deploy_keys.first.key).to match(/ssh-rsa/)
+ end
+ end
+
+ describe ".deploy_key" do
+ before do
+ stub_get("/projects/42/keys/2", "project_key")
+ @deploy_key = Gitlab.deploy_key(42, 2)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/42/keys/2")).to have_been_made
+ end
+
+ it "should return project deploy key" do
+ expect(@deploy_key.id).to eq 2
+ expect(@deploy_key.title).to eq "Key Title"
+ expect(@deploy_key.key).to match(/ssh-rsa/)
+ end
+ end
+
+ describe ".delete_deploy_key" do
+ before do
+ stub_delete("/projects/42/keys/2", "project_delete_key")
+ @deploy_key = Gitlab.delete_deploy_key(42, 2)
+ end
+
+ it "should get the correct resource" do
+ expect(a_delete("/projects/42/keys/2")).to have_been_made
+ end
+
+ it "should return information about a deleted key" do
+ expect(@deploy_key.id).to eq(2)
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/gitlab/client/repositories_spec.rb b/lib/gitlab-cli/spec/gitlab/client/repositories_spec.rb
new file mode 100644
index 000000000..f58e315d0
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/client/repositories_spec.rb
@@ -0,0 +1,92 @@
+require 'spec_helper'
+
+describe Gitlab::Client do
+ it { should respond_to :repo_tags }
+ it { should respond_to :repo_create_tag }
+ it { should respond_to :repo_branches }
+ it { should respond_to :repo_branch }
+ it { should respond_to :repo_commits }
+ it { should respond_to :repo_commit }
+ it { should respond_to :repo_commit_diff }
+
+ describe ".tags" do
+ before do
+ stub_get("/projects/3/repository/tags", "project_tags")
+ @tags = Gitlab.tags(3)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/repository/tags")).to have_been_made
+ end
+
+ it "should return an array of repository tags" do
+ expect(@tags).to be_an Array
+ expect(@tags.first.name).to eq("v2.8.2")
+ end
+ end
+
+ describe ".create_tag" do
+ before do
+ stub_post("/projects/3/repository/tags", "tag")
+ @tag = Gitlab.create_tag(3, 'v1.0.0', '2695effb5807a22ff3d138d593fd856244e155e7')
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/projects/3/repository/tags")).to have_been_made
+ end
+
+ it "should return information about a new repository tag" do
+ expect(@tag.name).to eq("v1.0.0")
+ end
+ end
+
+ describe ".commits" do
+ before do
+ stub_get("/projects/3/repository/commits", "project_commits").
+ with(:query => {:ref_name => "api"})
+ @commits = Gitlab.commits(3, :ref_name => "api")
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/repository/commits").
+ with(:query => {:ref_name => "api"})).to have_been_made
+ end
+
+ it "should return an array of repository commits" do
+ expect(@commits).to be_an Array
+ expect(@commits.first.id).to eq("f7dd067490fe57505f7226c3b54d3127d2f7fd46")
+ end
+ end
+
+ describe ".commit" do
+ before do
+ stub_get("/projects/3/repository/commits/6104942438c14ec7bd21c6cd5bd995272b3faff6", "project_commit")
+ @commit = Gitlab.commit(3, '6104942438c14ec7bd21c6cd5bd995272b3faff6')
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/repository/commits/6104942438c14ec7bd21c6cd5bd995272b3faff6"))
+ .to have_been_made
+ end
+
+ it "should return a repository commit" do
+ expect(@commit.id).to eq("6104942438c14ec7bd21c6cd5bd995272b3faff6")
+ end
+ end
+
+ describe ".commit_diff" do
+ before do
+ stub_get("/projects/3/repository/commits/6104942438c14ec7bd21c6cd5bd995272b3faff6/diff", "project_commit_diff")
+ @diff = Gitlab.commit_diff(3, '6104942438c14ec7bd21c6cd5bd995272b3faff6')
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/repository/commits/6104942438c14ec7bd21c6cd5bd995272b3faff6/diff"))
+ .to have_been_made
+ end
+
+ it "should return a diff of a commit" do
+ expect(@diff.new_path).to eq("doc/update/5.4-to-6.0.md")
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/gitlab/client/snippets_spec.rb b/lib/gitlab-cli/spec/gitlab/client/snippets_spec.rb
new file mode 100644
index 000000000..b6ceecc0d
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/client/snippets_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+describe Gitlab::Client do
+ describe ".snippets" do
+ before do
+ stub_get("/projects/3/snippets", "snippets")
+ @snippets = Gitlab.snippets(3)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/snippets")).to have_been_made
+ end
+
+ it "should return an array of project's snippets" do
+ expect(@snippets).to be_an Array
+ expect(@snippets.first.file_name).to eq("mailer_test.rb")
+ end
+ end
+
+ describe ".snippet" do
+ before do
+ stub_get("/projects/3/snippets/1", "snippet")
+ @snippet = Gitlab.snippet(3, 1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/projects/3/snippets/1")).to have_been_made
+ end
+
+ it "should return information about a snippet" do
+ expect(@snippet.file_name).to eq("mailer_test.rb")
+ expect(@snippet.author.name).to eq("John Smith")
+ end
+ end
+
+ describe ".create_snippet" do
+ before do
+ stub_post("/projects/3/snippets", "snippet")
+ @snippet = Gitlab.create_snippet(3, {:title => 'API', :file_name => 'api.rb', :code => 'code'})
+ end
+
+ it "should get the correct resource" do
+ body = {:title => 'API', :file_name => 'api.rb', :code => 'code'}
+ expect(a_post("/projects/3/snippets").with(:body => body)).to have_been_made
+ end
+
+ it "should return information about a new snippet" do
+ expect(@snippet.file_name).to eq("mailer_test.rb")
+ expect(@snippet.author.name).to eq("John Smith")
+ end
+ end
+
+ describe ".edit_snippet" do
+ before do
+ stub_put("/projects/3/snippets/1", "snippet")
+ @snippet = Gitlab.edit_snippet(3, 1, :file_name => 'mailer_test.rb')
+ end
+
+ it "should get the correct resource" do
+ expect(a_put("/projects/3/snippets/1").
+ with(:body => {:file_name => 'mailer_test.rb'})).to have_been_made
+ end
+
+ it "should return information about an edited snippet" do
+ expect(@snippet.file_name).to eq("mailer_test.rb")
+ expect(@snippet.author.name).to eq("John Smith")
+ end
+ end
+
+ describe ".delete_snippet" do
+ before do
+ stub_delete("/projects/3/snippets/1", "snippet")
+ @snippet = Gitlab.delete_snippet(3, 1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_delete("/projects/3/snippets/1")).to have_been_made
+ end
+
+ it "should return information about a deleted snippet" do
+ expect(@snippet.file_name).to eq("mailer_test.rb")
+ expect(@snippet.author.name).to eq("John Smith")
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/gitlab/client/system_hooks_spec.rb b/lib/gitlab-cli/spec/gitlab/client/system_hooks_spec.rb
new file mode 100644
index 000000000..7410c3655
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/client/system_hooks_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe Gitlab::Client do
+ it { should respond_to :system_hooks }
+ it { should respond_to :add_system_hook }
+ it { should respond_to :system_hook }
+ it { should respond_to :delete_system_hook }
+
+ describe ".hooks" do
+ before do
+ stub_get("/hooks", "system_hooks")
+ @hooks = Gitlab.hooks
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/hooks")).to have_been_made
+ end
+
+ it "should return an array of system hooks" do
+ expect(@hooks).to be_an Array
+ expect(@hooks.first.url).to eq("http://example.com/hook")
+ end
+ end
+
+ describe ".add_hook" do
+ before do
+ stub_post("/hooks", "system_hook")
+ @hook = Gitlab.add_hook("http://example.com/hook")
+ end
+
+ it "should get the correct resource" do
+ expect(a_post("/hooks")).to have_been_made
+ end
+
+ it "should return information about a added system hook" do
+ expect(@hook.url).to eq("http://example.com/hook")
+ end
+ end
+
+ describe ".hook" do
+ before do
+ stub_get("/hooks/3", "system_hook_test")
+ @hook = Gitlab.hook(3)
+ end
+ it "should get the correct resource" do
+ expect(a_get("/hooks/3")).to have_been_made
+ end
+
+ it "should return information about a added system hook" do
+ expect(@hook.event_name).to eq("project_create")
+ expect(@hook.project_id).to eq(1)
+ end
+ end
+
+ describe ".delete_hook" do
+ before do
+ stub_delete("/hooks/3", "system_hook")
+ @hook = Gitlab.delete_hook(3)
+ end
+
+ it "should get the correct resource" do
+ expect(a_delete("/hooks/3")).to have_been_made
+ end
+
+ it "should return information about a deleted system hook" do
+ expect(@hook.url).to eq("http://example.com/hook")
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/gitlab/client/users_spec.rb b/lib/gitlab-cli/spec/gitlab/client/users_spec.rb
new file mode 100644
index 000000000..ead205b47
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/client/users_spec.rb
@@ -0,0 +1,192 @@
+require 'spec_helper'
+
+describe Gitlab::Client do
+ describe ".users" do
+ before do
+ stub_get("/users", "users")
+ @users = Gitlab.users
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/users")).to have_been_made
+ end
+
+ it "should return an array of users" do
+ expect(@users).to be_an Array
+ expect(@users.first.email).to eq("john@example.com")
+ end
+ end
+
+ describe ".user" do
+ context "with user ID passed" do
+ before do
+ stub_get("/users/1", "user")
+ @user = Gitlab.user(1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/users/1")).to have_been_made
+ end
+
+ it "should return information about a user" do
+ expect(@user.email).to eq("john@example.com")
+ end
+ end
+
+ context "without user ID passed" do
+ before do
+ stub_get("/user", "user")
+ @user = Gitlab.user
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/user")).to have_been_made
+ end
+
+ it "should return information about an authorized user" do
+ expect(@user.email).to eq("john@example.com")
+ end
+ end
+ end
+
+ describe ".create_user" do
+ context "when successful request" do
+ before do
+ stub_post("/users", "user")
+ @user = Gitlab.create_user("email", "pass")
+ end
+
+ it "should get the correct resource" do
+ body = {:email => "email", :password => "pass", :name => "email"}
+ expect(a_post("/users").with(:body => body)).to have_been_made
+ end
+
+ it "should return information about a created user" do
+ expect(@user.email).to eq("john@example.com")
+ end
+ end
+
+ context "when bad request" do
+ it "should throw an exception" do
+ stub_post("/users", "error_already_exists", 409)
+ expect {
+ Gitlab.create_user("email", "pass")
+ }.to raise_error(Gitlab::Error::Conflict, "Server responded with code 409, message: 409 Already exists. Request URI: #{Gitlab.endpoint}/users")
+ end
+ end
+ end
+
+ describe ".edit_user" do
+ before do
+ @options = { :name => "Roberto" }
+ stub_put("/users/1", "user").with(:body => @options)
+ @user = Gitlab.edit_user(1, @options)
+ end
+
+ it "should get the correct resource" do
+ expect(a_put("/users/1").with(:body => @options)).to have_been_made
+ end
+ end
+
+ describe ".session" do
+ after do
+ Gitlab.endpoint = 'https://api.example.com'
+ Gitlab.private_token = 'secret'
+ end
+
+ before do
+ stub_request(:post, "#{Gitlab.endpoint}/session").
+ to_return(:body => load_fixture('session'), :status => 200)
+ @session = Gitlab.session("email", "pass")
+ end
+
+ context "when endpoint is not set" do
+ it "should raise Error::MissingCredentials" do
+ Gitlab.endpoint = nil
+ expect {
+ Gitlab.session("email", "pass")
+ }.to raise_error(Gitlab::Error::MissingCredentials, 'Please set an endpoint to API')
+ end
+ end
+
+ context "when private_token is not set" do
+ it "should not raise Error::MissingCredentials" do
+ Gitlab.private_token = nil
+ expect { Gitlab.session("email", "pass") }.to_not raise_error
+ end
+ end
+
+ context "when endpoint is set" do
+ it "should get the correct resource" do
+ expect(a_request(:post, "#{Gitlab.endpoint}/session")).to have_been_made
+ end
+
+ it "should return information about a created session" do
+ expect(@session.email).to eq("john@example.com")
+ expect(@session.private_token).to eq("qEsq1pt6HJPaNciie3MG")
+ end
+ end
+ end
+
+ describe ".ssh_keys" do
+ before do
+ stub_get("/user/keys", "keys")
+ @keys = Gitlab.ssh_keys
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/user/keys")).to have_been_made
+ end
+
+ it "should return an array of SSH keys" do
+ expect(@keys).to be_an Array
+ expect(@keys.first.title).to eq("narkoz@helium")
+ end
+ end
+
+ describe ".ssh_key" do
+ before do
+ stub_get("/user/keys/1", "key")
+ @key = Gitlab.ssh_key(1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_get("/user/keys/1")).to have_been_made
+ end
+
+ it "should return information about an SSH key" do
+ expect(@key.title).to eq("narkoz@helium")
+ end
+ end
+
+ describe ".create_ssh_key" do
+ before do
+ stub_post("/user/keys", "key")
+ @key = Gitlab.create_ssh_key("title", "body")
+ end
+
+ it "should get the correct resource" do
+ body = {:title => "title", :key => "body"}
+ expect(a_post("/user/keys").with(:body => body)).to have_been_made
+ end
+
+ it "should return information about a created SSH key" do
+ expect(@key.title).to eq("narkoz@helium")
+ end
+ end
+
+ describe ".delete_ssh_key" do
+ before do
+ stub_delete("/user/keys/1", "key")
+ @key = Gitlab.delete_ssh_key(1)
+ end
+
+ it "should get the correct resource" do
+ expect(a_delete("/user/keys/1")).to have_been_made
+ end
+
+ it "should return information about a deleted SSH key" do
+ expect(@key.title).to eq("narkoz@helium")
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/gitlab/objectified_hash_spec.rb b/lib/gitlab-cli/spec/gitlab/objectified_hash_spec.rb
new file mode 100644
index 000000000..db45711df
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/objectified_hash_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Gitlab::ObjectifiedHash do
+ before do
+ @hash = {a: 1, b: 2}
+ @oh = Gitlab::ObjectifiedHash.new @hash
+ end
+
+ it "should objectify hash" do
+ expect(@oh.a).to eq(@hash[:a])
+ expect(@oh.b).to eq(@hash[:b])
+ end
+
+ describe "#to_hash" do
+ it "should return an original hash" do
+ expect(@oh.to_hash).to eq(@hash)
+ end
+
+ it "should have an alias #to_h" do
+ expect(@oh.respond_to?(:to_h)).to be_truthy
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/gitlab/request_spec.rb b/lib/gitlab-cli/spec/gitlab/request_spec.rb
new file mode 100644
index 000000000..869c2e964
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab/request_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe Gitlab::Request do
+ it { should respond_to :get }
+ it { should respond_to :post }
+ it { should respond_to :put }
+ it { should respond_to :delete }
+
+ describe ".default_options" do
+ it "should have default values" do
+ default_options = Gitlab::Request.default_options
+ expect(default_options).to be_a Hash
+ expect(default_options[:parser]).to be_a Proc
+ expect(default_options[:format]).to eq(:json)
+ expect(default_options[:headers]).to eq({'Accept' => 'application/json'})
+ expect(default_options[:default_params]).to be_nil
+ end
+ end
+
+ describe ".parse" do
+ it "should return ObjectifiedHash" do
+ body = JSON.unparse({a: 1, b: 2})
+ expect(Gitlab::Request.parse(body)).to be_an Gitlab::ObjectifiedHash
+ end
+ end
+
+ describe "#set_request_defaults" do
+ context "when endpoint is not set" do
+ it "should raise Error::MissingCredentials" do
+ expect {
+ Gitlab::Request.new.set_request_defaults(nil, 1234000)
+ }.to raise_error(Gitlab::Error::MissingCredentials, 'Please set an endpoint to API')
+ end
+ end
+
+ context "when endpoint is set" do
+ it "should set base_uri" do
+ Gitlab::Request.new.set_request_defaults('http://rabbit-hole.example.org', 1234000)
+ expect(Gitlab::Request.base_uri).to eq("http://rabbit-hole.example.org")
+ end
+
+ it "should set default_params" do
+ Gitlab::Request.new.set_request_defaults('http://rabbit-hole.example.org', 1234000, 'sudoer')
+ expect(Gitlab::Request.default_params).to eq({:sudo => 'sudoer'})
+ end
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/gitlab_spec.rb b/lib/gitlab-cli/spec/gitlab_spec.rb
new file mode 100644
index 000000000..f037e70aa
--- /dev/null
+++ b/lib/gitlab-cli/spec/gitlab_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Gitlab do
+ after { Gitlab.reset }
+
+ describe ".client" do
+ it "should be a Gitlab::Client" do
+ expect(Gitlab.client).to be_a Gitlab::Client
+ end
+ end
+
+ describe ".actions" do
+ it "should return an array of client methods" do
+ actions = Gitlab.actions
+ expect(actions).to be_an Array
+ expect(actions.first).to be_a Symbol
+ expect(actions.sort.first).to match(/add_/)
+ end
+ end
+
+ describe ".endpoint=" do
+ it "should set endpoint" do
+ Gitlab.endpoint = 'https://api.example.com'
+ expect(Gitlab.endpoint).to eq('https://api.example.com')
+ end
+ end
+
+ describe ".private_token=" do
+ it "should set private_token" do
+ Gitlab.private_token = 'secret'
+ expect(Gitlab.private_token).to eq('secret')
+ end
+ end
+
+ describe ".sudo=" do
+ it "should set sudo" do
+ Gitlab.sudo = 'user'
+ expect(Gitlab.sudo).to eq('user')
+ end
+ end
+
+ describe ".user_agent" do
+ it "should return default user_agent" do
+ expect(Gitlab.user_agent).to eq(Gitlab::Configuration::DEFAULT_USER_AGENT)
+ end
+ end
+
+ describe ".user_agent=" do
+ it "should set user_agent" do
+ Gitlab.user_agent = 'Custom User Agent'
+ expect(Gitlab.user_agent).to eq('Custom User Agent')
+ end
+ end
+
+ describe ".configure" do
+ Gitlab::Configuration::VALID_OPTIONS_KEYS.each do |key|
+ it "should set #{key}" do
+ Gitlab.configure do |config|
+ config.send("#{key}=", key)
+ expect(Gitlab.send(key)).to eq(key)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab-cli/spec/spec_helper.rb b/lib/gitlab-cli/spec/spec_helper.rb
new file mode 100644
index 000000000..28df28be2
--- /dev/null
+++ b/lib/gitlab-cli/spec/spec_helper.rb
@@ -0,0 +1,74 @@
+require 'rspec'
+require 'webmock/rspec'
+
+require File.expand_path('../../lib/gitlab', __FILE__)
+require File.expand_path('../../lib/gitlab/cli', __FILE__)
+
+def capture_output
+ out = StringIO.new
+ $stdout = out
+ $stderr = out
+ yield
+ $stdout = STDOUT
+ $stderr = STDERR
+ out.string
+end
+
+def load_fixture(name)
+ File.new(File.dirname(__FILE__) + "/fixtures/#{name}.json")
+end
+
+RSpec.configure do |config|
+ config.before(:all) do
+ Gitlab.endpoint = 'https://api.example.com'
+ Gitlab.private_token = 'secret'
+ end
+end
+
+# GET
+def stub_get(path, fixture)
+ stub_request(:get, "#{Gitlab.endpoint}#{path}").
+ with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token}).
+ to_return(:body => load_fixture(fixture))
+end
+
+def a_get(path)
+ a_request(:get, "#{Gitlab.endpoint}#{path}").
+ with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token})
+end
+
+# POST
+def stub_post(path, fixture, status_code=200)
+ stub_request(:post, "#{Gitlab.endpoint}#{path}").
+ with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token}).
+ to_return(:body => load_fixture(fixture), :status => status_code)
+end
+
+def a_post(path)
+ a_request(:post, "#{Gitlab.endpoint}#{path}").
+ with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token})
+end
+
+# PUT
+def stub_put(path, fixture)
+ stub_request(:put, "#{Gitlab.endpoint}#{path}").
+ with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token}).
+ to_return(:body => load_fixture(fixture))
+end
+
+def a_put(path)
+ a_request(:put, "#{Gitlab.endpoint}#{path}").
+ with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token})
+end
+
+# DELETE
+def stub_delete(path, fixture)
+ stub_request(:delete, "#{Gitlab.endpoint}#{path}").
+ with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token}).
+ to_return(:body => load_fixture(fixture))
+end
+
+def a_delete(path)
+ a_request(:delete, "#{Gitlab.endpoint}#{path}").
+ with(:headers => {'PRIVATE-TOKEN' => Gitlab.private_token})
+end
diff --git a/lib/redmine/scm/adapters/gitlab_adapter.rb b/lib/redmine/scm/adapters/gitlab_adapter.rb
new file mode 100644
index 000000000..32429fe35
--- /dev/null
+++ b/lib/redmine/scm/adapters/gitlab_adapter.rb
@@ -0,0 +1,289 @@
+#coding=utf-8
+#
+require 'redmine/scm/adapters/abstract_adapter'
+require 'base64'
+
+module Redmine
+ module Scm
+ module Adapters
+
+ class GitlabAdapter < AbstractAdapter
+
+ class GitBranch < Branch
+ attr_accessor :is_default
+ end
+
+ def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
+ super
+ @g = Gitlab.client
+ @project = Repository.find_by_url(url).project.gpid
+ @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
+ end
+
+ def path_encoding
+ @path_encoding
+ end
+
+ def info
+ begin
+ Info.new(:root_url => url, :lastrev => lastrev('',nil))
+ rescue
+ nil
+ end
+ end
+
+ def branches
+ return @branches if @branches
+ @branches = []
+ branches = @g.branches(@project)
+ branches.each do |line|
+ name = line.name
+ scmid = line.commit.id
+ bran = GitBranch.new(name)
+ bran.revision = scmid
+ bran.scmid = name
+ bran.is_default = true #TODO
+ @branches << bran
+ end
+ @branches.sort!
+ rescue ScmCommandAborted
+ nil
+ end
+
+ def tags
+ return @tags if @tags
+ tags = @g.tags(@project)
+ @tags = []
+ tags.each do |tag|
+ @tags << tag.name
+ end
+ rescue ScmCommandAborted
+ nil
+ end
+
+ def default_branch
+ bras = self.branches
+ return nil if bras.nil?
+ default_bras = bras.select{|x| x.is_default == true}
+ return default_bras.first.to_s if ! default_bras.empty?
+ master_bras = bras.select{|x| x.to_s == 'master'}
+ master_bras.empty? ? bras.first.to_s : 'master'
+ end
+
+ def entry(path=nil, identifier=nil)
+ parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
+ search_path = parts[0..-2].join('/')
+ search_name = parts[-1]
+ if search_path.blank? && search_name.blank?
+ # Root entry
+ Entry.new(:path => '', :kind => 'dir')
+ else
+ # Search for the entry in the parent directory
+ es = entries(search_path, identifier,
+ options = {:report_last_commit => false})
+ es ? es.detect {|e| e.name == search_name} : nil
+ end
+ end
+
+ def entries(path=nil, identifier=nil, options={})
+ entries = Entries.new
+ trees = @g.trees(@project, path: path, ref_name: identifier)
+ trees.each do |tree|
+ entries << Entry.new({
+ :name => tree.name,
+ :path => File.join(path,tree.name),
+ :kind => tree.type == 'tree' ? 'dir' : 'file',
+ :size => nil,
+ :lastrev => nil
+ })
+ end
+ entries.sort_by_name
+ rescue ScmCommandAborted
+ nil
+ rescue Exception
+ nil
+ end
+
+ def lastrev(path, rev)
+ return nil if path.nil?
+ cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1|
+ cmd_args << rev if rev
+ cmd_args << "--" << path unless path.empty?
+ lines = []
+ git_cmd(cmd_args) { |io| lines = io.readlines }
+ begin
+ id = lines[0].split[1]
+ author = lines[1].match('Author:\s+(.*)$')[1]
+ time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1])
+
+ Revision.new({
+ :identifier => id,
+ :scmid => id,
+ :author => author,
+ :time => time,
+ :message => nil,
+ :paths => nil
+ })
+ rescue NoMethodError => e
+ logger.error("The revision '#{path}' has a wrong format")
+ return nil
+ end
+ rescue ScmCommandAborted
+ nil
+ end
+
+ def revisions(path, identifier_from, identifier_to, options={})
+ revs = Revisions.new
+ cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller --parents --stdin|
+ cmd_args << "--reverse" if options[:reverse]
+ cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit]
+ cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty?
+ revisions = []
+ if identifier_from || identifier_to
+ revisions << ""
+ revisions[0] << "#{identifier_from}.." if identifier_from
+ revisions[0] << "#{identifier_to}" if identifier_to
+ else
+ unless options[:includes].blank?
+ revisions += options[:includes]
+ end
+ unless options[:excludes].blank?
+ revisions += options[:excludes].map{|r| "^#{r}"}
+ end
+ end
+
+
+ commits = @g.commits(@project, {ref_name: identifier_to})
+ commits.each do |commit|
+ revision = Revision.new({
+ :identifier => commit.id,
+ :scmid => commit.id,
+ :author => commit.author_name,
+ :time => Time.parse(commit.created_at),
+ :message => commit.message,
+ :paths => nil,
+ :parents => nil
+ })
+ revs << revision
+ end
+
+ revs
+ rescue ScmCommandAborted => e
+ err_msg = "git log error: #{e.message}"
+ logger.error(err_msg)
+ if block_given?
+ raise CommandFailed, err_msg
+ else
+ revs
+ end
+ end
+
+ def diff(path, identifier_from, identifier_to=nil)
+ path ||= ''
+ cmd_args = []
+ if identifier_to
+ cmd_args << "diff" << "--no-color" << identifier_to << identifier_from
+ else
+ cmd_args << "show" << "--no-color" << identifier_from
+ end
+ cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty?
+ diff = []
+ git_cmd(cmd_args) do |io|
+ io.each_line do |line|
+ diff << line
+ end
+ end
+ diff
+ rescue ScmCommandAborted
+ nil
+ end
+
+ def annotate(path, identifier=nil)
+ identifier = 'HEAD' if identifier.blank?
+ cmd_args = %w|blame|
+ cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path)
+ blame = Annotate.new
+ content = nil
+ git_cmd(cmd_args) { |io| io.binmode; content = io.read }
+ # git annotates binary files
+ return nil if content.is_binary_data?
+ identifier = ''
+ # git shows commit author on the first occurrence only
+ authors_by_commit = {}
+ content.split("\n").each do |line|
+ if line =~ /^([0-9a-f]{39,40})\s.*/
+ identifier = $1
+ elsif line =~ /^author (.+)/
+ authors_by_commit[identifier] = $1.strip
+ elsif line =~ /^\t(.*)/
+ blame.add_line($1, Revision.new(
+ :identifier => identifier,
+ :revision => identifier,
+ :scmid => identifier,
+ :author => authors_by_commit[identifier]
+ ))
+ identifier = ''
+ author = ''
+ end
+ end
+ blame
+ rescue ScmCommandAborted
+ nil
+ end
+
+ def cat(path, identifier=nil)
+ if identifier.nil?
+ identifier = 'HEAD'
+ end
+ file = @g.files(@project, path, identifier)
+ cat = Base64.decode64 file.content
+ rescue ScmCommandAborted
+ nil
+ end
+
+ def parse_commit(commits)
+ sum = {file: 0, insertion: 0, deletion: 0}
+ commits.split("\n").each do |commit|
+ if /(\d+)\s+?file/ =~ commit
+ sum[:file] += $1 .to_i
+ end
+ if /(\d+)\s+?insertion/ =~ commit
+ sum[:insertion] += $1.to_i
+ end
+ if /(\d+)\s+?deletion/ =~ commit
+ sum[:deletion] += $1.to_i
+ end
+ end
+ sum[:insertion] + sum[:deletion]
+ end
+
+ def commits(authors, start_date, end_date, branch='master')
+ rs = []
+ authors.each do |author|
+ cmd_args = %W|log #{branch} --pretty=tformat: --shortstat --author=#{author} --since=#{start_date} --until=#{end_date}|
+ commits = ''
+ git_cmd(cmd_args) do |io|
+ commits = io.read
+ end
+ logger.info "git log output for #{author} #{commits}"
+ rs << {author: author, num: parse_commit(commits)}
+ end
+ rs
+ end
+
+ class Revision < Redmine::Scm::Adapters::Revision
+ # Returns the readable identifier
+ def format_identifier
+ identifier[0,8]
+ end
+ end
+
+ def git_cmd(args, options = {}, &block)
+ logger.info "git cmd: #{args.join(' ')}"
+ end
+ private :git_cmd
+ end
+
+ end
+ end
+end
diff --git a/lib/tasks/gitlab.rake b/lib/tasks/gitlab.rake
new file mode 100644
index 000000000..ffa81e912
--- /dev/null
+++ b/lib/tasks/gitlab.rake
@@ -0,0 +1,44 @@
+require 'trustie/gitlab/sync'
+
+namespace :gitlab do
+ namespace :sync do
+ desc "sync users to gitlab"
+ task :users => :environment do
+ # User.where(username: 'root').find_each do |user|
+ s = Trustie::Gitlab::Sync.new
+ User.find_each do |user|
+ s.sync_user(user)
+ end
+ end
+
+ desc "update user password"
+ task :password => :environment do
+ s = Trustie::Gitlab::Sync.new
+ s.change_password(1,'5188b7a65acf294ee7deceb397b6f9c62214ea50','dcb8d9fffabec60c2d0d1030b679fbbb')
+ end
+
+ desc "sync projects to gitlab"
+ task :projects => :environment do
+ s = Trustie::Gitlab::Sync.new
+ Project.where(id: ENV["PROJECT_ID"]).find_each do |project|
+ s.sync_project(project, path: ENV["REP_NAME"], import_url: project.repository.url)
+ end
+ end
+
+ desc "remove all projects"
+ task :remove_all_projects => :environment do
+ g = Gitlab.client
+ 100.times do
+ g.projects(scope: 'all').each do |p|
+ puts p.id
+ begin
+ g.delete_project(p.id)
+ rescue => e
+ puts e
+ end
+ end
+ end
+ end
+
+ end
+end
diff --git a/lib/trustie.rb b/lib/trustie.rb
index 3636efd95..2010e2d43 100644
--- a/lib/trustie.rb
+++ b/lib/trustie.rb
@@ -1,3 +1,4 @@
require 'trustie/utils'
require 'trustie/utils/image'
+require 'trustie/gitlab/api'
require 'trustie/grack/grack'
diff --git a/lib/trustie/gitlab/api.rb b/lib/trustie/gitlab/api.rb
new file mode 100644
index 000000000..6c1993721
--- /dev/null
+++ b/lib/trustie/gitlab/api.rb
@@ -0,0 +1,35 @@
+#coding=utf-8
+#
+#
+module Trustie
+ module Gitlab
+ class Api
+ # attr_accessor :g
+
+ def initialize(client=nil)
+ @g = client || ::Gitlab.client
+ end
+
+ def trees(project_id)
+ @g.trees(project_id)
+ end
+
+ def entries(project)
+ entries = []
+ api = Trustie::Gitlab::Api.new
+ trees = api.trees(11)
+ trees.each do |tree|
+ entries << Redmine::Scm::Adapters::Entry.new({
+ :name => tree.name,
+ :path => tree.id,
+ :kind => tree.type == 'tree' ? 'dir' : 'file',
+ :size => nil,
+ :lastrev => nil
+ })
+ end
+ entries
+ end
+
+ end
+ end
+end
diff --git a/lib/trustie/gitlab/helper.rb b/lib/trustie/gitlab/helper.rb
new file mode 100644
index 000000000..5ea4c13e1
--- /dev/null
+++ b/lib/trustie/gitlab/helper.rb
@@ -0,0 +1,37 @@
+#coding=utf-8
+
+module Trustie
+ module Gitlab
+ module Helper
+ def change_password(uid, en_pwd, salt)
+ options = {:encrypted_password=>en_pwd, :password_salt=>salt}
+ self.g.put("/users/ext/#{uid}", :body => options)
+ # g.edit_user(uid, :encrypted_password=>en_pwd, :password_salt=>salt)
+ end
+
+ def add_user(user)
+ u = nil
+ begin
+ u = self.g.get("/users?search=#{user.mail}").first
+ unless u
+ u = self.g.create_user(user.mail,
+ user.hashed_password,
+ name: user.show_name,
+ username: user.login,
+ confirm: "true")
+ user.gid = u.id
+ end
+ change_password(u.id, user.hashed_password, user.salt)
+ rescue => e
+ puts e
+ end
+ return u
+ end
+
+ def del_user(user)
+ ## gitlab unimplement
+ end
+
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/trustie/gitlab/manage_member.rb b/lib/trustie/gitlab/manage_member.rb
new file mode 100644
index 000000000..d0f74ad5e
--- /dev/null
+++ b/lib/trustie/gitlab/manage_member.rb
@@ -0,0 +1,56 @@
+#coding=utf-8
+#
+#
+module Trustie
+ module Gitlab
+
+ module ManageMember
+ def self.included(base)
+ base.class_eval {
+ before_create :add_gitlab_member
+ before_destroy :delete_gitlab_member
+ after_save :change_gitlab_member
+ }
+ end
+
+ def change_gitlab_member
+ if isGitlabProject?
+ @g ||= ::Gitlab.client
+ @g.edit_team_member(project.gpid, self.member.user.gid, self.role.to_gitlab_role )
+ end
+ end
+
+ def add_gitlab_member
+ if isGitlabProject?
+ @g ||= ::Gitlab.client
+ @g.add_team_member(project.gpid, self.member.user.gid, self.role.to_gitlab_role )
+ end
+ end
+
+ def delete_gitlab_member
+ if isGitlabProject?
+ if member.roles.count <=1
+ @g ||= ::Gitlab.client
+ @g.remove_team_member(project.gpid, self.member.user.gid)
+ end
+ end
+ end
+
+ private
+ def project
+ self.member.project
+ end
+
+ def repository
+ project.repository
+ end
+
+ def isGitlabProject?
+ repository && repository.gitlab?
+ end
+
+ end
+
+ end
+end
+
diff --git a/lib/trustie/gitlab/manage_user.rb b/lib/trustie/gitlab/manage_user.rb
new file mode 100644
index 000000000..76528739c
--- /dev/null
+++ b/lib/trustie/gitlab/manage_user.rb
@@ -0,0 +1,37 @@
+#coding=utf-8
+#
+#
+require_relative 'helper'
+module Trustie
+ module Gitlab
+ module ManageUser
+ include Helper
+
+ def self.included(base)
+ base.class_eval {
+ before_create :add_gitlab_user
+ before_destroy :delete_gitlab_user
+ before_save :change_gitlab_user
+ }
+ end
+
+ def add_gitlab_user
+ add_user(self)
+ end
+
+ def delete_gitlab_user
+ del_user(self)
+ end
+
+ def change_gitlab_user
+ change_password(self.gid, self.hashed_password, self.salt)
+ end
+
+ private
+ def g
+ @g ||= ::Gitlab.client
+ end
+
+ end
+ end
+end
diff --git a/lib/trustie/gitlab/sync.rb b/lib/trustie/gitlab/sync.rb
new file mode 100644
index 000000000..d941795ee
--- /dev/null
+++ b/lib/trustie/gitlab/sync.rb
@@ -0,0 +1,73 @@
+#coding=utf-8
+
+require_relative 'helper'
+
+module Trustie
+ module Gitlab
+ module UserLevel
+ GUEST = 10
+ REPORTER = 20
+ DEVELOPER = 30
+ MASTER = 40
+ OWNER = 50
+ end
+
+ class Sync
+ attr :g
+ include Helper
+
+ def initialize
+ @g = ::Gitlab.client
+ end
+
+ def sync_user(user)
+ u = add_user(user)
+ user.save! if u
+ end
+
+ def sync_project(project, opt={})
+ gid = project.owner.gid
+ raise "unknow gid" unless gid
+ path = opt[:path]
+ raise "unknow path" unless path
+ import_url = opt[:import_url]
+ raise "unknow import_url" unless import_url
+
+ if opt[:password]
+ import_url.sub('@', ":#{opt[:password]}@")
+ end
+
+ # import url http://xianbo_trustie2:1234@repository.trustie.net/xianbo/trustie2.git
+ # can use password
+ gproject = self.g.create_project(path,
+ path: path,
+ description: project.description,
+ wiki_enabled: false,
+ wall_enabled: false,
+ issues_enabled: false,
+ snippets_enabled: false,
+ public: false,
+ user_id: gid,
+ import_url: import_url
+ )
+ project.gpid = gproject.id
+ project.save!
+ puts "Successfully created #{project.name}"
+ # add team members
+ #
+
+ project.members.each do |m|
+ begin
+ self.g.add_team_member(gproject.id, m.user.gid, UserLevel::DEVELOPER)
+ rescue => e
+ puts e
+ end
+ end
+ end
+
+ def remove_project
+ end
+ end
+
+ end
+end
\ No newline at end of file
diff --git a/public/images/vlicon/branch_icon.png b/public/images/vlicon/branch_icon.png
new file mode 100644
index 000000000..c80e17134
Binary files /dev/null and b/public/images/vlicon/branch_icon.png differ
diff --git a/public/images/vlicon/clone_url.png b/public/images/vlicon/clone_url.png
new file mode 100644
index 000000000..a1c71862a
Binary files /dev/null and b/public/images/vlicon/clone_url.png differ
diff --git a/public/images/vlicon/commit_icon.png b/public/images/vlicon/commit_icon.png
new file mode 100644
index 000000000..148dbcd4b
Binary files /dev/null and b/public/images/vlicon/commit_icon.png differ
diff --git a/public/images/vlicon/download_icon.png b/public/images/vlicon/download_icon.png
new file mode 100644
index 000000000..b442730fe
Binary files /dev/null and b/public/images/vlicon/download_icon.png differ
diff --git a/public/images/vlicon/fork_icon.png b/public/images/vlicon/fork_icon.png
new file mode 100644
index 000000000..45a6b0e82
Binary files /dev/null and b/public/images/vlicon/fork_icon.png differ
diff --git a/public/javascripts/project.js b/public/javascripts/project.js
index c74ff18bf..5ba7c7145 100644
--- a/public/javascripts/project.js
+++ b/public/javascripts/project.js
@@ -511,3 +511,13 @@ function submitProjectFeedback() {
$("#project_feedback_form").submit();
}
+// 点击按钮复制功能
+function jsCopy(){
+ var e=document.getElementById("copy_rep_content");
+ e.select();
+ document.execCommand("Copy");
+}
+
+function zip(){
+ alert("该功能正在紧张的开发中,我们会争取在最短时间内上线,如若对您工作造成不便敬请谅解!")
+}
\ No newline at end of file
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 727d79538..f8b5395d5 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -17,6 +17,15 @@ li{list-style-type:none;}
.cancel_btn {background-color: #c1c1c1; color: #ffffff; padding: 2px 5px; border: none; border-radius: 3px; cursor: pointer;}
.cancel_btn:hover {background-color:#656565; }
/*huang*/
+.repository-update-dec{
+ padding: 10px;
+}
+.repository-update-dec .c_grey {
+ color: #999999;
+}
+.repository-update-dec .c_orange {
+ color: #ff7143;
+}
.hwork_input_news{ border:1px solid #64bdd9; height:22px; width:594px; background:#fff; margin-bottom:10px; padding:5px;}
.ml55{ margin-left:55px;}
diff --git a/public/stylesheets/project.css b/public/stylesheets/project.css
index 1c31f603f..e769e14e4 100644
--- a/public/stylesheets/project.css
+++ b/public/stylesheets/project.css
@@ -13,6 +13,7 @@ a:hover.lg-foot{ color:#787b7e;}
/*右侧内容--动态*/
.project_r_h{ width:670px; height:40px; background:#eaeaea; margin-bottom:10px;}
.project_h2{ background:#64bdd9; color:#fff; height:33px; width:90px; text-align:center; font-weight:normal; padding-top:7px; font-size:16px;}
+.project_h2_repository{ background:#64bdd9; color:#fff; height:33px; width:auto; text-align:center; font-weight:normal; padding-top:7px; font-size:16px;}
.project_h22{ background:#64bdd9; color:#fff; height:33px; width:124px; text-align:center; font-weight:normal; padding-top:7px; font-size:16px;}
.project_r_box{ border:1px solid #e2e1e1; width:670px; margin-top:10px;}
.project_h3 { color:#646464; font-size:14px; padding:0 10px; border-bottom:1px solid #e2e1e1;}
@@ -814,8 +815,8 @@ div.changeset { border-bottom: 1px solid #ddd; }
/*****项目版本库文件 Tables *****/
-.autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
-tr.entry { border: 1px solid #f8f8f8; }
+.autoscroll {overflow-x: auto; margin-bottom: 0.2em;}
+tr.entry { border: 1px solid #DDD; }
tr.entry td { white-space: nowrap; }
tr.entry td.filename { width: 30%; }
tr.entry td.filename_no_report { width: 70%; }
diff --git a/public/stylesheets/public.css b/public/stylesheets/public.css
index 91b073233..df42f783e 100644
--- a/public/stylesheets/public.css
+++ b/public/stylesheets/public.css
@@ -12,7 +12,7 @@ textarea {resize: none;}
.pInline {margin:0px; padding:0px; display:inline-block;}
/*常用*/
-select,input,textarea{ border:1px solid #269ac9; background:#fff; color:#000; padding-left:5px; }
+select,input,textarea{ border:1px solid #269ac9; background:#fff; color:#000; padding-left:5px;padding-right: 5px; }
.sub_btn{ cursor:pointer; -moz-border-radius:3px; -webkit-border-radius:3px; border:1px solid #707070; color:#000; border-radius:3px; padding:1px 10px; margin-bottom:10px; background:#dbdbdb;}
.sub_btn:hover{ background:#b5e2fa; color:#000; border:1px solid #3c7fb1;}
table{ background:#fff;}
@@ -104,6 +104,7 @@ h4{ font-size:14px; color:#3b3b3b;}
.mt8{ margin-top:8px;}
.mt10{ margin-top:10px !important;}
.mt30{ margin-top: 30px;}
+.mt40{ margin-top: 40px;}
.mt12 { margin-top:12px !important;}
.mt15 {margin-top:15px;}
.mt19 {margin-top:19px !important;}
@@ -116,6 +117,8 @@ h4{ font-size:14px; color:#3b3b3b;}
.mb20{ margin-bottom:20px;}
.pl15{ padding-left:15px;}
.pt5{ padding-top:5px;}
+.pt10{ padding-top:10px;}
+.pb5{ padding-bottom: 5px;}
.w20{ width:20px;}
.w40{width: 40px;}
.w45{ width: 45px;}
diff --git a/public/stylesheets/repository.css b/public/stylesheets/repository.css
new file mode 100644
index 000000000..375b188d3
--- /dev/null
+++ b/public/stylesheets/repository.css
@@ -0,0 +1,221 @@
+.git_usr_title{
+ margin: 0px;
+ overflow: hidden;
+ font-size: 18px;
+ font-weight: bold;
+ text-overflow: ellipsis;
+ vertical-align: top;
+ white-space: nowrap;
+ padding: 0px 10px;
+}
+.overall-summary{
+ position: relative;
+ margin-bottom: 10px;
+ border: 1px solid #DDD;
+ border-radius: 3px;
+}
+.overall-summary .overall-summary-bottomless{
+ margin-bottom: 0px;
+ border-bottom: 0px none;
+ border-radius: 3px 3px 0px 0px;
+}
+.stats-switcher-viewport{
+ height: 38px;
+ overflow: hidden;
+}
+.stats-switcher-viewport .stats-switcher-wrapper{
+ position: relative;
+ top: 0px;
+ transition: top 0.25s ease-in-out 0s;
+}
+.numbers-summary{
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+ margin-top: 9px;
+}
+.numbers-summary li{
+ display: table-cell;
+ padding: 0px;
+ margin: 0px;
+ text-align: center;
+ white-space: nowrap;
+}
+.numbers-summary .octicon {
+ color: #999;
+}
+.text-emphasized {
+ font-weight: bold;
+ color: #333;
+}
+.octicon .octicon-history {
+ font: 16px/1 octicons;
+ display: inline-block;
+ text-decoration: none;
+ text-rendering: auto;
+ -moz-user-select: none;
+}
+.select2-container {
+ margin: 0px;
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.select2-container .select2-choice {
+ display: block;
+ height: 26px;
+ padding: 0px 0px 0px 8px;
+ overflow: hidden;
+ position: relative;
+ border: 1px solid #AAA;
+ white-space: nowrap;
+ line-height: 26px;
+ color: #444;
+ text-decoration: none;
+ border-radius: 4px;
+ background-clip: padding-box;
+ -moz-user-select: none;
+ background-color: #FFF;
+ background-image: linear-gradient(to top, #EEE 0%, #FFF 50%);
+}
+.repository-title-dec{
+ color: #fff !important;
+}
+.repository-url{
+ color: #2D2D2D;
+ margin: auto 0px;
+ text-align: center;
+ font-size: 14px;
+ margin-bottom: 10px;
+ margin-top: 10px;
+}
+.center{
+ text-align: center;
+}
+.light-well {
+ background: #F9F9F9 none repeat scroll 0% 0%;
+ padding: 15px;
+}
+.page-title {
+ margin-top: 0px;
+ line-height: 1.5;
+ font-weight: bold;
+ margin-bottom: 5px;
+ font-size: 18px;
+}
+/************* CodeRay styles *************/
+.syntaxhl div {display: inline;}
+.syntaxhl .line-numbers {padding: 2px 4px 2px 4px; background-color: #eee; margin:0px 5px 0px 0px;}
+.syntaxhl .code pre { overflow: auto }
+.syntaxhl .debug { color: white !important; background: blue !important; }
+
+.syntaxhl .annotation { color:#007 }
+.syntaxhl .attribute-name { color:#b48 }
+.syntaxhl .attribute-value { color:#700 }
+.syntaxhl .binary { color:#509 }
+.syntaxhl .char .content { color:#D20 }
+.syntaxhl .char .delimiter { color:#710 }
+.syntaxhl .char { color:#D20 }
+.syntaxhl .class { color:#258; font-weight:bold }
+.syntaxhl .class-variable { color:#369 }
+.syntaxhl .color { color:#0A0 }
+.syntaxhl .comment { color:#385 }
+.syntaxhl .comment .char { color:#385 }
+.syntaxhl .comment .delimiter { color:#385 }
+.syntaxhl .complex { color:#A08 }
+.syntaxhl .constant { color:#258; font-weight:bold }
+.syntaxhl .decorator { color:#B0B }
+.syntaxhl .definition { color:#099; font-weight:bold }
+.syntaxhl .delimiter { color:black }
+.syntaxhl .directive { color:#088; font-weight:bold }
+.syntaxhl .doc { color:#970 }
+.syntaxhl .doc-string { color:#D42; font-weight:bold }
+.syntaxhl .doctype { color:#34b }
+.syntaxhl .entity { color:#800; font-weight:bold }
+.syntaxhl .error { color:#F00; background-color:#FAA }
+.syntaxhl .escape { color:#666 }
+.syntaxhl .exception { color:#C00; font-weight:bold }
+.syntaxhl .float { color:#06D }
+.syntaxhl .function { color:#06B; font-weight:bold }
+.syntaxhl .global-variable { color:#d70 }
+.syntaxhl .hex { color:#02b }
+.syntaxhl .imaginary { color:#f00 }
+.syntaxhl .include { color:#B44; font-weight:bold }
+.syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
+.syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
+.syntaxhl .instance-variable { color:#33B }
+.syntaxhl .integer { color:#06D }
+.syntaxhl .key .char { color: #60f }
+.syntaxhl .key .delimiter { color: #404 }
+.syntaxhl .key { color: #606 }
+.syntaxhl .keyword { color:#939; font-weight:bold }
+.syntaxhl .label { color:#970; font-weight:bold }
+.syntaxhl .local-variable { color:#963 }
+.syntaxhl .namespace { color:#707; font-weight:bold }
+.syntaxhl .octal { color:#40E }
+.syntaxhl .operator { }
+.syntaxhl .predefined { color:#369; font-weight:bold }
+.syntaxhl .predefined-constant { color:#069 }
+.syntaxhl .predefined-type { color:#0a5; font-weight:bold }
+.syntaxhl .preprocessor { color:#579 }
+.syntaxhl .pseudo-class { color:#00C; font-weight:bold }
+.syntaxhl .regexp .content { color:#808 }
+.syntaxhl .regexp .delimiter { color:#404 }
+.syntaxhl .regexp .modifier { color:#C2C }
+.syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
+.syntaxhl .reserved { color:#080; font-weight:bold }
+.syntaxhl .shell .content { color:#2B2 }
+.syntaxhl .shell .delimiter { color:#161 }
+.syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
+.syntaxhl .string .char { color: #46a }
+.syntaxhl .string .content { color: #46a }
+.syntaxhl .string .delimiter { color: #46a }
+.syntaxhl .string .modifier { color: #46a }
+.syntaxhl .symbol .content { color:#d33 }
+.syntaxhl .symbol .delimiter { color:#d33 }
+.syntaxhl .symbol { color:#d33 }
+.syntaxhl .tag { color:#070 }
+.syntaxhl .type { color:#339; font-weight:bold }
+.syntaxhl .value { color: #088; }
+.syntaxhl .variable { color:#037 }
+
+.syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
+.syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
+.syntaxhl .change { color: #bbf; background: #007; }
+.syntaxhl .head { color: #f8f; background: #505 }
+.syntaxhl .head .filename { color: white; }
+
+.syntaxhl .delete .eyecatcher { background-color: hsla(0,100%,50%,0.2); border: 1px solid hsla(0,100%,45%,0.5); margin: -1px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; }
+.syntaxhl .insert .eyecatcher { background-color: hsla(120,100%,50%,0.2); border: 1px solid hsla(120,100%,25%,0.5); margin: -1px; border-top: none; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }
+
+.syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
+.syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
+.syntaxhl .change .change { color: #88f }
+.syntaxhl .head .head { color: #f4f }
+
+/***** Media print specific styles *****/
+@media print {
+ #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
+ #main { background: #fff; }
+ #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
+ #wiki_add_attachment { display:none; }
+ .hide-when-print { display: none; }
+ .autoscroll {overflow-x: visible;}
+ table.list {margin-top:0.5em;}
+ table.list th, table.list td {border: 1px solid #aaa;}
+}
+
+.cloneUrl {width:235px; height:21px; border:1px solid #dddddd; outline:none; overflow:hidden; line-height:21px; resize:none;white-space:nowrap;}
+.clone_btn {width:30px; height:21px; border-top:1px solid #dddddd; border-bottom:1px solid #dddddd; border-right:1px solid #dddddd; outline:none; float:left; background-image:linear-gradient(#FCFCFC, #EEE); text-align:center;}
+.vl_btn {height:21px; padding:0px 5px; vertical-align:middle; border:1px solid #dddddd; float:left; line-height:21px; background-image:linear-gradient(#FCFCFC, #EEE);}
+.vl_btn_2 {height:21px; padding:0px 5px; vertical-align:middle; border-top:1px solid #dddddd; border-bottom:1px solid #dddddd; border-right:1px solid #dddddd; float:left; line-height:21px;}
+.recordBanner {width:670px; height:30px; background-color:#f1f1f1; color:#666666; line-height:30px; vertical-align:middle;}
+.vl_copy {background:url(../images/vlicon/clone_url.png) 0px 0px no-repeat; padding-left:22px;}
+.vl_zip {background:url(../images/vlicon/download_icon.png) 0px 0px no-repeat; padding-left:22px;}
+.vl_fork {background:url(../images/vlicon/fork_icon.png) 0px -2px no-repeat; padding-left:22px;}
+.vl_commit {background:url(../images/vlicon/commit_icon.png) 0px -2px no-repeat; padding-left:22px;weight:20px;height: 24px;}
+.vl_branch {background:url(../images/vlicon/branch_icon.png) 0px -2px no-repeat; padding-left:22px}
+.mt1 {margin-top:1px;}
+.mt2 {margin-top:2px;}
+.commit_content_dec{width: 300px;overflow: hidden; white-space: nowrap;text-overflow: ellipsis;}
diff --git a/spec/requests/gitlab_request_spec.rb b/spec/requests/gitlab_request_spec.rb
new file mode 100644
index 000000000..05a94568b
--- /dev/null
+++ b/spec/requests/gitlab_request_spec.rb
@@ -0,0 +1,13 @@
+require 'rails_helper'
+
+RSpec.describe "Gitlab request", :type => :request do
+
+ describe "get repository files" do
+ it "参数正确,可以获取正确列表 " do
+ api = Trustie::Gitlab::Api.new
+ trees = api.trees(11)
+ expect(trees).to be_instance_of(Array)
+ end
+ end
+end
+