def test_store_issue_stores_file(regular_issue, test_repo): ih = IssueHandler() ih.store_issue(regular_issue, None, True) result = ih.get_issue_from_issue_id(regular_issue.id) regular_issue.id = "ISSUE-11" # 11 because tracker is mocked to have 10 entries assert regular_issue == result
def test_create_resolution(issue_1: Issue, issue_2: Issue, monkeypatch, first_repo: git.Repo): handler = IssueHandler() issue_1_path = handler.get_issue_path(issue_1) issue_2_path = handler.get_issue_path(issue_2) issues = [issue_1, issue_2] os.chdir(first_repo.working_dir) monkeypatch.setattr("git_issue.git_manager.GitManager.get_choice_from_user", lambda x, y: True) monkeypatch.setattr("builtins.input", lambda x: 'Y') uuids = [UUIDTrack(issue_1.uuid, issue_1.id), UUIDTrack(issue_2.uuid, issue_2.id)] resolution = CreateResolutionTool(issues, Tracker(len(uuids), uuids)) resolution.resolve() GitManager().commit("-m", "merge conflict resolution") assert issue_1_path.exists() assert issue_2_path.exists() result_1 = handler.get_issue_from_issue_id(issues[0].id) result_2 = handler.get_issue_from_issue_id(issues[1].id) assert issues[0] == result_1 assert issues[1] == result_2
def test_store_issue_has_correct_path(regular_issue, test_repo): root = f"{test_repo.working_dir}/issue" ih = IssueHandler() ih.store_issue(regular_issue, None) gm = GitManager() gm.load_issue_branch() assert Path(f"{root}/{regular_issue.id}/issue.json").exists()
def get(self, id): handler = IssueHandler() if (not handler.does_issue_exist(id)): abort(HTTPStatus.NOT_FOUND) issue = handler.get_issue_from_issue_id(id) result = to_payload(GitUser(), issue, IssueSchema) return result.data
def get(self, args): page = args.get("page", 1) limit = 1000 # args.get("limit", 1000) handler = IssueHandler() issues, count = handler.get_issue_range(page, limit) response = IssueList(count, issues) result = to_payload(GitUser(), response, IssueListSchema) return result.data
def test_divergence_resolution(issue_3, first_repo, monkeypatch): os.chdir(first_repo.working_dir) monkeypatch.setattr("builtins.input", lambda x: 'Y') resolution = DivergenceResolutionTool([issue_3]) resolution.resolve() handler = IssueHandler() result = handler.get_issue_from_issue_id(issue_3.id) assert issue_3 == result
def resolve(self): gm = GitManager() gm.load_issue_branch() paths = [] handler = IssueHandler() for issue in self.resolved_issues: file_path = handler.get_issue_path(issue) JsonConvert.ToFile(issue, file_path) paths.append(str(file_path)) repo = gm.obtain_repo() for path in paths: repo.git.add(path)
def confirm_operation(issue, operation): print("Operation will result in the following issue:\n") handler = IssueHandler() handler.display_issue(issue) create = input(f"\nConfirm (Y/N): ").capitalize() while create != "Y" and create != "YES" and create != "N" and create != "NO": create = input("\nInvalid input, please try again (Y/N): ").capitalize() if create == "Y" or create == "YES": operation() print(f"Operation successful.") else: print("Operation cancelled.")
def post(self): json = request.get_json() issue = Issue() issue.summary = json.get("summary") issue.description = json.get("description") reporter = json.get("reporter") assignee = json.get("assignee") issue.reporter = GitUser(email=reporter.get( "email")) if reporter is not None else GitUser() issue.assignee = GitUser( email=assignee.get("email")) if assignee is not None else None issue.subscribers.append(issue.reporter) if issue.assignee is not None and issue.assignee not in issue.subscribers: issue.subscribers.append(issue.assignee) gm = GitManager() handler = gm.perform_git_workflow(lambda: IssueHandler()) created_issue = handler.store_issue(issue, "create", generate_id=True, store_tracker=True) result = to_payload(GitUser(), issue, IssueSchema) return result.data, HTTPStatus.CREATED, { 'location': f'issues/${created_issue.id}' }
def comment(args): ih = GitManager().perform_git_workflow(lambda: IssueHandler()) handler = GitManager().perform_git_workflow(lambda: CommentHandler(ih.get_issue_folder_path(args.issue), args.issue)) if args.comment: c = Comment(args.comment) handler.add_comment(c) else: comments = GitManager().perform_git_workflow(lambda: handler.get_comment_range(10000, 0)) for c in comments: print(f"Email: {c.user.email}\tDate: {c.date}\n\t{c.comment}\n")
def show(args): if args.issue is not None: handler = GitManager().perform_git_workflow(lambda: IssueHandler()) issue = handler.get_issue_from_issue_id(args.issue) if issue == None: print(f"Issue with ID {args.issue} was not found.") else: handler.display_issue(issue) else: list(args)
def change_status(issue_id, status): handler = GitManager().perform_git_workflow(lambda: IssueHandler()) if not handler.does_issue_exist(issue_id): print("Error: Issue does not exist") return issue = handler.get_issue_from_issue_id(issue_id) original_status = issue.status issue.status = status handler.store_issue(issue, "edit") print(f"Status of {issue.id} changed from '{original_status}' to '{issue.status}'")
def post(self, id): ih = IssueHandler() if (not ih.does_issue_exist(id)): raise BadRequest(f"Issue with id {id} does not exist.") comment = request.get_json().get("comment") if (comment is None): raise BadRequest(f"No comment given.") comment = Comment(comment) gm = GitManager() path = gm.perform_git_workflow(lambda: ih.get_issue_folder_path(id)) handler = CommentHandler(path, id) created_comment = handler.add_comment(comment) schema = CommentSchema() result = schema.dump(comment) return result.data, HTTPStatus.CREATED
def put(self, id): edit_schema = IssueSchema(only=tuple(IssueSchema.issue_fields.keys())) parsed_data = edit_schema.load(request.get_json()) if (len(parsed_data.errors.items()) > 0): return f"Errors encountered with the request: {parsed_data.errors}", 416 updated_issue = parsed_data.data issue = None httpStatus: HTTPStatus = None headers = {} handler = IssueHandler() if (not handler.does_issue_exist(id)): issue = handler.store_issue(updated_issue, "create", True) hash = hashlib.sha256( b"{regular_schema.dump(issue).data}").hexdigest() print(f"Hash: {hash}") headers["ETag"] = hash httpStatus = HTTPStatus.CREATED else: current_issue = handler.get_issue_from_issue_id(id) if (updated_issue.id != id): return "Given issue ID does not match url", 416 updated_issue.date = current_issue.date # Ensure date NEVER changes issue = handler.store_issue(updated_issue, "edit") result = to_payload(GitUser(), issue, IssueSchema) return result.data, HTTPStatus.OK
def get(self, args, id): issue_handler = IssueHandler() if not issue_handler.does_issue_exist(id): raise BadRequest(f"Issue with id {id} does not exist.") page = args.get("page", 1) limit = 1000 # args.get("limit", 10) # Each page is limit amount of comments, therefore start_pos # is the limit of comments per page, times by the page number start_pos = limit * (page - 1) gm = GitManager() def action(): path = issue_handler.get_issue_folder_path(id) comment_handler = CommentHandler(path, id) return comment_handler.get_comment_range(limit, start_pos) comments = gm.perform_git_workflow(action) schema = CommentSchema() result = schema.dump(comments, many=True) return result.data
def test_produce_divergence_resolver(issue_1, issue_2, issue_3, first_repo): resolved_issues = [issue_1, issue_2, issue_3] resolved_tracker = Tracker() for issue in resolved_issues: resolved_tracker.track_or_update_uuid(issue.uuid, issue.id) issue_2_json = JsonConvert.ToJSON(issue_2) issue_2_copy = JsonConvert.FromJSON(issue_2_json) issue_2_copy.uuid = 53368803138698180295652887974160049016 conflict = ConflictInfo(IssueHandler().get_issue_path(issue_2), [issue_2, issue_2, issue_2_copy]) merger = GitMerge(first_repo) resolver = merger.produce_create_edit_divergence_resolver([conflict], resolved_issues, resolved_tracker) assert resolved_issues == resolver.resolved_conflicts and resolved_tracker == resolver.resolved_tracker assert [(issue_2, issue_2_copy)] == resolver.diverged_issues
def edit(args): handler = GitManager().perform_git_workflow(lambda: IssueHandler()) if not handler.does_issue_exist(args.issue): print("Error: Issue does not exist") return issue = handler.get_issue_from_issue_id(args.issue) if (issue == None): print("There was a problem ") print("Issue before editing:") handler.display_issue(issue) issue.summary = args.summary if args.summary != None else issue.summary issue.description = args.description if args.description != None else issue.description issue.assignee = GitUser(email=args.assignee) if args.assignee != None else issue.assignee issue.reporter = GitUser(email=args.reporter) if args.reporter != None else issue.reporter issue.status = args.status if args.status != None else issue.status print() confirm_operation(issue, lambda: handler.store_issue(issue, "edit"))
def generate_resolution(self): matching_issues: (Issue, Issue) = [] diverged_issues = self.diverged_issues resolved_issues = [] for resolved in self.resolved_conflicts: found = False diverged_index = 0 for stage_2, stage_3 in diverged_issues: match = None if stage_2.uuid == resolved.uuid and stage_2.id != resolved.id: match = stage_2 resolved_issues.append(stage_3) elif stage_3.uuid == resolved.uuid and stage_3.id != resolved.id: match = stage_3 resolved_issues.append(stage_2) if match is not None: found = True matching_issues.append((match, resolved)) break diverged_index += 1 if found: # Remove the found issue from the list so we don't need to examine it again del diverged_issues[diverged_index] handler = IssueHandler(self.resolved_tracker) for stage_2, stage_3 in diverged_issues: loaded_stage_2 = None loaded_stage_3 = None # If it fails then it means the JSON is garbled with Git merge stuff. That or it's just messed up... # Either way, it's a fail. try: loaded_stage_2 = handler.get_issue_from_uuid(stage_2.uuid) except JSONDecodeError: pass try: loaded_stage_3 = handler.get_issue_from_uuid(stage_3.uuid) except JSONDecodeError: pass if loaded_stage_2 is not None and loaded_stage_2.id != stage_2.id: matching_issues.append((stage_2, loaded_stage_2)) resolved_issues.append(stage_3) elif loaded_stage_3 is not None and loaded_stage_3.id != stage_3.id: matching_issues.append((stage_3, loaded_stage_3)) resolved_issues.append(stage_2) else: raise DivergenceMatchMissingError( stage_2, stage_3, "Failed to discern the stage that is diverged") if len(matching_issues) > 0: print("One or more create-edit divergencies have been identified. This is when an issue on "\ "one branch has been edited, after it has been resolved as a create conflict "\ "on another branch that is being merged with the current branch.") for diverged, match in matching_issues: resolved = self._get_edit_resolution(diverged, match) resolved_issues.append(resolved) resolution = DivergenceResolutionTool(resolved_issues) return resolution
def generate_resolution(self): """ Resolves conflicts by re-ordering the issues based on their creation date. The conflicts are resolved by giving the oldest issue the first conflicting issue ID. Returns a tuple containing the list of resolved conflicts, and an updated tracker. These resolved conflicts are NOT stored to fie by this method. This is left as a job for the consumer. """ # Create copies of data to avoid mutating them as a side affect conflicts = [] for info in self.conflicts: for issue in info.conflicts: conflicts.append(issue) if self.tracker is None: tracker = Tracker.obtain_tracker() else: tracker = Tracker(self.tracker.issue_count, self.tracker.tracked_uuids.copy()) complete = False handler = IssueHandler(tracker) ids = [issue.id for issue in conflicts] while not complete: to_add = [] for issue in conflicts: next_id = handler.next_issue_id(issue.id) while next_id in ids: next_id = handler.next_issue_id(next_id) if next_id not in ids: if handler.does_issue_exist(next_id): missing = handler.get_issue_from_issue_id(next_id) to_add.append(missing) ids.append(next_id) for issue in to_add: conflicts.append(issue) ids.append(issue.id) complete = len(to_add) == 0 conflicts.sort(key=lambda x: x.date) sorted_ids = list(set(ids)) sorted_ids.sort() assert len(sorted_ids) >= len(conflicts) index = 0 for issue in conflicts: new_id = sorted_ids[index] issue.id = new_id tracker.track_or_update_uuid(issue.uuid, new_id) index += 1 return CreateResolutionTool(conflicts, tracker)
def test_unmerged_conflicts(issue_1: Issue, issue_2: Issue, issue_3: Issue, first_repo: git.Repo, second_repo: git.Repo, monkeypatch): # Create, Create-Edit-Divergence, Comment-Index, Manual issue_2_json = JsonConvert.ToJSON(issue_2) issue_2_copy = JsonConvert.FromJSON(issue_2_json) issue_2_copy.summary = "Edited Summary" monkeypatch.setattr("git_issue.git_manager.GitManager.get_choice_from_user", lambda x, y: True) monkeypatch.setattr("builtins.input", lambda x: 'Y') def merge(repo): try: repo.git.checkout("issue") repo.git.pull("--allow-unrelated-histories", "other", GitManager.ISSUE_BRANCH) except: pass merger = GitMerge(repo) return merger.parse_unmerged_conflicts() # Set up first repo for Create conflict os.chdir(first_repo.working_dir) handler = IssueHandler() handler.store_issue(issue_2, "test", generate_id=True) # Set up second repo for Create conflict os.chdir(second_repo.working_dir) handler = IssueHandler() handler.store_issue(issue_1, "test", generate_id=True) result = merge(second_repo) expected = [ConflictInfo("ISSUE-1/issue.json", [issue_1, issue_2])] assert expected == result GitSynchronizer().merge(GitMerge(second_repo)) # Set up first repo for divergence conflict os.chdir(first_repo.working_dir) handler = IssueHandler() issue_2_copy.id = issue_1.id handler.store_issue(issue_2_copy, "test") os.chdir(second_repo.working_dir) result = merge(second_repo) expected = [ConflictInfo("ISSUE-1/issue.json", [issue_2, issue_1, issue_2_copy])] assert expected == result GitSynchronizer().merge(GitMerge(second_repo)) # Comment os.chdir(first_repo.working_dir) handler = IssueHandler() comment_handler = CommentHandler(handler.get_issue_path(issue_1), issue_1.id) first_comment = Comment("first repo") first_entry = comment_handler.add_comment(first_comment) # Comment os.chdir(second_repo.working_dir) handler = IssueHandler() comment_handler = CommentHandler(handler.get_issue_path(issue_1), issue_1.id) second_comment = Comment("second repo") second_entry = comment_handler.add_comment(second_comment) result = merge(second_repo) expected = [ConflictInfo("ISSUE-1/index.json", [Index([second_entry]), Index([first_entry])])] assert expected == result GitSynchronizer().merge(GitMerge(second_repo)) # Edit conflict issue_1.id = "ISSUE-10" # Just so we don't need to work out the ID issue_1_json = JsonConvert.ToJSON(issue_1) issue_1_first_repo = JsonConvert.FromJSON(issue_1_json) issue_1_second_repo = JsonConvert.FromJSON(issue_1_json) issue_1_first_repo.summary = "First Repo" issue_1_second_repo.summary = "Second Repo" # Set up both repos with the issue that will be used as a conflict os.chdir(first_repo.working_dir) handler = IssueHandler() handler.store_issue(issue_1, "test") os.chdir(second_repo.working_dir) result = merge(second_repo) # Edit first repo os.chdir(first_repo.working_dir) handler.store_issue(issue_1_first_repo, "test") # Edit second repo os.chdir(second_repo.working_dir) handler = IssueHandler() handler.store_issue(issue_1_second_repo, "issue 10 edit") result = merge(second_repo) expected = [ConflictInfo("ISSUE-10/issue.json", [issue_1, issue_1_second_repo, issue_1_first_repo])] assert expected == result GitSynchronizer().merge(GitMerge(second_repo))
def operation(): handler = GitManager().perform_git_workflow(lambda: IssueHandler()) new_issue = handler.store_issue(issue, "creation", True, True) print(f"ID of newly created issue: {new_issue.id}")
def list(args): handler = GitManager().perform_git_workflow(lambda: IssueHandler()) issues = issue_handler.get_all_issues() for i in issues: handler.display_issue(i) print()