Exemple #1
0
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())
Exemple #2
0
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
Exemple #3
0
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)
Exemple #4
0
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))
Exemple #5
0
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("}")
Exemple #6
0
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)
Exemple #7
0
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
Exemple #8
0
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
Exemple #9
0
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')}")
Exemple #10
0
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)
Exemple #11
0
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}")
Exemple #12
0
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)
Exemple #13
0
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")
Exemple #14
0
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)