Commit bbd9a6fe authored by Douwe Maan's avatar Douwe Maan

Merge branch 'dz-merge-request-version'

parents 532489dc a943ccf1
...@@ -27,6 +27,9 @@ v 8.11.1 (unreleased) ...@@ -27,6 +27,9 @@ v 8.11.1 (unreleased)
- Change using size to use count and caching it for number of group members - Change using size to use count and caching it for number of group members
v 8.11.0 v 8.11.0
- Add merge request versions !5467
v 8.11.0 (unreleased)
- Use test coverage value from the latest successful pipeline in badge. !5862 - Use test coverage value from the latest successful pipeline in badge. !5862
- Add test coverage report badge. !5708 - Add test coverage report badge. !5708
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
......
...@@ -17,6 +17,12 @@ ...@@ -17,6 +17,12 @@
.dropdown { .dropdown {
position: relative; position: relative;
.btn-link {
&:hover {
cursor: pointer;
}
}
} }
.open { .open {
......
...@@ -375,6 +375,16 @@ ...@@ -375,6 +375,16 @@
} }
} }
.mr-version-switch {
background: $background-color;
padding: $gl-btn-padding;
color: $gl-placeholder-color;
a.btn-link {
color: $gl-dark-link-color;
}
}
.merge-request-details { .merge-request-details {
.title { .title {
......
...@@ -83,12 +83,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -83,12 +83,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diffs def diffs
apply_diff_view_cookie! apply_diff_view_cookie!
@merge_request_diff = @merge_request.merge_request_diff @merge_request_diff =
if params[:diff_id]
@merge_request.merge_request_diffs.find(params[:diff_id])
else
@merge_request.merge_request_diff
end
respond_to do |format| respond_to do |format|
format.html { define_discussion_vars } format.html { define_discussion_vars }
format.json do format.json do
@diffs = @merge_request.diffs(diff_options) unless @merge_request_diff.latest?
# Disable comments if browsing older version of the diff
@diff_notes_disabled = true
end
@diffs = @merge_request_diff.diffs(diff_options)
render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") }
end end
......
...@@ -10,14 +10,16 @@ class MergeRequest < ActiveRecord::Base ...@@ -10,14 +10,16 @@ class MergeRequest < ActiveRecord::Base
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
belongs_to :merge_user, class_name: "User" belongs_to :merge_user, class_name: "User"
has_one :merge_request_diff, dependent: :destroy has_many :merge_request_diffs, dependent: :destroy
has_one :merge_request_diff,
-> { order('merge_request_diffs.id DESC') }
has_many :events, as: :target, dependent: :destroy has_many :events, as: :target, dependent: :destroy
serialize :merge_params, Hash serialize :merge_params, Hash
after_create :create_merge_request_diff, unless: :importing? after_create :ensure_merge_request_diff, unless: :importing?
after_update :update_merge_request_diff after_update :reload_diff_if_branch_changed
delegate :commits, :real_size, to: :merge_request_diff, prefix: nil delegate :commits, :real_size, to: :merge_request_diff, prefix: nil
...@@ -170,10 +172,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -170,10 +172,10 @@ class MergeRequest < ActiveRecord::Base
end end
def diffs(diff_options = nil) def diffs(diff_options = nil)
if self.compare if compare
self.compare.diffs(diff_options) compare.diffs(diff_options)
else else
Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options) merge_request_diff.diffs(diff_options)
end end
end end
...@@ -184,8 +186,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -184,8 +186,8 @@ class MergeRequest < ActiveRecord::Base
def diff_base_commit def diff_base_commit
if persisted? if persisted?
merge_request_diff.base_commit merge_request_diff.base_commit
elsif diff_start_commit && diff_head_commit else
self.target_project.merge_base_commit(diff_start_sha, diff_head_sha) branch_merge_base_commit
end end
end end
...@@ -246,6 +248,15 @@ class MergeRequest < ActiveRecord::Base ...@@ -246,6 +248,15 @@ class MergeRequest < ActiveRecord::Base
target_project.repository.commit(target_branch) if target_branch_ref target_project.repository.commit(target_branch) if target_branch_ref
end end
def branch_merge_base_commit
start_sha = target_branch_sha
head_sha = source_branch_sha
if start_sha && head_sha
target_project.merge_base_commit(start_sha, head_sha)
end
end
def target_branch_sha def target_branch_sha
@target_branch_sha || target_branch_head.try(:sha) @target_branch_sha || target_branch_head.try(:sha)
end end
...@@ -267,16 +278,16 @@ class MergeRequest < ActiveRecord::Base ...@@ -267,16 +278,16 @@ class MergeRequest < ActiveRecord::Base
# Return diff_refs instance trying to not touch the git repository # Return diff_refs instance trying to not touch the git repository
def diff_sha_refs def diff_sha_refs
if merge_request_diff && merge_request_diff.diff_refs_by_sha? if merge_request_diff && merge_request_diff.diff_refs_by_sha?
return Gitlab::Diff::DiffRefs.new( merge_request_diff.diff_refs
base_sha: merge_request_diff.base_commit_sha,
start_sha: merge_request_diff.start_commit_sha,
head_sha: merge_request_diff.head_commit_sha
)
else else
diff_refs diff_refs
end end
end end
def branch_merge_base_sha
branch_merge_base_commit.try(:sha)
end
def validate_branches def validate_branches
if target_project == source_project && target_branch == source_branch if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target" errors.add :branch_conflict, "You can not use same project/branch for source and target"
...@@ -309,21 +320,31 @@ class MergeRequest < ActiveRecord::Base ...@@ -309,21 +320,31 @@ class MergeRequest < ActiveRecord::Base
end end
end end
def update_merge_request_diff def ensure_merge_request_diff
merge_request_diff || create_merge_request_diff
end
def create_merge_request_diff
merge_request_diffs.create
reload_merge_request_diff
end
def reload_merge_request_diff
merge_request_diff(true)
end
def reload_diff_if_branch_changed
if source_branch_changed? || target_branch_changed? if source_branch_changed? || target_branch_changed?
reload_diff reload_diff
end end
end end
def reload_diff def reload_diff
return unless merge_request_diff && open? return unless open?
old_diff_refs = self.diff_refs old_diff_refs = self.diff_refs
create_merge_request_diff
merge_request_diff.reload_content
MergeRequests::MergeRequestDiffCacheService.new.execute(self) MergeRequests::MergeRequestDiffCacheService.new.execute(self)
new_diff_refs = self.diff_refs new_diff_refs = self.diff_refs
update_diff_notes_positions( update_diff_notes_positions(
......
...@@ -8,8 +8,6 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -8,8 +8,6 @@ class MergeRequestDiff < ActiveRecord::Base
belongs_to :merge_request belongs_to :merge_request
delegate :source_branch_sha, :target_branch_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil
state_machine :state, initial: :empty do state_machine :state, initial: :empty do
state :collected state :collected
state :overflow state :overflow
...@@ -24,12 +22,47 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -24,12 +22,47 @@ class MergeRequestDiff < ActiveRecord::Base
serialize :st_commits serialize :st_commits
serialize :st_diffs serialize :st_diffs
after_create :reload_content, unless: :importing? # All diff information is collected from repository after object is created.
after_save :keep_around_commits, unless: :importing? # It allows you to override variables like head_commit_sha before getting diff.
after_create :save_git_content, unless: :importing?
def self.select_without_diff
select(column_names - ['st_diffs'])
end
def reload_content # Collect information about commits and diff from repository
# and save it to the database as serialized data
def save_git_content
ensure_commits_sha
save_commits
reload_commits reload_commits
reload_diffs save_diffs
keep_around_commits
end
def ensure_commits_sha
merge_request.fetch_ref
self.start_commit_sha ||= merge_request.target_branch_sha
self.head_commit_sha ||= merge_request.source_branch_sha
self.base_commit_sha ||= find_base_sha
save
end
# Override head_commit_sha to keep compatibility with merge request diff
# created before version 8.4 that does not store head_commit_sha in separate db field.
def head_commit_sha
if persisted? && super.nil?
last_commit.try(:sha)
else
super
end
end
# This method will rely on repository branch sha
# in case start_commit_sha is nil. Its necesarry for old merge request diff
# created before version 8.4 to work
def safe_start_commit_sha
start_commit_sha || merge_request.target_branch_sha
end end
def size def size
...@@ -38,14 +71,11 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -38,14 +71,11 @@ class MergeRequestDiff < ActiveRecord::Base
def raw_diffs(options = {}) def raw_diffs(options = {})
if options[:ignore_whitespace_change] if options[:ignore_whitespace_change]
@raw_diffs_no_whitespace ||= begin @diffs_no_whitespace ||=
compare = Gitlab::Git::Compare.new( Gitlab::Git::Compare.new(
repository.raw_repository, repository.raw_repository,
self.start_commit_sha || self.target_branch_sha, safe_start_commit_sha,
self.head_commit_sha || self.source_branch_sha, head_commit_sha).diffs(options)
)
compare.diffs(options)
end
else else
@raw_diffs ||= {} @raw_diffs ||= {}
@raw_diffs[options] ||= load_diffs(st_diffs, options) @raw_diffs[options] ||= load_diffs(st_diffs, options)
...@@ -56,6 +86,11 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -56,6 +86,11 @@ class MergeRequestDiff < ActiveRecord::Base
@commits ||= load_commits(st_commits || []) @commits ||= load_commits(st_commits || [])
end end
def reload_commits
@commits = nil
commits
end
def last_commit def last_commit
commits.first commits.first
end end
...@@ -65,54 +100,59 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -65,54 +100,59 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def base_commit def base_commit
return unless self.base_commit_sha return unless base_commit_sha
project.commit(self.base_commit_sha) project.commit(base_commit_sha)
end end
def start_commit def start_commit
return unless self.start_commit_sha return unless start_commit_sha
project.commit(self.start_commit_sha) project.commit(start_commit_sha)
end end
def head_commit def head_commit
return last_commit unless self.head_commit_sha return unless head_commit_sha
project.commit(head_commit_sha)
end
project.commit(self.head_commit_sha) def diff_refs
return unless start_commit_sha || base_commit_sha
Gitlab::Diff::DiffRefs.new(
base_sha: base_commit_sha,
start_sha: start_commit_sha,
head_sha: head_commit_sha
)
end end
def diff_refs_by_sha? def diff_refs_by_sha?
base_commit_sha? && head_commit_sha? && start_commit_sha? base_commit_sha? && head_commit_sha? && start_commit_sha?
end end
def diffs(diff_options = nil)
Gitlab::Diff::FileCollection::MergeRequestDiff.new(self, diff_options: diff_options)
end
def project
merge_request.target_project
end
def compare def compare
@compare ||= @compare ||=
begin
# Update ref for merge request
merge_request.fetch_ref
Gitlab::Git::Compare.new( Gitlab::Git::Compare.new(
repository.raw_repository, repository.raw_repository,
self.target_branch_sha, safe_start_commit_sha,
self.source_branch_sha head_commit_sha
) )
end end
end
private
# Collect array of Git::Commit objects
# between target and source branches
def unmerged_commits
commits = compare.commits
if commits.present? def latest?
commits = Commit.decorate(commits, merge_request.source_project).reverse self == merge_request.merge_request_diff
end end
commits private
end
def dump_commits(commits) def dump_commits(commits)
commits.map(&:to_hash) commits.map(&:to_hash)
...@@ -122,26 +162,21 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -122,26 +162,21 @@ class MergeRequestDiff < ActiveRecord::Base
array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) } array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) }
end end
# Reload all commits related to current merge request from repo # Load all commits related to current merge request diff from repo
# and save it as array of hashes in st_commits db field # and save it as array of hashes in st_commits db field
def reload_commits def save_commits
new_attributes = {} new_attributes = {}
commit_objects = unmerged_commits commits = compare.commits
if commit_objects.present? if commits.present?
new_attributes[:st_commits] = dump_commits(commit_objects) commits = Commit.decorate(commits, merge_request.source_project).reverse
new_attributes[:st_commits] = dump_commits(commits)
end end
update_columns_serialized(new_attributes) update_columns_serialized(new_attributes)
end end
# Collect array of Git::Diff objects
# between target and source branches
def unmerged_diffs
compare.diffs(Commit.max_diff_options)
end
def dump_diffs(diffs) def dump_diffs(diffs)
if diffs.respond_to?(:map) if diffs.respond_to?(:map)
diffs.map(&:to_hash) diffs.map(&:to_hash)
...@@ -162,16 +197,16 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -162,16 +197,16 @@ class MergeRequestDiff < ActiveRecord::Base
end end
end end
# Reload diffs between branches related to current merge request from repo # Load diffs between branches related to current merge request diff from repo
# and save it as array of hashes in st_diffs db field # and save it as array of hashes in st_diffs db field
def reload_diffs def save_diffs
new_attributes = {} new_attributes = {}
new_diffs = [] new_diffs = []
if commits.size.zero? if commits.size.zero?
new_attributes[:state] = :empty new_attributes[:state] = :empty
else else
diff_collection = unmerged_diffs diff_collection = compare.diffs(Commit.max_diff_options)
if diff_collection.overflow? if diff_collection.overflow?
# Set our state to 'overflow' to make the #empty? and #collected? # Set our state to 'overflow' to make the #empty? and #collected?
...@@ -188,32 +223,17 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -188,32 +223,17 @@ class MergeRequestDiff < ActiveRecord::Base
end end
new_attributes[:st_diffs] = new_diffs new_attributes[:st_diffs] = new_diffs
new_attributes[:start_commit_sha] = self.target_branch_sha
new_attributes[:head_commit_sha] = self.source_branch_sha
new_attributes[:base_commit_sha] = branch_base_sha
update_columns_serialized(new_attributes) update_columns_serialized(new_attributes)
keep_around_commits
end
def project
merge_request.target_project
end end
def repository def repository
project.repository project.repository
end end
def branch_base_commit def find_base_sha
return unless self.source_branch_sha && self.target_branch_sha return unless head_commit_sha && start_commit_sha
project.merge_base_commit(self.source_branch_sha, self.target_branch_sha)
end
def branch_base_sha project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha)
branch_base_commit.try(:sha)
end end
def utf8_st_diffs def utf8_st_diffs
...@@ -248,8 +268,8 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -248,8 +268,8 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def keep_around_commits def keep_around_commits
repository.keep_around(target_branch_sha) repository.keep_around(start_commit_sha)
repository.keep_around(source_branch_sha) repository.keep_around(head_commit_sha)
repository.keep_around(branch_base_sha) repository.keep_around(base_commit_sha)
end end
end end
- if @merge_request_diff.collected? - if @merge_request_diff.collected?
= render 'projects/merge_requests/show/versions'
= render "projects/diffs/diffs", diffs: @diffs = render "projects/diffs/diffs", diffs: @diffs
- elsif @merge_request_diff.empty? - elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
......
- merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff
- if merge_request_diffs.size > 1
.mr-version-switch
Version:
%span.dropdown.inline
%a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} }
%strong.monospace<
- if @merge_request_diff.latest?
#{"latest"}
- else
#{@merge_request_diff.head_commit.short_id}
%span.caret
%ul.dropdown-menu.dropdown-menu-selectable
- merge_request_diffs.each do |merge_request_diff|
%li
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, diff_id: merge_request_diff.id), class: ('is-active' if merge_request_diff == @merge_request_diff) do
%strong.monospace
#{merge_request_diff.head_commit.short_id}
%br
%small
#{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)},
= time_ago_with_tooltip(merge_request_diff.created_at)
- unless @merge_request_diff.latest?
%span.prepend-left-default
= icon('info-circle')
This version is not the latest one. Comments are disabled
.pull-right
%span.monospace
#{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id}
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class MergeRequestDiffRemoveUniq < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
def up
if index_exists?(:merge_request_diffs, :merge_request_id)
remove_index :merge_request_diffs, :merge_request_id
end
end
def down
unless index_exists?(:merge_request_diffs, :merge_request_id)
add_concurrent_index :merge_request_diffs, :merge_request_id, unique: true
end
end
end
class MergeRequestDiffAddIndex < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def up
add_concurrent_index :merge_request_diffs, :merge_request_id
end
def down
if index_exists?(:merge_request_diffs, :merge_request_id)
remove_index :merge_request_diffs, :merge_request_id
end
end
end
...@@ -593,7 +593,7 @@ ActiveRecord::Schema.define(version: 20160823081327) do ...@@ -593,7 +593,7 @@ ActiveRecord::Schema.define(version: 20160823081327) do
t.string "start_commit_sha" t.string "start_commit_sha"
end end
add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", using: :btree
create_table "merge_requests", force: :cascade do |t| create_table "merge_requests", force: :cascade do |t|
t.string "target_branch", null: false t.string "target_branch", null: false
...@@ -620,7 +620,6 @@ ActiveRecord::Schema.define(version: 20160823081327) do ...@@ -620,7 +620,6 @@ ActiveRecord::Schema.define(version: 20160823081327) do
t.string "merge_commit_sha" t.string "merge_commit_sha"
t.datetime "deleted_at" t.datetime "deleted_at"
t.string "in_progress_merge_commit_sha" t.string "in_progress_merge_commit_sha"
t.integer "lock_version"
end end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
......
...@@ -902,3 +902,112 @@ Example response: ...@@ -902,3 +902,112 @@ Example response:
"created_at": "2016-07-01T11:14:15.530Z" "created_at": "2016-07-01T11:14:15.530Z"
} }
``` ```
## Get MR diff versions
Get a list of merge request diff versions.
```
GET /projects/:id/merge_requests/:merge_request_id/versions
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | String | yes | The ID of the project |
| `merge_request_id` | integer | yes | The ID of the merge request |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions
```
Example response:
```json
[{
"id": 110,
"head_commit_sha": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30",
"base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd",
"start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd",
"created_at": "2016-07-26T14:44:48.926Z",
"merge_request_id": 105,
"state": "collected",
"real_size": "1"
}, {
"id": 108,
"head_commit_sha": "3eed087b29835c48015768f839d76e5ea8f07a24",
"base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd",
"start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd",
"created_at": "2016-07-25T14:21:33.028Z",
"merge_request_id": 105,
"state": "collected",
"real_size": "1"
}]
```
## Get a single MR diff version
Get a single merge request diff version.
```
GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | String | yes | The ID of the project |
| `merge_request_id` | integer | yes | The ID of the merge request |
| `version_id` | integer | yes | The ID of the merge request diff version |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions/1
```
Example response:
```json
{
"id": 110,
"head_commit_sha": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30",
"base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd",
"start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd",
"created_at": "2016-07-26T14:44:48.926Z",
"merge_request_id": 105,
"state": "collected",
"real_size": "1",
"commits": [{
"id": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30",
"short_id": "33e2ee85",
"title": "Change year to 2018",
"author_name": "Administrator",
"author_email": "admin@example.com",
"created_at": "2016-07-26T17:44:29.000+03:00",
"message": "Change year to 2018"
}, {
"id": "aa24655de48b36335556ac8a3cd8bb521f977cbd",
"short_id": "aa24655d",
"title": "Update LICENSE",
"author_name": "Administrator",
"author_email": "admin@example.com",
"created_at": "2016-07-25T17:21:53.000+03:00",
"message": "Update LICENSE"
}, {
"id": "3eed087b29835c48015768f839d76e5ea8f07a24",
"short_id": "3eed087b",
"title": "Add license",
"author_name": "Administrator",
"author_email": "admin@example.com",
"created_at": "2016-07-25T17:21:20.000+03:00",
"message": "Add license"
}],
"diffs": [{
"old_path": "LICENSE",
"new_path": "LICENSE",
"a_mode": "0",
"b_mode": "100644",
"diff": "--- /dev/null\n+++ b/LICENSE\n@@ -0,0 +1,21 @@\n+The MIT License (MIT)\n+\n+Copyright (c) 2018 Administrator\n+\n+Permission is hereby granted, free of charge, to any person obtaining a copy\n+of this software and associated documentation files (the \"Software\"), to deal\n+in the Software without restriction, including without limitation the rights\n+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+copies of the Software, and to permit persons to whom the Software is\n+furnished to do so, subject to the following conditions:\n+\n+The above copyright notice and this permission notice shall be included in all\n+copies or substantial portions of the Software.\n+\n+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n+SOFTWARE.\n",
"new_file": true,
"renamed_file": false,
"deleted_file": false
}]
}
```
...@@ -80,3 +80,11 @@ If you click the "Hide whitespace changes" button, you can see the diff without ...@@ -80,3 +80,11 @@ If you click the "Hide whitespace changes" button, you can see the diff without
It is also working on commits compare view. It is also working on commits compare view.
![Commit Compare](merge_requests/commit_compare.png) ![Commit Compare](merge_requests/commit_compare.png)
## Merge Requests versions
Every time you push to merge request branch, a new version of merge request diff
is created. When you visit the merge request page you see latest version of changes.
However you can select an older one from version dropdown
![Merge Request Versions](merge_requests/versions.png)
...@@ -24,7 +24,7 @@ Feature: Project Merge Requests ...@@ -24,7 +24,7 @@ Feature: Project Merge Requests
Scenario: I should see target branch when it is different from default Scenario: I should see target branch when it is different from default
Given project "Shop" have "Bug NS-06" open merge request Given project "Shop" have "Bug NS-06" open merge request
When I visit project "Shop" merge requests page When I visit project "Shop" merge requests page
Then I should see "other_branch" branch Then I should see "feature_conflict" branch
Scenario: I should not see the numbers of diverged commits if the branch is rebased on the target Scenario: I should not see the numbers of diverged commits if the branch is rebased on the target
Given project "Shop" have "Bug NS-07" open merge request with rebased branch Given project "Shop" have "Bug NS-07" open merge request with rebased branch
......
...@@ -58,8 +58,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -58,8 +58,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
expect(find('.merge-request-info')).not_to have_content "master" expect(find('.merge-request-info')).not_to have_content "master"
end end
step 'I should see "other_branch" branch' do step 'I should see "feature_conflict" branch' do
expect(page).to have_content "other_branch" expect(page).to have_content "feature_conflict"
end end
step 'I should see "Bug NS-04" in merge requests' do step 'I should see "Bug NS-04" in merge requests' do
...@@ -124,7 +124,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -124,7 +124,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
source_project: project, source_project: project,
target_project: project, target_project: project,
source_branch: 'fix', source_branch: 'fix',
target_branch: 'other_branch', target_branch: 'feature_conflict',
author: project.users.first, author: project.users.first,
description: "# Description header" description: "# Description header"
) )
......
...@@ -67,5 +67,6 @@ module API ...@@ -67,5 +67,6 @@ module API
mount ::API::Triggers mount ::API::Triggers
mount ::API::Users mount ::API::Users
mount ::API::Variables mount ::API::Variables
mount ::API::MergeRequestDiffs
end end
end end
...@@ -250,6 +250,19 @@ module API ...@@ -250,6 +250,19 @@ module API
end end
end end
class MergeRequestDiff < Grape::Entity
expose :id, :head_commit_sha, :base_commit_sha, :start_commit_sha,
:created_at, :merge_request_id, :state, :real_size
end
class MergeRequestDiffFull < MergeRequestDiff
expose :commits, using: Entities::RepoCommit
expose :diffs, using: Entities::RepoDiff do |compare, _|
compare.raw_diffs(all_diffs: true).to_a
end
end
class SSHKey < Grape::Entity class SSHKey < Grape::Entity
expose :id, :title, :key, :created_at expose :id, :title, :key, :created_at
end end
......
module API
# MergeRequestDiff API
class MergeRequestDiffs < Grape::API
before { authenticate! }
resource :projects do
desc 'Get a list of merge request diff versions' do
detail 'This feature was introduced in GitLab 8.12.'
success Entities::MergeRequestDiff
end
params do
requires :id, type: String, desc: 'The ID of a project'
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
end
get ":id/merge_requests/:merge_request_id/versions" do
merge_request = user_project.merge_requests.
find(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff
end
desc 'Get a single merge request diff version' do
detail 'This feature was introduced in GitLab 8.12.'
success Entities::MergeRequestDiffFull
end
params do
requires :id, type: String, desc: 'The ID of a project'
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
requires :version_id, type: Integer, desc: 'The ID of a merge request diff version'
end
get ":id/merge_requests/:merge_request_id/versions/:version_id" do
merge_request = user_project.merge_requests.
find(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull
end
end
end
end
module Gitlab module Gitlab
module Diff module Diff
module FileCollection module FileCollection
class MergeRequest < Base class MergeRequestDiff < Base
def initialize(merge_request, diff_options:) def initialize(merge_request_diff, diff_options:)
@merge_request = merge_request @merge_request_diff = merge_request_diff
super(merge_request, super(merge_request_diff,
project: merge_request.project, project: merge_request_diff.project,
diff_options: diff_options, diff_options: diff_options,
diff_refs: merge_request.diff_refs) diff_refs: merge_request_diff.diff_refs)
end end
def diff_files def diff_files
...@@ -61,11 +61,11 @@ module Gitlab ...@@ -61,11 +61,11 @@ module Gitlab
end end
def cacheable? def cacheable?
@merge_request.merge_request_diff.present? @merge_request_diff.present?
end end
def cache_key def cache_key
[@merge_request.merge_request_diff, 'highlighted-diff-files', diff_options] [@merge_request_diff, 'highlighted-diff-files', diff_options]
end end
end end
end end
......
require 'spec_helper'
feature 'Merge Request versions', js: true, feature: true do
before do
login_as :admin
merge_request = create(:merge_request, importing: true)
merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e')
project = merge_request.source_project
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
end
it 'show the latest version of the diff' do
page.within '.mr-version-switch' do
expect(page).to have_content 'Version: latest'
end
expect(page).to have_content '8 changed files'
end
describe 'switch between versions' do
before do
page.within '.mr-version-switch' do
find('.btn-link').click
click_link '6f6d7e7e'
end
end
it 'should show older version' do
page.within '.mr-version-switch' do
expect(page).to have_content 'Version: 6f6d7e7e'
end
expect(page).to have_content '5 changed files'
end
end
end
require 'spec_helper' require 'spec_helper'
describe MergeRequestDiff, models: true do describe MergeRequestDiff, models: true do
describe 'create new record' do
subject { create(:merge_request).merge_request_diff }
it { expect(subject).to be_valid }
it { expect(subject).to be_persisted }
it { expect(subject.commits.count).to eq(5) }
it { expect(subject.diffs.count).to eq(8) }
it { expect(subject.head_commit_sha).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
it { expect(subject.base_commit_sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }
it { expect(subject.start_commit_sha).to eq('0b4bc9a49b562e85de7cc9e834518ea6828729b9') }
end
describe '#latest' do
let!(:mr) { create(:merge_request, :with_diffs) }
let!(:first_diff) { mr.merge_request_diff }
let!(:last_diff) { mr.create_merge_request_diff }
it { expect(last_diff.latest?).to be_truthy }
it { expect(first_diff.latest?).to be_falsey }
end
describe '#diffs' do describe '#diffs' do
let(:mr) { create(:merge_request, :with_diffs) } let(:mr) { create(:merge_request, :with_diffs) }
let(:mr_diff) { mr.merge_request_diff } let(:mr_diff) { mr.merge_request_diff }
......
...@@ -9,7 +9,7 @@ describe MergeRequest, models: true do ...@@ -9,7 +9,7 @@ describe MergeRequest, models: true do
it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') } it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') }
it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') } it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') }
it { is_expected.to belong_to(:merge_user).class_name("User") } it { is_expected.to belong_to(:merge_user).class_name("User") }
it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) } it { is_expected.to have_many(:merge_request_diffs).dependent(:destroy) }
end end
describe 'modules' do describe 'modules' do
...@@ -159,7 +159,7 @@ describe MergeRequest, models: true do ...@@ -159,7 +159,7 @@ describe MergeRequest, models: true do
context 'when there are MR diffs' do context 'when there are MR diffs' do
it 'delegates to the MR diffs' do it 'delegates to the MR diffs' do
merge_request.merge_request_diff = MergeRequestDiff.new merge_request.save
expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options)) expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options))
...@@ -316,7 +316,7 @@ describe MergeRequest, models: true do ...@@ -316,7 +316,7 @@ describe MergeRequest, models: true do
end end
it "can be removed if the last commit is the head of the source branch" do it "can be removed if the last commit is the head of the source branch" do
allow(subject.source_project).to receive(:commit).and_return(subject.diff_head_commit) allow(subject).to receive(:source_branch_head).and_return(subject.diff_head_commit)
expect(subject.can_remove_source_branch?(user)).to be_truthy expect(subject.can_remove_source_branch?(user)).to be_truthy
end end
...@@ -721,12 +721,15 @@ describe MergeRequest, models: true do ...@@ -721,12 +721,15 @@ describe MergeRequest, models: true do
let(:commit) { subject.project.commit(sample_commit.id) } let(:commit) { subject.project.commit(sample_commit.id) }
it "reloads the diff content" do it "does not change existing merge request diff" do
expect(subject.merge_request_diff).to receive(:reload_content) expect(subject.merge_request_diff).not_to receive(:save_git_content)
subject.reload_diff subject.reload_diff
end end
it "creates new merge request diff" do
expect { subject.reload_diff }.to change { subject.merge_request_diffs.count }.by(1)
end
it "executs diff cache service" do it "executs diff cache service" do
expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject) expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject)
...@@ -736,13 +739,15 @@ describe MergeRequest, models: true do ...@@ -736,13 +739,15 @@ describe MergeRequest, models: true do
it "updates diff note positions" do it "updates diff note positions" do
old_diff_refs = subject.diff_refs old_diff_refs = subject.diff_refs
merge_request_diff = subject.merge_request_diff
# Update merge_request_diff so that #diff_refs will return commit.diff_refs # Update merge_request_diff so that #diff_refs will return commit.diff_refs
allow(merge_request_diff).to receive(:reload_content) do allow(subject).to receive(:create_merge_request_diff) do
merge_request_diff.base_commit_sha = commit.parent_id subject.merge_request_diffs.create(
merge_request_diff.start_commit_sha = commit.parent_id base_commit_sha: commit.parent_id,
merge_request_diff.head_commit_sha = commit.sha start_commit_sha: commit.parent_id,
head_commit_sha: commit.sha
)
subject.merge_request_diff(true)
end end
expect(Notes::DiffPositionUpdateService).to receive(:new).with( expect(Notes::DiffPositionUpdateService).to receive(:new).with(
...@@ -752,14 +757,31 @@ describe MergeRequest, models: true do ...@@ -752,14 +757,31 @@ describe MergeRequest, models: true do
new_diff_refs: commit.diff_refs, new_diff_refs: commit.diff_refs,
paths: note.position.paths paths: note.position.paths
).and_call_original ).and_call_original
expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note)
expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note)
expect_any_instance_of(DiffNote).to receive(:save).once expect_any_instance_of(DiffNote).to receive(:save).once
subject.reload_diff subject.reload_diff
end end
end end
describe '#branch_merge_base_commit' do
context 'source and target branch exist' do
it { expect(subject.branch_merge_base_commit.sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }
it { expect(subject.branch_merge_base_commit).to be_a(Commit) }
end
context 'when the target branch does not exist' do
before do
subject.project.repository.raw_repository.delete_branch(subject.target_branch)
end
it 'returns nil' do
expect(subject.branch_merge_base_commit).to be_nil
end
end
end
describe "#diff_sha_refs" do describe "#diff_sha_refs" do
context "with diffs" do context "with diffs" do
subject { create(:merge_request, :with_diffs) } subject { create(:merge_request, :with_diffs) }
......
require "spec_helper"
describe API::API, 'MergeRequestDiffs', api: true do
include ApiHelpers
let!(:user) { create(:user) }
let!(:merge_request) { create(:merge_request, importing: true) }
let!(:project) { merge_request.target_project }
before do
merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e')
project.team << [user, :master]
end
describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do
context 'valid merge request' do
before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user) }
let(:merge_request_diff) { merge_request.merge_request_diffs.first }
it { expect(response.status).to eq 200 }
it { expect(json_response.size).to eq(merge_request.merge_request_diffs.size) }
it { expect(json_response.first['id']).to eq(merge_request_diff.id) }
it { expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) }
end
it 'returns a 404 when merge_request_id not found' do
get api("/projects/#{project.id}/merge_requests/999/versions", user)
expect(response).to have_http_status(404)
end
end
describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do
context 'valid merge request' do
before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user) }
let(:merge_request_diff) { merge_request.merge_request_diffs.first }
it { expect(response.status).to eq 200 }
it { expect(json_response['id']).to eq(merge_request_diff.id) }
it { expect(json_response['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) }
it { expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size) }
end
it 'returns a 404 when merge_request_id not found' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user)
expect(response).to have_http_status(404)
end
end
end
...@@ -6,7 +6,7 @@ describe MergeRequests::MergeRequestDiffCacheService do ...@@ -6,7 +6,7 @@ describe MergeRequests::MergeRequestDiffCacheService do
describe '#execute' do describe '#execute' do
it 'retrieves the diff files to cache the highlighted result' do it 'retrieves the diff files to cache the highlighted result' do
merge_request = create(:merge_request) merge_request = create(:merge_request)
cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequest.default_options] cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequestDiff.default_options]
expect(Rails.cache).to receive(:read).with(cache_key).and_return({}) expect(Rails.cache).to receive(:read).with(cache_key).and_return({})
expect(Rails.cache).to receive(:write).with(cache_key, anything) expect(Rails.cache).to receive(:write).with(cache_key, anything)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment