Railguard to not display large commits or diffs

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-08 04:53:29 +0200
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-08 04:53:29 +0200
Commit 4416be593969333a14f5f1a9033c39e53d3a5e57 (patch)
-rw-r--r-- hcommit.go 193
-rw-r--r-- models.go 2
-rw-r--r-- views/commit.html 10
3 files changed, 116 insertions, 89 deletions
diff --git a/hcommit.go b/hcommit.go
...
28
  
28
  
29
	var fileDiffs []FileDiff
29
	var fileDiffs []FileDiff
30
	maxChanges := 0
30
	maxChanges := 0
  
31
	totalAdds, totalDels := 0, 0
  
32
	isCommitTooLarge := false
  
33
  
31
	if commit.NumParents() > 0 {
34
	if commit.NumParents() > 0 {
32
		parent, _ := commit.Parent(0)
35
		parent, _ := commit.Parent(0)
33
		patch, err := parent.Patch(commit)
36
		patch, err := parent.Patch(commit)
34
		if err == nil {
37
		if err == nil {
  
38
			pStats := patch.Stats()
  
39
			for _, s := range pStats {
  
40
				totalAdds += s.Addition
  
41
				totalDels += s.Deletion
  
42
			}
  
43
			isCommitTooLarge = (totalAdds + totalDels) > 5000
  
44
  
  
45
			// Map stats by name for quick lookup
  
46
			statsMap := make(map[string]object.FileStat)
  
47
			for _, s := range pStats {
  
48
				statsMap[s.Name] = s
  
49
			}
  
50
  
35
			for _, fp := range patch.FilePatches() {
51
			for _, fp := range patch.FilePatches() {
36
				from, to := fp.Files()
52
				from, to := fp.Files()
37
				name := ""
53
				name := ""
...
44
					mode = formatMode(from.Mode())
60
					mode = formatMode(from.Mode())
45
				}
61
				}
46
  
62
  
47
				fileAdd, fileDel := 0, 0
63
				stat := statsMap[name]
  
64
				fileAdd, fileDel := stat.Addition, stat.Deletion
48
				isBinary := fp.IsBinary()
65
				isBinary := fp.IsBinary()
49
				deleted := to == nil
66
				deleted := to == nil
50
				var oldSize, newSize int64
67
				var oldSize, newSize int64
...
64
					}
81
					}
65
				}
82
				}
66
  
83
  
67
				var diffLines []DiffLine
84
				if fileAdd+fileDel > maxChanges {
68
				leftNo, rightNo := 1, 1
85
					maxChanges = fileAdd + fileDel
  
86
				}
  
87
  
  
88
				isFileTooLarge := (fileAdd + fileDel) > 1000
  
89
				var filteredLines []DiffLine
69
  
90
  
70
				var delLines []string
