diff --git a/libwyag.py b/libwyag.py index db54616..ca57c01 100644 --- a/libwyag.py +++ b/libwyag.py @@ -634,3 +634,152 @@ def tree_checkout(repo, tree, path): # TODO: Support symlinks (identified by mode 12*****) with open(dest, 'wb') as f: f.write(obj.blobdata) + +def ref_resolve(repo, ref): + path = GitRepository.repo_file(repo, ref) + + # Sometimes, an indirect reference may be broken. This is normal + # in one specific case: we're looking for HEAD on a new repository + # with no commits. In that case, .git/HEAD points to "ref: + # refs/heads/master", but .git/refs/heads/master doesn't exist yet + # (since there's no commit for it to refer to). + if not os.path.isfile(path): + return None + + with open(path, "r") as fp: + data = fp.read()[:-1] # Drop final "\n" + + if data.startswith("ref: "): + return ref_resolve(repo, data[len("ref: "):]) + else: + return data + +def ref_list(repo, path=None): + if not path: + path = repo_dir(repo, "refs") + + ret = collections.OrderedDict() + + for f in sorted(os.listdir(path)): + can = os.path.join(path, f) + if os.path.isdir(can): + ret[f] = ref_list(repo, can) + else: + ret[f] = ref_resolve(repo, can) + + return ret + +argsp = argsubparsers.add_parser( + "tag", + help="List and create tags") + +argsp.add_argument("-a", + action="store_true", + dest="create_tag_object", + help="Whether to create a tag object") + +argsp.add_argument("name", + nargs="?", + help="The new tag's name") + +argsp.add_argument("object", + default="HEAD", + nargs="?", + help="The object the new tag will point to") + +def cmd_tag(args): + repo = repo_find() + + if args.name: + tag_create(repo, + args.name, + args.object, + type="object" if args.create_tag_object else "ref") + else: + refs = ref_list(repo) + show_ref(repo, refs["tags"], with_hash=False) + +def tag_create(repo, name, ref, create_tag_object=False): + sha = object_find(repo, ref) + + if create_tag_object: + tag = GitTag(repo) + tag.kvlm = collections.OrderedDict() + tag.kvlm[b'object'] = sha.encode() + tag.kvlm[b'type'] = b'commit' + tag.kvlm[b'tag'] = name.encode() + tag.kvlm[b'tagger'] = b'Wyag ' + tag.kvlm[None] = b'Tag generated by wyag' + tag_sha = object_write(tag) + ref_create(repo, "tags/" + name, tag_sha) + else: + ref_create(repo, "tags/" + name, sha) + +def ref_create(repo, ref_name, sha): + with open(repo_file(repo, "refs/" + ref_name), "w") as fp: + fp.write(sha + "\n") + +def object_resolve(repo, name): + """Resolve a name to an object hash in repo. + +This function is aware of: + +- the HEAD literal +- short and long hashes +- tags +- branches +- remote branches""" + candidates = list() + hashRE = re.compile(r"[0-9A-Fa-f]{4,40}$") + + # Abort on empty string + if not name.strip(): + return None + + if name == "HEAD": + return [ref_resolve(repo, "HEAD")] + + if hashRE.match(name): + name = name.lower() + prefix = name[0:2] + path = GitRepository.repo_dir(repo, "objects", prefix, mkdir=False) + if path: + rem = name[2:] + for f in os.listdir(path): + if f.startswith(rem): + candidates.append(prefix + f) + + # Try for references + as_tag = ref_resolve(repo, "refs/tags/" + name) + if as_tag: + candidates.append(as_tag) + + as_branch = ref_resolve(repo, "refs/heads/" + name) + if as_branch: + candidates.append(as_branch) + + return candidates + +argsp = argsubparsers.add_parser( + "rev-parse", + help="Parse revision (or other objects) identifiers") + +argsp.add_argument("--wyag-type", + metavar="type", + dest="type", + choices=["blob", "commit", "tag", "tree"], + default=None, + help="Specify the expected type") + +argsp.add_argument("name", + help="The name to parse") + +def cmd_rev_parse(args): + if args.type: + fmt = args.type.encode() + else: + fmt = None + + repo = repo_find() + + print(object_find(repo, args.name, fmt, follow=True))