def merge(self, src, op_cb=None): """Merges the divergent changes of the src branch onto this one.""" self._check_is_current() self._check_op_not_in_progress() result, unused_ff_conf = self.gl_repo.git_repo.merge_analysis(src.target) if result & pygit2.GIT_MERGE_ANALYSIS_UP_TO_DATE: raise GlError('No commits to merge') try: git.merge(src, '--no-ff') except ErrorReturnCode as e: err = stderr(e) if not 'stash' in err: raise GlError(stdout(e) + err) if op_cb and op_cb.save: op_cb.save() git.stash.save('--', _stash_msg_merge(self)) try: git.merge(src, '--no-ff') except ErrorReturnCode as e: raise GlError(stdout(e) + stderr(e)) restore_fn = op_cb.restore_ok if op_cb else None self._safe_restore(_stash_msg_merge, restore_fn=restore_fn) self._state_cleanup()
def commit(self, page, message='', author=None, parent=None, extra_path=None): path = page.path paths_to_commit = [path] if extra_path: paths_to_commit.append(extra_path) kwargs = {} User = get_user_model() if isinstance(author, User) and author.is_authenticated(): kwargs['author'] = u"%s <%s>" % (author.get_full_name() or author.username, author.email) elif isinstance(author, six.string_types): kwargs['author'] = author try: there_were_changes = parent and parent != self.last_version(page) status = git.status('--porcelain', path).stdout.decode('utf8')[:2] if parent and status != "UU": git.stash() git.checkout('--detach', parent) try: git.stash('pop') except: git.checkout('--theirs', path) if status == 'UU': # See http://stackoverflow.com/a/8062976/811740 kwargs['i'] = True git.add(path) git_commit_cmd = git.commit.bake(allow_empty=True, allow_empty_message=True, m=message, **kwargs) git_commit_cmd('--', *paths_to_commit) last = self.last_version(page) if parent and status != "UU": git.checkout('master') git.merge(last) except ErrorReturnCode as e: # TODO: make this more robust! error = e.stdout.decode('utf8') if 'CONFLICT' in error: # For '-i' attribute see http://stackoverflow.com/q/5827944/811740 git_commit_cmd = git.commit.bake(allow_empty=True, allow_empty_message=True, m=_('Merged with conflict'), i=True, **kwargs) git_commit_cmd('--', *paths_to_commit) raise Page.EditionConflict( _('Automatic merge failed. Please, fix the conflict and save the page.' )) else: raise return there_were_changes
def merge(self, src, op_cb=None): """Merges the divergent changes of the src branch onto this one.""" self._check_is_current() self._check_op_not_in_progress() result, unused_ff_conf = self.gl_repo.git_repo.merge_analysis( src.target) if result & pygit2.GIT_MERGE_ANALYSIS_UP_TO_DATE: raise GlError('No commits to merge') try: git.merge(src, '--no-ff') except ErrorReturnCode as e: err = stderr(e) if not 'stash' in err: raise GlError(stdout(e) + err) if op_cb and op_cb.save: op_cb.save() git.stash.save('--', _stash_msg_merge(self)) try: git.merge(src, '--no-ff') except ErrorReturnCode as e: raise GlError(stdout(e) + stderr(e)) self._state_cleanup() restore_fn = op_cb.restore_ok if op_cb else None self._safe_restore(_stash_msg_merge, restore_fn=restore_fn)
def add_to_blacklist(self, items_to_blacklist, username, code_permissions): # Check if we're on master if git("rev-parse", "--abbrev-ref", "HEAD").strip() != "master": return (False, "Not currently on master.") # Check that we're up-to-date with origin (GitHub) git.remote.update() if git("rev-parse", "refs/remotes/origin/master").strip() != git("rev-parse", "master").strip(): return (False, "HEAD isn't at tip of origin's master branch") # Check that blacklisted_websites.txt isn't modified locally. That could get ugly fast if "blacklisted_websites.txt" in git.status(): # Also ugly return (False, "blacklisted_websites.txt modified locally. This is probably bad.") # Store current commit hash current_commit = git("rev-parse", "HEAD").strip() # Add items to file with open("blacklisted_websites.txt", "a+") as blacklisted_websites: last_character = blacklisted_websites.read()[-1:] if last_character != "\n": blacklisted_websites.write("\n") blacklisted_websites.write("\n".join(items_to_blacklist) + "\n") # Checkout a new branch (mostly unnecessary, but may help if we create PRs in the future branch = "auto-blacklist-{0}".format(str(time.time())) git.checkout("-b", branch) # Clear HEAD just in case git.reset("HEAD") git.add("blacklisted_websites.txt") git.commit("-m", "Auto blacklist of {0} by {1} --autopull".format(", ".join(items_to_blacklist), username)) if code_permissions: git.checkout("master") git.merge(branch) git.push() else: git.push("origin", branch) git.checkout("master") if GlobalVars.github_username is None or GlobalVars.github_password is None: return (False, "tell someone to set a GH password") payload = {"title": "{0}: Blacklist {1}".format(username, ", ".join(items_to_blacklist)), "body": "{0} requests blacklist of domains: \n\n - {1}".format(username, "\n - ".join(items_to_blacklist)), "head": branch, "base": "master"} response = requests.post("https://api.github.com/repos/Charcoal-SE/SmokeDetector/pulls", auth=HTTPBasicAuth(GlobalVars.github_username, GlobalVars.github_password), data=json.dumps(payload)) print(response.json()) return (True, "You don't have code privileges, but I've [created a pull request for you]({0}).".format(response.json()["html_url"])) git.checkout(current_commit) # Return to old commit to await CI. This will make Smokey think it's in reverted mode if it restarts if not code_permissions: return (False, "Unable to perform action due to lack of code-level permissions. [Branch pushed](https://github.com/Charcoal-SE/SmokeDetector/tree/{0}), PR at your leisure.".format(branch)) return (True, "Blacklisted {0} - the entry will be applied via autopull if CI succeeds.".format(", ".join(items_to_blacklist)))
def update(conf, args): '''Apply updates from the upstream repository.''' print('Checking for updates...') # fetch changes from the canonical repo git.fetch(constants.GIT_REMOTE, no_tags=True, quiet=True) # get a list of the commit messages for the incoming changes updates = git('--no-pager', 'log', '..FETCH_HEAD', oneline=True) updates = [tuple(m.split(None, 1)) for m in updates.splitlines()] # print out a list of the incoming updates if len(updates) > 0: print('Available updates:') max_updates = 10 for commit, msg in updates[:max_updates]: print(color.yellow('*'), msg) # print a special message if too many updates are available if len(updates) > max_updates: print('...and', color.green(len(updates) - max_updates), 'more!') print('Run `git log ..FETCH_HEAD` to see the full list') # bail if we have uncommitted changes (git exits non-0 in this case) if git.diff(exit_code=True, quiet=True, _ok_code=(0, 1)).exit_code != 0: raise ValueError('The repository has uncommitted changes. Handle them, ' 'then try updating again.') print('Applying the update...') # stash _all_ changes to the repo git.stash(include_untracked=True, all=True, quiet=True) # squash all the fetched commits together and merge them into master git.merge('@{u}', squash=True, quiet=True) # add a nice update commit that includes the latest upstream commit hash commit_message = 'Update dotparty to %s' % updates[0][0] git.commit(m=commit_message, quiet=True) # TODO: if squash merge failed, roll back to the pre-update state and # complain with instructions for the user to do their own update. # un-stash all our old changes git.stash('pop', quiet=True) # push our changes back up to the remote git.push(quiet=True) print('Update successful!') else: print('Already up-to-date!')
def next(self, merge=False): current = self.current() try: switch_to = self.points()[self.points().index(current) + 1] except IndexError: # we've reached the end of the list; switch to master switch_to = 'master' git.checkout(switch_to) if merge: git.merge(current)
def reply_comment_email(from_email, subject, message): try: m = re.search('\[(\d+)\-(\w+)\]', subject) branch_name = m.group(1) article = m.group(2) message = decode_best_effort(message) # safe logic: no answer or unknown answer is a go for publishing if message[:2].upper() == 'NO': logger.info('discard comment: %s' % branch_name) email_body = get_template('drop_comment').render(original=message) mail(pecosys.get_config('post', 'from_email'), pecosys.get_config('post', 'to_email'), 'Re: ' + subject, email_body) else: if pecosys.get_config("git", "disabled"): logger.debug("GIT usage disabled (debug mode)") else: git.merge(branch_name) if pecosys.get_config("git", "remote"): git.push() logger.info('commit comment: %s' % branch_name) # execute post-commit command asynchronously postcommand = pecosys.get_config('post', 'postcommand') if postcommand: executor.execute(postcommand) # send approval confirmation email to admin email_body = get_template('approve_comment').render(original=message) mail(pecosys.get_config('post', 'from_email'), pecosys.get_config('post', 'to_email'), 'Re: ' + subject, email_body) # notify reader once comment is published reader_email, article_url = get_email_metadata(message) if reader_email: notify_reader(reader_email, article_url) # notify subscribers every time a new comment is published notify_subscribers(article) if pecosys.get_config("git", "disabled"): logger.debug("GIT usage disabled (debug mode)") else: git.branch("-D", branch_name) except: logger.exception("new email failure")
def next(self, merge=False): current = self.current() try: switch_to = self.points()[ self.points().index(current) + 1 ] except IndexError: # we've reached the end of the list; switch to master switch_to = 'master' git.checkout(switch_to) if merge: git.merge(current)
def setUp(self): super(TestFileResolve, self).setUp() # Generate a conflict git.checkout(b='branch') utils_lib.write_file(FP_IN_CONFLICT, contents='branch') utils_lib.write_file(DIR_FP_IN_CONFLICT, contents='branch') git.add(FP_IN_CONFLICT, DIR_FP_IN_CONFLICT) git.commit(FP_IN_CONFLICT, DIR_FP_IN_CONFLICT, m='branch') git.checkout('master') utils_lib.write_file(FP_IN_CONFLICT, contents='master') utils_lib.write_file(DIR_FP_IN_CONFLICT, contents='master') git.add(FP_IN_CONFLICT, DIR_FP_IN_CONFLICT) git.commit(FP_IN_CONFLICT, DIR_FP_IN_CONFLICT, m='master') git.merge('branch', _ok_code=[1])
def commit(self, page, message="", author=None, parent=None, extra_path=None): path = page.path paths_to_commit = [path] if extra_path: paths_to_commit.append(extra_path) kwargs = {} if isinstance(author, User) and author.is_authenticated(): kwargs["author"] = u"%s <%s>" % (author.get_full_name() or author.username, author.email) elif isinstance(author, six.string_types): kwargs["author"] = author try: there_were_changes = parent and parent != self.last_version(page) status = git.status("--porcelain", path).stdout.decode("utf8")[:2] if parent and status != "UU": git.stash() git.checkout("--detach", parent) try: git.stash("pop") except: git.checkout("--theirs", path) if status == "UU": # See http://stackoverflow.com/a/8062976/811740 kwargs["i"] = True git.add(path) git_commit_cmd = git.commit.bake(allow_empty=True, allow_empty_message=True, m=message, **kwargs) git_commit_cmd("--", *paths_to_commit) last = self.last_version(page) if parent and status != "UU": git.checkout("master") git.merge(last) except ErrorReturnCode as e: # TODO: make this more robust! error = e.stdout.decode("utf8") if "CONFLICT" in error: # For '-i' attribute see http://stackoverflow.com/q/5827944/811740 git_commit_cmd = git.commit.bake( allow_empty=True, allow_empty_message=True, m=_("Merged with conflict"), i=True, **kwargs ) git_commit_cmd("--", *paths_to_commit) raise Page.EditionConflict(_("Automatic merge failed. Please, fix the conflict and save the page.")) else: raise return there_were_changes
def commit(self, page, message='', author=None, parent=None, extra_path=None): path = page.path paths_to_commit = [path] if extra_path: paths_to_commit.append(extra_path) kwargs = {} User = get_user_model() if isinstance(author, User) and is_authenticated(author): kwargs['author'] = u"%s <%s>" % (author.get_full_name() or author.username, author.email) elif isinstance(author, six.string_types): kwargs['author'] = author try: there_were_changes = parent and parent != self.last_version(page) status = git.status('--porcelain', path).stdout.decode('utf8')[:2] if parent and status != "UU": git.stash() git.checkout('--detach', parent) try: git.stash('pop') except: git.checkout('--theirs', path) if status == 'UU': # See http://stackoverflow.com/a/8062976/811740 kwargs['i'] = True git.add(path) git_commit_cmd = git.commit.bake(allow_empty=True, allow_empty_message=True, m=message, **kwargs) git_commit_cmd('--', *paths_to_commit) last = self.last_version(page) if parent and status != "UU": git.checkout('master') git.merge(last) except ErrorReturnCode as e: # TODO: make this more robust! error = e.stdout.decode('utf8') if 'CONFLICT' in error: # For '-i' attribute see http://stackoverflow.com/q/5827944/811740 git_commit_cmd = git.commit.bake(allow_empty=True, allow_empty_message=True, m=_('Merged with conflict'), i=True, **kwargs) git_commit_cmd('--', *paths_to_commit) raise Page.EditionConflict(_('Automatic merge failed. Please, fix the conflict and save the page.')) else: raise return there_were_changes
def merge_and_check(base, head): """Merge <head> into <base>, then run some tests. Only modifies the working tree---doesn't actually create a merge commit. Resets and cleans the repo and leaves it in headless mode. Raises sh.ErrorReturnCode if the merge or the tests fail. """ # Make sure we're up to date git.fetch() # Make sure we can do a clean checkout git.reset(hard=True) git.clean('-dfx') git.checkout('origin/' + base) # Merge the working tree, but don't modify the index git.merge('origin/' + head, no_commit=True) # Check the PR! check()
def merge(self, branch, base_branch="master"): """ Merge the the given WIP branch to master (or base_branch, if specified) If the merge fails, the merge will be aborted and then a MergeException will be thrown. The message of the MergeException will be the "git status" output, so details about merge conflicts can be determined. """ os.chdir(self.repo) current_branch = self.current_branch() if current_branch != base_branch: git.checkout(base_branch) # Always create a merge commit, even if we could fast forward, so we know # when merges occured try: merge_output = git.merge("--no-ff", branch) except sh.ErrorReturnCode: # attempt to reset things so other operations can # continue output = git.status() git.merge("--abort") # re-raise the exception so other code can decide # what to do with it raise MergeException(output) # the merge succeeded, so remove the local WIP branch git.branch("-d", branch) new_sha = git("rev-parse", "HEAD") return new_sha.strip()
def commit(self, page, message='', author=None, parent=None): path = page.path kwargs = {} if isinstance(author, User) and author.is_authenticated(): kwargs['author'] = u"%s <%s>" % (author.get_full_name() or author.username, author.email) elif isinstance(author, six.string_types): kwargs['author'] = author try: there_were_changes = parent and parent != self.last_version(page) status = git.status('--porcelain', path).stdout.decode('utf8')[:2] if parent and status != "UU": git.stash() git.checkout('--detach', parent) try: git.stash('pop') except: git.checkout('--theirs', path) if status == 'UU': # See http://stackoverflow.com/a/8062976/811740 kwargs['i'] = True git.add(path) git.commit(path, allow_empty_message=True, m=message, **kwargs) last = self.last_version(page) if parent and status != "UU": git.checkout('master') git.merge(last) except ErrorReturnCode as e: # TODO: make this more robust! error = e.stdout.decode('utf8') if 'CONFLICT' in error: raise Page.EditionConflict(_('Automatic merge failed. Please, fix the conflict and save the page.')) else: raise return there_were_changes
def merge(self, branch, base_branch="master"): """ Merge the the given WIP branch to master (or base_branch, if specified) If the merge fails, the merge will be aborted and then a MergeException will be thrown. The message of the MergeException will be the "git status" output, so details about merge conflicts can be determined. """ os.chdir(self.repo) current_branch = self.current_branch() if current_branch != base_branch: git.checkout(base_branch) # Always create a merge commit, even if we could fast forward, so we know # when merges occured try: merge_output = git.merge("--no-ff", branch) except sh.ErrorReturnCode: # attempt to reset things so other operations can # continue output = git.status() git.merge("--abort") # re-raise the exception so other code can decide # what to do with it raise MergeException(output) # the merge succeeded, so remove the local WIP branch git.branch("-d", branch) new_sha = git("rev-parse","HEAD") return new_sha.strip()
def abort_merge(self): if not self.merge_in_progress: raise GlError('No merge in progress, nothing to abort') git.merge(abort=True)
def add_to_blacklist(self, **kwargs): blacklist = kwargs.get("blacklist", "website") items_to_blacklist = kwargs.get("items_to_blacklist", []) username = kwargs.get("username", "") chat_profile_link = kwargs.get("chat_profile_link", "http://chat.stackexchange.com/users") code_permissions = kwargs.get("code_permissions", False) if blacklist == "website": blacklist_file_name = "blacklisted_websites.txt" elif blacklist == "keyword": blacklist_file_name = "bad_keywords.txt" elif blacklist == "username": blacklist_file_name = "blacklisted_usernames.txt" # Check if we're on master if git("rev-parse", "--abbrev-ref", "HEAD").strip() != "master": return (False, "Not currently on master.") # Check that we're up-to-date with origin (GitHub) git.remote.update() if git("rev-parse", "refs/remotes/origin/master").strip() != git( "rev-parse", "master").strip(): return (False, "HEAD isn't at tip of origin's master branch") # Check that blacklisted_websites.txt isn't modified locally. That could get ugly fast if blacklist_file_name in git.status(): # Also ugly return (False, "{0} modified locally. This is probably bad.".format( blacklist_file_name)) # Store current commit hash current_commit = git("rev-parse", "HEAD").strip() # Add items to file with open(blacklist_file_name, "a+") as blacklist_file: last_character = blacklist_file.read()[-1:] if last_character != "\n": blacklist_file.write("\n") blacklist_file.write("\n".join(items_to_blacklist) + "\n") # Checkout a new branch (for PRs for non-code-privileged people) branch = "auto-blacklist-{0}".format(str(time.time())) git.checkout("-b", branch) # Clear HEAD just in case git.reset("HEAD") git.add(blacklist_file_name) git.commit( "-m", u"Auto blacklist of {0} by {1} --autopull".format( ", ".join(items_to_blacklist), username)) if code_permissions: git.checkout("master") git.merge(branch) git.push() else: git.push("origin", branch) git.checkout("master") if GlobalVars.github_username is None or GlobalVars.github_password is None: return (False, "Tell someone to set a GH password") list_of_domains = "" for domain in range(len(items_to_blacklist)): list_of_domains += "\n - {0} - [MS search](https://metasmoke.erwaysoftware.com/search?utf8=%E2%9C%93&body_is_regex=1&body={0})".format( items_to_blacklist[domain]) payload = { "title": "{0}: Blacklist {1}".format(username, ", ".join(items_to_blacklist)), "body": "[{0}]({1}) requests the blacklist of the following {2}(s): \n{3}\n<!-- METASMOKE-BLACKLIST {4} -->" .format(username, chat_profile_link, blacklist, list_of_domains, "|".join(items_to_blacklist)), "head": branch, "base": "master" } response = requests.post( "https://api.github.com/repos/Charcoal-SE/SmokeDetector/pulls", auth=HTTPBasicAuth(GlobalVars.github_username, GlobalVars.github_password), data=json.dumps(payload)) print(response.json()) return ( True, "You don't have code privileges, but I've [created a pull request for you]({0})." .format(response.json()["html_url"])) git.checkout( current_commit ) # Return to old commit to await CI. This will make Smokey think it's in reverted mode if it restarts return ( True, "Blacklisted {0} - the entry will be applied via autopull if CI succeeds." .format(", ".join(items_to_blacklist)))
def add_to_blacklist(**kwargs): blacklist = kwargs.get("blacklist", "") item_to_blacklist = kwargs.get("item_to_blacklist", "") username = kwargs.get("username", "") chat_profile_link = kwargs.get("chat_profile_link", "http://chat.stackexchange.com/users") code_permissions = kwargs.get("code_permissions", False) # Make sure git credentials are set up if git.config("--global", "--get", "user.name") == "": return (False, "Tell someone to run `git config --global user.name \"SmokeDetector\"`") if git.config("--global", "--get", "user.name") == "": return (False, "Tell someone to run `git config --global user.email \"[email protected]\"`") if blacklist == "": # If we broke the code, and this isn't assigned, error out before doing anything, but do # so gracefully with a nice error message. return (False, "Programming Error - Critical information missing for GitManager: blacklist") if item_to_blacklist == "": # If we broke the code, and this isn't assigned, error out before doing anything, but do # so gracefully with a nice error message. return (False, "Programming Error - Critical information missing for GitManager: item_to_blacklist") item_to_blacklist = item_to_blacklist.replace("\s", " ") if blacklist == "website": blacklist_file_name = "blacklisted_websites.txt" ms_search_option = "&body_is_regex=1&body=" elif blacklist == "keyword": blacklist_file_name = "bad_keywords.txt" ms_search_option = "&body_is_regex=1&body=" elif blacklist == "username": blacklist_file_name = "blacklisted_usernames.txt" ms_search_option = "&username_is_regex=1&username="******"Invalid blacklist type specified, something has broken badly!") git.checkout("master") try: git.pull() except: pass # Check that we're up-to-date with origin (GitHub) git.remote.update() if git("rev-parse", "refs/remotes/origin/master").strip() != git("rev-parse", "master").strip(): return (False, "HEAD isn't at tip of origin's master branch") # Check that blacklisted_websites.txt isn't modified locally. That could get ugly fast if blacklist_file_name in git.status(): # Also ugly return (False, "{0} is modified locally. This is probably bad.".format(blacklist_file_name)) # Add item to file with open(blacklist_file_name, "a+") as blacklist_file: last_character = blacklist_file.read()[-1:] if last_character != "\n": blacklist_file.write("\n") blacklist_file.write(item_to_blacklist + "\n") # Checkout a new branch (for PRs for non-code-privileged people) branch = "auto-blacklist-{0}".format(str(time.time())) git.checkout("-b", branch) # Clear HEAD just in case git.reset("HEAD") git.add(blacklist_file_name) git.commit("-m", u"Auto blacklist of {0} by {1} --autopull".format(item_to_blacklist, username)) if code_permissions: git.checkout("master") git.merge(branch) git.push() else: git.push("origin", branch) git.checkout("master") if GlobalVars.github_username is None or GlobalVars.github_password is None: return (False, "Tell someone to set a GH password") payload = {"title": u"{0}: Blacklist {1}".format(username, item_to_blacklist), "body": u"[{0}]({1}) requests the blacklist of the {2} {3}. See the Metasmoke search [here]" "(https://metasmoke.erwaysoftware.com/search?utf8=%E2%9C%93{4}{5})\n" u"<!-- METASMOKE-BLACKLIST-{6} {3} -->".format(username, chat_profile_link, blacklist, item_to_blacklist, ms_search_option, item_to_blacklist.replace(" ", "+"), blacklist.upper()), "head": branch, "base": "master"} response = requests.post("https://api.github.com/repos/Charcoal-SE/SmokeDetector/pulls", auth=HTTPBasicAuth(GlobalVars.github_username, GlobalVars.github_password), data=json.dumps(payload)) print(response.json()) try: git.checkout("deploy") # Return to deploy, pending the accept of the PR in Master. return (True, "You don't have code privileges, but I've [created a pull request for you]({0}).".format( response.json()["html_url"])) except KeyError: # Error capture/checking for any "invalid" GH reply without an 'html_url' item, # which will throw a KeyError. if "bad credentials" in str(response.json()['message']).lower(): # Capture the case when GH credentials are bad or invalid return (False, "Something is wrong with the GH credentials, tell someone to check them.") else: # Capture any other invalid response cases. return (False, "A bad or invalid reply was received from GH, the message was: %s" % response.json()['message']) git.checkout("deploy") # Return to deploy to await CI. return (True, "Blacklisted {0}".format(item_to_blacklist))
def add_to_blacklist(**kwargs): if 'windows' in str(platform.platform()).lower(): log('warning', "Git support not available in Windows.") return (False, "Git support not available in Windows.") blacklist = kwargs.get("blacklist", "") item_to_blacklist = kwargs.get("item_to_blacklist", "") username = kwargs.get("username", "") chat_profile_link = kwargs.get("chat_profile_link", "http://chat.stackexchange.com/users") code_permissions = kwargs.get("code_permissions", False) # Make sure git credentials are set up if git.config("--global", "--get", "user.name", _ok_code=[0, 1]) == "": return (False, "Tell someone to run `git config --global user.name \"SmokeDetector\"`") if git.config("--global", "--get", "user.email", _ok_code=[0, 1]) == "": return (False, "Tell someone to run `git config --global user.email \"[email protected]\"`") if blacklist == "": # If we broke the code, and this isn't assigned, error out before doing anything, but do # so gracefully with a nice error message. return (False, "Programming Error - Critical information missing for GitManager: blacklist") if item_to_blacklist == "": # If we broke the code, and this isn't assigned, error out before doing anything, but do # so gracefully with a nice error message. return (False, "Programming Error - Critical information missing for GitManager: item_to_blacklist") item_to_blacklist = item_to_blacklist.replace("\s", " ") if blacklist == "website": blacklist_file_name = "blacklisted_websites.txt" ms_search_option = "&body_is_regex=1&body=" elif blacklist == "keyword": blacklist_file_name = "bad_keywords.txt" ms_search_option = "&body_is_regex=1&body=" elif blacklist == "username": blacklist_file_name = "blacklisted_usernames.txt" ms_search_option = "&username_is_regex=1&username="******"Invalid blacklist type specified, something has broken badly!") git.checkout("master") try: git.pull() except: pass # Check that we're up-to-date with origin (GitHub) git.remote.update() if git("rev-parse", "refs/remotes/origin/master").strip() != git("rev-parse", "master").strip(): return (False, "HEAD isn't at tip of origin's master branch") # Check that blacklisted_websites.txt isn't modified locally. That could get ugly fast if blacklist_file_name in git.status(): # Also ugly return (False, "{0} is modified locally. This is probably bad.".format(blacklist_file_name)) # Add item to file with open(blacklist_file_name, "a+") as blacklist_file: last_character = blacklist_file.read()[-1:] if last_character != "\n": blacklist_file.write("\n") blacklist_file.write(item_to_blacklist + "\n") # Checkout a new branch (for PRs for non-code-privileged people) branch = "auto-blacklist-{0}".format(str(time.time())) git.checkout("-b", branch) # Clear HEAD just in case git.reset("HEAD") git.add(blacklist_file_name) git.commit("--author='SmokeDetector <*****@*****.**>'", "-m", u"Auto blacklist of {0} by {1} --autopull".format(item_to_blacklist, username)) if code_permissions: git.checkout("master") git.merge(branch) git.push("origin", "master") git.branch('-D', branch) # Delete the branch in the local git tree since we're done with it. else: git.push("origin", branch) git.checkout("master") if GlobalVars.github_username is None or GlobalVars.github_password is None: return (False, "Tell someone to set a GH password") payload = {"title": u"{0}: Blacklist {1}".format(username, item_to_blacklist), "body": u"[{0}]({1}) requests the blacklist of the {2} {3}. See the Metasmoke search [here]" "(https://metasmoke.erwaysoftware.com/search?utf8=%E2%9C%93{4}{5})\n" u"<!-- METASMOKE-BLACKLIST-{6} {3} -->".format(username, chat_profile_link, blacklist, item_to_blacklist, ms_search_option, item_to_blacklist.replace(" ", "+"), blacklist.upper()), "head": branch, "base": "master"} response = requests.post("https://api.github.com/repos/Charcoal-SE/SmokeDetector/pulls", auth=HTTPBasicAuth(GlobalVars.github_username, GlobalVars.github_password), data=json.dumps(payload)) log('debug', response.json()) try: git.checkout("deploy") # Return to deploy, pending the accept of the PR in Master. git.branch('-D', branch) # Delete the branch in the local git tree since we're done with it. return (True, "You don't have code privileges, but I've [created a pull request for you]({0}).".format( response.json()["html_url"])) except KeyError: git.checkout("deploy") # Return to deploy # Delete the branch in the local git tree, we'll create it again if the # command is run again. This way, we keep things a little more clean in # the local git tree git.branch('-D', branch) # Error capture/checking for any "invalid" GH reply without an 'html_url' item, # which will throw a KeyError. if "bad credentials" in str(response.json()['message']).lower(): # Capture the case when GH credentials are bad or invalid return (False, "Something is wrong with the GH credentials, tell someone to check them.") else: # Capture any other invalid response cases. return (False, "A bad or invalid reply was received from GH, the message was: %s" % response.json()['message']) git.checkout("deploy") # Return to deploy to await CI. return (True, "Blacklisted {0}".format(item_to_blacklist))