def cmd_checkout(args: argparse.Namespace) -> None: repo = repo_find() assert repo is not None, "Git repository not found" obj_sha = object_find(repo, args.commit) assert obj_sha is not None try: commit_contents = commit_read(repo, obj_sha) tree_contents = tree_read( repo, Sha(commit_contents.kvlm[b'tree'][0].decode("ascii"))) except GitObjectTypeError: raise ValueError( f"Cannot checkout {args.commit} since it's not a commit!") # Verify that path is an empty directory path = pathlib.Path(args.path) if path.exists(): if not path.is_dir(): raise ValueError(f"Not a directory {args.path}!") if list(path.iterdir()): raise ValueError(f"Not empty {args.path}!") else: path.mkdir(parents=True) tree_checkout(repo, tree_contents, path.resolve())
def commit(author: str, message: str) -> str: """https://benhoyt.com/writings/pygit/""" index = read_index() if len(index) == 0: raise ValueError("nothing to commit") repo = repo_find() assert repo is not None try: with open(str(repo_file(repo, "refs", "heads", "master")), "r") as f: parent: Optional[str] = f.read().strip() except FileNotFoundError: _LOG.debug("No prior commits") parent = None # git write-tree sha_of_tree = tree_write(repo, index) # git commit-tree lines = [f"tree {sha_of_tree}"] if parent: lines.append(f"parent {parent}") author_time = author_timestamp() lines = lines + [ f"author {author} {author_time}", f"committer {author} {author_time}", '', message, '' ] data = '\n'.join(lines).encode() sha1 = generic_object_hash(io.BytesIO(data), b"commit", repo) # git update-ref master_path = repo_file(repo, "refs", "heads", "master", write=True) write_file(str(master_path), (sha1 + "\n").encode()) return sha1
def cmd_add(args: argparse.Namespace) -> None: if args.all: repo = repo_find() assert repo is not None all_paths = [repo_path(repo, '.').parent] else: all_paths = [pathlib.Path(path) for path in args.paths] add_all(all_paths)
def cmd_rev_parse(args: argparse.Namespace) -> None: if args.type: fmt = args.type.encode() repo = repo_find() assert repo is not None _LOG.info(object_find(repo, args.name, fmt, follow=True))
def cmd_log(args: argparse.Namespace) -> None: repo = repo_find() assert repo is not None, "Git repository not found" _LOG.info("digraph ewyaglog{") git_object_sha = object_find(repo, args.commit) assert git_object_sha is not None log_graphviz(repo, git_object_sha, set()) _LOG.info("}")
def cmd_tag(args: argparse.Namespace) -> None: repo = repo_find() assert repo is not None if args.name: tag_create(repo, args.name, args.object, args.create_tag_object) else: refs = ref_list(repo) show_ref(repo, refs["tags"], with_hash=False)
def read_index() -> List[GitIndexEntry]: """Read git index file and return list of IndexEntry objects. https://benhoyt.com/writings/pygit/""" FIELDS_LENGTH = 62 HEADER_LENGTH = 12 CHECKSUM_LENGTH = 20 PADDING = 8 repo = repo_find() assert repo is not None, "Repo is None" try: data = open(str(repo_path(repo, "index")), 'rb').read() except FileNotFoundError: _LOG.debug("File .git/index not found!") return [] # verify checksum digest = hashlib.sha1(data[:-CHECKSUM_LENGTH]).digest() assert digest == data[-CHECKSUM_LENGTH:], 'invalid index checksum' # verify signature and version signature, version, num_entries = struct.unpack(HEADER_FORMAT, data[:HEADER_LENGTH]) assert signature == b'DIRC', \ 'invalid index signature {}'.format(signature) assert version == 2, 'unknown index version {}'.format(version) entry_data = data[HEADER_LENGTH:-CHECKSUM_LENGTH] entries: List[GitIndexEntry] = [] i = 0 while len(entries) < num_entries: # calculate dimensions fields_end = i + FIELDS_LENGTH path_end = entry_data.index(b'\x00', fields_end) full_entry_len = pad_to_multiple( FIELDS_LENGTH + (path_end - fields_end), PADDING) # read data fields = struct.unpack(ENTRY_FORMAT, entry_data[i:fields_end]) path = entry_data[fields_end:path_end] # next entries.append(GitIndexEntry(*(fields + (path.decode(), )))) i += full_entry_len if i + FIELDS_LENGTH < len(entry_data): _LOG.debug(( f"This index file contains extensions (signature: " f"{entry_data[i:i+4].decode()} -> {extension_signatures[entry_data[i:i+4]]})" )) return entries
def tree_print(obj: GitTree) -> str: ret = '' for i in obj.items: try: repo = repo_find() assert repo is not None fmt = object_get_type(repo, i.sha).decode() except FileNotFoundError: fmt = '????' ret += f"{i.mode.decode()} {fmt} {i.sha.zfill(40)} {i.path.decode()}\n" return ret
def cmd_ls_tree(args: argparse.Namespace) -> None: repo = repo_find() assert repo is not None, "Git repository not found" obj_sha = object_find(repo, args.object, fmt=b'tree') assert obj_sha is not None obj_content = tree_read(repo, obj_sha) for item in obj_content.items: mode = "0" * (6 - len(item.mode)) + item.mode.decode("ascii") fmt = object_get_type(repo, item.sha).decode("ascii") _LOG.info(f"{mode} {fmt} {item.sha}\t{item.path.decode('ascii')}")
def cmd_cat_file(args: argparse.Namespace) -> None: repo = repo_find() if repo is None: raise ValueError("Git repository not found!") if args.show_type: obj = generic_object_read(repo, args.object) _LOG.info(obj.fmt.decode()) return if args.pretty_print or args.type: fmt = args.type.encode() if args.type else None file_cat(repo, args.object, fmt=fmt)
def cmd_remote(args: argparse.Namespace) -> None: repo = repo_find() remotes = {} for section in repo.conf.sections(): if not section.startswith('remote '): continue remote_name = section.split('"')[1] remotes[remote_name] = { "fetch": repo.conf.get(section, "url"), "pull": repo.conf.get(section, "url") } if args.subcommand == "add": section = f'remote "{args.name}"' repo.add_to_config(section, { "url": args.url, "fetch": f"+refs/heads/*:refs/remotes/{args.name}/*" }) elif args.subcommand == "remove": section = f'remote "{args.name}"' repo.conf.remove_section(f'remote "{args.name}"') repo.write_config() elif args.subcommand == "get-url": print(remotes[args.name]['fetch']) elif args.subcommand == "prune": pass elif args.subcommand == "rename": old = f'remote "{args.old}"' new = f'remote "{args.new}"' repo.conf.add_section(new) for option_name in repo.conf.options(old): value = repo.conf.get(old, option_name) repo.conf.remove_option(old, option_name) repo.conf.set(new, option_name, value) repo.conf.remove_section(old) repo.write_config() elif args.subcommand == "set-branches": pass elif args.subcommand == "set-head": pass elif args.subcommand == "set-url": pass elif args.subcommand is None: for remote, value in remotes.items(): if args.verbose: print(f"{remote}\t{value['fetch']} (fetch)") print(f"{remote}\t{value['pull']} (push)") else: print(f"remote") else: raise ValueError(f"Unknown subcommand to remote {args.subcommand}")
def write_index(entries: List[GitIndexEntry]) -> None: """Write list of index entries objects to git index file. https://github.com/benhoyt/pygit/blob/master/pygit.py""" packed_entries = [] for entry in entries: entry_head = struct.pack(ENTRY_FORMAT, entry.ctime[0], entry.ctime[1], entry.mtime[0], entry.mtime[1], entry.dev, entry.ino, entry.mode, entry.uid, entry.gid, entry.size, entry.obj, entry.flags) path = entry.name.encode() length = ((62 + len(path) + 8) // 8) * 8 packed_entry = entry_head + path + b'\x00' * (length - 62 - len(path)) packed_entries.append(packed_entry) header = struct.pack(HEADER_FORMAT, b'DIRC', 2, len(entries)) all_data = header + b''.join(packed_entries) digest = hashlib.sha1(all_data).digest() repo = repo_find() assert repo is not None with open(str(repo_file(repo, 'index', write=True)), "wb") as f: f.write(all_data + digest)
def cmd_show_ref(args: argparse.Namespace) -> None: repo = repo_find() assert repo is not None refs = ref_list(repo) show_ref(repo, refs, prefix="refs")
def cmd_write_tree(args: argparse.Namespace) -> None: idx = read_index() repo = repo_find() assert repo is not None sha_of_tree = tree_write(repo, idx) _LOG.info(sha_of_tree)