def merge_branches(self, branch, to_branch, one_way=True, working_dir='var/release'): if one_way is False: # first merge to branch into the working branch out( 1, blue("Pulling from {} and merging into {}".format( to_branch, branch))) output = self.gitutils.git_merge_all(to_branch, branch.working_dir) if output is False: raise Exception("Aborting!! Could not run shell command :(") # Merge the from branch into the destination branch out( 1, blue("Pulling from {} and merging into {}".format( branch, to_branch))) output = self.gitutils.git_merge_all(branch, to_branch, working_dir) if output is False: raise Exception("Aborting!! Could not run shell command :(") return True
def main(self): """Parse arguments and then call the appropriate function(s).""" parser = argparse.ArgumentParser( description="""Merge one branch (-b) to another (-t)""") parser.add_argument('-b', '--branch', nargs='?', metavar="branch", required=True, help="""branch to merge from""") parser.add_argument('-t', '--to_branch', nargs='?', metavar="to_branch", required=True, help="""branch to merge into""") parser.add_argument( '-o', '--one-way', nargs='?', metavar="one_way", required=False, default="True", help="""Merge branch one way, or both ways if False""") parser.add_argument('-w', '--working-dir', nargs='?', metavar="working_dir", required=False, default=None, help="""Working Directory (to stage the merge)""") args = parser.parse_args() if args.working_dir is None: args.working_dir = self.default_working_dir try: self.merge_branches(args.branch, args.to_branch, args.one_way, args.working_dir) except Exception as e: out(0, red(e))
def main(self): """Parse arguments and then call the appropriate function(s).""" parser = argparse.ArgumentParser(description="""Pull multiple Git Repositories at once, on multiple servers.""") parser.add_argument('-p', '--path', nargs="*", metavar="path", default=None, help="""update all repositories under this directory / path (or the directory itself, if the given directory corresponds to a repo. eg. -p ots /srv/salt )""") parser.add_argument('-b', '--branch', metavar="branch", nargs='?', default=None, help="""check the repo out on the specified branch if it's not on this branch yet.""") parser.add_argument('-f', '--force', nargs='?', default=False, metavar="force pull/checkout", help="""force checkout / pull (discard local changes) if we're changing branches.""") parser.add_argument('-a', '--all', nargs='?', default=False, metavar="recurse through all directories", help="""Recurse through all subdirectories to find git repos.""") parser.add_argument('-r', '--remote', nargs='?', default=None, metavar="remote location for gpull_local.py", help="""Remote location of gpull_local.py, eg /usr/local/bin/gpull_local.py """) # get all aliases and sort them for help convenience all_aliases = sorted( list(self.server_aliases.keys()) + list(self.group_aliases.keys()) ) parser.add_argument('-s', '--servers', nargs="*", metavar="servers", default=None, help="A list of server aliases to update. \n " "List of all available server aliases: {}".format(", ".join(all_aliases))) parser.add_argument('-u', '--remote-user', nargs='?', default=None, metavar="your ssh username", help="""ssh into into git server with this user""") args = parser.parse_args() out(0, (yellow(bold("gpull") + ": remotely pull git repos"))) if args.servers is not None: pw = getpass.getpass("Your ssh password:") # Prompt user for ssh password else: pw = None # No password needed to update your local folders gitutils = git_utils.GitUtils() gitutils.remote_pull(args.path, args.branch, args.force, args.servers, args.remote_user, pw, args.all, args.remote)
def email_changes(self, recipient, name): """ Send an email if branches changed remotely :param recipient: string :param name: string :return: void """ hostname = socket.gethostname() sender = self.email_from if name is None: name = "Someone" msg = name + " switched branches on " + hostname + ":" if self.branch_changes and len(self.branch_changes) > 0: for change in self.branch_changes: msg += "\n" + "-" * 60 msg += "\n" + "Repository: " + change[0] msg += "\n" + "From: " + change[1] msg += "\n" + "To: " + change[2] msg += "\n" + "-" * 60 # Create a text/plain message msg = MIMEText(msg) # me == the sender's email address # you == the recipient's email address msg['Subject'] = 'Repository branch change on ' + hostname msg['From'] = sender msg['To'] = recipient # Send the message via our own SMTP server, but don't include the # envelope header. try: s = smtplib.SMTP(self.email_host) s.sendmail(sender, [recipient], msg.as_string()) s.quit() except Exception as e: print(e) out( 0, red("Error sending email:\nMessage: " + str(e) + "\n") + bold("Email Content:\n") + msg.as_string())
def email_changes(self, recipient, name): """ Send an email if branches changed remotely :param recipient: string :param name: string :return: void """ hostname = socket.gethostname() sender = self.email_from if name is None: name = "Someone" msg = name + " switched branches on " + hostname + ":" if self.branch_changes and len(self.branch_changes) > 0: for change in self.branch_changes: msg += "\n" + "-" * 60 msg += "\n" + "Repository: "+change[0] msg += "\n" + "From: "+change[1] msg += "\n" + "To: "+change[2] msg += "\n" + "-" * 60 # Create a text/plain message msg = MIMEText(msg) # me == the sender's email address # you == the recipient's email address msg['Subject'] = 'Repository branch change on ' + hostname msg['From'] = sender msg['To'] = recipient # Send the message via our own SMTP server, but don't include the # envelope header. try: s = smtplib.SMTP(self.email_host) s.sendmail(sender, [recipient], msg.as_string()) s.quit() except Exception as e: print(e) out(0, red("Error sending email:\nMessage: "+str(e)+"\n")+bold("Email Content:\n")+msg.as_string())
def update_directory(self, dir_path, dir_name): """First, make sure the specified object is actually a directory, then determine whether the directory is a git repo on its own or a directory of git repositories. If the former, update the single repository; if the latter, update all repositories contained within.""" dir_long_name = "{} '{}'".format('directory', bold(dir_path)) if not self.is_valid_directory(dir_path): out(0, red(dir_long_name + " is not a valid directory")) return False if self.directory_is_git_repo(dir_path): out( 0, yellow(dir_long_name.capitalize()) + yellow(" is a git repository:")) self.update_repository(dir_path, dir_name) elif self.all_dirs is False: # get the repos from git_repos_config and loop through them. for repo_name in self.config.repositories: repo_path = os.path.join(dir_path, repo_name) if self.directory_is_git_repo(repo_path): self.update_repository(repo_path, repo_name) else: repositories = [] for item in os.listdir(dir_path): repo_path = os.path.join(dir_path, item) repo_name = os.path.join(dir_name, item) if self.directory_is_git_repo( repo_path): # filter out non-repositories repositories.append((repo_path, repo_name)) num_of_repos = len(repositories) if num_of_repos == 1: out( 0, yellow(dir_long_name.capitalize()) + yellow(" contains 1 git repository:")) else: out( 0, yellow(dir_long_name.capitalize()) + yellow( " contains {} git repositories:".format(num_of_repos))) repositories.sort() # go alphabetically instead of randomly for repo_path, repo_name in repositories: self.update_repository(repo_path, repo_name)
def update_directory(self, dir_path, dir_name): """First, make sure the specified object is actually a directory, then determine whether the directory is a git repo on its own or a directory of git repositories. If the former, update the single repository; if the latter, update all repositories contained within.""" dir_long_name = "{} '{}'".format('directory', bold(dir_path)) if not self.is_valid_directory(dir_path): out(0, red(dir_long_name + " is not a valid directory")) return False if self.directory_is_git_repo(dir_path): out(0, yellow(dir_long_name.capitalize()) + yellow(" is a git repository:")) self.update_repository(dir_path, dir_name) elif self.all_dirs is False: # get the repos from git_repos_config and loop through them. for repo_name in self.config.repositories: repo_path = os.path.join(dir_path, repo_name) if self.directory_is_git_repo(repo_path): self.update_repository(repo_path, repo_name) else: repositories = [] for item in os.listdir(dir_path): repo_path = os.path.join(dir_path, item) repo_name = os.path.join(dir_name, item) if self.directory_is_git_repo(repo_path): # filter out non-repositories repositories.append((repo_path, repo_name)) num_of_repos = len(repositories) if num_of_repos == 1: out(0, yellow(dir_long_name.capitalize()) + yellow(" contains 1 git repository:")) else: out(0, yellow(dir_long_name.capitalize()) + yellow(" contains {} git repositories:".format(num_of_repos))) repositories.sort() # go alphabetically instead of randomly for repo_path, repo_name in repositories: self.update_repository(repo_path, repo_name)
if one_way is False: # first merge to branch into the working branch out( 1, blue("Pulling from {} and merging into {}".format( to_branch, branch))) output = self.gitutils.git_merge_all(to_branch, branch.working_dir) if output is False: raise Exception("Aborting!! Could not run shell command :(") # Merge the from branch into the destination branch out( 1, blue("Pulling from {} and merging into {}".format( branch, to_branch))) output = self.gitutils.git_merge_all(branch, to_branch, working_dir) if output is False: raise Exception("Aborting!! Could not run shell command :(") return True if __name__ == "__main__": try: StagePusher = GitMergeAll() StagePusher.main() except KeyboardInterrupt: out(0, "Stopped by user.")
list(self.group_aliases.keys()) ) parser.add_argument('-s', '--servers', nargs="*", metavar="servers", default=None, help="A list of server aliases to update. \n " "List of all available server aliases: {}".format(", ".join(all_aliases))) parser.add_argument('-u', '--remote-user', nargs='?', default=None, metavar="your ssh username", help="""ssh into into git server with this user""") args = parser.parse_args() out(0, (yellow(bold("gpull") + ": remotely pull git repos"))) if args.servers is not None: pw = getpass.getpass("Your ssh password:"******"__main__": try: GitPull = GitPull() GitPull.main() except KeyboardInterrupt: out(0, "Stopped by user.")
def main(self): """Parse arguments and then call the appropriate function(s).""" parser = argparse.ArgumentParser( description= """Pull multiple Git Repositories at once, on multiple servers.""") parser.add_argument( '-p', '--path', nargs="*", metavar="path", default=None, help= """update all repositories under this directory / path (or the directory itself, if the given directory corresponds to a repo. eg. -p ots /srv/salt )""" ) parser.add_argument( '-b', '--branch', metavar="branch", nargs='?', default=None, help= """check the repo out on the specified branch if it's not on this branch yet.""" ) parser.add_argument( '-f', '--force', nargs='?', default=False, metavar="force pull/checkout", help= """force checkout / pull (discard local changes) if we're changing branches.""" ) parser.add_argument( '-a', '--all', nargs='?', default=False, metavar="recurse through all directories", help="""Recurse through all subdirectories to find git repos.""") parser.add_argument( '-r', '--remote', nargs='?', default=None, metavar="remote location for gpull_local.py", help= """Remote location of gpull_local.py, eg /usr/local/bin/gpull_local.py """ ) # get all aliases and sort them for help convenience all_aliases = sorted( list(self.server_aliases.keys()) + list(self.group_aliases.keys())) parser.add_argument('-s', '--servers', nargs="*", metavar="servers", default=None, help="A list of server aliases to update. \n " "List of all available server aliases: {}".format( ", ".join(all_aliases))) parser.add_argument('-u', '--remote-user', nargs='?', default=None, metavar="your ssh username", help="""ssh into into git server with this user""") args = parser.parse_args() out(0, (yellow(bold("gpull") + ": remotely pull git repos"))) if args.servers is not None: pw = getpass.getpass( "Your ssh password:") # Prompt user for ssh password else: pw = None # No password needed to update your local folders gitutils = git_utils.GitUtils() gitutils.remote_pull(args.path, args.branch, args.force, args.servers, args.remote_user, pw, args.all, args.remote)
def update_repository(self, repo_path, repo_name): """ Update a single git repository by pulling from the remote. :param repo_path: :param repo_name: :return: bool """ out(1, bold(repo_name) + ":") # cd into our folder so git commands target the correct repo os.chdir(repo_path) try: # what branch are we on? curr_branch = self.exec_shell("git rev-parse --abbrev-ref HEAD") except subprocess.CalledProcessError as e: curr_branch = False out(2, yellow("warning: ") + e.output.decode('UTF-8')) # strip out spaces, new lines etc if curr_branch: curr_branch = curr_branch.strip(' \t\b\n\r') try: # check if there is anything to pull, but don't do it yet dry_fetch = self.exec_shell("git fetch --dry-run") except subprocess.CalledProcessError as e: out(2, red("Error: ") + "cannot fetch; do you have a remote repository configured correctly?\n" + e.output.decode('UTF-8')) return # if a specific branch was passed in, then make sure that's what we're on. if self.branch and curr_branch: branch = self.branch # if we're not on the required branch, then check it out. if branch != curr_branch: out(2, yellow("branch to switch from: " + curr_branch + "\nbranch to switch to: " + branch)) try: # need to fetch first git_fetch_txt = self.exec_shell("git fetch") if git_fetch_txt: out(2, yellow(git_fetch_txt.strip())) except subprocess.CalledProcessError as e: out(2, red("Could not fetch: \n" + e.output.decode('UTF-8'))) return False # get list of remote branches remote_branches = self.exec_shell("git branch -a") # see if the desired branch exists remotely, otherwise skip this process. if "remotes/origin/"+branch in remote_branches: out(2, green('Attempting to switch branch from ' + curr_branch + ' to ' + branch)) if self.force: git_checkout_txt = self.exec_shell("git checkout -f " + branch) out(2, yellow(git_checkout_txt)) else: try: git_checkout_txt = self.exec_shell("git checkout " + branch) out(2, yellow(git_checkout_txt)) except subprocess.CalledProcessError as e: out(2, red("Could not check out branch: \n" + e.output.decode('UTF-8'))) return False self.branch_changes.append([repo_name, curr_branch, branch]) # set curr_branch to the branch we just changed to. curr_branch = branch else: out(2, red("branch {} does not exist. skipping checkout.".format(branch, repo_path))) try: last_commit = self.exec_shell("git log -n 1 --pretty=\"%ar\"") last_commit = last_commit.strip(' \t\b\n\r') except subprocess.CalledProcessError: last_commit = "never" # couldn't get a log, so no commits if not dry_fetch: # try git status, just to make sure a fetch didn't happen without a pull: status = self.exec_shell("git status -uno") if "Your branch is behind" not in status: out(2, blue("No new changes.") + " Last commit was {}.".format(last_commit)) return False # stuffs have happened! out(2, "There are new changes upstream...") status = self.exec_shell("git status") if not status.endswith("nothing to commit (working directory clean)"): out(2, red("Warning: ") + "you have uncommitted changes in this repository!") if self.force: out(2, red("Since force is enabled, I will now reset your branch:")) reset_result = self.exec_shell("git reset --hard HEAD") out(2, green(reset_result)) out(2, green("Pulling changes...")) try: result = self.exec_shell("git pull") except subprocess.CalledProcessError as e: try: # if pull fails to pull because remote branch is not configured correctly: if 'You asked me to pull without telling me which branch' in e.output or \ 'Please specify which branch you want to merge with' in e.output: set_remote_branch = self.exec_shell( "git branch --set-upstream-to {} origin/{}".format(curr_branch, curr_branch)) out(2, green(set_remote_branch)) result = self.exec_shell("git pull") elif self.force and 'Your local changes to the following files would be overwritten' in e.output: reset_result = self.exec_shell("git reset --hard HEAD") out(2, green(reset_result)) result = self.exec_shell("git pull") else: out(2, red(e.output)) return False except subprocess.CalledProcessError as e: out(2, red(e.output)) return False if result: if 'Already up-to-date' in result: out(2, "No new changes in your branch. However, upstream the following changes happened:") else: out(2, "The following changes were made {}:".format(last_commit)) out(2, blue(result))
def update_repository(self, repo_path, repo_name): """ Update a single git repository by pulling from the remote. :param repo_path: :param repo_name: :return: bool """ out(1, bold(repo_name) + ":") # cd into our folder so git commands target the correct repo os.chdir(repo_path) try: # what branch are we on? curr_branch = self.exec_shell("git rev-parse --abbrev-ref HEAD") except subprocess.CalledProcessError as e: curr_branch = False out(2, yellow("warning: ") + e.output.decode('UTF-8')) # strip out spaces, new lines etc if curr_branch: curr_branch = curr_branch.strip(' \t\b\n\r') try: # check if there is anything to pull, but don't do it yet dry_fetch = self.exec_shell("git fetch --dry-run") except subprocess.CalledProcessError as e: out( 2, red("Error: ") + "cannot fetch; do you have a remote repository configured correctly?\n" + e.output.decode('UTF-8')) return # if a specific branch was passed in, then make sure that's what we're on. if self.branch and curr_branch: branch = self.branch # if we're not on the required branch, then check it out. if branch != curr_branch: out( 2, yellow("branch to switch from: " + curr_branch + "\nbranch to switch to: " + branch)) try: # need to fetch first git_fetch_txt = self.exec_shell("git fetch") if git_fetch_txt: out(2, yellow(git_fetch_txt.strip())) except subprocess.CalledProcessError as e: out(2, red("Could not fetch: \n" + e.output.decode('UTF-8'))) return False # get list of remote branches remote_branches = self.exec_shell("git branch -a") # see if the desired branch exists remotely, otherwise skip this process. if "remotes/origin/" + branch in remote_branches: out( 2, green('Attempting to switch branch from ' + curr_branch + ' to ' + branch)) if self.force: git_checkout_txt = self.exec_shell("git checkout -f " + branch) out(2, yellow(git_checkout_txt)) else: try: git_checkout_txt = self.exec_shell( "git checkout " + branch) out(2, yellow(git_checkout_txt)) except subprocess.CalledProcessError as e: out( 2, red("Could not check out branch: \n" + e.output.decode('UTF-8'))) return False self.branch_changes.append( [repo_name, curr_branch, branch]) # set curr_branch to the branch we just changed to. curr_branch = branch else: out( 2, red("branch {} does not exist. skipping checkout.". format(branch, repo_path))) try: last_commit = self.exec_shell("git log -n 1 --pretty=\"%ar\"") last_commit = last_commit.strip(' \t\b\n\r') except subprocess.CalledProcessError: last_commit = "never" # couldn't get a log, so no commits if not dry_fetch: # try git status, just to make sure a fetch didn't happen without a pull: status = self.exec_shell("git status -uno") if "Your branch is behind" not in status: out( 2, blue("No new changes.") + " Last commit was {}.".format(last_commit)) return False # stuffs have happened! out(2, "There are new changes upstream...") status = self.exec_shell("git status") if not status.endswith("nothing to commit (working directory clean)"): out( 2, red("Warning: ") + "you have uncommitted changes in this repository!") if self.force: out( 2, red("Since force is enabled, I will now reset your branch:" )) reset_result = self.exec_shell("git reset --hard HEAD") out(2, green(reset_result)) out(2, green("Pulling changes...")) try: result = self.exec_shell("git pull") except subprocess.CalledProcessError as e: try: # if pull fails to pull because remote branch is not configured correctly: if 'You asked me to pull without telling me which branch' in e.output or \ 'Please specify which branch you want to merge with' in e.output: set_remote_branch = self.exec_shell( "git branch --set-upstream-to {} origin/{}".format( curr_branch, curr_branch)) out(2, green(set_remote_branch)) result = self.exec_shell("git pull") elif self.force and 'Your local changes to the following files would be overwritten' in e.output: reset_result = self.exec_shell("git reset --hard HEAD") out(2, green(reset_result)) result = self.exec_shell("git pull") else: out(2, red(e.output)) return False except subprocess.CalledProcessError as e: out(2, red(e.output)) return False if result: if 'Already up-to-date' in result: out( 2, "No new changes in your branch. However, upstream the following changes happened:" ) else: out(2, "The following changes were made {}:".format(last_commit)) out(2, blue(result))