1// Copyright 2020 The Gitea Authors. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5//go:build !gogit
6// +build !gogit
7
8package git
9
10import (
11	"bufio"
12	"errors"
13	"io"
14	"strings"
15
16	"code.gitea.io/gitea/modules/log"
17)
18
19// ResolveReference resolves a name to a reference
20func (repo *Repository) ResolveReference(name string) (string, error) {
21	stdout, err := NewCommandContext(repo.Ctx, "show-ref", "--hash", name).RunInDir(repo.Path)
22	if err != nil {
23		if strings.Contains(err.Error(), "not a valid ref") {
24			return "", ErrNotExist{name, ""}
25		}
26		return "", err
27	}
28	stdout = strings.TrimSpace(stdout)
29	if stdout == "" {
30		return "", ErrNotExist{name, ""}
31	}
32
33	return stdout, nil
34}
35
36// GetRefCommitID returns the last commit ID string of given reference (branch or tag).
37func (repo *Repository) GetRefCommitID(name string) (string, error) {
38	wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
39	defer cancel()
40	_, err := wr.Write([]byte(name + "\n"))
41	if err != nil {
42		return "", err
43	}
44	shaBs, _, _, err := ReadBatchLine(rd)
45	if IsErrNotExist(err) {
46		return "", ErrNotExist{name, ""}
47	}
48
49	return string(shaBs), nil
50}
51
52// SetReference sets the commit ID string of given reference (e.g. branch or tag).
53func (repo *Repository) SetReference(name, commitID string) error {
54	_, err := NewCommandContext(repo.Ctx, "update-ref", name, commitID).RunInDir(repo.Path)
55	return err
56}
57
58// RemoveReference removes the given reference (e.g. branch or tag).
59func (repo *Repository) RemoveReference(name string) error {
60	_, err := NewCommandContext(repo.Ctx, "update-ref", "--no-deref", "-d", name).RunInDir(repo.Path)
61	return err
62}
63
64// IsCommitExist returns true if given commit exists in current repository.
65func (repo *Repository) IsCommitExist(name string) bool {
66	_, err := NewCommandContext(repo.Ctx, "cat-file", "-e", name).RunInDir(repo.Path)
67	return err == nil
68}
69
70func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
71	wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
72	defer cancel()
73
74	_, _ = wr.Write([]byte(id.String() + "\n"))
75
76	return repo.getCommitFromBatchReader(rd, id)
77}
78
79func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Commit, error) {
80	_, typ, size, err := ReadBatchLine(rd)
81	if err != nil {
82		if errors.Is(err, io.EOF) || IsErrNotExist(err) {
83			return nil, ErrNotExist{ID: id.String()}
84		}
85		return nil, err
86	}
87
88	switch typ {
89	case "missing":
90		return nil, ErrNotExist{ID: id.String()}
91	case "tag":
92		// then we need to parse the tag
93		// and load the commit
94		data, err := io.ReadAll(io.LimitReader(rd, size))
95		if err != nil {
96			return nil, err
97		}
98		_, err = rd.Discard(1)
99		if err != nil {
100			return nil, err
101		}
102		tag, err := parseTagData(data)
103		if err != nil {
104			return nil, err
105		}
106
107		commit, err := tag.Commit(repo)
108		if err != nil {
109			return nil, err
110		}
111
112		commit.CommitMessage = strings.TrimSpace(tag.Message)
113		commit.Author = tag.Tagger
114		commit.Signature = tag.Signature
115
116		return commit, nil
117	case "commit":
118		commit, err := CommitFromReader(repo, id, io.LimitReader(rd, size))
119		if err != nil {
120			return nil, err
121		}
122		_, err = rd.Discard(1)
123		if err != nil {
124			return nil, err
125		}
126
127		return commit, nil
128	default:
129		log.Debug("Unknown typ: %s", typ)
130		_, err = rd.Discard(int(size) + 1)
131		if err != nil {
132			return nil, err
133		}
134		return nil, ErrNotExist{
135			ID: id.String(),
136		}
137	}
138}
139
140// ConvertToSHA1 returns a Hash object from a potential ID string
141func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
142	if len(commitID) == 40 && SHAPattern.MatchString(commitID) {
143		sha1, err := NewIDFromString(commitID)
144		if err == nil {
145			return sha1, nil
146		}
147	}
148
149	wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
150	defer cancel()
151	_, err := wr.Write([]byte(commitID + "\n"))
152	if err != nil {
153		return SHA1{}, err
154	}
155	sha, _, _, err := ReadBatchLine(rd)
156	if err != nil {
157		if IsErrNotExist(err) {
158			return SHA1{}, ErrNotExist{commitID, ""}
159		}
160		return SHA1{}, err
161	}
162
163	return MustIDFromString(string(sha)), nil
164}
165