diff options
124 files changed, 1746 insertions, 660 deletions
| diff --git a/.gitignore b/.gitignore index 8c626989bb8..725f289db55 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ log/*.log  tmp/  .sass-cache/  coverage/* +backups/*  *.swp  public/uploads/  .rvmrc diff --git a/CHANGELOG b/CHANGELOG index 172357603cb..ec5d2f220d3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,13 @@  v 2.7.0    - Issue Labels +  - Inline diff +  - Git HTTP +  - API +  - UI improved +  - System hooks +  - UI improved +  - Dashboard events endless scroll +  - Source perfomance increased  v 2.6.0    - UI polished @@ -7,7 +7,7 @@ gem "sqlite3"  gem "mysql2"  # Auth -gem "devise", "~> 1.5" +gem "devise", "~> 2.1.0"  # GITLAB patched libs  gem "grit",          :git => "https://github.com/gitlabhq/grit.git",            :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837" @@ -71,7 +71,6 @@ group :development, :test do    gem "awesome_print"    gem "database_cleaner"    gem "launchy" -  gem "webmock"  end  group :test do @@ -82,4 +81,5 @@ group :test do    gem "shoulda-matchers"    gem 'email_spec'    gem 'resque_spec' +  gem "webmock"  end diff --git a/Gemfile.lock b/Gemfile.lock index e6a488f2753..e4c06fed229 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -148,10 +148,11 @@ GEM        nokogiri (>= 1.5.0)      daemons (1.1.8)      database_cleaner (0.8.0) -    devise (1.5.3) +    devise (2.1.2)        bcrypt-ruby (~> 3.0) -      orm_adapter (~> 0.0.3) -      warden (~> 1.1) +      orm_adapter (~> 0.1) +      railties (~> 3.1) +      warden (~> 1.2.1)      diff-lcs (1.1.3)      drapper (0.8.4)      email_spec (1.2.1) @@ -225,7 +226,7 @@ GEM      omniauth (1.1.0)        hashie (~> 1.2)        rack -    orm_adapter (0.0.7) +    orm_adapter (0.3.0)      polyglot (0.3.3)      posix-spawn (0.3.6)      pry (0.9.9.6) @@ -356,7 +357,7 @@ GEM        raindrops (~> 0.7)      vegas (0.1.11)        rack (>= 1.0.0) -    warden (1.2.0) +    warden (1.2.1)        rack (>= 1.0)      webmock (1.8.7)        addressable (>= 2.2.7) @@ -383,7 +384,7 @@ DEPENDENCIES    colored    cucumber-rails    database_cleaner -  devise (~> 1.5) +  devise (~> 2.1.0)    drapper    email_spec    ffaker @@ -1 +1 @@ -2.7.0pre +2.7.0 diff --git a/app/assets/images/ajax_loader_tree.gif b/app/assets/images/ajax_loader_tree.gifBinary files differ new file mode 100644 index 00000000000..99d5a0f37f3 --- /dev/null +++ b/app/assets/images/ajax_loader_tree.gif diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 42290f8e154..527b5c795e1 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,6 +12,7 @@  //= require jquery.cookie  //= require jquery.endless-scroll  //= require jquery.highlight +//= require jquery.waitforimages  //= require bootstrap-modal  //= require modernizr  //= require chosen-jquery @@ -20,10 +21,26 @@  //= require_tree .  $(document).ready(function(){ +    $(".one_click_select").live("click", function(){      $(this).select();    }); + +  $('body').on('ajax:complete, ajax:beforeSend, submit', 'form', function(e){ +    var buttons = $('[type="submit"]', this); +    switch( e.type ){ +      case 'ajax:beforeSend': +      case 'submit': +        buttons.attr('disabled', 'disabled'); +      break; +      case ' ajax:complete': +      default: +        buttons.removeAttr('disabled'); +      break; +    } +  }) +    $(".account-box").mouseenter(showMenu);    $(".account-box").mouseleave(resetMenu); @@ -97,3 +114,8 @@ function showDiff(link) {              return _chosen.apply(this, [default_options]);      }})  })(jQuery); + + +function ajaxGet(url) {  +  $.ajax({type: "GET", url: url, dataType: "script"});  +} diff --git a/app/assets/javascripts/issues.js b/app/assets/javascripts/issues.js index 49936e3f0ee..0acf9ec8aef 100644 --- a/app/assets/javascripts/issues.js +++ b/app/assets/javascripts/issues.js @@ -73,4 +73,25 @@ function issuesPage(){    $("#milestone_id, #assignee_id, #label_name").on("change", function(){      $(this).closest("form").submit();    }); + +  $('body').on('ajax:success', '.close_issue, .reopen_issue, #new_issue', function(){ +    var t = $(this), +        totalIssues, +        reopen = t.hasClass('reopen_issue'), +        newIssue = false; +    if( this.id == 'new_issue' ){ +      newIssue = true; +    } +    $('.issue_counter, #new_issue').each(function(){ +      var issue = $(this); +      totalIssues = parseInt( $(this).html(), 10 ); + +      if( newIssue || ( reopen && issue.closest('.main_menu').length ) ){ +        $(this).html( totalIssues+1 ); +      }else { +        $(this).html( totalIssues-1 ); +      } +    }); + +  });  } diff --git a/app/assets/javascripts/note.js b/app/assets/javascripts/note.js index 4d97ffefdce..c45a45d2fcb 100644 --- a/app/assets/javascripts/note.js +++ b/app/assets/javascripts/note.js @@ -25,11 +25,11 @@ init:        $(this).closest('li').fadeOut(); });      $("#new_note").live("ajax:before", function(){ -      $("#submit_note").attr("disabled", "disabled"); +      $(".submit_note").attr("disabled", "disabled");      })      $("#new_note").live("ajax:complete", function(){ -      $("#submit_note").removeAttr("disabled"); +      $(".submit_note").removeAttr("disabled");      })      $("#note_note").live("focus", function(){ diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 19092073c17..0db803aae23 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -604,7 +604,11 @@ li.note {    border-style: solid;    border-width: 1px;    @include border-radius(4px); -  min-height:42px; +  min-height:22px; + +  .avatar {  +    width:24px; +  }  }  .supp_diff_link, diff --git a/app/assets/stylesheets/gitlab_bootstrap.scss b/app/assets/stylesheets/gitlab_bootstrap.scss index 1b86cddee39..39e5998305a 100644 --- a/app/assets/stylesheets/gitlab_bootstrap.scss +++ b/app/assets/stylesheets/gitlab_bootstrap.scss @@ -202,6 +202,10 @@ a:focus {    color:$style_color;  } +.nav-tabs > .active > a {  +  font-weight:bold; +} +  /** COLORS **/  .cgray { color:gray; }  .cred { color:#D12F19; } @@ -209,6 +213,7 @@ a:focus {  .cblack { color:#111; }  .cdark { color:#444 }  .cwhite { color:#fff !important } +.bgred { background: #F2DEDE !important}  /** COMMON STYLES **/  .left { @@ -299,9 +304,24 @@ table.no-borders {  }  .event_label {  -  background: #FCEEC1; -  padding: 2px 2px 0; -  font-family: monospace; +  @extend .label; +  background-color: #999; + +  &.pushed {  +    background-color: #3A87AD; +  } + +  &.opened {  +    background-color: #468847; +  } + +  &.closed {  +    background-color: #B94A48; +  } + +  &.merged {  +    background-color: #2A2; +  }  }  img.avatar {  @@ -425,9 +445,10 @@ form {   */  .ui-box {     background:#F9F9F9; -  margin-bottom: 40px; +  margin-bottom: 25px;    @include round-borders-all(4px);    border-color: #CCC; +  @include solid_shade;    ul {       margin:0; @@ -443,6 +464,13 @@ form {      background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);      background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); +    &.small {  +      line-height: 28px; +      font-size: 14px; +      line-height:28px; +      text-shadow: 0 1px 1px white; +    } +      form {         padding:9px 0;        margin:0px; @@ -511,6 +539,7 @@ form {  table.admin-table {    @extend .table-bordered;     @extend .zebra-striped; +  @include solid_shade;    th {       border-color: #CCC;      border-bottom: 1px solid #bbb; @@ -568,6 +597,8 @@ ul.breadcrumb {    @extend .prepend-top-20;    @extend .append-bottom-20;    border-width:1px; +  @include solid_shade; +    img { max-width: 100%; } @@ -624,13 +655,166 @@ p {  h3.page_title {     color:#456;    font-size:20px; -  font-weight: 600; +  font-weight: normal;    line-height: 28px;  } -pre.logs {  -  .log {  -    font-size:12px; -    line-height:18px; +/** + * File content holder  + * + */ +.file_holder {  +  border:1px solid #CCC; +  margin-bottom:1em; +  @include solid_shade; + +  .file_title {  +    border-bottom: 1px solid #bbb; +    background:#eee; +    background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); +    background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); +    background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); +    background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); +    margin: 0; +    font-weight: normal; +    font-weight: bold; +    text-align: left; +    color: #666; +    padding: 9px 10px; +    height:18px; + +    .options {  +      float:right; +      margin-top: -5px; +    } + +    .file_name {  +      color:$style_color; +      font-size:14px; +      text-shadow: 0 1px 1px #fff; +      small {  +        color:#999; +        font-size:13px; +      } +    } +  } +  .file_content { +    background:#fff; +    font-size: 11px; + +    &.wiki {  +      font-size: 13px; +      code {  +        padding:0 4px; +      } +      padding:20px; +      h1, h2 {   +        line-height: 46px;   +      }   +      h3, h4 {   +        line-height: 40px;   +      }   +    } + +    &.image_file {  +      background:#eee; +      text-align:center; +      img { +        padding:100px; +        max-width:300px; +      } +    } + +    &.blob_file {  + +    } + +    /** +     *  Blame file +     */ +    &.blame {  +      tr {  +        border-bottom: 1px solid #eee; +      } +      td {  +        padding:5px; +      } +      .author,  +      .blame_commit {  +        background:#f5f5f5; +        vertical-align:top; +      } +      .lines {  +        pre {  +          padding:0; +          margin:0; +          background:none; +          border:none; +        } +      } +    } + +    &.logs {  +      background:#eee; +      max-height: 700px; +      overflow-y: auto; + +      ol {  +        margin-left:40px; +        padding: 10px 0; +        border-left: 1px solid #CCC; +        margin-bottom:0; +        background: white; +        li {  +          color:#888; +          p {  +            margin:0; +            color:#333; +            line-height:24px; +            padding-left: 10px; +          } + +          &:hover {  +            background:$hover; +          } +        } +      } +    } + +    /** +     *  Code file +     */ +    &.code {  +      padding:0; +      td.code { +        width: 100%; +        .highlight { +          margin-left: 55px; +          overflow:auto; +          overflow-y:hidden; +        } +      } +      .highlight pre { +        white-space: pre; +        word-wrap:normal; +      } + +      table.highlighttable { +        border: none; +      } +      body.project-page table.highlighttable td { border: none } +      table.highlighttable tr:hover { background:none;} + +      table.highlighttable pre{ +        line-height:16px !important; +        font-size:12px !important; +      } + +      table.highlighttable .linenodiv pre { +        text-align: right; +        padding-right: 4px; +        color:#666; +      } +    }    }  } diff --git a/app/assets/stylesheets/header.scss b/app/assets/stylesheets/header.scss index 5e2e410071b..07eba39b275 100644 --- a/app/assets/stylesheets/header.scss +++ b/app/assets/stylesheets/header.scss @@ -96,7 +96,7 @@ header {     */    .search {       float: right; -    margin-right: 55px; +    margin-right: 50px;      .search-input {        @extend .span2; @@ -126,10 +126,10 @@ header {      cursor: pointer;      img {        border-radius: 4px; -      right: 0px; +      right: 5px;        position: absolute; -      width: 33px; -      height: 33px; +      width: 31px; +      height: 31px;        display: block;        top: 0;        &:after { diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss index bff24dd68b9..7d60cd258e3 100644 --- a/app/assets/stylesheets/main.scss +++ b/app/assets/stylesheets/main.scss @@ -31,6 +31,12 @@ $hover: #FDF5D9;    box-shadow: 0 0 3px #ddd;  } +@mixin solid_shade {  +  -moz-box-shadow: 0 0 0 3px #eee; +  -webkit-box-shadow: 0 0 0 3px #eee; +  box-shadow: 0 0 0 3px #eee; +} +  @mixin border-radius($radius) {    -moz-border-radius: $radius;    -webkit-border-radius: $radius; @@ -136,7 +142,7 @@ $hover: #FDF5D9;  /**   * Code (files list) styles. Browsing project files there    */ -@import "tree.scss"; +@import "sections/tree.scss";  /**   * This file represent notes(comments) styles  diff --git a/app/assets/stylesheets/notes.scss b/app/assets/stylesheets/notes.scss index 70a31dbb0ff..39db704b1a9 100644 --- a/app/assets/stylesheets/notes.scss +++ b/app/assets/stylesheets/notes.scss @@ -63,18 +63,22 @@ p.notify_controls span{  tr.line_notes_row {     border-bottom:1px solid #DDD; +  border-left: 7px solid #2A79A3; +    &.reply {       background:#eee; - +    border-left: 7px solid #2A79A3; +    border-top:1px solid #ddd;      td {         padding:7px 10px;      }      a.line_note_reply_link {         @include round-borders-all(4px); -      border-color:#aaa; -      background: #bbb; -      padding: 3px 20px; +      padding: 3px 10px; +      margin-left:5px;        color: white; +      background: #2A79A3; +      border-color: #2A79A3;      }    }    ul {  @@ -95,6 +99,9 @@ tr.line_notes_row {    td {       border-bottom:1px solid #ddd;    } +  .actions {  +    margin:0; +  }  }  td .line_note_link {  diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index acab785ac71..6052ec3fabb 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -101,18 +101,21 @@        margin:50px;        padding:1px;        max-width:400px; -    } -    &.diff_image_removed {  -      img { + +      &.diff_image_removed {          border: 1px solid #C00;        } -    } -    &.diff_image_added {  -      img { +      &.diff_image_added {          border: 1px solid #0C0;;        }      } + +    &.img_compared {  +      img {  +        max-width:300px; +      } +    }    }  } diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index ad496238a13..34f43acf839 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -82,3 +82,15 @@      }    }  } + +li.merge_request {  +  padding:7px 10px; +  img.avatar {  +    width: 32px; +    margin-top: 4px; +  } +  p {  +    padding: 0px; +    padding-bottom: 2px; +  } +} diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss new file mode 100644 index 00000000000..c915e0a96fb --- /dev/null +++ b/app/assets/stylesheets/sections/tree.scss @@ -0,0 +1,96 @@ +#tree-holder {  +  #tree-content-holder { +    float:left; +    width:100%; +  } +  #tree-readme-holder { +    float:left; +    width:100%; +    .readme { +      border:1px solid #ccc; +      padding:12px; +      background: #F7F7F7; + +      pre {  +        overflow: auto; +      } +    } +  } + +  .tree_progress {  +    display:none; +    margin:20px; +    &.loading {  +      display:block; +    } +  } + +  #tree-slider { +    @include border-radius(0); +    .tree-item {  +      &:hover {  +        td { background: $hover; } +        cursor:pointer; +      } +    } +  } + +  .tree-item {  +    .tree-item-file-name {  +      vertical-align:middle; +      font-weight:bold; +      a {  +        color:$style_color; +        &:hover {  +          color:$blue_link; +        } +      } + +      img {  +        position: relative; +        top:-1px; +      } +    } +  } + + +  #tree-slider {  +    @include solid_shade; +    width:100%; + +    border-color:#ccc; + +    td {  +      padding:8px; +      border-color:#f1f1f1; +      background:#fafafa; +    } + +    tr:first-child td:first-child,  +    tr:first-child td:last-child {  +      border-radius:0; +    } + +    th {  +      border-color: #CCC; +      border-bottom: 1px solid #bbb; +      background:#eee; +      background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); +      background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); +      background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); +      background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); +    } +  } + +  .tree-commit-link {  +    color:#333; +  } + +  a.tree-commit-link {  +    color: #666; +    &:hover {  +      text-decoration: underline; +    } +  } + +} diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss index 0fea6144431..39dcab1d085 100644 --- a/app/assets/stylesheets/themes/ui_mars.scss +++ b/app/assets/stylesheets/themes/ui_mars.scss @@ -70,8 +70,7 @@          }        }        .separator { -        border-color:#444; -        background:#31363E; +        display:none;        }      } diff --git a/app/assets/stylesheets/tree.scss b/app/assets/stylesheets/tree.scss deleted file mode 100644 index 912c63ee16b..00000000000 --- a/app/assets/stylesheets/tree.scss +++ /dev/null @@ -1,232 +0,0 @@ -#tree-holder {  -  #tree-content-holder { -    float:left; -    width:100%; -  } -  #tree-readme-holder { -    float:left; -    width:100%; -    .readme { -      border:1px solid #ccc; -      padding:12px; -      background: #F7F7F7; - -      pre {  -        overflow: auto; -      } -    } -  } - -  .tree_progress {  -    display:none; -    margin:20px; -    &.loading {  -      display:block; -    } -  } - - -  /** FILE CONTENT VIEW **/ -  .view_file_content{ -    .old_line, .new_line { -      background:#ECECEC; -      color:#777; -      width:15px; -      float:left; -      padding: 0px 10px; -      border-right: 1px solid #ccc; -    } -    .old_line{ -      display:none; -    } -  } - -  .view_file .view_file_header, -  .diff_file .diff_file_header { -    border-bottom: 1px solid #bbb; -    background:#eee; -    background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); -    background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); -    background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); -    background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); -    margin: 0; -    font-weight: normal; -    font-weight: bold; -    text-align: left; -    color: #666; -    padding: 9px 10px; -    height:18px; - -    .options {  -      float:right; -      margin-top: -5px; -    } - -    .file_name {  -      color:$style_color; -      font-size:14px; -      text-shadow: 0 1px 1px #fff; -      small {  -        color:#999; -        font-size:13px; -      } -    } -  } - -  .view_file { -    border:1px solid #CCC; -    margin-bottom:1em; - -    .view_file_content { -      background:#fff; -      color:#514721; -      font-size: 11px; -    } -    .view_file_content_image { -      background:#eee; -      text-align:center; -      img { -        padding:100px; -        max-width:300px; -      } -    } -  } - -  td.code { -    width: 100%; -    .highlight { -      margin-left: 55px; -      overflow:auto; -      overflow-y:hidden; -    } -  } -  .highlight pre { -    white-space: pre; -    word-wrap:normal; -  } - -  table.highlighttable { -    border: none; -  } -  body.project-page table.highlighttable td { border: none } -  table.highlighttable tr:hover { background:none;} - -  table.highlighttable pre{ -    line-height:16px !important; -    font-size:12px !important; -  } - -  table.highlighttable .linenodiv pre { -    text-align: right; -    padding-right: 4px; -    color:#666; -  } - -  #tree-slider { -    @include border-radius(0); -    .tree-item {  -      &:hover {  -        td { background: $hover; } -        cursor:pointer; -      } -    } -  } - -  .tree-item {  -    .tree-item-file-name {  -      vertical-align:middle; -      font-weight:bold; -      a {  -        color:$style_color; -        &:hover {  -          color:$blue_link; -        } -      } - -      img {  -        position: relative; -        top:-1px; -      } -    } -  } - - -  #tree-slider {  -    @include shade; -    width:100%; - -    border-color:#ccc; - -    td {  -      padding:8px; -      border-color:#f1f1f1; -      background:#fafafa; -    } - -    tr:first-child td:first-child,  -    tr:first-child td:last-child {  -      border-radius:0; -    } - -    th {  -      border-color: #CCC; -      border-bottom: 1px solid #bbb; -      background:#eee; -      background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); -      background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); -      background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); -      background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); -    } -  } - -  .tree-commit-link {  -    color:#333; -  } - -  #tree-content-holder .view_file{  -    @include shade; -  } - -  #tree-readme-holder .readme {  -    @include shade; -    margin-bottom:20px; -    h1, h2 {   -      line-height: 56px;   - 	  }   -    h3, h4 {   - 	    line-height: 46px;   - 	  }   -  } - -  a.tree-commit-link {  -    color: #666; -    &:hover {  -      text-decoration: underline; -    } -  } - -} - -.blame_file {  -  .view_file_content { -    tr {  -      border-bottom: 1px solid #eee; -    } -    td {  -      padding:5px; -    } -    .author,  -    .commit {  -      background:#f5f5f5; -      vertical-align:top; -    } -    .lines {  -      pre {  -        padding:0; -        margin:0; -        background:none; -        border:none; -      } -    } -  } -} diff --git a/app/contexts/base_context.rb b/app/contexts/base_context.rb new file mode 100644 index 00000000000..6eb8ee46c80 --- /dev/null +++ b/app/contexts/base_context.rb @@ -0,0 +1,8 @@ +class BaseContext +  attr_accessor :project, :current_user, :params + +  def initialize(project, user, params) +    @project, @current_user, @params = project, user, params.dup +  end +end + diff --git a/app/contexts/commit_load.rb b/app/contexts/commit_load.rb new file mode 100644 index 00000000000..bab30d61007 --- /dev/null +++ b/app/contexts/commit_load.rb @@ -0,0 +1,26 @@ +class CommitLoad < BaseContext +  def execute +    result = {  +      :commit => nil, +      :suppress_diff => false, +      :line_notes => [], +      :notes_count => 0, +      :note => nil +    } + +    commit = project.commit(params[:id]) + +    if commit  +      commit = CommitDecorator.decorate(commit) +      line_notes = project.commit_line_notes(commit) + +      result[:suppress_diff] = true if commit.diffs.size > 200 && !params[:force_show_diff] +      result[:commit] = commit +      result[:note] = project.build_commit_note(commit) +      result[:line_notes] = line_notes +      result[:notes_count] = line_notes.count + project.commit_notes(commit).count +    end + +    result +  end +end diff --git a/app/contexts/merge_requests_load.rb b/app/contexts/merge_requests_load.rb new file mode 100644 index 00000000000..6778db3bce5 --- /dev/null +++ b/app/contexts/merge_requests_load.rb @@ -0,0 +1,16 @@ +class MergeRequestsLoad < BaseContext +  def execute +    type = params[:f].to_i + +    merge_requests = project.merge_requests + +    merge_requests = case type +                     when 1 then merge_requests +                     when 2 then merge_requests.closed +                     when 3 then merge_requests.opened.assigned(current_user) +                     else merge_requests.opened +                     end.page(params[:page]).per(20) + +    merge_requests.includes(:author, :project).order("closed, created_at desc") +  end +end diff --git a/app/contexts/notes_load.rb b/app/contexts/notes_load.rb new file mode 100644 index 00000000000..d1f8da9ce12 --- /dev/null +++ b/app/contexts/notes_load.rb @@ -0,0 +1,30 @@ +class NotesLoad < BaseContext +  def execute +    target_type = params[:target_type] +    target_id   = params[:target_id] +    first_id    = params[:first_id] +    last_id     = params[:last_id] + + +    @notes = case target_type +             when "commit"  +               then project.commit_notes(project.commit(target_id)).fresh.limit(20) +             when "snippet" +               then  project.snippets.find(target_id).notes +             when "wall" +               then project.common_notes.order("created_at DESC").fresh.limit(50) +             when "issue" +               then project.issues.find(target_id).notes.inc_author.order("created_at DESC").limit(20) +             when "merge_request" +               then project.merge_requests.find(target_id).notes.inc_author.order("created_at DESC").limit(20) +             end + +    @notes = if last_id +               @notes.where("id > ?", last_id) +             elsif first_id +               @notes.where("id < ?", first_id) +             else  +               @notes +             end +  end +end diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb new file mode 100644 index 00000000000..7f832fd5697 --- /dev/null +++ b/app/controllers/admin/hooks_controller.rb @@ -0,0 +1,44 @@ +class Admin::HooksController < ApplicationController +  layout "admin" +  before_filter :authenticate_user! +  before_filter :authenticate_admin! +   +  def index +    @hooks = SystemHook.all +    @hook = SystemHook.new +  end + +  def create +    @hook = SystemHook.new(params[:hook]) + +    if @hook.save +      redirect_to admin_hooks_path, notice: 'Hook was successfully created.' +    else +      @hooks = SystemHook.all +      render :index  +    end +  end + +  def destroy +    @hook = SystemHook.find(params[:id]) +    @hook.destroy + +    redirect_to admin_hooks_path +  end + + +  def test +    @hook = SystemHook.find(params[:hook_id]) +    data = { +      event_name: "project_create", +      name: "Ruby", +      path: "ruby", +      project_id: 1, +      owner_name: "Someone", +      owner_email: "example@gitlabhq.com" +    } +    @hook.execute(data) + +    redirect_to :back +  end +end diff --git a/app/controllers/admin/mailer_controller.rb b/app/controllers/admin/mailer_controller.rb deleted file mode 100644 index 2352e189204..00000000000 --- a/app/controllers/admin/mailer_controller.rb +++ /dev/null @@ -1,45 +0,0 @@ -class Admin::MailerController < ApplicationController -  layout "admin" -  before_filter :authenticate_user! -  before_filter :authenticate_admin! - -  def preview - -  end - -  def preview_note -    @note = Note.first -    @user = @note.author -    @project = @note.project -    case params[:type] -    when "Commit" then -      @commit = @project.commit -      render :file => 'notify/note_commit_email', :layout => 'notify' -    when "Issue" then -      @issue = Issue.first -      render :file => 'notify/note_issue_email', :layout => 'notify' -    else -      render :file => 'notify/note_wall_email', :layout => 'notify' -    end -  rescue -    render :text => "Preview not available" -  end - -  def preview_user_new -    @user = User.first -    @password = "DHasJKDHAS!" - -    render :file => 'notify/new_user_email', :layout => 'notify' -  rescue -    render :text => "Preview not available" -  end - -  def preview_issue_new -    @issue = Issue.first -    @user = @issue.assignee -    @project = @issue.project -    render :file => 'notify/new_issue_email', :layout => 'notify' -  rescue -    render :text => "Preview not available" -  end -end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 5266b406504..0ff97bf2c32 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -6,7 +6,7 @@ class Admin::ProjectsController < ApplicationController    def index      @admin_projects = Project.scoped      @admin_projects = @admin_projects.search(params[:name]) if params[:name].present? -    @admin_projects = @admin_projects.page(params[:page]) +    @admin_projects = @admin_projects.page(params[:page]).per(20)    end    def show @@ -72,6 +72,6 @@ class Admin::ProjectsController < ApplicationController      @admin_project = Project.find_by_code(params[:id])      @admin_project.destroy -    redirect_to admin_projects_url +    redirect_to admin_projects_url, notice: 'Project was successfully deleted.'    end  end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9a0f95bf0cb..3265046d2ae 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -52,7 +52,7 @@ class ApplicationController < ActionController::Base    def layout_by_resource      if devise_controller? -      "devise" +      "devise_layout"      else        "application"      end diff --git a/app/controllers/commits_controller.rb b/app/controllers/commits_controller.rb index 25e980e017b..cb1f74527a1 100644 --- a/app/controllers/commits_controller.rb +++ b/app/controllers/commits_controller.rb @@ -26,43 +26,31 @@ class CommitsController < ApplicationController    end    def show -    @commit = project.commit(params[:id]) - -    git_not_found! and return unless @commit - -    @commit = CommitDecorator.decorate(@commit) - -    @note = @project.build_commit_note(@commit) -    @comments_allowed = true -    @line_notes = project.commit_line_notes(@commit) - -    @notes_count = @line_notes.count + project.commit_notes(@commit).count - -    if @commit.diffs.size > 200 && !params[:force_show_diff] -      @suppress_diff = true  +    result = CommitLoad.new(project, current_user, params).execute + +    @commit = result[:commit] + +    if @commit +      @suppress_diff = result[:suppress_diff] +      @note          = result[:note] +      @line_notes    = result[:line_notes] +      @notes_count   = result[:notes_count] +      @comments_allowed = true +    else +      return git_not_found!      end +    rescue Grit::Git::GitTimeout      render "huge_commit"    end    def compare -    first = project.commit(params[:to].try(:strip)) -    last = project.commit(params[:from].try(:strip)) +    result = Commit.compare(project, params[:from], params[:to]) -    @diffs = [] -    @commits = [] +    @commits = result[:commits] +    @commit  = result[:commit] +    @diffs   = result[:diffs]      @line_notes = [] - -    if first && last -      commits = [first, last].sort_by(&:created_at) -      younger = commits.first -      older = commits.last - - -      @commits = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)} -      @diffs = project.repo.diff(younger.id, older.id) rescue [] -      @commit = Commit.new(older) -    end    end    def patch diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index a054940738e..8508e2454d2 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -2,15 +2,13 @@ class DashboardController < ApplicationController    respond_to :html    def index -    @projects = current_user.projects.includes(:events).order("events.created_at DESC") -    @projects = @projects.page(params[:page]).per(40) - -    @events = Event.where(:project_id => current_user.projects.map(&:id)).recent.limit(20) - +    @projects = current_user.projects_with_events.page(params[:page]).per(40) +    @events = Event.recent_for_user(current_user).limit(20).offset(params[:offset] || 0)      @last_push = current_user.recent_push      respond_to do |format|        format.html +      format.js        format.atom { render :layout => false }      end    end diff --git a/app/controllers/hooks_controller.rb b/app/controllers/hooks_controller.rb index 9627aba9771..ad2fb3ae781 100644 --- a/app/controllers/hooks_controller.rb +++ b/app/controllers/hooks_controller.rb @@ -11,24 +11,24 @@ class HooksController < ApplicationController    respond_to :html    def index -    @hooks = @project.web_hooks.all -    @hook = WebHook.new +    @hooks = @project.hooks.all +    @hook = ProjectHook.new    end    def create -    @hook = @project.web_hooks.new(params[:hook]) +    @hook = @project.hooks.new(params[:hook])      @hook.save      if @hook.valid?        redirect_to project_hooks_path(@project)      else -      @hooks = @project.web_hooks.all +      @hooks = @project.hooks.all        render :index      end    end    def test -    @hook = @project.web_hooks.find(params[:id]) +    @hook = @project.hooks.find(params[:id])      commits = @project.commits(@project.default_branch, nil, 3)      data = @project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{@project.default_branch}", current_user)      @hook.execute(data) @@ -37,7 +37,7 @@ class HooksController < ApplicationController    end    def destroy -    @hook = @project.web_hooks.find(params[:id]) +    @hook = @project.hooks.find(params[:id])      @hook.destroy      redirect_to project_hooks_path(@project) diff --git a/app/controllers/merge_requests_controller.rb b/app/controllers/merge_requests_controller.rb index ec4ed45fedf..1cb1d388465 100644 --- a/app/controllers/merge_requests_controller.rb +++ b/app/controllers/merge_requests_controller.rb @@ -24,16 +24,7 @@ class MergeRequestsController < ApplicationController    def index -    @merge_requests = @project.merge_requests - -    @merge_requests = case params[:f].to_i -                      when 1 then @merge_requests -                      when 2 then @merge_requests.closed -                      when 3 then @merge_requests.opened.assigned(current_user) -                      else @merge_requests.opened -                      end.page(params[:page]).per(20) - -    @merge_requests = @merge_requests.includes(:author, :project).order("closed, created_at desc") +    @merge_requests = MergeRequestsLoad.new(project, current_user, params).execute    end    def show diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb index a2638d9597c..1c997e380a0 100644 --- a/app/controllers/notes_controller.rb +++ b/app/controllers/notes_controller.rb @@ -40,25 +40,6 @@ class NotesController < ApplicationController    protected     def notes -    @notes = case params[:target_type] -             when "commit"  -               then project.commit_notes(project.commit((params[:target_id]))).fresh.limit(20) -             when "snippet" -               then  project.snippets.find(params[:target_id]).notes -             when "wall" -               then project.common_notes.order("created_at DESC").fresh.limit(50) -             when "issue" -               then project.issues.find(params[:target_id]).notes.inc_author.order("created_at DESC").limit(20) -             when "merge_request" -               then project.merge_requests.find(params[:target_id]).notes.inc_author.order("created_at DESC").limit(20) -             end - -    @notes = if params[:last_id] -               @notes.where("id > ?", params[:last_id]) -             elsif params[:first_id] -               @notes.where("id < ?", params[:first_id]) -             else  -               @notes -             end +    @notes = NotesLoad.new(project, current_user, params).execute    end  end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 629b6819fb1..fb759c371c4 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -1,4 +1,17 @@  class OmniauthCallbacksController < Devise::OmniauthCallbacksController + +  # Extend the standard message generation to accept our custom exception +  def failure_message +    exception = env["omniauth.error"] +    if exception.class == OmniAuth::Error +      error = exception.message +    else +      error   = exception.error_reason if exception.respond_to?(:error_reason) +      error ||= exception.error        if exception.respond_to?(:error) +      error ||= env["omniauth.error.type"].to_s +    end +    error.to_s.humanize if error +  end    def ldap      # We only find ourselves here if the authentication to LDAP was successful. diff --git a/app/controllers/refs_controller.rb b/app/controllers/refs_controller.rb index ddf5f675d0c..b610c9f34d4 100644 --- a/app/controllers/refs_controller.rb +++ b/app/controllers/refs_controller.rb @@ -9,7 +9,7 @@ class RefsController < ApplicationController    before_filter :require_non_empty_project    before_filter :ref -  before_filter :define_tree_vars, :only => [:tree, :blob, :blame] +  before_filter :define_tree_vars, :only => [:tree, :blob, :blame, :logs_tree]    before_filter :render_full_content    layout "project" @@ -46,6 +46,18 @@ class RefsController < ApplicationController      end    end +  def logs_tree +    contents = @tree.contents +    @logs = contents.map do |content| +      file = params[:path] ? File.join(params[:path], content.name) : content.name +      last_commit = @project.commits(@commit.id, file, 1).last +      {  +        :file_name => content.name,  +        :commit => last_commit +      } +    end +  end +    def blob      if @tree.is_blob?        if @tree.text? @@ -79,6 +91,15 @@ class RefsController < ApplicationController      @commit = project.commit(@ref)      @tree = Tree.new(@commit.tree, project, @ref, params[:path])      @tree = TreeDecorator.new(@tree) +    @hex_path = Digest::SHA1.hexdigest(params[:path] || "/") + +    if params[:path] +      @history_path = tree_file_project_ref_path(@project, @ref, params[:path]) +      @logs_path = logs_file_project_ref_path(@project, @ref, params[:path])  +    else +      @history_path = tree_project_ref_path(@project, @ref) +      @logs_path = logs_tree_project_ref_path(@project, @ref)  +    end    rescue      return render_404    end diff --git a/app/decorators/event_decorator.rb b/app/decorators/event_decorator.rb new file mode 100644 index 00000000000..50aaa615d49 --- /dev/null +++ b/app/decorators/event_decorator.rb @@ -0,0 +1,25 @@ +class EventDecorator < ApplicationDecorator +  decorates :event + +  def feed_title +    if self.issue? +      "#{self.author_name} #{self.action_name} issue ##{self.target_id}:" + self.issue_title +    elsif self.merge_request? +      "#{self.author_name} #{self.action_name} MR ##{self.target_id}:" + self.merge_request_title +    elsif self.push? +      "#{self.author_name} #{self.push_action_name} #{self.ref_type} " + self.ref_name +    else  +      "" +    end +  end + +  def feed_url +    if self.issue? +      h.project_issue_url(self.project, self.issue) +    elsif self.merge_request? +      h.project_merge_request_url(self.project, self.merge_request) +    elsif self.push? +      h.project_commits_url(self.project, :ref => self.ref_name) +    end +  end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2697fff433e..3f15fd9237f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -134,4 +134,8 @@ module ApplicationHelper               end      active ? "current" : nil    end + +  def hexdigest(string) +    Digest::SHA1.hexdigest string +  end  end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb new file mode 100644 index 00000000000..ed3053d8af5 --- /dev/null +++ b/app/helpers/tree_helper.rb @@ -0,0 +1,27 @@ +module TreeHelper +  def tree_icon(content) +    if content.is_a?(Grit::Blob) +      if content.text? +        image_tag "file_txt.png" +      elsif content.image? +        image_tag "file_img.png" +      else +        image_tag "file_bin.png" +      end +    else +      image_tag "file_dir.png" +    end +  end + +  def tree_hex_class(content) +    "file_#{hexdigest(content.name)}" +  end + +  def tree_full_path(content) +    if params[:path]  +      File.join(params[:path], content.name) +    else +      content.name +    end +  end +end diff --git a/app/models/commit.rb b/app/models/commit.rb index 800ad19b9f1..859bee29fa5 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -80,6 +80,29 @@ class Commit      def commits_between(repo, from, to)        repo.commits_between(from, to).map { |c| Commit.new(c) }      end + +    def compare(project, from, to) +      first = project.commit(to.try(:strip)) +      last = project.commit(from.try(:strip)) + +      result = {  +        :commits => [], +        :diffs => [], +        :commit => nil +      } + +      if first && last +        commits = [first, last].sort_by(&:created_at) +        younger = commits.first +        older = commits.last + +        result[:commits] = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)} +        result[:diffs] = project.repo.diff(younger.id, older.id) rescue [] +        result[:commit] = Commit.new(older) +      end + +      result +    end    end    def persisted? diff --git a/app/models/event.rb b/app/models/event.rb index dc7dfa16dd5..c75924e7500 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -28,6 +28,10 @@ class Event < ActiveRecord::Base      end    end +  def self.recent_for_user user +    where(:project_id => user.projects.map(&:id)).recent +  end +    # Next events currently enabled for system    #  - push     #  - new issue diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index d3e531f7818..2581f3be4c9 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -22,7 +22,6 @@ class MergeRequest < ActiveRecord::Base                  :should_remove_source_branch    validates_presence_of :project_id -  validates_presence_of :assignee_id    validates_presence_of :author_id    validates_presence_of :source_branch    validates_presence_of :target_branch @@ -36,6 +35,7 @@ class MergeRequest < ActiveRecord::Base    delegate :name,             :email,             :to => :assignee, +           :allow_nil => true,             :prefix => true    validates :title, @@ -128,7 +128,7 @@ class MergeRequest < ActiveRecord::Base    def unmerged_diffs      commits = project.repo.commits_between(target_branch, source_branch).map {|c| Commit.new(c)} -    diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) +    diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) rescue []    end    def last_commit diff --git a/app/models/project.rb b/app/models/project.rb index ec4893e2b17..a49b3f519b3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -19,7 +19,7 @@ class Project < ActiveRecord::Base    has_many :notes,          :dependent => :destroy    has_many :snippets,       :dependent => :destroy    has_many :deploy_keys,    :dependent => :destroy, :foreign_key => "project_id", :class_name => "Key" -  has_many :web_hooks,      :dependent => :destroy +  has_many :hooks,          :dependent => :destroy, :class_name => "ProjectHook"    has_many :wikis,          :dependent => :destroy    has_many :protected_branches, :dependent => :destroy @@ -120,7 +120,7 @@ class Project < ActiveRecord::Base        errors.add(:path, " like 'gitolite-admin' is not allowed")      end    end -   +    def self.access_options      UsersProject.access_roles    end diff --git a/app/models/project_hook.rb b/app/models/project_hook.rb new file mode 100644 index 00000000000..06388aaeb4c --- /dev/null +++ b/app/models/project_hook.rb @@ -0,0 +1,3 @@ +class ProjectHook < WebHook +  belongs_to :project +end diff --git a/app/models/system_hook.rb b/app/models/system_hook.rb new file mode 100644 index 00000000000..8517d43a9de --- /dev/null +++ b/app/models/system_hook.rb @@ -0,0 +1,13 @@ +class SystemHook < WebHook +   +  def async_execute(data) +    Resque.enqueue(SystemHookWorker, id, data) +  end + +  def self.all_hooks_fire(data) +    SystemHook.all.each do |sh| +      sh.async_execute data +    end +  end +   +end diff --git a/app/models/user.rb b/app/models/user.rb index 4ead60a92db..ff27660a6ee 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,11 +1,12 @@  class User < ActiveRecord::Base +    include Account -  devise :database_authenticatable, :token_authenticatable, +  devise :database_authenticatable, :token_authenticatable, :lockable,           :recoverable, :rememberable, :trackable, :validatable, :omniauthable    attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, -                  :name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme,  +                  :name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme,                    :theme_id, :force_random_password    attr_accessor :force_random_password @@ -15,6 +16,11 @@ class User < ActiveRecord::Base    has_many :my_own_projects, :class_name => "Project", :foreign_key => :owner_id    has_many :keys, :dependent => :destroy +  has_many :events, +    :class_name => "Event", +    :foreign_key => :author_id, +    :dependent => :destroy +    has_many :recent_events,      :class_name => "Event",      :foreign_key => :author_id, @@ -80,7 +86,8 @@ class User < ActiveRecord::Base    def self.find_for_ldap_auth(omniauth_info)      name = omniauth_info.name.force_encoding("utf-8") -    email = omniauth_info.email.downcase +    email = omniauth_info.email.downcase unless omniauth_info.email.nil? +    raise OmniAuth::Error, "LDAP accounts must provide an email address" if email.nil?      if @user = User.find_by_email(email)        @user diff --git a/app/models/users_project.rb b/app/models/users_project.rb index 6ba72370931..4ff86290a92 100644 --- a/app/models/users_project.rb +++ b/app/models/users_project.rb @@ -68,7 +68,7 @@ class UsersProject < ActiveRecord::Base    end    def repo_access_human -    "" +    self.class.access_roles.invert[self.project_access]    end  end  # == Schema Information diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index 26288476a6c..85d87898682 100644 --- a/app/models/web_hook.rb +++ b/app/models/web_hook.rb @@ -4,8 +4,6 @@ class WebHook < ActiveRecord::Base    # HTTParty timeout    default_timeout 10 -  belongs_to :project -    validates :url,              presence: true,              format: { @@ -14,9 +12,8 @@ class WebHook < ActiveRecord::Base    def execute(data)      WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }) -  rescue -    # There was a problem calling this web hook, let's forget about it.    end +    end  # == Schema Information  # diff --git a/app/observers/mailer_observer.rb b/app/observers/mailer_observer.rb index 880fd5026a4..451deccd14f 100644 --- a/app/observers/mailer_observer.rb +++ b/app/observers/mailer_observer.rb @@ -43,7 +43,7 @@ class MailerObserver < ActiveRecord::Observer    end    def new_merge_request(merge_request) -    if merge_request.assignee != current_user +    if merge_request.assignee && merge_request.assignee != current_user        Notify.new_merge_request_email(merge_request.id).deliver      end    end diff --git a/app/observers/system_hook_observer.rb b/app/observers/system_hook_observer.rb new file mode 100644 index 00000000000..312cd2b3622 --- /dev/null +++ b/app/observers/system_hook_observer.rb @@ -0,0 +1,67 @@ +class SystemHookObserver < ActiveRecord::Observer +  observe :user, :project, :users_project +   +  def after_create(model) +    if model.kind_of? Project +      SystemHook.all_hooks_fire({ +        event_name: "project_create", +        name: model.name, +        path: model.path, +        project_id: model.id, +        owner_name: model.owner.name, +        owner_email: model.owner.email, +        created_at: model.created_at +      }) +    elsif model.kind_of? User  +      SystemHook.all_hooks_fire({ +        event_name: "user_create", +        name: model.name, +        email: model.email, +        created_at: model.created_at +      }) + +    elsif model.kind_of? UsersProject +      SystemHook.all_hooks_fire({ +        event_name: "user_add_to_team", +        project_name: model.project.name, +        project_path: model.project.path, +        project_id: model.project_id, +        user_name: model.user.name, +        user_email: model.user.email, +        project_access: model.repo_access_human, +        created_at: model.created_at +      }) + +    end +  end + +  def after_destroy(model) +    if model.kind_of? Project +      SystemHook.all_hooks_fire({ +        event_name: "project_destroy", +        name: model.name, +        path: model.path, +        project_id: model.id, +        owner_name: model.owner.name, +        owner_email: model.owner.email, +      }) +    elsif model.kind_of? User +      SystemHook.all_hooks_fire({ +        event_name: "user_destroy", +        name: model.name, +        email: model.email +      }) + +    elsif model.kind_of? UsersProject +      SystemHook.all_hooks_fire({ +        event_name: "user_remove_from_team", +        project_name: model.project.name, +        project_path: model.project.path, +        project_id: model.project_id, +        user_name: model.user.name, +        user_email: model.user.email, +        project_access: model.repo_access_human +      }) +    end +  end +end diff --git a/app/roles/account.rb b/app/roles/account.rb index afa1f8a347d..e86dc5939e0 100644 --- a/app/roles/account.rb +++ b/app/roles/account.rb @@ -55,4 +55,8 @@ module Account      # Take only latest one      events = events.recent.limit(1).first    end + +  def projects_with_events +    projects.includes(:events).order("events.created_at DESC") +  end  end diff --git a/app/roles/git_push.rb b/app/roles/git_push.rb index b4c59472a5a..4ee7e62a69e 100644 --- a/app/roles/git_push.rb +++ b/app/roles/git_push.rb @@ -27,7 +27,7 @@ module GitPush      true    end -  def execute_web_hooks(oldrev, newrev, ref, user) +  def execute_hooks(oldrev, newrev, ref, user)      ref_parts = ref.split('/')      # Return if this is not a push to a branch (e.g. new commits) @@ -35,7 +35,7 @@ module GitPush      data = post_receive_data(oldrev, newrev, ref, user) -    web_hooks.each { |web_hook| web_hook.execute(data) } +    hooks.each { |hook| hook.execute(data) }    end    def post_receive_data(oldrev, newrev, ref, user) @@ -97,7 +97,7 @@ module GitPush      self.update_merge_requests(oldrev, newrev, ref, user)      # Execute web hooks -    self.execute_web_hooks(oldrev, newrev, ref, user) +    self.execute_hooks(oldrev, newrev, ref, user)      # Create satellite      self.satellite.create unless self.satellite.exists? diff --git a/app/views/admin/hooks/_data_ex.html.erb b/app/views/admin/hooks/_data_ex.html.erb new file mode 100644 index 00000000000..652ee5aa56f --- /dev/null +++ b/app/views/admin/hooks/_data_ex.html.erb @@ -0,0 +1,66 @@ +<% data_ex_str = <<eos +1. Project created: +{ +   "created_at": "2012-07-21T07:30:54Z", +   "event_name": "project_create", +         "name": "StoreCloud", +  "owner_email": "johnsmith@gmail.com", +   "owner_name": "John Smith", +         "path": "storecloud", +   "project_id": 74  +} + +2. Project destroyed: +{ +  "event_name": "project_destroy", +        "name": "Underscore", + "owner_email": "johnsmith@gmail.com", +  "owner_name": "John Smith", +        "path": "underscore", +  "project_id": 73  +} + +3. New Team Member: +{ +     "created_at": "2012-07-21T07:30:56Z", +     "event_name": "user_add_to_team", + "project_access": "Master", +     "project_id": 74,  +   "project_name": "StoreCloud", +   "project_path": "storecloud",  +    "owner_email": "johnsmith@gmail.com", +     "owner_name": "John Smith", +} + +4. Team Member Removed: +{ +     "created_at": "2012-07-21T07:30:56Z", +     "event_name": "user_remove_from_team", + "project_access": "Master", +     "project_id": 74,  +   "project_name": "StoreCloud", +   "project_path": "storecloud",  +    "owner_email": "johnsmith@gmail.com", +     "owner_name": "John Smith", +} + +5. User created: +{  +   "created_at": "2012-07-21T07:44:07Z", +        "email": "js@gitlabhq.com", +   "event_name": "user_create", +         "name": "John Smith"  +} + +6. User removed: +{  +   "created_at": "2012-07-21T07:44:07Z", +        "email": "js@gitlabhq.com", +   "event_name": "user_destroy", +         "name": "John Smith"  +} + +eos +%> +<% js_lexer = Pygments::Lexer[:js] %> +<%= raw js_lexer.highlight(data_ex_str) %> diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml new file mode 100644 index 00000000000..030e6136b1f --- /dev/null +++ b/app/views/admin/hooks/index.html.haml @@ -0,0 +1,39 @@ +.alert.alert-info +  %span +    Post receive hooks for binding events. +    %br +    Read more about system hooks +    %strong #{link_to "here", help_system_hooks_path, :class => "vlink"} + += form_for @hook, :as => :hook, :url => admin_hooks_path do |f| +  -if @hook.errors.any? +    .alert-message.block-message.error +      - @hook.errors.full_messages.each do |msg| +        %p= msg +  .clearfix +    = f.label :url, "URL:" +    .input +      = f.text_field :url, :class => "text_field xxlarge" +        +      = f.submit "Add System Hook", :class => "btn primary" +%hr + +-if @hooks.any? +  %h3 +    Hooks +    %small (#{@hooks.count}) +  %br +  %table.admin-table +    %tr +      %th URL +      %th Method +      %th +    - @hooks.each do |hook| +      %tr +        %td +          = link_to admin_hook_path(hook) do +            %strong= hook.url +          = link_to 'Test Hook', admin_hook_test_path(hook), :class => "btn small right" +        %td POST +        %td +          = link_to 'Remove', admin_hook_path(hook), :confirm => 'Are you sure?', :method => :delete, :class => "danger btn small right" diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index 7963e18adcd..800d3bb288f 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -1,6 +1,9 @@ -%h4 -  %i.icon-file -  githost.log -%pre.logs -  - Gitlab::Logger.read_latest.each do |line| -    %span.log= line +.file_holder#README +  .file_title +    %i.icon-file +    githost.log +  .file_content.logs +    %ol +      - Gitlab::Logger.read_latest.each do |line| +        %li +          %p= line diff --git a/app/views/admin/mailer/preview.html.haml b/app/views/admin/mailer/preview.html.haml deleted file mode 100644 index 23ea7381cf5..00000000000 --- a/app/views/admin/mailer/preview.html.haml +++ /dev/null @@ -1,28 +0,0 @@ -%p This is page with preview for all system emails that are sent to user -%p Email previews built based on existing Project/Commit/Issue base - so some preview maybe unavailable unless object appear in system - -#accordion -  %h3 -    %a New user -  %div -    %iframe{ :src=> admin_mailer_preview_user_new_path, :width=>"100%", :height=>"350"} -  %h3 -    %a New issue -  %div -    %iframe{ :src=> admin_mailer_preview_issue_new_path, :width=>"100%", :height=>"350"} -  %h3 -    %a Commit note -  %div -    %iframe{ :src=> admin_mailer_preview_note_path(:type => "Commit"), :width=>"100%", :height=>"350"} -  %h3 -    %a Issue note -  %div -    %iframe{ :src=> admin_mailer_preview_note_path(:type => "Issue"), :width=>"100%", :height=>"350"} -  %h3 -    %a Wall note -  %div -    %iframe{ :src=> admin_mailer_preview_note_path(:type => "Wall"), :width=>"100%", :height=>"350"} - -:javascript -  $(function() { -      $("#accordion").accordion(); }); diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 932fb37ddf6..7218eebb62a 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -13,8 +13,8 @@      %th Team Members      %th Post Receive      %th Last Commit -    %th -    %th +    %th Edit +    %th.cred Danger Zone!    - @admin_projects.each do |project|      %tr @@ -24,5 +24,5 @@        %td= check_box_tag :post_receive_file, 1, project.has_post_receive_file?, :disabled => true        %td= last_commit(project)        %td= link_to 'Edit', edit_admin_project_path(project), :id => "edit_#{dom_id(project)}", :class => "btn small" -      %td= link_to 'Destroy', [:admin, project], :confirm => 'Are you sure?', :method => :delete, :class => "btn small danger" +      %td.bgred= link_to 'Destroy', [:admin, project], :confirm => "REMOVE #{project.name}? Are you sure?", :method => :delete, :class => "btn small danger"  = paginate @admin_projects, :theme => "admin" diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index bd2e136247a..c1955b321d5 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -50,7 +50,7 @@          .alert            .clearfix -            %p Give user ability to manage application. +            %p Make the user a GitLab administrator.              = f.label :admin, :class => "checkbox" do                = f.check_box :admin                %span Administrator @@ -59,11 +59,11 @@              - if @admin_user.blocked                %span                  = link_to 'Unblock', unblock_admin_user_path(@admin_user), :method => :put, :class => "btn small" -                This user is blocked and is not able to login GitLab +                This user is blocked and is not able to login to GitLab              - else                %span                  = link_to 'Block', block_admin_user_path(@admin_user), :confirm => 'USER WILL BE BLOCKED! Are you sure?', :method => :put, :class => "btn small danger" -                Blocked user will removed from all projects & will not be able to login to GitLab. +                Blocked users will be removed from all projects & will not be able to login to GitLab.      .actions        = f.submit 'Save', :class => "btn primary"        - if @admin_user.new_record? diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 481bf37bb0b..5d5320db0e3 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -27,7 +27,7 @@      %th Projects      %th Edit      %th Blocked -    %th +    %th.cred Danger Zone!    - @admin_users.each do |user|      %tr @@ -41,6 +41,6 @@            = link_to 'Unblock', unblock_admin_user_path(user), :method => :put, :class => "btn small success"          - else            = link_to 'Block', block_admin_user_path(user), :confirm => 'USER WILL BE BLOCKED! Are you sure?', :method => :put, :class => "btn small danger" -      %td= link_to 'Destroy', [:admin, user], :confirm => 'USER WILL BE REMOVED! Are you sure?', :method => :delete, :class => "btn small danger" +      %td.bgred= link_to 'Destroy', [:admin, user], :confirm => "USER #{user.name} WILL BE REMOVED! Are you sure?", :method => :delete, :class => "btn small danger"  = paginate @admin_users, :theme => "admin" diff --git a/app/views/commits/_commits.html.haml b/app/views/commits/_commits.html.haml index c2c9ca624b2..c3c7d49ce74 100644 --- a/app/views/commits/_commits.html.haml +++ b/app/views/commits/_commits.html.haml @@ -1,4 +1,6 @@  - @commits.group_by { |c| c.committed_date.to_date }.each do |day, commits|    %div.ui-box -    %h5= day.stamp("28 Aug, 2010") +    %h5.small +      %i.icon-calendar +      = day.stamp("28 Aug, 2010")      %ul.unstyled= render commits diff --git a/app/views/commits/_diffs.html.haml b/app/views/commits/_diffs.html.haml index 02a15633089..d51561d90f8 100644 --- a/app/views/commits/_diffs.html.haml +++ b/app/views/commits/_diffs.html.haml @@ -35,7 +35,13 @@          - if file.text?            = render "commits/text_file", :diff => diff, :index => i          - elsif file.image? -          .diff_file_content_image{:class => image_diff_class(diff)} -            %img{:src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} +          - if diff.renamed_file || diff.new_file || diff.deleted_file  +            .diff_file_content_image +              %img{:class => image_diff_class(diff), :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} +          - else  +            - old_file = (@commit.prev_commit.tree / diff.old_path) +            .diff_file_content_image.img_compared +              %img{:class => "diff_image_removed", :src => "data:#{file.mime_type};base64,#{Base64.encode64(old_file.data)}"} +              %img{:class => "diff_image_added", :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}          - else            %p.nothing_here_message No preview for this file type diff --git a/app/views/commits/_head.html.haml b/app/views/commits/_head.html.haml index 8e9195c5b50..453ca4eac12 100644 --- a/app/views/commits/_head.html.haml +++ b/app/views/commits/_head.html.haml @@ -13,12 +13,12 @@    %li{:class => "#{branches_tab_class}"}      = link_to project_repository_path(@project) do        Branches -      %span.number= @project.repo.branch_count +      %span.badge= @project.repo.branch_count    %li{:class => "#{'active' if current_page?(tags_project_repository_path(@project)) }"}      = link_to tags_project_repository_path(@project) do        Tags -      %span.number= @project.repo.tag_count +      %span.badge= @project.repo.tag_count    - if current_page?(project_commits_path(@project)) && current_user.private_token diff --git a/app/views/commits/compare.html.haml b/app/views/commits/compare.html.haml index c02263296f4..66ed8dad595 100644 --- a/app/views/commits/compare.html.haml +++ b/app/views/commits/compare.html.haml @@ -20,7 +20,7 @@        = "..."        = text_field_tag :to, params[:to], :placeholder => "aa8b4ef", :class => "xlarge"      .actions -      = submit_tag "Compare", :class => "btn primary" +      = submit_tag "Compare", :class => "btn btn-primary"  - unless @commits.empty? diff --git a/app/views/dashboard/index.atom.builder b/app/views/dashboard/index.atom.builder index 706b808ee43..fa3bfade28b 100644 --- a/app/views/dashboard/index.atom.builder +++ b/app/views/dashboard/index.atom.builder @@ -8,17 +8,10 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear    @events.each do |event|      if event.allowed? +      event = EventDecorator.decorate(event)        xml.entry do -        if event.issue? -          event_link  = project_issue_url(event.project, event.issue) -          event_title = event.issue_title -        elsif event.merge_request? -          event_link  = project_merge_request_url(event.project, event.merge_request) -          event_title = event.merge_request_title -        elsif event.push? -          event_link  = project_commits_url(event.project, :ref => event.ref_name) -          event_title = event.ref_name -        end +        event_link = event.feed_url +        event_title = event.feed_title          xml.id      "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"          xml.link    :href => event_link diff --git a/app/views/dashboard/index.html.haml b/app/views/dashboard/index.html.haml index b38544509b2..e1d7781927a 100644 --- a/app/views/dashboard/index.html.haml +++ b/app/views/dashboard/index.html.haml @@ -10,9 +10,10 @@                  add new key              to your profile        - if @events.any? -        = render @events +        .content_list= render @events        - else          %h4.nothing_here_message Projects activity will be displayed here +      .loading.hide      .side        = render "events/event_last_push", :event => @last_push        .projects_box @@ -54,3 +55,7 @@            New Project »      - else        If you will be added to project - it will be displayed here + + +:javascript  +  $(function(){ Pager.init(20); }); diff --git a/app/views/dashboard/index.js.haml b/app/views/dashboard/index.js.haml index aa038e75928..7e5a148e5ef 100644 --- a/app/views/dashboard/index.js.haml +++ b/app/views/dashboard/index.js.haml @@ -1,2 +1,2 @@  :plain  -  $(".projects .activities").append("#{escape_javascript(render(@events))}"); +  Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}"); diff --git a/app/views/events/_event_issue.html.haml b/app/views/events/_event_issue.html.haml index 13fb20cd379..4293be8204e 100644 --- a/app/views/events/_event_issue.html.haml +++ b/app/views/events/_event_issue.html.haml @@ -1,7 +1,7 @@  = image_tag gravatar_icon(event.author_email), :class => "avatar"  %strong #{event.author_name} -%span.event_label= event.action_name - issue +%span.event_label{:class => event.action_name}= event.action_name +issue  = link_to project_issue_path(event.project, event.issue) do    %strong= truncate event.issue_title  at diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index fb4a728ed3e..212ef817e60 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -5,12 +5,9 @@        %span Your pushed to         = event.ref_type        = link_to project_commits_path(event.project, :ref => event.ref_name) do -        %strong= event.ref_name +        %strong= truncate(event.ref_name, :length => 28)        at        %strong= link_to event.project.name, event.project -      %span.cgray -        = time_ago_in_words(event.created_at) -        ago.        = link_to new_mr_path_from_push_event(event), :title => "New Merge Request", :class => "btn very_small primary" do          Create Merge Request diff --git a/app/views/events/_event_merge_request.html.haml b/app/views/events/_event_merge_request.html.haml index e59a74b9554..774921a7f2a 100644 --- a/app/views/events/_event_merge_request.html.haml +++ b/app/views/events/_event_merge_request.html.haml @@ -2,8 +2,8 @@    .event_icon= image_tag "event_mr_merged.png"  = image_tag gravatar_icon(event.author_email), :class => "avatar"  %strong #{event.author_name} -%span.event_label= event.action_name - merge request +%span.event_label{:class => event.action_name}= event.action_name +merge request  = link_to project_merge_request_path(event.project, event.merge_request) do    %strong= truncate event.merge_request_title  at diff --git a/app/views/events/_event_push.html.haml b/app/views/events/_event_push.html.haml index 3aadd226614..59d8962bb16 100644 --- a/app/views/events/_event_push.html.haml +++ b/app/views/events/_event_push.html.haml @@ -2,7 +2,7 @@    .event_icon= image_tag "event_push.png"    = image_tag gravatar_icon(event.author_email), :class => "avatar"    %strong #{event.author_name} -  %span.event_label= event.push_action_name +  %span.event_label.pushed= event.push_action_name    = event.ref_type    = link_to project_commits_path(event.project, :ref => event.ref_name) do      %strong= event.ref_name diff --git a/app/views/help/api.html.haml b/app/views/help/api.html.haml new file mode 100644 index 00000000000..4964c1bbd87 --- /dev/null +++ b/app/views/help/api.html.haml @@ -0,0 +1,41 @@ +%h3 API +.back_link +  = link_to help_path do  +    ← to index +%hr + +%ol +  %li  +    %a{:href => "#README"} README +  %li  +    %a{:href => "#projects"} Projects +  %li  +    %a{:href => "#users"} Users + +.file_holder#README +  .file_title +    %i.icon-file +    README +  .file_content.wiki +    = preserve do +      = markdown File.read(Rails.root.join("doc", "api", "README.md")) + +%br + +.file_holder#projects +  .file_title +    %i.icon-file +    Projects +  .file_content.wiki +    = preserve do +      = markdown File.read(Rails.root.join("doc", "api", "projects.md")) + +%br + +.file_holder#users +  .file_title +    %i.icon-file +    Users +  .file_content.wiki +    = preserve do +      = markdown File.read(Rails.root.join("doc", "api", "users.md")) diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 25b9e3e5208..e9602e33037 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -22,3 +22,9 @@    %li      %h5= link_to "Web Hooks", help_web_hooks_path + +  %li +    %h5= link_to "System Hooks", help_system_hooks_path + +  %li +    %h5= link_to "API", help_api_path diff --git a/app/views/help/system_hooks.html.haml b/app/views/help/system_hooks.html.haml new file mode 100644 index 00000000000..2088208ad47 --- /dev/null +++ b/app/views/help/system_hooks.html.haml @@ -0,0 +1,13 @@ +%h3 System hooks +.back_link +  = link_to :back do  +    ← back +%hr + +%p.slead  +  Your Gitlab instance can perform HTTP POST request on next event: create_project, delete_project, create_user, delete_user, change_team_member. +  %br +  System Hooks can be used for logging or change information in LDAP server. +  %br +%h5 Hooks request example: += render "admin/hooks/data_ex" diff --git a/app/views/issues/_issues.html.haml b/app/views/issues/_issues.html.haml index a20df176afc..17141cc453c 100644 --- a/app/views/issues/_issues.html.haml +++ b/app/views/issues/_issues.html.haml @@ -6,7 +6,9 @@      .row        .span7= paginate @issues, :remote => true, :theme => "gitlab"        .span3.right -        %span.cgray.right #{@issues.total_count} issues for this filter +        %span.cgray.right  +          %span.issue_counter #{@issues.total_count} +          issues for this filter  - else    %li      %h4.nothing_here_message Nothing to show here diff --git a/app/views/issues/_show.html.haml b/app/views/issues/_show.html.haml index 03524901c99..e12c3c1a99c 100644 --- a/app/views/issues/_show.html.haml +++ b/app/views/issues/_show.html.haml @@ -12,9 +12,9 @@          = issue.notes.count      - if can? current_user, :modify_issue, issue        - if issue.closed -        = link_to 'Reopen', project_issue_path(issue.project, issue, :issue => {:closed => false }, :status_only => true), :method => :put,  :class => "btn small grouped", :remote => true +        = link_to 'Reopen', project_issue_path(issue.project, issue, :issue => {:closed => false }, :status_only => true), :method => :put,  :class => "btn small grouped reopen_issue", :remote => true        - else -        = link_to 'Resolve', project_issue_path(issue.project, issue, :issue => {:closed => true }, :status_only => true), :method => :put, :class => "success btn small grouped", :remote => true +        = link_to 'Resolve', project_issue_path(issue.project, issue, :issue => {:closed => true }, :status_only => true), :method => :put, :class => "success btn small grouped close_issue", :remote => true        = link_to edit_project_issue_path(issue.project, issue), :class => "btn small edit-issue-link", :remote => true do          %i.icon-edit          Edit @@ -35,6 +35,4 @@               - if issue.upvotes > 0 -      %span.badge.badge-success= "+#{issue.upvotes}" - - +      %span.badge.badge-success= "+#{issue.upvotes}"
\ No newline at end of file diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml index 7328fa88812..fb8b9f8ee8e 100644 --- a/app/views/issues/index.html.haml +++ b/app/views/issues/index.html.haml @@ -2,7 +2,7 @@  .issues_content    %h3.page_title      Issues -    %small (#{@issues.total_count}) +    %small (<span class=issue_counter>#{@issues.total_count}</span>)      .right        .span5          - if can? current_user, :write_issue, @project @@ -45,4 +45,4 @@  :javascript    $(function(){      issuesPage(); -  }) +  })
\ No newline at end of file diff --git a/app/views/keys/new.html.haml b/app/views/keys/new.html.haml index 277936c6743..02e782b9f85 100644 --- a/app/views/keys/new.html.haml +++ b/app/views/keys/new.html.haml @@ -1,4 +1,4 @@ -%h3 New key +%h3.page_title New key  %hr  = render 'form' @@ -11,4 +11,4 @@      if( key_mail && key_mail.length > 0 && title.val() == '' ){        $('#key_title').val( key_mail );      } -  });
\ No newline at end of file +  }); diff --git a/app/views/layouts/_project_menu.html.haml b/app/views/layouts/_project_menu.html.haml index 62279bb058d..3f58fc5a664 100644 --- a/app/views/layouts/_project_menu.html.haml +++ b/app/views/layouts/_project_menu.html.haml @@ -17,14 +17,14 @@      %li{:class => tab_class(:issues)}        = link_to  project_issues_filter_path(@project)  do          Issues -        %span.count= @project.issues.opened.count +        %span.count.issue_counter= @project.issues.opened.count    - if @project.repo_exists?      - if @project.merge_requests_enabled        %li{:class => tab_class(:merge_requests)}          = link_to project_merge_requests_path(@project) do            Merge Requests -          %span.count= @project.merge_requests.opened.count +          %span.count.merge_counter= @project.merge_requests.opened.count    - if @project.wall_enabled      %li{:class =>  tab_class(:wall)} diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 8de25821ee7..69304edef3a 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -15,7 +15,7 @@          %li{:class => tab_class(:admin_logs)}            = link_to "Logs", admin_logs_path          %li{:class => tab_class(:admin_emails)} -          = link_to "Emails", admin_emails_path +          = link_to "Hooks", admin_hooks_path          %li{:class => tab_class(:admin_resque)}            = link_to "Resque", admin_resque_path diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise_layout.html.haml index c293734b368..c293734b368 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise_layout.html.haml diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index dfe48d68240..957a68bf482 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -12,16 +12,17 @@          %li{:class => tab_class(:password)}            = link_to "Password", profile_password_path +        %li{:class => tab_class(:ssh_keys)} +          = link_to keys_path do +            SSH Keys +            %span.count= current_user.keys.count +          %li{:class => tab_class(:token)}            = link_to "Token", profile_token_path          %li{:class => tab_class(:design)}            = link_to "Design", profile_design_path -        %li{:class => tab_class(:ssh_keys)} -          = link_to keys_path do -            SSH Keys -            %span.count= current_user.keys.count        .content          = yield diff --git a/app/views/merge_requests/_form.html.haml b/app/views/merge_requests/_form.html.haml index d69faa142d5..4f20a06fd25 100644 --- a/app/views/merge_requests/_form.html.haml +++ b/app/views/merge_requests/_form.html.haml @@ -5,7 +5,8 @@          - @merge_request.errors.full_messages.each do |msg|            %li= msg -  %h3.padded.cgray 1. Select Branches +  %h4.cdark 1. Select Branches +  %br    .row      .span6 @@ -30,14 +31,21 @@          .bottom_commit            .mr_target_commit -  %h3.padded.cgray 2. Fill info +  %h4.cdark 2. Fill info +    .clearfix -    = f.label :assignee_id, "Assign to", :class => "control-label" -    .controls= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" }, :style => "width:250px") +    .main_box +      .top_box_content +        = f.label :title do  +          %strong= "Title *" +        .input= f.text_field :title, :class => "input-xxlarge pad", :maxlength => 255, :rows => 5 +      .middle_box_content +        = f.label :assignee_id do  +          %i.icon-user  +          Assign to +        .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" }, :style => "width:250px")    .control-group -    = f.label :title, :class => "control-label" -    .controls= f.text_field :title, :class => "input-xxlarge pad", :maxlength => 255, :rows => 5    .form-actions      = f.submit 'Save', :class => "btn-primary btn" diff --git a/app/views/merge_requests/_merge_request.html.haml b/app/views/merge_requests/_merge_request.html.haml index d9634a4fcc8..b9a005e08be 100644 --- a/app/views/merge_requests/_merge_request.html.haml +++ b/app/views/merge_requests/_merge_request.html.haml @@ -15,12 +15,14 @@          →          = merge_request.target_branch    = image_tag gravatar_icon(merge_request.author_email), :class => "avatar" + +  = link_to project_merge_request_path(merge_request.project, merge_request) do +    %p.row_title= truncate(merge_request.title, :length => 80) +    %span.update-author -    %strong= merge_request.author_name -    authored +    %small.cdark= "##{merge_request.id}" +    authored by #{merge_request.author_name}      = time_ago_in_words(merge_request.created_at)      ago      - if merge_request.upvotes > 0        %span.badge.badge-success= "+#{merge_request.upvotes}" -  = link_to project_merge_request_path(merge_request.project, merge_request) do -    %p.row_title= truncate(merge_request.title, :length => 80) diff --git a/app/views/merge_requests/edit.html.haml b/app/views/merge_requests/edit.html.haml index 9e4f9327cdc..eee148994d7 100644 --- a/app/views/merge_requests/edit.html.haml +++ b/app/views/merge_requests/edit.html.haml @@ -1,4 +1,4 @@ -%h3 +%h3.page_title    = "Edit merge request #{@merge_request.id}"  %hr  = render 'form' diff --git a/app/views/merge_requests/new.html.haml b/app/views/merge_requests/new.html.haml index efafa45d758..594089995ea 100644 --- a/app/views/merge_requests/new.html.haml +++ b/app/views/merge_requests/new.html.haml @@ -1,3 +1,3 @@ -%h3 New Merge Request +%h3.page_title New Merge Request  %hr  = render 'form' diff --git a/app/views/merge_requests/show/_commits.html.haml b/app/views/merge_requests/show/_commits.html.haml index 78fe1a062d3..d10e8fd597a 100644 --- a/app/views/merge_requests/show/_commits.html.haml +++ b/app/views/merge_requests/show/_commits.html.haml @@ -1,6 +1,8 @@  - if @commits.present?    .ui-box -    %h5 Commits (#{@commits.count}) +    %h5 +      %i.icon-list +      Commits (#{@commits.count})      .merge-request-commits        - if @commits.count > 8          %ul.first_mr_commits.unstyled diff --git a/app/views/merge_requests/show/_mr_box.html.haml b/app/views/merge_requests/show/_mr_box.html.haml index 3027719d94d..b542dac98e0 100644 --- a/app/views/merge_requests/show/_mr_box.html.haml +++ b/app/views/merge_requests/show/_mr_box.html.haml @@ -13,9 +13,10 @@        = image_tag gravatar_icon(@merge_request.author_email), :width => 16, :class => "lil_av"        %strong.author= link_to_merge_request_author(@merge_request) -      %cite.cgray and currently assigned to -      = image_tag gravatar_icon(@merge_request.assignee_email), :width => 16, :class => "lil_av" -      %strong.author= link_to_merge_request_assignee(@merge_request) +      - if @merge_request.assignee +        %cite.cgray and currently assigned to +        = image_tag gravatar_icon(@merge_request.assignee_email), :width => 16, :class => "lil_av" +        %strong.author= link_to_merge_request_assignee(@merge_request)    - if @merge_request.closed diff --git a/app/views/notes/_form.html.haml b/app/views/notes/_form.html.haml index 03774d160b9..f5aa1495796 100644 --- a/app/views/notes/_form.html.haml +++ b/app/views/notes/_form.html.haml @@ -32,4 +32,4 @@          %span Any file less than 10 MB -  = f.submit 'Add Comment', :class => "btn primary", :id => "submit_note" +  = f.submit 'Add Comment', :class => "btn primary submit_note", :id => "submit_note" diff --git a/app/views/notes/_per_line_form.html.haml b/app/views/notes/_per_line_form.html.haml index 94c558029d2..8beaf9b5e0c 100644 --- a/app/views/notes/_per_line_form.html.haml +++ b/app/views/notes/_per_line_form.html.haml @@ -24,7 +24,7 @@                  = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"                  %span Commit author            .actions -            = f.submit 'Add note', :class => "btn primary", :id => "submit_note" +            = f.submit 'Add note', :class => "btn primary submit_note", :id => "submit_note"              = link_to "Close", "#", :class => "btn hide-button"  :javascript diff --git a/app/views/notes/_reply_button.html.haml b/app/views/notes/_reply_button.html.haml index f03ba4d7018..db6b35b7ada 100644 --- a/app/views/notes/_reply_button.html.haml +++ b/app/views/notes/_reply_button.html.haml @@ -1,3 +1,4 @@  %tr.line_notes_row.reply    %td{:colspan => 3} +    %i.icon-comment      = link_to "Reply", "#", :class => "line_note_reply_link", "line_code" => line_code, :title => "Add note for this line" diff --git a/app/views/refs/_tree.html.haml b/app/views/refs/_tree.html.haml index ee2f278693c..ba0bd69116b 100644 --- a/app/views/refs/_tree.html.haml +++ b/app/views/refs/_tree.html.haml @@ -13,7 +13,7 @@      = render :partial => "refs/tree_file", :locals => { :name => tree.name, :content => tree.data, :file => tree }    - else      - contents = tree.contents -    %table#tree-slider.bordered-table.table +    %table#tree-slider.bordered-table.table{:class  => "table_#{@hex_path}" }        %thead          %th Name          %th Last Update @@ -29,34 +29,39 @@            %td            %td +      - index = 0        - contents.select{ |i| i.is_a?(Grit::Tree)}.each do |content| -        = render :partial => "refs/tree_item", :locals => { :content => content } +        = render :partial => "refs/tree_item", :locals => { :content => content, :index => (index += 1) }        - contents.select{ |i| i.is_a?(Grit::Blob)}.each do |content| -        = render :partial => "refs/tree_item", :locals => { :content => content } +        = render :partial => "refs/tree_item", :locals => { :content => content, :index => (index += 1) }        - contents.select{ |i| i.is_a?(Grit::Submodule)}.each do |content| -        = render :partial => "refs/submodule_item", :locals => { :content => content } +        = render :partial => "refs/submodule_item", :locals => { :content => content, :index => (index += 1) }      - if content = contents.select{ |c| c.is_a?(Grit::Blob) and c.name =~ /^readme/i }.first -      #tree-readme-holder -        %h3= content.name -        .readme +      .file_holder#README +        .file_title +          %i.icon-file +          = content.name +        .file_content.wiki            - if content.name =~ /\.(md|markdown)$/i              = preserve do                = markdown(content.data)            - else              = simple_format(content.data) -- if params[:path] -  - history_path = tree_file_project_ref_path(@project, @ref, params[:path]) -- else -  - history_path = tree_project_ref_path(@project, @ref)  :javascript    $(function(){      $('select#branch').selectmenu({style:'popup', width:200});      $('select#tag').selectmenu({style:'popup', width:200});      $('.project-refs-select').chosen(); -    history.pushState({ path: this.path }, '', "#{history_path}") +    history.pushState({ path: this.path }, '', "#{@history_path}"); + +  }); + +  // Load last commit log for each file in tree +  $(window).load(function(){ +    ajaxGet('#{@logs_path}');     }); diff --git a/app/views/refs/_tree_commit.html.haml b/app/views/refs/_tree_commit.html.haml new file mode 100644 index 00000000000..1f2524a4c3a --- /dev/null +++ b/app/views/refs/_tree_commit.html.haml @@ -0,0 +1,3 @@ +- if tm +  %strong= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm) += link_to truncate(content_commit.safe_message, :length => tm ? 30 : 50), project_commit_path(@project, content_commit.id), :class => "tree-commit-link" diff --git a/app/views/refs/_tree_file.html.haml b/app/views/refs/_tree_file.html.haml index ee56ab36194..d45a03df8aa 100644 --- a/app/views/refs/_tree_file.html.haml +++ b/app/views/refs/_tree_file.html.haml @@ -1,5 +1,5 @@ -.view_file -  .view_file_header +.file_holder +  .file_title      %i.icon-file      %span.file_name        = name @@ -10,26 +10,28 @@        = link_to "blame", blame_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small"    - if file.text?      - if name =~ /\.(md|markdown)$/i -      #tree-readme-holder -        .readme -          = preserve do -            = markdown(file.data) +      .file_content.wiki +        = preserve do +          = markdown(file.data)      - else -      .view_file_content +      .file_content.code          - unless file.empty?            %div{:class => current_user.dark_scheme ? "black" : "white"}              = preserve do                = raw file.colorize(options: { linenos: 'True'})          - else            %h4.nothing_here_message Empty file +    - elsif file.image? -    .view_file_content_image +    .file_content.image_file        %img{ :src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} +    - else -    %center -      = link_to blob_project_ref_path(@project, @ref, :path => params[:path]) do -        %div.padded -          %br -          = image_tag "download.png", :width => 64 -          %h3 -            Download (#{file.mb_size}) +    .file_content.blob_file +      %center +        = link_to blob_project_ref_path(@project, @ref, :path => params[:path]) do +          %div.padded +            %br +            = image_tag "download.png", :width => 64 +            %h3 +              Download (#{file.mb_size}) diff --git a/app/views/refs/_tree_item.html.haml b/app/views/refs/_tree_item.html.haml index fe2f7293347..4ce16b22ddd 100644 --- a/app/views/refs/_tree_item.html.haml +++ b/app/views/refs/_tree_item.html.haml @@ -1,23 +1,11 @@ -- file = params[:path] ? File.join(params[:path], content.name) : content.name -- content_commit = @project.commits(@commit.id, file, 1).last -- return unless content_commit -%tr{ :class => "tree-item", :url => tree_file_project_ref_path(@project, @ref, file) } +- file = tree_full_path(content) +%tr{ :class => "tree-item #{tree_hex_class(content)}", :url => tree_file_project_ref_path(@project, @ref, file) }    %td.tree-item-file-name -    - if content.is_a?(Grit::Blob) -      - if content.text? -        = image_tag "file_txt.png" -      - elsif content.image? -        = image_tag "file_img.png" -      - else -        = image_tag "file_bin.png" -    - else -      = image_tag "file_dir.png" +    = tree_icon(content)      = link_to truncate(content.name, :length => 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), :remote => :true -  %td.cgray -    = time_ago_in_words(content_commit.committed_date) -    ago -  %td.commit -    - tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name) -    - if tm -      %strong= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm) -    = link_to truncate(content_commit.safe_message, :length => tm ? 30 : 50), project_commit_path(@project, content_commit.id), :class => "tree-commit-link" +  %td.tree_time_ago.cgray +    - if index == 1 +      %span.log_loading +        Loading commit data.. +        = image_tag "ajax_loader_tree.gif", :width => 14 +  %td.tree_commit diff --git a/app/views/refs/blame.html.haml b/app/views/refs/blame.html.haml index 7307d557a30..6a86b91fe74 100644 --- a/app/views/refs/blame.html.haml +++ b/app/views/refs/blame.html.haml @@ -11,8 +11,8 @@        %li= link    .clear -  .view_file.blame_file -    .view_file_header +  .file_holder +    .file_title        %i.icon-file        %span.file_name          = @tree.name @@ -21,7 +21,7 @@          = link_to "raw", blob_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small", :target => "_blank"          = link_to "history", project_commits_path(@project, :path => params[:path], :ref => @ref), :class => "btn very_small"          = link_to "source", tree_file_project_ref_path(@project, @ref, :path => params[:path]), :class => "btn very_small" -    .view_file_content +    .file_content.blame        %table          - @blame.each do |commit, lines|            - commit = Commit.new(commit) @@ -29,7 +29,7 @@              %td.author                = image_tag gravatar_icon(commit.author_email, 16)                = commit.author_name -            %td.commit +            %td.blame_commit                                 = link_to project_commit_path(@project, :id => commit.id) do                  %code= commit.id.to_s[0..10] @@ -37,8 +37,7 @@              %td.lines                = preserve do                  %pre -                  - lines.each do |line| -                    = line +                  = Gitlab::Encode.utf8 lines.join("\n")  :javascript    $(function(){ diff --git a/app/views/refs/logs_tree.js.haml b/app/views/refs/logs_tree.js.haml new file mode 100644 index 00000000000..402f5aa72bc --- /dev/null +++ b/app/views/refs/logs_tree.js.haml @@ -0,0 +1,9 @@ +- @logs.each do |content_data|  +  - file_name = content_data[:file_name] +  - content_commit = content_data[:commit] +  - tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name) + +  :plain +    var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}"); +    row.find("td.tree_time_ago").html('#{escape_javascript(time_ago_in_words(content_commit.committed_date))} ago'); +    row.find("td.tree_commit").html('#{escape_javascript(render("tree_commit", :tm => tm, :content_commit => content_commit))}'); diff --git a/app/views/refs/tree.js.haml b/app/views/refs/tree.js.haml index f4f28adc369..9cf55057a6a 100644 --- a/app/views/refs/tree.js.haml +++ b/app/views/refs/tree.js.haml @@ -1,4 +1,10 @@  :plain +  // Load Files list    $("#tree-holder").html("#{escape_javascript(render(:partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree}))}");    $("#tree-content-holder").show("slide", { direction: "right" }, 150);    $('.project-refs-form #path').val("#{params[:path]}"); + +  //  Load last commit log for each file in tree +  $('#tree-slider').waitForImages(function() { +    ajaxGet('#{@logs_path}');  +  }); diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index c934e262488..b266e4d2156 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -7,16 +7,14 @@      = link_to "Edit", edit_project_snippet_path(@project, @snippet), :class => "btn small right"  %br -#tree-holder -  #tree-content-holder -    .view_file -      .view_file_header -        %i.icon-file -        %strong= @snippet.file_name -        %span.options -          = link_to "raw", raw_project_snippet_path(@project, @snippet), :class => "btn very_small", :target => "_blank" -      .view_file_content -        %div{:class => current_user.dark_scheme ? "black" : ""} -          = raw @snippet.colorize(options: { linenos: 'True'}) +.file_holder +  .file_title +    %i.icon-file +    %strong= @snippet.file_name +    %span.options +      = link_to "raw", raw_project_snippet_path(@project, @snippet), :class => "btn very_small", :target => "_blank" +  .file_content.code +    %div{:class => current_user.dark_scheme ? "black" : ""} +      = raw @snippet.colorize(options: { linenos: 'True'})  = render "notes/notes", :tid => @snippet.id, :tt => "snippet" diff --git a/app/workers/system_hook_worker.rb b/app/workers/system_hook_worker.rb new file mode 100644 index 00000000000..ca154136b97 --- /dev/null +++ b/app/workers/system_hook_worker.rb @@ -0,0 +1,7 @@ +class SystemHookWorker +  @queue = :system_hook + +  def self.perform(hook_id, data) +    SystemHook.find(hook_id).execute data +  end +end diff --git a/config/application.rb b/config/application.rb index 3585c4b0a87..937262237e9 100644 --- a/config/application.rb +++ b/config/application.rb @@ -23,7 +23,7 @@ module Gitlab      # config.plugins = [ :exception_notification, :ssl_requirement, :all ]      # Activate observers that should always be running. -    config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer +    config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_observer      # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.      # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 12c28675139..1818f2c0d01 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -21,6 +21,8 @@ email:  # Like default project limit for user etc  app:     default_projects_limit: 10  +  # backup_path: "/vol/backups"   # default: Rails.root + backups/ +  # backup_keep_time: 604800      # default: 0 (forever) (in seconds)  #  diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 8b9ed8aebd6..5c5987a8857 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -95,11 +95,21 @@ class Settings < Settingslogic      end      def gitolite_admin_uri -      git['admin_uri'] || 'git@localhost:gitolite-admin' +      git_host['admin_uri'] || 'git@localhost:gitolite-admin'      end      def default_projects_limit        app['default_projects_limit'] || 10      end + +    def backup_path +      t = app['backup_path'] || "backups/" +      t = /^\//.match(t) ? t : File.join(Rails.root + t) +      t +    end + +    def backup_keep_time +      app['backup_keep_time'] || 0 +    end    end  end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index cb1ae0ac0be..54011ba5ea3 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -93,10 +93,6 @@ Devise.setup do |config|    # If true, extends the user's remember period when remembered via cookie.    # config.extend_remember_period = false -  # If true, uses the password salt as remember token. This should be turned -  # to false if you are not using database authenticatable. -  config.use_salt_as_remember_token = true -    # Options to be passed to the created cookie. For instance, you can set    # :secure => true in order to force SSL only cookies.    # config.cookie_options = {} @@ -119,7 +115,7 @@ Devise.setup do |config|    # Defines which strategy will be used to lock an account.    # :failed_attempts = Locks an account after a number of failed attempts to sign in.    # :none            = No lock strategy. You should handle locking by yourself. -  # config.lock_strategy = :failed_attempts +  config.lock_strategy = :failed_attempts    # Defines which key will be used when locking and unlocking an account    # config.unlock_keys = [ :email ] @@ -129,14 +125,14 @@ Devise.setup do |config|    # :time  = Re-enables login after a certain amount of time (see :unlock_in below)    # :both  = Enables both strategies    # :none  = No unlock strategy. You should handle unlocking by yourself. -  # config.unlock_strategy = :both +  config.unlock_strategy = :time    # Number of authentication tries before locking an account if lock_strategy    # is failed attempts. -  # config.maximum_attempts = 20 +  config.maximum_attempts = 10    # Time interval to unlock the account if :time is enabled as unlock_strategy. -  # config.unlock_in = 1.hour +  config.unlock_in = 10.minutes    # ==> Configuration for :recoverable    # @@ -160,9 +156,9 @@ Devise.setup do |config|    # Defines name of the authentication token params key    config.token_authentication_key = :private_token -  # If true, authentication through token does not store user in session and needs +  # Authentication through token does not store user in session and needs    # to be supplied on each request. Useful if you are using the token as API token. -  config.stateless_token = true +  config.skip_session_storage << :token_auth    # ==> Scopes configuration    # Turn scoped views on. Before rendering "sessions/new", it will first check for diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index b18263510f8..a78cb6b670b 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -35,13 +35,11 @@ en:        confirmed: 'Your account was successfully confirmed. You are now signed in.'      registrations:        signed_up: 'Welcome! You have signed up successfully.' -      inactive_signed_up: 'You have signed up successfully. However, we could not sign you in because your account is %{reason}.'        updated: 'You updated your account successfully.'        destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' -      reasons: -        inactive: 'inactive' -        unconfirmed: 'unconfirmed' -        locked: 'locked' +      signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.' +      signed_up_but_inactive: 'You have signed up successfully. However, we could not sign you in because your account is not yet activated.' +      signed_up_but_locked: 'You have signed up successfully. However, we could not sign you in because your account is locked.'      unlocks:        send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'        unlocked: 'Your account was successfully unlocked. You are now signed in.' diff --git a/config/routes.rb b/config/routes.rb index af99109883f..dea4df46a30 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -26,7 +26,9 @@ Gitlab::Application.routes.draw do    get 'help' => 'help#index'    get 'help/permissions' => 'help#permissions'    get 'help/workflow' => 'help#workflow' +  get 'help/api' => 'help#api'    get 'help/web_hooks' => 'help#web_hooks' +  get 'help/system_hooks' => 'help#system_hooks'    #    # Admin Area @@ -46,11 +48,13 @@ Gitlab::Application.routes.draw do        end      end      resources :team_members, :only => [:edit, :update, :destroy] -    get 'emails', :to => 'mailer#preview'      get 'mailer/preview_note'      get 'mailer/preview_user_new'      get 'mailer/preview_issue_new' +    resources :hooks, :only => [:index, :create, :destroy] do +      get :test +    end      resource :logs      resource :resque, :controller => 'resque'      root :to => "dashboard#index" @@ -116,6 +120,8 @@ Gitlab::Application.routes.draw do        member do          get "tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } +        get "logs_tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } +          get "blob",            :constraints => {              :id => /[a-zA-Z.0-9\/_\-]+/, @@ -131,6 +137,14 @@ Gitlab::Application.routes.draw do              :path => /.*/            } +        # tree viewer +        get "logs_tree/:path" => "refs#logs_tree", +          :as => :logs_file, +          :constraints => { +            :id => /[a-zA-Z.0-9\/_\-]+/, +            :path => /.*/ +          } +          # blame          get "blame/:path" => "refs#blame",            :as => :blame_file, diff --git a/db/migrate/20110913200833_devise_create_users.rb b/db/migrate/20110913200833_devise_create_users.rb index 01869a9e21c..e00f275c55d 100644 --- a/db/migrate/20110913200833_devise_create_users.rb +++ b/db/migrate/20110913200833_devise_create_users.rb @@ -1,15 +1,43 @@  class DeviseCreateUsers < ActiveRecord::Migration    def self.up      create_table(:users) do |t| -      t.database_authenticatable :null => false -      t.recoverable -      t.rememberable -      t.trackable - -      # t.encryptable -      # t.confirmable -      # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both -      # t.token_authenticatable +      ## Database authenticatable +      t.string :email,              :null => false, :default => "" +      t.string :encrypted_password, :null => false, :default => "" + +      ## Recoverable +      t.string   :reset_password_token +      t.datetime :reset_password_sent_at + +      ## Rememberable +      t.datetime :remember_created_at + +      ## Trackable +      t.integer  :sign_in_count, :default => 0 +      t.datetime :current_sign_in_at +      t.datetime :last_sign_in_at +      t.string   :current_sign_in_ip +      t.string   :last_sign_in_ip + +      ## Encryptable +      # t.string :password_salt + +      ## Confirmable +      # t.string   :confirmation_token +      # t.datetime :confirmed_at +      # t.datetime :confirmation_sent_at +      # t.string   :unconfirmed_email # Only if using reconfirmable + +      ## Lockable +      # t.integer  :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts +      # t.string   :unlock_token # Only if unlock strategy is :email or :both +      # t.datetime :locked_at + +      # Token authenticatable +      # t.string :authentication_token + +      ## Invitable +      # t.string :invitation_token        t.timestamps      end diff --git a/db/migrate/20120706065612_add_lockable_to_users.rb b/db/migrate/20120706065612_add_lockable_to_users.rb new file mode 100644 index 00000000000..cf86e660876 --- /dev/null +++ b/db/migrate/20120706065612_add_lockable_to_users.rb @@ -0,0 +1,6 @@ +class AddLockableToUsers < ActiveRecord::Migration +  def change +    add_column :users, :failed_attempts, :integer, :default => 0 +    add_column :users, :locked_at, :datetime +  end +end diff --git a/db/migrate/20120712080407_add_type_to_web_hook.rb b/db/migrate/20120712080407_add_type_to_web_hook.rb new file mode 100644 index 00000000000..18ab024c817 --- /dev/null +++ b/db/migrate/20120712080407_add_type_to_web_hook.rb @@ -0,0 +1,5 @@ +class AddTypeToWebHook < ActiveRecord::Migration +  def change +    add_column :web_hooks, :type, :string, :default => "ProjectHook" +  end +end diff --git a/db/schema.rb b/db/schema.rb index f2bb16937f4..c4c54f562a3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@  #  # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20120627145613) do +ActiveRecord::Schema.define(:version => 20120712080407) do    create_table "events", :force => true do |t|      t.string   "target_type" @@ -169,6 +169,8 @@ ActiveRecord::Schema.define(:version => 20120627145613) do      t.integer  "theme_id",                              :default => 1,     :null => false      t.string   "bio"      t.boolean  "blocked",                               :default => false, :null => false +    t.integer  "failed_attempts",                       :default => 0 +    t.datetime "locked_at"    end    add_index "users", ["email"], :name => "index_users_on_email", :unique => true @@ -185,8 +187,9 @@ ActiveRecord::Schema.define(:version => 20120627145613) do    create_table "web_hooks", :force => true do |t|      t.string   "url"      t.integer  "project_id" -    t.datetime "created_at", :null => false -    t.datetime "updated_at", :null => false +    t.datetime "created_at",                            :null => false +    t.datetime "updated_at",                            :null => false +    t.string   "type",       :default => "ProjectHook"    end    create_table "wikis", :force => true do |t| diff --git a/doc/installation.md b/doc/installation.md index bf579b174c3..3dfedfe10ad 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -60,7 +60,7 @@ Also read the [Read this before you submit an issue](https://github.com/gitlabhq      sudo apt-get update      sudo apt-get upgrade -    sudo apt-get install -y wget curl gcc checkinstall libxml2-dev libxslt-dev sqlite3 libsqlite3-dev libcurl4-openssl-dev libreadline-gplv2-dev libc6-dev libssl-dev libmysql++-dev make build-essential zlib1g-dev libicu-dev redis-server openssh-server git-core python-dev python-pip libyaml-dev sendmail +    sudo apt-get install -y wget curl gcc checkinstall libxml2-dev libxslt-dev sqlite3 libsqlite3-dev libcurl4-openssl-dev libreadline6-dev libc6-dev libssl-dev libmysql++-dev make build-essential zlib1g-dev libicu-dev redis-server openssh-server git-core python-dev python-pip libyaml-dev sendmail      # If you want to use MySQL:      sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev @@ -107,10 +107,10 @@ Get gitolite source code:  Setup: -    sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" > /home/git/.profile' +    sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" >> /home/git/.profile'      sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; /home/git/gitolite/src/gl-system-install"      sudo cp /home/gitlab/.ssh/id_rsa.pub /home/git/gitlab.pub -    sudo chmod 777 /home/git/gitlab.pub +    sudo chmod 0444 /home/git/gitlab.pub      sudo -u git -H sed -i 's/0077/0007/g' /home/git/share/gitolite/conf/example.gitolite.rc      sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gl-setup -q /home/git/gitlab.pub" @@ -139,6 +139,8 @@ Permissions:      cd /home/gitlab      sudo -H -u gitlab git clone -b stable git://github.com/gitlabhq/gitlabhq.git gitlab      cd gitlab +     +    sudo -u gitlab mkdir tmp      # Rename config files      sudo -u gitlab cp config/gitlab.yml.example config/gitlab.yml @@ -216,15 +218,15 @@ Application can be started with next command:      sudo -u gitlab cp config/unicorn.rb.orig config/unicorn.rb      sudo -u gitlab bundle exec unicorn_rails -c config/unicorn.rb -E production -D -Edit /etc/nginx/nginx.conf. Add in **http** section: +Edit /etc/nginx/nginx.conf. In the *http* section add:      upstream gitlab {          server unix:/home/gitlab/gitlab/tmp/sockets/gitlab.socket;      }      server { -        listen YOUR_SERVER_IP:80; -        server_name gitlab.YOUR_DOMAIN.com; +        listen YOUR_SERVER_IP:80;         # e.g., listen 192.168.1.1:80; +        server_name YOUR_SERVER_FQDN;     # e.g., server_name source.example.com;          root /home/gitlab/gitlab/public;          # individual nginx logs for this gitlab vhost @@ -232,26 +234,26 @@ Edit /etc/nginx/nginx.conf. Add in **http** section:          error_log   /var/log/nginx/gitlab_error.log;          location / { -        # serve static files from defined root folder;. -        # @gitlab is a named location for the upstream fallback, see below -        try_files $uri $uri/index.html $uri.html @gitlab; +            # serve static files from defined root folder;. +            # @gitlab is a named location for the upstream fallback, see below +            try_files $uri $uri/index.html $uri.html @gitlab;          }          # if a file, which is not found in the root folder is requested,           # then the proxy pass the request to the upsteam (gitlab unicorn)          location @gitlab {            proxy_redirect     off; +                      # you need to change this to "https", if you set "ssl" directive to "on"            proxy_set_header   X-FORWARDED_PROTO http; -          proxy_set_header   Host              gitlab.YOUR_SUBDOMAIN.com:80; +          proxy_set_header   Host              $http_host;            proxy_set_header   X-Real-IP         $remote_addr;            proxy_pass http://gitlab;          } -      } -gitlab.YOUR_DOMAIN.com - change to your domain. +Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN** to the IP address and fully-qualified domain name of the host serving GitLab.  Restart nginx: diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index e8d20ad90c2..aff13baf67b 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -1,14 +1,24 @@  module Gitlab -  class Logger +  class Logger < ::Logger      def self.error(message) -      @@logger ||= ::Logger.new(File.join(Rails.root, "log/githost.log")) -      message = Time.now.to_s(:long) + " -> " + message -      @@logger.error(message) +      build.error(message) +    end + +    def self.info(message) +      build.info(message)      end      def self.read_latest        path = Rails.root.join("log/githost.log") -      logs = `tail -n 50 #{path}`.split("\n") +      logs = File.read(path).split("\n")      end + +    def self.build +      new(File.join(Rails.root, "log/githost.log")) +    end + +    def format_message(severity, timestamp, progname, msg) +      "#{timestamp.to_s(:long)} -> #{severity} -> #{msg}\n"  +    end     end  end diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake new file mode 100644 index 00000000000..014483d4e8c --- /dev/null +++ b/lib/tasks/gitlab/backup.rake @@ -0,0 +1,190 @@ +require 'active_record/fixtures' + +namespace :gitlab do +  namespace :app do + +    # Create backup of gitlab system +    desc "GITLAB | Create a backup of the gitlab system" +    task :backup_create => :environment do + +      Rake::Task["gitlab:app:db_dump"].invoke +      Rake::Task["gitlab:app:repo_dump"].invoke + +      Dir.chdir(Gitlab.config.backup_path) + +      # saving additional informations +      s = Hash.new +      s["db_version"]         = "#{ActiveRecord::Migrator.current_version}" +      s["backup_created_at"]  = "#{Time.now}" +      s["gitlab_version"]     = %x{git rev-parse HEAD}.gsub(/\n/,"") +      s["tar_version"]        = %x{tar --version | head -1}.gsub(/\n/,"") + +      File.open("#{Gitlab.config.backup_path}/backup_information.yml", "w+") do |file| +        file << s.to_yaml.gsub(/^---\n/,'') +      end + +      # create archive +      print "Creating backup archive: #{Time.now.to_i}_gitlab_backup.tar " +      if Kernel.system("tar -cf #{Time.now.to_i}_gitlab_backup.tar repositories/ db/ backup_information.yml") +        puts "[DONE]".green +      else +        puts "[FAILED]".red +      end + +      # cleanup: remove tmp files +      print "Deletion of tmp directories..." +      if Kernel.system("rm -rf repositories/ db/ backup_information.yml") +        puts "[DONE]".green +      else +        puts "[FAILED]".red +      end + +      # delete backups +      print "Deleting old backups... " +      if Gitlab.config.backup_keep_time > 0 +        file_list = Dir.glob("*_gitlab_backup.tar").map { |f| f.split(/_/).first.to_i } +        file_list.sort.each do |timestamp| +          if Time.at(timestamp) < (Time.now - Gitlab.config.backup_keep_time) +            %x{rm #{timestamp}_gitlab_backup.tar} +          end +        end +        puts "[DONE]".green +      else +        puts "[SKIPPING]".yellow +      end + +    end + + +    # Restore backup of gitlab system +    desc "GITLAB | Restore a previously created backup" +    task :backup_restore => :environment do + +      Dir.chdir(Gitlab.config.backup_path) + +      # check for existing backups in the backup dir +      file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i } +      puts "no backup found" if file_list.count == 0 +      if file_list.count > 1 && ENV["BACKUP"].nil? +        puts "Found more than one backup, please specify which one you want to restore:" +        puts "rake gitlab:app:backup_restore BACKUP=timestamp_of_backup" +        exit 1; +      end + +      tar_file = ENV["BACKUP"].nil? ? File.join(file_list.first.to_s + "_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar") + +      unless File.exists?(tar_file) +        puts "The specified backup doesn't exist!" +        exit 1; +      end + +      print "Unpacking backup... " +      unless Kernel.system("tar -xf #{tar_file}") +        puts "[FAILED]".red +        exit 1 +      else +        puts "[DONE]".green +      end + +      settings = YAML.load_file("backup_information.yml") +      ENV["VERSION"] = "#{settings["db_version"]}" if settings["db_version"].to_i > 0 + +      # restoring mismatching backups can lead to unexpected problems +      if settings["gitlab_version"] != %x{git rev-parse HEAD}.gsub(/\n/,"") +        puts "gitlab_version mismatch:".red +        puts "  Your current HEAD differs from the HEAD in the backup!".red +        puts "  Please switch to the following revision and try again:".red +        puts "  revision: #{settings["gitlab_version"]}".red +        exit 1 +      end + +      Rake::Task["gitlab:app:db_restore"].invoke +      Rake::Task["gitlab:app:repo_restore"].invoke + +      # cleanup: remove tmp files +      print "Deletion of tmp directories..." +      if Kernel.system("rm -rf repositories/ db/ backup_information.yml") +        puts "[DONE]".green +      else +        puts "[FAILED]".red +      end + +    end + + +    ################################################################################ +    ################################# invoked tasks ################################ + +    ################################# REPOSITORIES ################################# + +    task :repo_dump => :environment do +      backup_path_repo = File.join(Gitlab.config.backup_path, "repositories") +      FileUtils.mkdir_p(backup_path_repo) until Dir.exists?(backup_path_repo) +      puts "Dumping repositories:" +      project = Project.all.map { |n| [n.name,n.path_to_repo] } +      project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")] +      project.each do |project| +        print "- Dumping repository #{project.first}... " +        if Kernel.system("cd #{project.second} > /dev/null 2>&1 && git bundle create #{backup_path_repo}/#{project.first}.bundle --all > /dev/null 2>&1") +          puts "[DONE]".green +        else +          puts "[FAILED]".red +        end +      end +    end + +    task :repo_restore => :environment do +      backup_path_repo = File.join(Gitlab.config.backup_path, "repositories") +      puts "Restoring repositories:" +      project = Project.all.map { |n| [n.name,n.path_to_repo] } +      project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")] +      project.each do |project| +        print "- Restoring repository #{project.first}... " +        FileUtils.rm_rf(project.second) if File.dirname(project.second) # delet old stuff +        if Kernel.system("cd #{File.dirname(project.second)} > /dev/null 2>&1 && git clone --bare #{backup_path_repo}/#{project.first}.bundle #{project.first}.git > /dev/null 2>&1") +          puts "[DONE]".green +        else +          puts "[FAILED]".red +        end +      end +    end + +    ###################################### DB ###################################### + +    task :db_dump => :environment do +      backup_path_db   = File.join(Gitlab.config.backup_path, "db") +      FileUtils.mkdir_p(backup_path_db) until Dir.exists?(backup_path_db) +      puts "Dumping database tables:" +      ActiveRecord::Base.connection.tables.each do |tbl| +        print "- Dumping table #{tbl}... " +        count = 1 +        File.open(File.join(backup_path_db, tbl + ".yml"), "w+") do |file| +          ActiveRecord::Base.connection.select_all("SELECT * FROM `#{tbl}`").each do |line| +            line.delete_if{|k,v| v.blank?} +            output = {tbl + '_' + count.to_s => line} +            file << output.to_yaml.gsub(/^---\n/,'') + "\n" +            count += 1 +          end +          puts "[DONE]".green +        end +      end +    end + +    task :db_restore=> :environment do +      backup_path_db   = File.join(Gitlab.config.backup_path, "db") +      puts "Restoring database tables:" +      Rake::Task["db:reset"].invoke +      Dir.glob(File.join(backup_path_db, "*.yml") ).each do |dir| +        fixture_file = File.basename(dir, ".*" ) +        print "- Loading fixture #{fixture_file}..." +        if File.size(dir) > 0 +          ActiveRecord::Fixtures.create_fixtures(backup_path_db, fixture_file) +          puts "[DONE]".green +        else +          puts "[SKIPPING]".yellow +        end +      end +    end + +  end # namespace end: app +end # namespace end: gitlab diff --git a/resque.sh b/resque.sh index ce7c944b735..ab67c650805 100755 --- a/resque.sh +++ b/resque.sh @@ -1,2 +1,2 @@  mkdir -p tmp/pids -bundle exec rake environment resque:work QUEUE=post_receive,mailer RAILS_ENV=production PIDFILE=tmp/pids/resque_worker.pid BACKGROUND=yes +bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook RAILS_ENV=production PIDFILE=tmp/pids/resque_worker.pid BACKGROUND=yes diff --git a/resque_dev.sh b/resque_dev.sh index 9df4dc1d087..b09cfd9e383 100755 --- a/resque_dev.sh +++ b/resque_dev.sh @@ -1 +1 @@ -bundle exec rake environment resque:work QUEUE=* VVERBOSE=1 +bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1 diff --git a/spec/api/projects_spec.rb b/spec/api/projects_spec.rb index a4e875f73c6..9998ee509bf 100644 --- a/spec/api/projects_spec.rb +++ b/spec/api/projects_spec.rb @@ -78,7 +78,7 @@ describe Gitlab::API do    end    describe "DELETE /projects/:id/snippets/:snippet_id" do -    it "should create a new project snippet" do +    it "should delete existing project snippet" do        expect {          delete "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}"        }.should change { Snippet.count }.by(-1) diff --git a/spec/factories.rb b/spec/factories.rb index ea8c7aef0e2..ab2ca4687da 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -7,6 +7,12 @@ Factory.add(:project, Project) do |obj|    obj.code = 'LGT'  end +Factory.add(:project_without_owner, Project) do |obj| +  obj.name = Faker::Internet.user_name +  obj.path = 'gitlabhq' +  obj.code = 'LGT' +end +  Factory.add(:public_project, Project) do |obj|    obj.name = Faker::Internet.user_name    obj.path = 'gitlabhq' @@ -60,7 +66,11 @@ Factory.add(:key, Key) do |obj|    obj.key = File.read(File.join(Rails.root, "db", "pkey.example"))  end -Factory.add(:web_hook, WebHook) do |obj| +Factory.add(:project_hook, ProjectHook) do |obj| +  obj.url = Faker::Internet.uri("http") +end + +Factory.add(:system_hook, SystemHook) do |obj|    obj.url = Faker::Internet.uri("http")  end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 8d750bef5a5..ac986ccebe3 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -13,7 +13,6 @@ describe MergeRequest do      it { should validate_presence_of(:title) }      it { should validate_presence_of(:author_id) }      it { should validate_presence_of(:project_id) } -    it { should validate_presence_of(:assignee_id) }    end    describe "Scope" do diff --git a/spec/models/project_hooks_spec.rb b/spec/models/project_hooks_spec.rb index fcc969ceba5..5544c5a8683 100644 --- a/spec/models/project_hooks_spec.rb +++ b/spec/models/project_hooks_spec.rb @@ -21,44 +21,44 @@ describe Project, "Hooks" do      end    end -  describe "Web hooks" do +  describe "Project hooks" do      context "with no web hooks" do        it "raises no errors" do          lambda { -          project.execute_web_hooks('oldrev', 'newrev', 'ref', @user) +          project.execute_hooks('oldrev', 'newrev', 'ref', @user)          }.should_not raise_error        end      end      context "with web hooks" do        before do -        @webhook = Factory(:web_hook) -        @webhook_2 = Factory(:web_hook) -        project.web_hooks << [@webhook, @webhook_2] +        @project_hook = Factory(:project_hook) +        @project_hook_2 = Factory(:project_hook) +        project.hooks << [@project_hook, @project_hook_2]        end        it "executes multiple web hook" do -        @webhook.should_receive(:execute).once -        @webhook_2.should_receive(:execute).once +        @project_hook.should_receive(:execute).once +        @project_hook_2.should_receive(:execute).once -        project.execute_web_hooks('oldrev', 'newrev', 'refs/heads/master', @user) +        project.execute_hooks('oldrev', 'newrev', 'refs/heads/master', @user)        end      end      context "does not execute web hooks" do        before do -        @webhook = Factory(:web_hook) -        project.web_hooks << [@webhook] +        @project_hook = Factory(:project_hook) +        project.hooks << [@project_hook]        end        it "when pushing a branch for the first time" do -        @webhook.should_not_receive(:execute) -        project.execute_web_hooks('00000000000000000000000000000000', 'newrev', 'refs/heads/master', @user) +        @project_hook.should_not_receive(:execute) +        project.execute_hooks('00000000000000000000000000000000', 'newrev', 'refs/heads/master', @user)        end        it "when pushing tags" do -        @webhook.should_not_receive(:execute) -        project.execute_web_hooks('oldrev', 'newrev', 'refs/tags/v1.0.0', @user) +        @project_hook.should_not_receive(:execute) +        project.execute_hooks('oldrev', 'newrev', 'refs/tags/v1.0.0', @user)        end      end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 381fe7592c9..351f5748b4d 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -11,7 +11,7 @@ describe Project do      it { should have_many(:issues).dependent(:destroy) }      it { should have_many(:notes).dependent(:destroy) }      it { should have_many(:snippets).dependent(:destroy) } -    it { should have_many(:web_hooks).dependent(:destroy) } +    it { should have_many(:hooks).dependent(:destroy) }      it { should have_many(:deploy_keys).dependent(:destroy) }    end diff --git a/spec/models/system_hook_spec.rb b/spec/models/system_hook_spec.rb new file mode 100644 index 00000000000..4ad4d1681fc --- /dev/null +++ b/spec/models/system_hook_spec.rb @@ -0,0 +1,63 @@ +require "spec_helper" + +describe SystemHook do +  describe "execute" do +    before(:each) { ActiveRecord::Base.observers.enable(:all) } + +    before(:each) do +      @system_hook = Factory :system_hook +      WebMock.stub_request(:post, @system_hook.url) +    end + +    it "project_create hook" do +      user = Factory :user +      with_resque do +        project = Factory :project_without_owner, :owner => user +      end +      WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once +    end +     +    it "project_destroy hook" do +      project = Factory :project +      with_resque do +        project.destroy +      end +      WebMock.should have_requested(:post, @system_hook.url).with(body: /project_destroy/).once +    end + +    it "user_create hook" do +      with_resque do +        Factory :user +      end +      WebMock.should have_requested(:post, @system_hook.url).with(body: /user_create/).once +    end +     +    it "user_destroy hook" do +      user = Factory :user +      with_resque do +        user.destroy +      end +      WebMock.should have_requested(:post, @system_hook.url).with(body: /user_destroy/).once +    end +     +    it "project_create hook" do +      user = Factory :user +      project = Factory :project +      with_resque do +        project.users << user +      end +      WebMock.should have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once +    end +     +    it "project_destroy hook" do +      user = Factory :user +      project = Factory :project +      project.users << user +      with_resque do +        project.users_projects.clear +      end +      WebMock.should have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once +    end +  end + +end diff --git a/spec/models/web_hook_spec.rb b/spec/models/web_hook_spec.rb index 9971bd5819d..885947614d7 100644 --- a/spec/models/web_hook_spec.rb +++ b/spec/models/web_hook_spec.rb @@ -1,6 +1,6 @@  require 'spec_helper' -describe WebHook do +describe ProjectHook do    describe "Associations" do      it { should belong_to :project }    end @@ -23,32 +23,32 @@ describe WebHook do    describe "execute" do      before(:each) do -      @webhook = Factory :web_hook +      @project_hook = Factory :project_hook        @project = Factory :project -      @project.web_hooks << [@webhook] +      @project.hooks << [@project_hook]        @data = { before: 'oldrev', after: 'newrev', ref: 'ref'} -      WebMock.stub_request(:post, @webhook.url) +      WebMock.stub_request(:post, @project_hook.url)      end      it "POSTs to the web hook URL" do -      @webhook.execute(@data) -      WebMock.should have_requested(:post, @webhook.url).once +      @project_hook.execute(@data) +      WebMock.should have_requested(:post, @project_hook.url).once      end      it "POSTs the data as JSON" do        json = @data.to_json -      @webhook.execute(@data) -      WebMock.should have_requested(:post, @webhook.url).with(body: json).once +      @project_hook.execute(@data) +      WebMock.should have_requested(:post, @project_hook.url).with(body: json).once      end      it "catches exceptions" do        WebHook.should_receive(:post).and_raise("Some HTTP Post error")        lambda { -        @webhook.execute(@data) -      }.should_not raise_error +        @project_hook.execute(@data) +      }.should raise_error      end    end  end diff --git a/spec/requests/admin/admin_hooks_spec.rb b/spec/requests/admin/admin_hooks_spec.rb new file mode 100644 index 00000000000..e8a345b689f --- /dev/null +++ b/spec/requests/admin/admin_hooks_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe "Admin::Hooks" do +  before do +    @project = Factory :project, +      :name => "LeGiT", +      :code => "LGT" +    login_as :admin + +    @system_hook = Factory :system_hook + +  end + +  describe "GET /admin/hooks" do +    it "should be ok" do +      visit admin_root_path +      within ".main_menu" do +        click_on "Hooks" +      end +      current_path.should == admin_hooks_path +    end + +    it "should have hooks list" do +      visit admin_hooks_path +      page.should have_content(@system_hook.url) +    end +  end + +  describe "New Hook" do +    before do +      @url = Faker::Internet.uri("http") +      visit admin_hooks_path +      fill_in "hook_url", :with => @url +      expect { click_button "Add System Hook" }.to change(SystemHook, :count).by(1) +    end + +    it "should open new hook popup" do +      page.current_path.should == admin_hooks_path +      page.should have_content(@url) +    end +  end + +  describe "Test" do +    before do +      WebMock.stub_request(:post, @system_hook.url) +      visit admin_hooks_path +      click_link "Test Hook" +    end + +    it { page.current_path.should == admin_hooks_path } +  end + +end diff --git a/spec/requests/admin/security_spec.rb b/spec/requests/admin/security_spec.rb index 0b0edb85a37..0c369740cff 100644 --- a/spec/requests/admin/security_spec.rb +++ b/spec/requests/admin/security_spec.rb @@ -13,9 +13,9 @@ describe "Admin::Projects" do      it { admin_users_path.should be_denied_for :visitor }    end -  describe "GET /admin/emails" do -    it { admin_emails_path.should be_allowed_for :admin } -    it { admin_emails_path.should be_denied_for :user } -    it { admin_emails_path.should be_denied_for :visitor } +  describe "GET /admin/hooks" do +    it { admin_hooks_path.should be_allowed_for :admin } +    it { admin_hooks_path.should be_denied_for :user } +    it { admin_hooks_path.should be_denied_for :visitor }    end  end diff --git a/spec/requests/hooks_spec.rb b/spec/requests/hooks_spec.rb index a508e5ea517..05432f13f3c 100644 --- a/spec/requests/hooks_spec.rb +++ b/spec/requests/hooks_spec.rb @@ -9,7 +9,7 @@ describe "Hooks" do    describe "GET index" do      it "should be available" do -      @hook = Factory :web_hook, :project => @project +      @hook = Factory :project_hook, :project => @project        visit project_hooks_path(@project)        page.should have_content "Hooks"        page.should have_content @hook.url @@ -21,7 +21,7 @@ describe "Hooks" do        @url = Faker::Internet.uri("http")        visit project_hooks_path(@project)        fill_in "hook_url", :with => @url -      expect { click_button "Add Web Hook" }.to change(WebHook, :count).by(1) +      expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1)      end      it "should open new team member popup" do @@ -32,7 +32,8 @@ describe "Hooks" do    describe "Test" do      before do -      @hook = Factory :web_hook, :project => @project +      @hook = Factory :project_hook, :project => @project +      stub_request(:post, @hook.url)        visit project_hooks_path(@project)        click_link "Test Hook"      end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index c70b2f6cebb..5f3b6d3daec 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -22,14 +22,14 @@ describe PostReceive do        Key.stub(find_by_identifier: nil)        project.should_not_receive(:observe_push) -      project.should_not_receive(:execute_web_hooks) +      project.should_not_receive(:execute_hooks)        PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id).should be_false      end      it "asks the project to execute web hooks" do        Project.stub(find_by_path: project) -      project.should_receive(:execute_web_hooks).with('sha-old', 'sha-new', 'refs/heads/master', project.owner) +      project.should_receive(:execute_hooks).with('sha-old', 'sha-new', 'refs/heads/master', project.owner)        PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id)      end diff --git a/vendor/assets/javascripts/jquery.waitforimages.js b/vendor/assets/javascripts/jquery.waitforimages.js new file mode 100644 index 00000000000..95b39c2e074 --- /dev/null +++ b/vendor/assets/javascripts/jquery.waitforimages.js @@ -0,0 +1,144 @@ +/* + * waitForImages 1.4 + * ----------------- + * Provides a callback when all images have loaded in your given selector. + * http://www.alexanderdickson.com/ + * + * + * Copyright (c) 2011 Alex Dickson + * Licensed under the MIT licenses. + * See website for more info. + * + */ + +;(function($) { +    // Namespace all events. +    var eventNamespace = 'waitForImages'; + +    // CSS properties which contain references to images.  +    $.waitForImages = { +        hasImageProperties: [ +        'backgroundImage', +        'listStyleImage', +        'borderImage', +        'borderCornerImage' +        ] +    }; +     +    // Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded. +    $.expr[':'].uncached = function(obj) { +        // Ensure we are dealing with an `img` element with a valid `src` attribute. +        if ( ! $(obj).is('img[src!=""]')) { +            return false; +        } + +        // Firefox's `complete` property will always be`true` even if the image has not been downloaded. +        // Doing it this way works in Firefox. +        var img = document.createElement('img'); +        img.src = obj.src; +        return ! img.complete; +    }; + +    $.fn.waitForImages = function(finishedCallback, eachCallback, waitForAll) { + +        // Handle options object. +        if ($.isPlainObject(arguments[0])) { +            eachCallback = finishedCallback.each; +            waitForAll = finishedCallback.waitForAll; +            finishedCallback = finishedCallback.finished; +        } + +        // Handle missing callbacks. +        finishedCallback = finishedCallback || $.noop; +        eachCallback = eachCallback || $.noop; + +        // Convert waitForAll to Boolean +        waitForAll = !! waitForAll; + +        // Ensure callbacks are functions. +        if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) { +            throw new TypeError('An invalid callback was supplied.'); +        }; + +        return this.each(function() { +            // Build a list of all imgs, dependent on what images will be considered. +            var obj = $(this), +                allImgs = []; + +            if (waitForAll) { +                // CSS properties which may contain an image. +                var hasImgProperties = $.waitForImages.hasImageProperties || [], +                    matchUrl = /url\((['"]?)(.*?)\1\)/g; +                 +                // Get all elements, as any one of them could have a background image. +                obj.find('*').each(function() { +                    var element = $(this); + +                    // If an `img` element, add it. But keep iterating in case it has a background image too. +                    if (element.is('img:uncached')) { +                        allImgs.push({ +                            src: element.attr('src'), +                            element: element[0] +                        }); +                    } + +                    $.each(hasImgProperties, function(i, property) { +                        var propertyValue = element.css(property); +                        // If it doesn't contain this property, skip. +                        if ( ! propertyValue) { +                            return true; +                        } + +                        // Get all url() of this element. +                        var match; +                        while (match = matchUrl.exec(propertyValue)) { +                            allImgs.push({ +                                src: match[2], +                                element: element[0] +                            }); +                        }; +                    }); +                }); +            } else { +                // For images only, the task is simpler. +                obj +                 .find('img:uncached') +                 .each(function() { +                    allImgs.push({ +                        src: this.src, +                        element: this +                    }); +                }); +            }; + +            var allImgsLength = allImgs.length, +                allImgsLoaded = 0; + +            // If no images found, don't bother. +            if (allImgsLength == 0) { +                finishedCallback.call(obj[0]); +            }; + +            $.each(allImgs, function(i, img) { +                 +                var image = new Image; +                 +                // Handle the image loading and error with the same callback. +                $(image).bind('load.' + eventNamespace + ' error.' + eventNamespace, function(event) { +                    allImgsLoaded++; +                     +                    // If an error occurred with loading the image, set the third argument accordingly. +                    eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load'); +                     +                    if (allImgsLoaded == allImgsLength) { +                        finishedCallback.call(obj[0]); +                        return false; +                    }; +                     +                }); + +                image.src = img.src; +            }); +        }); +    }; +})(jQuery); | 
