Welcome to mirror list, hosted at ThFree Co, Russian Federation.

write_ref.go « repository « service « gitaly « internal - gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: b37e8d1a393b09a434f79fdd3b22eed8da5d3c67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package repository

import (
	"bytes"
	"context"
	"fmt"

	"gitlab.com/gitlab-org/gitaly/v16/internal/git"
	"gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo"
	"gitlab.com/gitlab-org/gitaly/v16/internal/git/updateref"
	"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly"
	"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/service"
	"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/txutil"
	"gitlab.com/gitlab-org/gitaly/v16/internal/structerr"
	"gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
)

func (s *server) WriteRef(ctx context.Context, req *gitalypb.WriteRefRequest) (*gitalypb.WriteRefResponse, error) {
	if err := validateWriteRefRequest(req); err != nil {
		return nil, structerr.NewInvalidArgument("%w", err)
	}

	if s.partitionManager != nil {
		if err := s.writeRefWAL(ctx, req); err != nil {
			return nil, err
		}
	} else {
		if err := s.writeRef(ctx, req); err != nil {
			return nil, structerr.NewInternal("%w", err)
		}
	}

	return &gitalypb.WriteRefResponse{}, nil
}

func (s *server) writeRef(ctx context.Context, req *gitalypb.WriteRefRequest) error {
	repo := s.localrepo(req.GetRepository())

	if string(req.Ref) == "HEAD" {
		if err := repo.SetDefaultBranch(ctx, s.txManager, git.ReferenceName(req.GetRevision())); err != nil {
			return fmt.Errorf("setting default branch: %w", err)
		}

		return nil
	}

	return updateRef(ctx, repo, req)
}

func (s *server) writeRefWAL(ctx context.Context, req *gitalypb.WriteRefRequest) error {
	tx, err := s.partitionManager.Begin(ctx, req.GetRepository())
	if err != nil {
		return fmt.Errorf("begin: %w", err)
	}
	defer txutil.LogRollback(ctx, tx)

	if string(req.Ref) == "HEAD" {
		tx.SetDefaultBranch(git.ReferenceName(req.GetRevision()))
	} else {
		oldOID, newOID, err := resolveObjectIDs(ctx, s.localrepo(req.GetRepository()), req)
		if err != nil {
			return err
		}

		tx.UpdateReferences(gitaly.ReferenceUpdates{
			git.ReferenceName(req.GetRef()): {Force: oldOID == "", OldOID: oldOID, NewOID: newOID},
		})
	}

	return tx.Commit(ctx)
}

func resolveObjectIDs(ctx context.Context, repo *localrepo.Repo, req *gitalypb.WriteRefRequest) (git.ObjectID, git.ObjectID, error) {
	var newObjectID git.ObjectID
	if git.ObjectHashSHA1.IsZeroOID(git.ObjectID(req.GetRevision())) {
		// Passing the all-zeroes object ID as new value means that we should delete the
		// reference.
		newObjectID = git.ObjectHashSHA1.ZeroOID
	} else {
		// We need to resolve the new revision in order to make sure that we're actually
		// passing an object ID to git-update-ref(1), but more importantly this will also
		// ensure that the object ID we're updating to actually exists. Note that we also
		// verify that the object actually exists in the repository by adding "^{object}".
		var err error
		newObjectID, err = repo.ResolveRevision(ctx, git.Revision(req.GetRevision())+"^{object}")
		if err != nil {
			return "", "", fmt.Errorf("resolving new revision: %w", err)
		}
	}

	var oldObjectID git.ObjectID
	if len(req.GetOldRevision()) > 0 {
		if git.ObjectHashSHA1.IsZeroOID(git.ObjectID(req.GetOldRevision())) {
			// Passing an all-zeroes object ID indicates that we should only update the
			// reference if it didn't previously exist.
			oldObjectID = git.ObjectHashSHA1.ZeroOID
		} else {
			var err error
			oldObjectID, err = repo.ResolveRevision(ctx, git.Revision(req.GetOldRevision())+"^{object}")
			if err != nil {
				return "", "", fmt.Errorf("resolving old revision: %w", err)
			}
		}
	}

	return oldObjectID, newObjectID, nil
}

func updateRef(ctx context.Context, repo *localrepo.Repo, req *gitalypb.WriteRefRequest) (returnedErr error) {
	oldObjectID, newObjectID, err := resolveObjectIDs(ctx, repo, req)
	if err != nil {
		return err
	}

	u, err := updateref.New(ctx, repo)
	if err != nil {
		return fmt.Errorf("error when running creating new updater: %w", err)
	}
	defer func() {
		if err := u.Close(); err != nil && returnedErr == nil {
			returnedErr = fmt.Errorf("close updater: %w", err)
		}
	}()

	if err := u.Start(); err != nil {
		return fmt.Errorf("start reference transaction: %w", err)
	}

	if err = u.Update(git.ReferenceName(req.GetRef()), newObjectID, oldObjectID); err != nil {
		return fmt.Errorf("error when creating update-ref command: %w", err)
	}

	if err = u.Commit(); err != nil {
		return fmt.Errorf("error when running update-ref command: %w", err)
	}

	return nil
}

func validateWriteRefRequest(req *gitalypb.WriteRefRequest) error {
	if err := service.ValidateRepository(req.GetRepository()); err != nil {
		return err
	}
	if err := git.ValidateRevision(req.Ref); err != nil {
		return fmt.Errorf("invalid ref: %w", err)
	}
	if err := git.ValidateRevision(req.Revision); err != nil {
		return fmt.Errorf("invalid revision: %w", err)
	}
	if len(req.OldRevision) > 0 {
		if err := git.ValidateRevision(req.OldRevision); err != nil {
			return fmt.Errorf("invalid OldRevision: %w", err)
		}
	}

	if !bytes.Equal(req.Ref, []byte("HEAD")) && !bytes.HasPrefix(req.Ref, []byte("refs/")) {
		return fmt.Errorf("ref has to be a full reference")
	}
	return nil
}