class GitRepo: def __init__(self, config: Config, path: str = None) -> None: self.__config: Config = config try: if not path: self.__repo = Repo(config.root_path) else: self.__repo = Repo(path) assert not self.__repo.bare self.origin = self.__repo.remote('origin') except InvalidGitRepositoryError: log_error("Path is not a git repository. Please go to valid git repository!") sys.exit() def pull(self, branch_name: str = None): if not branch_name: branch = self.__repo.active_branch.name else: branch = branch_name try: log_info('Pull changes from origin ...') self.__repo.git.execute("git pull origin " + branch) except GitCommandError: log_error("Pull from origin/" + branch + " on " + self.__repo.working_tree_dir + " is not possible as you might have uncommitted or untracked files. Fix the working tree, and then try again.") if not prompt_yesno_question("Did you fix the issue manually? Resume script?"): self.reset() sys.exit() def reset(self): if(self.__config.cleanup_silently or prompt_yesno_question('Should the repository and file system to be reset automatically?\nThis will reset the entire repository inlcuding latest commits to comply to remote.\nThis will also delete untrackted files!')): # arbitrary 20, but extensive enough to reset all hopefully log_info("Executing reset (git reset --hard HEAD~20)") self.__repo.git.reset('--hard', 'HEAD~20') self.update_and_clean() def update_and_clean(self): log_info("Executing update and cleanup (git pull origin && git submodule update && git clean -fd)") self.pull() self.__repo.git.submodule("update") self.__repo.git.clean("-fd") if not self.is_working_copy_clean(): log_error("Reset and cleanup did not work out. Other branches have local commits not yet pushed:") log_info("\n" + self.__list_unpushed_commits()) sys.exit() def checkout(self, branch_name): log_info("Checkout " + branch_name) self.__repo.git.checkout(branch_name) self.update_and_clean() def commit(self, commit_message: str): try: if self.__list_uncommitted_files() != "": log_info("Committing ...") self.__repo.index.commit("#" + str(self.__config.github_issue_no) + " " + commit_message) else: log_info("Nothing to commit.") except Exception as e: if "no changes added to commit" in str(e): log_info("No File is changed, Nothing to commit..") def push(self, force: bool = False): ''' Boolean return type states, whether to continue process or abort''' if(not force and not self.has_unpushed_commits()): log_info("Nothing to be pushed.") return if (self.__config.test_run or self.__config.debug) and not prompt_yesno_question("[DEBUG] Changes will be pushed now. Continue?"): self.reset() sys.exit() if self.__config.dry_run: log_info_dry('Skipping pushing of changes.') return try: log_info("Pushing to origin/" + self.__repo.active_branch.name + " in " + self.__repo.working_tree_dir + " ...") self.__repo.git.execute("git push origin " + self.__repo.active_branch.name + " --tags") except Exception as e: if "no changes added to commit" in str(e): log_info("No file is changed, nothing to commit.") else: if not prompt_yesno_question("Something went wrong during pushing. Please check if you can perform pushing on your own. Resume the script?"): self.reset() def add(self, files: List[str], consider_as_build_folder_path: bool = True) -> None: files_to_add: List[str] if consider_as_build_folder_path: files_to_add = [os.path.join(self.__config.build_folder, i) for i in files] else: files_to_add = files self.__repo.index.add([i for i in files_to_add if self.__is_tracked_and_dirty(i)]) def add_submodule(self, module: str) -> None: submodule = self.__repo.submodule(module) submodule.binsha = submodule.module().head.commit.binsha self.__repo.index.add([submodule]) def merge(self, source: str, target: str) -> None: if self.__config.dry_run: log_info_dry("Would merge from "+source+" to " + target) return try: self.checkout(target) log_info("Executing git pull...") self.pull() log_info("Merging...") self.__repo.git.execute("git merge " + self.__config.branch_to_be_released) log_info("Adapting automatically generated merge commit message to include issue no.") automatic_commit_message = self.__repo.git.execute("git log -1 --pretty=%B") if "Merge" in automatic_commit_message and str(self.__config.github_issue_no) not in automatic_commit_message: self.__repo.git.execute('git commit --amend -m"#'+str(self.__config.github_issue_no)+' '+automatic_commit_message+'"') except Exception as ex: log_error("Something went wrong, please check if merge conflicts exist and solve them.") if self.__config.debug: print(ex) if not prompt_yesno_question("If there were conflicts you solved and committed, would you like to resume the script?"): self.__repo.git.execute("git merge --abort") self.reset() sys.exit() def update_submodule(self, submodule_path: str) -> None: sm_repo = GitRepo(self.__config, submodule_path) sm_repo.checkout('master') sm_repo.pull() log_info("Changing the "+self.__config.wiki_version_overview_page + " file, updating the version number...") version_decl = self.__config.cobigenwiki_title_name new_version_decl = version_decl+" v"+self.__config.release_version with FileInput(os.path.join(self.__config.wiki_submodule_path, self.__config.wiki_version_overview_page), inplace=True) as file: for line in file: line = re.sub(r''+version_decl+r'\s+v[0-9]\.[0-9]\.[0-9]', new_version_decl, line) sys.stdout.write(line) sm_repo.add([self.__config.wiki_version_overview_page], False) sm_repo.commit("update wiki docs") sm_repo.push() def exists_tag(self, tag_name) -> bool: return tag_name in self.__repo.tags def get_changed_files_of_last_commit(self) -> List[str]: return str(self.__repo.git.execute("git diff HEAD^ HEAD --name-only")).strip().splitlines() def create_tag_on_last_commit(self) -> None: self.__repo.create_tag(self.__config.tag_name) log_info("Git tag " + self.__config.tag_name + " created!") def assure_clean_working_copy(self) -> None: if not self.is_working_copy_clean(True): log_error("Working copy is not clean!") if self.__config.cleanup_silently or prompt_yesno_question("Should I clean the repo for you? This will delete all untracked files and hardly reset the repository!"): self.reset() else: log_info("Please cleanup your working copy first. Then run the script again.") sys.exit() else: log_info("Working copy clean.") def is_working_copy_clean(self, check_all_branches=False) -> bool: return self.__repo.git.execute("git diff --shortstat") == "" and not self.has_uncommitted_files() and not self.has_unpushed_commits() def __list_uncommitted_files(self) -> str: return self.__repo.git.execute("git status --porcelain") def has_uncommitted_files(self) -> bool: return self.__list_uncommitted_files() != "" def __list_unpushed_commits(self) -> str: return self.__repo.git.execute("git log --branches --not --remotes") def has_unpushed_commits(self) -> bool: return self.__list_unpushed_commits() != "" def __is_tracked_and_dirty(self, path: str) -> bool: changed = [item.a_path for item in self.__repo.index.diff(None)] changedAbs = [os.path.abspath(os.path.join(self.__repo.working_tree_dir, item.a_path)) for item in self.__repo.index.diff(None)] log_debug("Untracked and Dirty files: " + str(changed)) if path in changed or path in changedAbs: # modified return True else: return False