Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
history_merge.c File Reference

Merge histories, including optional pipeline topology merge. More...

#include "common/history_merge.h"
#include "common/history_merge_gui.h"
#include "common/darktable.h"
#include "common/debug.h"
#include "common/iop_order.h"
#include "common/topological_sort.h"
#include "develop/blend.h"
#include "develop/dev_history.h"
#include "develop/develop.h"
#include "develop/imageop.h"
#include <glib.h>
#include <stdlib.h>
#include <string.h>
+ Include dependency graph for history_merge.c:

Data Structures

struct  _hm_id_info_t
 
struct  _hm_dest_backup_t
 
struct  _hm_topo_merge_ctx_t
 

Typedefs

typedef struct _hm_topo_merge_ctx_t _hm_topo_merge_ctx_t
 

Enumerations

enum  _hm_id_origin_t {
  HM_ID_FROM_MOD_LIST = 1 << 0 ,
  HM_ID_FROM_SRC_IOP = 1 << 1 ,
  HM_ID_FROM_DST_IOP = 1 << 2 ,
  HM_ID_FROM_RULE = 1 << 3
}
 

Functions

char * _hm_make_node_id (const char *op, const char *multi_name)
 
static void _hm_free_input_node (dt_digraph_node_t *n)
 
static void _hm_free_input_nodes (GList *input_nodes)
 
void _hm_id_to_op_name (const char *id, char *op, char *name)
 
static int _hm_build_prev_map_from_ids (const GList *ids, GHashTable **out_prev)
 
static int _hm_build_next_map_from_ids (const GList *ids, GHashTable **out_next)
 
static gboolean _hm_node_has_predecessor (const dt_digraph_node_t *n, const dt_digraph_node_t *pred)
 
static void _hm_remove_predecessor (dt_digraph_node_t *n, const dt_digraph_node_t *pred)
 
static int _hm_build_last_history_by_id_from_history (GList *history, const int history_end, GHashTable **out_map)
 
static int _hm_backup_dest (const dt_develop_t *dev_dest, const GHashTable *mod_list_ids, _hm_dest_backup_t *backup)
 
static void _hm_restore_dest_from_backup (dt_develop_t *dev_dest, _hm_dest_backup_t *backup)
 
static void _hm_backup_cleanup (_hm_dest_backup_t *backup)
 
int _hm_build_last_history_by_id (const dt_develop_t *dev, GHashTable **out_map)
 
static int _hm_build_id_set_from_mod_list (const GList *mod_list, GHashTable **out_ids)
 
static _hm_id_info_t_hm_id_info_upsert (GHashTable *id_ht, const char *op, const char *multi_name, const _hm_id_origin_t origin, const dt_iop_module_t *mod_list, const dt_iop_module_t *src_iop, dt_iop_module_t *dst_iop)
 
static int _hm_ids_from_iop_list (GList *iop, GHashTable *id_ht, const guint keep_mask, GList **out_ids)
 
static int _hm_build_input_nodes_from_ids (const GList *ids, const char *tag, GList **out_nodes)
 
static int _hm_build_input_nodes_from_ids_filtered (const GList *ids, const char *tag, const GHashTable *focus, GList **out_nodes)
 
static int _hm_build_isolated_nodes_from_modules (const GList *modules, const char *tag, GList **out_nodes)
 
static int _hm_build_raster_mask_nodes_from_modules (const GList *modules, GHashTable *id_ht, const guint keep_mask, const char *tag, GList **out_nodes)
 
static int _iop_rules (GHashTable *keep, GList **out_nodes)
 
static void _hm_topo_merge_cleanup (_hm_topo_merge_ctx_t *ctx)
 
static int _hm_topo_build_id_info_table (_hm_topo_merge_ctx_t *ctx, dt_develop_t *dev_dest, dt_develop_t *dev_src, const GList *mod_list)
 
static int _hm_topo_build_constraint_ids (_hm_topo_merge_ctx_t *ctx, dt_develop_t *dev_dest, dt_develop_t *dev_src, const GList *mod_list, const gboolean merge_iop_order)
 
static int _hm_topo_resolve_incompatible_constraints (GList *flat, GHashTable *id_ht, const GList *src_ids, const GList *dest_ids)
 
static int _hm_topo_flatten_constraints (_hm_topo_merge_ctx_t *ctx)
 
static int _hm_topo_sort_constraints (_hm_topo_merge_ctx_t *ctx)
 
static int _hm_topo_apply_solution (_hm_topo_merge_ctx_t *ctx, dt_develop_t *dev_dest, dt_develop_t *dev_src)
 
static int _hm_try_merge_iop_order_topologically (dt_develop_t *dev_dest, dt_develop_t *dev_src, const GList *mod_list, const gboolean merge_iop_order)
 
static void _hm_renumber_history (GList *history)
 
static void _hm_truncate_dest_redo_tail (dt_develop_t *dev_dest)
 
