99import pprint
1010import subprocess
1111import sys
12-
13- # Used indirectly in the below Jinja2 block
14- from collections import OrderedDict # pylint: disable=unused-import
12+ from collections import OrderedDict
1513from logging import basicConfig , getLogger
1614from pathlib import Path
1715
18- import git
1916import yaml
20- from cookiecutter .repository import expand_abbreviations
2117
2218LOG_FORMAT = json .dumps (
2319 {
3531
3632def get_context () -> dict :
3733 """Return the context as a dict"""
34+ # Import git-related modules here so they're only loaded when needed
35+ import git
36+ from cookiecutter .repository import expand_abbreviations
37+
3838 cookiecutter = None
3939 timestamp = datetime .datetime .now (datetime .UTC ).isoformat (timespec = "seconds" )
4040
4141 ##############
4242 # This section leverages cookiecutter's jinja interpolation
43- # pylint: disable-next=unhashable-member
4443 cookiecutter_context_ordered : OrderedDict [str , str ] = {{cookiecutter | pprint }} # type: ignore
4544 cookiecutter_context : dict [str , str ] = dict (cookiecutter_context_ordered )
46-
47- project_name = cookiecutter_context ["project_slug" ] # pylint: disable=unsubscriptable-object
48- project_description = cookiecutter_context ["project_short_description" ] # pylint: disable=unsubscriptable-object
49- template = cookiecutter_context ["_template" ] # pylint: disable=unsubscriptable-object
50- output = cookiecutter_context ["_output_dir" ] # pylint: disable=unsubscriptable-object
5145 ##############
5246
53- try :
54- if Path (template ).is_absolute ():
55- template_path : Path = Path (template ).resolve ()
56- else :
57- output_path : Path = Path (output ).resolve ()
58- template_path : Path = output_path .joinpath (template )
59-
60- # IMPORTANT: If the specified template is remote (http/git/ssh) this SHOULD raise an exception. The remote logic is in the except block
61- repo : git .Repo = git .Repo (template_path )
47+ project_name = cookiecutter_context ["project_slug" ]
48+ project_description = cookiecutter_context ["project_short_description" ]
49+ template = cookiecutter_context ["_template" ]
50+ output = cookiecutter_context ["_output_dir" ]
51+ # Get the branch specified via --checkout, but fall back to main
52+ branch = cookiecutter_context .get ("_checkout" ) or "main"
6253
63- # Expect this is a local template
64- branch : str = str (repo .active_branch )
65- dirty : bool = repo .is_dirty (untracked_files = True )
66- template_commit_hash = git .cmd .Git ().ls_remote (template_path , "HEAD" )[:40 ]
67- except (git .exc .InvalidGitRepositoryError , git .exc .NoSuchPathError ):
68- # This exception handling occurs every time the template repo is remote
54+ # Check if template is a remote URL or abbreviation
55+ is_remote_template = any (
56+ template .startswith (prefix ) for prefix in ["http://" , "https://" , "git@" , "gh:" , "gl:" , "bb:" ]
57+ )
6958
59+ if is_remote_template :
7060 # From https://github.com/cookiecutter/cookiecutter/blob/b4451231809fb9e4fc2a1e95d433cb030e4b9e06/cookiecutter/config.py#L22
7161 abbreviations : dict [str , str ] = {
7262 "gh" : "https://github.com/{0}.git" ,
@@ -75,11 +65,36 @@ def get_context() -> dict:
7565 }
7666 template_repo : str = expand_abbreviations (template , abbreviations )
7767
78- # This currently assumes main until https://github.com/cookiecutter/cookiecutter/issues/1759 is resolved
79- branch : str = "main"
8068 dirty : bool = False
8169
70+ # For remote templates, get the commit hash from the remote
8271 template_commit_hash = git .cmd .Git ().ls_remote (template_repo , branch )[:40 ]
72+ # Store the expanded URL
73+ template_location = template_repo
74+ else :
75+ # This is a local template path
76+ if Path (template ).is_absolute ():
77+ template_path : Path = Path (template ).resolve ()
78+ else :
79+ output_path : Path = Path (output ).resolve ()
80+ template_path : Path = output_path .joinpath (template ).resolve ()
81+
82+ try :
83+ repo : git .Repo = git .Repo (template_path )
84+
85+ # Get info from the local repository
86+ branch : str = str (repo .active_branch )
87+ dirty : bool = repo .is_dirty (untracked_files = True )
88+ # Get the actual commit hash from the local repository
89+ template_commit_hash = repo .head .commit .hexsha
90+ # Store the fully qualified template path for local templates
91+ template_location = str (template_path )
92+ except (git .exc .InvalidGitRepositoryError , git .exc .NoSuchPathError ):
93+ # Not a git repository, fall back to empty values
94+ branch = "unknown"
95+ dirty = False
96+ template_commit_hash = ""
97+ template_location = str (template_path )
8398
8499 context : dict [str , str | dict [str , str | bool | dict [str , str | bool | dict [str , str ]]]] = {}
85100 context ["name" ] = project_name
@@ -91,12 +106,21 @@ def get_context() -> dict:
91106 context ["origin" ]["template" ]["branch" ] = branch
92107 context ["origin" ]["template" ]["commit hash" ] = template_commit_hash
93108 context ["origin" ]["template" ]["dirty" ] = dirty
94- context ["origin" ]["template" ]["location" ] = template
109+ context ["origin" ]["template" ]["location" ] = template_location
95110 context ["origin" ]["template" ]["cookiecutter" ] = {}
96111 context ["origin" ]["template" ]["cookiecutter" ] = cookiecutter_context
97112
98113 # Filter out unwanted cookiecutter context
99- del cookiecutter_context ["_output_dir" ] # pylint: disable=unsubscriptable-object
114+ del cookiecutter_context ["_output_dir" ]
115+
116+ # Replace relative paths with fully qualified paths in cookiecutter context
117+ if "_template" in cookiecutter_context :
118+ cookiecutter_context ["_template" ] = template_location
119+
120+ if "_repo_dir" in cookiecutter_context :
121+ # For local templates, _repo_dir should be the same as the resolved template path
122+ # For remote templates, it would be the cloned directory, but we'll use the template location
123+ cookiecutter_context ["_repo_dir" ] = template_location
100124
101125 return context
102126
0 commit comments