8
8
from git .exc import BadName , InvalidGitRepositoryError , NoSuchPathError
9
9
from gitdb .util import hex_to_bin
10
10
11
- from readthedocs .builds .constants import EXTERNAL
11
+ from readthedocs .builds .constants import BRANCH , EXTERNAL , TAG
12
12
from readthedocs .config import ALL
13
13
from readthedocs .projects .constants import (
14
14
GITHUB_BRAND ,
@@ -35,6 +35,7 @@ class Backend(BaseVCS):
35
35
repo_depth = 50
36
36
37
37
def __init__ (self , * args , ** kwargs ):
38
+ self .version_identifier = kwargs .pop ("version_identifier" , None )
38
39
super ().__init__ (* args , ** kwargs )
39
40
self .token = kwargs .get ('token' )
40
41
self .repo_url = self ._get_clone_url ()
@@ -58,15 +59,76 @@ def set_remote_url(self, url):
58
59
def update (self ):
59
60
"""Clone or update the repository."""
60
61
super ().update ()
61
- if self .repo_exists ():
62
- self .set_remote_url (self .repo_url )
63
- return self .fetch ()
64
- self .make_clean_working_dir ()
65
- # A fetch is always required to get external versions properly
62
+
63
+ if self .use_clone_fetch_checkout_pattern ():
64
+
65
+ # New behavior: Clone is responsible for skipping the operation if the
66
+ # repo is already cloned.
67
+ self .clone_ng ()
68
+ # New behavior: No confusing return value. We are not using return values
69
+ # in the callers.
70
+ self .fetch_ng ()
71
+
72
+ else :
73
+ if self .repo_exists ():
74
+ self .set_remote_url (self .repo_url )
75
+ return self .fetch ()
76
+ self .make_clean_working_dir ()
77
+ # A fetch is always required to get external versions properly
78
+ if self .version_type == EXTERNAL :
79
+ self .clone ()
80
+ return self .fetch ()
81
+ return self .clone ()
82
+
83
+ def get_remote_reference (self ):
84
+ # Tags and branches have the tag/branch identifier set by the caller who instantiated the
85
+ # Git backend -- this means that the build process needs to know this from build data,
86
+ # essentially from an incoming webhook call.
87
+ if self .version_type in (BRANCH , TAG ):
88
+ return self .version_identifier
66
89
if self .version_type == EXTERNAL :
67
- self .clone ()
68
- return self .fetch ()
69
- return self .clone ()
90
+ git_provider_name = self .project .git_provider_name
91
+ if git_provider_name == GITHUB_BRAND :
92
+ return GITHUB_PR_PULL_PATTERN .format (id = self .verbose_name )
93
+ if self .project .git_provider_name == GITLAB_BRAND :
94
+ return GITLAB_MR_PULL_PATTERN
95
+
96
+ # This seems to be the default behavior when we don't know the remote
97
+ # reference for a PR/MR: Just fetch everything
98
+ return None
99
+
100
+ def clone_ng (self ):
101
+ # If the repository is already cloned, we don't do anything.
102
+ # TODO: Why is it already cloned?
103
+ if self .repo_exists ():
104
+ return
105
+
106
+ # --no-checkout: Makes it explicit what we are doing here. Nothing is checked out
107
+ # until it's explcitly done.
108
+ # --depth 1: Shallow clone, fetch as little data as possible.
109
+ cmd = ["git" , "clone" , "--no-checkout" , "--depth" , "1" ]
110
+
111
+ code , stdout , stderr = self .run (* cmd )
112
+ return code , stdout , stderr
113
+
114
+ def fetch_ng (self ):
115
+ """Implementation for new clone+fetch+checkout pattern."""
116
+
117
+ # --force: ?
118
+ # --tags: We need to fetch tags in order to resolve these references in the checkout
119
+ # --prune: ?
120
+ # --prune-tags: ?
121
+ cmd = ["git" , "fetch" , "origin" , "--force" , "--tags" , "--prune" , "--prune-tags" ]
122
+ remote_reference = self .get_remote_reference ()
123
+
124
+ if remote_reference :
125
+ # TODO: If we are fetching just one reference, what should the depth be?
126
+ # A PR might have another commit added after the build has started...
127
+ cmd .append ("--depth 1" )
128
+ cmd .append (remote_reference )
129
+
130
+ code , stdout , stderr = self .run (* cmd )
131
+ return code , stdout , stderr
70
132
71
133
def repo_exists (self ):
72
134
try :
@@ -150,6 +212,13 @@ def use_shallow_clone(self):
150
212
from readthedocs .projects .models import Feature
151
213
return not self .project .has_feature (Feature .DONT_SHALLOW_CLONE )
152
214
215
+ def use_clone_fetch_checkout_pattern (self ):
216
+ """Use the new and optimized clone / fetch / checkout pattern."""
217
+ from readthedocs .projects .models import Feature
218
+
219
+ # TODO: Remove the 'not'
220
+ return not self .project .has_feature (Feature .GIT_CLONE_FETCH_CHECKOUT_PATTERN )
221
+
153
222
def fetch (self ):
154
223
# --force lets us checkout branches that are not fast-forwarded
155
224
# https://github.com/readthedocs/readthedocs.org/issues/6097
0 commit comments