Browse Source

add commit compare functionality

Christopher Brickley 5 years ago
parent
commit
00a864e693

+ 1 - 0
cmd/web.go

@@ -342,6 +342,7 @@ func runWeb(*cli.Context) {
342 342
 		r.Get("/commit/:branchname/*", repo.Diff)
343 343
 		r.Get("/releases", repo.Releases)
344 344
 		r.Get("/archive/*.*", repo.Download)
345
+		r.Get("/compare/:before([a-z0-9]+)...:after([a-z0-9]+)", repo.CompareDiff)
345 346
 	}, ignSignIn, middleware.RepoAssignment(true, true))
346 347
 
347 348
 	m.Group("/:username", func(r *macaron.Router) {

+ 5 - 1
models/action.go

@@ -172,7 +172,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
172 172
 
173 173
 // CommitRepoAction adds new action for committing repository.
174 174
 func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
175
-	repoId int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits) error {
175
+	repoId int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits, oldCommitId string, newCommitId string) error {
176 176
 
177 177
 	opType := COMMIT_REPO
178 178
 	// Check it's tag push or branch.
@@ -226,6 +226,7 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
226 226
 	}
227 227
 
228 228
 	repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
229
+	compareUrl := fmt.Sprintf("%s/compare/%s...%s", repoLink, oldCommitId, newCommitId)
229 230
 	commits := make([]*PayloadCommit, len(commit.Commits))
230 231
 	for i, cmt := range commit.Commits {
231 232
 		commits[i] = &PayloadCommit{
@@ -258,6 +259,9 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
258 259
 			Name:  repo.Owner.LowerName,
259 260
 			Email: repo.Owner.Email,
260 261
 		},
262
+		Before:     oldCommitId,
263
+		After:      newCommitId,
264
+		CompareUrl: compareUrl,
261 265
 	}
262 266
 
263 267
 	for _, w := range ws {

+ 17 - 8
models/git_diff.go

@@ -175,25 +175,30 @@ func ParsePatch(pid int64, cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
175 175
 	return diff, nil
176 176
 }
177 177
 
178
-func GetDiff(repoPath, commitid string) (*Diff, error) {
178
+func GetDiffRange(repoPath, beforeCommitId string, afterCommitId string) (*Diff, error) {
179 179
 	repo, err := git.OpenRepository(repoPath)
180 180
 	if err != nil {
181 181
 		return nil, err
182 182
 	}
183 183
 
184
-	commit, err := repo.GetCommit(commitid)
184
+	commit, err := repo.GetCommit(afterCommitId)
185 185
 	if err != nil {
186 186
 		return nil, err
187 187
 	}
188 188
 
189 189
 	rd, wr := io.Pipe()
190 190
 	var cmd *exec.Cmd
191
-	// First commit of repository.
192
-	if commit.ParentCount() == 0 {
193
-		cmd = exec.Command("git", "show", commitid)
191
+	// if "after" commit given
192
+	if beforeCommitId == "" {
193
+		// First commit of repository.
194
+		if commit.ParentCount() == 0 {
195
+			cmd = exec.Command("git", "show", afterCommitId)
196
+		} else {
197
+			c, _ := commit.Parent(0)
198
+			cmd = exec.Command("git", "diff", c.Id.String(), afterCommitId)
199
+		}
194 200
 	} else {
195
-		c, _ := commit.Parent(0)
196
-		cmd = exec.Command("git", "diff", c.Id.String(), commitid)
201
+		cmd = exec.Command("git", "diff", beforeCommitId, afterCommitId)
197 202
 	}
198 203
 	cmd.Dir = repoPath
199 204
 	cmd.Stdout = wr
@@ -208,7 +213,7 @@ func GetDiff(repoPath, commitid string) (*Diff, error) {
208 213
 	}()
209 214
 	defer rd.Close()
210 215
 
211
-	desc := fmt.Sprintf("GetDiff(%s)", repoPath)
216
+	desc := fmt.Sprintf("GetDiffRange(%s)", repoPath)
212 217
 	pid := process.Add(desc, cmd)
213 218
 	go func() {
214 219
 		// In case process became zombie.
@@ -226,3 +231,7 @@ func GetDiff(repoPath, commitid string) (*Diff, error) {
226 231
 
227 232
 	return ParsePatch(pid, cmd, rd)
228 233
 }
234
+
235
+func GetDiffCommit(repoPath, commitId string) (*Diff, error) {
236
+	return GetDiffRange(repoPath, "", commitId)
237
+}

+ 10 - 4
models/slack.go

@@ -70,19 +70,21 @@ func getSlackPushPayload(p *Payload, slack *Slack) (*SlackPayload, error) {
70 70
 	branchName := refSplit[len(refSplit)-1]
71 71
 	var commitString string
72 72
 
73
-	// TODO: add commit compare before/after link when gogs adds it
74 73
 	if len(p.Commits) == 1 {
75 74
 		commitString = "1 new commit"
76 75
 	} else {
77 76
 		commitString = fmt.Sprintf("%d new commits", len(p.Commits))
77
+		commitString = SlackLinkFormatter(p.CompareUrl, commitString)
78 78
 	}
79 79
 
80
-	text := fmt.Sprintf("[%s:%s] %s pushed by %s", p.Repo.Name, branchName, commitString, p.Pusher.Name)
80
+	repoLink := SlackLinkFormatter(p.Repo.Url, p.Repo.Name)
81
+	branchLink := SlackLinkFormatter(p.Repo.Url+"/src/"+branchName, branchName)
82
+	text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.Name)
81 83
 	var attachmentText string
82 84
 
83 85
 	// for each commit, generate attachment text
84 86
 	for i, commit := range p.Commits {
85
-		attachmentText += fmt.Sprintf("<%s|%s>: %s - %s", commit.Url, commit.Id[:7], SlackFormatter(commit.Message), commit.Author.Name)
87
+		attachmentText += fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.Url, commit.Id[:7]), SlackTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name))
86 88
 		// add linebreak to each commit but the last
87 89
 		if i < len(p.Commits)-1 {
88 90
 			attachmentText += "\n"
@@ -103,7 +105,7 @@ func getSlackPushPayload(p *Payload, slack *Slack) (*SlackPayload, error) {
103 105
 }
104 106
 
105 107
 // see: https://api.slack.com/docs/formatting
106
-func SlackFormatter(s string) string {
108
+func SlackTextFormatter(s string) string {
107 109
 	// take only first line of commit
108 110
 	first := strings.Split(s, "\n")[0]
109 111
 	// replace & < >
@@ -112,3 +114,7 @@ func SlackFormatter(s string) string {
112 114
 	first = strings.Replace(first, ">", "&gt;", -1)
113 115
 	return first
114 116
 }
117
+
118
+func SlackLinkFormatter(url string, text string) string {
119
+	return fmt.Sprintf("<%s|%s>", url, SlackTextFormatter(text))
120
+}

+ 2 - 2
models/update.go

@@ -101,7 +101,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
101 101
 		commit := &base.PushCommits{}
102 102
 
103 103
 		if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
104
-			repos.Id, repoUserName, repoName, refName, commit); err != nil {
104
+			repos.Id, repoUserName, repoName, refName, commit, oldCommitId, newCommitId); err != nil {
105 105
 			log.GitLogger.Fatal(4, "runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
106 106
 		}
107 107
 		return err
@@ -152,7 +152,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
152 152
 
153 153
 	//commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()})
154 154
 	if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
155
-		repos.Id, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits}); err != nil {
155
+		repos.Id, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits}, oldCommitId, newCommitId); err != nil {
156 156
 		return fmt.Errorf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
157 157
 	}
158 158
 	return nil

+ 8 - 5
models/webhook.go

@@ -169,11 +169,14 @@ type BasePayload interface {
169 169
 
170 170
 // Payload represents a payload information of hook.
171 171
 type Payload struct {
172
-	Secret  string           `json:"secret"`
173
-	Ref     string           `json:"ref"`
174
-	Commits []*PayloadCommit `json:"commits"`
175
-	Repo    *PayloadRepo     `json:"repository"`
176
-	Pusher  *PayloadAuthor   `json:"pusher"`
172
+	Secret     string           `json:"secret"`
173
+	Ref        string           `json:"ref"`
174
+	Commits    []*PayloadCommit `json:"commits"`
175
+	Repo       *PayloadRepo     `json:"repository"`
176
+	Pusher     *PayloadAuthor   `json:"pusher"`
177
+	Before     string           `json:"before"`
178
+	After      string           `json:"after"`
179
+	CompareUrl string           `json:"compare_url"`
177 180
 }
178 181
 
179 182
 func (p Payload) GetJSONPayload() ([]byte, error) {

+ 7 - 0
public/css/gogs.css

@@ -968,6 +968,13 @@ body {
968 968
 .guide-box .zclip {
969 969
     left: auto !important;
970 970
 }
971
+div.compare div#commits {
972
+    margin-top: 5px;
973
+}
974
+div.compare div#commits h4 {
975
+  margin: 10px 0;
976
+  line-height: 1.1;
977
+}
971 978
 .diff-head-box h4 {
972 979
     margin-top: 0;
973 980
     margin-bottom: 0;

+ 63 - 2
routers/repo/commit.go

@@ -114,9 +114,9 @@ func Diff(ctx *middleware.Context) {
114 114
 
115 115
 	commit := ctx.Repo.Commit
116 116
 
117
-	diff, err := models.GetDiff(models.RepoPath(userName, repoName), commitId)
117
+	diff, err := models.GetDiffCommit(models.RepoPath(userName, repoName), commitId)
118 118
 	if err != nil {
119
-		ctx.Handle(404, "GetDiff", err)
119
+		ctx.Handle(404, "GetDiffCommit", err)
120 120
 		return
121 121
 	}
122 122
 
@@ -162,6 +162,67 @@ func Diff(ctx *middleware.Context) {
162 162
 	ctx.HTML(200, DIFF)
163 163
 }
164 164
 
165
+func CompareDiff(ctx *middleware.Context) {
166
+	ctx.Data["IsRepoToolbarCommits"] = true
167
+	ctx.Data["IsDiffCompare"] = true
168
+	userName := ctx.Repo.Owner.Name
169
+	repoName := ctx.Repo.Repository.Name
170
+	beforeCommitId := ctx.Params(":before")
171
+	afterCommitId := ctx.Params(":after")
172
+
173
+	commit, err := ctx.Repo.GitRepo.GetCommit(afterCommitId)
174
+	if err != nil {
175
+		ctx.Handle(404, "GetCommit", err)
176
+		return
177
+	}
178
+
179
+	diff, err := models.GetDiffRange(models.RepoPath(userName, repoName), beforeCommitId, afterCommitId)
180
+	if err != nil {
181
+		ctx.Handle(404, "GetDiffRange", err)
182
+		return
183
+	}
184
+
185
+	isImageFile := func(name string) bool {
186
+		blob, err := commit.GetBlobByPath(name)
187
+		if err != nil {
188
+			return false
189
+		}
190
+
191
+		dataRc, err := blob.Data()
192
+		if err != nil {
193
+			return false
194
+		}
195
+		buf := make([]byte, 1024)
196
+		n, _ := dataRc.Read(buf)
197
+		if n > 0 {
198
+			buf = buf[:n]
199
+		}
200
+		_, isImage := base.IsImageFile(buf)
201
+		return isImage
202
+	}
203
+
204
+	commits, err := commit.CommitsBeforeUntil(beforeCommitId)
205
+	if err != nil {
206
+		ctx.Handle(500, "CommitsBeforeUntil", err)
207
+		return
208
+	}
209
+
210
+	ctx.Data["Commits"] = commits
211
+	ctx.Data["CommitCount"] = commits.Len()
212
+	ctx.Data["BeforeCommitId"] = beforeCommitId
213
+	ctx.Data["AfterCommitId"] = afterCommitId
214
+	ctx.Data["Username"] = userName
215
+	ctx.Data["Reponame"] = repoName
216
+	ctx.Data["IsImageFile"] = isImageFile
217
+	ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitId) + "..." + base.ShortSha(afterCommitId) + " · " + userName + "/" + repoName
218
+	ctx.Data["Commit"] = commit
219
+	ctx.Data["Diff"] = diff
220
+	ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
221
+	ctx.Data["SourcePath"] = "/" + path.Join(userName, repoName, "src", afterCommitId)
222
+	ctx.Data["RawPath"] = "/" + path.Join(userName, repoName, "raw", afterCommitId)
223
+	ctx.HTML(200, DIFF)
224
+}
225
+
165 226
 func FileHistory(ctx *middleware.Context) {
166 227
 	ctx.Data["IsRepoToolbarCommits"] = true
167 228
 

+ 1 - 42
templates/repo/commits.tmpl

@@ -3,47 +3,6 @@
3 3
 {{template "repo/nav" .}}
4 4
 {{template "repo/toolbar" .}}
5 5
 <div id="body" class="container">
6
-    <div id="commits">
7
-        <div class="panel panel-default commit-box info-box">
8
-            <div class="panel-heading info-head">
9
-                <form class="search pull-right col-md-3" action="{{.RepoLink}}/commits/{{.BranchName}}/search" method="get" id="commits-search-form">
10
-                    <div class="input-group">
11
-                        <input class="form-control search" type="search" placeholder="search commit" name="q" value="{{.Keyword}}" />
12
-                        <div class="input-group-btn">
13
-                            <button type="submit" class="btn btn-default">Find</button>
14
-                        </div>
15
-                    </div>
16
-                </form>
17
-                <h4>{{.CommitCount}} Commits</h4>
18
-            </div>
19
-            <table class="panel-footer table commit-list table table-striped">
20
-                <thead>
21
-                    <tr>
22
-                        <th class="author">Author</th>
23
-                        <th class="sha">SHA1</th>
24
-                        <th class="message">Message</th>
25
-                        <th class="date">Date</th>
26
-                    </tr>
27
-                </thead>
28
-                <tbody>
29
-                {{ $username := .Username}}
30
-                {{ $reponame := .Reponame}}
31
-                {{$r := List .Commits}}
32
-                {{range $r}}
33
-                <tr>
34
-                    <td class="author"><img class="avatar" src="{{AvatarLink .Author.Email}}" alt=""/><a href="/user/email2user?email={{.Author.Email}}">{{.Author.Name}}</a></td>
35
-                    <td class="sha"><a rel="nofollow" class="label label-success" href="/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td>
36
-                    <td class="message">{{.Summary}} </td>
37
-                    <td class="date">{{TimeSince .Author.When $.Lang}}</td>
38
-                </tr>
39
-                {{end}}
40
-                </tbody>
41
-            </table>
42
-        </div>
43
-        {{if not .IsSearchPage}}<ul class="pagination" id="commits-pager">
44
-            {{if .LastPageNum}}<li><a href="{{.RepoLink}}/commits/{{.BranchName}}{{if .FileName}}/{{.FileName}}{{end}}?p={{.LastPageNum}}" rel="nofollow">&laquo; Newer</a></li>{{end}}
45
-            {{if .NextPageNum}}<li><a href="{{.RepoLink}}/commits/{{.BranchName}}{{if .FileName}}/{{.FileName}}{{end}}?p={{.NextPageNum}}" rel="nofollow">&raquo; Older</a></li>{{end}}
46
-        </ul>{{end}}
47
-    </div>
6
+  {{template "repo/commits_table" .}}
48 7
 </div>
49 8
 {{template "base/footer" .}}

+ 42 - 0
templates/repo/commits_table.tmpl

@@ -0,0 +1,42 @@
1
+<div id="commits">
2
+    <div class="panel panel-default commit-box info-box">
3
+        <div class="panel-heading info-head">
4
+            <form class="search pull-right col-md-3" action="{{.RepoLink}}/commits/{{.BranchName}}/search" method="get" id="commits-search-form">
5
+                <div class="input-group">
6
+                    <input class="form-control search" type="search" placeholder="search commit" name="q" value="{{.Keyword}}" />
7
+                    <div class="input-group-btn">
8
+                        <button type="submit" class="btn btn-default">Find</button>
9
+                    </div>
10
+                </div>
11
+            </form>
12
+            <h4>{{.CommitCount}} Commits</h4>
13
+        </div>
14
+        <table class="panel-footer table commit-list table table-striped">
15
+            <thead>
16
+                <tr>
17
+                    <th class="author">Author</th>
18
+                    <th class="sha">SHA1</th>
19
+                    <th class="message">Message</th>
20
+                    <th class="date">Date</th>
21
+                </tr>
22
+            </thead>
23
+            <tbody>
24
+            {{ $username := .Username}}
25
+            {{ $reponame := .Reponame}}
26
+            {{$r := List .Commits}}
27
+            {{range $r}}
28
+            <tr>
29
+                <td class="author"><img class="avatar" src="{{AvatarLink .Author.Email}}" alt=""/><a href="/user/email2user?email={{.Author.Email}}">{{.Author.Name}}</a></td>
30
+                <td class="sha"><a rel="nofollow" class="label label-success" href="/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td>
31
+                <td class="message">{{.Summary}} </td>
32
+                <td class="date">{{TimeSince .Author.When $.Lang}}</td>
33
+            </tr>
34
+            {{end}}
35
+            </tbody>
36
+        </table>
37
+    </div>
38
+    {{if not .IsSearchPage}}<ul class="pagination" id="commits-pager">
39
+        {{if .LastPageNum}}<li><a href="{{.RepoLink}}/commits/{{.BranchName}}{{if .FileName}}/{{.FileName}}{{end}}?p={{.LastPageNum}}" rel="nofollow">&laquo; Newer</a></li>{{end}}
40
+        {{if .NextPageNum}}<li><a href="{{.RepoLink}}/commits/{{.BranchName}}{{if .FileName}}/{{.FileName}}{{end}}?p={{.NextPageNum}}" rel="nofollow">&raquo; Older</a></li>{{end}}
41
+    </ul>{{end}}
42
+</div>

+ 13 - 2
templates/repo/diff.tmpl

@@ -3,9 +3,20 @@
3 3
 {{template "repo/nav" .}}
4 4
 <div id="body" class="container" data-page="repo">
5 5
     <div id="source">
6
+      {{if .IsDiffCompare }}
6 7
         <div class="panel panel-info diff-box diff-head-box">
7 8
             <div class="panel-heading">
8 9
                 <a class="pull-right btn btn-primary btn-sm" rel="nofollow" href="{{.SourcePath}}">Browse Source</a>
10
+                <h4><a href="{{$.RepoLink}}/commit/{{.BeforeCommitId}}" class="label label-success">{{ShortSha .BeforeCommitId}}</a> ... <a href="{{$.RepoLink}}/commit/{{.AfterCommitId}}" class="label label-success">{{ShortSha .AfterCommitId}}</a></h4>
11
+            </div>
12
+            <div class="panel-body compare">
13
+              {{template "repo/commits_table" .}}
14
+            </div>
15
+        </div>
16
+      {{else}}
17
+          <div class="panel panel-info diff-box diff-head-box">
18
+            <div class="panel-heading">
19
+                <a class="pull-right btn btn-primary btn-sm" rel="nofollow" href="{{.SourcePath}}">Browse Source</a>
9 20
                 <h4>{{.Commit.Message}}</h4>
10 21
             </div>
11 22
             <div class="panel-body">
@@ -22,9 +33,9 @@
22 33
                     <a class="name" href="/user/email2user?email={{.Commit.Author.Email}}"><strong>{{.Commit.Author.Name}}</strong></a>
23 34
                     <span class="time">{{TimeSince .Commit.Author.When $.Lang}}</span>
24 35
                 </p>
25
-            </div>
36
+          </div>
26 37
         </div>
27
-
38
+      {{end}}
28 39
         {{if .DiffNotAvailable}}
29 40
         <h4>Diff Data Not Available.</h4>
30 41
         {{else}}