@@ -20,6 +20,7 @@ use imara_diff::{
2020use pulldown_cmark_escape:: FmtWriter ;
2121use regex:: Regex ;
2222
23+ use crate :: github:: GithubCompare ;
2324use crate :: { github, handlers:: Context , utils:: AppError } ;
2425
2526static MARKER_RE : LazyLock < Regex > =
@@ -31,7 +32,7 @@ static MARKER_RE: LazyLock<Regex> =
3132pub async fn gh_range_diff (
3233 Path ( ( owner, repo, basehead) ) : Path < ( String , String , String ) > ,
3334 State ( ctx) : State < Arc < Context > > ,
34- Host ( host) : Host ,
35+ host : Host ,
3536) -> axum:: response:: Result < impl IntoResponse , AppError > {
3637 let Some ( ( oldhead, newhead) ) = basehead. split_once ( ".." ) else {
3738 return Ok ( (
@@ -41,9 +42,6 @@ pub async fn gh_range_diff(
4142 ) ) ;
4243 } ;
4344
44- // Configure unified diff
45- let config = UnifiedDiffConfig :: default ( ) ;
46-
4745 let repos = ctx
4846 . team
4947 . repos ( )
@@ -96,18 +94,14 @@ pub async fn gh_range_diff(
9694 . sha ;
9795
9896 // Get the comparison between the oldbase..oldhead
99- let mut old = ctx
97+ let old = ctx
10098 . github
10199 . compare ( & issue_repo, & oldbase, oldhead)
102100 . await
103101 . with_context ( || {
104102 format ! ( "failed to retrive the comparison between {oldbase} and {oldhead}" )
105103 } ) ?;
106104
107- // Sort by filename, so it's consistent with GitHub UI
108- old. files
109- . sort_unstable_by ( |f1, f2| f1. filename . cmp ( & f2. filename ) ) ;
110-
111105 anyhow:: Result :: < _ > :: Ok ( ( oldbase, old) )
112106 } ;
113107
@@ -125,24 +119,128 @@ pub async fn gh_range_diff(
125119 . sha ;
126120
127121 // Get the comparison between the newbase..newhead
128- let mut new = ctx
122+ let new = ctx
129123 . github
130124 . compare ( & issue_repo, & newbase, newhead)
131125 . await
132126 . with_context ( || {
133127 format ! ( "failed to retrive the comparison between {newbase} and {newhead}" )
134128 } ) ?;
135129
136- // Sort by filename, so it's consistent with GitHub UI
137- new. files
138- . sort_unstable_by ( |f1, f2| f1. filename . cmp ( & f2. filename ) ) ;
139-
140130 anyhow:: Result :: < _ > :: Ok ( ( newbase, new) )
141131 } ;
142132
143133 // Wait for both futures and early exit if there is an error
144134 let ( ( oldbase, old) , ( newbase, new) ) = futures:: try_join!( old, new) ?;
145135
136+ process_old_new (
137+ host,
138+ ( & owner, & repo) ,
139+ ( & oldbase, oldhead, old) ,
140+ ( & newbase, newhead, new) ,
141+ )
142+ }
143+
144+ /// Compute and renders an emulated `git range-diff` between two pushes (old and new).
145+ ///
146+ /// - `oldbasehead` is `OLDBASE..OLDHEAD`
147+ /// - `newbasehead` is `NEWBASE..NEWHEAD`
148+ pub async fn gh_ranges_diff (
149+ Path ( ( owner, repo, oldbasehead, newbasehead) ) : Path < ( String , String , String , String ) > ,
150+ State ( ctx) : State < Arc < Context > > ,
151+ host : Host ,
152+ ) -> axum:: response:: Result < impl IntoResponse , AppError > {
153+ let Some ( ( oldbase, oldhead) ) = oldbasehead. split_once ( ".." ) else {
154+ return Ok ( (
155+ StatusCode :: BAD_REQUEST ,
156+ HeaderMap :: new ( ) ,
157+ format ! ( "`{oldbasehead}` is not in the form `base..head`" ) ,
158+ ) ) ;
159+ } ;
160+
161+ let Some ( ( newbase, newhead) ) = newbasehead. split_once ( ".." ) else {
162+ return Ok ( (
163+ StatusCode :: BAD_REQUEST ,
164+ HeaderMap :: new ( ) ,
165+ format ! ( "`{newbasehead}` is not in the form `base..head`" ) ,
166+ ) ) ;
167+ } ;
168+
169+ let repos = ctx
170+ . team
171+ . repos ( )
172+ . await
173+ . context ( "unable to retrieve team repos" ) ?;
174+
175+ // Verify that the request org is part of the Rust project
176+ let Some ( repos) = repos. repos . get ( & owner) else {
177+ return Ok ( (
178+ StatusCode :: BAD_REQUEST ,
179+ HeaderMap :: new ( ) ,
180+ format ! ( "organization `{owner}` is not part of the Rust Project team repos" ) ,
181+ ) ) ;
182+ } ;
183+
184+ // Verify that the request repo is part of the Rust project
185+ if !repos. iter ( ) . any ( |r| r. name == repo) {
186+ return Ok ( (
187+ StatusCode :: BAD_REQUEST ,
188+ HeaderMap :: new ( ) ,
189+ format ! ( "repository `{owner}` is not part of the Rust Project team repos" ) ,
190+ ) ) ;
191+ }
192+
193+ let issue_repo = github:: IssueRepository {
194+ organization : owner. to_string ( ) ,
195+ repository : repo. to_string ( ) ,
196+ } ;
197+
198+ // Get the comparison between the oldbase..oldhead
199+ let old = async {
200+ ctx. github
201+ . compare ( & issue_repo, & oldbase, oldhead)
202+ . await
203+ . with_context ( || {
204+ format ! ( "failed to retrive the comparison between {oldbase} and {oldhead}" )
205+ } )
206+ } ;
207+
208+ // Get the comparison between the newbase..newhead
209+ let new = async {
210+ ctx. github
211+ . compare ( & issue_repo, & newbase, newhead)
212+ . await
213+ . with_context ( || {
214+ format ! ( "failed to retrive the comparison between {newbase} and {newhead}" )
215+ } )
216+ } ;
217+
218+ // Wait for both futures and early exit if there is an error
219+ let ( old, new) = futures:: try_join!( old, new) ?;
220+
221+ process_old_new (
222+ host,
223+ ( & owner, & repo) ,
224+ ( & oldbase, oldhead, old) ,
225+ ( & newbase, newhead, new) ,
226+ )
227+ }
228+
229+ fn process_old_new (
230+ Host ( host) : Host ,
231+ ( owner, repo) : ( & str , & str ) ,
232+ ( oldbase, oldhead, mut old) : ( & str , & str , GithubCompare ) ,
233+ ( newbase, newhead, mut new) : ( & str , & str , GithubCompare ) ,
234+ ) -> axum:: response:: Result < ( StatusCode , HeaderMap , String ) , AppError > {
235+ // Configure unified diff
236+ let config = UnifiedDiffConfig :: default ( ) ;
237+
238+ // Sort by filename, so it's consistent with GitHub UI
239+ old. files
240+ . sort_unstable_by ( |f1, f2| f1. filename . cmp ( & f2. filename ) ) ;
241+ new. files
242+ . sort_unstable_by ( |f1, f2| f1. filename . cmp ( & f2. filename ) ) ;
243+
146244 // Create the HTML buffer with a very rough approximation for the capacity
147245 let mut html: String = String :: with_capacity ( 800 + old. files . len ( ) * 100 ) ;
148246
0 commit comments