Last active
August 1, 2022 11:41
-
-
Save clops/3ac9cd6c42a66ba65101 to your computer and use it in GitHub Desktop.
GIT P4T Console Deploy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/php | |
<?php | |
/** | |
* GIT Merge Helper | |
* | |
* Installation: | |
* 1. Download / Clone this GIST https://gist.github.com/clops/3ac9cd6c42a66ba65101 into some directory | |
* 2. chmod a+x deploy.php | |
* 3. Add an alias into .bash_profile with a full path to the deploy.php script, mine is: | |
* alias deploy="~/dev/3ac9cd6c42a66ba65101/deploy.php" | |
* 4. Done! | |
* | |
* Usage: | |
* 1. Inside a GIT directory type: deploy | |
* 2. Follow on-screen instructions | |
* | |
* Quick Options: deploy [branch_to_be_merged] [branch_to_be_merged_to] | |
* deploy this <-- will reference current branch, can be used in any of the above options | |
* deploy this master,release/1410,development <-- yes, destinations can now be comma separated | |
* deploy [branch_to_be_merged] here <-- yep, "here" and "this" are synonyms | |
* deploy this d,r,m <-- yep, shortcuts, deploy THIS branch to development, current release and master | |
* | |
* @author Alexey Kulikov <[email protected]> | |
* @since Aug 2014 | |
* | |
**/ | |
###### SETUP ########################################################## | |
$branchAliases = array( | |
'd' => 'development', | |
'r' => '%current_release%', | |
'm' => 'master' | |
); | |
date_default_timezone_set('Europe/Vienna'); | |
####################################################################### | |
echo "+++++++++++ \e[1mGIT P4T Console Deploy V 1.16\e[0m Author: AK +++++++++++\n"; | |
/* 1. ---- COLLECT PARAMETERS / USER INPUT --------------------------------------------- */ | |
//what is the branch name that needs to be merged? | |
if (!isset($_SERVER['argv'][1])) { | |
$branchName = readline("Branchname to merge, use \"this\" to reference the current branch\nMerge: "); | |
} else { | |
$branchName = $_SERVER['argv'][1]; | |
} | |
//is there some special destination or are we doing a special deployment-merge | |
if (!isset($_SERVER['argv'][2])) { | |
$mergeIntoBranchName = trim(readline("Branchname to merge into (leave empty for default master/release/development merge)\nInto: ")); | |
} else { | |
$mergeIntoBranchName = $_SERVER['argv'][2]; | |
} | |
//process user inputs / mayhaps modify them a little ----------------------------------- | |
//figure out the current branch name and if there are any modified files in it | |
$currentState = `git status --porcelain -b`; | |
$currentState = explode("\n", $currentState); | |
$currentBranch = trim(str_replace('##', '', $currentState[0])); | |
$currentBranch = explode('...', $currentBranch); | |
$currentBranch = $currentBranch[0]; //weird :) | |
//is the current branch referenced here by a shortcut? | |
if (in_array($branchName, array('this', 'here'))) { | |
$branchName = $currentBranch; | |
} | |
//is the destination branch referenced here by a shortcut? | |
if (in_array($mergeIntoBranchName, array('this', 'here'))) { | |
$mergeIntoBranchName = $currentBranch; | |
} | |
//what is the branch type (hotfix/bugfix/feature)? | |
$branchType = explode('/', $branchName); | |
$branchType = $branchType[0]; | |
/* 2. ---- REMEMBER STARTING POINT ---------------------------------------------------- */ | |
$stashed = false; | |
if (isset($currentState[1]) && !empty($currentState[1])) { //any modified files? stash them! | |
echo "Stashing unsaved changes away...\n"; | |
echo `git stash`; | |
$stashed = true; | |
} | |
/* 3. ---- SOME BASIC ERROR CHECKS ---------------------------------------------------- */ | |
//now check if the desired branch that has to be merged exists at all | |
$existingBranches = getBranches(); | |
if (!in_array($branchName, $existingBranches)) { | |
//refresh branch list, perhaps it was a new branch? | |
$existingBranches = getBranches(true); | |
if (!in_array($branchName, $existingBranches)) { | |
errorMessage("Branch \"" . $branchName . "\" does NOT exist!"); | |
} | |
} | |
//is this an automated merge? lets guess where stuff has to go to then | |
if (empty($mergeIntoBranchName)) { | |
//figure out current release branch (if any) | |
$currentReleaseBranch = getCurrentReleaseBranch(); | |
switch ($branchType) { | |
case 'hotfix': { | |
$mergeIntoBranchName = 'development,' . $currentReleaseBranch . ',master'; | |
break; | |
} | |
case 'bugfix': { | |
$mergeIntoBranchName = $currentReleaseBranch . ',development'; | |
break; | |
} | |
case 'feature': { | |
$mergeIntoBranchName = 'development'; | |
break; | |
} | |
default: { | |
$branchType = 'manual'; //marker for post-merge deletion | |
} | |
} | |
} | |
//last but not lease, current branch has unpushed commits maybe? | |
$unpushedFiles = `git diff origin/$(git name-rev --name-only HEAD)..HEAD --name-status`; | |
if($unpushedFiles){ | |
echo "There are unpushed, committed changes in the current branch: \n"; | |
echo $unpushedFiles; | |
echo "\n"; | |
while($confirm = readline("\n\e[92mDiff with \"D\", Push with \"Y\", any other input to continue with no push:\e[0m ")) { | |
if ($confirm == 'Y') { | |
echo "Pushing..."; | |
echo `git push`; | |
echo "done!\n"; | |
break; | |
} elseif ($confirm == 'D') { | |
echo `git diff --color HEAD origin/{$currentBranch}`; | |
echo "\n\n"; | |
} else { | |
break; | |
} | |
} | |
} | |
/* 4. ---- ACTUAL MERGING ------------------------------------------------------------- */ | |
echo "Will attempt to deploy branch \e[1m" . $branchName . "\e[0m, of type \"" . $branchType . "\" to the following destination(s) --> \e[1m" . $mergeIntoBranchName . "\e[0m\n"; | |
//make sure we have the desired branch locally (needed for merge) --> since v 1.11 decided to merge from origin | |
//echo "Checking out branch \"{$branchName}\"\n"; | |
//echo `git checkout {$branchName}`; | |
//echo `git pull`; //also updated to the latest version, useful for people working from many machines | |
$destinationBranch = ''; //init | |
$mergeIntoBranchNames = explode(',', $mergeIntoBranchName); | |
foreach ($mergeIntoBranchNames as $destinationBranch) { | |
//process aliases | |
if (isset($branchAliases[$destinationBranch])) { | |
$destinationBranch = $branchAliases[$destinationBranch]; | |
} | |
//current release alias | |
if ($destinationBranch == '%current_release%') { | |
$destinationBranch = 'release/' . date('ym', strtotime('next month')); | |
} | |
if (!in_array($destinationBranch, $existingBranches)) { | |
errorMessage("Destination Branch \"" . $destinationBranch . "\" does NOT exist!", false); | |
continue; | |
} | |
if (!mergeBranch($branchName, $destinationBranch)) { | |
errorMessage("Merge Terminated"); | |
} | |
} | |
//no errors, great! remove remote branch, its been merged everywhere | |
if ($branchType != 'manual' && $destinationBranch == 'development') { | |
$confirm = readline("\n\e[92mRemove branch from origin? Confirm with \"Y\", any other input to skip:\e[0m "); | |
if ($confirm == 'Y') { | |
echo "Removing branch from origin..."; | |
echo `git push origin --delete {$branchName}`; | |
echo "done!\n"; | |
} | |
} | |
//switch back to the original branch | |
if ($currentBranch != 'development') { | |
echo "Switching back to the initial branch\n"; | |
echo `git checkout {$currentBranch}`; | |
} | |
//anything stashed away before? | |
if ($stashed) { | |
echo "Getting back stashed changes...\n"; | |
echo `git stash pop`; | |
} | |
//echo signature | |
echo " ___ ___ | |
/ /\ ___ /__/\ | |
/ /:/_ / /\ \ \:\ | |
/ /:/ /\ / /:/ \ \:\ | |
/ /:/ /:/ /__/::\ _____\__\:\ | |
/__/:/ /:/ \__\/\:\__ /__/::::::::\ | |
\ \:\/:/ \ \:\/\ \ \:\~~\~~\/ | |
\ \::/ \__\::/ \ \:\ ~~~ | |
\ \:\ /__/:/ \ \:\ | |
\ \:\ \__\/ \ \:\ | |
\__\/ \__\/ \n"; | |
###################### UTILITY HELPER FUNCTIONS BELOW ###################### | |
/** | |
* Utility function: Merge a source branch into the destination branch | |
* | |
* @param String $source | |
* @param String $destination | |
**/ | |
function mergeBranch ($source, $destination) { | |
echo "\n\n+++++++++ \e[1mMerging " . $source . " --> " . $destination . "\e[0m +++++++++\n"; | |
echo "Checking out {$destination}\n"; | |
echo `git checkout {$destination}`; | |
echo `git pull`; | |
echo "\nStarting Merge of \e[1morigin/" . $source . " --> " . $destination . "\e[0m ...\n"; | |
$merge = `git merge --no-ff origin/{$source} `; //merging directly from origin, no local copy neccessary | |
echo $merge; | |
//check if the merge was a success? | |
if (strpos($merge, 'Automatic merge failed') !== false) { | |
if (readline("\n\e[31mAutomatic merge failed, undo merge with \"Y\", any other input to stop:\e[0m ") == 'Y') { | |
resetMerge(); | |
} | |
return false; | |
} elseif (strpos($merge, 'have diverged') !== false) { | |
if (readline("\n\e[31mYour destination branch has diverged from origin, handle with care! Undo merge with \"Y\", any other input to stop:\e[0m ") == 'Y') { | |
resetMerge(); | |
} | |
return false; | |
//this has been merged already, so continue gracefully | |
} elseif (strpos($merge, 'Already up-to-date.') !== false) { | |
echo "\n"; | |
return true; | |
} | |
//now need the user to confirm this prior to pushing changes | |
//this is inside a loop, as if the user wants to see a diff we need to get back to the confirmation message | |
while($confirm = readline("\n\e[92mConfirm merge & push with \"Y\", diff with \"D\", skip with \"S\", any other input to stop:\e[0m ")) { | |
if ($confirm == 'Y') { | |
echo `git push`; | |
echo "\n\n"; | |
return true; | |
} elseif ($confirm == 'S') { //skip | |
echo "Skipping push...\n"; | |
resetMerge(); | |
return true; | |
} elseif ($confirm == 'D') { //show diff | |
echo `git diff origin/{$destination}`; | |
echo "\n\n"; | |
} else { | |
return false; | |
} | |
} | |
//obsolete? | |
return false; | |
} | |
/** | |
* Utility function: Echo error message and terminate script | |
* | |
* @param String $message | |
**/ | |
function errorMessage ($message, $exit = true) { | |
echo "\n\e[31mERROR: " . $message . ". Terminating\e[0m\n\n"; | |
if ($exit) { | |
exit; | |
} | |
} | |
/** | |
* Utility function: get a list of all known branches | |
* | |
* @param boolean $forceUpdateBranchList | |
* | |
* @return Array | |
**/ | |
function getBranches ($forceUpdateBranchList = false) { | |
if ($forceUpdateBranchList) { | |
echo "Updating branch list... "; | |
echo `git remote update --prune`; | |
echo "done!\n"; | |
} | |
$branches = `git branch -a`; | |
$branches = explode("\n", $branches); | |
foreach ($branches as &$branch) { | |
$branch = str_replace('remotes/origin/', '', trim($branch)); | |
} | |
return $branches; | |
} | |
/** | |
* Utility Function: get the current release branch from origin | |
* | |
* @return String | |
**/ | |
function getCurrentReleaseBranch () { | |
$branches = `git branch -r --no-merged`; | |
$branches = explode("\n", $branches); | |
foreach ($branches as $branch) { | |
$branch = trim($branch); | |
if (strstr($branch, 'origin/release/') !== false) { | |
return str_replace('origin/', '', $branch); | |
} | |
} | |
return false; | |
} | |
/** | |
* Generic Merge Reset | |
*/ | |
function resetMerge () { | |
echo `git reset --merge ORIG_HEAD`; | |
echo "\nReset Merge Done!\n"; | |
} |
Best usage: add full path to script as an alias into .bash_profile such that it works from any GIT directory
I added a small fix in https://gist.github.com/wodka/0e75e036fb9ac1ca3077
you could note that it can be called like this: ../deploy feature/ZEN-20854 customer/hella
@wodka yep, you're right 👍 I have fixed some minor bugs, added a "skip" option when merging and consider it as beta software now :)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Awesome P4T Git branch deployment script.
It has to be run from the console inside a P4T Git repository. Will take exactly one argument --> the name of the branch to be deployed. It will automagically determine if the branch is a hot fix, bugfix or a feature and merge it to all the necessary core branches accordingly.