diff --git a/CHANGELOG.md b/CHANGELOG.md index b592b58b311..362a6bdba67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # go-ipfs changelog +### 0.4.0 - 2016-01-31 + +* Features + * add optional path arguments to 'ipfs pin ls' (@chriscool) + +* Incompatible Changes + * the default for '--type' in 'ipfs pin ls' is now "all" (@chriscool) + + ### 0.3.10 - 2015-12-07 This patch update introduces the 'ipfs update' command which will be used for diff --git a/core/commands/pin.go b/core/commands/pin.go index e6155df9c00..4a43275e47e 100644 --- a/core/commands/pin.go +++ b/core/commands/pin.go @@ -7,8 +7,11 @@ import ( key "github.com/ipfs/go-ipfs/blocks/key" cmds "github.com/ipfs/go-ipfs/commands" + context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" + core "github.com/ipfs/go-ipfs/core" corerepo "github.com/ipfs/go-ipfs/core/corerepo" dag "github.com/ipfs/go-ipfs/merkledag" + path "github.com/ipfs/go-ipfs/path" u "github.com/ipfs/go-ipfs/util" ) @@ -158,11 +161,11 @@ var listPinCmd = &cmds.Command{ Tagline: "List objects pinned to local storage", ShortDescription: ` Returns a list of objects that are pinned locally. -By default, only recursively pinned returned, but others may be shown via the '--type' flag. +By default, all pinned objects are returned, but the '--type' flag or arguments can restrict that to a specific pin type or to some specific objects respectively. `, LongDescription: ` Returns a list of objects that are pinned locally. -By default, only recursively pinned returned, but others may be shown via the '--type' flag. +By default, all pinned objects are returned, but the '--type' flag or arguments can restrict that to a specific pin type or to some specific objects respectively. Use --type= to specify the type of pinned keys to list. Valid values are: * "direct": pin that specific object. @@ -170,19 +173,29 @@ Use --type= to specify the type of pinned keys to list. Valid values are: * "indirect": pinned indirectly by an ancestor (like a refcount) * "all" +With arguments, the command fails if any of the arguments is not a pinned object. +And if --type= is additionally used, the command will also fail if any of the arguments is not of the specified type. + Example: $ echo "hello" | ipfs add -q QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN $ ipfs pin ls - QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN + QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN recursive # now remove the pin, and repin it directly $ ipfs pin rm QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN + unpinned QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN $ ipfs pin add -r=false QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN + pinned QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN directly $ ipfs pin ls --type=direct - QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN + QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN direct + $ ipfs pin ls QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN + QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN direct `, }, + Arguments: []cmds.Argument{ + cmds.StringArg("ipfs-path", false, true, "Path to object(s) to be listed"), + }, Options: []cmds.Option{ cmds.StringOption("type", "t", "The type of pinned keys to list. Can be \"direct\", \"indirect\", \"recursive\", or \"all\". Defaults to \"recursive\""), cmds.BoolOption("count", "n", "Show refcount when listing indirect pins"), @@ -195,60 +208,37 @@ Example: return } - typeStr, found, err := req.Option("type").String() + typeStr, typeStrFound, err := req.Option("type").String() if err != nil { res.SetError(err, cmds.ErrNormal) return } - if !found { - typeStr = "recursive" - } - switch typeStr { - case "all", "direct", "indirect", "recursive": - default: - err = fmt.Errorf("Invalid type '%s', must be one of {direct, indirect, recursive, all}", typeStr) - res.SetError(err, cmds.ErrClient) - } - - keys := make(map[string]RefKeyObject) - if typeStr == "direct" || typeStr == "all" { - for _, k := range n.Pinning.DirectKeys() { - keys[k.B58String()] = RefKeyObject{ - Type: "direct", - } + if typeStrFound { + switch typeStr { + case "all", "direct", "indirect", "recursive": + default: + err = fmt.Errorf("Invalid type '%s', must be one of {direct, indirect, recursive, all}", typeStr) + res.SetError(err, cmds.ErrClient) + return } + } else { + typeStr = "all" } - if typeStr == "indirect" || typeStr == "all" { - ks := key.NewKeySet() - for _, k := range n.Pinning.RecursiveKeys() { - nd, err := n.DAG.Get(n.Context(), k) - if err != nil { - res.SetError(err, cmds.ErrNormal) - return - } - err = dag.EnumerateChildren(n.Context(), n.DAG, nd, ks) - if err != nil { - res.SetError(err, cmds.ErrNormal) - return - } - } - for _, k := range ks.Keys() { - keys[k.B58String()] = RefKeyObject{ - Type: "indirect", - } - } - } - if typeStr == "recursive" || typeStr == "all" { - for _, k := range n.Pinning.RecursiveKeys() { - keys[k.B58String()] = RefKeyObject{ - Type: "recursive", - } - } + var keys map[string]RefKeyObject + + if len(req.Arguments()) > 0 { + keys, err = pinLsKeys(req.Arguments(), typeStr, req.Context(), n) + } else { + keys, err = pinLsAll(typeStr, req.Context(), n) } - res.SetOutput(&RefKeyList{Keys: keys}) + if err != nil { + res.SetError(err, cmds.ErrNormal) + } else { + res.SetOutput(&RefKeyList{Keys: keys}) + } }, Type: RefKeyList{}, Marshalers: cmds.MarshalerMap{ @@ -282,3 +272,76 @@ type RefKeyObject struct { type RefKeyList struct { Keys map[string]RefKeyObject } + +func pinLsKeys(args []string, typeStr string, ctx context.Context, n *core.IpfsNode) (map[string]RefKeyObject, error) { + + keys := make(map[string]RefKeyObject) + + for _, p := range args { + dagNode, err := core.Resolve(ctx, n, path.Path(p)) + if err != nil { + return nil, err + } + + k, err := dagNode.Key() + if err != nil { + return nil, err + } + + pinType, pinned, err := n.Pinning.IsPinnedWithType(k, typeStr) + if err != nil { + return nil, err + } + + if !pinned { + return nil, fmt.Errorf("Path '%s' is not pinned", p) + } + + switch pinType { + case "direct", "indirect", "recursive", "internal": + default: + pinType = "indirect through " + pinType + } + keys[k.B58String()] = RefKeyObject{ + Type: pinType, + } + } + + return keys, nil +} + +func pinLsAll(typeStr string, ctx context.Context, n *core.IpfsNode) (map[string]RefKeyObject, error) { + + keys := make(map[string]RefKeyObject) + + AddToResultKeys := func(keyList []key.Key, typeStr string) { + for _, k := range keyList { + keys[k.B58String()] = RefKeyObject{ + Type: typeStr, + } + } + } + + if typeStr == "direct" || typeStr == "all" { + AddToResultKeys(n.Pinning.DirectKeys(), "direct") + } + if typeStr == "indirect" || typeStr == "all" { + ks := key.NewKeySet() + for _, k := range n.Pinning.RecursiveKeys() { + nd, err := n.DAG.Get(ctx, k) + if err != nil { + return nil, err + } + err = dag.EnumerateChildren(n.Context(), n.DAG, nd, ks) + if err != nil { + return nil, err + } + } + AddToResultKeys(ks.Keys(), "indirect") + } + if typeStr == "recursive" || typeStr == "all" { + AddToResultKeys(n.Pinning.RecursiveKeys(), "recursive") + } + + return keys, nil +} diff --git a/pin/pin.go b/pin/pin.go index 4cb2b2c68b9..86b0d58daa8 100644 --- a/pin/pin.go +++ b/pin/pin.go @@ -36,6 +36,7 @@ const ( type Pinner interface { IsPinned(key.Key) (string, bool, error) + IsPinnedWithType(key.Key, string) (string, bool, error) Pin(context.Context, *mdag.Node, bool) error Unpin(context.Context, key.Key, bool) error @@ -126,7 +127,7 @@ func (p *pinner) Pin(ctx context.Context, node *mdag.Node, recurse bool) error { func (p *pinner) Unpin(ctx context.Context, k key.Key, recursive bool) error { p.lock.Lock() defer p.lock.Unlock() - reason, pinned, err := p.isPinned(k) + reason, pinned, err := p.isPinnedWithType(k, "all") if err != nil { return err } @@ -159,22 +160,46 @@ func (p *pinner) isInternalPin(key key.Key) bool { func (p *pinner) IsPinned(k key.Key) (string, bool, error) { p.lock.RLock() defer p.lock.RUnlock() - return p.isPinned(k) + return p.isPinnedWithType(k, "all") } -// isPinned is the implementation of IsPinned that does not lock. +func (p *pinner) IsPinnedWithType(k key.Key, typeStr string) (string, bool, error) { + p.lock.RLock() + defer p.lock.RUnlock() + return p.isPinnedWithType(k, typeStr) +} + +// isPinnedWithType is the implementation of IsPinnedWithType that does not lock. // intended for use by other pinned methods that already take locks -func (p *pinner) isPinned(k key.Key) (string, bool, error) { - if p.recursePin.HasKey(k) { +func (p *pinner) isPinnedWithType(k key.Key, typeStr string) (string, bool, error) { + switch typeStr { + case "all", "direct", "indirect", "recursive", "internal": + default: + err := fmt.Errorf("Invalid type '%s', must be one of {direct, indirect, recursive, internal, all}", typeStr) + return "", false, err + } + if (typeStr == "recursive" || typeStr == "all") && p.recursePin.HasKey(k) { return "recursive", true, nil } - if p.directPin.HasKey(k) { + if typeStr == "recursive" { + return "", false, nil + } + + if (typeStr == "direct" || typeStr == "all") && p.directPin.HasKey(k) { return "direct", true, nil } - if p.isInternalPin(k) { + if typeStr == "direct" { + return "", false, nil + } + + if (typeStr == "internal" || typeStr == "all") && p.isInternalPin(k) { return "internal", true, nil } + if typeStr == "internal" { + return "", false, nil + } + // Default is "indirect" for _, rk := range p.recursePin.GetKeys() { rnd, err := p.dserv.Get(context.Background(), rk) if err != nil { diff --git a/test/bin/.gitignore b/test/bin/.gitignore index ad9f6010df8..c032badeef4 100644 --- a/test/bin/.gitignore +++ b/test/bin/.gitignore @@ -7,5 +7,4 @@ # Do not ignore the following special scripts !checkflags !continueyn -!ipfs-pin-stat !verify-go-fmt.sh diff --git a/test/bin/ipfs-pin-stat b/test/bin/ipfs-pin-stat deleted file mode 100755 index 967ee9b653a..00000000000 --- a/test/bin/ipfs-pin-stat +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/sh - -die() { - echo "$@" - exit 1 -} - -if [ "$#" -eq 0 ]; then - echo "usage: $0 " - echo "show ipfs pin information for object" - exit 1 -fi - -path=$1 - -echo "$path" | grep "/" >/dev/null -if [ "$?" -eq 0 ]; then - die "error: paths not supported. please resolve to hash first." -fi - -ipfs pin ls --type=recursive | grep "$path" >/dev/null -[ "$?" -eq 0 ] && echo "$path pinned recursive-ly" - -ipfs pin ls --type=indirect | grep "$path" >/dev/null -[ "$?" -eq 0 ] && echo "$path pinned indirect-ly" - -ipfs pin ls --type=direct | grep "$path" >/dev/null -[ "$?" -eq 0 ] && echo "$path pinned direct-ly" - -exit 0 diff --git a/test/sharness/t0081-repo-pinning.sh b/test/sharness/t0081-repo-pinning.sh index 8b0d2bcdb05..b3bc1138da2 100755 --- a/test/sharness/t0081-repo-pinning.sh +++ b/test/sharness/t0081-repo-pinning.sh @@ -8,30 +8,23 @@ test_description="Test ipfs repo pinning" . lib/test-lib.sh - - test_pin_flag() { object=$1 ptype=$2 expect=$3 - echo "test_pin_flag" $@ + echo "test_pin_flag" "$@" - ipfs-pin-stat "$object" | egrep "\b$ptype\b" - actual=$? - - if [ "$expect" = "true" ]; then - if [ "$actual" != "0" ]; then - echo "$object should be pinned $ptype ($actual)" - return 1 - fi + if ipfs pin ls --type="$ptype" "$object" >actual + then + test "$expect" = "true" && return + test_fsh cat actual + return else - if [ "$actual" != "1" ]; then - echo "$object should NOT be pinned $ptype ($actual)" - return 1 - fi + test "$expect" = "false" && return + test_fsh cat actual + return fi - return 0 } test_pin() {