Login
Documentation
Login
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#if !defined(NET_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED)
#define NET_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED
/*
  Copyright 2013-2021 Stephan Beal (https://wanderinghorse.net).

  Derived heavily from previous work:

  Copyright (c) 2013 D. Richard Hipp (https://www.hwaci.com/drh/)


  This program is free software; you can redistribute it and/or
  modify it under the terms of the Simplified BSD License (also
  known as the "2-Clause License" or "FreeBSD License".)

  This program is distributed in the hope that it will be useful,
  but without any warranty; without even the implied warranty of
  merchantability or fitness for a particular purpose.

  ******************************************************************************
  This file declares public APIs for working with checkout-side fossil content.
*/

#include "fossil-db.h" /* MUST come first b/c of config macros */
#include "fossil-repo.h"

#if defined(__cplusplus)
extern "C" {
#endif


/**
   Given a fsl_cx with an opened checkout, and a filename, this
   function canonicalizes zOrigName to a form suitable for use as
   an in-repo filename, _appending_ the results to pOut. If pOut is
   NULL, it performs its normal checking but does not write a
   result, other than to return 0 for success.

   As a special case, if zOrigName refers to the top-level checkout
   directory, it resolves to either "." or "./", depending on whether
   zOrigName contains a trailing slash.

   If relativeToCwd is true then the filename is canonicalized
   based on the current working directory (see fsl_getcwd()),
   otherwise f's current checkout directory is used as the virtual
   root.

   If the input name contains a trailing slash, it is retained in
   the output sent to pOut except in the top-dir case mentioned
   above.

   Returns 0 on success, meaning that the value appended to pOut
   (if not NULL) is a syntactically valid checkout-relative path.

   Returns FSL_RC_RANGE if zOrigName points to a path outside
   of f's current checkout root.

   Returns FSL_RC_NOT_A_CHECKOUT if f has no checkout opened.

   Returns FSL_RC_MISUSE if !f, !zOrigName, FSL_RC_OOM on an
   allocation error.

   This function does not validate whether or not the file actually
   exists, only that its name is potentially valid as a filename
   for use in a checkout (though other, downstream rules might prohibit that, e.g.
   the filename "..../...." is not valid but is not seen as invalid by
   this function). (Reminder to self: we could run the end result through
   fsl_is_simple_pathname() to catch that?)
*/
FSL_EXPORT int fsl_checkout_filename_check( fsl_cx * f, char relativeToCwd,
                                            char const * zOrigName, fsl_buffer * pOut );


/**
   Adds the given filename to the current checkout vfile list of
   files as a to-be-added file, or updates an existing record if
   one exists.

   If relativeToCwd is true (non-0) then the filename is
   resolved/canonicalized based on the current working directory
   (see fsl_getcwd()), otherwise f's current checkout directory is
   used as the virtual root. This makes a subtle yet important
   difference in how the name is resolved. CLI apps which take file
   names from the user will generally want to set relativeToCwd to
   true. GUI apps, OTOH, will possibly need it to be false,
   depending on how they resolve and pass on the filenames.

   This function ensures that zFilename gets canonicalized and can
   be found under the checkout directory, and fails if no such file
   exists (checking against the canonicalized name).

   Returns 0 on success, non-0 on error.

   Note that unlike fsl_checkout_file_rm(), this routine cannot
   recursively add files from a directory name. Fixing that is on
   the TODO list.

   @see fsl_checkout_file_rm()
*/
FSL_EXPORT int fsl_checkout_file_add( fsl_cx * f, char relativeToCwd, char const * zFilename );

/**
   The converse of fsl_checkout_file_add(), this queues a file for
   removal from the current checkout. The arguments have identical
   meanings as for fsl_checkout_file_add() except that this routine
   does not ensure that the resolved filename actually exists - it
   only normalizes zFilename into its repository-friendly form.

   If recurseDirs is true then if zFilename refers to a directory
   then this operation queues all files under that directory
   (recursively) for removal. In this case, it is irrelevant
   whether or not zFilename ends in a trailing slash or not.

   Returns 0 on success, any of a number of non-0 codes on error.
   Returns FSL_RC_MISUSE if !f, !zFilename, or !*zFilename.
   Returns FSL_RC_NOT_A_CHECKOUT if f has no opened checkout.

   @see fsl_checkout_file_add()
*/
FSL_EXPORT int fsl_checkout_file_rm( fsl_cx * f, char relativeToCwd, char const * zFilename,
                          char recurseDirs );

/**
   Change-type flags for use with fsl_checkout_changes_visit() and
   friends.
*/
enum fsl_checkout_change_t {
/**
   Sentinel placeholder value.
*/
FSL_CKOUT_CHANGE_NONE = 0,
/**
   Indicates that a file was modified in some unspecified way.
*/
FSL_CKOUT_CHANGE_MOD,
/**
   Indicates that a file was modified as the result of a merge.
*/
FSL_CKOUT_CHANGE_MERGE_MOD,
/**
   Indicates that a file was added as the result of a merge.
*/
FSL_CKOUT_CHANGE_MERGE_ADD,
/**
   Indicates that a file was modified as the result of an
   integrate-merge.
*/
FSL_CKOUT_CHANGE_INTEGRATE_MOD,
/**
   Indicates that a file was added as the result of an
   integrate-merge.
*/
FSL_CKOUT_CHANGE_INTEGRATE_ADD,
/**
   Indicates that a file was added.
*/
FSL_CKOUT_CHANGE_ADDED,
/**
   Indicates that a file was removed.
*/
FSL_CKOUT_CHANGE_REMOVED,
/**
   Indicates that a file is missing from the local checkout.
*/
FSL_CKOUT_CHANGE_MISSING,
/**
   Indicates that a file was renamed.
*/
FSL_CKOUT_CHANGE_RENAMED,
/**
   NOT YET USED.

   Indicates that a file contains conflict markers.
*/
FSL_CKOUT_CHANGE_CONFLICT,
/**
   NOT YET USED.

   Indicates that a file in the changes table references
   a non-file on disk.
*/
FSL_CKOUT_CHANGE_NOT_A_FILE,
/**
   NOT YET USED.

   Indicates that a file is part of a cherrypick merge.
*/
FSL_CKOUT_CHANGE_CHERRYPICK,
/**
   NOT YET USED.

   Indicates that a file is part of a backout.
*/
FSL_CKOUT_CHANGE_BACKOUT
};

typedef enum fsl_checkout_change_t fsl_checkout_change_t;

/**
   Sets up the vfile table in f's opened checkout db and scans the
   checkout root directory's contents for changes compared to the
   pristine checkout state. It records any changes in the vfile
   table.

   Returns 0 on success, non-0 on error, FSL_RC_MISUSE
   if !f.

   For compatibility with fossil(1), this routine clears the vfile
   table of any entries not related to the current checkout.

   TODO: a variant of this which scans only a given file or directory.

   @see fsl_checkout_changes_visit()
*/
FSL_EXPORT int fsl_checkout_changes_scan(fsl_cx * f);

/**
   A typedef for visitors of checkout status information via
   fsl_checkout_changes_visit(). Implementions will receive the
   last argument passed to fsl_checkout_changes_visit() as their
   first argument. The second argument indicates the type of change
   and the third holds the repository-relative name of the file.

   If changes is FSL_CKOUT_CHANGE_RENAMED then origName will hold
   the original name, else it will be NULL.

   Implementations must return 0 on success, non-zero on error. On
   error any looping performed by fsl_checkout_changes_visit() will
   stop and this function's result code will be returned.

   @see fsl_checkout_changes_visit()
*/
typedef int (*fsl_checkout_changes_f)(void * state, fsl_checkout_change_t change,
                                      char const * filename,
                                      char const * origName);

/**
   Compares the changes of f's local checkout against repository
   version vid (checkout version if vid is negative). For each
   change detected it calls visitor(state,...) to report the
   change.  If visitor() returns non-0, that code is returned from
   this function. If doChangeScan is true then
   fsl_checkout_changes_scan() is called by this function before
   iterating, otherwise it is assumed that the caller has called
   that or has otherwise ensured that the checkout db's vfile table
   has been populated.

   Returns 0 on success.

   @see fsl_checkout_changes_scan()
*/
FSL_EXPORT int fsl_checkout_changes_visit( fsl_cx * f, fsl_id_t vid,
                                           char doChangeScan,
                                           fsl_checkout_changes_f visitor,
                                           void * state );
/**
   A bitmask of flags for fsl_vfile_changes_scan().
*/
enum fsl_ckout_sig_t {
/**
   The empty flags set.
*/
FSL_VFILE_CKSIG_NONE = 0,

/**
   Non-file FS objects throw an error. Not yet implemented.
*/
FSL_VFILE_CKSIG_ENOTFILE = 0x001,
/**
   Verify file content using sha1sum, regardless of whether or not
   file timestamps differ.
*/
FSL_VFILE_CKSIG_SHA1 = 0x002,
/**
   For unchanged or changed-by-merge files, set the mtime to last
   check-out time, as determined by fsl_mtime_of_manifest_file().
*/
FSL_VFILE_CKSIG_SETMTIME = 0x004,
/**
   Indicates that when populating the vfile table, it should be
   cleared of entries for other checkins. This is primarily for
   compatibility with fossil(1), which generally assumes only a
   single checkin's worth of state is in vfile and can get confused
   if that is not the case.
*/
FSL_VFILE_CKSIG_CLEAR_VFILE = 0x008
};

/**
    This function populates (if needed) the vfile table of f's
    checkout db for the given checkin version ID then compares files
    listed in it against files in the checkout directory, updating
    vfile's status for the current checkout version id as its goes.
    If vid is negative then the current checkout's RID is used in
    its place (note that 0 is the RID of an initial empty
    repository!). cksigFlags must be a bitmask of fsl_ckout_sig_t
    values.

    Returns 0 on success, non-0 on error.

    BUG: this does not properly catch one particular corner-case
    change, where a file has been replaced by a same-named non-file
    (symlink or directory).
*/
FSL_EXPORT int fsl_vfile_changes_scan(fsl_cx * f, fsl_id_t vid, int cksigFlags);


/**
   Adds the given file f's list of "selected" files - the list of
   filenames which should be included in the next commit
   (see fsl_checkin_commit()).

   Warning: if this function is not called before
   fsl_checkin_commit(), then fsl_checkin_commit() will select all
   modified, added, removed, or renamed files by default.

   zName must be a non-empty NUL-terminated string and its bytes are
   copied. The filename is canonicalized via
   fsl_checkout_filename_check() - see that function for the meaning
   of the relativeToCwd parameter.

   The resolved name must refer to either a single vfile.pathname
   value in the current vfile table (i.e. in the current checkout,
   though possibly newly-added and not necessarily committed), or it
   must refer to a directory, under which all modified, added,
   deleted, or renamed files are queued up for the next commit.

   If given a single file name it returns FSL_RC_NOT_FOUND if the
   file is not in the current checkout. In this case f's error state
   is updated with a description of the problem.

   If given a directory name, it does not validate that any changes
   are actually detected for queuing up (that detection comes at the
   final commit stage).

   The change-state of the file(s) is not actually checked by this
   function, other than to confirm that the file is indeed listed in
   the current checkout. That means that the file may still be
   modified by the client before the commit takes place, and the
   changes on disk at the point of the fsl_checkin_commit() are the
   ones which get saved (or not).

   Returns 0 on success, FSL_RC_MISUSE if either pointer is NULL,
   or *zName is NUL. Returns FSL_RC_OOM on allocation error.

   On error f's error state might (depending on the nature of the
   problem) contain more details.

   Returns 0 and has no side-effects if zName is already in the
   checkin queue. This function honors the
   fsl_cx_is_case_sensitive() setting when comparing names but the
   check for the repo-level file is case-sensitive! That's arguably
   a bug.

   @see fsl_checkin_file_is_enqueued()
   @see fsl_checkin_file_dequeue()
   @see fsl_checkin_discard()
   @see fsl_checkin_commit()
*/
FSL_EXPORT int fsl_checkin_file_enqueue(fsl_cx * f, char const * zName,
                             char relativeToCwd);

/**
   The opposite of fsl_checkin_file_enqueue(), then removes the
   given file or directory name from f's checkin queue. Returns 0 on
   succes. Unlike fsl_checkin_file_enqueue(), this function does
   little validation on the input and simply asks the internals to
   clean up. Specifically, it does not return an error if this
   operation finds no entries to unqueue. If zName is empty or NULL
   then ALL files are unqueued from the pending checkin.

   If relativeToCwd is true (non-0) then zName is resolved based on
   the current directory, otherwise it is resolved based on the
   checkout's root directory.


   @see fsl_checkin_file_enqueue()
   @see fsl_checkin_file_is_enqueued()
   @see fsl_checkin_discard()
   @see fsl_checkin_commit()
*/
FSL_EXPORT int fsl_checkin_file_dequeue(fsl_cx * f, char const * zName,
                             char relativeToCwd);

/**
   Returns true (non-0) if the file named by zName is in f's current
   file checkin queue.  If NO files are in the current selection
   queue then this routine assumes that ALL files are implicitely
   selected. As long as at least once file is enqueud (via
   fsl_checkin_file_enqueue()) then this function only returns true
   for files which have been explicitly enqueued.

   If relativeToCwd then zName is resolved based on the current
   directory, otherwise it is resolved based on the checkout's
   root directory.

   This function returning true does not necessarily indicate that
   the file _will_ be checked in at the next commit. If the file has
   not been modified at commit-time then it will not be part of the
   commit.

   This function honors the fsl_cx_is_case_sensitive() setting
   when comparing names.

   Achtung: this does not resolve directory names like
   fsl_checkin_file_enqueue() and fsl_checkin_file_dequeue() do. It
   only works with file names.

   @see fsl_checkin_file_enqueue()
   @see fsl_checkin_file_dequeue()
   @see fsl_checkin_discard()
   @see fsl_checkin_commit()
*/
FSL_EXPORT int fsl_checkin_file_is_enqueued(fsl_cx * f, char const * zName,
                                            int relativeToCwd);

/**
   Discards any state accumulated for a pending checking,
   including any files queued via fsl_checkin_file_enqueue()
   and tags added via fsl_checkin_T_add().

   @see fsl_checkin_file_enqueue()
   @see fsl_checkin_file_dequeue()
   @see fsl_checkin_file_is_enqueued()
   @see fsl_checkin_commit()
   @see fsl_checkin_T_add()
*/
FSL_EXPORT void fsl_checkin_discard(fsl_cx * f);

/**
   Parameters for fsl_checkin_commit().

   Checkins are created in a multi-step process:

   - fsl_checkin_file_enqueue() queues up a file or directory for
   commit at the next commit.

   - fsl_checkin_file_dequeue() removes an entry, allowing
   UIs to toggle files in and out of a checkin before
   committing it.

   - fsl_checkin_file_is_enqueued() can be used to determine whether
   a given name is already enqueued or not.

   - fsl_checkin_T_add() can be used to T-cards (tags) to a
   deck. Branch tags are intended to be applied via the
   fsl_checkin_opt::branch member.

   - fsl_checkin_discard() can be used to cancel any pending file
   enqueuings, effectively cancelling a commit (which can be
   re-started by enqueuing another file).

   - fsl_checkin_commit() creates a checkin for the list of enqueued
   files (defaulting to all modified files in the checkout!). It
   takes an object of this type to specify a variety of parameters
   for the check.

   Note that this API uses the terms "enqueue" and "unqueue" rather
   than "add" and "remove" because those both have very specific
   (and much different) meanings in the overall SCM scheme.
*/
struct fsl_checkin_opt {
  /**
     The commit message. May not be empty - the library
     forbids empty checkin messages.
  */
  char const * message;

  /**
     The optional mime type for the message. Only set
     this if you know what you're doing.
  */
  char const * messageMimeType;

  /**
     The user name for the checkin. If NULL or empty, it defaults to
     fsl_cx_user_get(). If that is NULL, a FSL_RC_RANGE error is
     triggered.
  */
  char const * user;

  /**
     Don't use this yet - it is not yet tested all that well.

     If not NULL, makes the checkin the start of a new branch with
     this name.
  */
  char const * branch;

  /**
     If this->branch is not NULL, this is applied as its "bgcolor"
     propagating property. If this->branch is NULL then this is
     applied as a one-time color tag to the checkin.

     It must be NULL, empty, or in a form usable by HTML/CSS,
     preferably \#RRGGBB form. Length-0 values are ignored (as if
     they were NULL).
  */
  char const * bgColor;

  /**
     If true, the checkin will be marked as private, otherwise it
     will be marked as private or public, depending on whether or
     not it inherits private content.
  */
  char isPrivate;

  /**
     Whether or not to calculate an R-card. Doing so is very
     expensive (memory and I/O) but it adds another layer of
     consistency checking to manifest files. In practice, the R-card
     is somewhat superfluous and the cost of calculating it has
     proven painful on very large repositories. fossil(1) creates an
     R-card for all checkins but does not require that one be set
     when it reads a manifest.
  */
  int calcRCard;

  /**
     Whether to allow (or try to force) a delta manifest or not. 0
     means no deltas allowed - it will generate a baseline
     manifest. Greater than 0 forces generation of a delta if
     possible (if one can be readily found) even if doing so would not
     save a notable amount of space. Less than 0 means to
     decide via some heuristics.

     A "readily available" baseline means either the current
     checkout is a baseline or has a baseline. In either case, we
     can use that as a baseline for a delta. i.e. a baseline
     "should" basically available except on the initial checkin,
     which has neither a parent checkin nor a baseline.

     The current behaviour for "auto-detect" mode is: it will
     generate a delta if a baseline is "readily available." Once it calculates a delta form, it calculates
     whether that form saves any appreciable space/overhead compared
     to whether a baseline manifest was generated. If so, it
     discards the delta and re-generates the manifest as a
     baseline. The "force" behaviour (deltaPolicy>0) bypasses the
     "is it too big?" test, and is only intended for testing, not
     real-life use.

     Caveat: if the repository has the "forbid-delta-manifests" set to
     a true value, this option is ignored: that setting takes
     priority.
  */
  int deltaPolicy;

  /**
     Tells the checkin to close merged-in branches (merge type of
     0). INTEGRATE merges (type=-4) are always closed by a
     checkin. This does not apply to CHERRYPICK (type=-1) and
     BACKOUT (type=-2) merges.
  */
  int integrate;

  /**
     Time of the checkin. If 0 or less, the current time
     is used.
  */
  double julianTime;

  /**
     If this is not NULL then the committed manifest will include a
     tag which closes the branch. The value of this string will be
     the value of the "closed" tag, and the value may be an empty
     string. The intention is that this gets set to a comment about
     why the branch is closed, but it is in no way mandatory.
  */
  char const * closeBranch;

  /**
     Tells fsl_checkin_commit() to dump the generated manifest to
     this file. Intended only for debugging and testing. Checking in
     will fail if this file cannot be opened for writing.
  */
  char const * dumpManifestFile;
  /*
    fossil(1) has many more options. We might want to wrap some of
    it up in the "incremental" state (f->ckin.mf).

    TODOs:

    A callback mechanism which supports the user cancelling
    the checkin. It is (potentially) needed for ops like
    confirming the commit of CRNL-only changes.
  */
};

/**
   Empty-initialized fsl_checkin_opt instance, intended for use in
   const-copy constructing.
*/
#define fsl_checkin_opt_empty_m {               \
    NULL/*message*/,                            \
      NULL/*messageMimeType*/,                  \
      NULL/*user*/,                             \
      NULL/*branch*/,                           \
      NULL/*bgColor*/,                          \
      0/*isPrivate*/,                           \
      1/*calcRCard*/,                           \
      -1/*deltaPolicy*/,                        \
      0/*integrate*/,                           \
      0.0/*julianTime*/,                        \
      NULL/*closeBranch*/,                      \
      NULL/*dumpManifestFile*/                  \
      }