int dt_history_merge (dt_develop_t *dev_dest, dt_develop_t *dev_src, const int32_t dest_imgid, const GList *mod_list, const gboolean merge_iop_order, const dt_history_merge_strategy_t strategy, const gboolean force_new_modules)
 Merge a list of modules into a destination image, solving pipeline topologies for proper insertion of source modules.
 

Detailed Description

Merge histories, including optional pipeline topology merge.

Date
2026-02-22

Merge histories, for copy-pasting and styles.

Merging histories is a twofold problems, we need to solve :

  1. the inner of the module (parameters, blendops)
  2. the topology of the pipeline (ordering).

Let 2 histories A and B, considering A is our existing one, and B is our candidate for pasting. They each have a devA and devB dt_develop_t object. The members of interest are dev->history, dev->iop, dev->iop_order

The first problem is simple : we have append mode (concatenate devB->history at the end of devA->history) or appstart mode (concatenate devB->history at the start of devA->history), decided by user. Histories are commited to pipeline nodes ("popped") in the order of items. Since each history item may overwrite any previous item targetting the same module, this order defines which history takes precedence over the other, in case of conflicts. We do not handle per-module conflict resolution.

The second problem is much harder, because both histories may have different topologies (number of nodes and relative ordering). For this, we need to build temporary pipelines devA->iop and devB->iop using each histories, and turn them into directed graphs. Each module will be a digraph node defined by its previous and next module in its original pipeline, identified by its module->op and module->multi_name, taken as unique ID. Modules having the same ID in both pipes will be assumed to be the same entity and merged. Then we will need a topological sort to resolve (if possible) the complete pipeline order, taking into account the ordering constraints imposed by devA and by devB, which may overconstrain each entity. Once we have the topological order sorted, we will need to create a new pipeline and apply ("pop") the concatenated histories as solved in the first problem.

This supposes users provided an explicit pipeline order as a { module -> index } list.

However, when users don't specify a pipeline order in the merge, we are to assume they don't care. In this case, we enforce a behaviour that is consistent with GUI operation :

  • the base instances of modules are directly matched B to A (no re-ordering, no new instance creation)
  • the additional instances of modules that exist in A and B, and have matching module->op and module->multi_name between A and B, are also directly matched (no re-ordering, no new instance),
  • the additional instances of modules from B that don't exist in A will be created after the last module instance of the same type in A (consistent with GUI interaction on "add new instance" event),
  • the modules from B will never overwrite the pipeline order of those from A.

There is also a special case for a "force new modules" merge. In this mode, all modules from B will be added to A as new instances. This will need to rename and reindex instances properly for the B modules, then update B history accordingly. Pipeline topology will obey the above rules, depending whether an explicit pipeline order was specified or not.

If A does not exist yet, it has to be inited with all the typical defaults applied to new edits. This is done automatically in dt_dev_history_read_history(), which either load history from database or init a fresh one, along with a pipeline (dev->iop).

Before popping the history into modules (as pipeline nodes), each history item will need to be resynced with nodes, especially the pipe order and dt_iop_module_t * references. Here again, module->op and module->multi_name act as unique IDs to match history entries and pipe nodes. History entries that don't find an existing module will need a new module to be created.

Typedef Documentation

◆ _hm_topo_merge_ctx_t

Enumeration Type Documentation

◆ _hm_id_origin_t

Enumerator
HM_ID_FROM_MOD_LIST 
HM_ID_FROM_SRC_IOP 
HM_ID_FROM_DST_IOP 
HM_ID_FROM_RULE 

Function Documentation

◆ _hm_backup_cleanup()

◆ _hm_backup_dest()

◆ _hm_build_id_set_from_mod_list()

static int _hm_build_id_set_from_mod_list ( const GList *  mod_list,
GHashTable **  out_ids 
)
static

◆ _hm_build_input_nodes_from_ids()

static int _hm_build_input_nodes_from_ids ( const GList *  ids,
const char *  tag,
GList **  out_nodes 
)
static

◆ _hm_build_input_nodes_from_ids_filtered()

static int _hm_build_input_nodes_from_ids_filtered ( const GList *  ids,
const char *  tag,
const GHashTable *  focus,
GList **  out_nodes 
)
static

◆ _hm_build_isolated_nodes_from_modules()

static int _hm_build_isolated_nodes_from_modules ( const GList *  modules,
const char *  tag,
GList **  out_nodes 
)
static

◆ _hm_build_last_history_by_id()

◆ _hm_build_last_history_by_id_from_history()

static int _hm_build_last_history_by_id_from_history ( GList *  history,
const int  history_end,
GHashTable **  out_map 
)
static

◆ _hm_build_next_map_from_ids()

static int _hm_build_next_map_from_ids ( const GList *  ids,
GHashTable **  out_next 
)
static

◆ _hm_build_prev_map_from_ids()

