OLD | NEW |
(Empty) | |
| 1 #!/bin/bash |
| 2 # Copyright 2011 the V8 project authors. All rights reserved. |
| 3 # Redistribution and use in source and binary forms, with or without |
| 4 # modification, are permitted provided that the following conditions are |
| 5 # met: |
| 6 # |
| 7 # * Redistributions of source code must retain the above copyright |
| 8 # notice, this list of conditions and the following disclaimer. |
| 9 # * Redistributions in binary form must reproduce the above |
| 10 # copyright notice, this list of conditions and the following |
| 11 # disclaimer in the documentation and/or other materials provided |
| 12 # with the distribution. |
| 13 # * Neither the name of Google Inc. nor the names of its |
| 14 # contributors may be used to endorse or promote products derived |
| 15 # from this software without specific prior written permission. |
| 16 # |
| 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 28 |
| 29 |
| 30 ########## Global variable definitions |
| 31 |
| 32 BRANCHNAME=prepare-push |
| 33 TRUNKBRANCH=trunk-push |
| 34 TEMP_BRANCH=v8-push-to-trunk-script-temporary-branch |
| 35 VERSION_FILE="src/version.cc" |
| 36 PERSISTFILE_BASENAME=/tmp/v8-push-to-trunk-tempfile |
| 37 CHANGELOG_ENTRY_FILE="$PERSISTFILE_BASENAME-changelog-entry" |
| 38 PATCH_FILE="$PERSISTFILE_BASENAME-patch" |
| 39 COMMITMSG_FILE="$PERSISTFILE_BASENAME-commitmsg" |
| 40 TOUCHED_FILES_FILE="$PERSISTFILE_BASENAME-touched-files" |
| 41 STEP=0 |
| 42 |
| 43 |
| 44 ########## Function definitions |
| 45 |
| 46 usage() { |
| 47 cat << EOF |
| 48 usage: $0 OPTIONS |
| 49 |
| 50 Performs the necessary steps for a V8 push to trunk. Only works for \ |
| 51 git checkouts. |
| 52 |
| 53 OPTIONS: |
| 54 -h Show this message |
| 55 -s Specify the step where to start work. Default: 0. |
| 56 -l Manually specify the git commit ID of the last push to trunk. |
| 57 EOF |
| 58 } |
| 59 |
| 60 die() { |
| 61 [[ -n "$1" ]] && echo "Error: $1" |
| 62 echo "Exiting." |
| 63 exit 1 |
| 64 } |
| 65 |
| 66 confirm() { |
| 67 echo -n "$1 [Y/n] " |
| 68 read ANSWER |
| 69 if [[ -z "$ANSWER" || "$ANSWER" == "Y" || "$ANSWER" == "y" ]] ; then |
| 70 return 0 |
| 71 else |
| 72 return 1 |
| 73 fi |
| 74 } |
| 75 |
| 76 delete_branch() { |
| 77 local MATCH=$(git branch | grep $1) |
| 78 if [ "$MATCH" == "$1" ] ; then |
| 79 confirm "Branch $1 exists, do you want to delete it?" |
| 80 if [ $? -eq 0 ] ; then |
| 81 git branch -D $1 |
| 82 echo "Branch $1 deleted." |
| 83 else |
| 84 die "Can't continue. Please delete branch $1 and try again." |
| 85 fi |
| 86 fi |
| 87 } |
| 88 |
| 89 # Persist and restore variables to support canceling/resuming execution |
| 90 # of this script. |
| 91 persist() { |
| 92 local VARNAME=$1 |
| 93 local FILE="$PERSISTFILE_BASENAME-$VARNAME" |
| 94 echo "${!VARNAME}" > $FILE |
| 95 } |
| 96 |
| 97 restore() { |
| 98 local VARNAME=$1 |
| 99 local FILE="$PERSISTFILE_BASENAME-$VARNAME" |
| 100 local VALUE="$(cat $FILE)" |
| 101 eval "$VARNAME=\"$VALUE\"" |
| 102 } |
| 103 |
| 104 restore_if_unset() { |
| 105 local VARNAME=$1 |
| 106 [[ -z "${!VARNAME}" ]] && restore "$VARNAME" |
| 107 [[ -z "${!VARNAME}" ]] && die "Variable '$VARNAME' could not be restored." |
| 108 } |
| 109 |
| 110 |
| 111 ########## Option parsing |
| 112 |
| 113 while getopts ":hs:l:" OPTION ; do |
| 114 case $OPTION in |
| 115 h) usage |
| 116 exit 0 |
| 117 ;; |
| 118 s) STEP=$OPTARG |
| 119 ;; |
| 120 l) LASTPUSH=$OPTARG |
| 121 ;; |
| 122 ?) echo "Illegal option: -$OPTARG" |
| 123 usage |
| 124 exit 1 |
| 125 ;; |
| 126 esac |
| 127 done |
| 128 |
| 129 |
| 130 ########## Regular workflow |
| 131 |
| 132 # Cancel if this is not a git checkout. |
| 133 [[ -d .git ]] \ |
| 134 || die "This is not a git checkout, this script won't work for you." |
| 135 |
| 136 # Cancel if EDITOR is unset or not executable. |
| 137 [[ -n "$EDITOR" && -x "$(which $EDITOR)" ]] \ |
| 138 || die "Please set your EDITOR environment variable, you'll need it." |
| 139 |
| 140 if [ $STEP -le 0 ] ; then |
| 141 echo ">>> Step 0: Preparation" |
| 142 # Check for a clean workdir. |
| 143 [[ -z "$(git status -s -uno)" ]] \ |
| 144 || die "Workspace is not clean. Please commit or undo your changes." |
| 145 |
| 146 # Persist current branch. |
| 147 CURRENT_BRANCH=$(git status -s -b -uno | grep "^##" | awk '{print $2}') |
| 148 persist "CURRENT_BRANCH" |
| 149 # Get ahold of a safe temporary branch and check it out. |
| 150 if [ "$CURRENT_BRANCH" != "$TEMP_BRANCH" ] ; then |
| 151 delete_branch $TEMP_BRANCH |
| 152 git checkout -b $TEMP_BRANCH |
| 153 fi |
| 154 # Delete branches if they exist. |
| 155 delete_branch $BRANCHNAME |
| 156 delete_branch $TRUNKBRANCH |
| 157 fi |
| 158 |
| 159 if [ $STEP -le 1 ] ; then |
| 160 echo ">>> Step 1: Fetch unfetched revisions." |
| 161 git svn fetch || die "'git svn fetch' failed." |
| 162 fi |
| 163 |
| 164 if [ $STEP -le 2 ] ; then |
| 165 echo ">>> Step 2: Create a fresh branch." |
| 166 git checkout -b $BRANCHNAME svn/bleeding_edge \ |
| 167 || die "Creating branch $BRANCHNAME failed." |
| 168 fi |
| 169 |
| 170 if [ $STEP -le 3 ] ; then |
| 171 echo ">>> Step 3: Detect commit ID of last push to trunk." |
| 172 [[ -n "$LASTPUSH" ]] || LASTPUSH=$(git log -1 --format=%H ChangeLog) |
| 173 LOOP=1 |
| 174 while [ $LOOP -eq 1 ] ; do |
| 175 # Print assumed commit, circumventing git's pager. |
| 176 git log -1 $LASTPUSH | cat |
| 177 confirm "Is the commit printed above the last push to trunk?" |
| 178 if [ $? -eq 0 ] ; then |
| 179 LOOP=0 |
| 180 else |
| 181 LASTPUSH=$(git log -1 --format=%H $LASTPUSH^ ChangeLog) |
| 182 fi |
| 183 done |
| 184 persist "LASTPUSH" |
| 185 fi |
| 186 |
| 187 if [ $STEP -le 4 ] ; then |
| 188 echo ">>> Step 4: Prepare raw ChangeLog entry." |
| 189 # These version numbers are used again later for the trunk commit. |
| 190 MAJOR=$(grep "#define MAJOR_VERSION" "$VERSION_FILE" | awk '{print $NF}') |
| 191 persist "MAJOR" |
| 192 MINOR=$(grep "#define MINOR_VERSION" "$VERSION_FILE" | awk '{print $NF}') |
| 193 persist "MINOR" |
| 194 BUILD=$(grep "#define BUILD_NUMBER" "$VERSION_FILE" | awk '{print $NF}') |
| 195 persist "BUILD" |
| 196 |
| 197 DATE=$(date +%Y-%m-%d) |
| 198 persist "DATE" |
| 199 echo "$DATE: Version $MAJOR.$MINOR.$BUILD" > "$CHANGELOG_ENTRY_FILE" |
| 200 echo "" >> "$CHANGELOG_ENTRY_FILE" |
| 201 COMMITS=$(git log $LASTPUSH..HEAD --format=%H) |
| 202 for commit in $COMMITS ; do |
| 203 # Get the commit's title line. |
| 204 git log -1 $commit --format="%w(80,8,8)%s" >> "$CHANGELOG_ENTRY_FILE" |
| 205 # Grep for "BUG=xxxx" lines in the commit message. |
| 206 git log -1 $commit --format="%b" | grep BUG= | grep -v "BUG=$" \ |
| 207 | sed -e 's/^/ /' \ |
| 208 >> "$CHANGELOG_ENTRY_FILE" |
| 209 # Append the commit's author for reference. |
| 210 git log -1 $commit --format="%w(80,8,8)(%an)" >> "$CHANGELOG_ENTRY_FILE" |
| 211 echo "" >> "$CHANGELOG_ENTRY_FILE" |
| 212 done |
| 213 fi |
| 214 |
| 215 if [ $STEP -le 5 ] ; then |
| 216 echo ">>> Step 5: Edit ChangeLog entry." |
| 217 echo -n "Please press <Return> to have your EDITOR open the ChangeLog entry, \ |
| 218 then edit its contents to your liking. When you're done, save the file and \ |
| 219 exit your EDITOR. " |
| 220 read ANSWER |
| 221 $EDITOR "$CHANGELOG_ENTRY_FILE" |
| 222 NEWCHANGELOG=$(mktemp) |
| 223 # Eliminate any trailing newlines by going through a shell variable. |
| 224 CHANGELOGENTRY=$(cat "$CHANGELOG_ENTRY_FILE") |
| 225 [[ -n "$CHANGELOGENTRY" ]] || die "Empty ChangeLog entry." |
| 226 echo "$CHANGELOGENTRY" > "$NEWCHANGELOG" |
| 227 echo "" >> "$NEWCHANGELOG" # Explicitly insert two empty lines. |
| 228 echo "" >> "$NEWCHANGELOG" |
| 229 cat ChangeLog >> "$NEWCHANGELOG" |
| 230 mv "$NEWCHANGELOG" ChangeLog |
| 231 fi |
| 232 |
| 233 if [ $STEP -le 6 ] ; then |
| 234 echo ">>> Step 6: Increment version number." |
| 235 restore_if_unset "BUILD" |
| 236 NEWBUILD=$(($BUILD + 1)) |
| 237 confirm "Automatically increment BUILD_NUMBER? (Saying 'n' will fire up \ |
| 238 your EDITOR on $VERSION_FILE so you can make arbitrary changes. When \ |
| 239 you're done, save the file and exit your EDITOR.)" |
| 240 if [ $? -eq 0 ] ; then |
| 241 sed -e "/#define BUILD_NUMBER/s/[0-9]*$/$NEWBUILD/" \ |
| 242 -i "$VERSION_FILE" |
| 243 else |
| 244 $EDITOR "$VERSION_FILE" |
| 245 fi |
| 246 NEWMAJOR=$(grep "#define MAJOR_VERSION" "$VERSION_FILE" | awk '{print $NF}') |
| 247 persist "NEWMAJOR" |
| 248 NEWMINOR=$(grep "#define MINOR_VERSION" "$VERSION_FILE" | awk '{print $NF}') |
| 249 persist "NEWMINOR" |
| 250 NEWBUILD=$(grep "#define BUILD_NUMBER" "$VERSION_FILE" | awk '{print $NF}') |
| 251 persist "NEWBUILD" |
| 252 fi |
| 253 |
| 254 if [ $STEP -le 7 ] ; then |
| 255 echo ">>> Step 7: Commit to local branch." |
| 256 restore_if_unset "NEWMAJOR" |
| 257 restore_if_unset "NEWMINOR" |
| 258 restore_if_unset "NEWBUILD" |
| 259 git commit -a -m "Prepare push to trunk. \ |
| 260 Now working on version $NEWMAJOR.$NEWMINOR.$NEWBUILD." \ |
| 261 || die "'git commit -a' failed." |
| 262 fi |
| 263 |
| 264 if [ $STEP -le 8 ] ; then |
| 265 echo ">>> Step 8: Upload for code review." |
| 266 echo -n "Please enter the email address of a V8 reviewer for your patch: " |
| 267 read REVIEWER |
| 268 git cl upload -r $REVIEWER --send-mail \ |
| 269 || die "'git cl upload' failed, please try again." |
| 270 fi |
| 271 |
| 272 if [ $STEP -le 9 ] ; then |
| 273 echo ">>> Step 9: Commit to the repository." |
| 274 echo "Please wait for an LGTM, then type \"LGTM<Return>\" to commit your \ |
| 275 change. (If you need to iterate on the patch, do so in another shell.)" |
| 276 unset ANSWER |
| 277 while [ "$ANSWER" != "LGTM" ] ; do |
| 278 [[ -n "$ANSWER" ]] && echo "That was not 'LGTM'." |
| 279 echo -n "> " |
| 280 read ANSWER |
| 281 done |
| 282 # Re-read the ChangeLog entry (to pick up possible changes). |
| 283 cat ChangeLog | awk --posix '{ |
| 284 if ($0 ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}:/) { |
| 285 if (in_firstblock == 1) { |
| 286 exit 0; |
| 287 } else { |
| 288 in_firstblock = 1; |
| 289 } |
| 290 }; |
| 291 print $0; |
| 292 }' > "$CHANGELOG_ENTRY_FILE" |
| 293 git cl dcommit || die "'git cl dcommit' failed, please try again." |
| 294 fi |
| 295 |
| 296 if [ $STEP -le 10 ] ; then |
| 297 echo ">>> Step 10: NOP" |
| 298 # Present in the manual guide, not necessary (even harmful!) for this script. |
| 299 fi |
| 300 |
| 301 if [ $STEP -le 11 ] ; then |
| 302 echo ">>> Step 11: Squash commits into one." |
| 303 # Instead of relying on "git rebase -i", we'll just create a diff, because |
| 304 # that's easier to automate. |
| 305 git diff svn/trunk > "$PATCH_FILE" |
| 306 # Convert the ChangeLog entry to commit message format: |
| 307 # - remove date |
| 308 # - remove indentation |
| 309 # - merge paragraphs into single long lines, keeping empty lines between them. |
| 310 restore_if_unset "DATE" |
| 311 CHANGELOGENTRY=$(cat "$CHANGELOG_ENTRY_FILE") |
| 312 echo "$CHANGELOGENTRY" \ |
| 313 | sed -e "s/^$DATE: //" \ |
| 314 | sed -e 's/^ *//' \ |
| 315 | awk '{ |
| 316 if (need_space == 1) { |
| 317 printf(" "); |
| 318 }; |
| 319 printf("%s", $0); |
| 320 if ($0 ~ /^$/) { |
| 321 printf("\n\n"); |
| 322 need_space = 0; |
| 323 } else { |
| 324 need_space = 1; |
| 325 } |
| 326 }' > "$COMMITMSG_FILE" || die "Commit message editing failed." |
| 327 LOOP=1 |
| 328 while [ $LOOP -eq 1 ] ; do |
| 329 echo "This is the trunk commit message:" |
| 330 echo "--------------------" |
| 331 cat "$COMMITMSG_FILE" |
| 332 echo -e "\n--------------------" |
| 333 confirm "Does this look good to you? (Saying 'n' will fire up your \ |
| 334 EDITOR so you can change the commit message. When you're done, save the \ |
| 335 file and exit your EDITOR.)" |
| 336 if [ $? -eq 0 ] ; then |
| 337 LOOP=0 |
| 338 else |
| 339 $EDITOR "$COMMITMSG_FILE" |
| 340 fi |
| 341 done |
| 342 rm -f "$CHANGELOG_ENTRY_FILE" |
| 343 fi |
| 344 |
| 345 if [ $STEP -le 12 ] ; then |
| 346 echo ">>> Step 12: Create a new branch from trunk." |
| 347 git checkout -b $TRUNKBRANCH svn/trunk \ |
| 348 || die "Checking out a new branch '$TRUNKBRANCH' failed." |
| 349 fi |
| 350 |
| 351 if [ $STEP -le 13 ] ; then |
| 352 echo ">>> Step 13: Apply squashed changes." |
| 353 patch -p1 < "$PATCH_FILE" | tee >(awk '{print $NF}' >> "$TOUCHED_FILES_FILE") |
| 354 [[ $? -eq 0 ]] || die "Applying the patch to trunk failed." |
| 355 TOUCHED_FILES=$(cat "$TOUCHED_FILES_FILE") |
| 356 for FILE in $TOUCHED_FILES ; do |
| 357 git add "$FILE" |
| 358 done |
| 359 rm -f "$PATCH_FILE" |
| 360 rm -f "$TOUCHED_FILES_FILE" |
| 361 fi |
| 362 |
| 363 if [ $STEP -le 14 ] ; then |
| 364 echo ">>> Step 14: Set correct version for trunk." |
| 365 restore_if_unset "MAJOR" |
| 366 restore_if_unset "MINOR" |
| 367 restore_if_unset "BUILD" |
| 368 sed -e "/#define MAJOR_VERSION/s/[0-9]*$/$MAJOR/" \ |
| 369 -e "/#define MINOR_VERSION/s/[0-9]*$/$MINOR/" \ |
| 370 -e "/#define BUILD_NUMBER/s/[0-9]*$/$BUILD/" \ |
| 371 -e "/#define PATCH_LEVEL/s/[0-9]*$/0/" \ |
| 372 -e "/#define IS_CANDIDATE_VERSION/s/[0-9]*$/0/" \ |
| 373 -i "$VERSION_FILE" || die "Patching $VERSION_FILE failed." |
| 374 fi |
| 375 |
| 376 if [ $STEP -le 15 ] ; then |
| 377 echo ">>> Step 15: Commit to local trunk branch." |
| 378 git add "$VERSION_FILE" |
| 379 git commit -F "$COMMITMSG_FILE" || die "'git commit' failed." |
| 380 rm -f "$COMMITMSG_FILE" |
| 381 fi |
| 382 |
| 383 if [ $STEP -le 16 ] ; then |
| 384 echo ">>> Step 16: Sanity check." |
| 385 confirm "Please check if your local checkout is sane: Inspect $VERSION_FILE, \ |
| 386 compile, run tests. Do you want to commit this new trunk revision to the \ |
| 387 repository?" |
| 388 [[ $? -eq 0 ]] || die "Execution canceled." |
| 389 fi |
| 390 |
| 391 if [ $STEP -le 17 ] ; then |
| 392 echo ">>> Step 17. Commit to SVN." |
| 393 git svn dcommit || die "'git svn dcommit' failed." |
| 394 fi |
| 395 |
| 396 if [ $STEP -le 18 ] ; then |
| 397 echo ">>> Step 18: Tag the new revision." |
| 398 restore_if_unset "MAJOR" |
| 399 restore_if_unset "MINOR" |
| 400 restore_if_unset "BUILD" |
| 401 git svn tag $MAJOR.$MINOR.$BUILD -m "Tagging version $MAJOR.$MINOR.$BUILD" \ |
| 402 || die "'git svn tag' failed." |
| 403 fi |
| 404 |
| 405 if [ $STEP -le 19 ] ; then |
| 406 echo ">>> Step 19: Cleanup." |
| 407 restore_if_unset "CURRENT_BRANCH" |
| 408 git checkout -f $CURRENT_BRANCH |
| 409 [[ "$TEMP_BRANCH" != "$CURRENT_BRANCH" ]] && git branch -D $TEMP_BRANCH |
| 410 [[ "$BRANCHNAME" != "$CURRENT_BRANCH" ]] && git branch -D $BRANCHNAME |
| 411 [[ "$TRUNKBRANCH" != "$CURRENT_BRANCH" ]] && git branch -D $TRUNKBRANCH |
| 412 fi |
| 413 |
| 414 if [ $STEP -le 20 ] ; then |
| 415 echo ">>> Step 20: Done!" |
| 416 restore_if_unset "MAJOR" |
| 417 restore_if_unset "MINOR" |
| 418 restore_if_unset "BUILD" |
| 419 echo "Congratulations, you have successfully created the trunk revision \ |
| 420 $MAJOR.$MINOR.$BUILD. Please don't forget to update the v8rel spreadsheet, \ |
| 421 and to roll this new version into Chromium." |
| 422 # Clean up all temporary files. |
| 423 rm -f "$PERSISTFILE_BASENAME"* |
| 424 fi |
OLD | NEW |