11#!/usr/bin/env python3
22
3+ import subprocess
34import sys
45
5- from common import printError , printWarning , printInfo , execCommand
6+ from common import printWarning , printInfo , execCommand
67
78# Parameters
89CLING_TAG_ROOT_HASH_PREFIX = "__internal-root-"
@@ -50,11 +51,30 @@ def getStartingRootHash(rootTag):
5051 prefixLen = len (CLING_TAG_ROOT_HASH_PREFIX )
5152 defHashLen = len (DEFAULT_STARTING_ROOT_HASH )
5253 hash = rootTag [prefixLen : prefixLen + defHashLen ]
54+ # A tag made from a local ROOT branch names a commit that is not in ROOT's history.
55+ # Then start from the ROOT commit that cling's last commit came from.
56+ if subprocess .run (["git" , "merge-base" , "--is-ancestor" , hash , "HEAD" ], cwd = ROOT_REPO_DIR_NAME ).returncode != 0 :
57+ printWarning (f"The sync tag names { hash } , which is not in ROOT's history" )
58+ hash = findRootCommit (execCommand ("git log -1 --format=%ae%x09%ad --date=raw HEAD" , CLING_REPO_DIR_NAME ))
5359 return hash
5460
5561
62+ def findRootCommit (identity ):
63+ """
64+ Find the ROOT commit with the given author identity. git am keeps the author
65+ and the author date, so they identify a ROOT commit in cling.
66+ """
67+ out = execCommand (f"git log --format=%h%x09%ae%x09%ad --date=raw HEAD -- { INTERP_DIR_NAME } " , ROOT_REPO_DIR_NAME )
68+ for line in out .split ("\n " ):
69+ hash , commitIdentity = line .split ("\t " , 1 )
70+ if commitIdentity == identity :
71+ printInfo (f"Cling's last commit comes from ROOT's { hash } " )
72+ return hash
73+ raise ValueError (f"No ROOT commit matches cling's last commit ({ identity } )" )
74+
75+
5676def getHashes (repoDirName , startingHash , dirInRepoName = "" ):
57- out = execCommand (f"git log --oneline { startingHash } ..HEAD { dirInRepoName } " , thisCwd = repoDirName )
77+ out = execCommand (f"git log --oneline { startingHash } ..HEAD -- { dirInRepoName } " , thisCwd = repoDirName )
5878 hashes = []
5979 if not out :
6080 return hashes
@@ -75,12 +95,19 @@ def createPatches(rootHashes, interpHashes):
7595 # We loop on ROOT hashes to sync, from oldest to newest
7696 # to return a list of triples [dirInRepo, hash, patchAsSting], where dirInRepo is the
7797 # directory and hash is there for debugging purposes.
98+ # Commits that cling already has, e.g. because the sync tag names an older ROOT
99+ # commit than the one cling was synchronized to, are skipped.
100+ syncedIdentities = set (execCommand ("git log -n 1000 --format=%ae%x09%ad --date=raw HEAD" , CLING_REPO_DIR_NAME ).split ("\n " ))
78101 for rootHashtoSync in reversed (rootHashesToSync ):
102+ identity = execCommand (f"git log -1 --format=%ae%x09%ad --date=raw { rootHashtoSync } " , ROOT_REPO_DIR_NAME )
103+ if identity in syncedIdentities :
104+ printWarning (f"Skipping { rootHashtoSync } : cling already has it" )
105+ continue
79106 keys = []
80107 if rootHashtoSync in interpHashesSet :
81108 keys .append (INTERP_DIR_NAME )
82109 for key in keys :
83- patchAsStr = execCommand (f"git format-patch -1 { rootHashtoSync } { key } --stdout" , ROOT_REPO_DIR_NAME )
110+ patchAsStr = execCommand (f"git format-patch -1 { rootHashtoSync } --stdout -- { key } " , ROOT_REPO_DIR_NAME )
84111 patches .append ([key , rootHashtoSync , patchAsStr ])
85112 return patches
86113
@@ -99,27 +126,20 @@ def syncTagAndPush(oldSyncTag, rootSyncHash):
99126 Replace the tag mentioning the previous root commit to which the repo was synchronized.
100127 Push the changes and tags upstream.
101128 """
102- # We fetch the remote and local tags to make the following operations more resilient
103- remoteTags = execCommand ("git ls-remote --tags origin" , CLING_REPO_DIR_NAME )
104- localTags = execCommand ("git tag" , CLING_REPO_DIR_NAME )
105-
106- # Clean the old tag
107- printInfo (f"Found a sync tag ({ oldSyncTag } ): deleting it." )
108- if oldSyncTag in localTags :
109- execCommand (f"git tag -d { oldSyncTag } " , CLING_REPO_DIR_NAME )
110- if oldSyncTag in remoteTags :
111- execCommand (f"git push --delete origin { oldSyncTag } " , CLING_REPO_DIR_NAME )
112- else :
113- printWarning (f"Tag { oldSyncTag } was not found in the upstream repository." )
114-
115- # Time to push the sync commits!
116- execCommand ("git push" , CLING_REPO_DIR_NAME )
117-
118- # And finally we tag and push the tag
129+ # Push the commits and the new tag together, so that cling never has the
130+ # commits without a tag that names them. Only then remove the old tag.
119131 newTag = CLING_TAG_ROOT_HASH_PREFIX + rootSyncHash
120132 printInfo (f"Creating a new tag ({ newTag } )." )
121133 execCommand (f"git tag { newTag } " , CLING_REPO_DIR_NAME )
122- execCommand (f"git push origin tag { newTag } " , CLING_REPO_DIR_NAME )
134+ execCommand (f"git push --atomic origin HEAD tag { newTag } " , CLING_REPO_DIR_NAME )
135+
136+ printInfo (f"Deleting the previous sync tag ({ oldSyncTag } )." )
137+ execCommand (f"git tag -d { oldSyncTag } " , CLING_REPO_DIR_NAME )
138+ remoteTags = execCommand ("git ls-remote --tags origin" , CLING_REPO_DIR_NAME )
139+ if f"refs/tags/{ oldSyncTag } " in remoteTags :
140+ execCommand (f"git push --delete origin tag { oldSyncTag } " , CLING_REPO_DIR_NAME )
141+ else :
142+ printWarning (f"Tag { oldSyncTag } was not found in the upstream repository." )
123143
124144
125145def principal ():
@@ -146,8 +166,7 @@ def principal():
146166
147167 # We now apply the patches
148168 if not patches :
149- printError ("No patch was distilled: this status should not be reachable" )
150- return 1
169+ printInfo ("Cling already has all these commits: only moving the sync tag." )
151170
152171 # We now need to apply patches, update the tag that mentions the ROOT commit
153172 # to which the cling repo was synchronised and push everything.
0 commit comments