Merge

Just like Git, Wrgl can merge two commits automatically using three-way merge. When Wrgl cannot decide how to merge certain rows, they are presented as conflicts which can be resolved via the built-in mergetool or with external tools. There are three phases in the merging process:

wrgl merge

Merge two commits together.

wrgl merge BRANCH COMMIT [flags]

If merge is successful then create a merge commit under BRANCH.

Flags

--commit-csv

don't perform merge, just create a merge commit with the specified CSV file

--ff

when merging a descendant commit into a branch, don't create a merge commit but simply fast-forward branch to the descendant commit. Create an extra merge commit otherwise. This is the default behavior unless merge.fastForward is configured.

--ff-only

only allow fast-forward merges. This is the default when merge.fastForward is set to "only".

-h, --help

help for merge

-m, --message

merge commit message

--no-commit

perform the merge but don't create a merge commit, instead output merge result to file MERGE_SUM1_SUM2_..._SUMn.csv

--no-ff

always create a merge commit, even when a simple fast-forward is possible. This is the default when merge.fastFoward is set to "never".

--no-gui

don't show mergetool, instead output conflicts (and resolved rows) to file CONFLICTS_SUM1_SUM2_..._SUMn.csv

-n, --num-workers

number of CPU threads to utilize (default to GOMAXPROCS)

-p, --primary-key

merge commit primary key. This is only used when --commit-csv is in use. If this isn't specified then primary key is the same as BRANCH HEAD's

Inherited flags

--badger-log

set Badger log level, valid options are "error", "warning", "debug", and "info" (defaults to "error")

--cpuprofile

write cpu profile to file

--debug-file

output debug logs to a file

--heapprofile

write heap profile to file

--wrgl-dir

parent directory of repo, default to current working directory.

Examples

# merge two branches
wrgl merge branch-1 branch-2

# merge a commit into a branch
wrgl merge my-branch 43a5f3447e82b53a2574ef5af470df96

# perform merge but don't create a merge commit, output result to file MERGE_SUM1_SUM2.csv instead
wrgl merge branch-1 branch-2 --no-commit

# don't show merge UI, output conflicts and resolved rows to CONFLICTS_SUM1_SUM2.csv instead
wrgl merge branch-1 branch-2 --no-gui

# create a merge commit from an already resolved CSV file
wrgl merge branch-1 branch-2 --commit-csv resolved.csv

Automatic resolution

The merging process always start by trying to automatically merge rows without user input. At the end of this phase, rows that Wrgl doesn't know how to merge will be presented to you as conflicts. In the best case scenario, all rows are merged automatically and no human input is needed.

The commits that need to be merged (specified as arguments to wrgl merge) are called merge heads. Wrgl perform three-way merge which follow three steps:

  • Identify the closest common ancestor for both heads (henceforth called the base commit).
  • For each merge head, identify changes compared to the base commit. Similar to wrgl diff, changes are identified by matching rows using the primary key and do pairwise comparison.
  • Group changes using the primary key value. For each group, try to compare and reconcile changes.

For this process to work, both heads as well as the base commit must share the same primary key, which mean the columns that make up the primary key and their order must remain the same.

For each primary key value, the following cases are handled:

  • Row remain unchanged across all merge heads: Add row verbatim to the end result.
  • Row was removed from all merge heads: Remove row from the end result.
  • Row was removed in one merge head but unchanged in the other: Remove row from the end result.
  • Row removed in one merge head, modified in the other: Mark as conflict.
  • Row was added in one merge head, or added in both merge heads and are identical: Add row to the end result.
  • Row was added in both merge heads but are not identical: Mark as conflict.
  • Row was modified in one merge head, or modified in both merge heads and are identical: Add modified row verbatim to the end result.
  • Row was modified in both merge heads but are not identical: Mark as conflict.

Within each conflicting row, each cell is further marked as unresolved if one of the following conditions is true:

  • The cell was modified in one head and removed in the other which is also true if the entire row was removed.
  • The cell was modified in both heads but with different values.
  • The cell was added in both heads (new column) but with different values.

If there are conflicts of any kind, the next step is to handle conflicts using the built-in mergetool or external tools.

Using the built-in mergetool

Running wrgl merge without --no-gui flag displays the built-in mergetool.

Built-in mergetool

Built-in mergetool

