summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouwe Maan <douwe@gitlab.com>2015-09-10 13:13:01 +0100
committerDouwe Maan <douwe@gitlab.com>2015-09-10 13:13:01 +0100
commita6997e339be703e769b32765ffbdbf722ac2aea5 (patch)
treea39e76580ffa460349f509f169cc44b1fe9d8f54
parente4b30f9db3d0b2a2d3b7274d312872fe41d22104 (diff)
parentdadf6daac47badf01a7a0bac94527a4d68555db6 (diff)
downloadgitlab-ce-dashboard-titles.tar.gz
Merge branch 'master' into dashboard-titlesdashboard-titles
-rw-r--r--CHANGELOG7
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/stylesheets/base/gl_bootstrap.scss26
-rw-r--r--app/assets/stylesheets/base/gl_variables.scss22
-rw-r--r--app/assets/stylesheets/base/mixins.scss38
-rw-r--r--app/assets/stylesheets/generic/blocks.scss9
-rw-r--r--app/assets/stylesheets/generic/buttons.scss2
-rw-r--r--app/assets/stylesheets/generic/common.scss32
-rw-r--r--app/assets/stylesheets/generic/issue_box.scss9
-rw-r--r--app/assets/stylesheets/generic/markdown_area.scss11
-rw-r--r--app/assets/stylesheets/generic/sidebar.scss8
-rw-r--r--app/assets/stylesheets/generic/timeline.scss140
-rw-r--r--app/assets/stylesheets/generic/zen.scss4
-rw-r--r--app/assets/stylesheets/pages/commit.scss13
-rw-r--r--app/assets/stylesheets/pages/diff.scss14
-rw-r--r--app/assets/stylesheets/pages/issuable.scss41
-rw-r--r--app/assets/stylesheets/pages/issues.scss12
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss37
-rw-r--r--app/assets/stylesheets/pages/milestone.scss4
-rw-r--r--app/assets/stylesheets/pages/note_form.scss16
-rw-r--r--app/assets/stylesheets/pages/notes.scss29
-rw-r--r--app/controllers/admin/labels_controller.rb58
-rw-r--r--app/controllers/application_controller.rb7
-rw-r--r--app/controllers/import/fogbugz_controller.rb106
-rw-r--r--app/helpers/page_layout_helper.rb22
-rw-r--r--app/models/application_setting.rb2
-rw-r--r--app/models/label.rb8
-rw-r--r--app/models/project.rb11
-rw-r--r--app/services/projects/create_service.rb2
-rw-r--r--app/services/projects/download_service.rb43
-rw-r--r--app/views/admin/labels/_form.html.haml35
-rw-r--r--app/views/admin/labels/_label.html.haml5
-rw-r--r--app/views/admin/labels/destroy.js.haml2
-rw-r--r--app/views/admin/labels/edit.html.haml9
-rw-r--r--app/views/admin/labels/index.html.haml16
-rw-r--r--app/views/admin/labels/new.html.haml7
-rw-r--r--app/views/dashboard/milestones/_milestone.html.haml24
-rw-r--r--app/views/groups/edit.html.haml1
-rw-r--r--app/views/groups/group_members/index.html.haml4
-rw-r--r--app/views/groups/milestones/_milestone.html.haml36
-rw-r--r--app/views/import/fogbugz/new.html.haml25
-rw-r--r--app/views/import/fogbugz/new_user_map.html.haml49
-rw-r--r--app/views/import/fogbugz/status.html.haml51
-rw-r--r--app/views/layouts/_head.html.haml1
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/nav/_admin.html.haml6
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml10
-rw-r--r--app/views/layouts/nav/_profile.html.haml2
-rw-r--r--app/views/layouts/profile.html.haml3
-rw-r--r--app/views/profiles/accounts/show.html.haml10
-rw-r--r--app/views/profiles/applications.html.haml9
-rw-r--r--app/views/profiles/audit_log.html.haml9
-rw-r--r--app/views/profiles/emails/index.html.haml10
-rw-r--r--app/views/profiles/keys/index.html.haml13
-rw-r--r--app/views/profiles/notifications/show.html.haml8
-rw-r--r--app/views/profiles/passwords/edit.html.haml10
-rw-r--r--app/views/profiles/passwords/new.html.haml2
-rw-r--r--app/views/profiles/preferences/show.html.haml8
-rw-r--r--app/views/profiles/show.html.haml9
-rw-r--r--app/views/projects/_md_preview.html.haml4
-rw-r--r--app/views/projects/commit/_commit_box.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml4
-rw-r--r--app/views/projects/diffs/_stats.html.haml2
-rw-r--r--app/views/projects/edit.html.haml7
-rw-r--r--app/views/projects/empty.html.haml6
-rw-r--r--app/views/projects/issues/_discussion.html.haml23
-rw-r--r--app/views/projects/issues/_issue.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml32
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml17
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml2
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml4
-rw-r--r--app/views/projects/merge_requests/_show.html.haml9
-rw-r--r--app/views/projects/merge_requests/show/_how_to_merge.html.haml16
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml21
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml9
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml2
-rw-r--r--app/views/projects/milestones/_milestone.html.haml42
-rw-r--r--app/views/projects/new.html.haml5
-rw-r--r--app/views/projects/notes/_note.html.haml11
-rw-r--r--app/views/projects/wikis/_main_links.html.haml7
-rw-r--r--app/views/projects/wikis/_nav.html.haml11
-rw-r--r--app/views/projects/wikis/git_access.html.haml19
-rw-r--r--app/views/projects/wikis/history.html.haml7
-rw-r--r--app/views/projects/wikis/pages.html.haml7
-rw-r--r--app/views/projects/wikis/show.html.haml19
-rw-r--r--app/views/shared/issuable/_form.html.haml2
-rw-r--r--app/workers/repository_import_worker.rb35
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--config/initializers/rouge_diff_lexer.rb24
-rw-r--r--config/initializers/session_store.rb7
-rw-r--r--config/routes.rb11
-rw-r--r--db/migrate/20150902001023_add_template_to_label.rb5
-rw-r--r--db/schema.rb3
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/update/6.x-or-7.x-to-7.14.md6
-rw-r--r--doc/update/7.13-to-7.14.md2
-rw-r--r--doc/workflow/importing/README.md1
-rw-r--r--doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.pngbin0 -> 53276 bytes
-rw-r--r--doc/workflow/importing/fogbugz_importer/fogbugz_import_login.pngbin0 -> 44444 bytes
-rw-r--r--doc/workflow/importing/fogbugz_importer/fogbugz_import_select_fogbogz.pngbin0 -> 35415 bytes
-rw-r--r--doc/workflow/importing/fogbugz_importer/fogbugz_import_select_project.pngbin0 -> 62552 bytes
-rw-r--r--doc/workflow/importing/fogbugz_importer/fogbugz_import_user_map.pngbin0 -> 157856 bytes
-rw-r--r--doc/workflow/importing/import_projects_from_fogbugz.md29
-rw-r--r--docker/Dockerfile3
-rw-r--r--features/admin/labels.feature38
-rw-r--r--features/steps/admin/labels.rb117
-rw-r--r--features/steps/admin/users.rb6
-rw-r--r--lib/gitlab/fogbugz_import/client.rb56
-rw-r--r--lib/gitlab/fogbugz_import/importer.rb298
-rw-r--r--lib/gitlab/fogbugz_import/project_creator.rb38
-rw-r--r--lib/gitlab/fogbugz_import/repository.rb31
-rw-r--r--lib/gitlab/import_sources.rb1
-rw-r--r--lib/gitlab/markdown.rb2
-rw-r--r--lib/support/nginx/gitlab9
-rw-r--r--lib/support/nginx/gitlab-ssl9
-rw-r--r--spec/controllers/import/fogbugz_controller_spec.rb39
-rw-r--r--spec/services/projects/create_service_spec.rb8
-rw-r--r--spec/services/projects/download_service_spec.rb65
-rw-r--r--spec/spec_helper.rb1
-rw-r--r--spec/support/matchers/markdown_matchers.rb3
-rw-r--r--spec/support/relative_url.rb8
124 files changed, 1792 insertions, 482 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 247eb1e3643..36ee3e69ab3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.0.0 (unreleased)
- Fix URL construction for merge requests, issues, notes, and commits for relative URL config (Stan Hu)
+ - Fix emoji URLs in Markdown when relative_url_root is used (Stan Hu)
- Omit filename in Content-Disposition header in raw file download to avoid RFC 6266 encoding issues (Stan HU)
- Fix broken Wiki Page History (Stan Hu)
- Prevent anchors from being hidden by header (Stan Hu)
@@ -40,13 +41,19 @@ v 8.0.0 (unreleased)
- Add ability to get user information by ID of an SSH key via the API
- Fix bug which IE cannot show image at markdown when the image is raw file of gitlab
- Add support for Crowd
+ - Global Labels that are available to all projects
+ - Fix highlighting of deleted lines in diffs.
v 7.14.1
- Improve abuse reports management from admin area
- Fix "Reload with full diff" URL button in compare branch view (Stan Hu)
+ - Disabled DNS lookups for SSH in docker image (Rowan Wookey)
+
+v 7.14.1 (unreleased)
- Only include base URL in OmniAuth full_host parameter (Stan Hu)
- Fix Error 500 in API when accessing a group that has an avatar (Stan Hu)
- Ability to enable SSL verification for Webhooks
+ - Add FogBugz project import (Jared Szechy)
v 7.14.0
- Fix bug where non-project members of the target project could set labels on new merge requests.
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 2714f5313ae..57cf282ebbc 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-2.6.4
+2.6.5
diff --git a/Gemfile b/Gemfile
index cca8fc38e57..d609ff6610f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -157,6 +157,9 @@ gem "slack-notifier", "~> 1.0.0"
# Asana integration
gem 'asana', '~> 0.0.6'
+# FogBugz integration
+gem 'ruby-fogbugz'
+
# d3
gem 'd3_rails', '~> 3.5.5'
@@ -259,6 +262,7 @@ group :test do
gem 'email_spec', '~> 1.6.0'
gem 'webmock', '~> 1.21.0'
gem 'test_after_commit'
+ gem 'sham_rack'
end
group :production do
diff --git a/Gemfile.lock b/Gemfile.lock
index 2df318f3382..dce7e4964a6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -575,6 +575,8 @@ GEM
powerpack (~> 0.0.6)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.4)
+ ruby-fogbugz (0.1.1)
+ crack
ruby-progressbar (1.7.1)
ruby-saml (1.0.0)
nokogiri (>= 1.5.10)
@@ -609,6 +611,8 @@ GEM
thor (~> 0.14)
settingslogic (2.0.9)
sexp_processor (4.4.5)
+ sham_rack (1.3.6)
+ rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (3.3.0)
@@ -845,12 +849,14 @@ DEPENDENCIES
rqrcode-rails3
rspec-rails (~> 3.3.0)
rubocop (= 0.28.0)
+ ruby-fogbugz
sanitize (~> 2.0)
sass-rails (~> 4.0.5)
sdoc
seed-fu
select2-rails (~> 3.5.9)
settingslogic
+ sham_rack
shoulda-matchers (~> 2.8.0)
sidekiq (~> 3.3)
sidetiq (= 0.6.3)
diff --git a/app/assets/stylesheets/base/gl_bootstrap.scss b/app/assets/stylesheets/base/gl_bootstrap.scss
index 21acbfa5e5a..eb8d23d6453 100644
--- a/app/assets/stylesheets/base/gl_bootstrap.scss
+++ b/app/assets/stylesheets/base/gl_bootstrap.scss
@@ -85,14 +85,14 @@
// Labels
.label {
padding: 2px 4px;
- font-size: 12px;
+ font-size: 13px;
font-style: normal;
font-weight: normal;
display: inline-block;
&.label-gray {
- background-color: #eee;
- color: #999;
+ background-color: #f8fafc;
+ color: $gl-gray;
text-shadow: none;
}
@@ -156,10 +156,16 @@
* Add some extra stuff to panels
*
*/
+
+.container-blank .panel .panel-heading {
+ font-size: 17px;
+ line-height: 38px;
+}
+
.panel {
- .panel-heading {
- font-weight: bold;
+ box-shadow: none;
+ .panel-heading {
.panel-head-actions {
position: relative;
top: -5px;
@@ -182,6 +188,10 @@
.pagination {
margin: 0;
}
+
+ .btn {
+ min-width: 124px;
+ }
}
&.panel-small {
@@ -209,6 +219,12 @@
}
}
+.alert-help {
+ background-color: $background-color;
+ border: 1px solid $border-color;
+ color: $gl-gray;
+}
+
// Typography =================================================================
.text-primary,
diff --git a/app/assets/stylesheets/base/gl_variables.scss b/app/assets/stylesheets/base/gl_variables.scss
index 6d3b4b1e73a..bfef5f78f83 100644
--- a/app/assets/stylesheets/base/gl_variables.scss
+++ b/app/assets/stylesheets/base/gl_variables.scss
@@ -22,6 +22,10 @@ $brand-info: $gl-info;
$brand-warning: $gl-warning;
$brand-danger: $gl-danger;
+$border-radius-base: 3px !default;
+$border-radius-large: 5px !default;
+$border-radius-small: 2px !default;
+
//== Scaffolding
//
@@ -110,11 +114,12 @@ $alert-border-radius: 0;
//
//##
-$panel-border-radius: 0;
-$panel-default-text: $text-color;
-$panel-default-border: #E7E9ED;
-$panel-default-heading-bg: #F8FAFC;
-
+$panel-border-radius: 2px;
+$panel-default-text: $text-color;
+$panel-default-border: $border-color;
+$panel-default-heading-bg: $background-color;
+$panel-footer-bg: $background-color;
+$panel-inner-border: $border-color;
//== Wells
//
@@ -144,3 +149,10 @@ $btn-default-border: #e7e9ed;
//
//##
$nav-link-padding: 13px $gl-padding;
+
+//== Code
+//
+//##
+$pre-bg: #f8fafc !default;
+$pre-color: $gl-gray !default;
+$pre-border-color: #e7e9ed;
diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss
index 8698109bc4c..0f661d6b1b6 100644
--- a/app/assets/stylesheets/base/mixins.scss
+++ b/app/assets/stylesheets/base/mixins.scss
@@ -177,7 +177,7 @@
margin: 0px;
&:last-child {
- border:none
+ border-bottom: none;
}
&.active {
@@ -215,3 +215,39 @@
font-size: 16px;
line-height: 24px;
}
+
+@mixin nav-menu {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ margin-top: 5px;
+ height: 56px;
+
+ li {
+ display: inline-block;
+
+ a {
+ padding: 14px;
+ font-size: 17px;
+ line-height: 28px;
+ color: #7f8fa4;
+ border-bottom: 2px solid transparent;
+
+ &:hover, &:active, &:focus {
+ text-decoration: none;
+ }
+ }
+
+ &.active a {
+ color: #4c4e54;
+ border-bottom: 2px solid #1cacfc;
+ }
+
+ .badge {
+ font-weight: normal;
+ background-color: #fff;
+ background-color: #eee;
+ color: #78a;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/generic/blocks.scss b/app/assets/stylesheets/generic/blocks.scss
index abf4657dd58..48d9f890f62 100644
--- a/app/assets/stylesheets/generic/blocks.scss
+++ b/app/assets/stylesheets/generic/blocks.scss
@@ -27,6 +27,15 @@
border-bottom: 1px solid #e7e9ed;
color: $gl-gray;
+ &.top-block {
+ border-top: none;
+ }
+
+ &.middle-block {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+
&.second-block {
margin-top: -1px;
margin-bottom: 0;
diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss
index e8237509092..46ef595ddf0 100644
--- a/app/assets/stylesheets/generic/buttons.scss
+++ b/app/assets/stylesheets/generic/buttons.scss
@@ -10,7 +10,7 @@
}
&.btn-save {
- @extend .btn-primary;
+ @extend .btn-success;
}
&.btn-remove {
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index b995486588f..5e191d5dd4a 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -370,41 +370,11 @@ table {
}
.center-top-menu {
- padding: 0;
- margin: 0;
- list-style: none;
+ @include nav-menu;
text-align: center;
margin-top: 5px;
margin-bottom: $gl-padding;
height: 56px;
margin-top: -$gl-padding;
padding-top: $gl-padding;
-
- li {
- display: inline-block;
-
- a {
- padding: 14px;
- font-size: 17px;
- line-height: 28px;
- color: #7f8fa4;
- border-bottom: 2px solid transparent;
-
- &:hover, &:active, &:focus {
- text-decoration: none;
- }
- }
-
- &.active a {
- color: #4c4e54;
- border-bottom: 2px solid #1cacfc;
- }
-
- .badge {
- font-weight: normal;
- background-color: #fff;
- background-color: #eee;
- color: #78a;
- }
- }
}
diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss
index 869e586839b..b1fb87a6830 100644
--- a/app/assets/stylesheets/generic/issue_box.scss
+++ b/app/assets/stylesheets/generic/issue_box.scss
@@ -5,10 +5,13 @@
*/
.issue-box {
+ @include border-radius(3px);
+
display: inline-block;
- padding: 4px 13px;
+ padding: 10px $gl-padding;
font-weight: normal;
- margin-right: 5px;
+ margin-right: 10px;
+ font-size: $gl-font-size;
&.issue-box-closed {
background-color: $gl-danger;
@@ -21,7 +24,7 @@
}
&.issue-box-open {
- background-color: $gl-success;
+ background-color: #019875;
color: #FFF;
}
diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss
index a4fc82e90bf..ed0333d2336 100644
--- a/app/assets/stylesheets/generic/markdown_area.scss
+++ b/app/assets/stylesheets/generic/markdown_area.scss
@@ -65,8 +65,11 @@
position: relative;
}
-.md-header ul {
- float: left;
+.md-header {
+ ul {
+ float: left;
+ margin-bottom: 1px;
+ }
}
.referenced-users {
@@ -80,7 +83,7 @@
.md-preview-holder {
background: #FFF;
border: 1px solid #ddd;
- min-height: 100px;
+ min-height: 169px;
padding: 5px;
box-shadow: none;
}
@@ -105,7 +108,7 @@
.markdown-area {
background: #FFF;
border: 1px solid #ddd;
- min-height: 100px;
+ min-height: 140px;
padding: 5px;
box-shadow: none;
width: 100%;
diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss
index 22720c2e1d5..41ffd358576 100644
--- a/app/assets/stylesheets/generic/sidebar.scss
+++ b/app/assets/stylesheets/generic/sidebar.scss
@@ -28,12 +28,18 @@
padding: $gl-padding;
border: 1px solid #e7e9ed;
min-height: 90vh;
+
+ &.container-blank {
+ background: none;
+ padding: 0;
+ border: none;
+ }
}
}
.nav-sidebar {
margin-top: 14 + $header-height;
- margin-bottom: 50px;
+ margin-bottom: 100px;
transition-duration: .3s;
list-style: none;
overflow: hidden;
diff --git a/app/assets/stylesheets/generic/timeline.scss b/app/assets/stylesheets/generic/timeline.scss
index 97831eb7c27..74bbaabad39 100644
--- a/app/assets/stylesheets/generic/timeline.scss
+++ b/app/assets/stylesheets/generic/timeline.scss
@@ -1,119 +1,50 @@
.timeline {
- list-style: none;
- padding: 20px 0 20px;
- position: relative;
+ @include basic-list;
- &:before {
- top: 0;
- bottom: 0;
- position: absolute;
- content: " ";
- width: 3px;
- background-color: #eeeeee;
- margin-left: 29px;
- }
+ margin: 0;
+ padding: 0;
.timeline-entry {
- position: relative;
- margin-top: 5px;
- margin-left: 30px;
- margin-bottom: 10px;
- clear: both;
-
-
- &:target {
- .timeline-entry-inner .timeline-content {
- -webkit-animation:target-note 2s linear;
- background: $hover;
- }
+ padding: $gl-padding;
+ border-color: #f1f2f4;
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ color: $gl-gray;
+ border-bottom: 1px solid #f1f2f4;
+ border-right: 1px solid #f1f2f4;
+
+ &:last-child {
+ border-bottom: none;
}
- .timeline-entry-inner {
- position: relative;
- margin-left: -20px;
-
- &:before, &:after {
- content: " ";
- display: table;
- }
-
- .timeline-icon {
- margin-top: 2px;
- background: #fff;
- color: #737881;
- float: left;
- @include border-radius($avatar_radius);
- @include box-shadow(0 0 0 3px #EEE);
- overflow: hidden;
-
- .avatar {
- margin: 0;
- padding: 0;
- }
- }
-
- .timeline-content {
- position: relative;
- background: $background-color;
- padding: 10px 15px;
- margin-left: 60px;
-
- img {
- max-width: 100%;
- }
+ .avatar {
+ margin-right: 15px;
+ }
- &:after {
- content: '';
- display: block;
- position: absolute;
- width: 0;
- height: 0;
- border-style: solid;
- border-width: 9px 9px 9px 0;
- border-color: transparent $background-color transparent transparent;
- left: 0;
- top: 10px;
- margin-left: -9px;
- }
- }
+ .controls {
+ padding-top: 10px;
+ float: right;
}
}
- .system-note .timeline-entry-inner {
- .timeline-icon {
- background: none;
- margin-left: 12px;
- margin-top: 0;
- @include box-shadow(none);
-
- span {
- margin: 0 2px;
- font-size: 16px;
- color: #eeeeee;
- }
+ .note-text {
+ p:last-child {
+ margin-bottom: 0;
}
+ }
- .timeline-content {
- background: none;
- margin-left: 45px;
- padding: 0px 15px;
-
- &:after { border: 0; }
-
- .note-header {
- span { font-size: 12px; }
-
- .avatar {
- margin-right: 5px;
- }
- }
-
- .note-text {
- font-size: 12px;
- margin-left: 20px;
- }
+ .system-note {
+ .note-text {
+ color: $gl-gray !important;
}
}
+
+ .diff-file {
+ border: 1px solid $border-color;
+ border-bottom: none;
+ margin-left: 0;
+ margin-right: 0;
+ }
}
@media (max-width: $screen-xs-max) {
@@ -132,3 +63,8 @@
}
}
}
+
+.discussion .timeline-entry {
+ margin: 0;
+ border-right: none;
+}
diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss
index 7e86a0fe4b9..32e2c020e06 100644
--- a/app/assets/stylesheets/generic/zen.scss
+++ b/app/assets/stylesheets/generic/zen.scss
@@ -4,7 +4,7 @@
}
.zen-enter-link {
- color: #888;
+ color: $gl-gray;
position: absolute;
top: 0px;
right: 4px;
@@ -13,7 +13,7 @@
.zen-leave-link {
display: none;
- color: #888;
+ color: $gl-text-color;
position: absolute;
top: 10px;
right: 10px;
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 5436c6dad97..051ca3792c3 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -26,14 +26,6 @@
margin-top: 10px;
}
-.commit-stat-summary {
- color: #666;
- font-size: 14px;
- font-weight: normal;
- padding: 3px 0;
- margin-bottom: 10px;
-}
-
.commit-info-row {
margin-bottom: 10px;
.avatar {
@@ -47,11 +39,6 @@
}
.commit-box {
- margin: 10px 0;
- border-top: 1px solid #ddd;
- border-bottom: 1px solid #ddd;
- padding: 20px 0;
-
.commit-title {
margin: 0;
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 1557c243db5..487b600e31d 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1,6 +1,8 @@
.diff-file {
- border: 1px solid $border-color;
- margin-bottom: 1em;
+ margin-left: -16px;
+ margin-right: -16px;
+ border: none;
+ border-bottom: 1px solid #E7E9EE;
.diff-header {
position: relative;
@@ -45,7 +47,7 @@
overflow-y: hidden;
background: #FFF;
color: #333;
- font-size: $code_font_size;
+
.old {
span.idiff {
background-color: #f8cbcb;
@@ -82,7 +84,7 @@
border: none;
margin: 0px;
padding: 0px;
- td {
+ .line_holder td {
line-height: $code_line_height;
font-size: $code_font_size;
}
@@ -367,3 +369,7 @@
white-space: pre-wrap;
}
+.inline-parallel-buttons {
+ float: right;
+ margin-top: -5px;
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 2d32d82e107..b5c61f7f91d 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -25,8 +25,6 @@
}
.issuable-context-title {
- font-size: 14px;
- line-height: 1.4;
margin-bottom: 5px;
.avatar {
@@ -34,18 +32,12 @@
}
label {
- color: #666;
+ color: $gl-gray;
font-weight: normal;
margin-right: 4px;
}
}
-.issuable-affix .context {
- font-size: 13px;
-
- .btn { font-size: 13px; }
-}
-
.project-issuable-filter {
.controls {
float: right;
@@ -56,3 +48,34 @@
text-align: left;
}
}
+
+.issuable-details {
+ .page-title {
+ margin-top: -15px;
+ padding: 10px 0;
+ margin-bottom: 0;
+ color: $gl-gray;
+ font-size: 16px;
+
+ .author {
+ color: $gl-gray;
+ }
+
+ .issue-id {
+ font-size: 19px;
+ color: $gl-text-color;
+ }
+ }
+
+ .issue-title {
+ margin: 0;
+ }
+
+ .description {
+ margin-top: 6px;
+
+ p:last-child {
+ margin-bottom: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 7928b6220fc..4bf58cb4a59 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -1,6 +1,6 @@
.issues-list {
.issue {
- padding: 10px 15px;
+ padding: 10px $gl-padding;
position: relative;
.issue-title {
@@ -11,7 +11,6 @@
.issue-info {
color: $gl-gray;
- font-size: 13px;
}
.issue-check {
@@ -47,10 +46,6 @@
}
}
-.participants {
- margin-bottom: 20px;
-}
-
.issue-search-form {
margin: 0;
height: 24px;
@@ -137,11 +132,6 @@ form.edit-issue {
}
}
-h2.issue-title {
- margin-top: 0;
- font-weight: bold;
-}
-
.issue-form .select2-container {
width: 250px !important;
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index f0fb68d3422..d8c8e5ad0a4 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -3,10 +3,10 @@
*
*/
.mr-state-widget {
- background: #FAFAFA;
+ background: #f8fafc;
margin-bottom: 20px;
- color: #666;
- border: 1px solid #e5e5e5;
+ color: $gl-gray;
+ border: 1px solid #eef0f2;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
@include border-radius(3px);
@@ -29,6 +29,14 @@
padding: 5px;
line-height: 20px;
+ &.right {
+ float: right;
+ padding-top: 12px;
+ a {
+ color: $gl-gray;
+ }
+ }
+
.remove_source_checkbox {
margin: 0;
}
@@ -36,7 +44,7 @@
}
.ci_widget {
- border-bottom: 1px solid #EEE;
+ border-bottom: 1px solid #eef0f2;
i {
margin-right: 4px;
@@ -89,20 +97,14 @@
}
}
-@media(min-width: $screen-sm-max) {
- .merge-request .merge-request-tabs{
- li {
- a {
- padding: 15px 40px;
- font-size: 14px;
- }
- }
- }
-}
-
.merge-request .merge-request-tabs{
- margin-top: 30px;
- margin-bottom: 20px;
+ @include nav-menu;
+ margin: -$gl-padding;
+ padding: $gl-padding;
+ text-align: center;
+ border-top: 1px solid #e7e9ed;
+ margin-top: 18px;
+ margin-bottom: 3px;
}
.mr_source_commit,
@@ -137,7 +139,6 @@
.merge-request-info {
color: $gl-gray;
- font-size: 13px;
}
}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 15e3948e402..e80dc9e84a1 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -6,4 +6,8 @@ li.milestone {
h4 {
font-weight: bold;
}
+
+ .progress {
+ height: 6px;
+ }
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 203f9374cee..b311d26d675 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -72,9 +72,13 @@
.common-note-form {
margin: 0;
- background: #F9F9F9;
- padding: 5px;
- border: 1px solid #DDD;
+ background: #f8fafc;
+ padding: $gl-padding;
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ border-right: 1px solid #f1f2f4;
+ border-top: 1px solid #f1f2f4;
+ margin-bottom: -$gl-padding;
}
.note-form-actions {
@@ -142,9 +146,9 @@
}
.discussion-reply-holder {
- background: #f9f9f9;
+ background: $background-color;
padding: 10px 15px;
- border-top: 1px solid #DDD;
+ border-top: 1px solid $border-color;
}
}
@@ -166,6 +170,6 @@
background: #FFF;
padding: 5px;
margin-top: -11px;
- border: 1px solid #DDD;
+ border: 1px solid $border-color;
font-size: 13px;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 85c828ec1ad..2544356a5f6 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -14,6 +14,19 @@ ul.notes {
margin: 0px;
padding: 0px;
+ .system-note {
+ font-size: 14px;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ background: #f8fafc;
+
+ .timeline-icon {
+ .avatar {
+ visibility: hidden;
+ }
+ }
+ }
+
.discussion-header,
.note-header {
@extend .cgray;
@@ -34,10 +47,8 @@ ul.notes {
content: "\00b7";
}
- font-size: 13px;
-
a {
- @extend .cgray;
+ color: $gl-gray;
&:hover {
text-decoration: underline;
@@ -45,8 +56,9 @@ ul.notes {
}
}
.author {
- color: #333;
- font-weight: bold;
+ color: #4c4e54;
+ margin-right: 3px;
+
&:hover {
color: $gl-link-color;
}
@@ -59,7 +71,7 @@ ul.notes {
margin-top: 1px;
border: 1px solid #bbb;
background-color: transparent;
- color: #999;
+ color: $gl-gray;
}
}
@@ -133,8 +145,6 @@ ul.notes {
}
.diff-file .notes_holder {
- font-size: 13px;
- line-height: 18px;
font-family: $regular_font;
td {
@@ -176,8 +186,7 @@ ul.notes {
a {
margin-left: 5px;
-
- color: #999;
+ color: $gl-gray;
i.fa {
font-size: 16px;
diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb
new file mode 100644
index 00000000000..3b070e65d0d
--- /dev/null
+++ b/app/controllers/admin/labels_controller.rb
@@ -0,0 +1,58 @@
+class Admin::LabelsController < Admin::ApplicationController
+ before_action :set_label, only: [:show, :edit, :update, :destroy]
+
+ def index
+ @labels = Label.templates.page(params[:page]).per(PER_PAGE)
+ end
+
+ def show
+ end
+
+ def new
+ @label = Label.new
+ end
+
+ def edit
+ end
+
+ def create
+ @label = Label.new(label_params)
+ @label.template = true
+
+ if @label.save
+ redirect_to admin_labels_url, notice: "Label was created"
+ else
+ render :new
+ end
+ end
+
+ def update
+ if @label.update(label_params)
+ redirect_to admin_labels_path, notice: 'label was successfully updated.'
+ else
+ render :edit
+ end
+ end
+
+ def destroy
+ @label.destroy
+ @labels = Label.templates
+
+ respond_to do |format|
+ format.html do
+ redirect_to(admin_labels_path, notice: 'Label was removed')
+ end
+ format.js
+ end
+ end
+
+ private
+
+ def set_label
+ @label = Label.find(params[:id])
+ end
+
+ def label_params
+ params[:label].permit(:title, :color)
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index cb1cf13d34d..4c112534ae6 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,4 +1,5 @@
require 'gon'
+require 'fogbugz'
class ApplicationController < ActionController::Base
include Gitlab::CurrentSettings
@@ -20,7 +21,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :abilities, :can?, :current_application_settings
- helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :git_import_enabled?
+ helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
@@ -337,6 +338,10 @@ class ApplicationController < ActionController::Base
current_application_settings.import_sources.include?('google_code')
end
+ def fogbugz_import_enabled?
+ current_application_settings.import_sources.include?('fogbugz')
+ end
+
def git_import_enabled?
current_application_settings.import_sources.include?('git')
end
diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb
new file mode 100644
index 00000000000..bda534fb4de
--- /dev/null
+++ b/app/controllers/import/fogbugz_controller.rb
@@ -0,0 +1,106 @@
+class Import::FogbugzController < Import::BaseController
+ before_action :verify_fogbugz_import_enabled
+ before_action :user_map, only: [:new_user_map, :create_user_map]
+
+ # Doesn't work yet due to bug in ruby-fogbugz, see below
+ rescue_from Fogbugz::AuthenticationException, with: :fogbugz_unauthorized
+
+ def new
+
+ end
+
+ def callback
+ begin
+ res = Gitlab::FogbugzImport::Client.new(import_params.symbolize_keys)
+ rescue
+ # Needed until https://github.com/firmafon/ruby-fogbugz/pull/9 is merged
+ return redirect_to :back, alert: 'Could not authenticate with FogBugz, check your URL, email, and password'
+ end
+ session[:fogbugz_token] = res.get_token
+ session[:fogbugz_uri] = params[:uri]
+
+ redirect_to new_user_map_import_fogbugz_path
+ end
+
+ def new_user_map
+
+ end
+
+ def create_user_map
+ user_map = params[:users]
+
+ unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? }
+ flash.now[:alert] = 'All users must have a name.'
+
+ render 'new_user_map' and return
+ end
+
+ session[:fogbugz_user_map] = user_map
+
+ flash[:notice] = 'The user map has been saved. Continue by selecting the projects you want to import.'
+
+ redirect_to status_import_fogbugz_path
+ end
+
+ def status
+ unless client.valid?
+ return redirect_to new_import_fogbugz_path
+ end
+
+ @repos = client.repos
+
+ @already_added_projects = current_user.created_projects.where(import_type: 'fogbugz')
+ already_added_projects_names = @already_added_projects.pluck(:import_source)
+
+ @repos.reject! { |repo| already_added_projects_names.include? repo.name }
+ end
+
+ def jobs
+ jobs = current_user.created_projects.where(import_type: 'fogbugz').to_json(only: [:id, :import_status])
+ render json: jobs
+ end
+
+ def create
+ @repo_id = params[:repo_id]
+ repo = client.repo(@repo_id)
+ fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] }
+ @target_namespace = current_user.namespace
+ @project_name = repo.name
+
+ namespace = @target_namespace
+
+ umap = session[:fogbugz_user_map] || client.user_map
+
+ @project = Gitlab::FogbugzImport::ProjectCreator.new(repo, fb_session, namespace, current_user, umap).execute
+ end
+
+ private
+
+ def client
+ @client ||= Gitlab::FogbugzImport::Client.new(token: session[:fogbugz_token], uri: session[:fogbugz_uri])
+ end
+
+ def user_map
+ @user_map ||= begin
+ user_map = client.user_map
+
+ stored_user_map = session[:fogbugz_user_map]
+ user_map.update(stored_user_map) if stored_user_map
+
+ user_map
+ end
+ end
+
+ def fogbugz_unauthorized(exception)
+ flash[:alert] = exception.message
+ redirect_to new_import_fogbugz_path
+ end
+
+ def import_params
+ params.permit(:uri, :email, :password)
+ end
+
+ def verify_fogbugz_import_enabled
+ not_found! unless fogbugz_import_enabled?
+ end
+end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 8473d6d75d0..df37be51ce9 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -31,4 +31,26 @@ module PageLayoutHelper
@fluid_layout
end
end
+
+ def blank_container(enabled = false)
+ if @blank_container.nil?
+ @blank_container = enabled
+ else
+ @blank_container
+ end
+ end
+
+ def container_class
+ css_class = "container-fluid"
+
+ unless fluid_layout
+ css_class += " container-limited"
+ end
+
+ if blank_container
+ css_class += " container-blank"
+ end
+
+ css_class
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 8f27e35d723..c8841178e93 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -83,7 +83,7 @@ class ApplicationSetting < ActiveRecord::Base
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
- import_sources: ['github','bitbucket','gitlab','gitorious','google_code','git']
+ import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git']
)
end
diff --git a/app/models/label.rb b/app/models/label.rb
index 230631b5180..4a22bd53400 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -24,7 +24,7 @@ class Label < ActiveRecord::Base
validates :color,
format: { with: /\A#[0-9A-Fa-f]{6}\Z/ },
allow_blank: false
- validates :project, presence: true
+ validates :project, presence: true, unless: Proc.new { |service| service.template? }
# Don't allow '?', '&', and ',' for label titles
validates :title,
@@ -34,6 +34,8 @@ class Label < ActiveRecord::Base
default_scope { order(title: :asc) }
+ scope :templates, -> { where(template: true) }
+
alias_attribute :name, :title
def self.reference_prefix
@@ -78,4 +80,8 @@ class Label < ActiveRecord::Base
def open_issues_count
issues.opened.count
end
+
+ def template?
+ template
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 8e33a4b2f0f..072d7d73f41 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -43,6 +43,8 @@ class Project < ActiveRecord::Base
extend Gitlab::ConfigHelper
extend Enumerize
+ UNKNOWN_IMPORT_URL = 'http://unknown.git'
+
default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :issues_enabled, gitlab_config_features.issues
@@ -401,6 +403,15 @@ class Project < ActiveRecord::Base
end
end
+ def create_labels
+ Label.templates.each do |label|
+ label = label.dup
+ label.template = nil
+ label.project_id = self.id
+ label.save
+ end
+ end
+
def find_service(list, name)
list.find { |service| service.to_param == name }
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index b35aed005da..1bb2462565a 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -87,6 +87,8 @@ module Projects
@project.build_missing_services
+ @project.create_labels
+
event_service.create_project(@project, current_user)
system_hook_service.execute_hooks_for(@project, :create)
diff --git a/app/services/projects/download_service.rb b/app/services/projects/download_service.rb
new file mode 100644
index 00000000000..99f22293d0d
--- /dev/null
+++ b/app/services/projects/download_service.rb
@@ -0,0 +1,43 @@
+module Projects
+ class DownloadService < BaseService
+
+ WHITELIST = [
+ /^[^.]+\.fogbugz.com$/
+ ]
+
+ def initialize(project, url)
+ @project, @url = project, url
+ end
+
+ def execute
+ return nil unless valid_url?(@url)
+
+ uploader = FileUploader.new(@project)
+ uploader.download!(@url)
+ uploader.store!
+
+ filename = uploader.image? ? uploader.file.basename : uploader.file.filename
+
+ {
+ 'alt' => filename,
+ 'url' => uploader.secure_url,
+ 'is_image' => uploader.image?
+ }
+ end
+
+ private
+
+ def valid_url?(url)
+ url && http?(url) && valid_domain?(url)
+ end
+
+ def http?(url)
+ url =~ /\A#{URI::regexp(['http', 'https'])}\z/
+ end
+
+ def valid_domain?(url)
+ host = URI.parse(url).host
+ WHITELIST.any? { |entry| entry === host }
+ end
+ end
+end
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
new file mode 100644
index 00000000000..ad58a3837f6
--- /dev/null
+++ b/app/views/admin/labels/_form.html.haml
@@ -0,0 +1,35 @@
+= form_for [:admin, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f|
+ -if @label.errors.any?
+ .row
+ .col-sm-offset-2.col-sm-10
+ .alert.alert-danger
+ - @label.errors.full_messages.each do |msg|
+ %span= msg
+ %br
+
+ .form-group
+ = f.label :title, class: 'control-label'
+ .col-sm-10
+ = f.text_field :title, class: "form-control", required: true
+ .form-group
+ = f.label :color, "Background Color", class: 'control-label'
+ .col-sm-10
+ .input-group
+ .input-group-addon.label-color-preview &nbsp;
+ = f.color_field :color, class: "form-control"
+ .help-block
+ Choose any color.
+ %br
+ Or you can choose one of suggested colors below
+
+ .suggest-colors
+ - suggested_colors.each do |color|
+ = link_to '#', style: "background-color: #{color}", data: { color: color } do
+ &nbsp;
+
+ .form-actions
+ = f.submit 'Save', class: 'btn btn-save js-save-button'
+ = link_to "Cancel", admin_labels_path, class: 'btn btn-cancel'
+
+:coffeescript
+ new Labels
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
new file mode 100644
index 00000000000..596e06243dd
--- /dev/null
+++ b/app/views/admin/labels/_label.html.haml
@@ -0,0 +1,5 @@
+%li{id: dom_id(label)}
+ = render_colored_label(label)
+ .pull-right
+ = link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm'
+ = link_to 'Remove', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
diff --git a/app/views/admin/labels/destroy.js.haml b/app/views/admin/labels/destroy.js.haml
new file mode 100644
index 00000000000..9d51762890f
--- /dev/null
+++ b/app/views/admin/labels/destroy.js.haml
@@ -0,0 +1,2 @@
+- if @labels.size == 0
+ $('.labels').load(document.URL + ' .light-well').hide().fadeIn(1000)
diff --git a/app/views/admin/labels/edit.html.haml b/app/views/admin/labels/edit.html.haml
new file mode 100644
index 00000000000..45c62a76259
--- /dev/null
+++ b/app/views/admin/labels/edit.html.haml
@@ -0,0 +1,9 @@
+- page_title "Edit", @label.name, "Labels"
+%h3
+ Edit label
+ %span.light #{@label.name}
+.back-link
+ = link_to admin_labels_path do
+ &larr; To labels list
+%hr
+= render 'form'
diff --git a/app/views/admin/labels/index.html.haml b/app/views/admin/labels/index.html.haml
new file mode 100644
index 00000000000..8b11c28c56e
--- /dev/null
+++ b/app/views/admin/labels/index.html.haml
@@ -0,0 +1,16 @@
+- page_title "Labels"
+= link_to new_admin_label_path, class: "pull-right btn btn-new" do
+ New label
+%h3.page-title
+ Labels
+%hr
+
+.labels
+ - if @labels.present?
+ %ul.bordered-list.manage-labels-list
+ = render @labels
+ = paginate @labels, theme: 'gitlab'
+ - else
+ .light-well
+ .nothing-here-block There are no any labels yet
+ \ No newline at end of file
diff --git a/app/views/admin/labels/new.html.haml b/app/views/admin/labels/new.html.haml
new file mode 100644
index 00000000000..8d298ad20f7
--- /dev/null
+++ b/app/views/admin/labels/new.html.haml
@@ -0,0 +1,7 @@
+- page_title "New Label"
+%h3 New label
+.back-link
+ = link_to admin_labels_path do
+ &larr; To labels list
+%hr
+= render 'form'
diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml
index d6f3e029a38..55080d6b3fe 100644
--- a/app/views/dashboard/milestones/_milestone.html.haml
+++ b/app/views/dashboard/milestones/_milestone.html.haml
@@ -1,20 +1,22 @@
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
- %h4
- = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
+ .row
+ .col-sm-6
+ %strong
+ = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
+ .col-sm-6
+ .pull-right.light #{milestone.percent_complete}% complete
.row
.col-sm-6
= link_to issues_dashboard_path(milestone_title: milestone.title) do
= pluralize milestone.issue_count, 'Issue'
- &nbsp;
+ &middot;
= link_to merge_requests_dashboard_path(milestone_title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
- &nbsp;
- %span.light #{milestone.percent_complete}% complete
-
.col-sm-6
= milestone_progress_bar(milestone)
- %div
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = milestone.project.name_with_namespace
+ .row
+ .col-sm-6
+ - milestone.milestones.each do |milestone|
+ = link_to milestone_path(milestone) do
+ %span.label.label-gray
+ = milestone.project.name_with_namespace
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 2ff4b7e23ea..ac7d9ba0f4f 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,3 +1,4 @@
+- @blank_container = true
.panel.panel-default
.panel-heading
%strong= @group.name
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index dba395cc8fa..3a6d07ebddf 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,15 +1,13 @@
- page_title "Members"
+- header_title group_title(@group, "Members", group_group_members_path(@group))
- show_roles = should_user_see_group_roles?(current_user, @group)
-%h3.page-title
- Group members
- if show_roles
%p.light
Members of group have access to all group projects.
Read more about permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
-%hr
.clearfix.js-toggle-container
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml
index ba30e6e07c6..41dffdd2fb8 100644
--- a/app/views/groups/milestones/_milestone.html.haml
+++ b/app/views/groups/milestones/_milestone.html.haml
@@ -1,25 +1,29 @@
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
- .pull-right
- - if can?(current_user, :admin_group, @group)
- - if milestone.closed?
- = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
- - else
- = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
- %h4
- = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
+ .row
+ .col-sm-6
+ %strong
+ = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
+ .col-sm-6
+ .pull-right.light #{milestone.percent_complete}% complete
.row
.col-sm-6
= link_to issues_group_path(@group, milestone_title: milestone.title) do
= pluralize milestone.issue_count, 'Issue'
- &nbsp;
+ &middot;
= link_to merge_requests_group_path(@group, milestone_title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
- &nbsp;
- %span.light #{milestone.percent_complete}% complete
.col-sm-6
= milestone_progress_bar(milestone)
- %div
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = milestone.project.name
+ .row
+ .col-sm-6
+ %div
+ - milestone.milestones.each do |milestone|
+ = link_to milestone_path(milestone) do
+ %span.label.label-gray
+ = milestone.project.name
+ .col-sm-6
+ - if can?(current_user, :admin_group, @group)
+ - if milestone.closed?
+ = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
+ - else
+ = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
diff --git a/app/views/import/fogbugz/new.html.haml b/app/views/import/fogbugz/new.html.haml
new file mode 100644
index 00000000000..e1bb88ca4ed
--- /dev/null
+++ b/app/views/import/fogbugz/new.html.haml
@@ -0,0 +1,25 @@
+- page_title "FogBugz Import"
+%h3.page-title
+ %i.fa.fa-bug
+ Import projects from FogBugz
+%hr
+
+= form_tag callback_import_fogbugz_path, class: 'form-horizontal' do
+ %p
+ To get started you enter your FogBugz URL and login information below.
+ In the next steps, you'll be able to map users and select the projects
+ you want to import.
+ .form-group
+ = label_tag :uri, 'FogBugz URL', class: 'control-label'
+ .col-sm-4
+ = text_field_tag :uri, nil, placeholder: 'https://mycompany.fogbugz.com', class: 'form-control'
+ .form-group
+ = label_tag :email, 'FogBugz Email', class: 'control-label'
+ .col-sm-4
+ = text_field_tag :email, nil, class: 'form-control'
+ .form-group
+ = label_tag :password, 'FogBugz Password', class: 'control-label'
+ .col-sm-4
+ = password_field_tag :password, nil, class: 'form-control'
+ .form-actions
+ = submit_tag 'Continue to the next step', class: 'btn btn-create'
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
new file mode 100644
index 00000000000..25cebfb3665
--- /dev/null
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -0,0 +1,49 @@
+- page_title 'User map', 'FogBugz import'
+%h3.page-title
+ %i.fa.fa-bug
+ Import projects from FogBugz
+%hr
+
+= form_tag create_user_map_import_fogbugz_path, class: 'form-horizontal' do
+ %p
+ Customize how FogBugz email addresses and usernames are imported into GitLab.
+ In the next step, you'll be able to select the projects you want to import.
+ %p
+ The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames wil be imported into GitLab. You can change this by populating the table below.
+ %ul
+ %li
+ %strong Default: Map a FogBugz account ID to a full name
+ %p
+ An empty GitLab User field will add the FogBugz user's full name
+ (e.g. "By John Smith") in the description of all issues and comments.
+ It will also associate and/or assign these issues and comments with
+ the project creator.
+ %li
+ %strong Map a FogBugz account ID to a GitLab user
+ %p
+ Selecting a GitLab user will add a link to the GitLab user in the descriptions
+ of issues and comments (e.g. "By <a href="#">@johnsmith</a>"). It will also
+ associate and/or assign these issues and comments with the selected user.
+
+ %table.table
+ %thead
+ %tr
+ %th ID
+ %th Name
+ %th Email
+ %th GitLab User
+ %tbody
+ - @user_map.each do |id, user|
+ %tr
+ %td= id
+ %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control'
+ %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control'
+ %td
+ = users_select_tag("users[#{id}][gitlab_user]", class: 'custom-form-control',
+ scope: :all, email_user: true, selected: user[:gitlab_user])
+
+ .form-actions
+ = submit_tag 'Continue to the next step', class: 'btn btn-create'
+
+:coffeescript
+ new UsersSelect()
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
new file mode 100644
index 00000000000..f179ece402d
--- /dev/null
+++ b/app/views/import/fogbugz/status.html.haml
@@ -0,0 +1,51 @@
+- page_title "FogBugz import"
+%h3.page-title
+ %i.fa.fa-bug
+ Import projects from FogBugz
+
+- if @repos.any?
+ %p.light
+ Select projects you want to import.
+ %p.light
+ Optionally, you can
+ = link_to 'customize', new_user_map_import_fogbugz_path
+ how FogBugz email addresses and usernames are imported into GitLab.
+ %hr
+ %p
+ = button_tag 'Import all projects', class: 'btn btn-success js-import-all'
+
+%table.table.import-jobs
+ %thead
+ %tr
+ %th From FogBugz
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = project.import_source
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
+
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = repo.name
+ %td.import-target
+ = "#{current_user.username}/#{repo.name}"
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+
+:coffeescript
+ new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}")
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 397649dacf8..c3b137e3ddf 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -3,6 +3,7 @@
%meta{charset: "utf-8"}
%meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'}
%meta{content: "GitLab Community Edition", name: "description"}
+ %meta{name: 'referrer', content: 'origin'}
%title= page_title
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 68e5da571e6..2468687b56d 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -23,7 +23,7 @@
= current_user.username
.content-wrapper
= render "layouts/flash"
- %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
+ %div{ class: container_class }
.content
.clearfix
= yield
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 2065be3828a..3fe0127041e 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -57,6 +57,12 @@
%span
Service Templates
+ = nav_link(controller: :labels) do
+ = link_to admin_labels_path, title: 'Labels', data: {placement: 'right'} do
+ = icon('tags fw')
+ %span
+ Labels
+
= nav_link(controller: :abuse_reports) do
= link_to admin_abuse_reports_path, title: "Abuse reports" do
= icon('exclamation-circle fw')
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 68f816b86af..c3b07200621 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -36,13 +36,13 @@
= icon('clipboard fw')
%span
Snippets
- = nav_link(controller: :profile) do
- = link_to profile_path, title: 'Profile settings', data: {placement: 'bottom'} do
- = icon('user fw')
- %span
- Profile Settings
= nav_link(controller: :help) do
= link_to help_path, title: 'Help', data: {placement: 'right'} do
= icon('question-circle fw')
%span
Help
+ = nav_link(controller: :profile) do
+ = link_to profile_path, title: 'Profile settings', data: {placement: 'bottom'} do
+ = icon('user fw')
+ %span
+ Profile Settings
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 729a584457b..5a47b8e6db2 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -11,7 +11,7 @@
= link_to profile_path, title: 'Profile', data: {placement: 'right'} do
= icon('user fw')
%span
- Profile
+ Profile Settings
= nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account', data: {placement: 'right'} do
= icon('gear fw')
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index 77d2ccbf762..b80ce0dfc75 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,5 +1,6 @@
- page_title "Profile Settings"
-- header_title "Profile Settings", profile_path
+- unless @header_title
+ - header_title "Profile Settings", profile_path
- sidebar "profile"
= render template: "layouts/application"
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 767fe2e0e9a..cd7b1b0fe03 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,9 +1,7 @@
- page_title "Account"
-%h3.page-title
- = page_title
-%p.light
- Change your username and basic account settings.
-%hr
+- header_title page_title, profile_account_path
+- @blank_container = true
+
- if current_user.ldap_user?
.alert.alert-info
Some options are unavailable for LDAP accounts
@@ -69,7 +67,7 @@
- button_based_providers.each do |provider|
.btn-group
= link_to provider_image_tag(provider), user_omniauth_authorize_path(provider), method: :post, class: "btn btn-lg #{'active' if auth_active?(provider)}", "data-no-turbolink" => "true"
-
+
- if auth_active?(provider)
= link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do
= icon('close')
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
index 3a3e6e1b1c4..2342936a5d5 100644
--- a/app/views/profiles/applications.html.haml
+++ b/app/views/profiles/applications.html.haml
@@ -1,13 +1,12 @@
- page_title "Applications"
-%h3.page-title
- = page_title
-%p.light
+- header_title page_title, applications_profile_path
+
+.gray-content-block.top-block
- if user_oauth_applications?
- Manage applications that can use GitLab as an OAuth provider,
+ Manage applications that can use GitLab as an OAuth provider,
and applications that you've authorized to use your account.
- else
Manage applications that you've authorized to use your account.
-%hr
- if user_oauth_applications?
.oauth-applications
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index 698d6037428..8fdba45b193 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,5 +1,8 @@
- page_title "Audit Log"
-%h3.page-title Audit Log
-%p.light History of authentications
+- header_title page_title, audit_log_profile_path
-= render 'event_table', events: @events \ No newline at end of file
+.gray-content-block.top-block
+ History of authentications
+
+.prepend-top-default
+= render 'event_table', events: @events
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 66812872c41..1d140347a5f 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,12 +1,10 @@
- page_title "Emails"
-%h3.page-title
- = page_title
-%p.light
- Control emails linked to your account
-%hr
+- header_title page_title, profile_emails_path
+.gray-content-block.top-block
+ Control emails linked to your account
-%ul
+%ul.prepend-top-default
%li
Your
%b Primary Email
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 06655f7ba3a..14adba1c797 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,11 +1,12 @@
- page_title "SSH Keys"
-%h3.page-title
- = page_title
+- header_title page_title, profile_keys_path
+
+.gray-content-block.top-block
.pull-right
= link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
-%p.light
- Before you can add an SSH key you need to
- = link_to "generate it.", help_page_path("ssh", "README")
-%hr
+ .oneline
+ Before you can add an SSH key you need to
+ = link_to "generate it.", help_page_path("ssh", "README")
+.prepend-top-default
= render 'key_table'
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index db7fa2eabe3..ea4e5f3e182 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,10 +1,10 @@
- page_title "Notifications"
-%h3.page-title
- = page_title
-%p.light
+- header_title page_title, profile_notifications_path
+
+.gray-content-block.top-block
These are your global notification settings.
-%hr
+.prepend-top-default
= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications form-horizontal global-notifications-form' } do |f|
-if @user.errors.any?
%div.alert.alert-danger
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 399ae98adf9..fab7c45c9b2 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,13 +1,13 @@
- page_title "Password"
-%h3.page-title
- = page_title
-%p.light
+- header_title page_title, edit_profile_password_path
+
+.gray-content-block.top-block
- if @user.password_automatically_set?
Set your password.
- else
Change your password or recover your current one.
-%hr
-.update-password
+
+.update-password.prepend-top-default
= form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f|
%div
%p.slead
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index 9c6204963e0..d165f758c81 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -12,7 +12,7 @@
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
-
+
- unless @user.password_automatically_set?
.form-group
= f.label :current_password, class: 'control-label'
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index aa0361a0a1b..60289bfe7cd 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,11 +1,11 @@
- page_title 'Preferences'
-%h3.page-title
- = page_title
-%p.light
+- header_title page_title, profile_preferences_path
+- @blank_container = true
+
+.alert.alert-help
These settings allow you to customize the appearance and behavior of the site.
They are saved with your account and will persist to any device you use to
access the site.
-%hr
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'js-preferences-form form-horizontal'} do |f|
.panel.panel-default.application-theme
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index c519e52e596..47412e2ef0c 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,14 +1,9 @@
-- page_title "Profile"
-%h3.page-title
- = page_title
-%p.light
+.gray-content-block.top-block
This information will appear on your profile.
- if current_user.ldap_user?
Some options are unavailable for LDAP accounts
-%hr
-
-
+.prepend-top-default
= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" }, authenticity_token: true do |f|
-if @user.errors.any?
%div.alert.alert-danger
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index b7bca6dae09..507757f6a2b 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -1,6 +1,6 @@
.md-area
.md-header.clearfix
- %ul.nav.nav-tabs
+ %ul.center-top-menu
%li.active
= link_to '#md-write-holder', class: 'js-md-write-button', tabindex: '-1' do
Write
@@ -14,7 +14,7 @@
You are about to add
%strong
%span.js-referenced-users-count 0
- people
+ people
to the discussion. Proceed with caution.
%div
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 3f645b81397..2ac79e87b4a 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -41,7 +41,7 @@
.commit-info-row.branches
%i.fa.fa-spinner.fa-spin
-.commit-box
+.commit-box.gray-content-block.middle-block
%h3.commit-title
= gfm escape_once(@commit.title)
- if @commit.description.present?
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 30943f49bba..2f24dc7c909 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -1,8 +1,8 @@
- if params[:view] == 'parallel'
- fluid_layout true
-.prepend-top-20.append-bottom-20
- .pull-right
+.gray-content-block.second-block
+ .inline-parallel-buttons
.btn-group
= inline_diff_btn
= parallel_diff_btn
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index 1625930615a..c4d7f26430b 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -10,7 +10,7 @@
and
%strong.cred #{@commit.stats.deletions} deletions
.file-stats.js-toggle-content.hide
- %ul.bordered-list
+ %ul
- diffs.each_with_index do |diff, i|
%li
- if diff.deleted_file
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index e8e65d87f47..90dce739992 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -1,10 +1,11 @@
+- @blank_container = true
+
.project-edit-container
.project-edit-errors
.project-edit-content
- %div
- %h3.page-title
+ .panel.panel-default
+ .panel-heading
Project settings
- %hr
.panel-body
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit_project form-horizontal fieldset-form" }, authenticity_token: true do |f|
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index e577d35d560..798f1c47da5 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -22,15 +22,15 @@
%h5 Git global setup
%pre.light-well
:preserve
- git config --global user.name "#{git_user_name}"
- git config --global user.email "#{git_user_email}"
+ git config --global user.name "#{h git_user_name}"
+ git config --global user.email "#{h git_user_email}"
%fieldset
%h5 Create a new repository
%pre.light-well
:preserve
git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
- cd #{@project.path}
+ cd #{h @project.path}
touch README.md
git add README.md
git commit -m "add README"
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index f61ae957208..d4a98eca473 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -7,21 +7,24 @@
= render 'shared/show_aside'
+.gray-content-block.second-block
+ .row
+ .col-md-9
+ .votes-holder.pull-right
+ #votes= render 'votes/votes_block', votable: @issue
+ .participants
+ %span= pluralize(@participants.count, 'participant')
+ - @participants.each do |participant|
+ = link_to_member(@project, participant, name: false, size: 24)
+ .col-md-3
+ %span.slead.has_tooltip{title: 'Cross-project reference'}
+ = cross_project_reference(@project, @issue)
+
.row
%section.col-md-9
- .votes-holder.pull-right
- #votes= render 'votes/votes_block', votable: @issue
- .participants
- %span= pluralize(@participants.count, 'participant')
- - @participants.each do |participant|
- = link_to_member(@project, participant, name: false, size: 24)
.voting_notes#notes= render 'projects/notes/notes_with_form'
%aside.col-md-3
.issuable-affix
- .clearfix
- %span.slead.has_tooltip{title: 'Cross-project reference'}
- = cross_project_reference(@project, @issue)
- %hr
.context
= render 'shared/issuable/context', issuable: @issue
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index b6910c8f796..55ce912829d 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -41,4 +41,4 @@
= issue.task_status
.pull-right.issue-updated-at
- %small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
+ %span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index e7b14e7582c..09080642293 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,15 +1,16 @@
- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
.issue
.issue-details.issuable-details
- %h4.page-title
+ .page-title
.issue-box{ class: issue_box_class(@issue) }
- if @issue.closed?
Closed
- else
Open
- Issue ##{@issue.iid}
- %small.creator
- &middot; created by #{link_to_member(@project, @issue.author)}
+ %span.issue-id Issue ##{@issue.iid}
+ %span.creator
+ &middot; created by #{link_to_member(@project, @issue.author, size: 24)}
+ &middot;
= time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
- if @issue.updated_at != @issue.created_at
%span
@@ -32,18 +33,17 @@
= icon('pencil-square-o')
Edit
- %hr
- %h2.issue-title
- = gfm escape_once(@issue.title)
- %div
- - if @issue.description.present?
- .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
- .wiki
- = preserve do
- = markdown(@issue.description)
- %textarea.hidden.js-task-list-field
- = @issue.description
+ .gray-content-block.middle-block
+ %h2.issue-title
+ = gfm escape_once(@issue.title)
+ %div
+ - if @issue.description.present?
+ .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
+ .wiki
+ = preserve do
+ = markdown(@issue.description)
+ %textarea.hidden.js-task-list-field
+ = @issue.description
- %hr
.issue-discussion
= render 'projects/issues/discussion'
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index f855dfec321..38e66c3828b 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -7,18 +7,21 @@
= render 'shared/show_aside'
+.gray-content-block.second-block
+ .row
+ .col-md-9
+ .votes-holder.pull-right
+ #votes= render 'votes/votes_block', votable: @merge_request
+ = render "projects/merge_requests/show/participants"
+ .col-md-3
+ %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
+ = cross_project_reference(@project, @merge_request)
+
.row
%section.col-md-9
- .votes-holder.pull-right
- #votes= render 'votes/votes_block', votable: @merge_request
- = render "projects/merge_requests/show/participants"
= render "projects/notes/notes_with_form"
%aside.col-md-3
.issuable-affix
- .clearfix
- %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
- = cross_project_reference(@project, @merge_request)
- %hr
.context
= render 'shared/issuable/context', issuable: @merge_request
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 4295c828dad..25e4e8ba80d 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -43,4 +43,4 @@
= merge_request.task_status
.pull-right.hidden-xs
- %small updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
+ %span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index 7709330611a..452006162db 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -37,7 +37,7 @@
%h4 Compare failed
%p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches.
- else
- .light-well
+ .light-well.append-bottom-10
.center
%h4
There isn't anything to merge.
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 76f44211dac..46aeecd8733 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -18,15 +18,13 @@
= f.hidden_field :target_branch
.mr-compare.merge-request
- %ul.nav.nav-tabs.merge-request-tabs
+ %ul.merge-request-tabs
%li.commits-tab
= link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
- = icon('history')
Commits
%span.badge= @commits.size
%li.diffs-tab.active
= link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
- = icon('list-alt')
Changes
%span.badge= @diffs.size
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index ec1838eb489..61e04dce5ab 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -5,10 +5,8 @@
.merge-request{'data-url' => merge_request_path(@merge_request)}
.merge-request-details.issuable-details
= render "projects/merge_requests/show/mr_title"
- %hr
= render "projects/merge_requests/show/mr_box"
- %hr
- .append-bottom-20.mr-source-target
+ .append-bottom-20.mr-source-target.prepend-top-default
- if @merge_request.open?
.pull-right
- if @merge_request.source_branch_exists?
@@ -39,20 +37,17 @@
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- if @commits.present?
- %ul.nav.nav-tabs.merge-request-tabs
+ %ul.merge-request-tabs
%li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do
- = icon('comments')
Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.count
%li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
- = icon('history')
Commits
%span.badge= @commits.size
%li.diffs-tab
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
- = icon('list-alt')
Changes
%span.badge= @merge_request.diffs.size
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index db1575f899a..f18cf96c17d 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -11,12 +11,12 @@
%pre.dark
- if @merge_request.for_fork?
:preserve
- git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
- git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD
+ git fetch #{h @merge_request.source_project.http_url_to_repo} #{h @merge_request.source_branch}
+ git checkout -b #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} FETCH_HEAD
- else
:preserve
git fetch origin
- git checkout -b #{@merge_request.source_branch} origin/#{@merge_request.source_branch}
+ git checkout -b #{h @merge_request.source_branch} origin/#{h @merge_request.source_branch}
%p
%strong Step 2.
Review the changes locally
@@ -27,18 +27,18 @@
%pre.dark
- if @merge_request.for_fork?
:preserve
- git checkout #{@merge_request.target_branch}
- git merge --no-ff #{@merge_request.source_project_path}-#{@merge_request.source_branch}
+ git checkout #{h @merge_request.target_branch}
+ git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch}
- else
:preserve
- git checkout #{@merge_request.target_branch}
- git merge --no-ff #{@merge_request.source_branch}
+ git checkout #{h @merge_request.target_branch}
+ git merge --no-ff #{h @merge_request.source_branch}
%p
%strong Step 4.
Push the result of the merge to GitLab
%pre.dark
:preserve
- git push origin #{@merge_request.target_branch}
+ git push origin #{h @merge_request.target_branch}
- unless @merge_request.can_be_merged_by?(current_user)
%p
Note that pushing to GitLab requires write access to this repository.
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index e3cd4346872..b4f62a75890 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,11 +1,12 @@
-%h2.issue-title
- = gfm escape_once(@merge_request.title)
+.gray-content-block.middle-block
+ %h2.issue-title
+ = gfm escape_once(@merge_request.title)
-%div
- - if @merge_request.description.present?
- .description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''}
- .wiki
- = preserve do
- = markdown(@merge_request.description)
- %textarea.hidden.js-task-list-field
- = @merge_request.description
+ %div
+ - if @merge_request.description.present?
+ .description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''}
+ .wiki
+ = preserve do
+ = markdown(@merge_request.description)
+ %textarea.hidden.js-task-list-field
+ = @merge_request.description
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 9a1eb36fc88..2bf9cd597a4 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,10 +1,11 @@
-%h4.page-title
+.page-title
.issue-box{ class: issue_box_class(@merge_request) }
= @merge_request.state_human_name
- Merge Request ##{@merge_request.iid}
- %small.creator
+ %span.issue-id Merge Request ##{@merge_request.iid}
+ %span.creator
+ &middot;
+ created by #{link_to_member(@project, @merge_request.author, size: 24)}
&middot;
- created by #{link_to_member(@project, @merge_request.author)}
= time_ago_with_tooltip(@merge_request.created_at)
- if @merge_request.updated_at != @merge_request.created_at
%span
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index b61e193fc42..613525437ab 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -9,7 +9,7 @@
= label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
= check_box_tag :should_remove_source_branch
Remove source branch
- .accept-control
+ .accept-control.right
= link_to "#", class: "modify-merge-commit-link js-toggle-button" do
= icon('edit')
Modify commit message
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 2ce5358fa74..5e93d55b1fb 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,28 +1,34 @@
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
- .pull-right
- - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-sm edit-milestone-link btn-grouped" do
- %i.fa.fa-pencil-square-o
- Edit
- = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close"
- = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-sm btn-remove" do
- %i.fa.fa-trash-o
- Remove
+ .row
+ .col-sm-6
+ %strong
+ = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- %h4
- = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- - if milestone.expired? and not milestone.closed?
- %span.cred (Expired)
- %small
- = milestone.expires_at
+ .col-sm-6
+ .pull-right.light #{milestone.percent_complete}% complete
.row
.col-sm-6
= link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
= pluralize milestone.issues.count, 'Issue'
- &nbsp;
+ &middot;
= link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
= pluralize milestone.merge_requests.count, 'Merge Request'
- &nbsp;
- %span.light #{milestone.percent_complete}% complete
.col-sm-6
= milestone_progress_bar(milestone)
+
+ .row
+ .col-sm-6
+ - if milestone.expired? and not milestone.closed?
+ %span.cred (Expired)
+ - if milestone.expires_at
+ %span
+ = milestone.expires_at
+ .col-sm-6
+ - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
+ = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do
+ %i.fa.fa-pencil-square-o
+ Edit
+ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
+ = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
+ %i.fa.fa-trash-o
+ Remove
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 636218368cc..bccea21e7a8 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -72,6 +72,11 @@
%i.fa.fa-google
Google Code
+ - if fogbugz_import_enabled?
+ = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
+ %i.fa.fa-bug
+ Fogbugz
+
- if git_import_enabled?
= link_to "#", class: 'btn js-toggle-button import_git' do
%i.fa.fa-git
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index de75d44fc41..9bfbde02ca2 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,11 +1,8 @@
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } }
.timeline-entry-inner
.timeline-icon
- - if note.system
- %span= icon('circle')
- - else
- = link_to user_path(note.author) do
- = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: ''
+ = link_to user_path(note.author) do
+ = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: ''
.timeline-content
.note-header
- if note_editable?(note)
@@ -22,10 +19,6 @@
%span.note-role.label
= member.human_access
- - if note.system
- = link_to user_path(note.author) do
- = image_tag avatar_icon(note.author_email), class: 'avatar s16', alt: ''
-
= link_to_member(note.project, note.author, avatar: false)
%span.author-username
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index acc2c8b2f7f..14f25822259 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,4 +1,9 @@
%span.pull-right
+ - if can?(current_user, :create_wiki, @project)
+ = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new btn-grouped", "data-toggle" => "modal" do
+ %i.fa.fa-plus
+ New Page
+
- if (@page && @page.persisted?)
= link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
Page History
@@ -6,3 +11,5 @@
= link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
+
+= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index ec87c3c765a..fffb4eb31ab 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,4 +1,4 @@
-%ul.nav.nav-tabs
+%ul.center-top-menu
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
= link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
@@ -7,13 +7,4 @@
= nav_link(path: 'wikis#git_access') do
= link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
- %i.fa.fa-download
Git Access
-
- - if can?(current_user, :create_wiki, @project)
- .pull-right
- = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
- %i.fa.fa-plus
- New Page
-
-= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index 825f2a161c4..226fd3b2290 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -1,15 +1,16 @@
- page_title "Git Access", "Wiki"
= render 'nav'
-.row
- .col-sm-6
- %h3.page-title
- Git access for
- %strong= @project_wiki.path_with_namespace
+.gray-content-block
+ .row
+ .col-sm-6
+ %h3.page-title
+ Git access for
+ %strong= @project_wiki.path_with_namespace
- .col-sm-6
- = render "shared/clone_panel", project: @project_wiki
+ .col-sm-6
+ = render "shared/clone_panel", project: @project_wiki
-.git-empty
+.git-empty.prepend-top-default
%fieldset
%legend Install Gollum:
%pre.dark
@@ -20,7 +21,7 @@
%pre.dark
:preserve
git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')}
- cd #{@project_wiki.path}
+ cd #{h @project_wiki.path}
%legend Start Gollum And Edit Locally:
%pre.dark
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 673ec2d20e5..7c81ad53d32 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,8 +1,9 @@
- page_title "History", @page.title, "Wiki"
= render 'nav'
-%h3.page-title
- %span.light History for
- = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
+.gray-content-block
+ %h3.page-title
+ %span.light History for
+ = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
%table.table
%thead
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 890ff1aed73..7fb91507eb2 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,8 +1,9 @@
- page_title "All Pages", "Wiki"
= render 'nav'
-%h3.page-title
- All Pages
-%ul.bordered-list
+.gray-content-block
+ %h3.page-title
+ All Pages
+%ul.content-list
- @wiki_pages.each do |wiki_page|
%li
%h4
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 90c4b83cf37..126810811ec 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,25 +1,26 @@
- page_title @page.title, "Wiki"
= render 'nav'
-%h3.page-title
- = @page.title
+
+.gray-content-block
= render 'main_links'
+ %h3.page-title
+ = @page.title.capitalize
-.wiki-last-edit-by
- Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+ .wiki-last-edit-by
+ Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
- if @page.historical?
.warning_message
This is an old version of this page.
You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
-%hr
-.wiki-holder
+.wiki-holder.prepend-top-default
.wiki
= preserve do
= render_wiki_content(@page)
-%hr
-.wiki-last-edit-by
- Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+.gray-content-block.footer-block
+ .wiki-last-edit-by
+ Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 09327d645f3..1aa1e3c6c97 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -9,7 +9,7 @@
= f.label :title, class: 'control-label' do
%strong= 'Title *'
.col-sm-10
- = f.text_field :title, maxlength: 255, autofocus: true,
+ = f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
class: 'form-control pad js-gfm-input', required: true
- if issuable.is_a?(MergeRequest)
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index f2ba2e15e7b..ea2808045eb 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -7,22 +7,31 @@ class RepositoryImportWorker
def perform(project_id)
project = Project.find(project_id)
- import_result = gitlab_shell.send(:import_repository,
+ unless project.import_url == Project::UNKNOWN_IMPORT_URL
+ import_result = gitlab_shell.send(:import_repository,
project.path_with_namespace,
project.import_url)
- return project.import_fail unless import_result
+ return project.import_fail unless import_result
+ else
+ unless project.create_repository
+ return project.import_fail
+ end
+ end
- data_import_result = if project.import_type == 'github'
- Gitlab::GithubImport::Importer.new(project).execute
- elsif project.import_type == 'gitlab'
- Gitlab::GitlabImport::Importer.new(project).execute
- elsif project.import_type == 'bitbucket'
- Gitlab::BitbucketImport::Importer.new(project).execute
- elsif project.import_type == 'google_code'
- Gitlab::GoogleCodeImport::Importer.new(project).execute
- else
- true
- end
+ data_import_result = case project.import_type
+ when 'github'
+ Gitlab::GithubImport::Importer.new(project).execute
+ when 'gitlab'
+ Gitlab::GitlabImport::Importer.new(project).execute
+ when 'bitbucket'
+ Gitlab::BitbucketImport::Importer.new(project).execute
+ when 'google_code'
+ Gitlab::GoogleCodeImport::Importer.new(project).execute
+ when 'fogbugz'
+ Gitlab::FogbugzImport::Importer.new(project).execute
+ else
+ true
+ end
return project.import_fail unless data_import_result
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index c47e5dab27c..689c3f3049d 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -158,7 +158,7 @@ Settings.gitlab.default_projects_features['snippets'] = false if Settings.
Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitlab['repository_downloads_path'] || 'tmp/repositories', Rails.root)
Settings.gitlab['restricted_signup_domains'] ||= []
-Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','git']
+Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git']
#
# Reply by email
diff --git a/config/initializers/rouge_diff_lexer.rb b/config/initializers/rouge_diff_lexer.rb
new file mode 100644
index 00000000000..fdb2d7b748e
--- /dev/null
+++ b/config/initializers/rouge_diff_lexer.rb
@@ -0,0 +1,24 @@
+# Here until https://github.com/jneen/rouge/pull/297 is merged into Rouge and the gem is updated in GitLab.
+module Rouge
+ module Lexers
+ class Diff
+ def self.analyze_text(text)
+ return 1 if text.start_with?('Index: ')
+ return 1 if text.start_with?('diff ')
+ return 0.9 if text.start_with?('--- ')
+ end
+
+ state :root do
+ rule(/^ .*\n/, Text)
+ rule(/^---\n/, Text)
+ rule(/^\+.*\n/, Generic::Inserted)
+ rule(/^-+.*\n/, Generic::Deleted)
+ rule(/^!.*\n/, Generic::Strong)
+ rule(/^@.*\n/, Generic::Subheading)
+ rule(/^([Ii]ndex|diff).*\n/, Generic::Heading)
+ rule(/^=.*\n/, Generic::Heading)
+ rule(/.*\n/, Text)
+ end
+ end
+ end
+end
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 6d274cd95a1..88651394d1d 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -2,7 +2,12 @@
require 'gitlab/current_settings'
include Gitlab::CurrentSettings
-Settings.gitlab['session_expire_delay'] = current_application_settings.session_expire_delay
+
+# allow it to fail: it may to do so when create_from_defaults is executed before migrations are actually done
+begin
+ Settings.gitlab['session_expire_delay'] = current_application_settings.session_expire_delay
+rescue
+end
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
diff --git a/config/routes.rb b/config/routes.rb
index 720aee2d2ac..fad6b3c569a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -99,6 +99,15 @@ Gitlab::Application.routes.draw do
get :new_user_map, path: :user_map
post :create_user_map, path: :user_map
end
+
+ resource :fogbugz, only: [:create, :new], controller: :fogbugz do
+ get :status
+ post :callback
+ get :jobs
+
+ get :new_user_map, path: :user_map
+ post :create_user_map, path: :user_map
+ end
end
#
@@ -202,6 +211,8 @@ Gitlab::Application.routes.draw do
resources :services
end
+ resources :labels
+
root to: 'dashboard#index'
end
diff --git a/db/migrate/20150902001023_add_template_to_label.rb b/db/migrate/20150902001023_add_template_to_label.rb
new file mode 100644
index 00000000000..bd381a97b69
--- /dev/null
+++ b/db/migrate/20150902001023_add_template_to_label.rb
@@ -0,0 +1,5 @@
+class AddTemplateToLabel < ActiveRecord::Migration
+ def change
+ add_column :labels, :template, :boolean, default: false
+ end
+end \ No newline at end of file
diff --git a/db/schema.rb b/db/schema.rb
index 36568dd4edd..55cbd8c293e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20150824002011) do
+ActiveRecord::Schema.define(version: 20150902001023) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -186,6 +186,7 @@ ActiveRecord::Schema.define(version: 20150824002011) do
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
+ t.boolean "template", default: false
end
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 52031e9b9a1..ee13b0f2537 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -299,7 +299,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
GitLab Shell is an SSH access and repository management software developed specially for GitLab.
# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
- sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.3] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
+ sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.5] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
# By default, the gitlab-shell config is generated from your main GitLab config.
# You can review (and modify) the gitlab-shell config as follows:
diff --git a/doc/update/6.x-or-7.x-to-7.14.md b/doc/update/6.x-or-7.x-to-7.14.md
index a1488474f60..b34fb12da6f 100644
--- a/doc/update/6.x-or-7.x-to-7.14.md
+++ b/doc/update/6.x-or-7.x-to-7.14.md
@@ -127,7 +127,7 @@ sudo apt-get install nodejs
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.6.4
+sudo -u git -H git checkout v2.6.5
```
## 7. Install libs, migrations, etc.
@@ -162,12 +162,12 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
TIP: to see what changed in `gitlab.yml.example` in this release use next command:
```
-git diff 6-0-stable:config/gitlab.yml.example 7.14-stable:config/gitlab.yml.example
+git diff 6-0-stable:config/gitlab.yml.example 7-14-stable:config/gitlab.yml.example
```
* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/config/gitlab.yml.example but with your settings.
* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/config/unicorn.rb.example but with your settings.
-* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.0/config.yml.example but with your settings.
+* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.5/config.yml.example but with your settings.
* Copy rack attack middleware config
```bash
diff --git a/doc/update/7.13-to-7.14.md b/doc/update/7.13-to-7.14.md
index 7c2d3f4498a..6dd9727fb49 100644
--- a/doc/update/7.13-to-7.14.md
+++ b/doc/update/7.13-to-7.14.md
@@ -63,7 +63,7 @@ sudo -u git -H git checkout 7-14-stable-ee
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.6.4
+sudo -u git -H git checkout v2.6.5
```
### 5. Install libs, migrations, etc.
diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md
index 5cde90993d2..7ccf06fbd60 100644
--- a/doc/workflow/importing/README.md
+++ b/doc/workflow/importing/README.md
@@ -3,6 +3,7 @@
1. [Bitbucket](import_projects_from_bitbucket.md)
2. [GitHub](import_projects_from_github.md)
3. [GitLab.com](import_projects_from_gitlab_com.md)
+4. [FogBugz](import_projects_from_fogbugz.md)
4. [SVN](migrating_from_svn.md)
### Note
diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.png
new file mode 100644
index 00000000000..205c515bd3f
--- /dev/null
+++ b/doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.png
Binary files differ
diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png
new file mode 100644
index 00000000000..a1e348d46ad
--- /dev/null
+++ b/doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png
Binary files differ
diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_fogbogz.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_fogbogz.png
new file mode 100644
index 00000000000..ed362846909
--- /dev/null
+++ b/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_fogbogz.png
Binary files differ
diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_project.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_project.png
new file mode 100644
index 00000000000..d2fbd0267bd
--- /dev/null
+++ b/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_project.png
Binary files differ
diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_user_map.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_user_map.png
new file mode 100644
index 00000000000..b1cc4b58525
--- /dev/null
+++ b/doc/workflow/importing/fogbugz_importer/fogbugz_import_user_map.png
Binary files differ
diff --git a/doc/workflow/importing/import_projects_from_fogbugz.md b/doc/workflow/importing/import_projects_from_fogbugz.md
new file mode 100644
index 00000000000..71af0f9ea44
--- /dev/null
+++ b/doc/workflow/importing/import_projects_from_fogbugz.md
@@ -0,0 +1,29 @@
+# Import your project from FogBugz to GitLab
+
+It only takes a few simple steps to import your project from FogBugz.
+The importer will import all of your cases and comments with original case
+numbers and timestamps. You will also have the opportunity to map FogBugz
+users to GitLab users.
+
+* From your GitLab dashboard click 'New project'
+
+* Click on the 'FogBugz' button
+
+![FogBugz](fogbugz_importer/fogbugz_import_select_fogbogz.png)
+
+* Enter your FogBugz URL, email address, and password.
+
+![Login](fogbugz_importer/fogbugz_import_login.png)
+
+* Create mapping from FogBugz users to GitLab users.
+
+![User Map](fogbugz_importer/fogbugz_import_user_map.png)
+
+* Select the projects you wish to import by clicking the Import buttons
+
+![Import Project](fogbugz_importer/fogbugz_import_select_project.png)
+
+* Once the import has finished click the link to take you to the project
+dashboard. Follow the directions to push your existing repository.
+
+![Finished](fogbugz_importer/fogbugz_import_finished.png)
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 05521af6963..304bb97409e 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -25,6 +25,9 @@ RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
&& ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
&& mkdir -p /var/run/sshd
+# Disabling use DNS in ssh since it tends to slow connecting
+RUN echo "UseDNS no" >> /etc/ssh/sshd_config
+
# Prepare default configuration
RUN ( \
echo "" && \
diff --git a/features/admin/labels.feature b/features/admin/labels.feature
new file mode 100644
index 00000000000..1af0e700bd4
--- /dev/null
+++ b/features/admin/labels.feature
@@ -0,0 +1,38 @@
+Feature: Admin Issues Labels
+ Background:
+ Given I sign in as an admin
+ And I have labels: "bug", "feature", "enhancement"
+ Given I visit admin labels page
+
+ Scenario: I should see labels list
+ Then I should see label 'bug'
+ And I should see label 'feature'
+
+ Scenario: I create new label
+ Given I submit new label 'support'
+ Then I should see label 'support'
+
+ Scenario: I edit label
+ Given I visit 'bug' label edit page
+ When I change label 'bug' to 'fix'
+ Then I should not see label 'bug'
+ Then I should see label 'fix'
+
+ Scenario: I remove label
+ When I remove label 'bug'
+ Then I should not see label 'bug'
+
+ @javascript
+ Scenario: I delete all labels
+ When I delete all labels
+ Then I should see labels help message
+
+ Scenario: I create a label with invalid color
+ Given I visit admin new label page
+ When I submit new label with invalid color
+ Then I should see label color error message
+
+ Scenario: I create a label that already exists
+ Given I visit admin new label page
+ When I submit new label 'bug'
+ Then I should see label exist error message
diff --git a/features/steps/admin/labels.rb b/features/steps/admin/labels.rb
new file mode 100644
index 00000000000..d64380abf73
--- /dev/null
+++ b/features/steps/admin/labels.rb
@@ -0,0 +1,117 @@
+class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ step 'I visit \'bug\' label edit page' do
+ visit edit_admin_label_path(bug_label)
+ end
+
+ step 'I visit admin new label page' do
+ visit new_admin_label_path
+ end
+
+ step 'I visit admin labels page' do
+ visit admin_labels_path
+ end
+
+ step 'I remove label \'bug\'' do
+ page.within "#label_#{bug_label.id}" do
+ click_link 'Remove'
+ end
+ end
+
+ step 'I have labels: "bug", "feature", "enhancement"' do
+ ["bug", "feature", "enhancement"].each do |title|
+ Label.create(title: title, template: true)
+ end
+ end
+
+ step 'I delete all labels' do
+ page.within '.labels' do
+ page.all('.btn-remove').each do |remove|
+ remove.click
+ sleep 0.05
+ end
+ end
+ end
+
+ step 'I should see labels help message' do
+ page.within '.labels' do
+ expect(page).to have_content 'There are no any labels yet'
+ end
+ end
+
+ step 'I submit new label \'support\'' do
+ visit new_admin_label_path
+ fill_in 'Title', with: 'support'
+ fill_in 'Background Color', with: '#F95610'
+ click_button 'Save'
+ end
+
+ step 'I submit new label \'bug\'' do
+ visit new_admin_label_path
+ fill_in 'Title', with: 'bug'
+ fill_in 'Background Color', with: '#F95610'
+ click_button 'Save'
+ end
+
+ step 'I submit new label with invalid color' do
+ visit new_admin_label_path
+ fill_in 'Title', with: 'support'
+ fill_in 'Background Color', with: '#12'
+ click_button 'Save'
+ end
+
+ step 'I should see label exist error message' do
+ page.within '.label-form' do
+ expect(page).to have_content 'Title has already been taken'
+ end
+ end
+
+ step 'I should see label color error message' do
+ page.within '.label-form' do
+ expect(page).to have_content 'Color is invalid'
+ end
+ end
+
+ step 'I should see label \'feature\'' do
+ page.within '.manage-labels-list' do
+ expect(page).to have_content 'feature'
+ end
+ end
+
+ step 'I should see label \'bug\'' do
+ page.within '.manage-labels-list' do
+ expect(page).to have_content 'bug'
+ end
+ end
+
+ step 'I should not see label \'bug\'' do
+ page.within '.manage-labels-list' do
+ expect(page).not_to have_content 'bug'
+ end
+ end
+
+ step 'I should see label \'support\'' do
+ page.within '.manage-labels-list' do
+ expect(page).to have_content 'support'
+ end
+ end
+
+ step 'I change label \'bug\' to \'fix\'' do
+ fill_in 'Title', with: 'fix'
+ fill_in 'Background Color', with: '#F15610'
+ click_button 'Save'
+ end
+
+ step 'I should see label \'fix\'' do
+ page.within '.manage-labels-list' do
+ expect(page).to have_content 'fix'
+ end
+ end
+
+ def bug_label
+ Label.templates.find_or_create_by(title: 'bug')
+ end
+end
diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb
index 2e17d5c4c2e..4bc290b6bdf 100644
--- a/features/steps/admin/users.rb
+++ b/features/steps/admin/users.rb
@@ -4,11 +4,13 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
include SharedAdmin
before do
- allow(Devise).to receive(:omniauth_providers).and_return([:twitter, :twitter_updated])
+ allow(Gitlab::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated])
+ allow_any_instance_of(ApplicationHelper).to receive(:user_omniauth_authorize_path).and_return(root_path)
end
after do
- allow(Devise).to receive(:omniauth_providers).and_call_original
+ allow(Gitlab::OAuth::Provider).to receive(:providers).and_call_original
+ allow_any_instance_of(ApplicationHelper).to receive(:user_omniauth_authorize_path).and_call_original
end
step 'I should see all users' do
diff --git a/lib/gitlab/fogbugz_import/client.rb b/lib/gitlab/fogbugz_import/client.rb
new file mode 100644
index 00000000000..431d50882fd
--- /dev/null
+++ b/lib/gitlab/fogbugz_import/client.rb
@@ -0,0 +1,56 @@
+require 'fogbugz'
+
+module Gitlab
+ module FogbugzImport
+ class Client
+ attr_reader :api
+
+ def initialize(options = {})
+ if options[:uri] && options[:token]
+ @api = ::Fogbugz::Interface.new(options)
+ elsif options[:uri] && options[:email] && options[:password]
+ @api = ::Fogbugz::Interface.new(options)
+ @api.authenticate
+ @api
+ end
+ end
+
+ def get_token
+ @api.token
+ end
+
+ def valid?
+ !get_token.blank?
+ end
+
+ def user_map
+ users = {}
+ res = @api.command(:listPeople)
+ res['people']['person'].each do |user|
+ users[user['ixPerson']] = { name: user['sFullName'], email: user['sEmail'] }
+ end
+ users
+ end
+
+ def repos
+ res = @api.command(:listProjects)
+ @repos ||= res['projects']['project'].map { |proj| FogbugzImport::Repository.new(proj) }
+ end
+
+ def repo(id)
+ repos.find { |r| r.id.to_s == id.to_s }
+ end
+
+ def cases(project_id)
+ project_name = repo(project_id).name
+ res = @api.command(:search, q: "project:'#{project_name}'", cols: 'ixPersonAssignedTo,ixPersonOpenedBy,ixPersonClosedBy,sStatus,sPriority,sCategory,fOpen,sTitle,sLatestTextSummary,dtOpened,dtClosed,dtResolved,dtLastUpdated,events')
+ return [] unless res['cases']['count'].to_i > 0
+ res['cases']['case']
+ end
+
+ def categories
+ @api.command(:listCategories)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb
new file mode 100644
index 00000000000..61e08b23543
--- /dev/null
+++ b/lib/gitlab/fogbugz_import/importer.rb
@@ -0,0 +1,298 @@
+module Gitlab
+ module FogbugzImport
+ class Importer
+ attr_reader :project, :repo
+
+ def initialize(project)
+ @project = project
+
+ import_data = project.import_data.try(:data)
+ repo_data = import_data['repo'] if import_data
+ @repo = FogbugzImport::Repository.new(repo_data)
+
+ @known_labels = Set.new
+ end
+
+ def execute
+ return true unless repo.valid?
+
+ data = project.import_data.try(:data)
+
+ client = Gitlab::FogbugzImport::Client.new(token: data['fb_session']['token'], uri: data['fb_session']['uri'])
+
+ @cases = client.cases(@repo.id.to_i)
+ @categories = client.categories
+
+ import_cases
+
+ true
+ end
+
+ private
+
+ def user_map
+ @user_map ||= begin
+ user_map = Hash.new
+ import_data = project.import_data.try(:data)
+ stored_user_map = import_data['user_map'] if import_data
+ user_map.update(stored_user_map) if stored_user_map
+
+ user_map
+ end
+ end
+
+ def import_labels
+ @categories['categories']['category'].each do |label|
+ create_label(label['sCategory'])
+ @known_labels << name
+ end
+ end
+
+ def nice_label_color(name)
+ case name
+ when 'Blocker'
+ '#ff0000'
+ when 'Crash'
+ '#ffcfcf'
+ when 'Major'
+ '#deffcf'
+ when 'Minor'
+ '#cfe9ff'
+ when 'Bug'
+ '#d9534f'
+ when 'Feature'
+ '#44ad8e'
+ when 'Technical Task'
+ '#4b6dd0'
+ else
+ '#e2e2e2'
+ end
+ end
+
+ def create_label(name)
+ color = nice_label_color(name)
+ Label.create!(project_id: project.id, title: name, color: color)
+ end
+
+ def user_info(person_id)
+ user_hash = user_map[person_id.to_s]
+
+ user_name = ''
+ gitlab_id = nil
+
+ unless user_hash.nil?
+ user_name = user_hash['name']
+ if user = User.find_by(id: user_hash['gitlab_user'])
+ user_name = "@#{user.username}"
+ gitlab_id = user.id
+ end
+ end
+
+ { name: user_name, gitlab_id: gitlab_id }
+ end
+
+ def import_cases
+ return unless @cases
+
+ while bug = @cases.shift
+ author = user_info(bug['ixPersonOpenedBy'])[:name]
+ date = DateTime.parse(bug['dtOpened'])
+
+ comments = bug['events']['event']
+
+ content = format_content(opened_content(comments))
+ body = format_issue_body(author, date, content)
+
+ labels = []
+ [bug['sCategory'], bug['sPriority']].each do |label|
+ unless label.blank?
+ labels << label
+ unless @known_labels.include?(label)
+ create_label(label)
+ @known_labels << label
+ end
+ end
+ end
+
+ assignee_id = user_info(bug['ixPersonAssignedTo'])[:gitlab_id]
+ author_id = user_info(bug['ixPersonOpenedBy'])[:gitlab_id] || project.creator_id
+
+ issue = Issue.create!(
+ project_id: project.id,
+ title: bug['sTitle'],
+ description: body,
+ author_id: author_id,
+ assignee_id: assignee_id,
+ state: bug['fOpen'] == 'true' ? 'opened' : 'closed'
+ )
+ issue.add_labels_by_names(labels)
+
+ if issue.iid != bug['ixBug']
+ issue.update_attribute(:iid, bug['ixBug'])
+ end
+
+ import_issue_comments(issue, comments)
+
+ issue.update_attribute(:created_at, date)
+
+ last_update = DateTime.parse(bug['dtLastUpdated'])
+ issue.update_attribute(:updated_at, last_update)
+ end
+ end
+
+ def opened_content(comments)
+ while comment = comments.shift
+ if comment['sVerb'] == 'Opened'
+ return comment['s']
+ end
+ end
+ ''
+ end
+
+ def import_issue_comments(issue, comments)
+ Note.transaction do
+ while comment = comments.shift
+ verb = comment['sVerb']
+
+ next if verb == 'Opened' || verb === 'Closed'
+
+ content = format_content(comment['s'])
+ attachments = format_attachments(comment['rgAttachments'])
+ updates = format_updates(comment)
+
+ next if content.blank? && attachments.empty? && updates.empty?
+
+ author = user_info(comment['ixPerson'])[:name]
+ author_id = user_info(comment['ixPerson'])[:gitlab_id] || project.creator_id
+ date = DateTime.parse(comment['dt'])
+
+ body = format_issue_comment_body(
+ comment['ixBugEvent'],
+ author,
+ date,
+ content,
+ attachments,
+ updates
+ )
+
+ note = Note.create!(
+ project_id: project.id,
+ noteable_type: "Issue",
+ noteable_id: issue.id,
+ author_id: author_id,
+ note: body
+ )
+
+ note.update_attribute(:created_at, date)
+ note.update_attribute(:updated_at, date)
+ end
+ end
+ end
+
+ def linkify_issues(s)
+ s = s.gsub(/([Ii]ssue) ([0-9]+)/, '\1 #\2')
+ s = s.gsub(/([Cc]ase) ([0-9]+)/, '\1 #\2')
+ s
+ end
+
+ def escape_for_markdown(s)
+ s = s.gsub(/^#/, "\\#")
+ s = s.gsub(/^-/, "\\-")
+ s = s.gsub("`", "\\~")
+ s = s.gsub("\r", "")
+ s = s.gsub("\n", " \n")
+ s
+ end
+
+ def format_content(raw_content)
+ return raw_content if raw_content.nil?
+ linkify_issues(escape_for_markdown(raw_content))
+ end
+
+ def format_attachments(raw_attachments)
+ return [] unless raw_attachments
+
+ attachments = case raw_attachments['attachment']
+ when Array
+ raw_attachments['attachment']
+ when Hash
+ [raw_attachments['attachment']]
+ else
+ []
+ end
+
+ attachments.map! { |a| format_attachment(a) }
+ attachments.compact
+ end
+
+ def format_attachment(attachment)
+ link = build_attachment_url(attachment['sURL'])
+
+ res = ::Projects::DownloadService.new(project, link).execute
+
+ return nil if res.nil?
+
+ text = "[#{res['alt']}](#{res['url']})"
+ text = "!#{text}" if res['is_image']
+ text
+ end
+
+ def build_attachment_url(rel_url)
+ data = project.import_data.try(:data)
+ uri = data['fb_session']['uri']
+ token = data['fb_session']['token']
+ "#{uri}/#{rel_url}&token=#{token}"
+ end
+
+ def format_updates(comment)
+ updates = []
+
+ if comment['sChanges']
+ updates << "*Changes: #{linkify_issues(comment['sChanges'].chomp)}*"
+ end
+
+ if comment['evtDescription']
+ updates << "*#{comment['evtDescription']}*"
+ end
+
+ updates
+ end
+
+ def format_issue_body(author, date, content)
+ body = []
+ body << "*By #{author} on #{date} (imported from FogBugz)*"
+ body << '---'
+
+ if content.blank?
+ content = '*(No description has been entered for this issue)*'
+ end
+ body << content
+
+ body.join("\n\n")
+ end
+
+ def format_issue_comment_body(id, author, date, content, attachments, updates)
+ body = []
+ body << "*By #{author} on #{date} (imported from FogBugz)*"
+ body << '---'
+
+ if content.blank?
+ content = "*(No comment has been entered for this change)*"
+ end
+ body << content
+
+ if updates.any?
+ body << '---'
+ body += updates
+ end
+
+ if attachments.any?
+ body << '---'
+ body += attachments
+ end
+
+ body.join("\n\n")
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb
new file mode 100644
index 00000000000..f02ea43910f
--- /dev/null
+++ b/lib/gitlab/fogbugz_import/project_creator.rb
@@ -0,0 +1,38 @@
+module Gitlab
+ module FogbugzImport
+ class ProjectCreator
+ attr_reader :repo, :fb_session, :namespace, :current_user, :user_map
+
+ def initialize(repo, fb_session, namespace, current_user, user_map = nil)
+ @repo = repo
+ @fb_session = fb_session
+ @namespace = namespace
+ @current_user = current_user
+ @user_map = user_map
+ end
+
+ def execute
+ project = ::Projects::CreateService.new(current_user,
+ name: repo.safe_name,
+ path: repo.path,
+ namespace: namespace,
+ creator: current_user,
+ visibility_level: Gitlab::VisibilityLevel::INTERNAL,
+ import_type: 'fogbugz',
+ import_source: repo.name,
+ import_url: Project::UNKNOWN_IMPORT_URL
+ ).execute
+
+ import_data = project.create_import_data(
+ data: {
+ 'repo' => repo.raw_data,
+ 'user_map' => user_map,
+ 'fb_session' => fb_session
+ }
+ )
+
+ project
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/fogbugz_import/repository.rb b/lib/gitlab/fogbugz_import/repository.rb
new file mode 100644
index 00000000000..d1dc63db2b2
--- /dev/null
+++ b/lib/gitlab/fogbugz_import/repository.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module FogbugzImport
+ class Repository
+ attr_accessor :raw_data
+
+ def initialize(raw_data)
+ @raw_data = raw_data
+ end
+
+ def valid?
+ raw_data.is_a?(Hash)
+ end
+
+ def id
+ raw_data['ixProject']
+ end
+
+ def name
+ raw_data['sProject']
+ end
+
+ def safe_name
+ name.gsub(/[^\s\w.-]/, '')
+ end
+
+ def path
+ safe_name.gsub(/[\s]/, '_')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index 991b70aab6a..ccfdfbe73e8 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -19,6 +19,7 @@ module Gitlab
'GitLab.com' => 'gitlab',
'Gitorious.org' => 'gitorious',
'Google Code' => 'google_code',
+ 'FogBugz' => 'fogbugz',
'Any repo by URL' => 'git',
}
end
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 097caf67a65..ae5f2544691 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -77,7 +77,7 @@ module Gitlab
pipeline: options[:pipeline],
# EmojiFilter
- asset_root: Gitlab.config.gitlab.url,
+ asset_root: Gitlab.config.gitlab.base_url,
asset_host: Gitlab::Application.config.asset_host,
# TableOfContentsFilter
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 17f89c8beb6..7218a4d2f20 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -124,6 +124,15 @@ server {
proxy_connect_timeout 300;
proxy_redirect off;
+ # Do not buffer Git HTTP responses
+ proxy_buffering off;
+
+ # The following settings only work with NGINX 1.7.11 or newer
+ #
+ # # Pass chunked request bodies to gitlab-git-http-server as-is
+ # proxy_request_buffering off;
+ # proxy_http_version 1.1;
+
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 5ba39fc41a4..7dabfba87e2 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -171,6 +171,15 @@ server {
proxy_connect_timeout 300;
proxy_redirect off;
+ # Do not buffer Git HTTP responses
+ proxy_buffering off;
+
+ # The following settings only work with NGINX 1.7.11 or newer
+ #
+ # # Pass chunked request bodies to gitlab-git-http-server as-is
+ # proxy_request_buffering off;
+ # proxy_http_version 1.1;
+
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Ssl on;
diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb
new file mode 100644
index 00000000000..27b11267d2a
--- /dev/null
+++ b/spec/controllers/import/fogbugz_controller_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+require_relative 'import_spec_helper'
+
+describe Import::FogbugzController do
+ include ImportSpecHelper
+
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET status' do
+ before do
+ @repo = OpenStruct.new(name: 'vim')
+ stub_client(valid?: true)
+ end
+
+ it 'assigns variables' do
+ @project = create(:project, import_type: 'fogbugz', creator_id: user.id)
+ stub_client(repos: [@repo])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([@project])
+ expect(assigns(:repos)).to eq([@repo])
+ end
+
+ it 'does not show already added project' do
+ @project = create(:project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim')
+ stub_client(repos: [@repo])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([@project])
+ expect(assigns(:repos)).to eq([])
+ end
+ end
+end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 66cdfd5d758..ff4ed2dd484 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -17,6 +17,14 @@ describe Projects::CreateService do
expect(project.services).not_to be_empty
end
+ it 'creates labels on Project creation if there are templates' do
+ Label.create(title: "bug", template: true)
+ project = create_project(@user, @opts)
+ project.reload
+
+ expect(project.labels).not_to be_empty
+ end
+
context 'user namespace' do
before do
@project = create_project(@user, @opts)
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
new file mode 100644
index 00000000000..f12e09c58c3
--- /dev/null
+++ b/spec/services/projects/download_service_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Projects::DownloadService do
+ describe 'File service' do
+ before do
+ @user = create :user
+ @project = create :project, creator_id: @user.id, namespace: @user.namespace
+ end
+
+ context 'for a URL that is not on whitelist' do
+ before do
+ url = 'https://code.jquery.com/jquery-2.1.4.min.js'
+ @link_to_file = download_file(@project, url)
+ end
+
+ it { expect(@link_to_file).to eq(nil) }
+ end
+
+ context 'for URLs that are on the whitelist' do
+ before do
+ sham_rack_app = ShamRack.at('mycompany.fogbugz.com').stub
+ sham_rack_app.register_resource('/rails_sample.jpg', File.read(Rails.root + 'spec/fixtures/rails_sample.jpg'), 'image/jpg')
+ sham_rack_app.register_resource('/doc_sample.txt', File.read(Rails.root + 'spec/fixtures/doc_sample.txt'), 'text/plain')
+ end
+
+ after do
+ ShamRack.unmount_all
+ end
+
+ context 'an image file' do
+ before do
+ url = 'http://mycompany.fogbugz.com/rails_sample.jpg'
+ @link_to_file = download_file(@project, url)
+ end
+
+ it { expect(@link_to_file).to have_key('alt') }
+ it { expect(@link_to_file).to have_key('url') }
+ it { expect(@link_to_file).to have_key('is_image') }
+ it { expect(@link_to_file['is_image']).to be true }
+ it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
+ it { expect(@link_to_file['url']).to match('rails_sample.jpg') }
+ it { expect(@link_to_file['alt']).to eq('rails_sample') }
+ end
+
+ context 'a txt file' do
+ before do
+ url = 'http://mycompany.fogbugz.com/doc_sample.txt'
+ @link_to_file = download_file(@project, url)
+ end
+
+ it { expect(@link_to_file).to have_key('alt') }
+ it { expect(@link_to_file).to have_key('url') }
+ it { expect(@link_to_file).to have_key('is_image') }
+ it { expect(@link_to_file['is_image']).to be false }
+ it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
+ it { expect(@link_to_file['url']).to match('doc_sample.txt') }
+ it { expect(@link_to_file['alt']).to eq('doc_sample.txt') }
+ end
+ end
+ end
+
+ def download_file(repository, url)
+ Projects::DownloadService.new(repository, url).execute
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index d0f1873ee2d..0780c4f3203 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -28,6 +28,7 @@ RSpec.configure do |config|
config.include LoginHelpers, type: :feature
config.include LoginHelpers, type: :request
config.include StubConfiguration
+ config.include RelativeUrl, type: feature
config.include TestEnv
config.infer_spec_type_from_file_location!
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 9df226c3af8..7500d0fdf80 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -27,6 +27,9 @@ module MarkdownMatchers
match do |actual|
expect(actual).to have_selector('img.emoji', count: 10)
+
+ image = actual.at_css('img.emoji')
+ expect(image['src'].to_s).to start_with(Gitlab.config.gitlab.url + '/assets')
end
end
diff --git a/spec/support/relative_url.rb b/spec/support/relative_url.rb
new file mode 100644
index 00000000000..72e3ccce75b
--- /dev/null
+++ b/spec/support/relative_url.rb
@@ -0,0 +1,8 @@
+# Fix route helpers in tests (e.g. root_path, ...)
+module RelativeUrl
+ extend ActiveSupport::Concern
+
+ included do
+ default_url_options[:script_name] = Rails.application.config.relative_url_root
+ end
+end