1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-12-15 14:11:14 +08:00

feat(add): add support for naming pinned CIDs (#10877)

* feat(add): add support for naming pinned CID

Signed-off-by: kapil <kapilsareen584@gmail.com>

* fix(add): no double pinning and simplify pin-name

- modify PinRoot to accept name parameter, eliminating double pinning
- remove automatic filename fallback logic for cleaner behavior
- only create named pins when explicitly requested via --pin-name=value
- replace NoPinName constant with idiomatic empty string literals
- Update help text and tests to reflect explicit-only behavior

* docs: changelog

* chore: lint

* test: negative case for empty pin-name

* chore: gofmt

---------

Signed-off-by: kapil <kapilsareen584@gmail.com>
Co-authored-by: Marcin Rataj <lidel@lidel.org>
This commit is contained in:
Kapil Sareen
2025-08-06 05:46:16 +05:30
committed by GitHub
parent 90b73d2ad2
commit 10abb9073d
10 changed files with 86 additions and 13 deletions

View File

@@ -86,7 +86,7 @@ func addMigrationFiles(ctx context.Context, node *core.IpfsNode, paths []string,
return err
}
ipfsPath, err := ufs.Add(ctx, files.NewReaderStatFile(f, fi), options.Unixfs.Pin(pin))
ipfsPath, err := ufs.Add(ctx, files.NewReaderStatFile(f, fi), options.Unixfs.Pin(pin, ""))
if err != nil {
return err
}

View File

@@ -21,7 +21,6 @@ const (
// write-batch. The total size of the batch is limited by
// BatchMaxnodes and BatchMaxSize.
DefaultBatchMaxSize = 100 << 20 // 20MiB
)
var (

View File

@@ -37,6 +37,7 @@ type AddEvent struct {
}
const (
pinNameOptionName = "pin-name"
quietOptionName = "quiet"
quieterOptionName = "quieter"
silentOptionName = "silent"
@@ -184,6 +185,7 @@ See 'dag export' and 'dag import' for more information.
cmds.BoolOption(inlineOptionName, "Inline small blocks into CIDs. (experimental)"),
cmds.IntOption(inlineLimitOptionName, "Maximum block size to inline. (experimental)").WithDefault(32),
cmds.BoolOption(pinOptionName, "Pin locally to protect added files from garbage collection.").WithDefault(true),
cmds.StringOption(pinNameOptionName, "Name to use for the pin. Requires explicit value (e.g., --pin-name=myname)."),
cmds.StringOption(toFilesOptionName, "Add reference to Files API (MFS) at the provided path."),
cmds.BoolOption(preserveModeOptionName, "Apply existing POSIX permissions to created UnixFS entries. Disables raw-leaves. (experimental)"),
cmds.BoolOption(preserveMtimeOptionName, "Apply existing POSIX modification time to created UnixFS entries. Disables raw-leaves. (experimental)"),
@@ -230,6 +232,7 @@ See 'dag export' and 'dag import' for more information.
silent, _ := req.Options[silentOptionName].(bool)
chunker, _ := req.Options[chunkerOptionName].(string)
dopin, _ := req.Options[pinOptionName].(bool)
pinName, pinNameSet := req.Options[pinNameOptionName].(string)
rawblks, rbset := req.Options[rawLeavesOptionName].(bool)
maxFileLinks, maxFileLinksSet := req.Options[maxFileLinksOptionName].(int)
maxDirectoryLinks, maxDirectoryLinksSet := req.Options[maxDirectoryLinksOptionName].(int)
@@ -260,6 +263,8 @@ See 'dag export' and 'dag import' for more information.
cidVer = int(cfg.Import.CidVersion.WithDefault(config.DefaultCidVersion))
}
// Pin names are only used when explicitly provided via --pin-name=value
if !rbset && cfg.Import.UnixFSRawLeaves != config.Default {
rbset = true
rawblks = cfg.Import.UnixFSRawLeaves.WithDefault(config.DefaultUnixFSRawLeaves)
@@ -296,7 +301,9 @@ See 'dag export' and 'dag import' for more information.
if onlyHash && toFilesSet {
return fmt.Errorf("%s and %s options are not compatible", onlyHashOptionName, toFilesOptionName)
}
if !dopin && pinNameSet {
return fmt.Errorf("%s option requires %s to be set", pinNameOptionName, pinOptionName)
}
if wrap && toFilesSet {
return fmt.Errorf("%s and %s options are not compatible", wrapOptionName, toFilesOptionName)
}
@@ -326,7 +333,7 @@ See 'dag export' and 'dag import' for more information.
options.Unixfs.Chunker(chunker),
options.Unixfs.Pin(dopin),
options.Unixfs.Pin(dopin, pinName),
options.Unixfs.HashOnly(onlyHash),
options.Unixfs.FsCache(fscache),
options.Unixfs.Nocopy(nocopy),

View File

@@ -39,7 +39,7 @@ func TestPathUnixFSHAMTPartial(t *testing.T) {
dir[strconv.Itoa(i)] = files.NewBytesFile([]byte(strconv.Itoa(i)))
}
r, err := a.Unixfs().Add(ctx, files.NewMapDirectory(dir), options.Unixfs.Pin(false))
r, err := a.Unixfs().Add(ctx, files.NewMapDirectory(dir), options.Unixfs.Pin(false, ""))
if err != nil {
t.Fatal(err)
}

View File

@@ -58,6 +58,7 @@ func (api *UnixfsAPI) Add(ctx context.Context, files files.Node, opts ...options
attribute.Bool("maxhamtfanoutset", settings.MaxHAMTFanoutSet),
attribute.Int("layout", int(settings.Layout)),
attribute.Bool("pin", settings.Pin),
attribute.String("pin-name", settings.PinName),
attribute.Bool("onlyhash", settings.OnlyHash),
attribute.Bool("fscache", settings.FsCache),
attribute.Bool("nocopy", settings.NoCopy),
@@ -136,6 +137,9 @@ func (api *UnixfsAPI) Add(ctx context.Context, files files.Node, opts ...options
fileAdder.Progress = settings.Progress
}
fileAdder.Pin = settings.Pin && !settings.OnlyHash
if settings.Pin {
fileAdder.PinName = settings.PinName
}
fileAdder.Silent = settings.Silent
fileAdder.RawLeaves = settings.RawLeaves
if settings.MaxFileLinksSet {

View File

@@ -39,6 +39,7 @@ type UnixfsAddSettings struct {
Layout Layout
Pin bool
PinName string
OnlyHash bool
FsCache bool
NoCopy bool
@@ -83,6 +84,7 @@ func UnixfsAddOptions(opts ...UnixfsAddOption) (*UnixfsAddSettings, cid.Prefix,
Layout: BalancedLayout,
Pin: false,
PinName: "",
OnlyHash: false,
FsCache: false,
NoCopy: false,
@@ -280,9 +282,12 @@ func (unixfsOpts) Layout(layout Layout) UnixfsAddOption {
}
// Pin tells the adder to pin the file root recursively after adding
func (unixfsOpts) Pin(pin bool) UnixfsAddOption {
func (unixfsOpts) Pin(pin bool, pinName string) UnixfsAddOption {
return func(settings *UnixfsAddSettings) error {
settings.Pin = pin
if pin {
settings.PinName = pinName
}
return nil
}
}

View File

@@ -539,7 +539,7 @@ func (tp *TestSuite) TestAddPinned(t *testing.T) {
t.Fatal(err)
}
_, err = api.Unixfs().Add(ctx, strFile(helloStr)(), options.Unixfs.Pin(true))
_, err = api.Unixfs().Add(ctx, strFile(helloStr)(), options.Unixfs.Pin(true, ""))
if err != nil {
t.Fatal(err)
}

View File

@@ -76,6 +76,7 @@ type Adder struct {
Out chan<- interface{}
Progress bool
Pin bool
PinName string
Trickle bool
RawLeaves bool
MaxLinks int
@@ -182,9 +183,10 @@ func (adder *Adder) curRootNode() (ipld.Node, error) {
return root, err
}
// Recursively pins the root node of Adder and
// writes the pin state to the backing datastore.
func (adder *Adder) PinRoot(ctx context.Context, root ipld.Node) error {
// PinRoot recursively pins the root node of Adder with an optional name and
// writes the pin state to the backing datastore. If name is empty, the pin
// will be created without a name.
func (adder *Adder) PinRoot(ctx context.Context, root ipld.Node, name string) error {
ctx, span := tracing.Span(ctx, "CoreUnix.Adder", "PinRoot")
defer span.End()
@@ -207,7 +209,7 @@ func (adder *Adder) PinRoot(ctx context.Context, root ipld.Node) error {
adder.tempRoot = rnk
}
err = adder.pinning.PinWithMode(ctx, rnk, pin.Recursive, "")
err = adder.pinning.PinWithMode(ctx, rnk, pin.Recursive, name)
if err != nil {
return err
}
@@ -369,7 +371,12 @@ func (adder *Adder) AddAllAndPin(ctx context.Context, file files.Node) (ipld.Nod
if !adder.Pin {
return nd, nil
}
return nd, adder.PinRoot(ctx, nd)
if err := adder.PinRoot(ctx, nd, adder.PinName); err != nil {
return nil, err
}
return nd, nil
}
func (adder *Adder) addFileNode(ctx context.Context, path string, file files.Node, toplevel bool) error {
@@ -530,7 +537,7 @@ func (adder *Adder) maybePauseForGC(ctx context.Context) error {
return err
}
err = adder.PinRoot(ctx, rn)
err = adder.PinRoot(ctx, rn, "")
if err != nil {
return err
}

View File

@@ -11,6 +11,7 @@ This release was brought to you by the [Interplanetary Shipyard](https://ipship
- [Overview](#overview)
- [🔦 Highlights](#-highlights)
- [Clear provide queue when reprovide strategy changes](#clear-provide-queue-when-reprovide-strategy-changes)
- [Named pins in `ipfs add` command](#-named-pins-in-ipfs-add-command)
- [Removed unnecessary dependencies](#removed-unnecessary-dependencies)
- [📦️ Important dependency updates](#-important-dependency-updates)
- [📝 Changelog](#-changelog)
@@ -31,6 +32,18 @@ A new `ipfs provide clear` command also allows manual queue clearing for debuggi
> [!NOTE]
> Upgrading to Kubo 0.37 will automatically clear any preexisting provide queue. The next time `Reprovider.Interval` hits, `Reprovider.Strategy` will be executed on a clean slate, ensuring consistent behavior with your current configuration.
#### 🧷 Named pins in `ipfs add` command
Added `--pin-name` flag to `ipfs add` for assigning names to pins.
```console
$ ipfs add --pin-name=testname cat.jpg
added bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi cat.jpg
$ ipfs pin ls --names
bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi recursive testname
```
#### Removed unnecessary dependencies
Kubo has been cleaned up by removing unnecessary dependencies and packages:

View File

@@ -108,6 +108,44 @@ func TestAdd(t *testing.T) {
require.Equal(t, shortStringCidV1NoRawLeaves, cidStr)
})
t.Run("ipfs add --pin-name=foo", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init().StartDaemon()
defer node.StopDaemon()
pinName := "test-pin-name"
cidStr := node.IPFSAddStr(shortString, "--pin-name", pinName)
require.Equal(t, shortStringCidV0, cidStr)
pinList := node.IPFS("pin", "ls", "--names").Stdout.Trimmed()
require.Contains(t, pinList, shortStringCidV0)
require.Contains(t, pinList, pinName)
})
t.Run("ipfs add --pin=false --pin-name=foo returns an error", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init().StartDaemon()
defer node.StopDaemon()
// Use RunIPFS to allow for errors without assertion
result := node.RunIPFS("add", "--pin=false", "--pin-name=foo")
require.Error(t, result.Err, "Expected an error due to incompatible --pin and --pin-name")
require.Contains(t, result.Stderr.String(), "pin-name option requires pin to be set")
})
t.Run("ipfs add --pin-name without value should fail", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init().StartDaemon()
defer node.StopDaemon()
// When --pin-name is passed without any value, it should fail
result := node.RunIPFS("add", "--pin-name")
require.Error(t, result.Err, "Expected an error when --pin-name has no value")
require.Contains(t, result.Stderr.String(), "missing argument for option \"pin-name\"")
})
t.Run("produced unixfs max file links: command flag --max-file-links overrides configuration in Import.UnixFSFileMaxLinks", func(t *testing.T) {
t.Parallel()