Created
August 25, 2017 16:41
-
-
Save parrotbait/1f24c6ea11cb6ec1f0518b762b81d92e to your computer and use it in GitHub Desktop.
Git has an unfortunate habit of detaching heads every time you change branches on the top level repo. This script goes through all the submodules and looks for a commit on a branch that matches the current hash of that branch. If it finds it it does a checkout on the branch. NOTE: this isn't foolproof, if there are multiple branches with the sam…
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/env python | |
import platform | |
import os | |
import sys | |
import io | |
import subprocess | |
BASE_DIR = os.getcwd() | |
print("" + BASE_DIR) | |
DEBUG_OUTPUT = False | |
def log(string): | |
print("--- " + string) | |
def errorLog(string): | |
print("--- ERROR " + string) | |
def dlog(string): | |
if DEBUG_OUTPUT: | |
print("*** " + string) | |
def findToolCommand(command, paths_to_search, required = False): | |
command_res = command | |
found = False | |
for path in paths_to_search: | |
command_abs = os.path.join(path, command) | |
if os.path.exists(command_abs): | |
command_res = command_abs | |
found = True | |
break; | |
if required and not found: | |
log("WARNING: command " + command + " not found, but required by script") | |
dlog("Found '" + command + "' as " + command_res) | |
return command_res | |
# execute command but return status of command as an integer | |
def executeCommand(command, printCommand = False, quiet = False): | |
printCommand = printCommand or DEBUG_OUTPUT | |
out = None | |
err = None | |
if quiet: | |
out = open(os.devnull, 'w') | |
err = subprocess.STDOUT | |
if printCommand: | |
if DEBUG_OUTPUT: | |
dlog(">>> " + command) | |
else: | |
log(">>> " + command) | |
return subprocess.call(command, shell = True, stdout=out, stderr=err); | |
# Execute command but return output as text | |
def executeCommandWithOutput(command, printCommand = False, quiet = False): | |
printCommand = printCommand or DEBUG_OUTPUT | |
out = None | |
err = None | |
if quiet: | |
out = open(os.devnull, 'w') | |
err = subprocess.STDOUT | |
if printCommand: | |
if DEBUG_OUTPUT: | |
dlog(">>> " + command) | |
else: | |
log(">>> " + command) | |
return os.popen(command).read() | |
def dieIfNonZero(res): | |
if res != 0: | |
raise ValueError("Command returned non-zero status."); | |
# Returns a list of dictionaries whhere each item is a submodule | |
def getHeadsOfSubmodulesIncludingGodRepo(): | |
submoduleRecords = []; | |
output = executeCommandWithOutput(TOOL_COMMAND_GIT + " submodule"); | |
if (len(output) == 0): | |
errorLog("No submodule results found"); | |
return; | |
mainBranchHash = executeCommandWithOutput(TOOL_COMMAND_GIT + " rev-parse HEAD").strip() | |
submoduleRecords.append({"hash": mainBranchHash, "path": "."}); | |
submoduleRecord = output.split('\n'); | |
for submodule in submoduleRecord: | |
submoduleText = submodule.strip(); | |
if (len(submoduleText) == 0): | |
continue; | |
recordDetails = submodule.strip().split(' '); | |
dlog ("submodule hash " + recordDetails[0] + ", path: " + recordDetails[1]); | |
submoduleRecords.append({"hash":recordDetails[0], "path":recordDetails[1]}); | |
return submoduleRecords; | |
def getHeadsOfCurrentBranches(): | |
branchRecords = []; | |
output = executeCommandWithOutput(TOOL_COMMAND_GIT + " for-each-ref --sort=-committerdate refs/heads/ --format='%(objectname) %(refname:short)'").strip(); | |
if (len(output) == 0): | |
errorLog("No branch results found"); | |
return; | |
branchRecord = output.split('\n'); | |
for branch in branchRecord: | |
branchText = branch.strip(); | |
if (len(branchText) == 0): | |
continue; | |
branchDetails = branchText.strip().split(' '); | |
dlog ("branch name " + branchDetails[1] + ", hash: " + branchDetails[0]); | |
branchRecords.append({"hash":branchDetails[0], "name":branchDetails[1]}); | |
return branchRecords; | |
log("Fixing Detached Heads in " + BASE_DIR); | |
paths_to_search = os.environ["PATH"].split(":") + ["/usr/local/bin", "/opt/local/bin", "/usr/bin"] | |
TOOL_COMMAND_GIT = findToolCommand("git", paths_to_search, required = True) | |
# get all repos | |
repos = getHeadsOfSubmodulesIncludingGodRepo(); | |
mainRepoBranchName = ""; | |
# update repos first | |
executeCommand(TOOL_COMMAND_GIT + " submodule update --recursive") | |
# Go through all repos, check the current hash of the branch | |
# If we detect it is a DETACHED HEAD then we check all branches | |
# If we find a branch whose tip commit matches what we're at then perform a git command | |
for repo in repos: | |
os.chdir(os.path.join(BASE_DIR, repo["path"])); | |
currentHead = executeCommandWithOutput(TOOL_COMMAND_GIT + " rev-parse --symbolic-full-name --abbrev-ref HEAD").strip() | |
repoName = repo["path"]; | |
if (repoName == "."): | |
repoName = "God-Repo"; | |
log ("=============================================="); | |
log ("Repo: " + repoName); | |
log ("Hash: '" + repo["hash"] + "'"); | |
log ("Current head: '" + currentHead + "'"); | |
# Is it a detached head? | |
if currentHead == "HEAD": | |
branchRecords = getHeadsOfCurrentBranches(); | |
currentHeadCommitHash = executeCommandWithOutput(TOOL_COMMAND_GIT + " rev-parse HEAD").strip() | |
branchName = ""; | |
branchHash = ""; | |
for branch in branchRecords: | |
log ("checking branch '" + branch["name"] + "', hash: " + branch["hash"]); | |
if branch["hash"] == currentHeadCommitHash: | |
if (branchName == mainRepoBranchName or len(branchName) == 0): | |
branchName = branch["name"]; | |
branchHash = branch["hash"]; | |
if len(branchName) > 0 and len(branchHash) > 0: | |
log("Found match for current hash in branch: " + branchName); | |
# NOTE: pulled out sourcetree specifics so the user interface updates correctly | |
dieIfNonZero(executeCommand(TOOL_COMMAND_GIT + " -c diff.mnemonicprefix=false -c core.quotepath=false -c credential.helper=sourcetree checkout " + branchName)); | |
else: | |
log ("HEAD DETACHED - Did not find a commit on any branch tip for hash " + currentHeadCommitHash); | |
else: | |
log("Repo - HEAD is not detached"); | |
mainRepoBranchName = currentHead; | |
log ("=============================================="); | |
log("") | |
os.chdir(BASE_DIR); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment