Skip to content

Commit 006103a

Browse files
committed
Pull request revisions.
1 parent 2dbca92 commit 006103a

File tree

23 files changed

+804
-7
lines changed

23 files changed

+804
-7
lines changed

integrations/pull_revision_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"fmt"
9+
"net/http"
10+
"net/url"
11+
"testing"
12+
"time"
13+
14+
"code.gitea.io/gitea/models"
15+
api "code.gitea.io/gitea/modules/structs"
16+
"github.com/stretchr/testify/assert"
17+
)
18+
19+
func TestPullRevisions(t *testing.T) {
20+
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
21+
session := loginUser(t, "user1")
22+
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
23+
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n")
24+
25+
user1 := models.AssertExistsAndLoadBean(t, &models.User{
26+
Name: "user1",
27+
}).(*models.User)
28+
repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{
29+
OwnerID: user1.ID,
30+
Name: "repo1",
31+
}).(*models.Repository)
32+
33+
testEditFileToNewBranch(t, session, "user1", "repo1", "base", "head", "README.md", "Hello, World (Edited Once)\n")
34+
35+
// Use API to create a conflicting pr
36+
token := getTokenForLoggedInUser(t, session)
37+
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", "user1", "repo1", token), &api.CreatePullRequestOption{
38+
Head: "head",
39+
Base: "base",
40+
Title: "revisions",
41+
})
42+
session.MakeRequest(t, req, 201)
43+
44+
pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{
45+
HeadRepoID: repo1.ID,
46+
BaseRepoID: repo1.ID,
47+
HeadBranch: "head",
48+
BaseBranch: "base",
49+
}).(*models.PullRequest)
50+
51+
req = NewRequest(t, "GET", fmt.Sprintf("/user1/repo1/pulls/%d/revisions", pr.Index))
52+
resp := session.MakeRequest(t, req, http.StatusOK)
53+
54+
htmlDoc := NewHTMLParser(t, resp.Body)
55+
revisions := htmlDoc.doc.Find("td.revision")
56+
assert.Equal(t, 1, len(revisions.Nodes), "The template has changed")
57+
58+
testEditFile(t, session, "user1", "repo1", "head", "README.md", "Revision2")
59+
60+
//Wait for AddTestPullRequestTask
61+
time.Sleep(2 * time.Second)
62+
63+
req = NewRequest(t, "GET", fmt.Sprintf("/user1/repo1/pulls/%d/revisions", pr.Index))
64+
resp = session.MakeRequest(t, req, http.StatusOK)
65+
66+
htmlDoc = NewHTMLParser(t, resp.Body)
67+
revisions = htmlDoc.doc.Find("td.revision")
68+
assert.Equal(t, 2, len(revisions.Nodes), "The template has changed")
69+
})
70+
}

models/fixtures/revision.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
3+
-
4+
id: 1
5+
index: 1
6+
commit: 4a357436d925b5c974181ff12a994538ddc5a269
7+
pr_id: 2
8+
user_id: 1
9+
created: 946684800
10+
number_of_commits: 1
11+
12+
-
13+
id: 2
14+
index: 2
15+
commit: 4a357436d925b5c974181ff12a994538ddc5a269
16+
pr_id: 2
17+
user_id: 98989897
18+
created: 946684800
19+
number_of_commits: 1

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ var migrations = []Migration{
239239
NewMigration("set default password algorithm to Argon2", setDefaultPasswordToArgon2),
240240
// v152 -> v153
241241
NewMigration("add TrustModel field to Repository", addTrustModelToRepository),
242+
// v153 -> v154
243+
NewMigration("create revision table", addRevisionTable),
242244
}
243245

244246
// GetCurrentDBVersion returns the current db version

