Commit c4b9bd04 authored by Paco Guzman's avatar Paco Guzman

API support for the 'since' and 'until' operators on commit requests

- Parameter validation as ISO8601 format
parent a792427e
...@@ -8,6 +8,7 @@ v 8.8.0 (unreleased) ...@@ -8,6 +8,7 @@ v 8.8.0 (unreleased)
- Replace Devise Async with Devise ActiveJob integration. !3902 (Connor Shea) - Replace Devise Async with Devise ActiveJob integration. !3902 (Connor Shea)
- Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea) - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea)
- Added button to toggle whitespaces changes on diff view - Added button to toggle whitespaces changes on diff view
- API support for the 'since' and 'until' operators on commit requests (Paco Guzman)
v 8.7.1 (unreleased) v 8.7.1 (unreleased)
- Throttle the update of `project.last_activity_at` to 1 minute. !3848 - Throttle the update of `project.last_activity_at` to 1 minute. !3848
......
...@@ -351,7 +351,7 @@ GEM ...@@ -351,7 +351,7 @@ GEM
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_emoji (0.3.1) gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1) gemojione (~> 2.2, >= 2.2.1)
gitlab_git (10.0.0) gitlab_git (10.0.2)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
......
...@@ -15,7 +15,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -15,7 +15,7 @@ class Projects::CommitsController < Projects::ApplicationController
if search.present? if search.present?
@repository.find_commits_by_message(search, @ref, @path, @limit, @offset).compact @repository.find_commits_by_message(search, @ref, @path, @limit, @offset).compact
else else
@repository.commits(@ref, @path, @limit, @offset) @repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
end end
@note_counts = project.notes.where(commit_id: @commits.map(&:id)). @note_counts = project.notes.where(commit_id: @commits.map(&:id)).
......
...@@ -17,7 +17,7 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -17,7 +17,7 @@ class Projects::GraphsController < Projects::ApplicationController
end end
def commits def commits
@commits = @project.repository.commits(@ref, nil, 2000, 0, true) @commits = @project.repository.commits(@ref, limit: 2000, skip_merges: true)
@commits_graph = Gitlab::Graphs::Commits.new(@commits) @commits_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days @commits_per_week_days = @commits_graph.commits_per_week_days
@commits_per_time = @commits_graph.commits_per_time @commits_per_time = @commits_graph.commits_per_time
...@@ -55,7 +55,7 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -55,7 +55,7 @@ class Projects::GraphsController < Projects::ApplicationController
private private
def fetch_graph def fetch_graph
@commits = @project.repository.commits(@ref, nil, 6000, 0, true) @commits = @project.repository.commits(@ref, limit: 6000, skip_merges: true)
@log = [] @log = []
@commits.each do |commit| @commits.each do |commit|
......
...@@ -87,13 +87,15 @@ class Repository ...@@ -87,13 +87,15 @@ class Repository
nil nil
end end
def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false) def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
options = { options = {
repo: raw_repository, repo: raw_repository,
ref: ref, ref: ref,
path: path, path: path,
limit: limit, limit: limit,
offset: offset, offset: offset,
after: after,
before: before,
# --follow doesn't play well with --skip. See: # --follow doesn't play well with --skip. See:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520 # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
follow: false, follow: false,
...@@ -575,7 +577,7 @@ class Repository ...@@ -575,7 +577,7 @@ class Repository
end end
def contributors def contributors
commits = self.commits(nil, nil, 2000, 0, true) commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
commits.group_by(&:author_email).map do |email, commits| commits.group_by(&:author_email).map do |email, commits|
contributor = Gitlab::Contributor.new contributor = Gitlab::Contributor.new
......
...@@ -12,6 +12,8 @@ GET /projects/:id/repository/commits ...@@ -12,6 +12,8 @@ GET /projects/:id/repository/commits
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch | | `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
| `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
```bash ```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits" curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits"
......
...@@ -12,14 +12,20 @@ module API ...@@ -12,14 +12,20 @@ module API
# Parameters: # Parameters:
# id (required) - The ID of a project # id (required) - The ID of a project
# ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
# since (optional) - Only commits after or in this date will be returned
# until (optional) - Only commits before or in this date will be returned
# Example Request: # Example Request:
# GET /projects/:id/repository/commits # GET /projects/:id/repository/commits
get ":id/repository/commits" do get ":id/repository/commits" do
datetime_attributes! :since, :until
page = (params[:page] || 0).to_i page = (params[:page] || 0).to_i
per_page = (params[:per_page] || 20).to_i per_page = (params[:per_page] || 20).to_i
ref = params[:ref_name] || user_project.try(:default_branch) || 'master' ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
after = params[:since]
before = params[:until]
commits = user_project.repository.commits(ref, nil, per_page, page * per_page) commits = user_project.repository.commits(ref, limit: per_page, offset: page * per_page, after: after, before: before)
present commits, with: Entities::RepoCommit present commits, with: Entities::RepoCommit
end end
......
...@@ -183,6 +183,22 @@ module API ...@@ -183,6 +183,22 @@ module API
Gitlab::Access.options_with_owner.values.include? level.to_i Gitlab::Access.options_with_owner.values.include? level.to_i
end end
# Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601
# format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked.
#
# Parameters:
# keys (required) - An array consisting of elements that must be parseable as dates from the params hash
def datetime_attributes!(*keys)
keys.each do |key|
begin
params[key] = Time.xmlschema(params[key]) if params[key].present?
rescue ArgumentError
message = "\"" + key.to_s + "\" must be a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ"
render_api_error!(message, 400)
end
end
end
def issuable_order_by def issuable_order_by
if params["order_by"] == 'updated_at' if params["order_by"] == 'updated_at'
'updated_at' 'updated_at'
......
...@@ -66,7 +66,7 @@ module Gitlab ...@@ -66,7 +66,7 @@ module Gitlab
# This method provide a sample data generated with # This method provide a sample data generated with
# existing project and commits to test webhooks # existing project and commits to test webhooks
def build_sample(project, user) def build_sample(project, user)
commits = project.repository.commits(project.default_branch, nil, 3) commits = project.repository.commits(project.default_branch, limit: 3)
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}" ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
build(project, user, commits.last.id, commits.first.id, ref, commits) build(project, user, commits.last.id, commits.first.id, ref, commits)
end end
......
...@@ -561,7 +561,7 @@ describe Repository, models: true do ...@@ -561,7 +561,7 @@ describe Repository, models: true do
end end
describe :skip_merged_commit do describe :skip_merged_commit do
subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } } subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map{ |k| k.id } }
it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') } it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
end end
......
...@@ -32,6 +32,41 @@ describe API::API, api: true do ...@@ -32,6 +32,41 @@ describe API::API, api: true do
expect(response.status).to eq(401) expect(response.status).to eq(401)
end end
end end
context "since optional parameter" do
it "should return project commits since provided parameter" do
commits = project.repository.commits("master")
since = commits.second.created_at
get api("/projects/#{project.id}/repository/commits?since=#{since.utc.iso8601}", user)
expect(json_response.size).to eq 2
expect(json_response.first["id"]).to eq(commits.first.id)
expect(json_response.second["id"]).to eq(commits.second.id)
end
end
context "until optional parameter" do
it "should return project commits until provided parameter" do
commits = project.repository.commits("master")
before = commits.second.created_at
get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user)
expect(json_response.size).to eq(commits.size - 1)
expect(json_response.first["id"]).to eq(commits.second.id)
expect(json_response.second["id"]).to eq(commits.third.id)
end
end
context "invalid xmlschema date parameters" do
it "should return an invalid parameter error message" do
get api("/projects/#{project.id}/repository/commits?since=invalid-date", user)
expect(response.status).to eq(400)
expect(json_response['message']).to include "\"since\" must be a timestamp in ISO 8601 format"
end
end
end end
describe "GET /projects:id/repository/commits/:sha" do describe "GET /projects:id/repository/commits/:sha" do
......
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