Commit a6997e33 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into dashboard-titles

parents e4b30f9d dadf6daa
...@@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -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) v 8.0.0 (unreleased)
- Fix URL construction for merge requests, issues, notes, and commits for relative URL config (Stan Hu) - 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) - 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) - Fix broken Wiki Page History (Stan Hu)
- Prevent anchors from being hidden by header (Stan Hu) - Prevent anchors from being hidden by header (Stan Hu)
...@@ -40,13 +41,19 @@ v 8.0.0 (unreleased) ...@@ -40,13 +41,19 @@ v 8.0.0 (unreleased)
- Add ability to get user information by ID of an SSH key via the API - 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 - Fix bug which IE cannot show image at markdown when the image is raw file of gitlab
- Add support for Crowd - Add support for Crowd
- Global Labels that are available to all projects
- Fix highlighting of deleted lines in diffs.
v 7.14.1 v 7.14.1
- Improve abuse reports management from admin area - Improve abuse reports management from admin area
- Fix "Reload with full diff" URL button in compare branch view (Stan Hu) - 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) - 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) - Fix Error 500 in API when accessing a group that has an avatar (Stan Hu)
- Ability to enable SSL verification for Webhooks - Ability to enable SSL verification for Webhooks
- Add FogBugz project import (Jared Szechy)
v 7.14.0 v 7.14.0
- Fix bug where non-project members of the target project could set labels on new merge requests. - Fix bug where non-project members of the target project could set labels on new merge requests.
......
...@@ -157,6 +157,9 @@ gem "slack-notifier", "~> 1.0.0" ...@@ -157,6 +157,9 @@ gem "slack-notifier", "~> 1.0.0"
# Asana integration # Asana integration
gem 'asana', '~> 0.0.6' gem 'asana', '~> 0.0.6'
# FogBugz integration
gem 'ruby-fogbugz'
# d3 # d3
gem 'd3_rails', '~> 3.5.5' gem 'd3_rails', '~> 3.5.5'
...@@ -259,6 +262,7 @@ group :test do ...@@ -259,6 +262,7 @@ group :test do
gem 'email_spec', '~> 1.6.0' gem 'email_spec', '~> 1.6.0'
gem 'webmock', '~> 1.21.0' gem 'webmock', '~> 1.21.0'
gem 'test_after_commit' gem 'test_after_commit'
gem 'sham_rack'
end end
group :production do group :production do
......
...@@ -575,6 +575,8 @@ GEM ...@@ -575,6 +575,8 @@ GEM
powerpack (~> 0.0.6) powerpack (~> 0.0.6)
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.4) ruby-progressbar (~> 1.4)
ruby-fogbugz (0.1.1)
crack
ruby-progressbar (1.7.1) ruby-progressbar (1.7.1)
ruby-saml (1.0.0) ruby-saml (1.0.0)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
...@@ -609,6 +611,8 @@ GEM ...@@ -609,6 +611,8 @@ GEM
thor (~> 0.14) thor (~> 0.14)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.4.5) sexp_processor (4.4.5)
sham_rack (1.3.6)
rack
shoulda-matchers (2.8.0) shoulda-matchers (2.8.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
sidekiq (3.3.0) sidekiq (3.3.0)
...@@ -845,12 +849,14 @@ DEPENDENCIES ...@@ -845,12 +849,14 @@ DEPENDENCIES
rqrcode-rails3 rqrcode-rails3
rspec-rails (~> 3.3.0) rspec-rails (~> 3.3.0)
rubocop (= 0.28.0) rubocop (= 0.28.0)
ruby-fogbugz
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 4.0.5) sass-rails (~> 4.0.5)
sdoc sdoc
seed-fu seed-fu
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
settingslogic settingslogic
sham_rack
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
sidekiq (~> 3.3) sidekiq (~> 3.3)
sidetiq (= 0.6.3) sidetiq (= 0.6.3)
......
...@@ -85,14 +85,14 @@ ...@@ -85,14 +85,14 @@
// Labels // Labels
.label { .label {
padding: 2px 4px; padding: 2px 4px;
font-size: 12px; font-size: 13px;
font-style: normal; font-style: normal;
font-weight: normal; font-weight: normal;
display: inline-block; display: inline-block;
&.label-gray { &.label-gray {
background-color: #eee; background-color: #f8fafc;
color: #999; color: $gl-gray;
text-shadow: none; text-shadow: none;
} }
...@@ -156,10 +156,16 @@ ...@@ -156,10 +156,16 @@
* Add some extra stuff to panels * Add some extra stuff to panels
* *
*/ */
.container-blank .panel .panel-heading {
font-size: 17px;
line-height: 38px;
}
.panel { .panel {
.panel-heading { box-shadow: none;
font-weight: bold;
.panel-heading {
.panel-head-actions { .panel-head-actions {
position: relative; position: relative;
top: -5px; top: -5px;
...@@ -182,6 +188,10 @@ ...@@ -182,6 +188,10 @@
.pagination { .pagination {
margin: 0; margin: 0;
} }
.btn {
min-width: 124px;
}
} }
&.panel-small { &.panel-small {
...@@ -209,6 +219,12 @@ ...@@ -209,6 +219,12 @@
} }
} }
.alert-help {
background-color: $background-color;
border: 1px solid $border-color;
color: $gl-gray;
}
// Typography ================================================================= // Typography =================================================================
.text-primary, .text-primary,
......
...@@ -22,6 +22,10 @@ $brand-info: $gl-info; ...@@ -22,6 +22,10 @@ $brand-info: $gl-info;
$brand-warning: $gl-warning; $brand-warning: $gl-warning;
$brand-danger: $gl-danger; $brand-danger: $gl-danger;
$border-radius-base: 3px !default;
$border-radius-large: 5px !default;
$border-radius-small: 2px !default;
//== Scaffolding //== Scaffolding
// //
...@@ -110,11 +114,12 @@ $alert-border-radius: 0; ...@@ -110,11 +114,12 @@ $alert-border-radius: 0;
// //
//## //##
$panel-border-radius: 0; $panel-border-radius: 2px;
$panel-default-text: $text-color; $panel-default-text: $text-color;
$panel-default-border: #E7E9ED; $panel-default-border: $border-color;
$panel-default-heading-bg: #F8FAFC; $panel-default-heading-bg: $background-color;
$panel-footer-bg: $background-color;
$panel-inner-border: $border-color;
//== Wells //== Wells
// //
...@@ -144,3 +149,10 @@ $btn-default-border: #e7e9ed; ...@@ -144,3 +149,10 @@ $btn-default-border: #e7e9ed;
// //
//## //##
$nav-link-padding: 13px $gl-padding; $nav-link-padding: 13px $gl-padding;
//== Code
//
//##
$pre-bg: #f8fafc !default;
$pre-color: $gl-gray !default;
$pre-border-color: #e7e9ed;
...@@ -177,7 +177,7 @@ ...@@ -177,7 +177,7 @@
margin: 0px; margin: 0px;
&:last-child { &:last-child {
border:none border-bottom: none;
} }
&.active { &.active {
...@@ -215,3 +215,39 @@ ...@@ -215,3 +215,39 @@
font-size: 16px; font-size: 16px;
line-height: 24px; 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;
}
}
}
...@@ -27,6 +27,15 @@ ...@@ -27,6 +27,15 @@
border-bottom: 1px solid #e7e9ed; border-bottom: 1px solid #e7e9ed;
color: $gl-gray; color: $gl-gray;
&.top-block {
border-top: none;
}
&.middle-block {
margin-top: 0;
margin-bottom: 0;
}
&.second-block { &.second-block {
margin-top: -1px; margin-top: -1px;
margin-bottom: 0; margin-bottom: 0;
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
} }
&.btn-save { &.btn-save {
@extend .btn-primary; @extend .btn-success;
} }
&.btn-remove { &.btn-remove {
......
...@@ -370,41 +370,11 @@ table { ...@@ -370,41 +370,11 @@ table {
} }
.center-top-menu { .center-top-menu {
padding: 0; @include nav-menu;
margin: 0;
list-style: none;
text-align: center; text-align: center;
margin-top: 5px; margin-top: 5px;
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
height: 56px; height: 56px;
margin-top: -$gl-padding; margin-top: -$gl-padding;
padding-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;
}
}
} }
...@@ -5,10 +5,13 @@ ...@@ -5,10 +5,13 @@
*/ */
.issue-box { .issue-box {
@include border-radius(3px);
display: inline-block; display: inline-block;
padding: 4px 13px; padding: 10px $gl-padding;
font-weight: normal; font-weight: normal;
margin-right: 5px; margin-right: 10px;
font-size: $gl-font-size;
&.issue-box-closed { &.issue-box-closed {
background-color: $gl-danger; background-color: $gl-danger;
...@@ -21,7 +24,7 @@ ...@@ -21,7 +24,7 @@
} }
&.issue-box-open { &.issue-box-open {
background-color: $gl-success; background-color: #019875;
color: #FFF; color: #FFF;
} }
......
...@@ -65,8 +65,11 @@ ...@@ -65,8 +65,11 @@
position: relative; position: relative;
} }
.md-header ul { .md-header {
ul {
float: left; float: left;
margin-bottom: 1px;
}
} }
.referenced-users { .referenced-users {
...@@ -80,7 +83,7 @@ ...@@ -80,7 +83,7 @@
.md-preview-holder { .md-preview-holder {
background: #FFF; background: #FFF;
border: 1px solid #ddd; border: 1px solid #ddd;
min-height: 100px; min-height: 169px;
padding: 5px; padding: 5px;
box-shadow: none; box-shadow: none;
} }
...@@ -105,7 +108,7 @@ ...@@ -105,7 +108,7 @@
.markdown-area { .markdown-area {
background: #FFF; background: #FFF;
border: 1px solid #ddd; border: 1px solid #ddd;
min-height: 100px; min-height: 140px;
padding: 5px; padding: 5px;
box-shadow: none; box-shadow: none;
width: 100%; width: 100%;
......
...@@ -28,12 +28,18 @@ ...@@ -28,12 +28,18 @@
padding: $gl-padding; padding: $gl-padding;
border: 1px solid #e7e9ed; border: 1px solid #e7e9ed;
min-height: 90vh; min-height: 90vh;
&.container-blank {
background: none;
padding: 0;
border: none;
}
} }
} }
.nav-sidebar { .nav-sidebar {
margin-top: 14 + $header-height; margin-top: 14 + $header-height;
margin-bottom: 50px; margin-bottom: 100px;
transition-duration: .3s; transition-duration: .3s;
list-style: none; list-style: none;
overflow: hidden; overflow: hidden;
......
.timeline { .timeline {
list-style: none; @include basic-list;
padding: 20px 0 20px;
position: relative;
&:before {
top: 0;
bottom: 0;
position: absolute;
content: " ";
width: 3px;
background-color: #eeeeee;
margin-left: 29px;
}
.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;
}
}
.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; margin: 0;
padding: 0; padding: 0;
}
}
.timeline-content { .timeline-entry {
position: relative; padding: $gl-padding;
background: $background-color; border-color: #f1f2f4;
padding: 10px 15px; margin-left: -$gl-padding;
margin-left: 60px; margin-right: -$gl-padding;
color: $gl-gray;
border-bottom: 1px solid #f1f2f4;
border-right: 1px solid #f1f2f4;
img { &:last-child {
max-width: 100%; border-bottom: none;
} }
&:after { .avatar {
content: ''; margin-right: 15px;
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;
}
}
}
} }
.system-note .timeline-entry-inner { .controls {
.timeline-icon { padding-top: 10px;
background: none; float: right;
margin-left: 12px;
margin-top: 0;
@include box-shadow(none);
span {
margin: 0 2px;
font-size: 16px;
color: #eeeeee;
} }
} }
.timeline-content { .note-text {
background: none; p:last-child {
margin-left: 45px; margin-bottom: 0;
padding: 0px 15px;
&:after { border: 0; }
.note-header {
span { font-size: 12px; }
.avatar {
margin-right: 5px;
} }
} }
.system-note {
.note-text { .note-text {
font-size: 12px; color: $gl-gray !important;
margin-left: 20px;
} }
} }
.diff-file {
border: 1px solid $border-color;
border-bottom: none;
margin-left: 0;
margin-right: 0;
} }
} }
...@@ -132,3 +63,8 @@ ...@@ -132,3 +63,8 @@
} }
} }
} }
.discussion .timeline-entry {
margin: 0;
border-right: none;
}
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
} }
.zen-enter-link { .zen-enter-link {
color: #888; color: $gl-gray;
position: absolute; position: absolute;
top: 0px; top: 0px;
right: 4px; right: 4px;
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.zen-leave-link { .zen-leave-link {
display: none; display: none;
color: #888; color: $gl-text-color;
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 10px;
......
...@@ -26,14 +26,6 @@ ...@@ -26,14 +26,6 @@
margin-top: 10px; margin-top: 10px;
} }
.commit-stat-summary {
color: #666;
font-size: 14px;
font-weight: normal;
padding: 3px 0;
margin-bottom: 10px;
}
.commit-info-row { .commit-info-row {
margin-bottom: 10px; margin-bottom: 10px;
.avatar { .avatar {
...@@ -47,11 +39,6 @@ ...@@ -47,11 +39,6 @@
} }
.commit-box { .commit-box {
margin: 10px 0;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
padding: 20px 0;
.commit-title { .commit-title {
margin: 0; margin: 0;
} }
......
.diff-file { .diff-file {
border: 1px solid $border-color; margin-left: -16px;
margin-bottom: 1em; margin-right: -16px;
border: none;
border-bottom: 1px solid #E7E9EE;
.diff-header { .diff-header {
position: relative; position: relative;
...@@ -45,7 +47,7 @@ ...@@ -45,7 +47,7 @@
overflow-y: hidden; overflow-y: hidden;
background: #FFF; background: #FFF;
color: #333; color: #333;
font-size: $code_font_size;
.old { .old {
span.idiff { span.idiff {
background-color: #f8cbcb; background-color: #f8cbcb;
...@@ -82,7 +84,7 @@ ...@@ -82,7 +84,7 @@
border: none; border: none;
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
td { .line_holder td {
line-height: $code_line_height; line-height: $code_line_height;
font-size: $code_font_size; font-size: $code_font_size;
} }
...@@ -367,3 +369,7 @@ ...@@ -367,3 +369,7 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
.inline-parallel-buttons {
float: right;
margin-top: -5px;
}
...@@ -25,8 +25,6 @@ ...@@ -25,8 +25,6 @@
} }
.issuable-context-title { .issuable-context-title {
font-size: 14px;
line-height: 1.4;
margin-bottom: 5px; margin-bottom: 5px;
.avatar { .avatar {
...@@ -34,18 +32,12 @@ ...@@ -34,18 +32,12 @@
} }
label { label {
color: #666; color: $gl-gray;
font-weight: normal; font-weight: normal;
margin-right: 4px; margin-right: 4px;
} }
} }
.issuable-affix .context {
font-size: 13px;
.btn { font-size: 13px; }
}
.project-issuable-filter { .project-issuable-filter {
.controls { .controls {
float: right; float: right;
...@@ -56,3 +48,34 @@ ...@@ -56,3 +48,34 @@
text-align: left; 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;
}
}
}
.issues-list { .issues-list {
.issue { .issue {
padding: 10px 15px; padding: 10px $gl-padding;
position: relative; position: relative;
.issue-title { .issue-title {
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
.issue-info { .issue-info {
color: $gl-gray; color: $gl-gray;
font-size: 13px;
} }
.issue-check { .issue-check {
...@@ -47,10 +46,6 @@ ...@@ -47,10 +46,6 @@
} }
} }
.participants {
margin-bottom: 20px;
}
.issue-search-form { .issue-search-form {
margin: 0; margin: 0;
height: 24px; height: 24px;
...@@ -137,11 +132,6 @@ form.edit-issue { ...@@ -137,11 +132,6 @@ form.edit-issue {
} }
} }
h2.issue-title {
margin-top: 0;
font-weight: bold;
}
.issue-form .select2-container { .issue-form .select2-container {
width: 250px !important; width: 250px !important;
} }
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
* *
*/ */
.mr-state-widget { .mr-state-widget {
background: #FAFAFA; background: #f8fafc;
margin-bottom: 20px; margin-bottom: 20px;
color: #666; color: $gl-gray;
border: 1px solid #e5e5e5; border: 1px solid #eef0f2;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05)); @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
@include border-radius(3px); @include border-radius(3px);
...@@ -29,6 +29,14 @@ ...@@ -29,6 +29,14 @@
padding: 5px; padding: 5px;
line-height: 20px; line-height: 20px;
&.right {
float: right;
padding-top: 12px;
a {
color: $gl-gray;
}
}
.remove_source_checkbox { .remove_source_checkbox {
margin: 0; margin: 0;
} }
...@@ -36,7 +44,7 @@ ...@@ -36,7 +44,7 @@
} }
.ci_widget { .ci_widget {
border-bottom: 1px solid #EEE; border-bottom: 1px solid #eef0f2;
i { i {
margin-right: 4px; margin-right: 4px;
...@@ -89,20 +97,14 @@ ...@@ -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{ .merge-request .merge-request-tabs{
margin-top: 30px; @include nav-menu;
margin-bottom: 20px; margin: -$gl-padding;
padding: $gl-padding;
text-align: center;
border-top: 1px solid #e7e9ed;
margin-top: 18px;
margin-bottom: 3px;
} }
.mr_source_commit, .mr_source_commit,
...@@ -137,7 +139,6 @@ ...@@ -137,7 +139,6 @@
.merge-request-info { .merge-request-info {
color: $gl-gray; color: $gl-gray;
font-size: 13px;
} }
} }
......
...@@ -6,4 +6,8 @@ li.milestone { ...@@ -6,4 +6,8 @@ li.milestone {
h4 { h4 {
font-weight: bold; font-weight: bold;
} }
.progress {
height: 6px;
}
} }
...@@ -72,9 +72,13 @@ ...@@ -72,9 +72,13 @@
.common-note-form { .common-note-form {
margin: 0; margin: 0;
background: #F9F9F9; background: #f8fafc;
padding: 5px; padding: $gl-padding;
border: 1px solid #DDD; 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 { .note-form-actions {
...@@ -142,9 +146,9 @@ ...@@ -142,9 +146,9 @@
} }
.discussion-reply-holder { .discussion-reply-holder {
background: #f9f9f9; background: $background-color;
padding: 10px 15px; padding: 10px 15px;
border-top: 1px solid #DDD; border-top: 1px solid $border-color;
} }
} }
...@@ -166,6 +170,6 @@ ...@@ -166,6 +170,6 @@
background: #FFF; background: #FFF;
padding: 5px; padding: 5px;
margin-top: -11px; margin-top: -11px;
border: 1px solid #DDD; border: 1px solid $border-color;
font-size: 13px; font-size: 13px;
} }
...@@ -14,6 +14,19 @@ ul.notes { ...@@ -14,6 +14,19 @@ ul.notes {
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
.system-note {
font-size: 14px;
padding-top: 10px;
padding-bottom: 10px;
background: #f8fafc;
.timeline-icon {
.avatar {
visibility: hidden;
}
}
}
.discussion-header, .discussion-header,
.note-header { .note-header {
@extend .cgray; @extend .cgray;
...@@ -34,10 +47,8 @@ ul.notes { ...@@ -34,10 +47,8 @@ ul.notes {
content: "\00b7"; content: "\00b7";
} }
font-size: 13px;
a { a {
@extend .cgray; color: $gl-gray;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
...@@ -45,8 +56,9 @@ ul.notes { ...@@ -45,8 +56,9 @@ ul.notes {
} }
} }
.author { .author {
color: #333; color: #4c4e54;
font-weight: bold; margin-right: 3px;
&:hover { &:hover {
color: $gl-link-color; color: $gl-link-color;
} }
...@@ -59,7 +71,7 @@ ul.notes { ...@@ -59,7 +71,7 @@ ul.notes {
margin-top: 1px; margin-top: 1px;
border: 1px solid #bbb; border: 1px solid #bbb;
background-color: transparent; background-color: transparent;
color: #999; color: $gl-gray;
} }
} }
...@@ -133,8 +145,6 @@ ul.notes { ...@@ -133,8 +145,6 @@ ul.notes {
} }
.diff-file .notes_holder { .diff-file .notes_holder {
font-size: 13px;
line-height: 18px;
font-family: $regular_font; font-family: $regular_font;
td { td {
...@@ -176,8 +186,7 @@ ul.notes { ...@@ -176,8 +186,7 @@ ul.notes {
a { a {
margin-left: 5px; margin-left: 5px;
color: $gl-gray;
color: #999;
i.fa { i.fa {
font-size: 16px; font-size: 16px;
......
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
require 'gon' require 'gon'
require 'fogbugz'
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
...@@ -20,7 +21,7 @@ class ApplicationController < ActionController::Base ...@@ -20,7 +21,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception protect_from_forgery with: :exception
helper_method :abilities, :can?, :current_application_settings 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| rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception) log_exception(exception)
...@@ -337,6 +338,10 @@ class ApplicationController < ActionController::Base ...@@ -337,6 +338,10 @@ class ApplicationController < ActionController::Base
current_application_settings.import_sources.include?('google_code') current_application_settings.import_sources.include?('google_code')
end end
def fogbugz_import_enabled?
current_application_settings.import_sources.include?('fogbugz')
end
def git_import_enabled? def git_import_enabled?
current_application_settings.import_sources.include?('git') current_application_settings.import_sources.include?('git')
end end
......
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
...@@ -31,4 +31,26 @@ module PageLayoutHelper ...@@ -31,4 +31,26 @@ module PageLayoutHelper
@fluid_layout @fluid_layout
end end
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 end
...@@ -83,7 +83,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -83,7 +83,7 @@ class ApplicationSetting < ActiveRecord::Base
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_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'], 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 end
......
...@@ -24,7 +24,7 @@ class Label < ActiveRecord::Base ...@@ -24,7 +24,7 @@ class Label < ActiveRecord::Base
validates :color, validates :color,
format: { with: /\A#[0-9A-Fa-f]{6}\Z/ }, format: { with: /\A#[0-9A-Fa-f]{6}\Z/ },
allow_blank: false allow_blank: false
validates :project, presence: true validates :project, presence: true, unless: Proc.new { |service| service.template? }
# Don't allow '?', '&', and ',' for label titles # Don't allow '?', '&', and ',' for label titles
validates :title, validates :title,
...@@ -34,6 +34,8 @@ class Label < ActiveRecord::Base ...@@ -34,6 +34,8 @@ class Label < ActiveRecord::Base
default_scope { order(title: :asc) } default_scope { order(title: :asc) }
scope :templates, -> { where(template: true) }
alias_attribute :name, :title alias_attribute :name, :title
def self.reference_prefix def self.reference_prefix
...@@ -78,4 +80,8 @@ class Label < ActiveRecord::Base ...@@ -78,4 +80,8 @@ class Label < ActiveRecord::Base
def open_issues_count def open_issues_count
issues.opened.count issues.opened.count
end end
def template?
template
end
end end
...@@ -43,6 +43,8 @@ class Project < ActiveRecord::Base ...@@ -43,6 +43,8 @@ class Project < ActiveRecord::Base
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
extend Enumerize extend Enumerize
UNKNOWN_IMPORT_URL = 'http://unknown.git'
default_value_for :archived, false default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :issues_enabled, gitlab_config_features.issues default_value_for :issues_enabled, gitlab_config_features.issues
...@@ -401,6 +403,15 @@ class Project < ActiveRecord::Base ...@@ -401,6 +403,15 @@ class Project < ActiveRecord::Base
end end
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) def find_service(list, name)
list.find { |service| service.to_param == name } list.find { |service| service.to_param == name }
end end
......
...@@ -87,6 +87,8 @@ module Projects ...@@ -87,6 +87,8 @@ module Projects
@project.build_missing_services @project.build_missing_services
@project.create_labels
event_service.create_project(@project, current_user) event_service.create_project(@project, current_user)
system_hook_service.execute_hooks_for(@project, :create) system_hook_service.execute_hooks_for(@project, :create)
......
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
= 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
%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?"}
- if @labels.size == 0
$('.labels').load(document.URL + ' .light-well').hide().fadeIn(1000)
- 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'
- 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
- page_title "New Label"
%h3 New label
.back-link
= link_to admin_labels_path do
&larr; To labels list
%hr
= render 'form'
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
%h4 .row
.col-sm-6
%strong
= link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title) = 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 .row
.col-sm-6 .col-sm-6
= link_to issues_dashboard_path(milestone_title: milestone.title) do = link_to issues_dashboard_path(milestone_title: milestone.title) do
= pluralize milestone.issue_count, 'Issue' = pluralize milestone.issue_count, 'Issue'
&nbsp; &middot;
= link_to merge_requests_dashboard_path(milestone_title: milestone.title) do = link_to merge_requests_dashboard_path(milestone_title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request' = pluralize milestone.merge_requests_count, 'Merge Request'
&nbsp;
%span.light #{milestone.percent_complete}% complete
.col-sm-6 .col-sm-6
= milestone_progress_bar(milestone) = milestone_progress_bar(milestone)
%div .row
.col-sm-6
- milestone.milestones.each do |milestone| - milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do = link_to milestone_path(milestone) do
%span.label.label-gray %span.label.label-gray
......
- @blank_container = true
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%strong= @group.name %strong= @group.name
......
- page_title "Members" - page_title "Members"
- header_title group_title(@group, "Members", group_group_members_path(@group))
- show_roles = should_user_see_group_roles?(current_user, @group) - show_roles = should_user_see_group_roles?(current_user, @group)
%h3.page-title
Group members
- if show_roles - if show_roles
%p.light %p.light
Members of group have access to all group projects. Members of group have access to all group projects.
Read more about permissions Read more about permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
%hr
.clearfix.js-toggle-container .clearfix.js-toggle-container
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
......
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
.pull-right .row
- if can?(current_user, :admin_group, @group) .col-sm-6
- if milestone.closed? %strong
= 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) = 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 .row
.col-sm-6 .col-sm-6
= link_to issues_group_path(@group, milestone_title: milestone.title) do = link_to issues_group_path(@group, milestone_title: milestone.title) do
= pluralize milestone.issue_count, 'Issue' = pluralize milestone.issue_count, 'Issue'
&nbsp; &middot;
= link_to merge_requests_group_path(@group, milestone_title: milestone.title) do = link_to merge_requests_group_path(@group, milestone_title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request' = pluralize milestone.merge_requests_count, 'Merge Request'
&nbsp;
%span.light #{milestone.percent_complete}% complete
.col-sm-6 .col-sm-6
= milestone_progress_bar(milestone) = milestone_progress_bar(milestone)
.row
.col-sm-6
%div %div
- milestone.milestones.each do |milestone| - milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do = link_to milestone_path(milestone) do
%span.label.label-gray %span.label.label-gray
= milestone.project.name = 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"
- 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'
- 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()
- 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}")
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
%meta{charset: "utf-8"} %meta{charset: "utf-8"}
%meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'}
%meta{content: "GitLab Community Edition", name: "description"} %meta{content: "GitLab Community Edition", name: "description"}
%meta{name: 'referrer', content: 'origin'}
%title= page_title %title= page_title
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
= current_user.username = current_user.username
.content-wrapper .content-wrapper
= render "layouts/flash" = render "layouts/flash"
%div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" } %div{ class: container_class }
.content .content
.clearfix .clearfix
= yield = yield
...@@ -57,6 +57,12 @@ ...@@ -57,6 +57,12 @@
%span %span
Service Templates 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 = nav_link(controller: :abuse_reports) do
= link_to admin_abuse_reports_path, title: "Abuse reports" do = link_to admin_abuse_reports_path, title: "Abuse reports" do
= icon('exclamation-circle fw') = icon('exclamation-circle fw')
......
...@@ -36,13 +36,13 @@ ...@@ -36,13 +36,13 @@
= icon('clipboard fw') = icon('clipboard fw')
%span %span
Snippets 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 = nav_link(controller: :help) do
= link_to help_path, title: 'Help', data: {placement: 'right'} do = link_to help_path, title: 'Help', data: {placement: 'right'} do
= icon('question-circle fw') = icon('question-circle fw')
%span %span
Help Help
= nav_link(controller: :profile) do
= link_to profile_path, title: 'Profile settings', data: {placement: 'bottom'} do
= icon('user fw')
%span
Profile Settings
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
= link_to profile_path, title: 'Profile', data: {placement: 'right'} do = link_to profile_path, title: 'Profile', data: {placement: 'right'} do
= icon('user fw') = icon('user fw')
%span %span
Profile Profile Settings
= nav_link(controller: [:accounts, :two_factor_auths]) do = nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account', data: {placement: 'right'} do = link_to profile_account_path, title: 'Account', data: {placement: 'right'} do
= icon('gear fw') = icon('gear fw')
......
- page_title "Profile Settings" - page_title "Profile Settings"
- header_title "Profile Settings", profile_path - unless @header_title
- header_title "Profile Settings", profile_path
- sidebar "profile" - sidebar "profile"
= render template: "layouts/application" = render template: "layouts/application"
- page_title "Account" - page_title "Account"
%h3.page-title - header_title page_title, profile_account_path
= page_title - @blank_container = true
%p.light
Change your username and basic account settings.
%hr
- if current_user.ldap_user? - if current_user.ldap_user?
.alert.alert-info .alert.alert-info
Some options are unavailable for LDAP accounts Some options are unavailable for LDAP accounts
......
- page_title "Applications" - page_title "Applications"
%h3.page-title - header_title page_title, applications_profile_path
= page_title
%p.light .gray-content-block.top-block
- if user_oauth_applications? - 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. and applications that you've authorized to use your account.
- else - else
Manage applications that you've authorized to use your account. Manage applications that you've authorized to use your account.
%hr
- if user_oauth_applications? - if user_oauth_applications?
.oauth-applications .oauth-applications
......
- page_title "Audit Log" - page_title "Audit Log"
%h3.page-title Audit Log - header_title page_title, audit_log_profile_path
%p.light History of authentications
.gray-content-block.top-block
History of authentications
.prepend-top-default
= render 'event_table', events: @events = render 'event_table', events: @events
- page_title "Emails" - page_title "Emails"
%h3.page-title - header_title page_title, profile_emails_path
= page_title
%p.light
Control emails linked to your account
%hr
.gray-content-block.top-block
Control emails linked to your account
%ul %ul.prepend-top-default
%li %li
Your Your
%b Primary Email %b Primary Email
......
- page_title "SSH Keys" - page_title "SSH Keys"
%h3.page-title - header_title page_title, profile_keys_path
= page_title
.gray-content-block.top-block
.pull-right .pull-right
= link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new" = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
%p.light .oneline
Before you can add an SSH key you need to Before you can add an SSH key you need to
= link_to "generate it.", help_page_path("ssh", "README") = link_to "generate it.", help_page_path("ssh", "README")
%hr
.prepend-top-default
= render 'key_table' = render 'key_table'
- page_title "Notifications" - page_title "Notifications"
%h3.page-title - header_title page_title, profile_notifications_path
= page_title
%p.light .gray-content-block.top-block
These are your global notification settings. 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| = form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications form-horizontal global-notifications-form' } do |f|
-if @user.errors.any? -if @user.errors.any?
%div.alert.alert-danger %div.alert.alert-danger
......
- page_title "Password" - page_title "Password"
%h3.page-title - header_title page_title, edit_profile_password_path
= page_title
%p.light .gray-content-block.top-block
- if @user.password_automatically_set? - if @user.password_automatically_set?
Set your password. Set your password.
- else - else
Change your password or recover your current one. 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| = form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f|
%div %div
%p.slead %p.slead
......
- page_title 'Preferences' - page_title 'Preferences'
%h3.page-title - header_title page_title, profile_preferences_path
= page_title - @blank_container = true
%p.light
.alert.alert-help
These settings allow you to customize the appearance and behavior of the site. 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 They are saved with your account and will persist to any device you use to
access the site. access the site.
%hr
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'js-preferences-form form-horizontal'} do |f| = 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 .panel.panel-default.application-theme
......
- page_title "Profile" .gray-content-block.top-block
%h3.page-title
= page_title
%p.light
This information will appear on your profile. This information will appear on your profile.
- if current_user.ldap_user? - if current_user.ldap_user?
Some options are unavailable for LDAP accounts 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| = 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? -if @user.errors.any?
%div.alert.alert-danger %div.alert.alert-danger
......
.md-area .md-area
.md-header.clearfix .md-header.clearfix
%ul.nav.nav-tabs %ul.center-top-menu
%li.active %li.active
= link_to '#md-write-holder', class: 'js-md-write-button', tabindex: '-1' do = link_to '#md-write-holder', class: 'js-md-write-button', tabindex: '-1' do
Write Write
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
.commit-info-row.branches .commit-info-row.branches
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
.commit-box .commit-box.gray-content-block.middle-block
%h3.commit-title %h3.commit-title
= gfm escape_once(@commit.title) = gfm escape_once(@commit.title)
- if @commit.description.present? - if @commit.description.present?
......
- if params[:view] == 'parallel' - if params[:view] == 'parallel'
- fluid_layout true - fluid_layout true
.prepend-top-20.append-bottom-20 .gray-content-block.second-block
.pull-right .inline-parallel-buttons
.btn-group .btn-group
= inline_diff_btn = inline_diff_btn
= parallel_diff_btn = parallel_diff_btn
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
and and
%strong.cred #{@commit.stats.deletions} deletions %strong.cred #{@commit.stats.deletions} deletions
.file-stats.js-toggle-content.hide .file-stats.js-toggle-content.hide
%ul.bordered-list %ul
- diffs.each_with_index do |diff, i| - diffs.each_with_index do |diff, i|
%li %li
- if diff.deleted_file - if diff.deleted_file
......
- @blank_container = true
.project-edit-container .project-edit-container
.project-edit-errors .project-edit-errors
.project-edit-content .project-edit-content
%div .panel.panel-default
%h3.page-title .panel-heading
Project settings Project settings
%hr
.panel-body .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| = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit_project form-horizontal fieldset-form" }, authenticity_token: true do |f|
......
...@@ -22,15 +22,15 @@ ...@@ -22,15 +22,15 @@
%h5 Git global setup %h5 Git global setup
%pre.light-well %pre.light-well
:preserve :preserve
git config --global user.name "#{git_user_name}" git config --global user.name "#{h git_user_name}"
git config --global user.email "#{git_user_email}" git config --global user.email "#{h git_user_email}"
%fieldset %fieldset
%h5 Create a new repository %h5 Create a new repository
%pre.light-well %pre.light-well
:preserve :preserve
git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')} git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
cd #{@project.path} cd #{h @project.path}
touch README.md touch README.md
git add README.md git add README.md
git commit -m "add README" git commit -m "add README"
......
...@@ -7,21 +7,24 @@ ...@@ -7,21 +7,24 @@
= render 'shared/show_aside' = render 'shared/show_aside'
.row .gray-content-block.second-block
%section.col-md-9 .row
.col-md-9
.votes-holder.pull-right .votes-holder.pull-right
#votes= render 'votes/votes_block', votable: @issue #votes= render 'votes/votes_block', votable: @issue
.participants .participants
%span= pluralize(@participants.count, 'participant') %span= pluralize(@participants.count, 'participant')
- @participants.each do |participant| - @participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24) = 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
.voting_notes#notes= render 'projects/notes/notes_with_form' .voting_notes#notes= render 'projects/notes/notes_with_form'
%aside.col-md-3 %aside.col-md-3
.issuable-affix .issuable-affix
.clearfix
%span.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @issue)
%hr
.context .context
= render 'shared/issuable/context', issuable: @issue = render 'shared/issuable/context', issuable: @issue
......
...@@ -41,4 +41,4 @@ ...@@ -41,4 +41,4 @@
= issue.task_status = issue.task_status
.pull-right.issue-updated-at .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')}
- page_title "#{@issue.title} (##{@issue.iid})", "Issues" - page_title "#{@issue.title} (##{@issue.iid})", "Issues"
.issue .issue
.issue-details.issuable-details .issue-details.issuable-details
%h4.page-title .page-title
.issue-box{ class: issue_box_class(@issue) } .issue-box{ class: issue_box_class(@issue) }
- if @issue.closed? - if @issue.closed?
Closed Closed
- else - else
Open Open
Issue ##{@issue.iid} %span.issue-id Issue ##{@issue.iid}
%small.creator %span.creator
&middot; created by #{link_to_member(@project, @issue.author)} &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') = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
- if @issue.updated_at != @issue.created_at - if @issue.updated_at != @issue.created_at
%span %span
...@@ -32,7 +33,7 @@ ...@@ -32,7 +33,7 @@
= icon('pencil-square-o') = icon('pencil-square-o')
Edit Edit
%hr .gray-content-block.middle-block
%h2.issue-title %h2.issue-title
= gfm escape_once(@issue.title) = gfm escape_once(@issue.title)
%div %div
...@@ -44,6 +45,5 @@ ...@@ -44,6 +45,5 @@
%textarea.hidden.js-task-list-field %textarea.hidden.js-task-list-field
= @issue.description = @issue.description
%hr
.issue-discussion .issue-discussion
= render 'projects/issues/discussion' = render 'projects/issues/discussion'
...@@ -7,18 +7,21 @@ ...@@ -7,18 +7,21 @@
= render 'shared/show_aside' = render 'shared/show_aside'
.row .gray-content-block.second-block
%section.col-md-9 .row
.col-md-9
.votes-holder.pull-right .votes-holder.pull-right
#votes= render 'votes/votes_block', votable: @merge_request #votes= render 'votes/votes_block', votable: @merge_request
= render "projects/merge_requests/show/participants" = 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
= render "projects/notes/notes_with_form" = render "projects/notes/notes_with_form"
%aside.col-md-3 %aside.col-md-3
.issuable-affix .issuable-affix
.clearfix
%span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
= cross_project_reference(@project, @merge_request)
%hr
.context .context
= render 'shared/issuable/context', issuable: @merge_request = render 'shared/issuable/context', issuable: @merge_request
......
...@@ -43,4 +43,4 @@ ...@@ -43,4 +43,4 @@
= merge_request.task_status = merge_request.task_status
.pull-right.hidden-xs .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')}
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
%h4 Compare failed %h4 Compare failed
%p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches. %p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches.
- else - else
.light-well .light-well.append-bottom-10
.center .center
%h4 %h4
There isn't anything to merge. There isn't anything to merge.
......
...@@ -18,15 +18,13 @@ ...@@ -18,15 +18,13 @@
= f.hidden_field :target_branch = f.hidden_field :target_branch
.mr-compare.merge-request .mr-compare.merge-request
%ul.nav.nav-tabs.merge-request-tabs %ul.merge-request-tabs
%li.commits-tab %li.commits-tab
= link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do = link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
= icon('history')
Commits Commits
%span.badge= @commits.size %span.badge= @commits.size
%li.diffs-tab.active %li.diffs-tab.active
= link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do = link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
= icon('list-alt')
Changes Changes
%span.badge= @diffs.size %span.badge= @diffs.size
......
...@@ -5,10 +5,8 @@ ...@@ -5,10 +5,8 @@
.merge-request{'data-url' => merge_request_path(@merge_request)} .merge-request{'data-url' => merge_request_path(@merge_request)}
.merge-request-details.issuable-details .merge-request-details.issuable-details
= render "projects/merge_requests/show/mr_title" = render "projects/merge_requests/show/mr_title"
%hr
= render "projects/merge_requests/show/mr_box" = render "projects/merge_requests/show/mr_box"
%hr .append-bottom-20.mr-source-target.prepend-top-default
.append-bottom-20.mr-source-target
- if @merge_request.open? - if @merge_request.open?
.pull-right .pull-right
- if @merge_request.source_branch_exists? - if @merge_request.source_branch_exists?
...@@ -39,20 +37,17 @@ ...@@ -39,20 +37,17 @@
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- if @commits.present? - if @commits.present?
%ul.nav.nav-tabs.merge-request-tabs %ul.merge-request-tabs
%li.notes-tab %li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do
= icon('comments')
Discussion Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.count %span.badge= @merge_request.mr_and_commit_notes.user.count
%li.commits-tab %li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
= icon('history')
Commits Commits
%span.badge= @commits.size %span.badge= @commits.size
%li.diffs-tab %li.diffs-tab
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do = 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 Changes
%span.badge= @merge_request.diffs.size %span.badge= @merge_request.diffs.size
......
...@@ -11,12 +11,12 @@ ...@@ -11,12 +11,12 @@
%pre.dark %pre.dark
- if @merge_request.for_fork? - if @merge_request.for_fork?
:preserve :preserve
git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch} git fetch #{h @merge_request.source_project.http_url_to_repo} #{h @merge_request.source_branch}
git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD git checkout -b #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} FETCH_HEAD
- else - else
:preserve :preserve
git fetch origin 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 %p
%strong Step 2. %strong Step 2.
Review the changes locally Review the changes locally
...@@ -27,18 +27,18 @@ ...@@ -27,18 +27,18 @@
%pre.dark %pre.dark
- if @merge_request.for_fork? - if @merge_request.for_fork?
:preserve :preserve
git checkout #{@merge_request.target_branch} git checkout #{h @merge_request.target_branch}
git merge --no-ff #{@merge_request.source_project_path}-#{@merge_request.source_branch} git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch}
- else - else
:preserve :preserve
git checkout #{@merge_request.target_branch} git checkout #{h @merge_request.target_branch}
git merge --no-ff #{@merge_request.source_branch} git merge --no-ff #{h @merge_request.source_branch}
%p %p
%strong Step 4. %strong Step 4.
Push the result of the merge to GitLab Push the result of the merge to GitLab
%pre.dark %pre.dark
:preserve :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) - unless @merge_request.can_be_merged_by?(current_user)
%p %p
Note that pushing to GitLab requires write access to this repository. Note that pushing to GitLab requires write access to this repository.
......
%h2.issue-title .gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@merge_request.title) = gfm escape_once(@merge_request.title)
%div %div
- if @merge_request.description.present? - if @merge_request.description.present?
.description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''} .description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''}
.wiki .wiki
......
%h4.page-title .page-title
.issue-box{ class: issue_box_class(@merge_request) } .issue-box{ class: issue_box_class(@merge_request) }
= @merge_request.state_human_name = @merge_request.state_human_name
Merge Request ##{@merge_request.iid} %span.issue-id Merge Request ##{@merge_request.iid}
%small.creator %span.creator
&middot;
created by #{link_to_member(@project, @merge_request.author, size: 24)}
&middot; &middot;
created by #{link_to_member(@project, @merge_request.author)}
= time_ago_with_tooltip(@merge_request.created_at) = time_ago_with_tooltip(@merge_request.created_at)
- if @merge_request.updated_at != @merge_request.created_at - if @merge_request.updated_at != @merge_request.created_at
%span %span
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
= label_tag :should_remove_source_branch, class: "remove_source_checkbox" do = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
= check_box_tag :should_remove_source_branch = check_box_tag :should_remove_source_branch
Remove source branch Remove source branch
.accept-control .accept-control.right
= link_to "#", class: "modify-merge-commit-link js-toggle-button" do = link_to "#", class: "modify-merge-commit-link js-toggle-button" do
= icon('edit') = icon('edit')
Modify commit message Modify commit message
......
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) } %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
.pull-right .row
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active? .col-sm-6
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-sm edit-milestone-link btn-grouped" do %strong
%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
%h4
= link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) = 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) .col-sm-6
%small .pull-right.light #{milestone.percent_complete}% complete
= milestone.expires_at
.row .row
.col-sm-6 .col-sm-6
= link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
= pluralize milestone.issues.count, 'Issue' = pluralize milestone.issues.count, 'Issue'
&nbsp; &middot;
= link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
= pluralize milestone.merge_requests.count, 'Merge Request' = pluralize milestone.merge_requests.count, 'Merge Request'
&nbsp;
%span.light #{milestone.percent_complete}% complete
.col-sm-6 .col-sm-6
= milestone_progress_bar(milestone) = 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
...@@ -72,6 +72,11 @@ ...@@ -72,6 +72,11 @@
%i.fa.fa-google %i.fa.fa-google
Google Code 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? - if git_import_enabled?
= link_to "#", class: 'btn js-toggle-button import_git' do = link_to "#", class: 'btn js-toggle-button import_git' do
%i.fa.fa-git %i.fa.fa-git
......
%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 } } %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-entry-inner
.timeline-icon .timeline-icon
- if note.system
%span= icon('circle')
- else
= link_to user_path(note.author) do = link_to user_path(note.author) do
= image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: '' = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: ''
.timeline-content .timeline-content
...@@ -22,10 +19,6 @@ ...@@ -22,10 +19,6 @@
%span.note-role.label %span.note-role.label
= member.human_access = 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) = link_to_member(note.project, note.author, avatar: false)
%span.author-username %span.author-username
......
%span.pull-right %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?) - if (@page && @page.persisted?)
= link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
Page History Page History
...@@ -6,3 +11,5 @@ ...@@ -6,3 +11,5 @@
= link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
Edit Edit
= render 'projects/wikis/new'
%ul.nav.nav-tabs %ul.center-top-menu
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
= link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
...@@ -7,13 +7,4 @@ ...@@ -7,13 +7,4 @@
= nav_link(path: 'wikis#git_access') do = nav_link(path: 'wikis#git_access') do
= link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
%i.fa.fa-download
Git Access 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'
- page_title "Git Access", "Wiki" - page_title "Git Access", "Wiki"
= render 'nav' = render 'nav'
.row .gray-content-block
.row
.col-sm-6 .col-sm-6
%h3.page-title %h3.page-title
Git access for Git access for
...@@ -9,7 +10,7 @@ ...@@ -9,7 +10,7 @@
.col-sm-6 .col-sm-6
= render "shared/clone_panel", project: @project_wiki = render "shared/clone_panel", project: @project_wiki
.git-empty .git-empty.prepend-top-default
%fieldset %fieldset
%legend Install Gollum: %legend Install Gollum:
%pre.dark %pre.dark
...@@ -20,7 +21,7 @@ ...@@ -20,7 +21,7 @@
%pre.dark %pre.dark
:preserve :preserve
git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')} 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: %legend Start Gollum And Edit Locally:
%pre.dark %pre.dark
......
- page_title "History", @page.title, "Wiki" - page_title "History", @page.title, "Wiki"
= render 'nav' = render 'nav'
%h3.page-title .gray-content-block
%h3.page-title
%span.light History for %span.light History for
= link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page) = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
......
- page_title "All Pages", "Wiki" - page_title "All Pages", "Wiki"
= render 'nav' = render 'nav'
%h3.page-title .gray-content-block
%h3.page-title
All Pages All Pages
%ul.bordered-list %ul.content-list
- @wiki_pages.each do |wiki_page| - @wiki_pages.each do |wiki_page|
%li %li
%h4 %h4
......
- page_title @page.title, "Wiki" - page_title @page.title, "Wiki"
= render 'nav' = render 'nav'
%h3.page-title
= @page.title .gray-content-block
= render 'main_links' = render 'main_links'
%h3.page-title
= @page.title.capitalize
.wiki-last-edit-by .wiki-last-edit-by
Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
- if @page.historical? - if @page.historical?
...@@ -12,14 +14,13 @@ ...@@ -12,14 +14,13 @@
This is an old version of this page. 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)}. 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 .wiki
= preserve do = preserve do
= render_wiki_content(@page) = render_wiki_content(@page)
%hr .gray-content-block.footer-block
.wiki-last-edit-by .wiki-last-edit-by
Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
= f.label :title, class: 'control-label' do = f.label :title, class: 'control-label' do
%strong= 'Title *' %strong= 'Title *'
.col-sm-10 .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 class: 'form-control pad js-gfm-input', required: true
- if issuable.is_a?(MergeRequest) - if issuable.is_a?(MergeRequest)
......
...@@ -7,19 +7,28 @@ class RepositoryImportWorker ...@@ -7,19 +7,28 @@ class RepositoryImportWorker
def perform(project_id) def perform(project_id)
project = Project.find(project_id) project = Project.find(project_id)
unless project.import_url == Project::UNKNOWN_IMPORT_URL
import_result = gitlab_shell.send(:import_repository, import_result = gitlab_shell.send(:import_repository,
project.path_with_namespace, project.path_with_namespace,
project.import_url) 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' data_import_result = case project.import_type
when 'github'
Gitlab::GithubImport::Importer.new(project).execute Gitlab::GithubImport::Importer.new(project).execute
elsif project.import_type == 'gitlab' when 'gitlab'
Gitlab::GitlabImport::Importer.new(project).execute Gitlab::GitlabImport::Importer.new(project).execute
elsif project.import_type == 'bitbucket' when 'bitbucket'
Gitlab::BitbucketImport::Importer.new(project).execute Gitlab::BitbucketImport::Importer.new(project).execute
elsif project.import_type == 'google_code' when 'google_code'
Gitlab::GoogleCodeImport::Importer.new(project).execute Gitlab::GoogleCodeImport::Importer.new(project).execute
when 'fogbugz'
Gitlab::FogbugzImport::Importer.new(project).execute
else else
true true
end end
......
...@@ -158,7 +158,7 @@ Settings.gitlab.default_projects_features['snippets'] = false if Settings. ...@@ -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.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['repository_downloads_path'] = File.absolute_path(Settings.gitlab['repository_downloads_path'] || 'tmp/repositories', Rails.root)
Settings.gitlab['restricted_signup_domains'] ||= [] 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 # Reply by email
......
# 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
...@@ -2,7 +2,12 @@ ...@@ -2,7 +2,12 @@
require 'gitlab/current_settings' require 'gitlab/current_settings'
include Gitlab::CurrentSettings 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( Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks. :redis_store, # Using the cookie_store would enable session replay attacks.
......
...@@ -99,6 +99,15 @@ Gitlab::Application.routes.draw do ...@@ -99,6 +99,15 @@ Gitlab::Application.routes.draw do
get :new_user_map, path: :user_map get :new_user_map, path: :user_map
post :create_user_map, path: :user_map post :create_user_map, path: :user_map
end 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 end
# #
...@@ -202,6 +211,8 @@ Gitlab::Application.routes.draw do ...@@ -202,6 +211,8 @@ Gitlab::Application.routes.draw do
resources :services resources :services
end end
resources :labels
root to: 'dashboard#index' root to: 'dashboard#index'
end end
......
class AddTemplateToLabel < ActiveRecord::Migration
def change
add_column :labels, :template, :boolean, default: false
end
end
\ No newline at end of file
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -186,6 +186,7 @@ ActiveRecord::Schema.define(version: 20150824002011) do ...@@ -186,6 +186,7 @@ ActiveRecord::Schema.define(version: 20150824002011) do
t.integer "project_id" t.integer "project_id"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.boolean "template", default: false
end end
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
......
...@@ -299,7 +299,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -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. 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): # 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. # By default, the gitlab-shell config is generated from your main GitLab config.
# You can review (and modify) the gitlab-shell config as follows: # You can review (and modify) the gitlab-shell config as follows:
......
...@@ -127,7 +127,7 @@ sudo apt-get install nodejs ...@@ -127,7 +127,7 @@ sudo apt-get install nodejs
```bash ```bash
cd /home/git/gitlab-shell cd /home/git/gitlab-shell
sudo -u git -H git fetch 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. ## 7. Install libs, migrations, etc.
...@@ -162,12 +162,12 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ...@@ -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: 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/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/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 * Copy rack attack middleware config
```bash ```bash
......
...@@ -63,7 +63,7 @@ sudo -u git -H git checkout 7-14-stable-ee ...@@ -63,7 +63,7 @@ sudo -u git -H git checkout 7-14-stable-ee
```bash ```bash
cd /home/git/gitlab-shell cd /home/git/gitlab-shell
sudo -u git -H git fetch 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. ### 5. Install libs, migrations, etc.
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
1. [Bitbucket](import_projects_from_bitbucket.md) 1. [Bitbucket](import_projects_from_bitbucket.md)
2. [GitHub](import_projects_from_github.md) 2. [GitHub](import_projects_from_github.md)
3. [GitLab.com](import_projects_from_gitlab_com.md) 3. [GitLab.com](import_projects_from_gitlab_com.md)
4. [FogBugz](import_projects_from_fogbugz.md)
4. [SVN](migrating_from_svn.md) 4. [SVN](migrating_from_svn.md)
### Note ### Note
......
# 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)
...@@ -25,6 +25,9 @@ RUN mkdir -p /opt/gitlab/sv/sshd/supervise \ ...@@ -25,6 +25,9 @@ RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
&& ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \ && ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
&& mkdir -p /var/run/sshd && 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 # Prepare default configuration
RUN ( \ RUN ( \
echo "" && \ echo "" && \
......
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
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
...@@ -4,11 +4,13 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps ...@@ -4,11 +4,13 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
include SharedAdmin include SharedAdmin
before do 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 end
after do 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 end
step 'I should see all users' do step 'I should see all users' do
......
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
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
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
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
...@@ -19,6 +19,7 @@ module Gitlab ...@@ -19,6 +19,7 @@ module Gitlab
'GitLab.com' => 'gitlab', 'GitLab.com' => 'gitlab',
'Gitorious.org' => 'gitorious', 'Gitorious.org' => 'gitorious',
'Google Code' => 'google_code', 'Google Code' => 'google_code',
'FogBugz' => 'fogbugz',
'Any repo by URL' => 'git', 'Any repo by URL' => 'git',
} }
end end
......
...@@ -77,7 +77,7 @@ module Gitlab ...@@ -77,7 +77,7 @@ module Gitlab
pipeline: options[:pipeline], pipeline: options[:pipeline],
# EmojiFilter # EmojiFilter
asset_root: Gitlab.config.gitlab.url, asset_root: Gitlab.config.gitlab.base_url,
asset_host: Gitlab::Application.config.asset_host, asset_host: Gitlab::Application.config.asset_host,
# TableOfContentsFilter # TableOfContentsFilter
......
...@@ -124,6 +124,15 @@ server { ...@@ -124,6 +124,15 @@ server {
proxy_connect_timeout 300; proxy_connect_timeout 300;
proxy_redirect off; 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 Host $http_host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
......
...@@ -171,6 +171,15 @@ server { ...@@ -171,6 +171,15 @@ server {
proxy_connect_timeout 300; proxy_connect_timeout 300;
proxy_redirect off; 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 Host $http_host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Ssl on; proxy_set_header X-Forwarded-Ssl on;
......
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
...@@ -17,6 +17,14 @@ describe Projects::CreateService do ...@@ -17,6 +17,14 @@ describe Projects::CreateService do
expect(project.services).not_to be_empty expect(project.services).not_to be_empty
end 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 context 'user namespace' do
before do before do
@project = create_project(@user, @opts) @project = create_project(@user, @opts)
......
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
...@@ -28,6 +28,7 @@ RSpec.configure do |config| ...@@ -28,6 +28,7 @@ RSpec.configure do |config|
config.include LoginHelpers, type: :feature config.include LoginHelpers, type: :feature
config.include LoginHelpers, type: :request config.include LoginHelpers, type: :request
config.include StubConfiguration config.include StubConfiguration
config.include RelativeUrl, type: feature
config.include TestEnv config.include TestEnv
config.infer_spec_type_from_file_location! config.infer_spec_type_from_file_location!
......
...@@ -27,6 +27,9 @@ module MarkdownMatchers ...@@ -27,6 +27,9 @@ module MarkdownMatchers
match do |actual| match do |actual|
expect(actual).to have_selector('img.emoji', count: 10) 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
end end
......
# 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment