@@ -592,6 +592,73 @@ def content_deploy(
592592 response = self ._server .handle_bad_response (response )
593593 return response
594594
595+ def deploy_git (
596+ self ,
597+ app_id : Optional [str ],
598+ name : str ,
599+ repository : str ,
600+ branch : str ,
601+ subdirectory : str ,
602+ title : Optional [str ],
603+ env_vars : Optional [dict [str , str ]],
604+ ) -> RSConnectClientDeployResult :
605+ """Deploy content from a git repository.
606+
607+ Creates a git-backed content item in Posit Connect. Connect will clone
608+ the repository and automatically redeploy when commits are pushed.
609+
610+ :param app_id: Existing content ID/GUID to update, or None to create new content
611+ :param name: Name for the content item (used if creating new)
612+ :param repository: URL of the git repository (https:// only)
613+ :param branch: Branch to deploy from
614+ :param subdirectory: Subdirectory containing manifest.json
615+ :param title: Title for the content
616+ :param env_vars: Environment variables to set
617+ :return: Deployment result with task_id, app info, etc.
618+ """
619+ # Create or get existing content
620+ if app_id is None :
621+ app = self .content_create (name )
622+ else :
623+ try :
624+ app = self .get_content_by_id (app_id )
625+ except RSConnectException as e :
626+ raise RSConnectException (
627+ f"{ e } Try setting the --new flag or omit --app-id to create new content."
628+ ) from e
629+
630+ app_guid = app ["guid" ]
631+
632+ # Set repository info via POST to applications/{guid}/repo
633+ # This is a Connect-specific endpoint for git-backed content
634+ resp = self .post (
635+ "applications/%s/repo" % app_guid ,
636+ body = {"repository" : repository , "branch" : branch , "subdirectory" : subdirectory },
637+ )
638+ self ._server .handle_bad_response (resp )
639+
640+ # Update title if provided (and different from current)
641+ if title and app .get ("title" ) != title :
642+ self .patch ("v1/content/%s" % app_guid , body = {"title" : title })
643+
644+ # Set environment variables
645+ if env_vars :
646+ result = self .add_environment_vars (app_guid , list (env_vars .items ()))
647+ self ._server .handle_bad_response (result )
648+
649+ # Trigger deployment (bundle_id=None uses the latest bundle from git clone)
650+ task = self .content_deploy (app_guid , bundle_id = None )
651+
652+ return RSConnectClientDeployResult (
653+ app_id = str (app ["id" ]),
654+ app_guid = app_guid ,
655+ app_url = app ["content_url" ],
656+ task_id = task ["task_id" ],
657+ title = title or app .get ("title" ),
658+ dashboard_url = app ["dashboard_url" ],
659+ draft_url = None ,
660+ )
661+
595662 def system_caches_runtime_list (self ) -> list [ListEntryOutputDTO ]:
596663 response = cast (Union [List [ListEntryOutputDTO ], HTTPResponse ], self .get ("v1/system/caches/runtime" ))
597664 response = self ._server .handle_bad_response (response )
@@ -784,6 +851,9 @@ def __init__(
784851 disable_env_management : Optional [bool ] = None ,
785852 env_vars : Optional [dict [str , str ]] = None ,
786853 metadata : Optional [dict [str , str ]] = None ,
854+ repository : Optional [str ] = None ,
855+ branch : Optional [str ] = None ,
856+ subdirectory : Optional [str ] = None ,
787857 ) -> None :
788858 self .remote_server : TargetableServer
789859 self .client : RSConnectClient | PositClient
@@ -805,6 +875,11 @@ def __init__(
805875 self .title_is_default : bool = not title
806876 self .deployment_name : str | None = None
807877
878+ # Git deployment parameters
879+ self .repository : str | None = repository
880+ self .branch : str | None = branch
881+ self .subdirectory : str | None = subdirectory
882+
808883 self .bundle : IO [bytes ] | None = None
809884 self .deployed_info : RSConnectClientDeployResult | None = None
810885
@@ -847,6 +922,9 @@ def fromConnectServer(
847922 disable_env_management : Optional [bool ] = None ,
848923 env_vars : Optional [dict [str , str ]] = None ,
849924 metadata : Optional [dict [str , str ]] = None ,
925+ repository : Optional [str ] = None ,
926+ branch : Optional [str ] = None ,
927+ subdirectory : Optional [str ] = None ,
850928 ):
851929 return cls (
852930 ctx = ctx ,
@@ -870,6 +948,9 @@ def fromConnectServer(
870948 disable_env_management = disable_env_management ,
871949 env_vars = env_vars ,
872950 metadata = metadata ,
951+ repository = repository ,
952+ branch = branch ,
953+ subdirectory = subdirectory ,
873954 )
874955
875956 def output_overlap_header (self , previous : bool ) -> bool :
@@ -1169,6 +1250,48 @@ def deploy_bundle(self, activate: bool = True):
11691250 )
11701251 return self
11711252
1253+ @cls_logged ("Creating git-backed deployment ..." )
1254+ def deploy_git (self ):
1255+ """Deploy content from a remote git repository.
1256+
1257+ Creates a git-backed content item in Posit Connect. Connect will clone
1258+ the repository and automatically redeploy when commits are pushed.
1259+ """
1260+ if not isinstance (self .client , RSConnectClient ):
1261+ raise RSConnectException (
1262+ "Git deployment is only supported for Posit Connect servers, " "not shinyapps.io or Posit Cloud."
1263+ )
1264+
1265+ if not self .repository :
1266+ raise RSConnectException ("Repository URL is required for git deployment." )
1267+
1268+ # Generate a valid deployment name from the title
1269+ # This sanitizes characters like "/" that aren't allowed in names
1270+ force_unique_name = self .app_id is None
1271+ deployment_name = self .make_deployment_name (self .title , force_unique_name )
1272+
1273+ try :
1274+ result = self .client .deploy_git (
1275+ app_id = self .app_id ,
1276+ name = deployment_name ,
1277+ repository = self .repository ,
1278+ branch = self .branch or "main" ,
1279+ subdirectory = self .subdirectory or "" ,
1280+ title = self .title ,
1281+ env_vars = self .env_vars ,
1282+ )
1283+ except RSConnectException as e :
1284+ # Check for 404 on /repo endpoint (git not enabled)
1285+ if "404" in str (e ) and "repo" in str (e ).lower ():
1286+ raise RSConnectException (
1287+ "Git-backed deployment is not enabled on this Connect server. "
1288+ "Contact your administrator to enable Git support."
1289+ ) from e
1290+ raise
1291+
1292+ self .deployed_info = result
1293+ return self
1294+
11721295 def emit_task_log (
11731296 self ,
11741297 log_callback : logging .Logger = connect_logger ,
0 commit comments