diff --git a/fileserver/commitmgr/commitmgr.go b/fileserver/commitmgr/commitmgr.go index 5ab5e31..f719e71 100644 --- a/fileserver/commitmgr/commitmgr.go +++ b/fileserver/commitmgr/commitmgr.go @@ -22,8 +22,8 @@ type Commit struct { CreatorID string `json:"creator"` Desc string `json:"description"` Ctime int64 `json:"ctime"` - ParentID string `json:"parent_id,omitempty"` - SecondParentID string `json:"second_parent_id,omitempty"` + ParentID String `json:"parent_id"` + SecondParentID String `json:"second_parent_id"` RepoName string `json:"repo_name"` RepoDesc string `json:"repo_desc"` RepoCategory string `json:"repo_category"` @@ -57,7 +57,7 @@ func NewCommit(repoID, parentID, newRoot, user, desc string) *Commit { commit.CreatorID = "0000000000000000000000000000000000000000" commit.Ctime = time.Now().Unix() commit.CommitID = computeCommitID(commit) - commit.ParentID = parentID + commit.ParentID.SetValid(parentID) return commit } diff --git a/fileserver/commitmgr/commitmgr_test.go b/fileserver/commitmgr/commitmgr_test.go index fd4501d..a08d928 100644 --- a/fileserver/commitmgr/commitmgr_test.go +++ b/fileserver/commitmgr/commitmgr_test.go @@ -48,7 +48,7 @@ func TestCommit(t *testing.T) { newCommit.CreatorID = commitID newCommit.Desc = "This is a commit" newCommit.Ctime = time.Now().Unix() - newCommit.ParentID = commitID + newCommit.ParentID.SetValid(commitID) newCommit.DeviceName = "Linux" err := Save(newCommit) if err != nil { diff --git a/fileserver/commitmgr/null.go b/fileserver/commitmgr/null.go new file mode 100644 index 0000000..f085aea --- /dev/null +++ b/fileserver/commitmgr/null.go @@ -0,0 +1,114 @@ +package commitmgr + +import ( + "bytes" + "database/sql" + "encoding/json" + "fmt" +) + +// nullBytes is a JSON null literal +var nullBytes = []byte("null") + +// String is a nullable string. It supports SQL and JSON serialization. +// It will marshal to null if null. Blank string input will be considered null. +type String struct { + sql.NullString +} + +// StringFrom creates a new String that will never be blank. +func StringFrom(s string) String { + return NewString(s, true) +} + +// StringFromPtr creates a new String that be null if s is nil. +func StringFromPtr(s *string) String { + if s == nil { + return NewString("", false) + } + return NewString(*s, true) +} + +// ValueOrZero returns the inner value if valid, otherwise zero. +func (s String) ValueOrZero() string { + if !s.Valid { + return "" + } + return s.String +} + +// NewString creates a new String +func NewString(s string, valid bool) String { + return String{ + NullString: sql.NullString{ + String: s, + Valid: valid, + }, + } +} + +// UnmarshalJSON implements json.Unmarshaler. +// It supports string and null input. Blank string input does not produce a null String. +func (s *String) UnmarshalJSON(data []byte) error { + if bytes.Equal(data, nullBytes) { + s.Valid = false + return nil + } + + if err := json.Unmarshal(data, &s.String); err != nil { + return fmt.Errorf("null: couldn't unmarshal JSON: %w", err) + } + + s.Valid = true + return nil +} + +// MarshalJSON implements json.Marshaler. +// It will encode null if this String is null. +func (s String) MarshalJSON() ([]byte, error) { + if !s.Valid { + return []byte("null"), nil + } + return json.Marshal(s.String) +} + +// MarshalText implements encoding.TextMarshaler. +// It will encode a blank string when this String is null. +func (s String) MarshalText() ([]byte, error) { + if !s.Valid { + return []byte{}, nil + } + return []byte(s.String), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +// It will unmarshal to a null String if the input is a blank string. +func (s *String) UnmarshalText(text []byte) error { + s.String = string(text) + s.Valid = s.String != "" + return nil +} + +// SetValid changes this String's value and also sets it to be non-null. +func (s *String) SetValid(v string) { + s.String = v + s.Valid = true +} + +// Ptr returns a pointer to this String's value, or a nil pointer if this String is null. +func (s String) Ptr() *string { + if !s.Valid { + return nil + } + return &s.String +} + +// IsZero returns true for null strings, for potential future omitempty support. +func (s String) IsZero() bool { + return !s.Valid +} + +// Equal returns true if both strings have the same value or are both null. +func (s String) Equal(other String) bool { + return s.Valid == other.Valid && (!s.Valid || s.String == other.String) +} diff --git a/fileserver/fileop.go b/fileserver/fileop.go index 8083a18..8ec0c55 100644 --- a/fileserver/fileop.go +++ b/fileserver/fileop.go @@ -1671,7 +1671,7 @@ func genCommitNeedRetry(repo *repomgr.Repo, base *commitmgr.Commit, commit *comm mergedCommit = commitmgr.NewCommit(repoID, currentHead.CommitID, opt.mergedRoot, user, mergeDesc) repomgr.RepoToCommit(repo, mergedCommit) - mergedCommit.SecondParentID = commit.CommitID + mergedCommit.SecondParentID.SetValid(commit.CommitID) mergedCommit.NewMerge = 1 if opt.conflict { mergedCommit.Conflict = 1 diff --git a/fileserver/fsmgr/fsmgr.go b/fileserver/fsmgr/fsmgr.go index bb336bc..bee775b 100644 --- a/fileserver/fsmgr/fsmgr.go +++ b/fileserver/fsmgr/fsmgr.go @@ -19,8 +19,8 @@ import ( // Seafile is a file object type Seafile struct { Version int `json:"version"` - FileType int `json:"type,omitempty"` - FileID string `json:"file_id,omitempty"` + FileType int `json:"type"` + FileID string `json:"file_id"` FileSize uint64 `json:"size"` BlkIDs []string `json:"block_ids"` } @@ -38,8 +38,8 @@ type SeafDirent struct { //SeafDir is a dir object type SeafDir struct { Version int `json:"version"` - DirType int `json:"type,omitempty"` - DirID string `json:"dir_id,omitempty"` + DirType int `json:"type"` + DirID string `json:"dir_id"` Entries []*SeafDirent `json:"dirents"` } diff --git a/fileserver/sync_api.go b/fileserver/sync_api.go index 97ff8ed..6da5f0b 100644 --- a/fileserver/sync_api.go +++ b/fileserver/sync_api.go @@ -909,9 +909,9 @@ func putUpdateBranchCB(rsp http.ResponseWriter, r *http.Request) *appError { return &appError{err, "", http.StatusInternalServerError} } - base, err := commitmgr.Load(repoID, newCommit.ParentID) + base, err := commitmgr.Load(repoID, newCommit.ParentID.String) if err != nil { - err := fmt.Errorf("Failed to get commit %s for repo %s", newCommit.ParentID, repoID) + err := fmt.Errorf("Failed to get commit %s for repo %s", newCommit.ParentID.String, repoID) return &appError{err, "", http.StatusInternalServerError} } diff --git a/fileserver/virtual_repo.go b/fileserver/virtual_repo.go index 4a7c4c2..be4c044 100644 --- a/fileserver/virtual_repo.go +++ b/fileserver/virtual_repo.go @@ -191,9 +191,9 @@ func cleanupVirtualRepos(repoID string) error { } func handleMissingVirtualRepo(repo *repomgr.Repo, head *commitmgr.Commit, vInfo *repomgr.VRepoInfo) (string, error) { - parent, err := commitmgr.Load(head.RepoID, head.ParentID) + parent, err := commitmgr.Load(head.RepoID, head.ParentID.String) if err != nil { - err := fmt.Errorf("failed to load commit %s/%s : %v", head.RepoID, head.ParentID, err) + err := fmt.Errorf("failed to load commit %s/%s : %v", head.RepoID, head.ParentID.String, err) return "", err }