github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libgit/git_config_without_remotes_storer.go (about)

     1  // Copyright 2017 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libgit
     6  
     7  import (
     8  	"github.com/keybase/client/go/kbfs/libfs"
     9  	gogitcfg "gopkg.in/src-d/go-git.v4/config"
    10  	format "gopkg.in/src-d/go-git.v4/plumbing/format/config"
    11  	"gopkg.in/src-d/go-git.v4/plumbing/storer"
    12  	"gopkg.in/src-d/go-git.v4/storage"
    13  	"gopkg.in/src-d/go-git.v4/storage/filesystem"
    14  )
    15  
    16  // GitConfigWithoutRemotesStorer strips remotes from the config before
    17  // writing them to disk, to work around a gcfg bug (used by go-git
    18  // when reading configs from disk) that causes a freakout when it sees
    19  // backslashes in git file URLs.
    20  type GitConfigWithoutRemotesStorer struct {
    21  	*filesystem.Storage
    22  	cfg    *gogitcfg.Config
    23  	stored bool
    24  }
    25  
    26  // NewGitConfigWithoutRemotesStorer creates a new git config
    27  // implementation that strips remotes from the config before writing
    28  // them to disk.
    29  func NewGitConfigWithoutRemotesStorer(fs *libfs.FS) (
    30  	*GitConfigWithoutRemotesStorer, error) {
    31  	fsStorer, err := filesystem.NewStorage(fs)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  	cfg, err := fsStorer.Config()
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  	// To figure out if this config has been written already, check if
    40  	// it differs from the zero Core value (probably because the
    41  	// IsBare bit is flipped).
    42  	return &GitConfigWithoutRemotesStorer{
    43  		fsStorer,
    44  		cfg,
    45  		cfg.Core != gogitcfg.Config{}.Core,
    46  	}, nil
    47  }
    48  
    49  // Init implements the `storer.Initializer` interface.
    50  func (cwrs *GitConfigWithoutRemotesStorer) Init() error {
    51  	return cwrs.Storage.Init()
    52  }
    53  
    54  // Config implements the `storer.Storer` interface.
    55  func (cwrs *GitConfigWithoutRemotesStorer) Config() (*gogitcfg.Config, error) {
    56  	return cwrs.cfg, nil
    57  }
    58  
    59  // SetConfig implements the `storer.Storer` interface.
    60  func (cwrs *GitConfigWithoutRemotesStorer) SetConfig(c *gogitcfg.Config) (
    61  	err error) {
    62  	if cwrs.stored && c.Core == cwrs.cfg.Core {
    63  		// Ignore any change that doesn't change the core we know
    64  		// about, to avoid attempting to write config files with
    65  		// read-only access.
    66  		return nil
    67  	}
    68  
    69  	defer func() {
    70  		if err != nil {
    71  			cwrs.stored = true
    72  		}
    73  	}()
    74  
    75  	cwrs.cfg = c
    76  	if len(c.Remotes) != 0 {
    77  		// If there are remotes, we need to strip them out before
    78  		// writing them out to disk.  Do that by making a copy of
    79  		// everything but the remotes.  (Note that we can't just
    80  		// Marshal+Unmarshal for a deep-copy, since Unmarshal is where
    81  		// the gcfg bug is.)
    82  		cCopy := gogitcfg.NewConfig()
    83  		cCopy.Core = c.Core
    84  		for k, v := range c.Submodules {
    85  			v2 := *v
    86  			cCopy.Submodules[k] = &v2
    87  		}
    88  
    89  		// Get the raw config so we don't lose any unsupported fields
    90  		// from c, but clear out the remotes.
    91  		_, err := c.Marshal()
    92  		if err != nil {
    93  			return err
    94  		}
    95  		s := c.Raw.Section("remote")
    96  		s.Subsections = make(format.Subsections, 0)
    97  		cCopy.Raw = c.Raw
    98  
    99  		c = cCopy
   100  	}
   101  	return cwrs.Storage.SetConfig(c)
   102  }
   103  
   104  var _ storage.Storer = (*GitConfigWithoutRemotesStorer)(nil)
   105  var _ storer.Initializer = (*GitConfigWithoutRemotesStorer)(nil)