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 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 { if err != nil {
return err return err
} }

View File

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

View File

@@ -37,6 +37,7 @@ type AddEvent struct {
} }
const ( const (
pinNameOptionName = "pin-name"
quietOptionName = "quiet" quietOptionName = "quiet"
quieterOptionName = "quieter" quieterOptionName = "quieter"
silentOptionName = "silent" 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.BoolOption(inlineOptionName, "Inline small blocks into CIDs. (experimental)"),
cmds.IntOption(inlineLimitOptionName, "Maximum block size to inline. (experimental)").WithDefault(32), 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.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.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(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)"), 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) silent, _ := req.Options[silentOptionName].(bool)
chunker, _ := req.Options[chunkerOptionName].(string) chunker, _ := req.Options[chunkerOptionName].(string)
dopin, _ := req.Options[pinOptionName].(bool) dopin, _ := req.Options[pinOptionName].(bool)
pinName, pinNameSet := req.Options[pinNameOptionName].(string)
rawblks, rbset := req.Options[rawLeavesOptionName].(bool) rawblks, rbset := req.Options[rawLeavesOptionName].(bool)
maxFileLinks, maxFileLinksSet := req.Options[maxFileLinksOptionName].(int) maxFileLinks, maxFileLinksSet := req.Options[maxFileLinksOptionName].(int)
maxDirectoryLinks, maxDirectoryLinksSet := req.Options[maxDirectoryLinksOptionName].(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)) 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 { if !rbset && cfg.Import.UnixFSRawLeaves != config.Default {
rbset = true rbset = true
rawblks = cfg.Import.UnixFSRawLeaves.WithDefault(config.DefaultUnixFSRawLeaves) rawblks = cfg.Import.UnixFSRawLeaves.WithDefault(config.DefaultUnixFSRawLeaves)
@@ -296,7 +301,9 @@ See 'dag export' and 'dag import' for more information.
if onlyHash && toFilesSet { if onlyHash && toFilesSet {
return fmt.Errorf("%s and %s options are not compatible", onlyHashOptionName, toFilesOptionName) 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 { if wrap && toFilesSet {
return fmt.Errorf("%s and %s options are not compatible", wrapOptionName, toFilesOptionName) 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.Chunker(chunker),
options.Unixfs.Pin(dopin), options.Unixfs.Pin(dopin, pinName),
options.Unixfs.HashOnly(onlyHash), options.Unixfs.HashOnly(onlyHash),
options.Unixfs.FsCache(fscache), options.Unixfs.FsCache(fscache),
options.Unixfs.Nocopy(nocopy), 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))) 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 { if err != nil {
t.Fatal(err) 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.Bool("maxhamtfanoutset", settings.MaxHAMTFanoutSet),
attribute.Int("layout", int(settings.Layout)), attribute.Int("layout", int(settings.Layout)),
attribute.Bool("pin", settings.Pin), attribute.Bool("pin", settings.Pin),
attribute.String("pin-name", settings.PinName),
attribute.Bool("onlyhash", settings.OnlyHash), attribute.Bool("onlyhash", settings.OnlyHash),
attribute.Bool("fscache", settings.FsCache), attribute.Bool("fscache", settings.FsCache),
attribute.Bool("nocopy", settings.NoCopy), 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.Progress = settings.Progress
} }
fileAdder.Pin = settings.Pin && !settings.OnlyHash fileAdder.Pin = settings.Pin && !settings.OnlyHash
if settings.Pin {
fileAdder.PinName = settings.PinName
}
fileAdder.Silent = settings.Silent fileAdder.Silent = settings.Silent
fileAdder.RawLeaves = settings.RawLeaves fileAdder.RawLeaves = settings.RawLeaves
if settings.MaxFileLinksSet { if settings.MaxFileLinksSet {

View File

@@ -39,6 +39,7 @@ type UnixfsAddSettings struct {
Layout Layout Layout Layout
Pin bool Pin bool
PinName string
OnlyHash bool OnlyHash bool
FsCache bool FsCache bool
NoCopy bool NoCopy bool
@@ -83,6 +84,7 @@ func UnixfsAddOptions(opts ...UnixfsAddOption) (*UnixfsAddSettings, cid.Prefix,
Layout: BalancedLayout, Layout: BalancedLayout,
Pin: false, Pin: false,
PinName: "",
OnlyHash: false, OnlyHash: false,
FsCache: false, FsCache: false,
NoCopy: 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 // 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 { return func(settings *UnixfsAddSettings) error {
settings.Pin = pin settings.Pin = pin
if pin {
settings.PinName = pinName
}
return nil return nil
} }
} }

View File

@@ -539,7 +539,7 @@ func (tp *TestSuite) TestAddPinned(t *testing.T) {
t.Fatal(err) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

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

View File

@@ -11,6 +11,7 @@ This release was brought to you by the [Interplanetary Shipyard](https://ipship
- [Overview](#overview) - [Overview](#overview)
- [🔦 Highlights](#-highlights) - [🔦 Highlights](#-highlights)
- [Clear provide queue when reprovide strategy changes](#clear-provide-queue-when-reprovide-strategy-changes) - [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) - [Removed unnecessary dependencies](#removed-unnecessary-dependencies)
- [📦️ Important dependency updates](#-important-dependency-updates) - [📦️ Important dependency updates](#-important-dependency-updates)
- [📝 Changelog](#-changelog) - [📝 Changelog](#-changelog)
@@ -31,6 +32,18 @@ A new `ipfs provide clear` command also allows manual queue clearing for debuggi
> [!NOTE] > [!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. > 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 #### Removed unnecessary dependencies
Kubo has been cleaned up by removing unnecessary dependencies and packages: 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) 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.Run("produced unixfs max file links: command flag --max-file-links overrides configuration in Import.UnixFSFileMaxLinks", func(t *testing.T) {
t.Parallel() t.Parallel()