Documentation ¶
Index ¶
- Constants
- Variables
- func ParentDirectoryForCommit(lkr *c.Linker, cmt *n.Commit, curr n.Node) (*n.Directory, error)
- func ResetFile(lkr *c.Linker, cmt *n.Commit, currPath string) error
- func Sync(lkrSrc, lkrDst *c.Linker, cfg *SyncConfig) error
- type Change
- type ChangeType
- type ConflictStrategy
- type Diff
- type DiffPair
- type HistoryWalker
- type MapPair
- type Mapper
- type SyncConfig
Constants ¶
const ( // ChangeTypeNone means that a node did not change (compared to HEAD) ChangeTypeNone = ChangeType(0) // ChangeTypeAdd says that the node was initially added after HEAD. ChangeTypeAdd = ChangeType(1 << iota) // ChangeTypeModify says that the the node was modified after HEAD ChangeTypeModify // ChangeTypeMove says that the node was moved after HEAD. // Note that Move and Modify may happen at the same time. ChangeTypeMove // ChangeTypeRemove says that the node was removed after HEAD. ChangeTypeRemove )
const ( ConflictStragetyMarker = iota ConflictStragetyIgnore ConflictStragetyUnknown )
Variables ¶
var (
DefaultSyncConfig = &SyncConfig{}
)
Functions ¶
func ResetFile ¶
ResetFile resets a certain file to the state it had in cmt. If the file did not exist back then, it will be deleted. `nd` is usually retrieved by calling ResolveNode() and sorts.
A special case occurs when the file was moved we reset to. In this case the state of the old node (at the old path) is being written to the node at the new path. This is the more obvious choice to the user when he types:
$ brig reset HEAD^ i-was-somewhere-else-before # name does not change.
Types ¶
type Change ¶
type Change struct { // Mask is a bitmask of changes that were made. // It describes the change that was made between `Next` to `Head` // and which is part of `Head`. Mask ChangeType // Head is the commit that was the current HEAD when this change happened. // Note that this is NOT the commit that contains the change, but the commit before. Head *n.Commit // Next is the commit that comes before `Head`. Next *n.Commit // Curr is the node with the attributes at a specific state Curr n.ModNode // ReferToPath is only filled for ghosts that were the source // of a move. It's the path of the node it was moved to. ReferToPath string }
Change represents a single change of a node between two commits.
type ChangeType ¶
type ChangeType uint8
ChangeType is a mask of possible state change events.
func (ChangeType) IsCompatible ¶
func (ct ChangeType) IsCompatible(ot ChangeType) bool
rule: do not loose content,
but we may loose metadata. | a c r m
--------------- a | n n n y c | n n n y r | y y y y m | y y y y
func (ChangeType) String ¶
func (ct ChangeType) String() string
String will convert a ChangeType to a human readable form
type ConflictStrategy ¶
type ConflictStrategy int
func ConflictStrategyFromString ¶
func ConflictStrategyFromString(spec string) ConflictStrategy
func (ConflictStrategy) String ¶
func (cs ConflictStrategy) String() string
type Diff ¶
type Diff struct { // Nodes that were added from remote. Added []n.ModNode // Nodes that were removed on remote side. Removed []n.ModNode // Nodes (of us) that are missing on the remote side. Missing []n.ModNode // Nodes from remote that were ignored. Ignored []n.ModNode // Nodes that were only moved (but nothing else) Moved []DiffPair // Merged contains nodes where sync is able to combine changes // on both sides (i.e. one side moved, another modified) Merged []DiffPair // Conflict contains nodes where sync was not able to combine // the changes made on both sides. Conflict []DiffPair // contains filtered or unexported fields }
type DiffPair ¶
type DiffPair struct { Src n.ModNode Dst n.ModNode SrcMask ChangeType DstMask ChangeType }
type HistoryWalker ¶
type HistoryWalker struct {
// contains filtered or unexported fields
}
HistoryWalker provides a way to iterate over all changes a single Node had. It is capable of tracking a file even over multiple moves.
The API is loosely modeled after bufio.Scanner and can be used like this:
head, _ := lkr.Head() nd, _ := lkr.LookupFile("/x") walker := NewHistoryWalker(lkr, head, nd) for walker.Next() { walker.Change() } if walker.Error() != nil { // Handle errors. }
func NewHistoryWalker ¶
NewHistoryWalker will return a new HistoryWalker that will yield changes of `node` starting from the state in `cmt` until the root commit if desired. Note that it is not checked that `node` is actually part of `cmt`.
func (*HistoryWalker) Err ¶
func (hw *HistoryWalker) Err() error
Err returns the last happened error or nil if none.
func (*HistoryWalker) Next ¶
func (hw *HistoryWalker) Next() bool
Next advances the walker to the next commit. Call State() to get the current state after. If there are no commits left or an error happended, false is returned. True otherwise. You should check after a failing Next() if an error happended via Err()
func (*HistoryWalker) State ¶
func (hw *HistoryWalker) State() *Change
State returns the current change state. Note that the change may have ChangeTypeNone as Mask if nothing changed. If you only want states where it actually changed, just filter those.
type MapPair ¶
type MapPair struct { Src n.ModNode Dst n.ModNode SrcWasRemoved bool SrcWasMoved bool TypeMismatch bool }
MapPair is a pair of nodes (a file or a directory) One of Src and Dst might be nil: - If Src is nil, the node was removed on the remote side. - If Dst is nil, the node was added on the remote side.
Both shall never be nil at the same time.
If TypeMismatch is true, nodes have a different type and need conflict resolution.
If SrcWasRemoved is true, the node was deleted on the remote's side and we might need to propagate this remove. Otherwise, if src is nil, dst can be considered as missing file on src's side.
If SrcWasMoved is true, the two nodes were purely moved, but not modified otherwise.
type Mapper ¶
type Mapper struct {
// contains filtered or unexported fields
}
Mapper holds the state for the mapping algorithm.
func (*Mapper) Map ¶
Diff calls `fn` for each pairing that was found. Equal files and directories are not reported. Most directories are also not reported, but if they are empty and not present on our side they will. No ghosts will be reported.
Some implementation background for the curious reader:
In the simplest case a filesystem is a tree and the assumption can be made that one node that lives at the same path on both sides is the same "file" (i.e. in terms of "this is the file that the user wants to synchronize with").
With ghosts though, we have nodes that can indicate a removed or a moved file. Due to moved files the filesystem tree becomes a graph and the mapping algorithm (that is the base of Mapper) needs to do a depth first search and thus needs to remember already visited nodes.
Since moved nodes also takes priorty we need to iterate over all ghosts first, and mark their respective counterparts or report that they were removed on the remote side (i.e. no counterpart exists.). Only after that we cycle through all other nodes and assume that files living at the same path reference the same "file". At this point we can treat the file graph as tree again by ignoring all ghosts.
A special case is when a file was moved on one side but, a file exists already on the other side. In this case the already existing files wins.
Some examples of the described behaviours can be found in the tests of Mapper.
type SyncConfig ¶
type SyncConfig struct { ConflictStrategy ConflictStrategy IgnoreDeletes bool IgnoreMoves bool OnAdd func(newNd n.ModNode) bool OnRemove func(oldNd n.ModNode) bool OnMerge func(src, dst n.ModNode) bool OnConflict func(src, dst n.ModNode) bool }
SyncConfig gives you the possibility to configure the sync algorithm.