91
				if !isCommitTooLarge && !isFileTooLarge && !isBinary {
71
				var addLines []string
92
					var diffLines []DiffLine
  
93
					leftNo, rightNo := 1, 1
72
  
94
  
73
				flush := func() {
95
					var delLines []string
74
					max := len(delLines)
96
					var addLines []string
75
					if len(addLines) > max {
97
  
76
						max = len(addLines)
98
					flush := func() {
77
					}
99
						max := len(delLines)
78
					for i := 0; i < max; i++ {
100
						if len(addLines) > max {
79
						line := DiffLine{}
101
							max = len(addLines)
80
						if i < len(delLines) && i < len(addLines) {
102
						}
81
							line.LeftNo = fmt.Sprintf("%d", leftNo)
103
						for i := 0; i < max; i++ {
82
							line.Left = delLines[i]
104
							line := DiffLine{}
83
							line.RightNo = fmt.Sprintf("%d", rightNo)
105
							if i < len(delLines) && i < len(addLines) {
84
							line.Right = addLines[i]
106
								line.LeftNo = fmt.Sprintf("%d", leftNo)
85
							line.Type = "mod"
107
								line.Left = delLines[i]
86
							leftNo++
108
								line.RightNo = fmt.Sprintf("%d", rightNo)
87
							rightNo++
109
								line.Right = addLines[i]
88
							fileAdd++
110
								line.Type = "mod"
89
							fileDel++
111
								leftNo++
90
						} else if i < len(delLines) {
112
								rightNo++
91
							line.LeftNo = fmt.Sprintf("%d", leftNo)
113
							} else if i < len(delLines) {
92
							line.Left = delLines[i]
114
								line.LeftNo = fmt.Sprintf("%d", leftNo)
93
							line.Type = "del"
115
								line.Left = delLines[i]
94
							leftNo++
116
								line.Type = "del"
95
							fileDel++
117
								leftNo++
96
						} else if i < len(addLines) {
118
							} else if i < len(addLines) {
97
							line.RightNo = fmt.Sprintf("%d", rightNo)
119
								line.RightNo = fmt.Sprintf("%d", rightNo)
98
							line.Right = addLines[i]
120
								line.Right = addLines[i]
99
							line.Type = "add"
121
								line.Type = "add"
100
							rightNo++
122
								rightNo++
101
							fileAdd++
123
							}
  
124
							diffLines = append(diffLines, line)
102
						}
125
						}
103
						diffLines = append(diffLines, line)
126
						delLines = nil
  
127
						addLines = nil
104
					}
128
					}
105
					delLines = nil
  
106
					addLines = nil
  
107
				}
  
108
  
129
  
109
				for _, chunk := range fp.Chunks() {
130
					for _, chunk := range fp.Chunks() {
110
					lines := strings.Split(strings.TrimSuffix(chunk.Content(), "\n"), "\n")
131
						lines := strings.Split(strings.TrimSuffix(chunk.Content(), "\n"), "\n")
111
					switch chunk.Type() {
132
						switch chunk.Type() {
112
					case diff.Equal:
133
						case diff.Equal:
113
						flush()
134
							flush()
114
						for _, line := range lines {
135
							for _, line := range lines {
115
							diffLines = append(diffLines, DiffLine{
136
								diffLines = append(diffLines, DiffLine{
116
								LeftNo:  fmt.Sprintf("%d", leftNo),
137
									LeftNo:  fmt.Sprintf("%d", leftNo),
117
								Left:    line,
138
									Left:    line,
118
								RightNo: fmt.Sprintf("%d", rightNo),
139
									RightNo: fmt.Sprintf("%d", rightNo),
119
								Right:   line,
140
									Right:   line,
120
								Type:    "eq",
141
									Type:    "eq",
121
							})
142
								})
122
							leftNo++
143
								leftNo++
123
							rightNo++
144
								rightNo++
  
145
							}
  
146
						case diff.Delete:
  
147
							delLines = append(delLines, lines...)
  
148
						case diff.Add:
  
149
							addLines = append(addLines, lines...)
124
						}
150
						}
125
					case diff.Delete:
  
126
						delLines = append(delLines, lines...)
  
127
					case diff.Add:
  
128
						addLines = append(addLines, lines...)
  
129
					}
151
					}
130
				}
152
					flush()
131
				flush()
  
132
  
  
133
				if fileAdd+fileDel > maxChanges {
  
134
					maxChanges = fileAdd + fileDel
  
135
				}
  
136
  
153
  
137
				visible := make([]bool, len(diffLines))
154
					visible := make([]bool, len(diffLines))
138
				for i, line := range diffLines {
155
					for i, line := range diffLines {
139
					if line.Type == "add" || line.Type == "del" || line.Type == "mod" {
156
						if line.Type == "add" || line.Type == "del" || line.Type == "mod" {
140
						for j := i - 3; j <= i+3; j++ {
157
							for j := i - 3; j <= i+3; j++ {
141
							if j >= 0 && j < len(diffLines) {
158
								if j >= 0 && j < len(diffLines) {
142
								visible[j] = true
159
									visible[j] = true
  
160
								}
143
							}
161
							}
144
						}
162
						}
145
					}
163
					}
146
				}
  
147
  
164
  
148
				var filteredLines []DiffLine
165
					lastWasGap := false
149
				lastWasGap := false
166
					for i, isVisible := range visible {
150
				for i, isVisible := range visible {
167
						if isVisible {
151
					if isVisible {
168
							filteredLines = append(filteredLines, diffLines[i])
152
						filteredLines = append(filteredLines, diffLines[i])
169
							lastWasGap = false
153
						lastWasGap = false
170
						} else {
154
					} else {
171
							if !lastWasGap {
155
						if !lastWasGap {
172
								filteredLines = append(filteredLines, DiffLine{Type: "gap"})
156
							filteredLines = append(filteredLines, DiffLine{Type: "gap"})
173
								lastWasGap = true
157
							lastWasGap = true
174
							}
158
						}
175
						}
159
					}
176
					}
160
				}
177
				}