static int _hm_build_prev_map_from_ids ( const GList *  ids,
GHashTable **  out_prev 
)
static

◆ _hm_build_raster_mask_nodes_from_modules()

static int _hm_build_raster_mask_nodes_from_modules ( const GList *  modules,
GHashTable *  id_ht,
const guint  keep_mask,
const char *  tag,
GList **  out_nodes 
)
static

◆ _hm_free_input_node()

◆ _hm_free_input_nodes()

◆ _hm_id_info_upsert()

static _hm_id_info_t * _hm_id_info_upsert ( GHashTable *  id_ht,
const char *  op,
const char *  multi_name,
const _hm_id_origin_t  origin,
const dt_iop_module_t mod_list,
const dt_iop_module_t src_iop,
dt_iop_module_t dst_iop 
)
static

◆ _hm_id_to_op_name()

void _hm_id_to_op_name ( const char *  id,
char *  op,
char *  name 
)

◆ _hm_ids_from_iop_list()

static int _hm_ids_from_iop_list ( GList *  iop,
GHashTable *  id_ht,
const guint  keep_mask,
GList **  out_ids 
)
static

◆ _hm_make_node_id()

◆ _hm_node_has_predecessor()

static gboolean _hm_node_has_predecessor ( const dt_digraph_node_t n,
const dt_digraph_node_t pred 
)
static

References FALSE, n, p, and TRUE.

Referenced by _hm_topo_resolve_incompatible_constraints().

◆ _hm_remove_predecessor()

static void _hm_remove_predecessor ( dt_digraph_node_t n,
const dt_digraph_node_t pred 
)
static

References n.

Referenced by _hm_topo_resolve_incompatible_constraints().

◆ _hm_renumber_history()

static void _hm_renumber_history ( GList *  history)
static

References h.

Referenced by dt_history_merge().

◆ _hm_restore_dest_from_backup()

◆ _hm_topo_apply_solution()

◆ _hm_topo_build_constraint_ids()

◆ _hm_topo_build_id_info_table()

◆ _hm_topo_flatten_constraints()

◆ _hm_topo_merge_cleanup()

◆ _hm_topo_resolve_incompatible_constraints()

static int _hm_topo_resolve_incompatible_constraints ( GList *  flat,
GHashTable *  id_ht,
const GList *  src_ids,
const GList *  dest_ids 
)
static

◆ _hm_topo_sort_constraints()

◆ _hm_truncate_dest_redo_tail()

static void _hm_truncate_dest_redo_tail ( dt_develop_t dev_dest)
static

◆ _hm_try_merge_iop_order_topologically()

◆ _iop_rules()

static int _iop_rules ( GHashTable *  keep,
GList **  out_nodes 
)
static

◆ dt_history_merge()

int dt_history_merge ( struct dt_develop_t dev_dest,
struct dt_develop_t dev_src,
const int32_t  dest_imgid,
const GList *  mod_list,
const gboolean  merge_iop_order,
const dt_history_merge_strategy_t  strategy,
const gboolean  force_new_modules 
)

Merge a list of modules into a destination image, solving pipeline topologies for proper insertion of source modules.

Parameters
dev_destDestination develop stack (must be initialized, history read and popped).
dev_srcSource develop stack (must be initialized, history read and popped). May be NULL if merge_iop_order is FALSE (masks won't be copied).
dest_imgidDestination image id.
mod_listList of dt_iop_module_t* to merge (usually coming from dev_src).
merge_iop_orderIf TRUE, attempt to merge the pipeline order constraints from src and dest using a topological sort. On unsatisfiable constraints, falls back to overwriting the destination iop-order list with the source list.
strategyDT_HISTORY_MERGE_APPEND or DT_HISTORY_MERGE_APPSTART.
force_new_modulesIf TRUE, always add modules from source as new instances (when possible).
Returns
0 on success, 1 on error.

References _hm_backup_cleanup(), _hm_backup_dest(), _hm_build_id_set_from_mod_list(), _hm_build_last_history_by_id(), _hm_renumber_history(), _hm_restore_dest_from_backup(), _hm_show_merge_report_popup(), _hm_truncate_dest_redo_tail(), _hm_try_merge_iop_order_topologically(), _hm_warn_missing_raster_producers(), cleanup(), DT_DEBUG_HISTORY, dt_dev_get_history_end_ext(), dt_dev_get_module_instance(), dt_dev_history_get_last_item_by_module(), dt_dev_history_item_from_source_history_item(), dt_dev_set_history_end_ext(), DT_HISTORY_MERGE_APPEND, dt_ioppr_resync_pipeline(), dt_print(), FALSE, dt_develop_t::history, dt_iop_module_t::multi_name, dt_iop_module_t::multi_priority, dt_iop_module_t::op, _hm_dest_backup_t::orig_ids, _hm_dest_backup_t::orig_labels, and _hm_dest_backup_t::orig_styles.

Referenced by dt_dev_merge_history_into_image().