Documentation ¶
Index ¶
- Constants
- func ApplyPatch(lkr *c.Linker, p *Patch) error
- func ResetNode(lkr *c.Linker, cmt *n.Commit, currPath string) (n.Node, error)
- func Sync(lkrSrc, lkrDst *c.Linker, cfg *SyncOptions) error
- func Undelete(lkr *c.Linker, root string) error
- type Change
- type ChangeType
- type ConflictStrategy
- type Diff
- type DiffPair
- type HistoryWalker
- type MapPair
- type Mapper
- type Patch
- type SyncOptions
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 creates marker files for each conflict. ConflictStragetyMarker = iota // ConflictStragetyIgnore ignores conflicts totally. ConflictStragetyIgnore // ConflictStragetyEmbrace takes the version of the remote. ConflictStragetyEmbrace // ConflictStragetyUnknown should be used when the strategy is not clear. ConflictStragetyUnknown )
Variables ¶
This section is empty.
Functions ¶
func ApplyPatch ¶
ApplyPatch applies the patch `p` to the linker `lkr`.
func ResetNode ¶
ResetNode 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.
This method returns the old node (or nil if none) and any possible error.
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 // MovedTo is only filled for ghosts that were the source // of a move. It's the path of the node it was moved to. MovedTo string // WasPreviouslyAt points to the place `Curr` was at // before a move. On changes without a move this is empty. WasPreviouslyAt string }
Change represents a single change of a node between two commits.
func CombineChanges ¶
CombineChanges compresses a list of changes (in a lossy way) to one Change. The one change should be enough to re-create the changes that were made.
func History ¶
History returns a list of `nd`'s states starting with the commit in `start` and stopping at `stop`. `stop` can be nil; in this case all commits will be iterated. The returned list has the most recent change upfront, and the latest change as last element.
type ChangeType ¶
type ChangeType uint8
ChangeType is a mask of possible state change events.
func (ChangeType) IsCompatible ¶
func (ct ChangeType) IsCompatible(ot ChangeType) bool
IsCompatible checks if two change masks are compatible. Changes are compatible when they can be both applied without loosing any content. We may loose metadata though, e.g. when one side was moved, but the other removed: Here the remove would win and no move is counted.
func (ChangeType) String ¶
func (ct ChangeType) String() string
String will convert a ChangeType to a human readable form
type ConflictStrategy ¶
type ConflictStrategy int
ConflictStrategy defines what conflict strategy to apply in case of nodes with different content hashes.
func ConflictStrategyFromString ¶
func ConflictStrategyFromString(spec string) ConflictStrategy
ConflictStrategyFromString converts a string to a ConflictStrategy. It it is not valid, ConflictStragetyUnknown is returned.
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 }
Diff describes a difference between two commits.
type DiffPair ¶
type DiffPair struct { Src n.ModNode Dst n.ModNode SrcMask ChangeType DstMask ChangeType }
DiffPair is a pair of nodes that have a relation in regard of a change. The change is described by the masks.
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 err := walker.Error(); err != 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 happened, false is returned. True otherwise. You should check after a failing Next() if an error happened 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 NewMapper ¶
func NewMapper(lkrSrc, lkrDst *c.Linker, srcHead, dstHead *n.Commit, srcRoot n.Node) (*Mapper, error)
NewMapper creates a new mapper object that is capable of finding pairs of nodes between lkrDst and lkrSrc.
func (*Mapper) Map ¶
Map 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 Patch ¶
Patch is a set of changes that changed since a certain version of a graph.
func MakePatch ¶
MakePatch creates a patch with all changes starting from `from`. It will only include nodes that are located under one of the prefixes in `prefixes`.
type SyncOptions ¶
type SyncOptions struct { ConflictStrategy ConflictStrategy IgnoreDeletes bool IgnoreMoves bool Message string ReadOnlyFolders map[string]bool ConflictStrategyPerFolder map[string]ConflictStrategy 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 }
SyncOptions gives you the possibility to configure the sync algorithm.