Skip to content

Instantly share code, notes, and snippets.

@clops
Last active August 1, 2022 11:41
Show Gist options
  • Save clops/3ac9cd6c42a66ba65101 to your computer and use it in GitHub Desktop.
Save clops/3ac9cd6c42a66ba65101 to your computer and use it in GitHub Desktop.
GIT P4T Console Deploy
#!/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";
}
@clops
Copy link
Author

clops commented Jul 31, 2014

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.

@clops
Copy link
Author

clops commented Aug 1, 2014

Best usage: add full path to script as an alias into .bash_profile such that it works from any GIT directory

@wodka
Copy link

wodka commented Aug 5, 2014

@wodka
Copy link

wodka commented Aug 5, 2014

you could note that it can be called like this: ../deploy feature/ZEN-20854 customer/hella

@clops
Copy link
Author

clops commented Aug 12, 2014

@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