aboutsummaryrefslogtreecommitdiffstats
path: root/modules/repofiles/upload.go
blob: 2da101c64dad673beb24594fc3bbd5d485163604 (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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package repofiles

import (
	"fmt"
	"os"
	"path"
	"strings"

	"code.gitea.io/gitea/models"
	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/lfs"
	"code.gitea.io/gitea/modules/setting"
)

// UploadRepoFileOptions contains the uploaded repository file options
type UploadRepoFileOptions struct {
	LastCommitID string
	OldBranch    string
	NewBranch    string
	TreePath     string
	Message      string
	Files        []string // In UUID format.
}

type uploadInfo struct {
	upload        *models.Upload
	lfsMetaObject *models.LFSMetaObject
}

func cleanUpAfterFailure(infos *[]uploadInfo, t *TemporaryUploadRepository, original error) error {
	for _, info := range *infos {
		if info.lfsMetaObject == nil {
			continue
		}
		if !info.lfsMetaObject.Existing {
			if err := t.repo.RemoveLFSMetaObjectByOid(info.lfsMetaObject.Oid); err != nil {
				original = fmt.Errorf("%v, %v", original, err)
			}
		}
	}
	return original
}

// UploadRepoFiles uploads files to the given repository
func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRepoFileOptions) error {
	if len(opts.Files) == 0 {
		return nil
	}

	uploads, err := models.GetUploadsByUUIDs(opts.Files)
	if err != nil {
		return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %v", opts.Files, err)
	}

	t, err := NewTemporaryUploadRepository(repo)
	if err != nil {
		return err
	}
	defer t.Close()
	if err := t.Clone(opts.OldBranch); err != nil {
		return err
	}
	if err := t.SetDefaultIndex(); err != nil {
		return err
	}

	names := make([]string, len(uploads))
	infos := make([]uploadInfo, len(uploads))
	for i, upload := range uploads {
		names[i] = upload.Name
		infos[i] = uploadInfo{upload: upload}
	}

	filename2attribute2info, err := t.CheckAttribute("filter", names...)
	if err != nil {
		return err
	}

	// Copy uploaded files into repository.
	for i, uploadInfo := range infos {
		file, err := os.Open(uploadInfo.upload.LocalPath())
		if err != nil {
			return err
		}
		defer file.Close()

		var objectHash string
		if filename2attribute2info[uploadInfo.upload.Name] != nil && filename2attribute2info[uploadInfo.upload.Name]["filter"] == "lfs" {
			// Handle LFS
			// FIXME: Inefficient! this should probably happen in models.Upload
			oid, err := models.GenerateLFSOid(file)
			if err != nil {
				return err
			}
			fileInfo, err := file.Stat()
			if err != nil {
				return err
			}

			uploadInfo.lfsMetaObject = &models.LFSMetaObject{Oid: oid, Size: fileInfo.Size(), RepositoryID: t.repo.ID}

			if objectHash, err = t.HashObject(strings.NewReader(uploadInfo.lfsMetaObject.Pointer())); err != nil {
				return err
			}
			infos[i] = uploadInfo

		} else if objectHash, err = t.HashObject(file); err != nil {
			return err
		}

		// Add the object to the index
		if err := t.AddObjectToIndex("100644", objectHash, path.Join(opts.TreePath, uploadInfo.upload.Name)); err != nil {
			return err

		}
	}

	// Now write the tree
	treeHash, err := t.WriteTree()
	if err != nil {
		return err
	}

	// make author and committer the doer
	author := doer
	committer := doer

	// Now commit the tree
	commitHash, err := t.CommitTree(author, committer, treeHash, opts.Message)
	if err != nil {
		return err
	}

	// Now deal with LFS objects
	for _, uploadInfo := range infos {
		if uploadInfo.lfsMetaObject == nil {
			continue
		}
		uploadInfo.lfsMetaObject, err = models.NewLFSMetaObject(uploadInfo.lfsMetaObject)
		if err != nil {
			// OK Now we need to cleanup
			return cleanUpAfterFailure(&infos, t, err)
		}
		// Don't move the files yet - we need to ensure that
		// everything can be inserted first
	}

	// OK now we can insert the data into the store - there's no way to clean up the store
	// once it's in there, it's in there.
	contentStore := &lfs.ContentStore{BasePath: setting.LFS.ContentPath}
	for _, uploadInfo := range infos {
		if uploadInfo.lfsMetaObject == nil {
			continue
		}
		if !contentStore.Exists(uploadInfo.lfsMetaObject) {
			file, err := os.Open(uploadInfo.upload.LocalPath())
			if err != nil {
				return cleanUpAfterFailure(&infos, t, err)
			}
			defer file.Close()
			// FIXME: Put regenerates the hash and copies the file over.
			// I guess this strictly ensures the soundness of the store but this is inefficient.
			if err := contentStore.Put(uploadInfo.lfsMetaObject, file); err != nil {
				// OK Now we need to cleanup
				// Can't clean up the store, once uploaded there they're there.
				return cleanUpAfterFailure(&infos, t, err)
			}
		}
	}

	// Then push this tree to NewBranch
	if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
		return err
	}

	// Simulate push event.
	oldCommitID := opts.LastCommitID
	if opts.NewBranch != opts.OldBranch {
		oldCommitID = git.EmptySHA
	}

	if err = repo.GetOwner(); err != nil {
		return fmt.Errorf("GetOwner: %v", err)
	}
	err = PushUpdate(
		repo,
		opts.NewBranch,
		models.PushUpdateOptions{
			PusherID:     doer.ID,
			PusherName:   doer.Name,
			RepoUserName: repo.Owner.Name,
			RepoName:     repo.Name,
			RefFullName:  git.BranchPrefix + opts.NewBranch,
			OldCommitID:  oldCommitID,
			NewCommitID:  commitHash,
		},
	)
	if err != nil {
		return fmt.Errorf("PushUpdate: %v", err)
	}

	return models.DeleteUploads(uploads...)
}