models/migrations/v153.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"code.gitea.io/gitea/modules/timeutil"
9+
10+
"xorm.io/xorm"
11+
)
12+
13+
func addRevisionTable(x *xorm.Engine) error {
14+
type Revision struct {
15+
ID int64 `xorm:"pk autoincr"`
16+
Commit string `xorm:"NOT NULL"`
17+
NumberOfCommits int64 `xorm:"NOT NULL"`
18+
UserID int64 `xorm:"NOT NULL"`
19+
PRID int64 `xorm:"pr_id NOT NULL"`
20+
Index int64 `xorm:"NOT NULL"`
21+
Created timeutil.TimeStamp `xorm:"NOT NULL"`
22+
}
23+
return x.Sync2(new(Revision))
24+
}

models/models.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ func init() {
8282
new(Action),
8383
new(Issue),
8484
new(PullRequest),
85+
new(Revision),
8586
new(Comment),
8687
new(Attachment),
8788
new(Label),

models/revision.go

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package models
6+
7+
import (
8+
"sort"
9+
10+
"code.gitea.io/gitea/modules/git"
11+
"code.gitea.io/gitea/modules/log"
12+
"code.gitea.io/gitea/modules/timeutil"
13+
)
14+
15+
// Revision Tracking information about PR revisions.
16+
type Revision struct {
17+
ID int64 `xorm:"pk autoincr"`
18+
Commit string `xorm:"NOT NULL"`
19+
NumberOfCommits int64 `xorm:"NOT NULL"`
20+
ChangesetHeadRef string `xorm:"-"`
21+
PrevChangesetHeadRef string `xorm:"-"`
22+
UserID int64 `xorm:"NOT NULL"`
23+
User *User `xorm:"-"`
24+
PRID int64 `xorm:"pr_id NOT NULL"`
25+
PR *PullRequest `xorm:"-"`
26+
Index int64 `xorm:"NOT NULL"`
27+
Created timeutil.TimeStamp `xorm:"NOT NULL"`
28+
}
29+
30+
// GetRevision get the revision by pr and revision index.
31+
func GetRevision(pr *PullRequest, index int64) (*Revision, error) {
32+
rev := &Revision{PRID: pr.ID, Index: index}
33+
has, e := x.Get(rev)
34+
35+
if e != nil {
36+
return nil, e
37+
}
38+
39+
if has {
40+
rev.ChangesetHeadRef = git.GetRevisionRef(pr.ID, index)
41+
if index > 1 {
42+
rev.PrevChangesetHeadRef = git.GetRevisionRef(pr.ID, index-1)
43+
}
44+
45+
return rev, nil
46+
}
47+
return nil, nil
48+
}
49+
50+
// LoadUser load the user field
51+
func (rev *Revision) LoadUser() error {
52+
if rev.User != nil {
53+
return nil
54+
}
55+
56+
user, err := GetUserByID(rev.UserID)
57+
if err != nil {
58+
if IsErrUserNotExist(err) {
59+
rev.User = NewGhostUser()
60+
}
61+
return err
62+
}
63+
rev.User = user
64+
65+
return nil
66+
}
67+
68+
// LoadPullRequest load the PR field
69+
func (rev *Revision) LoadPullRequest() error {
70+
if rev.PR != nil {
71+
return nil
72+
}
73+
74+
pr, err := GetPullRequestByID(rev.PRID)
75+
if err != nil {
76+
return err
77+
}
78+
rev.PR = pr
79+
80+
return nil
81+
}
82+
83+
// GetRevisions Gets the list of revisions of a PR.
84+
func GetRevisions(pr *PullRequest) ([]*Revision, error) {
85+
err := pr.LoadBaseRepo()
86+
if err != nil {
87+
return nil, err
88+
}
89+
90+
baseRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
91+
if err != nil {
92+
return nil, err
93+
}
94+
defer baseRepo.Close()
95+
96+
var refs, error = baseRepo.GetRevisionRefs(pr.Index)
97+
if error != nil {
98+
return nil, error
99+
}
100+
101+
var revisions []*Revision
102+
103+
for _, ref := range refs {
104+
var index = git.GetRevisionIndexFromRef(ref.Name)
105+
106+
if index == nil {
107+
continue
108+
}
109+
110+
rev, err := GetRevision(pr, *index)
111+
112+
if err != nil {
113+
log.Error("GetRevisions, GetRevision: %v", err)
114+
continue
115+
}
116+
117+
err = rev.LoadUser()
118+
119+
if err != nil {
120+
log.Error("GetRevisions, LoadUser: %v", err)
121+
continue
122+
}
123+
124+
err = rev.LoadPullRequest()
125+
126+
if err != nil {
127+
log.Error("GetRevisions, LoadPullRequest: %v", err)
128+
continue
129+
}
130+
131+
revisions = append(revisions, rev)
132+
}
133+
134+
sort.Slice(revisions, func(i, j int) bool {
135+
return revisions[j].Index < revisions[i].Index
136+
})
137+
138+
return revisions, nil
139+
}
140+
141+
// InitializeRevisions Enables revisioning and creates the first revision for a PR.
142+
func InitializeRevisions(pr *PullRequest) (*Revision, error) {
143+
err := pr.LoadBaseRepo()
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
repo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
149+
if err != nil {
150+
return nil, err
151+
}
152+
defer repo.Close()
153+
154+
ref := pr.GetGitRefName()
155+
commit, err := repo.GetRefCommitID(ref)
156+
if err != nil {
157+
return nil, err
158+
}
159+
160+
index, err := repo.InitializeRevisions(pr.Index, commit)
161+
if err != nil {
162+
return nil, err
163+
}
164+
165+
count, err := repo.CommitsCountBetween(pr.MergeBase, commit)
166+
if err != nil {
167+
return nil, err
168+
}
169+
170+
return createMetadata(pr, pr.Issue.Poster, commit, count, index)
171+
}
172+
173+
// CreateNewRevision Creates a new revision entry for the PR.
174+
func CreateNewRevision(pr *PullRequest, user *User, commit string) (*Revision, error) {
175+
err := pr.LoadBaseRepo()
176+
if err != nil {
177+
return nil, err
178+
}
179+
180+
repo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
181+
if err != nil {
182+
return nil, err
183+
}
184+
defer repo.Close()
185+
186+
var refs, error = repo.GetRevisionRefs(pr.Index)
187+
if error != nil {
188+
return nil, error
189+
}
190+
191+
for _, ref := range refs {
192+
if ref.Object.String() == commit {
193+
index := git.GetRevisionIndexFromRef(ref.Name)
194+
195+
if index == nil {
196+
continue
197+
}
198+
199+
count, err := repo.CommitsCountBetween(pr.MergeBase, commit)
200+
if err != nil {
201+
return nil, err
202+
}
203+
204+
return createMetadata(pr, user, commit, count, *index)
205+
}
206+
}
207+
208+
// this can only happen if this is a PR between repos. In that case the push doesn't create the git ref for the revisions
209+
// Only later will we "force-pull" in the PushToBase func which is called from AddTestPullRequestTask.
210+
// If this is the case then it is not surprising that we didn't find a ref for the commit. Lets create it now
211+
index, err := repo.CreateNewRevision(pr.Index, commit)
212+
213+
if err != nil {
214+
return nil, err
215+
}
216+
217+
count, err := repo.CommitsCountBetween(pr.MergeBase, commit)
218+
if err != nil {
219+
return nil, err
220+
}
221+
222+
return createMetadata(pr, user, commit, count, index)
223+
}
224+
225+
func createMetadata(pr *PullRequest, user *User, commit string, count int64, index int64) (*Revision, error) {
226+
rev := &Revision{
227+
PRID: pr.ID,
228+
Index: index,
229+
Commit: commit,
230+
NumberOfCommits: count,
231+
UserID: user.ID,
232+
Created: timeutil.TimeStampNow(),
233+
}
234+
_, err := x.Insert(rev)
235+
if err != nil {
236+
return nil, err
237+
}
238+
_, err = x.Get(rev)
239+
if err != nil {
240+
return nil, err
241+
}
242+
return rev, nil
243+
}

0 commit comments

Comments
 (0)