@@ -2,16 +2,20 @@ use std::env;
22
33use git2:: Repository ;
44
5+ use crate :: cli:: Platform ;
56use crate :: context:: Context ;
67use crate :: error:: ShipItError ;
7- use crate :: common:: { open_github_pr, open_gitlab_mr, summarize_with_ollama} ;
8+ use crate :: common:: { lookup_github_identifier , lookup_gitlab_project_id , open_github_pr, open_gitlab_mr, summarize_with_ollama} ;
89
910pub async fn branch_to_branch (
1011 ctx : & Context ,
1112 args_source : String ,
1213 args_target : String ,
1314 args_dir : Option < String > ,
1415 args_id : Option < String > ,
16+ args_platform : Option < Platform > ,
17+ args_remote : String ,
18+ args_prompt : Option < String > ,
1519) -> Result < ( ) , ShipItError > {
1620 let dir = match args_dir {
1721 Some ( path) => std:: path:: PathBuf :: from ( path) ,
@@ -80,52 +84,112 @@ pub async fn branch_to_branch(
8084
8185 // ask a local llm to summarize these commit messages
8286 let mut summary = if ctx. settings . shipit . ai {
87+ let mut ollama = ctx. settings . ollama . clone ( ) ;
88+ if let Some ( prompt) = args_prompt {
89+ ollama. prompt = prompt;
90+ }
8391 let result = summarize_with_ollama (
84- & description, & ctx . settings . ollama
92+ & description, & ollama
8593 ) . await . or_else ( |_e| Err ( ShipItError :: Error ( "Failed to summarize with Ollama!" . to_string ( ) ) ) ) ?;
8694 println ! ( "The merge request description is:\n \n {}" , result) ;
8795 result
8896 } else {
8997 description
9098 } ;
91- summary += "\n \n \n *This request was opened by Shipit* 🚢" ;
99+ summary += "\n \n \n *This request was generated by [ Shipit](https://gitshipit.net) * 🚢" ;
92100
93101 if ctx. settings . shipit . dryrun {
94102 println ! ( "\n \n Dry run complete! Re-run without the dry-run flag to open a request." ) ;
95103 return Ok ( ( ) ) ;
96104 }
97105
98- // handle opening a github pr or gitlab mr
99- // defaults to github if both are configured
100- let id = args_id. as_deref ( )
101- . ok_or_else ( || ShipItError :: Error ( "A project identifier is required via '--id'." . to_string ( ) ) ) ?;
106+ // always fetch the remote URL — needed both for platform detection and ID auto-lookup
107+ let remote_url = {
108+ let remote = repo. find_remote ( & args_remote) . map_err ( |e| ShipItError :: Git ( e) ) ?;
109+ remote. url ( )
110+ . ok_or_else ( || ShipItError :: Error ( format ! ( "The '{}' remote has no URL." , args_remote) ) ) ?
111+ . to_string ( )
112+ } ;
113+
114+ // determine the platform to use:
115+ // use --platform flag if provided, otherwise detect from origin remote url
116+ let ( is_github, is_gitlab) = match args_platform {
117+ Some ( Platform :: Github ) => ( true , false ) ,
118+ Some ( Platform :: Gitlab ) => ( false , true ) ,
119+ None => ( remote_url. contains ( "github" ) , remote_url. contains ( "gitlab" ) ) ,
120+ } ;
102121
103- let use_github = ctx. settings . github . token . is_some ( ) ;
104- let use_gitlab = ctx. settings . gitlab . token . is_some ( ) && !use_github;
122+ // resolve the project identifier:
123+ // use --id if provided, otherwise look it up from the remote url via the platform api
124+ let resolved_id: String = match args_id {
125+ Some ( id) => id,
126+ None => {
127+ if is_github {
128+ lookup_github_identifier ( & remote_url)
129+ . map_err ( |e| ShipItError :: Error ( format ! ( "Failed to detect GitHub owner/repo from remote URL: {}" , e) ) ) ?
130+ } else if is_gitlab {
131+ let token = ctx. settings . gitlab . token . as_deref ( )
132+ . ok_or_else ( || ShipItError :: Error ( "GitLab token is required to look up the project ID." . to_string ( ) ) ) ?;
133+ let id = lookup_gitlab_project_id ( & remote_url, & ctx. settings . gitlab . domain , token) . await
134+ . map_err ( |e| ShipItError :: Error ( format ! ( "Failed to look up GitLab project ID from remote URL: {}" , e) ) ) ?;
135+ println ! ( "Auto-detected GitLab project ID: {}" , id) ;
136+ id. to_string ( )
137+ } else {
138+ return Err ( ShipItError :: Error ( "Could not determine platform. Use '--platform github' or '--platform gitlab' to specify it explicitly." . to_string ( ) ) ) ;
139+ }
140+ }
141+ } ;
142+
143+ // check if the local source branch is ahead of its remote tracking branch
144+ let needs_push = {
145+ let local_oid = source. get ( ) . target ( )
146+ . ok_or_else ( || ShipItError :: Git ( git2:: Error :: from_str ( "Failed to get source branch OID" ) ) ) ?;
147+ let remote_tracking_ref = format ! ( "refs/remotes/{}/{}" , args_remote, args_source) ;
148+ match repo. find_reference ( & remote_tracking_ref) {
149+ Ok ( remote_ref) => match remote_ref. target ( ) {
150+ Some ( remote_oid) => {
151+ let ( ahead, _) = repo. graph_ahead_behind ( local_oid, remote_oid)
152+ . map_err ( |e| ShipItError :: Git ( e) ) ?;
153+ ahead > 0
154+ }
155+ None => true ,
156+ } ,
157+ Err ( _) => true , // no remote tracking branch yet
158+ }
159+ } ;
160+
161+ if needs_push {
162+ println ! (
163+ "\n \n Your local source branch is ahead of the remote. Please push it, then press Enter to continue:\n \n git push {} {}\n " ,
164+ args_remote, args_source
165+ ) ;
166+ let mut input = String :: new ( ) ;
167+ std:: io:: stdin ( ) . read_line ( & mut input) . map_err ( |e| ShipItError :: Error ( format ! ( "Failed to read input: {}" , e) ) ) ?;
168+ }
105169
106- if use_github {
107- let parts: Vec < & str > = id . splitn ( 2 , '/' ) . collect ( ) ;
170+ if is_github {
171+ let parts: Vec < & str > = resolved_id . splitn ( 2 , '/' ) . collect ( ) ;
108172 if parts. len ( ) != 2 {
109- return Err ( ShipItError :: Error ( "'--id ' must be in 'owner/repo' format for GitHub." . to_string ( ) ) ) ;
173+ return Err ( ShipItError :: Error ( format ! ( "GitHub project identifier '{} ' must be in 'owner/repo' format." , resolved_id ) ) ) ;
110174 }
111- let ( owner, repo) = ( parts[ 0 ] , parts[ 1 ] ) ;
112175 let token = ctx. settings . github . token . as_deref ( ) . unwrap ( ) ;
176+ let ( owner, gh_repo) = ( parts[ 0 ] , parts[ 1 ] ) ;
113177 let pr_url = open_github_pr (
114178 & args_source, & args_target, & ctx. settings . github . domain ,
115- token, owner, repo , & summary,
179+ token, owner, gh_repo , & summary,
116180 ) . await . map_err ( |e| ShipItError :: Error ( format ! ( "Failed to open a GitHub PR: {}" , e) ) ) ?;
117181 println ! ( "\n \n The pull request is available at:\n \n {}" , pr_url) ;
118- } else if use_gitlab {
119- let project_id: u64 = id . parse ( )
120- . map_err ( |_| ShipItError :: Error ( "'--id ' must be a numeric project ID for GitLab." . to_string ( ) ) ) ?;
182+ } else if is_gitlab {
183+ let project_id: u64 = resolved_id . parse ( )
184+ . map_err ( |_| ShipItError :: Error ( format ! ( "GitLab project identifier '{} ' must be a numeric project ID." , resolved_id ) ) ) ?;
121185 let token = ctx. settings . gitlab . token . as_deref ( ) . unwrap ( ) ;
122186 let mr_url = open_gitlab_mr (
123187 & args_source, & args_target, & ctx. settings . gitlab . domain ,
124188 token, & project_id, & summary,
125189 ) . await . map_err ( |e| ShipItError :: Error ( format ! ( "Failed to open a GitLab MR: {}" , e) ) ) ?;
126190 println ! ( "\n \n The merge request is available at:\n \n {}" , mr_url[ "web_url" ] ) ;
127191 } else {
128- return Err ( ShipItError :: Error ( "No platform token configured. Set github.token or gitlab.token in your shipit config ." . to_string ( ) ) ) ;
192+ return Err ( ShipItError :: Error ( "Could not determine platform. Use '--platform github' or '--platform gitlab' to specify it explicitly ." . to_string ( ) ) ) ;
129193 }
130194
131195 Ok ( ( ) )
0 commit comments