fix(ls): improve --long flag docs and fix minor issues

- improved godocs for formatMode and formatModTime functions
- fixed permBit signature: char rune → char byte (avoids unnecessary cast)
- clarified help text: mode/mtime are optional UnixFS metadata
- documented that times are displayed in UTC
- fixed flaky time test by using 1 month ago instead of 1 hour
- removed hardcoded CID assertion that would break on DAG changes
This commit is contained in:
Marcin Rataj 2026-01-16 01:31:24 +01:00
parent e66fa0dbc4
commit 97562d1423
3 changed files with 31 additions and 21 deletions

View File

@ -58,21 +58,23 @@ var LsCmd = &cmds.Command{
Displays the contents of an IPFS or IPNS object(s) at the given path, with
the following format:
<link base58 hash> <link size in bytes> <link name>
<cid> <size> <name>
With the --long (-l) option, also display file mode (permissions) and
modification time for files that were added with --preserve-mode and
--preserve-mtime flags. Format similar to Unix 'ls -l':
With the --long (-l) option, display optional file mode (permissions) and
modification time in a format similar to Unix 'ls -l':
<mode> <link base58 hash> <size> <mtime> <name>
<mode> <cid> <size> <mtime> <name>
Example output with --long:
Mode and mtime are optional UnixFS metadata. They are only present if the
content was imported with 'ipfs add --preserve-mode' and '--preserve-mtime'.
Without preserved metadata, mode shows '----------' and mtime shows '-'.
Times are displayed in UTC.
Example output with --long (content imported with preserved metadata):
-rw-r--r-- QmZULkCELmmk5XNf... 1234 Jan 15 10:30 document.txt
-rwxr-xr-x QmaRGe7bVmVaLmxb... 5678 Dec 01 2023 script.sh
drwxr-xr-x QmWWEQhcLufF3qPm... - Nov 20 2023 subdir/
Files without preserved metadata show '----------' for mode and '-' for mtime.
The JSON output contains type information.
`,
},
@ -230,7 +232,16 @@ The JSON output contains type information.
Type: LsOutput{},
}
// formatMode formats os.FileMode to Unix-style permission string (e.g., "-rw-r--r--").
// formatMode converts os.FileMode to a 10-character Unix ls-style string.
//
// Format: [type][owner rwx][group rwx][other rwx]
//
// Type indicators: - (regular), d (directory), l (symlink), p (named pipe),
// s (socket), c (char device), b (block device).
//
// Special bits replace the execute position: setuid on owner (s/S),
// setgid on group (s/S), sticky on other (t/T). Lowercase when the
// underlying execute bit is also set, uppercase when not.
func formatMode(mode os.FileMode) string {
var buf [10]byte
@ -300,15 +311,20 @@ func formatMode(mode os.FileMode) string {
}
// permBit returns the permission character if the bit is set.
func permBit(mode os.FileMode, bit os.FileMode, char rune) byte {
func permBit(mode os.FileMode, bit os.FileMode, char byte) byte {
if mode&bit != 0 {
return byte(char)
return char
}
return '-'
}
// formatModTime formats time.Time for display.
// Returns empty string if time is zero.
// formatModTime formats time.Time for display, following Unix ls conventions.
//
// Returns "-" for zero time. Otherwise returns a 12-character string:
// recent files (within 6 months) show "Jan 02 15:04",
// older or future files show "Jan 02 2006".
//
// The output uses the timezone embedded in t (UTC for IPFS metadata).
func formatModTime(t time.Time) string {
if t.IsZero() {
return "-"

View File

@ -181,9 +181,8 @@ func TestFormatModTime(t *testing.T) {
oldResult := formatModTime(oldTime)
assert.Len(t, oldResult, 12, "old time format should be 12 chars")
// Recent time (use a fixed time to avoid flakiness)
// We test the format length by checking a definitely-recent time
recentTime := time.Now().Add(-1 * time.Hour)
// Recent time: use 1 month ago to ensure it's always within the 6-month window
recentTime := time.Now().AddDate(0, -1, 0)
recentResult := formatModTime(recentTime)
assert.Len(t, recentResult, 12, "recent time format should be 12 chars")
})

View File

@ -57,11 +57,6 @@ func TestLsLongFormat(t *testing.T) {
assert.Contains(t, lines[1], "-rw-r--r--", "readable file should have 644 permissions")
assert.Contains(t, lines[1], "Jun 15 2020", "should show mtime with year format")
assert.Contains(t, lines[1], "readable.txt", "should show filename")
// Explicit full output check to catch any formatting regressions
expectedOutput := `-rwxr-xr-x QmNmu36VhUL46L1ebS7pjGNBhBWNhL6kERHiswK6fg2HJz 17 Jun 15 2020 executable.sh
-rw-r--r-- QmUHndqmVyXbCS4FkEAQRZ4FeoBZMn8NBsy86bAVR8JhYQ 5 Jun 15 2020 readable.txt`
assert.Equal(t, expectedOutput, strings.TrimSpace(output), "output format must remain stable")
})
t.Run("long format shows dash for files without preserved metadata", func(t *testing.T) {