10 Merge individual Athena packages between git branches. Commit the resulting diff as a cherry-pick.
13 __author__ =
"Tim Martin"
14 __version__ =
"$Revision: 1.0 $"
15 __doc__ =
"Merge individual Athena packages between git branches. Commit the resulting diff as a cherry-pick."
19 parser = argparse.ArgumentParser(description=
'\
20 Perform a pseudo-merge (a selective merge followed by cherry-pick) of select packages between branches in Athena. \
21 This performs a proper merge, using the full history in both the source and target branches but restricting to select packages (paths). \
22 The result of this operation, after conflicts are resolved, is isolated as a cherry-pick which is used to apply the changes to the official code base.')
23 parser.add_argument(
'--packages', type=str, nargs=
'+', required=
True,
24 help=
'List of package paths to migrate to the target branch, separated by spaces "Path/To/Package1 Path/To/Package2" etc.')
25 parser.add_argument(
'--stage', type=int,
26 help=
'Set initially to 1 to setup the merge and present conflicts, set to 2 to create a branch which will apply the changes of the pseudo-merge')
27 parser.add_argument(
'--source', type=str,
28 default=
'upstream/21.0', help=
'source branch (default: upstream/21.0)')
29 parser.add_argument(
'--target', type=str,
30 default=
'upstream/master', help=
'target branch (default: upstream/master)')
31 parser.add_argument(
'--reset', action=
'store_true',
32 help=
'Remove branches created by this script. Use to start again or recover from any issues')
33 parser.add_argument(
'--debug', action=
'store_true',
34 help=
'Extra printing')
36 args = parser.parse_args()
39 return len(element.split(
'/'))
41 args.packages =
sorted(args.packages, key=sortBySplitLen)
56 from subprocess
import call, STDOUT, check_output
59 print(bcolors.FAIL +
'Cannot import subprocess or os, please make sure you have an OKish Python version. "asetup Athena,master,latest && lsetup git" will set you up.' + bcolors.ENDC)
62 if call([
"git",
"branch"], stderr=STDOUT, stdout=
open(os.devnull,
'w')) != 0:
63 print(bcolors.FAIL +
'Must be called from within a clone of Athena. If you are in your athena directory then please double check that you have run "lsetup git".' + bcolors.ENDC)
67 userBranch = os.environ[
'USER'] +
"_" + time.strftime(
'%d_%b') +
"_" + args.packages[0].rstrip(
'/').
split(
'/')[-1] +
"_to_" + args.target.split(
'/')[-1]
68 userTempBranch = userBranch +
"_TEMP_BRANCH_DO_NOT_MERGE_TO_OFFICIAL_REPOSITORY"
70 if call([
"git",
"branch"], stderr=STDOUT, stdout=
open(os.devnull,
'w')) != 0:
71 print(bcolors.FAIL +
'Must be called from within a clone of Athena' + bcolors.ENDC)
75 print(bcolors.HEADER +
'Removing branches created by this script.' + bcolors.ENDC)
76 print(bcolors.HEADER +
'Checking out ' + args.target + bcolors.ENDC)
77 call([
"rm",
"-f",
".git/index.lock"])
78 print(bcolors.OKBLUE +
'git checkout --force ' + args.target + bcolors.ENDC)
79 call([
"git",
"checkout",
"--force", args.target])
81 print(bcolors.OKBLUE +
'git reset --hard HEAD' + bcolors.ENDC)
82 call([
"git",
"reset",
"--hard",
"HEAD"])
84 print(bcolors.OKBLUE +
'git clean -fdx' + bcolors.ENDC)
85 call([
"git",
"clean",
"-fdx"])
88 print(bcolors.HEADER +
'Delete local branch ' + userBranch + bcolors.ENDC)
89 print(bcolors.OKBLUE +
'git branch -D ' + userBranch + bcolors.ENDC)
90 call([
"git",
"branch",
"-D", userBranch])
93 print(bcolors.HEADER +
'Delete remote branch ' + userBranch + bcolors.ENDC)
94 print(bcolors.OKBLUE +
'git push origin --delete ' + userBranch + bcolors.ENDC)
95 call([
"git",
"push",
"origin",
"--delete", userBranch])
98 print(bcolors.HEADER +
'Delete local temporary branch ' + userTempBranch + bcolors.ENDC)
99 print(bcolors.OKBLUE +
'git branch -D ' + userBranch + bcolors.ENDC)
100 call([
"git",
"branch",
"-D", userTempBranch])
103 print(bcolors.HEADER +
'Reset done. Start again by specifying "--stage 1"' + bcolors.ENDC)
109 print(bcolors.FAIL +
'Must specify "--stage 1" or "--stage 2"' + bcolors.ENDC)
113 print(bcolors.HEADER +
'Updating from upstream' + bcolors.ENDC)
114 print(bcolors.OKBLUE +
'git fetch upstream ' + bcolors.ENDC)
115 call([
"git",
"fetch",
"upstream"])
118 localChanges = check_output([
"git",
"status",
"--porcelain"],text=
True)
119 if localChanges !=
"":
120 print(
"Local changes: " + localChanges)
121 prompt =
input(bcolors.WARNING +
'This will discard all local changes in the repository which are not already committed, please confirm this is OK! (y/n): ' + bcolors.ENDC)
122 if (prompt !=
"Y" and prompt !=
"y"):
123 print(bcolors.HEADER +
'Exiting' + bcolors.ENDC)
126 print(bcolors.HEADER +
'Checking out ' + args.target +
' and discarding any un-committed changes' + bcolors.ENDC)
129 call([
"rm",
"-f",
".git/index.lock"])
130 print(bcolors.OKBLUE +
'git checkout --force ' + args.target + bcolors.ENDC)
131 call([
"git",
"checkout",
"--force", args.target])
134 print(bcolors.OKBLUE +
'git clean -fdx' + bcolors.ENDC)
135 call([
"git",
"clean",
"-fdx"])
138 print(bcolors.HEADER +
'Creating new temporary branch: ' + userTempBranch + bcolors.ENDC)
141 print(bcolors.OKBLUE +
'git branch ' + userTempBranch +
' ' + args.target + bcolors.ENDC)
142 call([
"git",
"branch", userTempBranch, args.target])
144 print(bcolors.OKBLUE +
'git checkout ' + userTempBranch + bcolors.ENDC)
145 call([
"git",
"checkout", userTempBranch])
148 print(bcolors.HEADER +
'Performing merge (this may take a while...)' + bcolors.ENDC)
149 print(bcolors.OKBLUE +
'git merge --no-commit ' + args.source + bcolors.ENDC)
150 call([
"git",
"merge",
"--no-commit", args.source])
153 print(bcolors.HEADER +
'Performing reset ' + bcolors.ENDC)
155 result = check_output([
"git",
"status",
"--porcelain"],text=
True)
159 for line
in result.splitlines():
160 fileTuple = line.split()
165 if (args.debug):
print(
"Evaluate " + fileTuple[1] +
" level=" +
str(level))
166 if level == len(fileSplit):
167 print(bcolors.FAIL +
"ERR" + bcolors.ENDC)
173 for path
in args.packages:
174 pathSplit = path.rstrip(
'/').
split(
'/')
175 if level == len(pathSplit)
and path == responsibleRule:
177 if (args.debug):
print(
" doKeep=True (" + path +
" = " + responsibleRule +
") as level is equal to size of " + path +
", " +
str(len(pathSplit)) )
178 elif doKeep ==
False and level < len(pathSplit)
and fileSplit[level] == pathSplit[level]:
180 responsibleRule = path
181 if (args.debug):
print(
" doProgress=True (" + fileSplit[level] +
" = " + pathSplit[level] +
") as level is less than the size of " + path +
", " +
str(len(pathSplit)) )
184 if (args.debug):
print(
" Keeping "+path)
187 resetPath += fileSplit[level] +
'/'
188 if (args.debug):
print(
" Progressing "+resetPath)
191 resetPath += fileSplit[level]
192 if (args.debug):
print(
" Resetting "+resetPath)
193 if not resetPath
in toReset:
194 print(
"To reset: "+resetPath +
" level = " +
str(level))
195 toReset.append(resetPath)
198 for pathToReset
in toReset:
199 print(
"Resetting: " + pathToReset)
200 print(bcolors.OKBLUE +
'git reset HEAD -- ' + pathToReset + bcolors.ENDC)
201 call([
"git",
"reset",
"HEAD",
"--", pathToReset], stdout=
open(os.devnull,
'w'))
203 print(bcolors.OKBLUE +
'git checkout HEAD -- ' + pathToReset + bcolors.ENDC)
204 call([
"git",
"checkout",
"HEAD",
"--", pathToReset], stdout=
open(os.devnull,
'w'))
207 print(bcolors.HEADER +
'Removing remaining untracked' + bcolors.ENDC)
208 print(bcolors.OKBLUE +
'git clean -fdx' + bcolors.ENDC)
209 call([
"git",
"clean",
"-fdx"])
212 print(bcolors.HEADER +
'Printing git status' + bcolors.ENDC)
213 print(bcolors.OKBLUE +
'git status' + bcolors.ENDC)
214 call([
"git",
"status"])
216 print(bcolors.HEADER +
'Please fix any merge conflicts which show up in red in the above "git status" output.' + bcolors.ENDC)
217 print(bcolors.HEADER +
'Please then test recompilation and runtime tests of these (and any dependent) packages against the most recent ' + args.target +
' nightly' + bcolors.ENDC)
218 print(bcolors.HEADER +
'When happy, run this command again with "--stage 2" to prepare the branch which may be merged to the official repository' + bcolors.ENDC)
219 print(bcolors.FAIL +
'Do NOT commit anything yourself, "--stage 2" will handle committing the changes.' + bcolors.ENDC)
221 elif args.stage == 2:
223 conflicted = check_output([
"git",
"diff",
"--name-only",
"--diff-filter=U"],text=
True)
225 print(bcolors.FAIL +
"Conflicting paths remain, please resolve and use 'git add' on:\n" + conflicted + bcolors.ENDC)
226 print(bcolors.HEADER +
'Use "git status" to check the state of the merge' + bcolors.ENDC)
227 print(bcolors.HEADER +
'Exiting' + bcolors.ENDC)
230 print(bcolors.HEADER +
'Stashing changes' + bcolors.ENDC)
231 print(bcolors.OKBLUE +
'git stash' + bcolors.ENDC)
232 call([
"git",
"stash"])
235 print(bcolors.HEADER +
'Creating new branch: ' + userBranch + bcolors.ENDC)
236 print(bcolors.OKBLUE +
'git checkout -b ' + userBranch +
' ' + args.target + bcolors.ENDC)
237 call([
"git",
"checkout",
"-b", userBranch, args.target])
240 print(bcolors.HEADER +
'Popping stash' + bcolors.ENDC)
241 print(bcolors.OKBLUE +
'git stash pop' + bcolors.ENDC)
242 call([
"git",
"stash",
"pop"])
245 print(bcolors.HEADER +
'Committing' + bcolors.ENDC)
247 for path
in args.packages:
248 allPackages += path.rstrip(
'/').
split(
'/')[-1] +
" "
250 commitMessage =
"Update packages:" + allPackages +
" from " + args.source +
" to " + args.target +
" via pseudo-merge"
251 print(bcolors.OKBLUE +
'git commit -am "' + commitMessage +
'"' + bcolors.ENDC)
252 call([
"git",
"commit",
"-am", commitMessage])
255 print(bcolors.HEADER +
'Push to user fork and cleanup' + bcolors.ENDC)
256 print(bcolors.OKBLUE +
'git push --set-upstream origin ' + userBranch + bcolors.ENDC)
257 call([
"git",
"push",
"--set-upstream",
"origin", userBranch])
260 print(bcolors.OKBLUE +
'git branch -D ' + userTempBranch + bcolors.ENDC)
261 call([
"git",
"branch",
"-D", userTempBranch])
263 print(bcolors.HEADER +
'Please open a merge request for ' + userBranch +
" to " + args.target + bcolors.ENDC)
264 print(bcolors.HEADER +
'https://gitlab.cern.ch/' + os.environ[
'USER'] +
'/athena/merge_requests/new' + bcolors.ENDC)
265 print(bcolors.HEADER +
'Exiting' + bcolors.ENDC)