def handle_levels(self, args): if args.opt_all: skills_to_show = [skill for skill in all_skills] print("All levels and skills:") else: # Only show levels in one skill if args.skill_name: try: skills_to_show = [all_skills[args.skill_name]] print('Levels in skill "{}" :'.format(args.skill_name)) except KeyError: print('There is no skill "{}".'.format(args.skill_name)) print( 'You may run "git gud levels --all" or "git gud levels --skills" to print all the skills.' ) # noqa: E501 return elif self.is_initialized(): current_skill = get_operator().get_level().skill skills_to_show = [current_skill] print('Levels in the current skill "{}" :'.format( current_skill.name)) # noqa: E501 else: self.subparsers.choices['levels'].print_help() return print() show_skill_tree(skills_to_show, bool(get_operator()), expand_skills=True, show_human_names=not args.opt_short) print() print("Load a level with `git gud load`")
def handle_load(self, args): self.assert_initialized() args.skill_name = args.skill_name.lower() if args.level_name: args.level_name = args.level_name.lower() level = get_operator().get_level() if level: if level._test(): level.mark_complete() if args.skill_name in {"next", "prev", "previous"}: self.load_level_by_direction(args.skill_name, args.force) return # No matter what, the dash separates the skill and level if '-' in args.skill_name: identifier = args.skill_name + (args.level_name or '') else: identifier = args.skill_name + '-' + (args.level_name or '') if identifier.count('-') != 1: print("Load formula must not contain more than one dash.") return args.skill_name, args.level_name = identifier.split('-') if not args.skill_name: args.skill_name, loaded_level_name = get_operator() \ .get_level_identifier() print("Inferring skill from currently loaded level: {} {}".format( args.skill_name, loaded_level_name)) if not args.level_name: args.level_name = '1' # Skill doesn't exist if args.skill_name not in all_skills.keys(): print('Skill "{}" does not exist'.format(args.skill_name)) print('\nTo view levels/skills, use "git gud levels --all"') return # Level doesn't exist skill = all_skills[args.skill_name] if args.level_name not in skill.keys(): print('Level "{}" does not exist.'.format(args.level_name)) print('\nTo view levels/skills, use "git gud levels --all"') return level = skill[args.level_name] self.load_level(level)
def display_commit_content(show_branches=True, show_content=True, content_order=None, sort_commits=True, num_files=2, num_commits=2): # noqa: E501 file_operator = operations.get_operator() referred_by = target_branch_str() commit_format_str = 'Commit {commit_num}: "{message}"' if show_branches: commit_format_str += "{branches}" commits = file_operator.get_all_commits(sort_commits) for commit_num, commit in enumerate(commits): if commit.hexsha in referred_by and show_branches: branches = f" ({referred_by[commit.hexsha]})" else: branches = "" header = commit_format_str.format( commit_num=commit_num+1, message=commit.message.split('\n')[0].strip(), branches=branches ) display_tree_content( header, file_operator.get_commit_content(commit), content_order=content_order, show_content=show_content, num_files=num_files ) for commit_num in range(len(commits), num_commits): print(f"Commit {commit_num+1}: " + existence_str(False))
def _test5(self): # File 2 was moved in commit 5 file_operator = operations.get_operator() commits = file_operator.get_commits() if len(commits) < 5: return None content4 = file_operator.get_commit_content(commits[3]) content5 = file_operator.get_commit_content(commits[4]) if len(content5) != 1: return False filename2_orig = next(iter(content4.keys())) filename2_new = next(iter(content5.keys())) if filename2_orig in content5: return False if content4[filename2_orig] != content5[filename2_new]: return False return True
def _test(self): file_operator = operations.get_operator() # There are two commits if len(file_operator.get_commits()) != 2: return False # The first commit has one file content1 = file_operator.get_commit_content('HEAD~') if len(content1.keys()) != 1: return False # The second commit has two files content2 = file_operator.get_commit_content('HEAD') if len(content2.keys()) != 2: return False # The file from the first commit is in the second commit file1_name = next(iter(content1.keys())) if file1_name not in content2: return False # The first file is unchanged if content1[file1_name] != content2[file1_name]: return False # Working Directory, Staging Area and Commit 2 are the same content_wd = file_operator.get_working_directory_content() content_sa = file_operator.get_staging_content() if not (content2 == content_wd == content_sa): return False return True
def load_level_by_direction(self, load_direction, force): if load_direction == "prev": load_direction = "previous" try: level = get_operator().get_level() except InitializationError as e: print(e) print(f"Cannot load {load_direction} level.") return if load_direction == "next": if level.has_ever_been_completed() or force: level_to_load = level.next_level else: handle_load_confirm() return else: level_to_load = level.prev_level if level_to_load is not None: self.load_level(level_to_load) else: if load_direction == "next": all_levels_complete() else: print( 'Already on the first level. To reload the level, use "git gud reload".' ) # noqa: E501 print('\nTo view levels/skills, use "git gud levels --all"' ) # noqa: E501
def handle_skills(self, args): print("All skills:") print() show_skill_tree([skill for skill in all_skills], bool(get_operator()), expand_skills=False, show_human_names=not args.opt_short)
def _test(self): file_operator = operations.get_operator() # Get commit trees test_tree = level_json(*parse_spec(self.file('test.spec'))) level_tree = file_operator.get_current_tree() # Make all user-created branches lowecase setup_tree = level_json(*parse_spec(self.file('setup.spec'))) branches_to_lowercase(level_tree, setup_tree, test_tree) # Get commit info non_merges = get_non_merges(level_tree) # Name known commits known_commits = file_operator.get_known_commits() name_from_map(level_tree, known_commits) # Name rebases and cherrypicks known_non_merges = { commit_hash: name for commit_hash, name in known_commits.items() if name[:1] != 'M' } diff_map = file_operator.get_copy_mapping(non_merges, known_non_merges) name_from_map(level_tree, diff_map) # Name merges name_merges(level_tree, test_tree) # Test for similarity return test_ancestry(level_tree, test_tree)
def solution(self): commit = operations.get_operator().repo.head.commit commits = [] commit_dict = {} while commit: commits.append((commit.hexsha[:7], commit.message)) commit_dict[commit.message] = commit.hexsha[:7] if commit.parents: commit = commit.parents[0] else: commit = None # Chronological order commits = list(reversed(commits)) this_hash = commit_dict['This'] print('Run: "git rebase -i {}"'.format(this_hash)) print('"{}" is the hash of the commit with the message "This"'.format( this_hash)) print() print("You will see this: ") for sha, msg in commits[1:]: print(4 * " " + "pick {} {}".format(sha, msg)) print() print("Change it to this:") for msg in 'is an easy level'.split(): sha = commit_dict[msg] print(4 * " " + "pick {} {}".format(sha, msg)) print() print('The order of commits will now be "This is an easy level"')
def repo_already_initialized(): file_operator = operations.get_operator() print('Repo {} already initialized for Git Gud.' .format(file_operator.path)) print('Use "git gud init --force" to delete progress and reinitialize') if file_operator.path != Path.cwd(): print('{} will be left as is.'.format(file_operator.gg_path)) # noqa: E501
def handle_solution(self, args): self.assert_initialized() current_level = get_operator().get_level() if not args.confirm and \ not current_level.has_ever_been_completed(): rerun_with_confirm_for_solution(current_level) else: current_level.solution()
def handle_reset(self, args): self.assert_initialized() file_operator = get_operator() file_operator.update_level_completion() level = file_operator.get_level() self.load_level(level)
def get_state(self): file_operator = operations.get_operator() created = bool(file_operator.get_working_directory_content()) added = bool(file_operator.get_staging_content()) committed = file_operator.repo.head.is_valid() \ and bool(file_operator.get_commit_content('HEAD')) return created, added, committed
def test_load(gg): load_tests = [('git gud load 1', all_skills["1"]["1"]), ('git gud load rampup', all_skills["rampup"]["1"]), ('git gud load 2 detaching', all_skills["2"]["detaching"]), ('git gud load rampup 4', all_skills["rampup"]["4"]), ('git gud load 5-octopus', all_skills["5"]["octopus"]), ('git gud load rampup-4', all_skills["rampup"]["4"]), ('git gud load -2', all_skills["rampup"]["2"])] for command, level in load_tests: subprocess.call(command, shell=True) op = operations.get_operator() assert level == op.get_level()
def _test1(self): # Test if a single file has been added to the first commit file_operator = operations.get_operator() commits = file_operator.get_commits() if len(commits) < 1: return None content = file_operator.get_commit_content(commits[0]) if len(content.keys()) != 1: return False return True
def handle_debug(self, args): import readline # noqa: F401 import code variables = globals() file_operator = get_operator() variables['file_operator'] = file_operator variables['op'] = file_operator shell = code.InteractiveConsole(variables) shell.interact(banner="\n".join([ "You are now in the Python interpreter invoked by `git gud debug`.", # noqa: E501 "Your current path is " + str(Path.cwd()), "To exit, type exit()" ]))
def _test(self): file_operator = operations.get_operator() if file_operator.branch_has_merges(): return False if not self._test1(): return False if not self._test2(): return False if not self._test3(): return False if not self._test4(): return False if not self._test5(): return False return True
def setup_rebase_conflict(): file_operator = get_operator() with open("foo", "w") as f: f.write("branch 1") file_operator.repo.index.add("foo") file_operator.repo.index.commit("Add a file") file_operator.repo.git.checkout('-b', 'branch', 'HEAD~') with open("foo", "w") as f: f.write("branch 2") file_operator.repo.index.add("foo") file_operator.repo.index.commit("Add a file") try: file_operator.repo.git.rebase('master') except GitCommandError: # This will happen every time pass
def handle_commit(self, args): self.assert_initialized() file_operator = get_operator() last_commit = file_operator.get_last_commit() commit_name = str(int(last_commit) + 1) if args.file is not None: try: int(args.file) commit_name = args.file except ValueError: pass commit = file_operator.add_and_commit(commit_name, silent=False) file_operator.track_commit(commit_name, commit.hexsha) # Next "git gud commit" name if int(commit_name) > int(last_commit): file_operator.write_last_commit(commit_name)
def _test4(self): # File 1 was removed in commit 4 file_operator = operations.get_operator() commits = file_operator.get_commits() if len(commits) < 4: return None content1 = file_operator.get_commit_content(commits[0]) content3 = file_operator.get_commit_content(commits[2]) content4 = file_operator.get_commit_content(commits[3]) file1 = next(iter(content1.keys())) # Construct content4 from content3 del content3[file1] return content3 == content4
def handle_init(self, args): # Make sure it's safe to initialize file_operator = get_operator() if file_operator: if not args.force: repo_already_initialized() return else: force_initializing() elif len(list(Path.cwd().iterdir())) != 0: if not (args.force and args.prettyplease): cant_init_repo_not_empty() return else: deleting_and_initializing() file_operator = Operator(Path.cwd()) file_operator.init_gg() self.load_level(all_skills["0"]["1"])
def _setup(self): file_operator = operations.get_operator() file_operator.use_repo() commits, head = parse_spec(self.file('setup.spec')) details_path = self.file('details.yaml') if details_path.is_file(): details = yaml.safe_load(details_path.open()) else: details = None file_operator.create_tree(commits, head, details, self.level_dir) latest_commit = '0' for commit_name, _, _, _ in commits: try: if int(commit_name) > int(latest_commit): latest_commit = commit_name except ValueError: pass # Commit is merge and doesn't have number file_operator.write_last_commit(latest_commit)
def parse(self): if len(sys.argv) >= 2 and sys.argv[1] in self.aliases: sys.argv[1] = self.aliases[sys.argv[1]] args, _ = self.parser.parse_known_args(sys.argv[1:]) if args.command is None: if get_operator() is None: print('Currently in an uninitialized directory.') print( 'Get started by running "git gud init" (without quotes) in an empty directory!' ) # noqa: E501 if len(list(Path.cwd().iterdir())) != 0: print('Current directory is not empty.') else: print('Current directory is empty.') else: self.parser.print_help() else: try: args = self.parser.parse_args() args.func(self, args) except InitializationError as error: print(error)
def _test2(self): # Test if a single file has been added to the second commit file_operator = operations.get_operator() commits = file_operator.get_commits() if len(commits) < 2: return None content1 = file_operator.get_commit_content(commits[0]) content2 = file_operator.get_commit_content(commits[1]) # There is only one more file in the second commit if len(content1) + 1 != len(content2): return False filename1 = next(iter(content1.keys())) if filename1 not in content2: return False if content1[filename1] != content2[filename1]: return False return True
def _test3(self): # Test that both files were modified in commit three file_operator = operations.get_operator() commits = file_operator.get_commits() if len(commits) < 3: return None content2 = file_operator.get_commit_content(commits[1]) content3 = file_operator.get_commit_content(commits[2]) # Same number of files if len(content2) != len(content3): return False # Both files have new content and same name for filename in content2: if filename not in content3: return False if content2[filename] == content3[filename]: return False return True
def get_progress(self): file_operator = operations.get_operator() return file_operator.get_level_progress(self)
def mark_visited(self): file_operator = operations.get_operator() file_operator.mark_level(self, "visited")
def mark_partial(self): file_operator = operations.get_operator() file_operator.mark_level(self, "partial")
def mark_complete(self): file_operator = operations.get_operator() file_operator.mark_level(self, "complete")
def display_staging_area_content(**kwargs): file_operator = operations.get_operator() staging_area = file_operator.get_staging_content() display_tree_content("Staging Area:", staging_area, **kwargs)