BigW Consortium Gitlab

Commit 8d74123d by Nihad Abbasov

Merge branch 'master' into features/feeds

parents f476c42d 76294699
v 1.2.0
- new design
- user dashboard
- markdown support for comments
- encoding issues
- wall like twitter timeline
v 1.1.0
- project dashboard
- wall redesigned
......
......@@ -37,7 +37,6 @@ end
group :development, :test do
gem 'rspec-rails'
gem "shoulda", "~> 3.0.0.beta2"
gem 'capybara'
gem 'autotest'
gem 'autotest-rails'
......@@ -51,4 +50,5 @@ end
group :test do
gem 'turn', :require => false
gem 'simplecov', :require => false
gem "shoulda", "~> 3.0.0.beta2"
end
......@@ -10,6 +10,8 @@
//= require jquery.ui.selectmenu
//= require jquery.tagify
//= require jquery.cookie
//= require raphael
//= require branch-graph
//= require_tree .
$(function(){
......@@ -19,8 +21,20 @@ $(function(){
$('select#branch').selectmenu({style:'popup', width:200});
$('select#tag').selectmenu({style:'popup', width:200});
$(".account-box").mouseenter(showMenu);
$(".account-box").mouseleave(resetMenu);
});
function updatePage(data){
$.ajax({type: "GET", url: location.href, data: data, dataType: "script"});
}
function showMenu() {
$(this).toggleClass('hover');
}
function resetMenu() {
$(this).removeClass("hover");
}
......@@ -8,3 +8,34 @@
*= require_self
*= require_tree .
*/
/** COLORS **/
.cgray { color:gray; }
.cred { color:#D12F19; }
.cgreen { color:#44aa22; }
/** COMMON STYLES **/
.left {
float:left;
}
.right {
float:right;
}
.width-50p{
width:50%;
}
.width-49p{
width:49%;
}
.width-30p{
width:30%;
}
.width-65p{
width:65%;
}
.append-bottom-10 {
margin-bottom:10px;
}
.prepend-top-10 {
margin-top:10px;
}
/** Commit diff view **/
.diff_file {
border:1px solid #CCC;
margin-bottom:1em;
.diff_file_header {
padding:5px 5px;
border-bottom:1px solid #CCC;
background: #eee;
}
.diff_file_content {
overflow:auto;
overflow-y:hidden;
background:#fff;
color:#333;
font-size: 12px;
font-family: 'Courier New', 'andale mono','lucida console',monospace;
}
.diff_file_content_image {
background:#eee;
text-align:center;
img {
padding:100px;
max-width:300px;
}
}
}
.diff_file_content{
.old_line, .new_line {
background:#ECECEC;
color:#777;
width:30px;
float:left;
padding: 0px 5px;
border-right: 1px solid #ccc;
}
}
pre.commit_message {
white-space: pre-wrap;
}
// Place all the styles related to the Dashboard controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
// Place all the styles related to the Issues controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
.issue-number {
float: left;
border-radius: 5px;
text-shadow: none;
background: rgba(0, 0, 0, 0.12);
text-align: center;
padding: 14px 8px;
width: 40px;
margin-right: 10px;
color: #444;
}
.issues_filter {
margin-top:10px;
.left {
margin-right:15px;
}
}
.top_panel_issues{
#issue_search_form {
margin:5px 0;
input {
border:1px solid #D3D3D3;
padding: 3px;
height: 28px;
width: 300px;
-webkit-appearance:none;
box-sizing: border-box;
-moz-box-sizing: border-box;
&:focus {
border-color:#c2e1ef;
}
}
}
}
/** ISSUES LIST **/
.issue .action-links {
display:none;
a {
margin-left:10px;
}
}
.issue:hover .action-links { display:block; }
/** Notes **/
#notes-list {
display:block;
list-style:none;
margin:0px;
padding:0px;
}
.issue_notes {
.note_content {
float:left;
width:400px;
}
}
/* Note textare */
#note_note {
height:100px;
width:97%;
font-size:14px;
}
#new_note {
#note_note {
height:25px;
}
.attach_holder {
display:none;
}
}
#notes-list .note .delete-note { display:none; }
#notes-list .note:hover .delete-note { display:block; }
body.project-page #notes-list .note {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;}
body.project-page #notes-list .note {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;}
body.project-page #notes-list .note img{float: left; margin-right: 10px;}
body.project-page #notes-list .note span.note-title{display: block;}
body.project-page #notes-list .note span.note-title{margin-bottom: 10px}
body.project-page #notes-list .note span.note-author{color: #999; font-weight: normal; font-style: italic;}
body.project-page #notes-list .note span.note-author strong{font-weight: bold; font-style: normal;}
// Place all the styles related to the Profile controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
// Place all the styles related to the Snippets controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
class ApplicationController < ActionController::Base
before_filter :authenticate_user!
before_filter :view_style
protect_from_forgery
helper_method :abilities, :can?
rescue_from Gitosis::AccessDenied do |exception|
......@@ -64,7 +61,7 @@ class ApplicationController < ActionController::Base
else
@branch = params[:branch].blank? ? nil : params[:branch]
@tag = params[:tag].blank? ? nil : params[:tag]
@ref = @branch || @tag || "master"
@ref = @branch || @tag || Repository.default_ref
end
end
......@@ -76,20 +73,6 @@ class ApplicationController < ActionController::Base
redirect_to @project unless @project.repo_exists?
end
def view_style
if params[:view_style] == "collapsed"
cookies[:view_style] = "collapsed"
elsif params[:view_style] == "fluid"
cookies[:view_style] = ""
end
@view_mode = if cookies[:view_style] == "collapsed"
:fixed
else
:fluid
end
end
def respond_with_notes
if params[:last_id] && params[:first_id]
@notes = @notes.where("id >= ?", params[:first_id])
......
......@@ -8,18 +8,18 @@ class CommitsController < ApplicationController
before_filter :add_project_abilities
before_filter :authorize_read_project!
before_filter :require_non_empty_project
before_filter :load_refs, :only => :index # load @branch, @tag & @ref
def index
load_refs # load @branch, @tag & @ref
def index
@repo = project.repo
limit, offset = (params[:limit] || 20), (params[:offset] || 0)
if params[:path]
@commits = @repo.log(@ref, params[:path], :max_count => limit, :skip => offset)
else
@commits = @repo.commits(@ref, limit, offset)
end
@commits = if params[:path]
@repo.log(@ref, params[:path], :max_count => limit, :skip => offset)
else
@repo.commits(@ref, limit, offset)
end
respond_to do |format|
format.html # index.html.erb
......@@ -30,8 +30,8 @@ class CommitsController < ApplicationController
def show
@commit = project.repo.commits(params[:id]).first
@notes = project.notes.where(:noteable_id => @commit.id, :noteable_type => "Commit").order("created_at DESC").limit(20)
@note = @project.notes.new(:noteable_id => @commit.id, :noteable_type => "Commit")
@notes = project.commit_notes(@commit).fresh.limit(20)
@note = @project.build_commit_note(@commit)
respond_to do |format|
format.html
......
require File.join(Rails.root, 'lib', 'graph_commit')
class ProjectsController < ApplicationController
before_filter :project, :except => [:index, :new, :create]
layout :determine_layout
......@@ -6,8 +8,8 @@ class ProjectsController < ApplicationController
before_filter :add_project_abilities
before_filter :authorize_read_project!, :except => [:index, :new, :create]
before_filter :authorize_admin_project!, :only => [:edit, :update, :destroy]
before_filter :require_non_empty_project, :only => [:blob, :tree]
before_filter :load_refs, :only => :tree # load @branch, @tag & @ref
def index
source = current_user.projects
......@@ -64,21 +66,8 @@ class ProjectsController < ApplicationController
def show
return render "projects/empty" unless @project.repo_exists?
@date = case params[:view]
when "week" then Date.today - 7.days
when "day" then Date.today
else nil
end
if @date
@date = @date.at_beginning_of_day
@commits = @project.commits_since(@date)
@messages = project.notes.since(@date).order("created_at DESC")
else
@commits = @project.fresh_commits
@messages = project.notes.fresh.limit(10)
end
limit = (params[:limit] || 40).to_i
@activities = @project.updates(limit)
end
#
......@@ -101,15 +90,13 @@ class ProjectsController < ApplicationController
#
def tree
load_refs # load @branch, @tag & @ref
@repo = project.repo
if params[:commit_id]
@commit = @repo.commits(params[:commit_id]).first
else
@commit = @repo.commits(@ref || "master").first
end
@commit = if params[:commit_id]
@repo.commits(params[:commit_id]).first
else
@repo.commits(@ref).first
end
@tree = @commit.tree
@tree = @tree / params[:path] if params[:path]
......@@ -127,6 +114,34 @@ class ProjectsController < ApplicationController
return render_404
end
def graph
@repo = project.repo
commits = Grit::Commit.find_all(@repo, nil, {:max_count => 650})
ref_cache = {}
commits.collect! do |commit|
add_refs(commit, ref_cache)
GraphCommit.new(commit)
end
days = GraphCommit.index_commits(commits)
@days_json = days.compact.collect{|d| [d.day, d.strftime("%b")] }.to_json
@commits_json = commits.collect do |c|
h = {}
h[:parents] = c.parents.collect do |p|
[p.id,0,0]
end
h[:author] = c.author.name.force_encoding("UTF-8")
h[:time] = c.time
h[:space] = c.space
h[:refs] = c.refs.collect{|r|r.name}.join(" ") unless c.refs.nil?
h[:id] = c.sha
h[:date] = c.date
h[:message] = c.message.force_encoding("UTF-8")
h[:login] = c.author.email
h
end.to_json
end
def blob
@repo = project.repo
@commit = project.commit(params[:commit_id])
......@@ -151,6 +166,17 @@ class ProjectsController < ApplicationController
protected
def add_refs(commit, ref_cache)
if ref_cache.empty?
@repo.refs.each do |ref|
ref_cache[ref.commit.id] ||= []
ref_cache[ref.commit.id] << ref
end
end
commit.refs = ref_cache[commit.id] if ref_cache.include? commit.id
commit.refs ||= []
end
def project
@project ||= Project.find_by_code(params[:id])
end
......
require 'digest/md5'
module ApplicationHelper
def gravatar_icon(user_email)
"http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(user_email)}?s=40&d=identicon"
gravatar_host = request.ssl? ? "https://secure.gravatar.com" : "http://www.gravatar.com"
"#{gravatar_host}/avatar/#{Digest::MD5.hexdigest(user_email)}?s=40&d=identicon"
end
def fixed_mode?
@view_mode == :fixed
true
end
def body_class(default_class = nil)
......@@ -13,7 +15,7 @@ module ApplicationHelper
default_class :
content_for(:body_class)
[main, cookies[:view_style]].join(" ")
[main, "collapsed"].join(" ")
end
def commit_name(project, commit)
......
module DashboardHelper
def dashboard_feed_path(project, object)
case object.class.name.to_s
when "Issue" then project_issues_path(project, project.issues.find(object.id))
when "Issue" then project_issue_path(project, project.issues.find(object.id))
when "Grit::Commit" then project_commit_path(project, project.repo.commits(object.id).first)
when "Note"
then
......@@ -19,12 +19,15 @@ module DashboardHelper
end
def dashboard_feed_title(object)
title = case object.class.name.to_s
klass = object.class.to_s.split("::").last
title = case klass
when "Note" then markdown(object.note)
when "Issue" then object.title
when "Grit::Commit" then object.safe_message
when "Commit" then object.safe_message
else return "Project Wall"
end
"[#{object.class.name}] #{truncate(sanitize(title, :tags => []), :length => 60)} "
"[#{klass}] #{truncate(sanitize(title, :tags => []), :length => 60)} "
end
end
module IssuesHelper
def sort_class
if can?(current_user, :admin_issue, @project) && (!params[:f] || params[:f] == "0")
"handle"
end
end
def project_issues_filter_path project, params = {}
params[:f] ||= cookies['issue_filter']
project_issues_path project, params
end
def sort_class
if can?(current_user, :admin_issue, @project) && (!params[:f] || params[:f] == "0")
"handle"
end
end
def project_issues_filter_path project, params = {}
params[:f] ||= cookies['issue_filter']
project_issues_path project, params
end
end
module TagsHelper
def tag_path tag
"/tags/#{tag}"
end
def tag_path tag
"/tags/#{tag}"
end
def tag_list project
html = ''
project.tag_list.each do |tag|
html += link_to tag, tag_path(tag)
end
html.html_safe
end
def tag_list project
html = ''
project.tag_list.each do |tag|
html += link_to tag, tag_path(tag)
end
html.html_safe
end
end
......@@ -54,5 +54,6 @@ end
# updated_at :datetime
# closed :boolean default(FALSE), not null
# position :integer default(0)
# critical :boolean default(FALSE), not null
#
......@@ -38,7 +38,7 @@ end
# Table name: notes
#
# id :integer not null, primary key
# note :string(255)
# note :text
# noteable_id :string(255)
# noteable_type :string(255)
# author_id :integer
......
......@@ -46,6 +46,25 @@ class Project < ActiveRecord::Base
scope :public_only, where(:private_flag => false)
def repository
@repository ||= Repository.new(self)
end
delegate :repo,
:url_to_repo,
:path_to_repo,
:update_gitosis_project,
:destroy_gitosis_project,
:tags,
:repo_exists?,
:commit,
:commits,
:tree,
:heads,
:commits_since,
:fresh_commits,
:to => :repository, :prefix => nil
def to_param
code
end
......@@ -59,16 +78,12 @@ class Project < ActiveRecord::Base
notes.where(:noteable_type => ["", nil])
end
def update_gitosis_project
Gitosis.new.configure do |c|
c.update_project(path, gitosis_writers)
end
def build_commit_note(commit)
notes.new(:noteable_id => commit.id, :noteable_type => "Commit")
end
def destroy_gitosis_project
Gitosis.new.configure do |c|
c.destroy_project(self)
end
def commit_notes(commit)
notes.where(:noteable_id => commit.id, :noteable_type => "Commit")
end
def add_access(user, *access)
......@@ -106,26 +121,6 @@ class Project < ActiveRecord::Base
private_flag
end
def url_to_repo
"#{GITOSIS["git_user"]}@#{GITOSIS["host"]}:#{path}.git"
end
def path_to_repo
GITOSIS["base_path"] + path + ".git"
end
def repo
@repo ||= Grit::Repo.new(path_to_repo)
end
def tags
repo.tags.map(&:name).sort.reverse
end
def repo_exists?
repo rescue false
end
def last_activity
updates(1).first
rescue
......@@ -146,48 +141,6 @@ class Project < ActiveRecord::Base
end[0...n]
end
def commit(commit_id = nil)
if commit_id
repo.commits(commit_id).first
else
repo.commits.first
end
end
def heads
@heads ||= repo.heads
end
def fresh_commits(n = 10)
commits = heads.map do |h|
repo.commits(h.name, n)
end.flatten.uniq { |c| c.id }
commits.sort! do |x, y|
y.committed_date <=> x.committed_date
end
commits[0...n]
end
def commits_since(date)
commits = heads.map do |h|
repo.log(h.name, nil, :since => date)
end.flatten.uniq { |c| c.id }
commits.sort! do |x, y|
y.committed_date <=> x.committed_date
end
commits
end
def tree(fcommit, path = nil)
fcommit = commit if fcommit == :head
tree = fcommit.tree
path ? (tree / path) : tree
end
def check_limit
unless owner.can_create_project?
errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it")
......
class Repository
attr_accessor :project
def self.default_ref
"master"
end
def initialize(project)
@project = project
end
def path
@path ||= project.path
end
def project_id
project.id
end
def repo
@repo ||= Grit::Repo.new(project.path_to_repo)
end
def url_to_repo
if !GITOSIS["port"] or GITOSIS["port"] == 22
"#{GITOSIS["git_user"]}@#{GITOSIS["host"]}:#{path}.git"
else
"ssh://#{GITOSIS["git_user"]}@#{GITOSIS["host"]}:#{GITOSIS["port"]}/#{path}.git"
end
end
def path_to_repo
GITOSIS["base_path"] + path + ".git"
end
def update_gitosis_project
Gitosis.new.configure do |c|
c.update_project(path, project.gitosis_writers)
end
end
def destroy_gitosis_project
Gitosis.new.configure do |c|
c.destroy_project(@project)
end
end
def repo_exists?
repo rescue false
end
def tags
repo.tags.map(&:name).sort.reverse
end
def heads
@heads ||= repo.heads
end
def tree(fcommit, path = nil)
fcommit = commit if fcommit == :head
tree = fcommit.tree
path ? (tree / path) : tree
end
def commit(commit_id = nil)
if commit_id
repo.commits(commit_id).first
else
repo.commits.first
end
end
def fresh_commits(n = 10)
commits = heads.map do |h|
repo.commits(h.name, n).each { |c| c.head = h }
end.flatten.uniq { |c| c.id }
commits.sort! do |x, y|
y.committed_date <=> x.committed_date
end
commits[0...n]
end
def commits_since(date)
commits = heads.map do |h|
repo.log(h.name, nil, :since => date).each { |c| c.head = h }
end.flatten.uniq { |c| c.id }
commits.sort! do |x, y|
y.committed_date <=> x.committed_date
end
commits
end
end
......@@ -16,6 +16,10 @@ class User < ActiveRecord::Base
:foreign_key => :author_id,
:dependent => :destroy
has_many :notes,
:foreign_key => :author_id,
:dependent => :destroy
has_many :assigned_issues,
:class_name => "Issue",
:foreign_key => :assignee_id,
......
......@@ -27,12 +27,13 @@
%a.project-update{:href => dashboard_feed_path(project, update)}
= image_tag gravatar_icon(update.author_email), :class => "left", :width => 40
%span.update-title
- if update.kind_of?(Grit::Commit)
%span.right.tag.commit= update.head.name
= dashboard_feed_title(update)
%span.update-author
%strong= update.author_name
authored
= time_ago_in_words(update.created_at)
ago
%br
/ #news-feed
/ #dashboard-content
......@@ -8,15 +8,11 @@
<%= image_tag gravatar_icon(current_user.email) %>
<% end %>
<a href="#" class="arrow-up"></a>
<div class="account-links">
<%= link_to profile_path, :class => "username" do %>
<%#= current_user.name %>
Your profile
My profile
<% end %>
<%= link_to "Fluid layout", url_for( :view_style => 'fluid' ) if cookies[:view_style] == "collapsed"%>
<%= link_to "Fixed layout", url_for( :view_style => 'collapsed' ) unless cookies[:view_style] == "collapsed"%>
<%= link_to 'Logout', destroy_user_session_path, :class => "logout", :method => :delete %>
</div>
</div><!-- .account-box -->
......@@ -50,3 +46,11 @@
});
<% end %>
<% end %>
<% if current_user.keys.all.empty? %>
<div id="no_ssh_key_defined">
<h2>ATTENTION!</h2>
<p>No SSH Key is defined. You won't be able to use any Git command!
<p>Click <%=link_to( 'here', keys_path ) %> to add one!
</div>
<% end %>
......@@ -20,11 +20,13 @@
.project-container
.project-sidebar
.fixed
%input.git-url.text{:id => "", :name => "", :readonly => "", :type => "text", :value => @project.url_to_repo, :class => "one_click_select"}
.git_url_wrapper
%input.git-url.text{:id => "", :name => "", :readonly => "", :type => "text", :value => @project.url_to_repo, :class => "one_click_select"}
%aside
= link_to "History", project_path(@project), :class => current_page?(:controller => "projects", :action => "show", :id => @project) ? "current" : nil
= link_to "Activities", project_path(@project), :class => current_page?(:controller => "projects", :action => "show", :id => @project) ? "current" : nil
= link_to "Tree", tree_project_path(@project), :class => current_page?(:controller => "projects", :action => "tree", :id => @project) ? "current" : nil
= link_to "Commits", project_commits_path(@project), :class => current_page?(:controller => "commits", :action => "index", :project_id => @project) ? "current" : nil
= link_to "Network graph", graph_project_path(@project), :class => current_page?(:controller => "projects", :action => "graph", :id => @project) ? "current" : nil
= link_to team_project_path(@project), :class => (current_page?(:controller => "projects", :action => "team", :id => @project) || controller.controller_name == "team_members") ? "current" : nil do
Team
- if @project.users_projects.count > 0
......
......@@ -28,4 +28,4 @@
.clear
%br
= f.submit 'Add note', :class => "lbutton vm", :id => "submit_note"
= f.submit 'Add note', :class => "button", :id => "submit_note"
%a.project-update{:href => dashboard_feed_path(project, update)}
= image_tag gravatar_icon(update.author_email), :class => "left", :width => 40
%span.update-title
= dashboard_feed_title(update)
%span.update-author
%strong= update.author_name
authored
= time_ago_in_words(update.created_at)
ago
.right
- klass = update.class.to_s.split("::").last.downcase
%span.tag{ :class => klass }= klass
- if update.kind_of?(Grit::Commit)
%span.tag.commit= update.head.name
%table
%thead
%th
Commits
.filter.right
= form_tag project_path(@project), :method => :get, :class => "right" do
.left
= radio_button_tag :view, "recent", (params[:view] || "recent") == "recent", :onclick => "this.form.submit()", :id => "recent_view"
= label_tag "recent_view","Recent"
.left
= radio_button_tag :view, "day", params[:view] == "day", :onclick => "this.form.submit()", :id => "day_view"
= label_tag "day_view","Today"
.left
= radio_button_tag :view, "week", params[:view] == "week", :onclick => "this.form.submit()", :id => "week_view"
= label_tag "week_view","Week"
- @commits.each do |commit|
%tr
%td
%div.commit
- if commit.author.email
= image_tag gravatar_icon(commit.author.email), :class => "left", :width => 40, :style => "padding-right:5px;"
- else
= image_tag "no_avatar.png", :class => "left", :width => 40, :style => "padding-right:5px;"
.title
%p= link_to truncate(commit.safe_message, :length => fixed_mode? ? 40 : 100), project_commit_path(@project, :id => commit.id)
%span
%span.author
%strong= commit.author.name.force_encoding("UTF-8")
%cite.cgray
= time_ago_in_words(commit.committed_date)
ago
- @messages.group_by{ |x| [x.noteable_id, x.noteable_type]}.each do |item, notes|
- id, type = item[0], item[1]
- parent = load_note_parent(id, type, @project)
- next unless parent
%table
%thead
%th
%div{ :class => "recent_message_parent"}
= link_to(truncate(dashboard_feed_title(parent), :length => fixed_mode? ? 40 : 100 ), dashboard_feed_path(@project, parent))
- notes.sort {|x,y| y.updated_at <=> x.updated_at }.each do |note|
%tr
%td
%div.message
= image_tag gravatar_icon(note.author_email), :class => "left", :width => 40, :style => "padding-right:5px;"
%div.title
= link_to markdown(truncate(note.note, :length => fixed_mode? ? 40 : 100)), dashboard_feed_path(@project, parent) + "#note_#{note.id}"
- if note.attachment.url
%br
Attachment:
= link_to note.attachment_identifier, note.attachment.url
%div.author
%strong= note.author_name
%cite.cgray
= time_ago_in_words(note.updated_at)
ago
%br
- @projects.in_groups_of(3, false) do |projects|
- projects.each_with_index do |project, i|
%div.grid_1
%div.grid_1.projects_selector
%div{ :class => "project-box ui-box ui-box-big" }
= link_to project_path(project) do
%h3= project.name
%h3= truncate(project.name, :length => 20)
.data
%p.title.repository
%p.title.repository.git_url_wrapper
%span Repository:
= project.url_to_repo
%input{ :value => project.url_to_repo, :class => ['git-url', 'one_click_select', 'text', 'project_list_url'], :readonly => 'readonly' }
%p.title.activity
%span Last Activity:
- last_note = project.notes.last
......
......@@ -12,7 +12,7 @@
= time_ago_in_words(content_commit.committed_date)
ago
%td.commit
= link_to truncate(content_commit.safe_message, :length => fixed_mode? ? 40 : 80), project_commit_path(@project, content_commit), :class => "tree-commit-link"
= link_to truncate(content_commit.safe_message, :length => 40), project_commit_path(@project, content_commit), :class => "tree-commit-link"
- tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name)
- if tm
= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm)
......@@ -28,7 +28,7 @@ eos
<h2>Existing Git Repo?</h2>
<% exist_repo_setup_str = <<eos
cd existing_git_repo
git remote add origin #{@project.url_to_repo}
git remote add origin #{@project.url_to_repo}
git push -u origin master
eos
%>
......
#holder.graph
:javascript
var chunk1={commits:#{@commits_json}};
var days=#{@days_json};
initGraph();
$(function(){
branchGraph($("#holder")[0]);
});
.new-project-hodler
.container_4
%h2.icon
%span
New Project
- content_for(:body_class, "new-project-page")
- content_for(:page_title) do
.new-project-hodler
.container
%h2.icon
%span
New Project
%div.clear
= render 'form'
%div.clear
= render 'form'
.left.width-49p
=render "projects/recent_commits"
- content_for(:body_class, "project-page dashboard")
.right.width-49p
=render "projects/recent_messages"
#news-feed.news-feed
%h2.icon
%span>
Activities
.project-box.project-updates.ui-box.ui-box-small.ui-box-big
- @activities.each do |update|
= render "projects/feed", :update => update, :project => @project
:javascript
function updateDashboard(){
......
......@@ -19,7 +19,8 @@
%td{:colspan => 2}
= f.label :content, "Code"
%br
= f.text_area :content, :style => "height:240px;width:932px;"
%br
= f.text_area :content
.actions.prepend-top
= f.submit 'Save', :class => "lbutton vm"
= f.submit 'Save', :class => "button"
......@@ -2,11 +2,17 @@
%tr{ :id => dom_id(snippet), :class => "snippet", :url => project_snippet_path(@project, snippet) }
%td
= image_tag gravatar_icon(snippet.author.email), :class => "left", :width => 40, :style => "padding:0 5px;"
= truncate snippet.author.name, :lenght => 20
%td= html_escape snippet.title
%td= html_escape snippet.file_name
%td
- if can?(current_user, :admin_snippet, @project) || snippet.author == current_user
= link_to 'Edit', edit_project_snippet_path(@project, snippet), :class => "lbutton positive"
- if can?(current_user, :admin_snippet, @project) || snippet.author == current_user
= link_to 'Destroy', [@project, snippet], :confirm => 'Are you sure?', :method => :delete, :remote => true, :class => "lbutton delete-snippet negative", :id => "destroy_snippet_#{snippet.id}"
%span
%strong= html_escape snippet.title
%br
%br
%div.author
%strong= truncate snippet.author.name, :lenght => 20
%cite.cgray
= time_ago_in_words(snippet.updated_at)
ago
.right.action-links
- if can?(current_user, :admin_snippet, @project) || snippet.author == current_user
= link_to 'Edit', edit_project_snippet_path(@project, snippet), :class => "cgray"
- if can?(current_user, :admin_snippet, @project) || snippet.author == current_user
= link_to 'Destroy', [@project, snippet], :confirm => 'Are you sure?', :method => :delete, :remote => true, :class => "cred delete-snippet negative", :id => "destroy_snippet_#{snippet.id}"
%div
- if can? current_user, :write_snippet, @project
.left= link_to 'New Snippet', new_project_snippet_path(@project), :class => "lbutton vm"
= link_to 'New Snippet', new_project_snippet_path(@project), :class => "button append-bottom-10"
%table.round-borders#snippets-table
%tr
%th Author
%th Title
%th File name
%th
%thead
%th
= render @snippets.fresh
:javascript
$('.delete-snippet').live('ajax:success', function() {
......
......@@ -2,3 +2,4 @@ admin_uri: git@localhost:gitosis-admin.git
base_path: /home/git/repositories/
host: localhost
git_user: git
# port: 22
......@@ -34,6 +34,7 @@ Gitlab::Application.routes.draw do
get "blob"
get "team"
get "wall"
get "graph"
# tree viewer
get "tree/:commit_id" => "projects#tree"
......
var commits = {},
comms = {},
pixelsX = [],
pixelsY = [],
mmax = Math.max,
mtime = 0,
mspace = 0,
parents = {},
ii = 0,
colors = ["#000"];
function initGraph(){
commits = chunk1.commits;
ii = commits.length;
for (var i = 0; i < ii; i++) {
for (var j = 0, jj = commits[i].parents.length; j < jj; j++) {
parents[commits[i].parents[j][0]] = true;
}
mtime = Math.max(mtime, commits[i].time);
mspace = Math.max(mspace, commits[i].space);
}
mtime = mtime + 4;
mspace = mspace + 10;
for (i = 0; i < ii; i++) {
if (commits[i].id in parents) {
commits[i].isParent = true;
}
comms[commits[i].id] = commits[i];
}
for (var k = 0; k < mspace; k++) {
colors.push(Raphael.getColor());
}
}
function branchGraph(holder) {
var ch = mspace * 20 + 20, cw = mtime * 20 + 20,
r = Raphael("holder", cw, ch),
top = r.set();
var cuday = 0, cumonth = "";
r.rect(0, 0, days.length * 20 + 20, 20).attr({fill: "#474D57"});
r.rect(0, 20, days.length * 20 + 20, 20).attr({fill: "#f7f7f7"});
for (mm = 0; mm < days.length; mm++) {
if(days[mm] != null){
if(cuday != days[mm][0]){
r.text(10 + mm * 20, 30, days[mm][0]).attr({font: "12px Fontin-Sans, Arial", fill: "#444"});
cuday = days[mm][0]
}
if(cumonth != days[mm][1]){
r.text(10 + mm * 20, 10, days[mm][1]).attr({font: "12px Fontin-Sans, Arial", fill: "#444"});
cumonth = days[mm][1]
}
}
}
for (i = 0; i < ii; i++) {
var x = 10 + 20 * commits[i].time,
y = 70 + 20 * commits[i].space;
r.circle(x, y, 3).attr({fill: colors[commits[i].space], stroke: "none"});
if (commits[i].refs != null && commits[i].refs != "") {
var longrefs = commits[i].refs
var shortrefs = commits[i].refs;
if (shortrefs.length > 15){
shortrefs = shortrefs.substr(0,13) + "...";
}
var t = r.text(x+5, y+5, shortrefs).attr({font: "12px Fontin-Sans, Arial", fill: "#666",
title: longrefs, cursor: "pointer", rotation: "90"});
var textbox = t.getBBox();
t.translate(textbox.height/-4,textbox.width/2);
}
for (var j = 0, jj = commits[i].parents.length; j < jj; j++) {
var c = comms[commits[i].parents[j][0]];
if (c) {
var cx = 10 + 20 * c.time,
cy = 70 + 20 * c.space;
if (c.space == commits[i].space) {
r.path("M" + (x - 5) + "," + (y + .0001) + "L" + (15 + 20 * c.time) + "," + (y + .0001))
.attr({stroke: colors[c.space], "stroke-width": 2});
} else if (c.space < commits[i].space) {
r.path(["M", x - 5, y + .0001, "l-5-2,0,4,5,-2C",x-5,y,x -17, y+2, x -20, y-10,"L", cx,y-10,cx , cy])
.attr({stroke: colors[commits[i].space], "stroke-width": 2});
} else {
r.path(["M", x-5, y, "l-5-2,0,4,5,-2C",x-5,y,x -17, y-2, x -20, y+10,"L", cx,y+10,cx , cy])
.attr({stroke: colors[commits[i].space], "stroke-width": 2});
}
}
}
(function (c, x, y) {
top.push(r.circle(x, y, 10).attr({fill: "#000", opacity: 0, cursor: "pointer"})
.hover(function () {
var s = r.text(100, 100,c.author + "\n \n" +c.id + "\n \n" + c.message).attr({fill: "#fff"});
this.popup = r.popupit(x, y + 5, s, 0);
top.push(this.popup.insertBefore(this));
}, function () {
this.popup && this.popup.remove() && delete this.popup;
}));
}(commits[i], x, y));
}
top.toFront();
var hw = holder.offsetWidth,
hh = holder.offsetHeight,
v = r.rect(hw - 8, 0, 4, Math.pow(hh, 2) / ch, 2).attr({fill: "#000", opacity: 0}),
h = r.rect(0, hh - 8, Math.pow(hw, 2) / cw, 4, 2).attr({fill: "#000", opacity: 0}),
bars = r.set(v, h),
drag,
dragger = function (e) {
if (drag) {
e = e || window.event;
holder.scrollLeft = drag.sl - (e.clientX - drag.x);
holder.scrollTop = drag.st - (e.clientY - drag.y);
}
};
holder.onmousedown = function (e) {
e = e || window.event;
drag = {x: e.clientX, y: e.clientY, st: holder.scrollTop, sl: holder.scrollLeft};
document.onmousemove = dragger;
bars.animate({opacity: .5}, 300);
};
document.onmouseup = function () {
drag = false;
document.onmousemove = null;
bars.animate({opacity: 0}, 300);
};
holder.scrollLeft = cw;
};
Raphael.fn.popupit = function (x, y, set, dir, size) {
dir = dir == null ? 2 : dir;
size = size || 5;
x = Math.round(x);
y = Math.round(y);
var bb = set.getBBox(),
w = Math.round(bb.width / 2),
h = Math.round(bb.height / 2),
dx = [0, w + size * 2, 0, -w - size * 2],
dy = [-h * 2 - size * 3, -h - size, 0, -h - size],
p = ["M", x - dx[dir], y - dy[dir], "l", -size, (dir == 2) * -size, -mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, -size, -size,
"l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, 0, -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size,
"l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, size, size,
"l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0, mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size,
"l", -mmax(w - size, 0), 0, "z"].join(","),
xy = [{x: x, y: y + size * 2 + h}, {x: x - size * 2 - w, y: y}, {x: x, y: y - size * 2 - h}, {x: x + size * 2 + w, y: y}][dir];
set.translate(xy.x - w - bb.x, xy.y - h - bb.y);
return this.set(this.path(p).attr({fill: "#234", stroke: "none"}).insertBefore(set.node ? set : set[0]), set);
};
Raphael.fn.popup = function (x, y, text, dir, size) {
dir = dir == null ? 2 : dir > 3 ? 3 : dir;
size = size || 5;
text = text || "$9.99";
var res = this.set(),
d = 3;
res.push(this.path().attr({fill: "#000", stroke: "#000"}));
res.push(this.text(x, y, text).attr(this.g.txtattr).attr({fill: "#fff", "font-family": "Helvetica, Arial"}));
res.update = function (X, Y, withAnimation) {
X = X || x;
Y = Y || y;
var bb = this[1].getBBox(),
w = bb.width / 2,
h = bb.height / 2,
dx = [0, w + size * 2, 0, -w - size * 2],
dy = [-h * 2 - size * 3, -h - size, 0, -h - size],
p = ["M", X - dx[dir], Y - dy[dir], "l", -size, (dir == 2) * -size, -mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, -size, -size,
"l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, 0, -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size,
"l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, size, size,
"l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0, mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size,
"l", -mmax(w - size, 0), 0, "z"].join(","),
xy = [{x: X, y: Y + size * 2 + h}, {x: X - size * 2 - w, y: Y}, {x: X, y: Y - size * 2 - h}, {x: X + size * 2 + w, y: Y}][dir];
xy.path = p;
if (withAnimation) {
this.animate(xy, 500, ">");
} else {
this.attr(xy);
}
return this;
};
return res.update(x, y);
};
module CommitExt
attr_accessor :head
attr_accessor :refs
def safe_message
message.encode("UTF-8",
:invalid => :replace,
......
require "grit"
class GraphCommit
attr_accessor :time, :space
def initialize(commit)
@_commit = commit
@time = -1
@space = 0
end
def method_missing(m, *args, &block)
@_commit.send(m, *args, &block)
end
# Method is adding time and space on the
# list of commits. As well as returns date list
# corelated with time set on commits.
#
# @param [Array<GraphCommit>] comits to index
#
# @return [Array<TimeDate>] list of commit dates corelated with time on commits
def self.index_commits(commits)
days, heads = [], []
map = {}
commits.reverse.each_with_index do |c,i|
c.time = i
days[i] = c.committed_date
map[c.id] = c
heads += c.refs unless c.refs.nil?
end
heads.select!{|h| h.is_a? Grit::Head or h.is_a? Grit::Remote}
# sort heads so the master is top and current branches are closer
heads.sort! do |a,b|
if a.name == "master"
-1
elsif b.name == "master"
1
else
b.commit.committed_date <=> a.commit.committed_date
end
end
j = 0
heads.each do |h|
if map.include? h.commit.id then
j = mark_chain(j+=1, map[h.commit.id], map)
end
end
days
end
# Add space mark on commit and its parents
#
# @param [Fixnum] space (row on the graph) to be set
# @param [GraphCommit] the commit object.
# @param [Hash<String,GraphCommit>] map of commits
#
# @return [Fixnum] max space used.
def self.mark_chain(mark, commit, map)
commit.space = mark if commit.space == 0
m1 = mark - 1
marks = commit.parents.collect do |p|
if map.include? p.id and map[p.id].space == 0 then
mark_chain(m1 += 1, map[p.id],map)
else
m1 + 1
end
end
marks << mark
marks.compact.max
end
end
require 'spec_helper'
describe ApplicationHelper do
context ".gravatar_icon" do
context "over http" do
it "returns the correct URL to www.gravatar.com" do
expected = "http://www.gravatar.com/avatar/f7daa65b2aa96290bb47c4d68d11fe6a?s=40&d=identicon"
# Pretend we're running over HTTP
helper.stub(:request) do
request = double('request')
request.stub(:ssl?) { false }
request
end
helper.gravatar_icon("admin@local.host").should == expected
end
end
context "over https" do
it "returns the correct URL to secure.gravatar.com" do
expected = "https://secure.gravatar.com/avatar/f7daa65b2aa96290bb47c4d68d11fe6a?s=40&d=identicon"
# Pretend we're running over HTTPS
helper.stub(:request) do
request = double('request')
request.stub(:ssl?) { true }
request
end
helper.gravatar_icon("admin@local.host").should == expected
end
end
end
end
......@@ -39,5 +39,6 @@ end
# updated_at :datetime
# closed :boolean default(FALSE), not null
# position :integer default(0)
# critical :boolean default(FALSE), not null
#
......@@ -66,7 +66,7 @@ end
# Table name: notes
#
# id :integer not null, primary key
# note :string(255)
# note :text
# noteable_id :string(255)
# noteable_type :string(255)
# author_id :integer
......
......@@ -18,6 +18,21 @@ describe User do
user = User.new(:email => "test@mail.com")
user.identifier.should == "test_mail.com"
end
describe "dependent" do
before do
@user = Factory :user
@note = Factory :note,
:author => @user,
:project => Factory(:project)
end
it "should destroy all notes with user" do
Note.find_by_id(@note.id).should_not be_nil
@user.destroy
Note.find_by_id(@note.id).should be_nil
end
end
end
# == Schema Information
#
......
......@@ -22,6 +22,7 @@ describe "Dashboard" do
it "should have news feed" do
within "#news-feed" do
page.should have_content("master")
page.should have_content(@project.commit.author.name)
page.should have_content(@project.commit.safe_message)
end
......
......@@ -72,10 +72,13 @@ describe "Projects" do
current_path.should == project_path(@project)
end
it "should beahave like dashboard" do
page.should have_content("History")
it "should beahave like activities page" do
within ".project-update" do
page.should have_content("master")
page.should have_content(@project.commit.author.name)
page.should have_content(@project.commit.safe_message)
end
end
end
describe "GET /projects/team" do
......
......@@ -7,8 +7,9 @@ describe "Top Panel", :js => true do
before do
visit projects_path
fill_in "search", :with => "Ke"
sleep(2)
find(:xpath, "//ul[contains(@class,'ui-autocomplete')]/li/a[.=\"Keys\"]").click
within ".ui-autocomplete" do
find(:xpath, "//a[.=\"Keys\"]").click
end
end
it "should be on projects page" do
......@@ -23,8 +24,9 @@ describe "Top Panel", :js => true do
visit project_path(@project)
fill_in "search", :with => "Commi"
sleep(2)
find(:xpath, "//ul[contains(@class,'ui-autocomplete')]/li/a[.=\"#{@project.code} / Commits\"]").click
within ".ui-autocomplete" do
find(:xpath, "//a[.=\"#{@project.code} / Commits\"]").click
end
end
it "should be on projects page" do
......
......@@ -382,15 +382,13 @@
padding:.2em .4em;
line-height:1.5;
zoom:1;
font-weight: normal;
}
.ui-menu .ui-menu-item a.ui-state-hover,
.ui-menu .ui-menu-item a.ui-state-active {
font-weight: normal;
margin: -1px;
background: #fff !important;
background: -webkit-gradient(linear,left top,left bottom,from(#fff),to(#FFF6BF)) !important;
background: -moz-linear-gradient(top,#fff,#FFF6BF) !important;
background: transparent 9 !important;
background: #eee;
border-radius:0px;
border-color:white;
border-bottom: 1px solid #E2EAEE;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment