ATLAS Offline Software
Loading...
Searching...
No Matches
git-package-pseudomerge.py
Go to the documentation of this file.
1#!/usr/bin/env python
2
3
4# Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
5# @file: git-package-pseudomerge.py
6# @author: Tim Martin
7#
8
9"""
10Merge individual Athena packages between git branches. Commit the resulting diff as a cherry-pick.
11"""
12
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."
16
17import argparse, time
18
19parser = 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.')
23parser.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.')
25parser.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')
27parser.add_argument('--source', type=str,
28 default='upstream/21.0', help='source branch (default: upstream/21.0)')
29parser.add_argument('--target', type=str,
30 default='upstream/master', help='target branch (default: upstream/master)')
31parser.add_argument('--reset', action='store_true',
32 help='Remove branches created by this script. Use to start again or recover from any issues')
33parser.add_argument('--debug', action='store_true',
34 help='Extra printing')
35
36args = parser.parse_args()
37
38def sortBySplitLen(element):
39 return len(element.split('/'))
40
41args.packages = sorted(args.packages, key=sortBySplitLen)
42
43print(args)
44
45class bcolors:
46 HEADER = '\033[95m'
47 OKBLUE = '\033[94m'
48 OKGREEN = '\033[92m'
49 WARNING = '\033[93m'
50 FAIL = '\033[91m'
51 ENDC = '\033[0m'
52 BOLD = '\033[1m'
53 UNDERLINE = '\033[4m'
54
55try:
56 from subprocess import call, STDOUT, check_output
57 import os
58except:
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)
60 exit()
61
62if 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)
64 exit()
65
66import time
67userBranch = os.environ['USER'] + "_" + time.strftime('%d_%b') + "_" + args.packages[0].rstrip('/').split('/')[-1] + "_to_" + args.target.split('/')[-1]
68userTempBranch = userBranch + "_TEMP_BRANCH_DO_NOT_MERGE_TO_OFFICIAL_REPOSITORY"
69
70if 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)
72 exit()
73
74if args.reset:
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])
80 time.sleep(0.1)
81 print(bcolors.OKBLUE + 'git reset --hard HEAD' + bcolors.ENDC)
82 call(["git", "reset", "--hard", "HEAD"])
83 time.sleep(0.1)
84 print(bcolors.OKBLUE + 'git clean -fdx' + bcolors.ENDC)
85 call(["git", "clean", "-fdx"])
86 time.sleep(0.1)
87 #
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])
91 time.sleep(0.1)
92 #
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])
96 time.sleep(0.1)
97 #
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])
101 time.sleep(0.1)
102 #
103 print(bcolors.HEADER + 'Reset done. Start again by specifying "--stage 1"' + bcolors.ENDC)
104 exit()
105
106try:
107 args.stage
108except NameError:
109 print(bcolors.FAIL + 'Must specify "--stage 1" or "--stage 2"' + bcolors.ENDC)
110 exit()
111
112if args.stage == 1:
113 print(bcolors.HEADER + 'Updating from upstream' + bcolors.ENDC)
114 print(bcolors.OKBLUE + 'git fetch upstream ' + bcolors.ENDC)
115 call(["git", "fetch", "upstream"])
116
117 # Check we're not going to trample on work
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)
124 exit()
125
126 print(bcolors.HEADER + 'Checking out ' + args.target + ' and discarding any un-committed changes' + bcolors.ENDC)
127
128 # Try and cover all cases - start in a good place
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])
132 time.sleep(0.1)
133 #
134 print(bcolors.OKBLUE + 'git clean -fdx' + bcolors.ENDC)
135 call(["git", "clean", "-fdx"])
136 time.sleep(0.1)
137
138 print(bcolors.HEADER + 'Creating new temporary branch: ' + userTempBranch + bcolors.ENDC)
139
140 # Make the new branch
141 print(bcolors.OKBLUE + 'git branch ' + userTempBranch + ' ' + args.target + bcolors.ENDC)
142 call(["git", "branch", userTempBranch, args.target])
143 time.sleep(0.1)
144 print(bcolors.OKBLUE + 'git checkout ' + userTempBranch + bcolors.ENDC)
145 call(["git", "checkout", userTempBranch])
146 time.sleep(0.1)
147
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])
151 time.sleep(0.1)
152
153 print(bcolors.HEADER + 'Performing reset ' + bcolors.ENDC)
154
155 result = check_output(["git", "status", "--porcelain"],text=True)
156 toReset = []
157 responsibleRule = ""
158
159 for line in result.splitlines():
160 fileTuple = line.split() # Git status flag and path
161 fileSplit = fileTuple[1].split('/') # Exploded path
162 level = 0 # Distance into directory tree
163 resetPath = ''
164 while (True):
165 if (args.debug): print("Evaluate " + fileTuple[1] + " level=" + str(level))
166 if level == len(fileSplit): # Reached beyond bottom of directory tree
167 print(bcolors.FAIL + "ERR" + bcolors.ENDC)
168 break
169
170 doKeep = False
171 doProgress = False
172
173 for path in args.packages: # For each package in the list of merge-packages
174 pathSplit = path.rstrip('/').split('/') # Explode path
175 if level == len(pathSplit) and path == responsibleRule:
176 doKeep = True # The previous operation matched the final path segment. Keep this file
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]:
179 doProgress = True # We match the current level - check next level of directory
180 responsibleRule = path # Record which package rule is allowing us to progress
181 if (args.debug): print(" doProgress=True (" + fileSplit[level] + " = " + pathSplit[level] + ") as level is less than the size of " + path + ", " + str(len(pathSplit)) )
182
183 if doKeep: # At least one match to keep this
184 if (args.debug): print(" Keeping "+path)
185 break
186 elif doProgress: # At least one partial match - check next level
187 resetPath += fileSplit[level] + '/'
188 if (args.debug): print(" Progressing "+resetPath)
189 level += 1
190 else: # No matches - reset this path
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)
196 break
197
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'))
202 time.sleep(0.05)
203 print(bcolors.OKBLUE + 'git checkout HEAD -- ' + pathToReset + bcolors.ENDC)
204 call(["git", "checkout", "HEAD", "--", pathToReset], stdout=open(os.devnull, 'w'))
205 time.sleep(0.05)
206
207 print(bcolors.HEADER + 'Removing remaining untracked' + bcolors.ENDC)
208 print(bcolors.OKBLUE + 'git clean -fdx' + bcolors.ENDC)
209 call(["git", "clean", "-fdx"])
210 time.sleep(0.1)
211
212 print(bcolors.HEADER + 'Printing git status' + bcolors.ENDC)
213 print(bcolors.OKBLUE + 'git status' + bcolors.ENDC)
214 call(["git", "status"])
215
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)
220
221elif args.stage == 2:
222
223 conflicted = check_output(["git", "diff", "--name-only", "--diff-filter=U"],text=True)
224 if conflicted != "":
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)
228 exit()
229
230 print(bcolors.HEADER + 'Stashing changes' + bcolors.ENDC)
231 print(bcolors.OKBLUE + 'git stash' + bcolors.ENDC)
232 call(["git", "stash"])
233 time.sleep(0.1)
234
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])
238 time.sleep(0.1)
239
240 print(bcolors.HEADER + 'Popping stash' + bcolors.ENDC)
241 print(bcolors.OKBLUE + 'git stash pop' + bcolors.ENDC)
242 call(["git", "stash", "pop"])
243 time.sleep(0.1)
244
245 print(bcolors.HEADER + 'Committing' + bcolors.ENDC)
246 allPackages = ""
247 for path in args.packages:
248 allPackages += path.rstrip('/').split('/')[-1] + " "
249
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])
253 time.sleep(0.1)
254
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])
258 time.sleep(0.1)
259
260 print(bcolors.OKBLUE + 'git branch -D ' + userTempBranch + bcolors.ENDC)
261 call(["git", "branch", "-D", userTempBranch])
262
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)
void print(char *figname, TCanvas *c1)
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:177