# How the Zakirullin Files Sync Algorithm Handles Conflicts: Timestamp Detection and LCS-Based Merging

> Discover how the Zakirullin Files sync algorithm resolves conflicts using timestamp detection and LCS-based merging for efficient data synchronization.

- Repository: [Artem Zakirullin/files.md](https://github.com/zakirullin/files.md)
- Tags: deep-dive
- Published: 2026-05-21

---

**The Zakirullin Files sync algorithm detects conflicts by comparing server and client modification timestamps, resolving them through an LCS-based three-way merge when both sides have divergent edits, or returning the server version unmodified when only the server has changed.**

The [`zakirullin/files.md`](https://github.com/zakirullin/files.md/blob/main/zakirullin/files.md) repository implements a distributed file synchronization system designed to handle concurrent modifications across multiple clients. The conflict resolution logic resides in the `server/sync` package, which orchestrates detection through temporal comparisons and resolution through intelligent text merging algorithms.

## Conflict Detection via Timestamp Comparison

The algorithm's first phase determines whether the server's version of a file is newer than the version the client currently holds. This detection mechanism operates identically across both bulk and single-file synchronization endpoints.

### The Modification Time Check

In [`server/sync/sync.go`](https://github.com/zakirullin/files.md/blob/main/server/sync/sync.go), the server retrieves the current modification time for the requested file and compares it against the timestamp provided by the client. A conflict is flagged when the server's timestamp is strictly greater:

```go
fileWasModifiedOnServer := serverModifiedTime > clientFile.LastModified

```

This check appears in `SyncFilenames` at lines 34-38 for bulk operations, where the server iterates through multiple files and assesses each one independently. The same logic surfaces in `SyncFile` at lines 14-16 for single-file synchronization, using the variable `serverLastModified` instead:

```go
fileWasModifiedOnServer = serverLastModified > clientFile.LastModified

```

### Detecting Client State with Sync Markers

Beyond timestamp comparison, the algorithm determines whether the client has actually modified its local copy since the last successful synchronization. The server examines `clientFile.ClientLastSynced` and `clientFile.ClientLastModified` to identify if the client version remains untouched:

```go
wasNotModifiedOnClient := clientFile.ClientLastSynced != 0 && clientFile.ClientLastModified == clientFile.ClientLastSynced

```

This distinction proves critical for deciding whether to overwrite client data or preserve local changes.

## Conflict Resolution Strategies

Once the server identifies that its version is newer, it employs different resolution strategies depending on the client's modification state.

### Server-Only Changes

When `fileWasModifiedOnServer` evaluates to true but `wasNotModifiedOnClient` also holds true, the client has not edited the file locally. In this scenario, the server simply transmits its current version back to the client without writing to the filesystem, as implemented in `SyncFile` at lines 15-19:

```go
if fileWasModifiedOnServer && wasNotModifiedOnClient {
    content = serverContent
    shouldUpdateOnServer = false
}

```

The response status remains `"ok"` or transitions to `"updatedOnServer"`, indicating that the client should replace its local copy with the server's version.

### Bidirectional Modifications and Text Merging

When both the server and client have modified the file (`fileWasModifiedOnServer && !wasNotModifiedOnClient`), the algorithm invokes the `Merge` function to reconcile the divergent content. This occurs in `SyncFile` at lines 20-25:

```go
} else if fileWasModifiedOnServer {
    content = Merge(serverContent, clientFile.Content)
    status = StatusMerged
}

```

The merged result is written back to the server's filesystem and returned to the client with a `"merged"` status code.

## Implementation of the LCS-Based Merge Algorithm

The core merging logic resides in [`server/sync/merge.go`](https://github.com/zakirullin/files.md/blob/main/server/sync/merge.go), where the `Merge` function implements a longest common subsequence (LCS) algorithm to perform three-way text reconciliation.

### Algorithm Structure

The function first splits both input strings into lines, then constructs a dynamic programming table to calculate LCS lengths:

```go
func Merge(s1, s2 string) string {
    lines1 := strings.Split(s1, "\n")
    lines2 := strings.Split(s2, "\n")

    lcsLength := make([][]int, len(lines1)+1)
    for i := range lcsLength {
        lcsLength[i] = make([]int, len(lines2)+1)
    }
    
    for i := 1; i <= len(lines1); i++ {
        for j := 1; j <= len(lines2); j++ {
            if lines1[i-1] == lines2[j-1] {
                lcsLength[i][j] = lcsLength[i-1][j-1] + 1
            } else {
                lcsLength[i][j] = max(lcsLength[i-1][j], lcsLength[i][j-1])
            }
        }
    }
    
    result := backtrack(lines1, lines2, lcsLength, len(lines1), len(lines2))
    result = mergeEmojisInJournalHeaders(result)
    return strings.Join(result, "\n")
}

```

(lines 24-55 in [`server/sync/merge.go`](https://github.com/zakirullin/files.md/blob/main/server/sync/merge.go))

### Post-Processing Journal Headers

After backtracking through the LCS table to assemble the merged line list, the algorithm performs a specialized post-processing step via `mergeEmojisInJournalHeaders`. This function collapses repeated journal headers containing emojis to maintain document consistency in markdown files.

## Sync Response Protocol and Status Codes

The synchronization endpoints return structured responses that inform the client how to update its local state. According to the implementation in [`server/sync/sync.go`](https://github.com/zakirullin/files.md/blob/main/server/sync/sync.go) at lines 66-71, the response payload contains a `status` field with three possible values:

- **`"ok"`** – No conflict detected; the server accepted the client's version
- **`"updatedOnServer"`** – The server had a newer version and the client had not modified the file locally; the client should replace its copy
- **`"merged"`** – Both sides modified the file; the client should update its local copy with the merged content returned in the response

The client uses this status flag to determine whether to overwrite its local file, preserving the synchronization state for subsequent operations.

## Summary

- The Zakirullin Files sync algorithm detects conflicts by comparing `serverModifiedTime` against `clientFile.LastModified` in [`server/sync/sync.go`](https://github.com/zakirullin/files.md/blob/main/server/sync/sync.go)
- **Server-only changes** result in the server returning its version without filesystem writes, identified when `ClientLastModified == ClientLastSynced`
- **Bidirectional modifications** trigger the `Merge` function in [`server/sync/merge.go`](https://github.com/zakirullin/files.md/blob/main/server/sync/merge.go), which implements an LCS-based algorithm to reconcile divergent text
- The merge process includes post-processing for emoji-aware journal header collapsing via `mergeEmojisInJournalHeaders`
- Response status codes (`"ok"`, `"updatedOnServer"`, `"merged"`) direct the client on how to update its local state

## Frequently Asked Questions

### How does the Zakirullin Files sync algorithm determine if a conflict exists?

The algorithm detects conflicts by comparing timestamps in the `server/sync` package. Specifically, it evaluates whether `serverModifiedTime > clientFile.LastModified` in both the `SyncFilenames` and `SyncFile` functions. If the server's modification time is newer than the timestamp the client last saw, the server flags the file as having been modified on the server, triggering conflict resolution logic.

### What happens when both the client and server modify the same file?

When both sides have divergent changes, the server invokes the `Merge` function from [`server/sync/merge.go`](https://github.com/zakirullin/files.md/blob/main/server/sync/merge.go). This function implements a longest common subsequence (LCS) algorithm that builds a dynamic programming table to identify common lines between both versions, then backtracks to assemble a merged text. The result is written to the server and returned to the client with a `"merged"` status code.

### How does the algorithm handle cases where only the server modified the file?

If the server detects a newer modification time but the client has not made local changes (determined by checking `clientFile.ClientLastModified == clientFile.ClientLastSynced`), the algorithm treats this as a server-only change. The server returns its current content to the client with an `"updatedOnServer"` status without writing to the filesystem, instructing the client to replace its local copy.

### What is the purpose of the `mergeEmojisInJournalHeaders` function in the merging process?

The `mergeEmojisInJournalHeaders` function performs post-processing on the merged text after the LCS algorithm assembles the combined line list. As implemented in [`server/sync/merge.go`](https://github.com/zakirullin/files.md/blob/main/server/sync/merge.go), this function specifically handles markdown journal headers containing emojis, collapsing repeated headers to maintain document consistency and prevent duplicate dated entries in synchronized markdown files.