UI elements

From top to bottom:

  • Names of merge heads and the base commit in the format Merging <merge head 1>, <merge head 2> (base <base commit>)
  • Resolution progress and column/cell status in the format <resolution progress> - <column/cell status>. Resolution progress display number and percentage of rows that still need to be manually resolved. Column/cell status displays additional information for any column/cell that the cursor is on top of.
  • Resolution table which is described in the next section.
  • Keyboard shortcuts.

Resolution table

The purpose of this table is to manually resolve conflicts. Each conflicting row is presented as a set of three rows in this order:

  • Row from the first merge head with row label <name of first merge head> (<commit sum>).
  • Row from the second merge head with row label <name of second merge head> (<commit sum>).
  • Row resolution (final value) with row label unresolved if there are still unresolved cells, or resolved if all cells are resolved. Conflicting cells have red background and must be edit to be considered resolved.

Row label of each mergehead are color-coded as follow:

  • Red: row was removed from this head, in which case the entire row is empty.
  • Yellow: row was modified in this head.
  • Green: row was added in this head (was not present in the base commit).

Each column label is color-coded as follow:

  • White: column was preserved across all merge heads.
  • Green: column was added in one or both heads.
  • Red: column was removed in one or both heads.

Each cell is color-coded as follow:

  • Teal: cell belongs to one of the primary key columns. Primary key columns are always freezed to the left, right after the row label.
  • White: cell value is unchanged compared to the base commit.
  • Green: cell was added.
  • Empty cell (red background when hovered): cell was removed.
  • Yellow: cell was modified.

Interacting with resolution table

You can of course hit enter on any cell in the resolution row to edit their final value. There are however many shortcuts that make manual resolution as easy as possible:

  • Hit enter on any cell in one of the merge head rows to copy the value in that cell to the corresponding cell in the resolution row.
  • Hit d to delete row from the end result.
  • Hit Shift+d to delete column from the end result.
  • Hit u, Shift+u to undo, redo.
  • Hit n to go to the next conflicting cell.
  • Hit r to mark the current row as resolved (output the row as is).
  • Hit Shift+r to mark the current row as unresolved.

When you are satisfied with the result, hit Shift+x to commit the result.

Resolving conflicts with external tools

Resolving conflicts with external tools follows these three steps:

  • Output conflicts to a CSV file by running wrgl merge with --no-gui flag.
  • Resolve conflicts with any tool of your choosing e.g. Excel or Python script.
  • Commit the resolved file as the merge commit by running wrgl merge with --commit-csv flag.

Conflicts file

The conflicts file will be named CONFLICTS_<SUM1>_<SUM2>.csv. The very first row in this file list the column names. The next two rows list column statuses (REMOVED, or NEW, or empty) for two merge heads.

The next group of rows are conflicts, each conflict are presented as four rows:

  • The row from the base commit.
  • The row from the first merge head.
  • The row from the second merge head.
  • The suggested resolution (or lack thereof for unresolved cells).

The final group of rows are resolved rows with zero conflicts. They are presented as is. Non-conflicting removals are not presented in this group of course.

Example conflicts file

Given these merge heads:

a,b,d
1,g,e
2,h,d

branch-1 (0402137)


a,c,e
1,q,w
3,z,x

branch-2 (ac8cf44)

Which share this commit as the closest common ancestor:

a,b,c
1,q,w
2,a,s
4,v,b

base (2ca57c7)

Running this command:

wrgl merge branch-1 branch-2 --no-gui

Will produce this file:

,a,c,e,b,d
COLUMNS IN branch-1 (0402137),,REMOVED,,,NEW
COLUMNS IN branch-2 (ac8cf44),,,NEW,REMOVED,
BASE 2ca57c7,1,w,,q,
branch-1 (0402137),1,,,g,e
branch-2 (ac8cf44),1,q,w,,
RESOLUTION,1,w,w,q,e
BASE 2ca57c7,2,s,,a,
branch-1 (0402137),2,,,h,d
branch-2 (ac8cf44),REMOVED IN ac8cf44,REMOVED IN ac8cf44,REMOVED IN ac8cf44,REMOVED IN ac8cf44,REMOVED IN ac8cf44
RESOLUTION,2,,,a,d
,3,z,x,,

CONFLICTS_72718e7_25a6dda.csv