/**
   Empty-initialized fsl_checkin_opt instance, intended for use in
   copy-constructing. It is important that clients copy this value
   (or fsl_checkin_opt_empty_m) to cleanly initialize their
   fsl_checkin_opt instances, as this may set default values which
   (e.g.) a memset() would not.
*/
FSL_EXPORT const fsl_checkin_opt fsl_checkin_opt_empty;

/**
   Do not use - under construction/testing. Very dangerous. Stay
   away. If you choose to ignore this warning, then read on...

   This creates a "checkin manifest" for the current checkout.

   Its primary inputs is a list of files to commit. This list
   is provided by the client by calling fsl_checkin_file_enqueue()
   one or more times.  If no files are explicitely selected
   (enqueued) then it calculates which local files have changed vs
   the current checkout and selects all of those.

   Non-file inputs are provided via the opt parameter.

   Tip: to implement a "dry-run" mode, simply wrap this call in a
   transaction started on the fsl_cx_db_checkout() db handle
   (passing it to fsl_db_transaction_begin()), then, after this
   call, either cal; fsl_db_transaction_rollback() (to implement
   dry-run mode) or fsl_db_transaction_commit() (for "wet-run"
   mode). If this function returns non-0, such a transaction should
   _always_ be rolled back!

   On success, it returns 0 and...

   - If newRid is not NULL, it is assigned the new checkin's RID
   value.

   - If newUuid is not NULL, it is assigned the new checkin's UUID
   value. Ownership of the bytes is passed to the caller, who must
   eventually pass them to fsl_free() to free them.

   On error non-0 is returned and f's error state may (depending on
   the nature of the problem) contain details about the problem.
   Note, however, that any error codes returned here may have
   arrived from several layers down in the internals, and may not
   have a single specific interpretation here. When
   possible/practical, f's error state gets updated with a
   human-readable description of the problem.

   ACHTUNG: all pending checking state is cleaned if this function
   fails for any reason other than basic argument validation. This
   means any queued files or tags need to be re-applied if the
   client wants to try again. That is somewhat of a bummer, but this
   behaviour is the only way we can ensure that then the pending
   checkin state does not get garbled on a second use. When in doubt
   about the state, the client should call fsl_checkin_discard() to
   clear it before try to re-commit. (Potential TODO: add a
   success/fail state flag to the checkin state and only clean up on
   success? OTOH, since something in the state likely caused the
   problem, we might not want to do that.)

   This operation does all of its db-related work i a transaction,
   so it rolls back any db changes if it fails.

   Some of the more notable, potentially not obvious, error
   conditions:

   - Trying to commit against a closed leaf: FSL_RC_MISUSE

   - An empty/NULL user name or commit message, or no files were
   selected which actually changed: FSL_RC_RANGE. In these cases
   f's error state describes the problem.

   - Some resource is not found (e.g. an expected RID/UUID could not
   be resolved): FSL_RC_NOT_FOUND. This would generally indicate
   some sort of data consistency problem. i.e. it's quite possible
   very bad if this is returned.

   BUGS:

   - It cannot currently properly distinguish a "no-op" commit, one in
   which no files were modified. If, e.g.

   @see fsl_checkin_file_enqueue()
   @see fsl_checkin_file_dequeue()
   @see fsl_checkin_discard()
   @see fsl_checkin_T_add()
*/
FSL_EXPORT int fsl_checkin_commit(fsl_cx * f, fsl_checkin_opt const * opt,
                       fsl_id_t * newRid, fsl_uuid_str * newUuid);

/**
   Works like fsl_deck_T_add(), adding the given tag information to
   the pending checkin state. Returns 0 on success, non-0 on
   error. A checkin may, in principal, have any number of tags, and
   this may be called any number of times to add new tags to the
   pending commit. This list of tags gets cleared by a successful
   fsl_checkin_commit() or by fsl_checkin_discard().

   @see fsl_checkin_file_enqueue()
   @see fsl_checkin_file_dequeue()
   @see fsl_checkin_commit()
   @see fsl_checkin_discard()
*/
FSL_EXPORT int fsl_checkin_T_add( fsl_cx * f, fsl_tagtype_e tagType,
                       fsl_uuid_cstr uuid, char const * name,
                       char const * value);



#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* NET_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED */