12 "github.com/git-lfs/git-lfs/config"
13 "github.com/git-lfs/git-lfs/errors"
14 "github.com/git-lfs/git-lfs/git"
15 "github.com/git-lfs/git-lfs/lfs"
16 "github.com/git-lfs/git-lfs/lfsapi"
17 "github.com/git-lfs/git-lfs/locking"
18 "github.com/git-lfs/git-lfs/progress"
19 "github.com/git-lfs/git-lfs/tools"
20 "github.com/git-lfs/git-lfs/tq"
21 "github.com/rubyist/tracerx"
24 func uploadLeftOrAll(g *lfs.GitScanner, ctx *uploadContext, ref string) error {
26 if err := g.ScanRefWithDeleted(ref, nil); err != nil {
30 if err := g.ScanLeftToRemote(ref, nil); err != nil {
34 return ctx.scannerError()
37 type uploadContext struct {
41 uploadedOids tools.StringSet
49 trackedLocksMu *sync.Mutex
51 // ALL verifiable locks
52 lockVerifyState verifyState
53 ourLocks map[string]locking.Lock
54 theirLocks map[string]locking.Lock
56 // locks from ourLocks that were modified in this push
57 ownedLocks []locking.Lock
59 // locks from theirLocks that were modified in this push
60 unownedLocks []locking.Lock
62 // allowMissing specifies whether pushes containing missing/corrupt
63 // pointers should allow pushing Git blobs
66 // tracks errors from gitscanner callbacks
71 // Determines if a filename is lockable. Serves as a wrapper around theirLocks
72 // that implements GitScannerSet.
73 type gitScannerLockables struct {
74 m map[string]locking.Lock
77 func (l *gitScannerLockables) Contains(name string) bool {
88 verifyStateUnknown verifyState = iota
93 func newUploadContext(remote string, dryRun bool) *uploadContext {
94 cfg.CurrentRemote = remote
96 ctx := &uploadContext{
98 Manifest: getTransferManifestOperationRemote("upload", remote),
100 uploadedOids: tools.NewStringSet(),
101 ourLocks: make(map[string]locking.Lock),
102 theirLocks: make(map[string]locking.Lock),
103 trackedLocksMu: new(sync.Mutex),
104 allowMissing: cfg.Git.Bool("lfs.allowincompletepush", true),
107 ctx.meter = buildProgressMeter(ctx.DryRun)
108 ctx.tq = newUploadQueue(ctx.Manifest, ctx.Remote, tq.WithProgress(ctx.meter), tq.DryRun(ctx.DryRun))
109 ctx.committerName, ctx.committerEmail = cfg.CurrentCommitter()
111 // Do not check locks for standalone transfer, because there is no LFS
113 if ctx.Manifest.IsStandaloneTransfer() {
114 ctx.lockVerifyState = verifyStateDisabled
118 ourLocks, theirLocks, verifyState := verifyLocks(remote)
119 ctx.lockVerifyState = verifyState
120 for _, l := range theirLocks {
121 ctx.theirLocks[l.Path] = l
123 for _, l := range ourLocks {
124 ctx.ourLocks[l.Path] = l
130 func verifyLocks(remote string) (ours, theirs []locking.Lock, st verifyState) {
131 endpoint := getAPIClient().Endpoints.Endpoint("upload", remote)
132 state := getVerifyStateFor(endpoint)
133 if state == verifyStateDisabled {
137 lockClient := newLockClient(remote)
139 ours, theirs, err := lockClient.VerifiableLocks(0)
141 if errors.IsNotImplementedError(err) {
143 } else if state == verifyStateUnknown || state == verifyStateEnabled {
144 if errors.IsAuthError(err) {
145 if state == verifyStateUnknown {
146 Error("WARNING: Authentication error: %s", err)
147 } else if state == verifyStateEnabled {
148 Exit("ERROR: Authentication error: %s", err)
151 Print("Remote %q does not support the LFS locking API. Consider disabling it with:", remote)
152 Print(" $ git config lfs.%s.locksverify false", endpoint.Url)
153 if state == verifyStateEnabled {
158 } else if state == verifyStateUnknown {
159 Print("Locking support detected on remote %q. Consider enabling it with:", remote)
160 Print(" $ git config lfs.%s.locksverify true", endpoint.Url)
163 return ours, theirs, state
166 func (c *uploadContext) scannerError() error {
168 defer c.errMu.Unlock()
173 func (c *uploadContext) addScannerError(err error) {
175 defer c.errMu.Unlock()
177 if c.scannerErr != nil {
178 c.scannerErr = fmt.Errorf("%v\n%v", c.scannerErr, err)
184 func (c *uploadContext) buildGitScanner() (*lfs.GitScanner, error) {
185 gitscanner := lfs.NewGitScanner(func(p *lfs.WrappedPointer, err error) {
187 c.addScannerError(err)
193 gitscanner.FoundLockable = func(name string) {
194 if lock, ok := c.theirLocks[name]; ok {
195 c.trackedLocksMu.Lock()
196 c.unownedLocks = append(c.unownedLocks, lock)
197 c.trackedLocksMu.Unlock()
201 gitscanner.PotentialLockables = &gitScannerLockables{m: c.theirLocks}
202 return gitscanner, gitscanner.RemoteForPush(c.Remote)
205 // AddUpload adds the given oid to the set of oids that have been uploaded in
206 // the current process.
207 func (c *uploadContext) SetUploaded(oid string) {
208 c.uploadedOids.Add(oid)
211 // HasUploaded determines if the given oid has already been uploaded in the
213 func (c *uploadContext) HasUploaded(oid string) bool {
214 return c.uploadedOids.Contains(oid)
217 func (c *uploadContext) prepareUpload(unfiltered ...*lfs.WrappedPointer) (*tq.TransferQueue, []*lfs.WrappedPointer) {
218 numUnfiltered := len(unfiltered)
219 uploadables := make([]*lfs.WrappedPointer, 0, numUnfiltered)
221 // XXX(taylor): temporary measure to fix duplicate (broken) results from
223 uniqOids := tools.NewStringSet()
225 // separate out objects that _should_ be uploaded, but don't exist in
226 // .git/lfs/objects. Those will skipped if the server already has them.
227 for _, p := range unfiltered {
228 // object already uploaded in this process, or we've already
229 // seen this OID (see above), skip!
230 if uniqOids.Contains(p.Oid) || c.HasUploaded(p.Oid) {
235 // canUpload determines whether the current pointer "p" can be
236 // uploaded through the TransferQueue below. It is set to false
237 // only when the file is locked by someone other than the
238 // current committer.
239 var canUpload bool = true
241 if lock, ok := c.theirLocks[p.Name]; ok {
242 c.trackedLocksMu.Lock()
243 c.unownedLocks = append(c.unownedLocks, lock)
244 c.trackedLocksMu.Unlock()
246 // If the verification state is enabled, this failed
247 // locks verification means that the push should fail.
249 // If the state is disabled, the verification error is
250 // silent and the user can upload.
252 // If the state is undefined, the verification error is
253 // sent as a warning and the user can upload.
254 canUpload = c.lockVerifyState != verifyStateEnabled
257 if lock, ok := c.ourLocks[p.Name]; ok {
258 c.trackedLocksMu.Lock()
259 c.ownedLocks = append(c.ownedLocks, lock)
260 c.trackedLocksMu.Unlock()
264 // estimate in meter early (even if it's not going into
265 // uploadables), since we will call Skip() based on the
266 // results of the download check queue.
269 uploadables = append(uploadables, p)
273 return c.tq, uploadables
276 func uploadPointers(c *uploadContext, unfiltered ...*lfs.WrappedPointer) {
278 for _, p := range unfiltered {
279 if c.HasUploaded(p.Oid) {
283 Print("push %s => %s", p.Oid, p.Name)
290 q, pointers := c.prepareUpload(unfiltered...)
291 for _, p := range pointers {
292 t, err := uploadTransfer(p, c.allowMissing)
293 if err != nil && !errors.IsCleanPointerError(err) {
297 q.Add(t.Name, t.Path, t.Oid, t.Size)
302 func (c *uploadContext) Await() {
305 var missing = make(map[string]string)
306 var corrupt = make(map[string]string)
307 var others = make([]error, 0, len(c.tq.Errors()))
309 for _, err := range c.tq.Errors() {
310 if malformed, ok := err.(*tq.MalformedObjectError); ok {
311 if malformed.Missing() {
312 missing[malformed.Name] = malformed.Oid
313 } else if malformed.Corrupt() {
314 corrupt[malformed.Name] = malformed.Oid
317 others = append(others, err)
321 for _, err := range others {
325 if len(missing) > 0 || len(corrupt) > 0 {
328 action = "missing objects"
333 Print("LFS upload %s:", action)
334 for name, oid := range missing {
335 Print(" (missing) %s (%s)", name, oid)
337 for name, oid := range corrupt {
338 Print(" (corrupt) %s (%s)", name, oid)
350 c.trackedLocksMu.Lock()
351 if ul := len(c.unownedLocks); ul > 0 {
352 Print("Unable to push %d locked file(s):", ul)
353 for _, unowned := range c.unownedLocks {
354 Print("* %s - %s", unowned.Path, unowned.Owner)
357 if c.lockVerifyState == verifyStateEnabled {
358 Exit("ERROR: Cannot update locked files.")
360 Error("WARNING: The above files would have halted this push.")
362 } else if len(c.ownedLocks) > 0 {
363 Print("Consider unlocking your own locked file(s): (`git lfs unlock <path>`)")
364 for _, owned := range c.ownedLocks {
365 Print("* %s", owned.Path)
368 c.trackedLocksMu.Unlock()
372 githubHttps, _ = url.Parse("https://github.com")
373 githubSsh, _ = url.Parse("ssh://github.com")
375 // hostsWithKnownLockingSupport is a list of scheme-less hostnames
376 // (without port numbers) that are known to implement the LFS locking
379 // Additions are welcome.
380 hostsWithKnownLockingSupport = []*url.URL{
381 githubHttps, githubSsh,
385 func uploadTransfer(p *lfs.WrappedPointer, allowMissing bool) (*tq.Transfer, error) {
389 localMediaPath, err := lfs.LocalMediaPath(oid)
391 return nil, errors.Wrapf(err, "Error uploading file %s (%s)", filename, oid)
394 if len(filename) > 0 {
395 if err = ensureFile(filename, localMediaPath, allowMissing); err != nil && !errors.IsCleanPointerError(err) {
402 Path: localMediaPath,
408 // ensureFile makes sure that the cleanPath exists before pushing it. If it
409 // does not exist, it attempts to clean it by reading the file at smudgePath.
410 func ensureFile(smudgePath, cleanPath string, allowMissing bool) error {
411 if _, err := os.Stat(cleanPath); err == nil {
415 localPath := filepath.Join(config.LocalWorkingDir, smudgePath)
416 file, err := os.Open(localPath)
426 stat, err := file.Stat()
431 cleaned, err := lfs.PointerClean(file, file.Name(), stat.Size(), nil)
442 // getVerifyStateFor returns whether or not lock verification is enabled for the
443 // given "endpoint". If no state has been explicitly set, an "unknown" state
444 // will be returned instead.
445 func getVerifyStateFor(endpoint lfsapi.Endpoint) verifyState {
446 uc := config.NewURLConfig(cfg.Git)
448 v, ok := uc.Get("lfs", endpoint.Url, "locksverify")
450 if supportsLockingAPI(endpoint) {
451 return verifyStateEnabled
453 return verifyStateUnknown
456 if enabled, _ := strconv.ParseBool(v); enabled {
457 return verifyStateEnabled
459 return verifyStateDisabled
462 // supportsLockingAPI returns whether or not a given lfsapi.Endpoint "e"
463 // is known to support the LFS locking API by whether or not its hostname is
464 // included in the list above.
465 func supportsLockingAPI(e lfsapi.Endpoint) bool {
466 u, err := url.Parse(e.Url)
468 tracerx.Printf("commands: unable to parse %q to determine locking support: %v", e.Url, err)
472 for _, supported := range hostsWithKnownLockingSupport {
473 if supported.Scheme == u.Scheme &&
474 supported.Hostname() == u.Hostname() &&
475 strings.HasPrefix(u.Path, supported.Path) {
482 // disableFor disables lock verification for the given lfsapi.Endpoint,
484 func disableFor(endpoint lfsapi.Endpoint) error {
485 tracerx.Printf("commands: disabling lock verification for %q", endpoint.Url)
487 key := strings.Join([]string{"lfs", endpoint.Url, "locksverify"}, ".")
489 _, err := git.Config.SetLocal("", key, "false")