77
88import copy
99import os
10- import re
1110
1211from .global_constants import EMPTY_STR , LOCAL_PATH_INDICATOR
1312from .global_constants import VERBOSITY_VERBOSE
@@ -37,15 +36,6 @@ class GitRepository(Repository):
3736
3837 """
3938
40- # match XYZ of '* (HEAD detached at {XYZ}):
41- # e.g. * (HEAD detached at origin/feature-2)
42- RE_DETACHED = re .compile (
43- r'\* \((?:[\w]+[\s]+)?detached (?:at|from) ([\w\-./]+)\)' )
44-
45- # match tracking reference info, return XYZ from [XYZ]
46- # e.g. [origin/master]
47- RE_TRACKING = re .compile (r'\[([\w\-./]+)(?::[\s]+[\w\s,]+)?\]' )
48-
4939 def __init__ (self , component_name , repo ):
5040 """
5141 Parse repo (a <repo> XML element).
@@ -93,81 +83,42 @@ def _clone_repo(self, base_dir_path, repo_dir_name, verbosity):
9383 self ._git_clone (self ._url , repo_dir_name , verbosity )
9484 os .chdir (cwd )
9585
96- def _current_ref_from_branch_command (self , git_output ):
97- """Parse output of the 'git branch -vv' command to determine the current
98- branch. The line starting with '*' is the current branch. It
99- can be one of the following head states:
100-
101- 1. On local branch
102-
103- feature2 36418b4 [origin/feature2] Work on feature2
104- * feature3 36418b4 Work on feature2
105- master 9b75494 [origin/master] Initialize repository.
106-
107- 2. Detached from sha
108-
109- * (HEAD detached at 36418b4) 36418b4 Work on feature2
110- feature2 36418b4 [origin/feature2] Work on feature2
111- master 9b75494 [origin/master] Initialize repository.
112-
113- 3. Detached from remote branch
114-
115- * (HEAD detached at origin/feature2) 36418b4 Work on feature2
116- feature2 36418b4 [origin/feature2] Work on feature2
117- feature3 36418b4 Work on feature2
118- master 9b75494 [origin/master] Initialize repository.
119-
120- 4. Detached from tag
121-
122- * (HEAD detached at clm4_5_18_r272) b837fc36 clm4_5_18_r272
123-
124- 5. On tracking branch. Note, may be may be ahead or behind remote.
125-
126- * master 562bac9a [origin/master] more test junk
127-
128- * master 408a8920 [origin/master: ahead 3] more junk
129-
130- * master 408a8920 [origin/master: ahead 3, behind 2] more junk
131-
132- * master 822d687d [origin/master: behind 3] more junk
133-
134- NOTE: Parsing the output of the porcelain is probably not a
135- great idea, but there doesn't appear to be a single plumbing
136- command that will return the same info.
137-
138- """
139- lines = git_output .splitlines ()
140- ref = ''
141- for line in lines :
142- if line .startswith ('*' ):
143- ref = line
144- break
145- current_ref = EMPTY_STR
146- if not ref :
147- # not a git repo? some other error? we return so the
148- # caller can handle.
149- pass
150- elif 'detached' in ref :
151- match = self .RE_DETACHED .search (ref )
152- try :
153- current_ref = match .group (1 )
154- except BaseException :
155- msg = 'DEV_ERROR: regex to detect detached head state failed!'
156- msg += '\n ref:\n {0}\n git_output\n {1}\n ' .format (ref , git_output )
157- fatal_error (msg )
158- elif '[' in ref :
159- match = self .RE_TRACKING .search (ref )
160- try :
161- current_ref = match .group (1 )
162- except BaseException :
163- msg = 'DEV_ERROR: regex to detect tracking branch failed.'
164- msg += '\n ref:\n {0}\n git_output\n {1}\n ' .format (ref , git_output )
165- fatal_error (msg )
166- else :
167- # assumed local branch
168- current_ref = ref .split ()[1 ]
86+ def _current_ref (self ):
87+ """Determine the *name* associated with HEAD.
88+
89+ If we're on a branch, then returns the branch name; otherwise,
90+ if we're on a tag, then returns the tag name; otherwise, returns
91+ the current hash. Returns an empty string if no reference can be
92+ determined (e.g., if we're not actually in a git repository).
93+ """
94+ ref_found = False
95+
96+ # If we're on a branch, then use that as the current ref
97+ branch_found , branch_name = self ._git_current_branch ()
98+ if branch_found :
99+ current_ref = branch_name
100+ ref_found = True
101+
102+ if not ref_found :
103+ # Otherwise, if we're exactly at a tag, use that as the
104+ # current ref
105+ tag_found , tag_name = self ._git_current_tag ()
106+ if tag_found :
107+ current_ref = tag_name
108+ ref_found = True
109+
110+ if not ref_found :
111+ # Otherwise, use current hash as the current ref
112+ hash_found , hash_name = self ._git_current_hash ()
113+ if hash_found :
114+ current_ref = hash_name
115+ ref_found = True
116+
117+ if not ref_found :
118+ # If we still can't find a ref, return empty string. This
119+ # can happen if we're not actually in a git repo
120+ current_ref = ''
169121
170- current_ref = current_ref .strip ()
171122 return current_ref
172123
173124 def _check_sync (self , stat , repo_dir_path ):
@@ -194,12 +145,11 @@ def _check_sync(self, stat, repo_dir_path):
194145 self ._check_sync_logic (stat , repo_dir_path )
195146
196147 def _check_sync_logic (self , stat , repo_dir_path ):
197- """Isolate the complicated synce logic so it is not so deeply nested
198- and a bit easier to understand.
199-
200- Sync logic - only reporting on whether we are on the ref
201- (branch, tag, hash) specified in the externals description.
148+ """Compare the underlying hashes of the currently checkout ref and the
149+ expected ref.
202150
151+ Output: sets the sync_state as well as the current and
152+ expected ref in the input status object.
203153
204154 """
205155 def compare_refs (current_ref , expected_ref ):
@@ -215,8 +165,8 @@ def compare_refs(current_ref, expected_ref):
215165 cwd = os .getcwd ()
216166 os .chdir (repo_dir_path )
217167
218- git_output = self . _git_branch_vv ()
219- current_ref = self ._current_ref_from_branch_command ( git_output )
168+ # get the full hash of the current commit
169+ _ , current_ref = self ._git_current_hash ( )
220170
221171 if self ._branch :
222172 if self ._url == LOCAL_PATH_INDICATOR :
@@ -230,22 +180,33 @@ def compare_refs(current_ref, expected_ref):
230180 else :
231181 expected_ref = "{0}/{1}" .format (remote_name , self ._branch )
232182 elif self ._hash :
233- # NOTE(bja, 2018-03) For comparison purposes, we could
234- # determine which is longer and check that the short ref
235- # is a substring of the long ref. But it is simpler to
236- # just expand both to the full sha and do an exact
237- # comparison.
238- _ , expected_ref = self ._git_revparse_commit (self ._hash )
239- _ , current_ref = self ._git_revparse_commit (current_ref )
240- else :
183+ expected_ref = self ._hash
184+ elif self ._tag :
241185 expected_ref = self ._tag
186+ else :
187+ msg = 'In repo "{0}": none of branch, hash or tag are set' .format (
188+ self ._name )
189+ fatal_error (msg )
190+
191+ # record the *names* of the current and expected branches
192+ stat .current_version = self ._current_ref ()
193+ stat .expected_version = copy .deepcopy (expected_ref )
242194
243- stat .sync_state = compare_refs (current_ref , expected_ref )
244195 if current_ref == EMPTY_STR :
245196 stat .sync_state = ExternalStatus .UNKNOWN
246-
247- stat .current_version = current_ref
248- stat .expected_version = expected_ref
197+ else :
198+ # get the underlying hash of the expected ref
199+ revparse_status , expected_ref_hash = self ._git_revparse_commit (
200+ expected_ref )
201+ if revparse_status :
202+ # We failed to get the hash associated with
203+ # expected_ref. Maybe we should assign this to some special
204+ # status, but for now we're just calling this out-of-sync to
205+ # remain consistent with how this worked before.
206+ stat .sync_state = ExternalStatus .MODEL_MODIFIED
207+ else :
208+ # compare the underlying hashes
209+ stat .sync_state = compare_refs (current_ref , expected_ref_hash )
249210
250211 os .chdir (cwd )
251212
@@ -595,14 +556,58 @@ def _status_v1z_is_dirty(git_output):
595556 #
596557 # ----------------------------------------------------------------
597558 @staticmethod
598- def _git_branch_vv ():
599- """Run git branch -vv to obtain verbose branch information, including
600- upstream tracking and hash.
559+ def _git_current_hash ():
560+ """Return the full hash of the currently checked-out version.
601561
562+ Returns a tuple, (hash_found, hash), where hash_found is a
563+ logical specifying whether a hash was found for HEAD (False
564+ could mean we're not in a git repository at all). (If hash_found
565+ is False, then hash is ''.)
602566 """
603- cmd = ['git' , 'branch' , '--verbose' , '--verbose' ]
604- git_output = execute_subprocess (cmd , output_to_caller = True )
605- return git_output
567+ status , git_output = GitRepository ._git_revparse_commit ("HEAD" )
568+ hash_found = not status
569+ if not hash_found :
570+ git_output = ''
571+ return hash_found , git_output
572+
573+ @staticmethod
574+ def _git_current_branch ():
575+ """Determines the name of the current branch.
576+
577+ Returns a tuple, (branch_found, branch_name), where branch_found
578+ is a logical specifying whether a branch name was found for
579+ HEAD. (If branch_found is False, then branch_name is ''.)
580+ """
581+ cmd = ['git' , 'symbolic-ref' , '--short' , '-q' , 'HEAD' ]
582+ status , git_output = execute_subprocess (cmd ,
583+ output_to_caller = True ,
584+ status_to_caller = True )
585+ branch_found = not status
586+ if branch_found :
587+ git_output = git_output .strip ()
588+ else :
589+ git_output = ''
590+ return branch_found , git_output
591+
592+ @staticmethod
593+ def _git_current_tag ():
594+ """Determines the name tag corresponding to HEAD (if any).
595+
596+ Returns a tuple, (tag_found, tag_name), where tag_found is a
597+ logical specifying whether we found a tag name corresponding to
598+ HEAD. (If tag_found is False, then tag_name is ''.)
599+ """
600+ # git describe --exact-match --tags HEAD
601+ cmd = ['git' , 'describe' , '--exact-match' , '--tags' , 'HEAD' ]
602+ status , git_output = execute_subprocess (cmd ,
603+ output_to_caller = True ,
604+ status_to_caller = True )
605+ tag_found = not status
606+ if tag_found :
607+ git_output = git_output .strip ()
608+ else :
609+ git_output = ''
610+ return tag_found , git_output
606611
607612 @staticmethod
608613 def _git_showref_tag (ref ):
@@ -647,6 +652,7 @@ def _git_revparse_commit(ref):
647652 '{0}^{1}' .format (ref , '{commit}' ), ]
648653 status , git_output = execute_subprocess (cmd , status_to_caller = True ,
649654 output_to_caller = True )
655+ git_output = git_output .strip ()
650656 return status , git_output
651657
652658 @staticmethod
0 commit comments