Skip to content

Commit af9bcba

Browse files
authored
feat: add --dereference-symlinks flag for recursive symlink resolution (#315)
* feat: add --dereference-symlinks flag for recursive symlink resolution add new --dereference-symlinks boolean flag that recursively resolves all symlinks to their target content during file collection. this works on symlinks inside directories, not just CLI arguments. the flag is wired through cli/parse.go to boxo's SerialFileOptions.DereferenceSymlinks. deprecate --dereference-args which only worked on symlinks passed directly as CLI arguments. the help text now indicates it is deprecated and directs users to use --dereference-symlinks instead. ref: ipfs/specs#499 * fix: make --dereference-symlinks resolve CLI arg symlinks too --dereference-symlinks is now a superset of --dereference-args: - resolves symlinks passed as CLI arguments (like --dereference-args) - ALSO resolves symlinks found during directory traversal (new behavior) this allows users to use just --dereference-symlinks instead of needing to pass both flags for full symlink resolution. * chore: update to rebased boxo PR updates github.com/ipfs/boxo to 56cf0aecdc1a (feat/ipip-499-unixfs-2025 rebased on main) * fix: reuse derefSymlinks variable, fix typo in deprecation notice * chore: update boxo to f188f79fd412 switches to boxo@main after merging ipfs/boxo#1088
1 parent b4c9264 commit af9bcba

File tree

4 files changed

+33
-22
lines changed

4 files changed

+33
-22
lines changed

cli/parse.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ func stdinName(req *cmds.Request) string {
8484
return name
8585
}
8686

87+
func dereferenceSymlinks(req *cmds.Request) bool {
88+
deref, _ := req.Options[cmds.DerefSymlinks].(bool)
89+
return deref
90+
}
91+
8792
type parseState struct {
8893
cmdline []string
8994
i int
@@ -303,6 +308,7 @@ func parseArgs(req *cmds.Request, root *cmds.Command, stdin *os.File) error {
303308
} else {
304309
fpath = filepath.Clean(fpath)
305310
derefArgs, _ := req.Options[cmds.DerefLong].(bool)
311+
derefSymlinks := dereferenceSymlinks(req)
306312
var err error
307313

308314
switch {
@@ -313,7 +319,7 @@ func parseArgs(req *cmds.Request, root *cmds.Command, stdin *os.File) error {
313319
}
314320
fpath = filepath.ToSlash(cwd)
315321
fallthrough
316-
case derefArgs:
322+
case derefArgs || derefSymlinks:
317323
if fpath, err = filepath.EvalSymlinks(fpath); err != nil {
318324
return err
319325
}
@@ -324,7 +330,7 @@ func parseArgs(req *cmds.Request, root *cmds.Command, stdin *os.File) error {
324330
if err != nil {
325331
return err
326332
}
327-
nf, err := appendFile(fpath, argDef, isRecursive(req), filter)
333+
nf, err := appendFile(fpath, argDef, isRecursive(req), filter, derefSymlinks)
328334
if err != nil {
329335
return err
330336
}
@@ -542,7 +548,7 @@ func getArgDef(i int, argDefs []cmds.Argument) *cmds.Argument {
542548
const notRecursiveFmtStr = "'%s' is a directory, use the '-%s' flag to specify directories"
543549
const dirNotSupportedFmtStr = "invalid path '%s', argument '%s' does not support directories"
544550

545-
func appendFile(fpath string, argDef *cmds.Argument, recursive bool, filter *files.Filter) (files.Node, error) {
551+
func appendFile(fpath string, argDef *cmds.Argument, recursive bool, filter *files.Filter, dereferenceSymlinks bool) (files.Node, error) {
546552
stat, err := os.Lstat(fpath)
547553
if err != nil {
548554
return nil, err
@@ -566,7 +572,10 @@ func appendFile(fpath string, argDef *cmds.Argument, recursive bool, filter *fil
566572

567573
return files.NewReaderFile(file), nil
568574
}
569-
return files.NewSerialFileWithFilter(fpath, filter, stat)
575+
return files.NewSerialFileWithOptions(fpath, stat, files.SerialFileOptions{
576+
Filter: filter,
577+
DereferenceSymlinks: dereferenceSymlinks,
578+
})
570579
}
571580

572581
// Inform the user if a file is waiting on input

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/ipfs/go-ipfs-cmds
33
go 1.24.6
44

55
require (
6-
github.com/ipfs/boxo v0.36.0
6+
github.com/ipfs/boxo v0.36.1-0.20260204203152-f188f79fd412
77
github.com/ipfs/go-log/v2 v2.9.1
88
github.com/rs/cors v1.11.1
99
github.com/texttheater/golang-levenshtein v1.0.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ github.com/crackcomm/go-gitignore v0.0.0-20241020182519-7843d2ba8fdf h1:dwGgBWn8
22
github.com/crackcomm/go-gitignore v0.0.0-20241020182519-7843d2ba8fdf/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE=
33
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
44
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5-
github.com/ipfs/boxo v0.36.0 h1:DarrMBM46xCs6GU6Vz+AL8VUyXykqHAqZYx8mR0Oics=
6-
github.com/ipfs/boxo v0.36.0/go.mod h1:92hnRXfP5ScKEIqlq9Ns7LR1dFXEVADKWVGH0fjk83k=
5+
github.com/ipfs/boxo v0.36.1-0.20260204203152-f188f79fd412 h1:nfRIkMIhetCWD8jw5ya+FY+jn9ii2c+U5gdkmSS4L1Q=
6+
github.com/ipfs/boxo v0.36.1-0.20260204203152-f188f79fd412/go.mod h1:92hnRXfP5ScKEIqlq9Ns7LR1dFXEVADKWVGH0fjk83k=
77
github.com/ipfs/go-log/v2 v2.9.1 h1:3JXwHWU31dsCpvQ+7asz6/QsFJHqFr4gLgQ0FWteujk=
88
github.com/ipfs/go-log/v2 v2.9.1/go.mod h1:evFx7sBiohUN3AG12mXlZBw5hacBQld3ZPHrowlJYoo=
99
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=

opts.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,30 @@ package cmds
22

33
// Flag names
44
const (
5-
EncShort = "enc"
6-
EncLong = "encoding"
7-
RecShort = "r"
8-
RecLong = "recursive"
9-
ChanOpt = "stream-channels"
10-
TimeoutOpt = "timeout"
11-
OptShortHelp = "h"
12-
OptLongHelp = "help"
13-
DerefLong = "dereference-args"
14-
StdinName = "stdin-name"
15-
Hidden = "hidden"
16-
HiddenShort = "H"
17-
Ignore = "ignore"
18-
IgnoreRules = "ignore-rules-path"
5+
EncShort = "enc"
6+
EncLong = "encoding"
7+
RecShort = "r"
8+
RecLong = "recursive"
9+
ChanOpt = "stream-channels"
10+
TimeoutOpt = "timeout"
11+
OptShortHelp = "h"
12+
OptLongHelp = "help"
13+
DerefLong = "dereference-args"
14+
DerefSymlinks = "dereference-symlinks"
15+
StdinName = "stdin-name"
16+
Hidden = "hidden"
17+
HiddenShort = "H"
18+
Ignore = "ignore"
19+
IgnoreRules = "ignore-rules-path"
1920
)
2021

2122
// options that are used by this package
2223
var OptionEncodingType = StringOption(EncLong, EncShort, "The encoding type the output should be encoded with (json, xml, or text)").WithDefault("text")
2324
var OptionRecursivePath = BoolOption(RecLong, RecShort, "Add directory paths recursively")
2425
var OptionStreamChannels = BoolOption(ChanOpt, "Stream channel output")
2526
var OptionTimeout = StringOption(TimeoutOpt, "Set a global timeout on the command")
26-
var OptionDerefArgs = BoolOption(DerefLong, "Symlinks supplied in arguments are dereferenced")
27+
var OptionDerefArgs = BoolOption(DerefLong, "DEPRECATED: use --dereference-symlinks instead. Only dereferences symlinks in CLI arguments, not inside directories.")
28+
var OptionDerefSymlinks = BoolOption(DerefSymlinks, "Recursively resolve all symlinks to their target content. Works on symlinks inside directories, not just CLI arguments.")
2729
var OptionStdinName = StringOption(StdinName, "Assign a name if the file source is stdin.")
2830
var OptionHidden = BoolOption(Hidden, HiddenShort, "Include files that are hidden. Only takes effect on recursive add.")
2931
var OptionIgnore = StringsOption(Ignore, "A rule (.gitignore-stype) defining which file(s) should be ignored (variadic, experimental)")

0 commit comments

Comments
 (0)