...
169
					OldSize:  oldSize,
186
					OldSize:  oldSize,
170
					NewSize:  newSize,
187
					NewSize:  newSize,
171
					Deleted:  deleted,
188
					Deleted:  deleted,
  
189
					TooLarge: isFileTooLarge,
172
				})
190
				})
173
			}
191
			}
174
		}
192
		}
175
	}
193
	} else {
176
  
194
		// Initial commit - no parents
177
	stats, _ := commit.Stats()
195
		// We could still show the whole file as additions, but the current logic handles parents only
178
	adds, dels := 0, 0
  
179
	for _, s := range stats {
  
180
		adds += s.Addition
  
181
		dels += s.Deletion
  
182
	}
196
	}
183
  
197
  
184
	data := struct {
198
	data := struct {
...
197
			CommitterEmail: commit.Committer.Email,
211
			CommitterEmail: commit.Committer.Email,
198
			CommitterDate:  commit.Committer.When,
212
			CommitterDate:  commit.Committer.When,
199
			Message:        commit.Message,
213
			Message:        commit.Message,
200
			Additions:      adds,
214
			Additions:      totalAdds,
201
			Deletions:      dels,
215
			Deletions:      totalDels,
  
216
			TooLarge:       isCommitTooLarge,
202
		},
217
		},
203
		FileDiffs:  fileDiffs,
218
		FileDiffs:  fileDiffs,
204
		MaxChanges: maxChanges,
219
		MaxChanges: maxChanges,
...
diff --git a/models.go b/models.go
...
26
	Message        string
26
	Message        string
27
	Additions      int
27
	Additions      int
28
	Deletions      int
28
	Deletions      int
  
29
	TooLarge       bool
29
}
30
}
30
  
31
  
31
type DiffLine struct {
32
type DiffLine struct {
...
46
	OldSize  int64
47
	OldSize  int64
47
	NewSize  int64
48
	NewSize  int64
48
	Deleted  bool
49
	Deleted  bool
  
50
	TooLarge bool
49
}
51
}
50
  
52
  
51
type GroupedRepositories struct {
53
type GroupedRepositories struct {
...
diff --git a/views/commit.html b/views/commit.html
...
83
			</div>
83
			</div>
84
  
84
  
85
			<div class="file-diffs">
85
			<div class="file-diffs">
  
86
				{{if .Commit.TooLarge}}
  
87
				<p class="text-center padding-none margin-none"><b>Commit too large to display</b></p>
  
88
				{{else}}
86
				{{range .FileDiffs}}
89
				{{range .FileDiffs}}
87
				<table class="diff-table">
90
				<table class="diff-table">
88
					<tr class="diff-fname">
91
					<tr class="diff-fname">
...
96
						<col style="width: 30px;">
99
						<col style="width: 30px;">
97
						<col style="width: calc(50% - 30px);">
100
						<col style="width: calc(50% - 30px);">
98
					</colgroup>
101
					</colgroup>
  
102
					{{if .TooLarge}}
  
103
					<tr class="diff-gap">
  
104
						<td colspan="4" class="text-center"><b>Changes too big to display</b></td>
  
105
					</tr>
  
106
					{{else}}
99
					{{range .Lines}}
107
					{{range .Lines}}
100
					{{if eq .Type "gap"}}
108
					{{if eq .Type "gap"}}
101
					<tr class="diff-gap">
109
					<tr class="diff-gap">
...
110
					</tr>
118
					</tr>
111
					{{end}}
119
					{{end}}
112
					{{end}}
120
					{{end}}
  
121
					{{end}}
113
				</table>
122
				</table>
114
				{{else}}
123
				{{else}}
115
				<p>No changes in this commit.</p>
124
				<p>No changes in this commit.</p>
  
125
				{{end}}
116
				{{end}}
126
				{{end}}
117
			</div>
127
			</div>
118
		</main>
128
		</main>
...