/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
#if !defined(ORG_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED
/*
Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt
SPDX-License-Identifier: BSD-2-Clause-FreeBSD
SPDX-FileCopyrightText: 2021 The Libfossil Authors
SPDX-ArtifactOfProjectName: Libfossil
SPDX-FileType: Code
Heavily indebted to the Fossil SCM project (https://fossil-scm.org).
*/
/** @file fossil-checkout.h
fossil-checkout.h declares APIs specifically dealing with
checkout-side state, as opposed to purely repository-db-side state
or non-content-related APIs.
*/
#include "fossil-db.h" /* MUST come first b/c of config macros */
#include "fossil-repo.h"
#if defined(__cplusplus)
extern "C" {
#endif
/**
Returns version information for the current checkout.
If f has an opened checkout then...
If uuid is not NULL then *uuid is set to the UUID of the opened
checkout, or NULL if there is no checkout. If rid is not NULL, *rid
is set to the record ID of that checkout, or 0 if there is no
checkout (or the current checkout is from an empty repository). The
returned uuid bytes and rid are owned by f and valid until the
library updates its checkout state to a newer checkout version
(essentially unpredictably). When in doubt about lifetime issues,
copy the UUID immediately after calling this if they will be needed
later.
Corner case: a new repo with no checkins has an RID of 0 and a UUID
of NULL. That does not happen with fossil-generated repositories,
as those always "seed" the database with an initial commit artifact
containing no files.
*/
FSL_EXPORT void fsl_ckout_version_info(fsl_cx * const f, fsl_id_t * const rid,
fsl_uuid_cstr * const uuid );
/**
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_CKOUT if f has no checkout opened.
Returns FSL_RC_MISUSE if !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_ckout_filename_check( fsl_cx * f, bool relativeToCwd,
char const * zOrigName, fsl_buffer * pOut );
/**
Callback type for use with fsl_ckout_manage_opt(). It should
inspect the given filename using whatever criteria it likes, set
*include to true or false to indicate whether the filename is okay
to include the current add-file-to-repo operation, and return 0.
If it returns non-0 the add-file-to-repo process will end and that
error code will be reported to its caller. Such result codes must
come from the FSL_RC_xxx family.
It will be passed a name which is relative to the top-most checkout
directory.
The final argument is not used by the library, but is passed on
as-is from the fsl_ckout_manage_opt::callbackState pointer which
is passed to fsl_ckout_manage().
*/
typedef int (*fsl_ckout_manage_f)(const char *zFilename,
bool *include,
void *state);
/**
Options for use with fsl_ckout_manage().
*/
struct fsl_ckout_manage_opt {
/**
The file or directory name to add. If it is a directory, the add
process will recurse into it.
*/
char const * filename;
/**
Whether to evaluate the given name as relative to the current working
directory or to the current checkout root.
This makes a subtle yet important difference in how the name is
resolved. CLI apps which take file names from the user from
within a checkout directory 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.
*/
bool relativeToCwd;
/**
Whether or not to check the name(s) against the 'ignore-globs'
config setting (if set).
*/
bool checkIgnoreGlobs;
/**
Optional predicate function which may be called for each
to-be-added filename. It is only called if:
- It is not NULL (obviously) and...
- The file is not already in the checkout database and...
- The is-internal-name check passes (see
fsl_reserved_fn_check()) and...
- If checkIgnoreGlobs is false or the name does not match one of
the ignore-globs values.
The name it is passed is relative to the checkout root.
Because the callback is called only if other options have not
already excluded the file, the client may use the callback to
report to the user (or otherwise record) exactly which files
get added.
*/
fsl_ckout_manage_f callback;
/**
State to be passed to this->callback.
*/
void * callbackState;
/**
These counts are updated by fsl_ckout_manage() to report
what it did.
*/
struct {
/**
Number of files actually added by fsl_ckout_manage().
*/
uint32_t added;
/**
Number of files which were requested to be added but were only
updated because they had previously been added. Updates set the
vfile table entry's current mtime, executable-bit state, and
is-it-a-symlink state. (That said, this code currently ignores
symlinks altogether!)
*/
uint32_t updated;
/**
The number of files skipped over for addition. This includes
files which meet any of these criteria:
- fsl_reserved_fn_check() fails.
- If the checkIgnoreGlobs option is true and a filename matches
any of those globs.
- The client-provided callback says not to include the file.
*/
uint32_t skipped;
} counts;
};
typedef struct fsl_ckout_manage_opt fsl_ckout_manage_opt;
/**
Initialized-with-defaults fsl_ckout_manage_opt instance,
intended for use in const-copy initialization.
*/
#define fsl_ckout_manage_opt_empty_m {\
NULL/*filename*/, true/*relativeToCwd*/, true/*checkIgnoreGlobs*/, \
NULL/*callback*/, NULL/*callbackState*/, \
{/*counts*/ 0/*added*/, 0/*updated*/, 0/*skipped*/} \
}
/**
Initialized-with-defaults fsl_ckout_manage_opt instance,
intended for use in non-const copy initialization.
*/
FSL_EXPORT const fsl_ckout_manage_opt fsl_ckout_manage_opt_empty;
/**
Adds the given filename or directory (recursively) to the current
checkout vfile list of files as a to-be-added file, or updates an
existing record if one exists.
This function ensures that opt->filename gets canonicalized and can
be found under the checkout directory, and fails if no such file
exists (checking against the canonicalized name). Filenames are all
filtered through fsl_reserved_fn_check() and may have other filters
applied to them, as determined by the options object.
Each filename which passes through the filters is passed to
the opt->callback (if not NULL), which may perform a final
filtering check and/or alert the client about the file being
queued.
The options object is non-const because this routine updates
opt->counts when it adds, updates, or skips a file. On each call,
it updated opt->counts without resetting it (as this function is
typically called in a loop). This function does not modify any
other entries of that object and it requires that the object not be
modified (e.g. via opt->callback()) while it is recursively
processing. To reset the counts between calls, if needed:
```
opt->counts = fsl_ckout_manage_opt_empty.counts;
```
Returns 0 on success, non-0 on error.
Files queued for addition this way can be unqueued before they are
committed using fsl_ckout_unmanage().
@see fsl_ckout_unmanage()
@see fsl_reserved_fn_check()
*/
FSL_EXPORT int fsl_ckout_manage( fsl_cx * const f,
fsl_ckout_manage_opt * const opt );
/**
Callback type for use with fsl_ckout_unmanage(). It is called
by the removal process, immediately after a file is "removed"
from SCM management (a.k.a. when the file becomes "unmanaged").
If it returns non-0 the unmanage process will end and that
error code will be reported to its caller. Such result codes must
come from the FSL_RC_xxx family.
It will be passed a name which is relative to the top-most checkout
directory. The client is free to unlink the file from the filesystem
if they like - the library does not do so automatically
The final argument is not used by the library, but is passed on
as-is from the callbackState pointer which
is passed to fsl_ckout_unmanage().
*/
typedef int (*fsl_ckout_unmanage_f)(const char *zFilename, void *state);
/**
Options for use with fsl_ckout_unmanage().
*/
struct fsl_ckout_unmanage_opt {
/**
The file or directory name to add. If it is a directory, the add
process will recurse into it. See also this->vfileIds.
*/
char const * filename;
/**
An alternative to assigning this->filename is to point
this->vfileIds to a bag of vfile.id values. If this member is not
NULL, fsl_ckout_unmanage() will ignore this->filename.
@see fsl_filename_to_vfile_ids()
*/
fsl_id_bag const * vfileIds;
/**
Whether to evaluate this->filename as relative to the current
working directory (true) or to the current checkout root
(false). This is ignored when this->vfileIds is not NULL.
This makes a subtle yet important difference in how the name is
resolved. CLI apps which take file names from the user from
within a checkout directory 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.
*/
bool relativeToCwd;
/**
If true, fsl_vfile_changes_scan() is called to ensure that
the filesystem and vfile tables agree. If the client code has
called that function, or its equivalent, since any changes were
made to the checkout then this may be set to false to speed up
the rm process.
*/
bool scanForChanges;
/**
Optional predicate function which will be called after each
file is made unmanaged.
The name it is passed is relative to the checkout root.
*/
fsl_ckout_unmanage_f callback;
/**
State to be passed to this->callback.
*/
void * callbackState;
};
typedef struct fsl_ckout_unmanage_opt fsl_ckout_unmanage_opt;
/**
Initialized-with-defaults fsl_ckout_unmanage_opt instance,
intended for use in const-copy initialization.
*/
#define fsl_ckout_unmanage_opt_empty_m {\
NULL/*filename*/, NULL/*vfileIds*/,\
true/*relativeToCwd*/,true/*scanForChanges*/, \
NULL/*callback*/, NULL/*callbackState*/ \
}
/**
Initialized-with-defaults fsl_ckout_unmanage_opt instance,
intended for use in non-const copy initialization.
*/
FSL_EXPORT const fsl_ckout_unmanage_opt fsl_ckout_unmanage_opt_empty;
/**
The converse of fsl_ckout_manage(), this queues a file for removal
from the current checkout. Unlike fsl_ckout_manage(), this routine
does not ensure that opt->filename actually exists - it only
normalizes zFilename into its repository-friendly form and passes
it through the vfile table.
If opt->filename 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 opt->filename ends in a
trailing slash.
Returns 0 on success, any of a number of non-0 codes on error.
Returns FSL_RC_MISUSE if !opt->filename or !*opt->filename.
Returns FSL_RC_NOT_A_CKOUT if f has no opened checkout.
If opt->callback is not NULL, it is called for each
newly-unamanaged entry. The intention is to provide it the
opportunity to notify the user, record the filename for later use,
remove the file from the filesystem, etc. If it returns non-0, the
unmanaging process will fail with that code and any pending
transaction will be placed into a rollback state.
This routine does not actually remove any files from the
filesystem, it only modifies the vfile table entry so that the
file(s) will be removed from the SCM by the commit process. If
opt->filename is an entry which was previously
fsl_ckout_manage()'d, but not yet committed, or any such entries
are found under directory opt->filename, they are removed from the
vfile table. i.e. this effective undoes the add operation.
@see fsl_ckout_manage()
*/
FSL_EXPORT int fsl_ckout_unmanage( fsl_cx * f,
fsl_ckout_unmanage_opt const * opt );
/**
Hard-coded range of values of the vfile.chnged db field.
These values are part of the fossil schema and must not
be modified.
*/
enum fsl_vfile_change_e {
/** File is unchanged. */
FSL_VFILE_CHANGE_NONE = 0,
/** File edit. */
FSL_VFILE_CHANGE_MOD = 1,
/** File changed due to a merge. */
FSL_VFILE_CHANGE_MERGE_MOD = 2,
/** File added by a merge. */
FSL_VFILE_CHANGE_MERGE_ADD = 3,
/** File changed due to an integrate merge. */
FSL_VFILE_CHANGE_INTEGRATE_MOD = 4,
/** File added by an integrate merge. */
FSL_VFILE_CHANGE_INTEGRATE_ADD = 5,
/** File became executable but has unmodified contents. */
FSL_VFILE_CHANGE_IS_EXEC = 6,
/** File became a symlink whose target equals its old contents. */
FSL_VFILE_CHANGE_BECAME_SYMLINK = 7,
/** File lost executable status but has unmodified contents. */
FSL_VFILE_CHANGE_NOT_EXEC = 8,
/** File lost symlink status and has contents equal to its old target. */
FSL_VFILE_CHANGE_NOT_SYMLINK = 9
};
typedef enum fsl_vfile_change_e fsl_vfile_change_e;
/**
Change-type flags for use with fsl_ckout_changes_visit() and
friends.
TODO: consolidate this with fsl_vfile_change_e insofar as possible.
There are a few checkout change statuses not reflected in
fsl_vfile_change_e.
*/
enum fsl_ckout_change_e {
/**
Sentinel placeholder value.
*/
FSL_CKOUT_CHANGE_NONE = 0,
/**
Indicates that a file was modified in some unspecified way.
*/
FSL_CKOUT_CHANGE_MOD = FSL_VFILE_CHANGE_MOD,
/**
Indicates that a file was modified as the result of a merge.
*/
FSL_CKOUT_CHANGE_MERGE_MOD = FSL_VFILE_CHANGE_MERGE_MOD,
/**
Indicates that a file was added as the result of a merge.
*/
FSL_CKOUT_CHANGE_MERGE_ADD = FSL_VFILE_CHANGE_MERGE_ADD,
/**
Indicates that a file was modified as the result of an
integrate-merge.
*/
FSL_CKOUT_CHANGE_INTEGRATE_MOD = FSL_VFILE_CHANGE_INTEGRATE_MOD,
/**
Indicates that a file was added as the result of an
integrate-merge.
*/
FSL_CKOUT_CHANGE_INTEGRATE_ADD = FSL_VFILE_CHANGE_INTEGRATE_ADD,
/**
Indicates that the file gained the is-executable trait
but is otherwise unmodified.
*/
FSL_CKOUT_CHANGE_IS_EXEC = FSL_VFILE_CHANGE_IS_EXEC,
/**
Indicates that the file has changed to a symlink.
*/
FSL_CKOUT_CHANGE_BECAME_SYMLINK = FSL_VFILE_CHANGE_BECAME_SYMLINK,
/**
Indicates that the file lost the is-executable trait
but is otherwise unmodified.
*/
FSL_CKOUT_CHANGE_NOT_EXEC = FSL_VFILE_CHANGE_NOT_EXEC,
/**
Indicates that the file was previously a symlink but is
now a plain file.
*/
FSL_CKOUT_CHANGE_NOT_SYMLINK = FSL_VFILE_CHANGE_NOT_SYMLINK,
/**
Indicates that a file was added.
*/
FSL_CKOUT_CHANGE_ADDED = FSL_CKOUT_CHANGE_NOT_SYMLINK + 1000,
/**
Indicates that a file was removed from SCM management.
*/
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
};
typedef enum fsl_ckout_change_e fsl_ckout_change_e;
/**
This is equivalent to calling fsl_vfile_changes_scan() with the
arguments (f, -1, 0).
@see fsl_ckout_changes_visit()
@see fsl_vfile_changes_scan()
*/
FSL_EXPORT int fsl_ckout_changes_scan(fsl_cx * f);
/**
A typedef for visitors of checkout status information via
fsl_ckout_changes_visit(). Implementions will receive the
last argument passed to fsl_ckout_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_ckout_changes_visit() will
stop and this function's result code will be returned.
@see fsl_ckout_changes_visit()
*/
typedef int (*fsl_ckout_changes_f)(void * state, fsl_ckout_change_e 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_ckout_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.
If the callback returns FSL_RC_BREAK, this function stops iteration
and returns 0.
Returns 0 on success.
@see fsl_ckout_changes_scan()
*/
FSL_EXPORT int fsl_ckout_changes_visit( fsl_cx * f, fsl_id_t vid,
bool doChangeScan,
fsl_ckout_changes_f visitor,
void * state );
/**
A bitmask of flags for fsl_vfile_changes_scan().
*/
enum fsl_ckout_sig_e {
/**
The empty flags set.
*/
FSL_VFILE_CKSIG_NONE = 0,
/**
Non-file/non-link FS objects trigger an error.
*/
FSL_VFILE_CKSIG_ENOTFILE = 0x001,
/**
Verify file content using hashing, regardless of whether or not
file timestamps differ.
*/
FSL_VFILE_CKSIG_HASH = 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 not be
cleared of entries for other checkins. Normally we want to clear
all versions except for the one we're working with, but at least
a couple of use cases call for having multiple versions in vfile at
once. Many algorithms generally assume only a single checkin's
worth of state is in vfile and can get confused if that is not the
case.
*/
FSL_VFILE_CKSIG_KEEP_OTHERS = 0x008,
/**
If set and fsl_vfile_changes_scan() is passed a version other than
the pre-call checkout version, it will, when finished, write the
given version in the "checkout" setting of the ckout.vvar table,
effectively switching the checkout to that version. It does not do
this by default because it is sometimes necessary to have two
versions in the vfile table at once and the operation doing so
needs to control which version number is the current checkout.
*/
FSL_VFILE_CKSIG_WRITE_CKOUT_VERSION = 0x010
};
/**
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<=0 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 0 or a bitmask of fsl_ckout_sig_e values.
This is a relatively memory- and filesystem-intensive operation,
and should not be performed more often than necessary. Many SCM
algorithms rely on its state being correct, however, so it's
generally better to err on the side of running it once too often
rather than once too few times.
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 * const f, fsl_id_t vid,
unsigned cksigFlags);
/**
If f has an opened checkout which has local changes noted in its
checkout db state (the vfile table), returns true, else returns
false. Note that this function does not do the filesystem scan to
check for changes, but checks only the db state. Use
fsl_vfile_changes_scan() to perform the actual scan (noting that
library-side APIs which update that state may also record
individual changes or automatically run a scan).
*/
FSL_EXPORT bool fsl_ckout_has_changes(fsl_cx * const f);
/**
Callback type for use with fsl_checkin_queue_opt for alerting a
client about exactly which files get enqueued/dequeued via
fsl_checkin_enqueue() and fsl_checkin_dequeue().
This function gets passed the checkout-relative name of the file
being enqueued/dequeued and the client-provided state pointer which
was passed to the relevant API. It must return 0 on success. If it
returns non-0, the API on whose behalf this callback is invoked
will propagate that error code back to the caller.
The intent of this callback is simply to report changes to the
client, not to perform validation. Thus such callbacks "really
should not fail" unless, e.g., they encounter an OOM condition or
some such. Any validation required by the client should be
performed before calling fsl_checkin_enqueue()
resp. fsl_checkin_dequeue().
*/
typedef int (*fsl_checkin_queue_f)(const char * filename, void * state);
/**
Options object type used by fsl_checkin_enqueue() and
fsl_checkin_dequeue().
*/
struct fsl_checkin_queue_opt {
/**
File or directory name to enqueue/dequeue to/from a pending
checkin.
*/
char const * filename;
/**
If true, filename (if not absolute) is interpreted as relative to
the current working directory, else it is assumed to be relative
to the top of the current checkout directory.
*/
bool relativeToCwd;
/**
If not NULL then this->filename and this->relativeToCwd are
IGNORED and any to-queue filename(s) is/are added from this
container. It is an error (FSL_RC_MISUSE) to pass an empty bag.
(Should that be FSL_RC_RANGE instead?)
The bag is assumed to contain values from the vfile.id checkout
db field, refering to one or more files which should be queued
for the pending checkin. It is okay to pass IDs for unmodified
files or to queue the same files multiple times. Unmodified files
may be enqueued but will be ignored by the checkin process if, at
the time the checkin is processed, they are still unmodified.
Duplicated entries are simply ignored for the 2nd and subsequent
inclusion.
@see fsl_ckout_vfile_ids()
*/
fsl_id_bag const * vfileIds;
/**
If true, fsl_vfile_changes_scan() is called to ensure that the
filesystem and vfile tables agree. If the client code has called
that function, or its equivalent, since any changes were made to
the checkout then this may be set to false to speed up the
enqueue process. This is only used by fsl_checkin_enqueue(), not
fsl_checkin_dequeue().
*/
bool scanForChanges;
/**
If true, only flagged-as-modified files will be enqueued by
fsl_checkin_enqueue(). By and large, this should be set to
true. Setting this to false is generally only intended/useful for
testing.
*/
bool onlyModifiedFiles;
/**
It not NULL, is pass passed the checkout-relative filename of
each enqueued/dequeued file and this->callbackState. See the
callback type's docs for more details.
*/
fsl_checkin_queue_f callback;
/**
Opaque client-side state for use as the 2nd argument to
this->callback.
*/
void * callbackState;
};
/** Convenience typedef. */
typedef struct fsl_checkin_queue_opt fsl_checkin_queue_opt;
/** Initialized-with-defaults fsl_checkin_queue_opt structure, intended for
const-copy initialization. */
#define fsl_checkin_queue_opt_empty_m { \
NULL/*filename*/,true/*relativeToCwd*/, \
NULL/*vfileIds*/, \
true/*scanForChanges*/,true/*onlyModifiedFiles*/, \
NULL/*callback*/,NULL/*callbackState*/ \
}
/** Initialized-with-defaults fsl_checkin_queue_opt structure, intended for
non-const copy initialization. */
extern const fsl_checkin_queue_opt fsl_checkin_queue_opt_empty;
/**
Adds one or more files to f's list of "selected" files - those
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, fsl_ckout_manage()'d, fsl_ckout_unmanage()'d, or renamed
files by default.
opt->filename must be a non-empty NUL-terminated string. The
filename is canonicalized via fsl_ckout_filename_check() - see that
function for the meaning of the opt->relativeToCwd parameter. To
queue all modified files in a checkout, set opt->filename to ".",
opt->relativeToCwd to false, and opt->onlyModifiedFiles to true.
"Modified" includes any which are pending deletion, are
newly-added, or for which a rename is pending.
The resolved name must refer to either a single vfile.pathname
value in the current vfile table or to a checkout-root-relative
directory. All matching filenames which refer to modified files (as
recorded in the vfile table) are queued up for the next commit.
If opt->filename is NULL, empty, or ("." and opt->relativeToCwd is false)
then all files in the vfile table are checked for changes.
If opt->scanForChanges is true then fsl_vfile_changes_scan() is
called before starting to ensure that the vfile entries are up to
date. If the client app has "recently" run that (or its
equivalent), that (slow) step can be skipped by setting
opt->scanForChanges to false before calling this
Note that after this returns, any given 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).
For each resolved entry which actually gets enqueued (i.e. was not
already enqueued and which is marked as modified), opt->callback
(if it is not NULL) is passed the checkout-relative file name and
the opt->callbackState pointer.
Returns 0 on success, FSL_RC_MISUSE if either pointer is NULL, or
*zName is NUL. Returns FSL_RC_OOM on allocation error. It is not
inherently an error for opt->filename to resolve to no queue-able
entries. A client can check for that case, if needed, by assigning
opt->callback and incrementing a counter in that callback. If the
callback is never called, no queue-able entries were found.
On error f's error state might (depending on the nature of the
problem) contain more details.
@see fsl_checkin_is_enqueued()
@see fsl_checkin_dequeue()
@see fsl_checkin_discard()
@see fsl_checkin_commit()
*/
FSL_EXPORT int fsl_checkin_enqueue(fsl_cx * f,
fsl_checkin_queue_opt const * opt);
/**
The opposite of fsl_checkin_enqueue(), this opt->filename,
which may resolve to a single name or a directory, from the checkin
queue. Returns 0 on succes. This function does no validation on
whether a given file(s) actually exist(s), it simply asks the
internals to clean up matching strings from the checkout's vfile
table. Specifically, it does not return an error if this operation
finds no entries to dequeue.
If opt->filename is empty or NULL then ALL files are unqueued from
the pending checkin.
If opt->relativeToCwd is true (non-0) then opt->filename is
resolved based on the current directory, otherwise it is resolved
based on the checkout's root directory.
If opt->filename is not NULL or empty, this functions runs the
given path through fsl_ckout_filename_check() and will fail if that
function fails, propagating any error from that function. Ergo,
opt->filename must refer to a path within the current checkout.
@see fsl_checkin_enqueue()
@see fsl_checkin_is_enqueued()
@see fsl_checkin_discard()
@see fsl_checkin_commit()
*/
FSL_EXPORT int fsl_checkin_dequeue(fsl_cx * f,
fsl_checkin_queue_opt const * opt);
/**
Returns true 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 one file is enqueued (via fsl_checkin_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 assumed to be related to 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_enqueue() and fsl_checkin_dequeue() do. It
only works with file names.
Results are undefined if f is NULL.
@see fsl_checkin_enqueue()
@see fsl_checkin_dequeue()
@see fsl_checkin_discard()
@see fsl_checkin_commit()
*/
FSL_EXPORT bool fsl_checkin_is_enqueued(fsl_cx * const f,
char const * zName,
bool relativeToCwd);
/**
Discards any state accumulated for a pending checking,
including any files queued via fsl_checkin_enqueue()
and tags added via fsl_checkin_T_add().
@see fsl_checkin_enqueue()
@see fsl_checkin_dequeue()
@see fsl_checkin_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_enqueue() queues up a file or directory for
commit at the next commit.
- fsl_checkin_dequeue() removes an entry, allowing
UIs to toggle files in and out of a checkin before
committing it.
- fsl_checkin_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;
/**
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.
*/
bool 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.
*/
bool calcRCard;
/**
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.
*/
bool integrate;
/**
If true, allow a file to be checked in if it contains
fossil-style merge conflict markers, else fail if an attempt is
made to commit any files with such markers.
*/
bool allowMergeConflict;
/**
A hint to fsl_checkin_commit() about whether it needs to scan the
checkout for changes. Set this to false ONLY if the calling code
calls fsl_ckout_changes_scan() (or equivalent,
e.g. fsl_vfile_changes_scan()) immediately before calling
fsl_checkin_commit(). fsl_checkin_commit() requires a non-stale
changes scan in order to function properly, but it's a
computationally slow operation so the checkin process does not
want to duplicate it if the application has recently done so.
*/
bool scanForChanges;
/**
NOT YET IMPLEMENTED! TODO!
If true, files which are removed from the SCM by this checkin
should be removed from the filesystem.
Reminder to self: when we do this, incorporate
fsl_rm_empty_dirs().
*/
bool rmRemovedFiles;
/**
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" generally be
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" _and_ the repository
has at least one delta already. 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. Similarly, it will not create a delta in a repository
unless a delta has been "seen" in that repository before or this
policy is set to >0. When a checkin is created with a delta
manifest, that fact gets recorded in the repository's config
table.
Note that delta manifests have some advantages and may not
actually save much (if any) repository space because the
lower-level delta framework already compresses parent versions of
artifacts tightly. For more information see:
https://fossil-scm.org/home/doc/tip/www/delta-manifests.md
*/
int deltaPolicy;
/**
Time of the checkin. If 0 or less, the time of the
fsl_checkin_commit() call 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.
2021-03-09: we now have fsl_confirmer for this but currently no
part of the checkin code needs a prompt.
*/
};
/**
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*/, \
false/*isPrivate*/, \
true/*calcRCard*/, \
false/*integrate*/, \
false/*allowMergeConflict*/,\
true/*scanForChanges*/,\
false/*rmRemovedFiles*/,\
0/*deltaPolicy*/, \
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;
/**
This creates and saves 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_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.
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.
Note that the new RID and UUID can also be fetched afterwards by
calling fsl_ckout_version_info().
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 in a transaction, so
it rolls back any db changes if it fails. To implement a "dry-run"
mode, simply wrap this call in a transaction started on the
fsl_cx_db_ckout() 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 due to anything more serious than basic argument
validation, such a transaction will be in a roll-back state.
Some of the more notable, potentially not obvious, error
conditions:
- Trying to commit against a closed leaf: FSL_RC_ACCESS. Doing so
is not permitted by fossil(1), so we disallow it here.
- An empty/NULL user name or commit message, or no files were
selected which actually changed: FSL_RC_MISSING_INFO. 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 possibly
very bad if this is returned.
- If the checkin would result in no file-level changes vis-a-vis
the current checkout, FSL_RC_NOOP is returned.
BUGS:
- It cannot currently properly distinguish a "no-op" commit, one in
which no files were modified or only their permissions were
modifed.
@see fsl_checkin_enqueue()
@see fsl_checkin_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(). Decks require
that each tag be distinct from each other (none may compare
equivalent), but that check is delayed until the deck is output
into its final artifact form.
@see fsl_checkin_enqueue()
@see fsl_checkin_dequeue()
@see fsl_checkin_commit()
@see fsl_checkin_discard()
@see fsl_checkin_T_add2()
*/
FSL_EXPORT int fsl_checkin_T_add( fsl_cx * f, fsl_tagtype_e tagType,
fsl_uuid_cstr uuid, char const * name,
char const * value);
/**
Works identically to fsl_checkin_T_add() except that it takes its
argument in the form of a T-card object.
On success ownership of t is passed to mf. On error (see
fsl_deck_T_add()) ownership is not modified.
Results are undefined if either argument is NULL or improperly
initialized.
*/
FSL_EXPORT int fsl_checkin_T_add2( fsl_cx * f, fsl_card_T * t );
/**
Clears all contents from f's checkout database, including the vfile
table, vmerge table, and some of the vvar table. The tables are
left intact. Returns 0 on success, non-0 if f has no checkout or for
a database error.
*/
FSL_EXPORT int fsl_ckout_clear_db(fsl_cx *f);
/**
Returns the base name of the current platform's checkout database
file. That is "_FOSSIL_" on Windows and ".fslckout" everywhere
else. The returned bytes are static.
TODO: an API which takes a dir name and looks for either name
*/
FSL_EXPORT char const *fsl_preferred_ckout_db_name();
/**
File-overwrite policy values for use with fsl_ckup_opt and friends.
*/
enum fsl_file_overwrite_policy_e {
/** Indicates that an error should be triggered if a file would be
overwritten. */
FSL_OVERWRITE_ERROR = 0,
/**
Indicates that files should always be overwritten by
*/
FSL_OVERWRITE_ALWAYS,
/**
Indicates that files should never be overwritten, and silently
skipped over. This is almost never what one wants to do.
*/
FSL_OVERWRITE_NEVER
};
typedef enum fsl_file_overwrite_policy_e fsl_file_overwrite_policy_e;
/**
State values for use with fsl_ckup_state::fileRmInfo.
*/
enum fsl_ckup_rm_state_e {
/**
Indicates that the file was not removed in a given checkout.
Guaranteed to have the value 0 so that it is treated as boolean
false. No other entries in this enum have well-defined values.
*/
FSL_CKUP_RM_NOT = 0,
/**
Indicates that a file was removed from a checkout but kept
in the filesystem because it was locally modified.
*/
FSL_CKUP_RM_KEPT,
/**
Indicates that a file was removed from a checkout and the
filesystem, with the caveat that failed attempts to remove from the
filesystem are ignored for Reasons but will be reported as if the
unlink worked.
*/
FSL_CKUP_RM
};
typedef enum fsl_ckup_rm_state_e fsl_ckup_rm_state_e;
/**
Under construction. Work in progress...
Options for "opening" a fossil repository database. That is,
creating a new fossil checkout database and populating its schema,
_without_ checking out any files. (That latter part is up for
reconsideration and this API might change in the future to check
out files after creating/opening the db.)
*/
struct fsl_repo_open_ckout_opt {
/**
Name of the target directory, which must already exist. May be
relative, e.g. ".". The repo-open operation will chdir to this
directory for the duration of the operation. May be NULL, in
which case the current directory is assumed and no chdir is
performed.
*/
char const * targetDir;
/**
The filename, with no directory components, of the desired
checkout db name. For the time being, always leave this NULL and
let the library decide. It "might" (but probably won't) be
interesting at some point to allow the client to specify a
different name (noting that that would be directly incompatible
with fossil(1)).
*/
char const * ckoutDbFile;
/**
Policy for how to handle overwrites of files extracted from a
newly-opened checkout.
Potential TODO: replace this with a fsl_confirmer, though that
currently seems like overkill for this particular case.
*/
fsl_file_overwrite_policy_e fileOverwritePolicy;
/**
fsl_repo_open_ckout() installs the fossil checkout schema. If
this is true it will forcibly replace any existing relevant
schema components in the checkout db, otherwise it will fail when
it tries to overwrite an existing schema and cannot.
*/
bool dbOverwritePolicy;
/**
Of true, the checkout-open process will look for an opened
checkout in the target directory and its parents (recursively)
and fail with FSL_RC_ALREADY_EXISTS if one is found.
*/
bool checkForOpenedCkout;
};
typedef struct fsl_repo_open_ckout_opt fsl_repo_open_ckout_opt;
/**
Empty-initialized fsl_repo_open_ckout_opt const-copy constructer.
*/
#define fsl_repo_open_ckout_opt_m { \
NULL/*targetDir*/, NULL/*ckoutDbFile*/, \
FSL_OVERWRITE_ERROR/*fileOverwritePolicy*/, \
false/*dbOverwritePolicy*/, \
-1/*checkForOpenedCkout*/ \
}
/**
Empty-initialised fsl_repo_open_ckout_opt instance. Clients should copy
this value (or fsl_repo_open_ckout_opt_empty_m) to initialise
fsl_repo_open_ckout_opt instances for sane default values.
*/
FSL_EXPORT const fsl_repo_open_ckout_opt fsl_repo_open_ckout_opt_empty;
/**
Work in progress...
Opens a checkout db for use with the currently-connected repository
or creates a new one. If opening an existing one, it gets "stolen"
from any repository it might have been previously mapped to.
- Requires that f have an opened repository db and no opened
checkout. Returns FSL_RC_NOT_A_REPO if no repo is opened and
FSL_RC_MISUSE if a checkout *is* opened.
- Creates/re-uses a .fslckout DB in the dir opt->targetDir. The
directory must be NULL or already exist, else FSL_RC_NOT_FOUND is
returned. If opt->dbOverwritePolicy is false then it fails with
FSL_RC_ALREADY_EXISTS if that directory already contains a
checkout db.
Note that this does not extract any SCM'd files from the
repository, it only opens (and possibly creates) the checkout
database.
Pending:
- If opening an existing checkout db for a different repo then
delete the STASH and UNDO entries, as they're not valid for a
different repo.
*/
FSL_EXPORT int fsl_repo_open_ckout( fsl_cx * f, fsl_repo_open_ckout_opt const * opt );
typedef struct fsl_ckup_state fsl_ckup_state;
/**
A callback type for use with fsl_ckup_state. It gets called via
fsl_repo_ckout() and fsl_ckout_update() to report progress of the
extraction process. It gets called after one of those functions has
successfully extracted a file or skipped over it because the file
existed and the checkout options specified to leave existing files
in place. It must return 0 on success, and non-0 will end the
extraction process, propagating that result code back to the
caller. If this callback fails, the checkout's contents may be left
in an undefined state, with some files updated and others not. All
database-side data will be consistent (the transaction is rolled
back) but filesystem-side changes may not be.
*/
typedef int (*fsl_ckup_f)( fsl_ckup_state const * cState );
/**
This enum lists the various types of individual file change states
which can happen during a checkout, update, or merge.
*/
enum fsl_ckup_fchange_e {
/** Sentinel value. */
FSL_CKUP_FCHANGE_INVALID = -1,
/** Was unchanged between the previous and updated-to version,
so no change was made to the on-disk file. This is the
only entry in the enum which is guaranteed to have a specific
value: 0, so that it can be used as a boolean false. */
FSL_CKUP_FCHANGE_NONE = 0,
/**
Added to SCM in the updated-to version.
*/
FSL_CKUP_FCHANGE_ADDED,
/**
Added to SCM in the current checkout version and carried over into
the updated-to version.
*/
FSL_CKUP_FCHANGE_ADD_PROPAGATED,
/**
Removed from SCM in the updated-to to version OR in the checked-out
version but not yet committed. a.k.a. it became "unmanaged."
Do we need to differentiate between those cases?
*/
FSL_CKUP_FCHANGE_RM,
/**
Removed from the checked-out version but not yet commited,
so was carried over to the updated-to version.
*/
FSL_CKUP_FCHANGE_RM_PROPAGATED,
/** Updated or replaced without a merge by the checkout/update
process. */
FSL_CKUP_FCHANGE_UPDATED,
/** Merge was not performed because at least one of the inputs appears
to be binary. The updated-to version overwrites the previous
version in this case.
*/
FSL_CKUP_FCHANGE_UPDATED_BINARY,
/** Updated with a merge by the update process. */
FSL_CKUP_FCHANGE_MERGED,
/** Special case of FSL_CKUP_FCHANGE_MERGED. Merge was performed
and conflicts were detected. The newly-updated file will contain
conflict markers.
@see fsl_buffer_contains_merge_marker()
*/
FSL_CKUP_FCHANGE_CONFLICT_MERGED,
/** Added in the current checkout but also contained in the
updated-to version. The local copy takes precedence.
*/
FSL_CKUP_FCHANGE_CONFLICT_ADDED,
/**
Added by the updated-to version but a local unmanaged copy exists.
The local copy is overwritten, per historical fossil(1) convention
(noting that fossil has undo support to allow one to avoid loss of
such a file's contents).
TODO: use confirmer here to ask user whether to overwrite.
*/
FSL_CKUP_FCHANGE_CONFLICT_ADDED_UNMANAGED,
/** Edited locally but removed from updated-to version. Local
edits will be left in the checkout tree. */
FSL_CKUP_FCHANGE_CONFLICT_RM,
/** Cannot merge if one or both of the update/updating verions of a
file is a symlink. The updated-to version overwrites the previous
version in this case.
We probably need a better name for this.
*/
FSL_CKUP_FCHANGE_CONFLICT_SYMLINK,
/**
Indicates that a merge of binary content was requested.
TODO: figure out why UPDATE uses the target version, instead of
triggering an error here, and why MERGE does not do the same.
fossil(1) simply skips over, with a warning, binaries during a
merge.
*/
FSL_CKUP_FCHANGE_CONFLICT_BINARY,
/** File was renamed in the updated-to version. If a file is both
modified and renamed, it is flagged as renamed instead
of modified. */
FSL_CKUP_FCHANGE_RENAMED,
/** Locally modified. This state appears only when
"updating" a checkout to the same version. */
FSL_CKUP_FCHANGE_EDITED
};
typedef enum fsl_ckup_fchange_e fsl_ckup_fchange_e;
/**
State to be passed to fsl_ckup_f() implementations via
calls to fsl_repo_ckout() and fsl_ckout_update().
*/
struct fsl_ckup_state {
/**
The core SCM state for the just-extracted file. Note that its
content member will be NULL: the content is not passed on via
this interface because it is only loaded for files which require
overwriting.
An update process may synthesize content for extractState->fCard
which do not 100% reflect the file on disk. Of primary note here:
1) fCard->uuid will refer to the hash of the updated-to
version, as opposed to the hash of the on-disk file (which may
differ due to having local edits merged in).
2) For the update process, fCard->priorName will be NULL unless
the file was renamed between the original and updated-to
versions, in which case priorName will refer to the original
version's name.
*/
fsl_repo_extract_state const * extractState;
/**
Optional client-dependent state for use in the fsl_ckup_f()
callback. This is copies from the corresponding
fsl_ckup_opt::callbackState member.
*/
void * callbackState;
/**
Vaguely describes the type of change the current call into
the fsl_ckup_f() represents. The full range of values is
not valid for all operations. Specifically:
Checkout only uses:
FSL_CKUP_FCHANGE_NONE
FSL_CKUP_FCHANGE_UPDATED
FSL_CKUP_FCHANGE_RM
For update operations all (or most) values are potentially
possible.
If this has a value of FSL_CKUP_FCHANGE_RM,
this->fileRmInfo will provide a bit more detail.
*/
fsl_ckup_fchange_e fileChangeType;
/**
Indicates whether the file was removed by the process:
- FSL_CKUP_RM_NOT = Was not removed.
- FSL_CKUP_RM_KEPT = Was removed from the checked-out version but
left in the filesystem because the confirmer said to.
- FSL_CKUP_RM = Was removed from the checkout and the filesystem.
When this->dryRun is true, this specifies whether the file would
have been removed.
*/
fsl_ckup_rm_state_e fileRmInfo;
/**
If fsl_repo_ckout()'s or fsl_ckout_update()'s options specified
that the mtime should be set on each updated file, this holds
that time. If the file existed and was not overwritten, it is set
to that file's time. Else it is set to the current time (which
may differ by a small fraction of a second from the file-write
time because we avoid stat()'ing it again after writing). If
this->fileRmInfo indicates that a file was removed, this might
(depending on availability of the file in the filesystem at the
time) be set to 0.
When running in dry-run mode, this value may be 0, as we may not
have a file in place which we can stat() to get it, nor a db
entry from which to fetch it.
This option is ignored for merge operations.
*/
fsl_time_t mtime;
/**
The size of the extracted file, in bytes. If the file was removed
from the filesystem (or removal was at least attempted) then this
is set to -1.
*/
fsl_int_t size;
/**
True if the current checkout/update is running in dry-run mode,
else false. See fsl_ckup_opt::dryRun for details.
*/
bool dryRun;
};
/**
Options for use with fsl_repo_ckout() and
fsl_ckout_update(). i.e. for checkout and update.
*/
struct fsl_ckup_opt {
/**
The version of the repostitory to check out or update. This must
be the blob.rid of a checkin artifact.
*/
fsl_id_t checkinRid;
/**
Gets called once per checked-out or updated file, passed a
fsl_ckup_state instance with information about the
checked-out file and related metadata. May be NULL.
*/
fsl_ckup_f callback;
/**
State to be passed to this->callback via the
fsl_ckup_state::callbackState member.
*/
void * callbackState;
/**
An optional "confirmer" for answering questions about file
overwrites and deletions posed by the checkout process.
By default this confirmer of the associated fsl_cx instance
is used.
Caveats:
- This is not currently used by the update process, only
checkout.
- If this->setMTime is true, the mtime is NOT set for any files
which already exist and are skipped due to the confirmer saying
to leave them in place.
- Similarly, if the confirmer says to never overwrite files,
permissions on existing files are not modified. fsl_repo_ckout()
does not (re)write unmodified files, and thus may leave such
files with different permissions. That's on the to-fix list.
*/
fsl_confirmer confirmer;
/**
If true, the checkout/update processes will calculate the
(synthetic) mtime of each extracted file and set its mtime. This
is a relatively expensive operation which calculates the
"effective mtime" of each file by calculating it: Fossil does not
record file timestamps, instead treating files as if they had the
timestamp of the most recent checkin in which they were added or
modified.
It's generally a good idea to let the update process stamp the
_current_ time on modified files, in order to avoid any hiccups
with build processes which rely on accurate times
(e.g. Makefiles). When doing a clean checkout, it's often
interesting to see the "original" times, though.
*/
bool setMtime;
/**
A hint to fsl_repo_ckout() and fsl_ckout_update() about whether
it needs to scan the checkout for changes. Set this to false ONLY
if the calling code calls fsl_ckout_changes_scan() (or
equivalent, e.g. fsl_vfile_changes_scan()) immediately before
calling fsl_repo_ckout() or fsl_ckout_update(), as those require
a non-stale changes scan in order to function properly.
*/
bool scanForChanges;
/**
If true, the extraction process will "go through the motions" but
will not write any files to disk. It will perform I/O such as
stat()'ing to see, e.g., if it would have needed to overwrite a
file.
*/
bool dryRun;
};
typedef struct fsl_ckup_opt fsl_ckup_opt;
/**
Empty-initialized fsl_ckup_opt const-copy constructor.
*/
#define fsl_ckup_opt_m {\
-1/*checkinRid*/, NULL/*callback*/, NULL/*callbackState*/, \
fsl_confirmer_empty_m/*confirmer*/,\
false/*setMtime*/, true/*scanForChanges*/,false/*dryRun*/ \
}
/**
Empty-initialised fsl_ckup_opt instance. Clients should copy
this value (or fsl_ckup_opt_empty_m) to initialise
fsl_ckup_opt instances for sane default values.
*/
FSL_EXPORT const fsl_ckup_opt fsl_ckup_opt_empty;
/**
A fsl_repo_extract() proxy which extracts the contents of the
repository version specified by opt->checkinRid to the root
directory of f's currently-opened checkout. i.e. it performs a
"checkout" operation.
For each extracted entry, cOpt->callback (if not NULL) will be
passed a (fsl_ckup_state const*) which contains a pointer
to the fsl_repo_extract_state and some additional metadata
regarding the extraction. The value of cOpt->callbackState will be
set as the callbackState member of that fsl_ckup_state
struct, so that the client has a way of passing around app-specific
state to that callback.
After successful completion, the process will report (see below)
any files which were part of the previous checkout version but are
not part of the current version, optionally removing them from the
filesystem (depending on the value of opt->rmMissingPolicy). It
will IGNORE ANY DELETION FAILURE of files it attempts to
delete. The reason it does not fail on removal error is because
doing so would require rolling back the transaction, effectively
undoing the checkout, but it cannot roll back any prior deletions
which succeeded. Similarly, after all file removal is complete, it
attempts to remove any now-empty directories left over by that
process, also silently ignoring any errors. If the cOpt->dryRun
option is specified, it will "go through the motions" of removing
files but will not actually attempt filesystem removal. For
purposes of the callback, however, it will report deletions as
having happened (but will also set the dryRun flag on the object
passed to the callback).
After unpacking the SCM-side files, it may write out one or more
manifest files, as described for fsl_ckout_manifest_write(), if the
'manifest' config setting says to do so.
As part of the file-removal process, AFTER all "existing" files are
processed, it calls cOpt->callback() (if not NULL) for each removed
file, noting the following peculiarities in the
fsl_ckup_state object which is passed to it for those
calls:
- It is called after the processing of "existing" files. Thus the
file names passed during this step may appear "out of order" with
regards to the others (which are guaranteed to be passed in lexical
order, though whether it is case-sensitive or not depends on the
repository's case-sensitivity setting).
- fileRmInfo will indicate that the file was removed from the
checkout, and whether it was actually removed or retained in the
filesystem. This will indicate filesystem-level removal even when
in dry-run mode, though in that case no filesystem-level removal is
actually attempted.
- extractState->fileRid will refer to the file's blob's RID for the
previous checkout version.
- extractState->content will be NULL.
- extractState->callbackState will be NULL.
- extractState->fCard will refer to the pre-removal state of the
file. i.e. the state as it was in the checkout prior to this
function being called.
Returns 0 on success. Returns FSL_RC_NOT_A_REPO if f has no opened
repo, FSL_RC_NOT_A_CKOUT if no checkout is opened. If
cOpt->callback is not NULL and returns a non-0 result code,
extraction ends and that result is returned. If it returns non-0 at
any point after basic argument validation, it rolls back all
changes or sets the current transaction stack into a rollback
state.
@see fsl_repo_ckout_open()
*/
FSL_EXPORT int fsl_repo_ckout(fsl_cx * f, fsl_ckup_opt const * cOpt);
/**
UNDER CONSTRUCTION.
Performs an "update" operation on f's currenly-opened
checkout. Performing an update is similar to performing a checkout,
the primary difference being that an update will merge local file
modifications into any newly-updated files, whereas a checkout will
overwrite them.
TODO?: fossil(1)'s update permits a list of files, in which case it
behaves differently: it updates the given files to the version
requested but leaves the checkout at its current version. To be
able to implement that we either need clients to call this in a
loop, changing opt->filename on each call (like how we do
fsl_ckout_manage()) or we need a way for them to pass on the list
of files/dir in the opt object.
@see fsl_repo_ckout().
*/
FSL_EXPORT int fsl_ckout_update(fsl_cx * f, fsl_ckup_opt const *opt);
/**
Tries to calculate a version to update the current checkout version
to, preferring the tip of the current checkout's branch.
On success, 0 is returned and *outRid is set to the calculated RID,
which may be 0, indicating that no errors were encountered but no
version could be calculated.
On error, non-0 is returned, outRid is not modified, and f's error
state is updated.
Returns FSL_RC_NOT_A_CKOUT if f has no checkout opened and
FSL_RC_NOT_A_REPO if no repo is opened.
If it calculates that there are multiple viable descendants it
returns FSL_RC_AMBIGUOUS and f's error state will contain a list of
the UUIDs (or UUID prefixes) of those descendants.
Sidebar: to get the absolute latest version, irrespective of the
branch, use fsl_sym_to_rid() to resolve the symbolic name "tip".
*/
FSL_EXPORT int fsl_ckout_calc_update_version(fsl_cx * f, fsl_id_t * outRid);
/**
Bitmask used by fsl_ckout_manifest_setting() and
fsl_ckout_manifest_write().
*/
enum fsl_cx_manifest_mask_e {
/** Coresponds to the file "manifest". */
FSL_MANIFEST_MAIN = 0x001,
/** Coresponds to the file "manifest.uuid". */
FSL_MANIFEST_UUID = 0x010,
/** Coresponds to the file "manifest.tags". */
FSL_MANIFEST_TAGS = 0x100
};
typedef enum fsl_cx_manifest_mask_e fsl_cx_manifest_mask_e;
/**
Returns a bitmask representing which manifest files, if any, will
be written when opening or updating a checkout directory, as
specified by the repository's 'manifest' configuration setting, and
sets *m to a bitmask indicating which of those are enabled. It
first checks for a versioned setting then, if no versioned setting
is found, a repository-level setting.
A truthy setting value (1, "on", "true") means to write the
manifest and manifest.uuid files. A string with any of the letters
'r', 'u', or 't' means to write the [r]aw, [u]uid, and/or [t]ags
file(s), respectively.
If the manifest setting is falsy or not set, *m is set to 0, else
*m is set to a bitmask representing which file(s) are considered to
be auto-generated for this repository:
- FSL_MANIFEST_MAIN = manifest
- FSL_MANIFEST_UUID = manifest.uuid
- FSL_MANIFEST_TAGS = manifest.tags
Any db-related or allocation errors while trying to fetch the
setting are silently ignored.
For performance's sake, since this is potentially called often from
fsl_reserved_fn_check(), this setting is currently cached by this
routine (in the fsl_cx object), but that ignores the fact that the
manifest setting can be modified at any time, either in a versioned
setting file or the repository db, and may be modified from outside
the library. There's a tiny back-door for working around that: if m
is NULL, the cache will be flushed and no other work will be
performed. Thus the following approach can be used to force a fresh
check for that setting:
```
fsl_ckout_manifest_setting(f, NULL); // clears caches, does nothing else
fsl_ckout_manifest_setting(f, &myInt); // loads/caches the setting
```
*/
FSL_EXPORT void fsl_ckout_manifest_setting(fsl_cx *f, int *m);
/**
Might write out the files manifest, manifest.uuid, and/or
manifest.tags for the current checkout to the the checkout's root
directory. The 2nd-4th arguments are interpreted as follows:
0: Do not write that file.
>0: Always write that file.
<0: Use the value of the "manifest" config setting (see
fsl_ckout_manifest_setting()) to determine whether or not to write
that file.
As each file is written, its mtime is set to that of the checkout
version. (Forewarning: that behaviour may change if it proves to be
problematic vis a vis build processes.)
Returns 0 on success, non-0 on error:
- FSL_RC_NOT_A_CKOUT if no checkout is opened.
- FSL_RC_RANGE if the current checkout RID is 0 (indicating a fresh,
empty repository).
- Various potential DB/IO-related error codes.
If the final argument is not NULL then it will be updated to
contain a bitmask representing which files, if any, were written:
see fsl_ckout_manifest_setting() for the values. It is updated
regardless of success or failure and will indicate which file(s)
was/were written before the error was triggered.
Each file implied by the various manifest settings which is NOT
written by this routine and is also not part of the current
checkout (i.e. not listed in the vfile table) will be removed from
disk, but a failure while attempting to do so will be silently
ignored.
@see fsl_repo_manifest_write()
*/
FSL_EXPORT int fsl_ckout_manifest_write(fsl_cx * const f,
int manifest,
int manifestUuid,
int manifestTags,
int * const wroteWhat );
/**
Returns true if f has an opened checkout and the given absolute
path is rooted in that checkout, else false. As a special case, it
returns false if the path _is_ the checkout root unless zAbsPath
has a trailing slash. (The checkout root is always stored with a
trailing slash because that simplifies its internal usage.)
Note that this is strictly a string comparison, not a
filesystem-level operation.
*/
FSL_EXPORT bool fsl_is_rooted_in_ckout(fsl_cx * const f, char const * const zAbsPath);
/**
Works like fsl_is_rooted_in_ckout() except that it returns 0 on
success, and on error updates f with a description of the problem
and returns non-0: FSL_RC_RANGE or (if updating the error state
fails) FSL_RC_OOM.
*/
FSL_EXPORT int fsl_is_rooted_in_ckout2(fsl_cx * const f, char const * const zAbsPath);
/**
Change-type values for use with fsl_ckout_revert_f() callbacks.
*/
enum fsl_ckout_revert_e {
/** Sentinel value. */
FSL_REVERT_NONE = 0,
/**
File was previously queued for addition but unqueued
by the revert process.
*/
FSL_REVERT_UNMANAGE,
/**
File was previously queued for removal but unqueued by the revert
process. If the file's contents or permissions were also reverted
then the file is reported as FSL_REVERT_PERMISSIONS or
FSL_REVERT_CONTENTS instead.
*/
FSL_REVERT_REMOVE,
/**
File was previously scheduled to be renamed, but the rename was
reverted. The name reported to the callback is the original one.
If a file was both modified and renamed, it will be flagged as
renamed instead of modified, for consistency with the usage of
fsl_ckup_fchange_e's FSL_CKUP_FCHANGE_RENAMED.
FIXME: this does not mean that the file on disk was actually
renamed (if needed). That is TODO, pending addition of code to
perform renames.
*/
FSL_REVERT_RENAME,
/** File's permissions (only) were reverted. */
FSL_REVERT_PERMISSIONS,
/**
File's contents reverted. This value trumps any others in this
enum. Thus if a file's permissions and contents were reverted,
or it was un-renamed and its contents reverted, it will be
reported using this enum entry.
*/
FSL_REVERT_CONTENTS
};
typedef enum fsl_ckout_revert_e fsl_ckout_revert_e;
/**
Callback type for use with fsl_ckout_revert(). For each reverted
file it gets passed the checkout-relative filename, type of change,
and the callback state pointer which was passed to
fsl_ckout_revert(). If it returns non-0, the revert process will
end in an error and that code will be propagated back to the
caller. In such cases, any files reverted up until that point will
still be reverted on disk but the reversion in the database will be
rolled back. A change scan (e.g. fsl_ckout_changes_scan()) will
restore balance to that equation, but these callbacks should only
return non-0 in for catastrophic failure.
*/
typedef int (*fsl_ckout_revert_f)( char const *zFilename,
fsl_ckout_revert_e changeType,
void * callbackState );
/**
Options for passing to fsl_ckout_revert().
*/
struct fsl_ckout_revert_opt {
/**
File or directory name to revert. See also this->vfileIds.
*/
char const * filename;
/**
An alternative to assigning this->filename is to point
this->vfileIds to a bag of vfile.id values. If this member is not
NULL, fsl_ckout_revert() will ignore this->filename.
@see fsl_filename_to_vfile_ids()
*/
fsl_id_bag const * vfileIds;
/**
Interpret filename as relative to cwd if true, else relative to
the current checkout root. This is ignored when this->vfileIds is
not NULL.
*/
bool relativeToCwd;
/**
If true, fsl_vfile_changes_scan() is called to ensure that
the filesystem and vfile tables agree. If the client code has
called that function, or its equivalent, since any changes were
made to the checkout then this may be set to false to speed up
the revert process.
*/
bool scanForChanges;
/**
Optional callback to notify the client of what gets reverted.
*/
fsl_ckout_revert_f callback;
/**
State for this->callback.
*/
void * callbackState;
};
typedef struct fsl_ckout_revert_opt fsl_ckout_revert_opt;
/**
Initialized-with-defaults fsl_ckout_revert_opt instance,
intended for use in const-copy initialization.
*/
#define fsl_ckout_revert_opt_empty_m { \
NULL/*filename*/,NULL/*vfileIds*/,true/*relativeToCwd*/,true/*scanForChanges*/, \
NULL/*callback*/,NULL/*callbackState*/ \
}
/**
Initialized-with-defaults fsl_ckout_revert_opt instance,
intended for use in non-const copy initialization.
*/
FSL_EXPORT const fsl_ckout_revert_opt fsl_ckout_revert_opt_empty;
/**
Reverts changes to checked-out files, replacing their
on-disk versions with the current checkout's version.
If zFilename refers to a directory, all managed files under that
directory are reverted (if modified). If zFilename is NULL or
empty, all modifications in the current checkout are reverted.
If a file has been added but not yet committed, the add
is un-queued but the file is otherwise untouched. If the
file has been queued for removal, this removes it from
that queue as well as restores its contents.
If a rename is pending for the given filename, the name may match
either its original name or new name. Whether or not that will
actually work when file A is renamed to B and file C is renamed to
A is anyone's guess. (Noting that (fossil mv) won't allow that
situation to exist in the vfile table for a single checkout
version, so it seems safe enough.)
If the 4th argument is not NULL then it is called for each revert
(_after_ the revert happens) to report what was done with that
file. It gets passed the checkout-relative name of each reverted
file. The 5th argument is not interpreted by this function but is
passed on as-is as the final argument to the callback. If the
callback returns non-0, the revert process is cancelled, any
pending transaction is set to roll back, and that error code is
returned. Note that cancelling a revert mid-process will leave file
changes made by the revert so far in place, and thus the checkout
db and filesystem will be in an inconsistent state until
fsl_vfile_changes_scan() (or equivalent) is called to restore
balance to the world.
Files which are not actually reverted because their contents or
permissions were not modified on disk are not reported to the
callback unless the reversion was the un-queuing of an ADD or
REMOVE operation.
Returns 0 on success, any number of non-0 results on error.
*/
FSL_EXPORT int fsl_ckout_revert( fsl_cx * const f,
fsl_ckout_revert_opt const * opt );
/**
Expects f to have an opened checkout and zName to be the name of
an entry in the vfile table where vfile.vid == vid. If vid<=0 then
the current checkout RID is used. This function does not do any
path resolution or normalization on zName and checks only for an
exact match (honoring f's case-sensitivity setting - see
fsl_cx_case_sensitive_set()).
On success it returns 0 and assigns *vfid to the vfile.id value of
the matching file. If no match is found, 0 is returned and *vfile
is set to 0. Returns FSL_RC_NOT_A_CKOUT if no checkout is opened,
FSL_RC_RANGE if zName is not a simple path (see
fsl_is_simple_pathname()), and any number of codes for db-related
errors.
This function matches only vfile.pathname, not vfile.origname,
because it is possible for a given name to be in both fields (in
different records) at the same time.
*/
FSL_EXPORT int fsl_filename_to_vfile_id( fsl_cx * f, fsl_id_t vid,
char const * zName,
fsl_id_t * vfid );
/**
Searches the `vfile` table where `vfile.vid=vid` for a name which
matches `zName` or all `vfile` entries found under a subdirectory
named `zName` (with no trailing slash). `zName` must be relative to
the checkout root. As a special case, if `zName` is `NULL`, empty,
or `"."` then all files in `vfile` with the given `vid` are
selected. For each entry it finds, it adds the `vfile.id` to
`dest`. If `vid<=0` then the current checkout RID is used.
If `changedOnly` is `true` then only entries which have been marked
in the `vfile` table as having some sort of change are included, so
if `true` then fsl_ckout_changes_scan() (or equivalent) must have
been "recently" called to ensure that state is up to do. This routine
only checks the `vfile` table for "is changed" state, it does not
do filesystem-level checks on the files.
This search honors the context-level case-sensitivity setting (see
fsl_cx_case_sensitive_set()).
Returns 0 on success. Not finding anything is not treated as an
error, though we could arguably return `FSL_RC_NOT_FOUND` for the
cases which use this function. In order to determine whether or not
any results were actually found, compare `dest->entryCount` before
and after calling this.
This function matches only `vfile.pathname`, not `vfile.origname`,
because it is possible for a given name to be in both fields (in
different records) at the same time.
@see fsl_ckout_vfile_ids()
*/
FSL_EXPORT int fsl_filename_to_vfile_ids( fsl_cx * const f, fsl_id_t vid,
fsl_id_bag * const dest,
char const * zName,
bool changedOnly);
/**
This is a variant of fsl_filename_to_vfile_ids() which accepts
filenames in a more flexible form than that routine. This routine
works exactly like that one except for the following differences:
1) The given filename and the relativeToCwd arguments are passed to
by fsl_ckout_filename_check() to canonicalize the name and ensure
that it points to someplace within f's current checkout.
2) Because of (1), zName may not be NULL or empty. To fetch all of
the vfile IDs for the current checkout, pass a zName of "." and
relativeToCwd=false.
Returns 0 on success, FSL_RC_MISUSE if zName is NULL or empty,
FSL_RC_OOM on allocation error, FSL_RC_NOT_A_CKOUT if f has no
opened checkout.
*/
FSL_EXPORT int fsl_ckout_vfile_ids( fsl_cx * const f, fsl_id_t vid,
fsl_id_bag * const dest, char const * zName,
bool relativeToCwd, bool changedOnly );
/**
This "mostly internal" routine (re)populates f's checkout vfile
table with all files from the given checkin manifest. If
manifestRid is 0 or less then the current checkout's RID is
used. If vfile already contains any content for the given checkin,
it is left intact (and several processes rely on that behavior to
keep it from nuking, e.g., as-yet-uncommitted queued add/rm
entries).
Returns 0 on success, any number of codes on any of many potential
errors.
f must not be NULL and must have opened checkout and repository
databases. In debug builds it will assert that that is so.
If the 3rd argument is true, any entries in vfile for checkin
versions other than the one specified in the 2nd argument are
cleared from the vfile table. That is _almost_ always the desired
behavior, but there are rare cases where vfile needs to temporarily
(for the duration of a single transaction) hold state for multiple
versions.
If the 4th argument is not NULL, it gets assigned the number of
blobs from the given version which are currently missing from the
repository due to being phantoms (as opposed to being shunned).
Returns 0 on success, FSL_RC_NOT_A_CKOUT if no checkout is opened,
FSL_RC_OOM on allocation error, FSL_RC_DB for db-related problems,
et.al.
Misc. notes:
- This does NOT update the "checkout" vvar table entry because this
routine is sometimes used in contexts where we need to briefly
maintain two vfile versions and keep the previous checkout version.
- Apps must take care to not leave more than one version in the
vfile table for longer than absolutely needed. They "really should"
use fsl_vfile_unload() to clear out any version they load with this
routine.
@see fsl_vfile_unload()
@see fsl_vfile_unload_except()
*/
FSL_EXPORT int fsl_vfile_load(fsl_cx * const f, fsl_id_t manifestRid,
bool clearOtherVersions,
uint32_t * missingCount);
/**
Clears out all entries in the current checkout's vfile table with
the given vfile.vid value. If vid<=0 then the current checkout RID
is used (which is never a good idea from client-side code!).
ACHTUNG: never do this without understanding the consequences. It
can ruin the current checkout state.
Returns 0 on success, FSL_RC_NOT_A_CKOUT if f has no checkout
opened, FSL_RC_DB on any sort of db-related error (in which case
f's error state is updated with a description of the problem).
@see fsl_vfile_load()
@see fsl_vfile_unload_except()
*/
FSL_EXPORT int fsl_vfile_unload(fsl_cx * const f, fsl_id_t vid);
/**
A counterpart of fsl_vfile_unload() which removes all vfile
entries where vfile.vid is not the given vid. If vid is <=0 then
the current checkout RID is used.
Returns 0 on success, FSL_RC_NOT_A_CKOUT if f has no checkout
opened, FSL_RC_DB on any sort of db-related error (in which case
f's error state is updated with a description of the problem).
@see fsl_vfile_load()
@see fsl_vfile_unload()
*/
FSL_EXPORT int fsl_vfile_unload_except(fsl_cx * const f, fsl_id_t vid);
/**
Performs a "fingerprint check" between f's current checkout and
repository databases. Returns 0 if either there is no checkout, no
mismatch, or it is impossible to determine because the checkout is
missing a fingerprint (which is legal for "older" checkout
databases).
If a mismatch is found, FSL_RC_REPO_MISMATCH is returned. Returns
some other non-0 code on a lower-level error (db access, OOM,
etc.).
A mismatch can happen when the repository to which a checkout
belongs is replaced, either with a completely different repository
or a copy/clone of that same repository. Each repository copy may
have differing blob.rid values, and those are what the checkout
database uses to refer to repository-side data. If those RIDs
change, then the checkout is left pointing to data other than what
it should be.
TODO: currently the library offers no automated recovery mechanism
from a mismatch, the only remedy being to close the checkout
database, destroy it, and re-create it. fossil(1) is able, in some cases,
to automatically recover from this situation.
*/
FSL_EXPORT int fsl_ckout_fingerprint_check(fsl_cx * f);
/**
Looks for the given file in f's current checkout. If relativeToCwd
then the name is resolved from the current directory, otherwise it is
assumed to be relative to the checkout root or an absolute path
with the checkout dir as a prefix of that path.
On success, 0 is returned and dest's gets populated with the
content of the file.
On error, non-0 is returned and, depending on the error type, dest
might be partially populated. f's error state will be updated to
describe the error.
Results are undefined if any pointer argument is NULL.
This function currently resolves symlinks on its way to the
content, but that behaviour may change in the future to reflect f's
symlink preferences.
*/
FSL_EXPORT int fsl_ckout_file_content(fsl_cx * const f, bool relativeToCwd,
char const * zName,
fsl_buffer * const dest);
/**
Fetches the timestamp of the given F-card's name against the
filesystem and/or the most recent checkin in which it was modified
(as reported by fsl_mtime_of_manifest()). vid is the checkin
version to look at. If it's <=0, the current checkout will be used.
On success, returns 0 and:
- If repoMtime is not NULL then (*repoMtime) is assigned to the
result of fsl_mtime_of_manifest_file() for the given file.
- If localMtime is not NULL then (*localMtime) is assigned to
the checkout-local timestamp of the file.
Returns non-0 on error. Some of the potential results include:
- FSL_RC_NOT_A_CKOUT.
- FSL_RC_NOT_FOUND if the filename cannot be resolved in the
requested version or cannot be stat()'d.
- FSL_RC_OOM.
*/
FSL_EXPORT int fsl_card_F_ckout_mtime(fsl_cx * const f, fsl_id_t vid,
fsl_card_F const * const fc,
fsl_time_t * repoMtime,
fsl_time_t * localMtime);
/**
File change types for use with fsl_merge_state::fileChangeType.
Terminology used in some of the descriptions:
- (P) is the "pivot" - the common ancestor for the merge.
- (M) is the version being merged in to...
- (V) is current checkout version into which (M) is being merged.
Maintenance reminder: this enum's values must start at 0 and
increment sequentially, and FSL_MERGE_FCHANGE_count must be the
final entry. This is to enable the creation of arrays, e.g. for
keeping track (client-side) of how many times a given change type
has been seen during a given merge run.
*/
enum fsl_merge_fchange_e {
/**
Not currently used. Merge does not (and cannot without some
surgery) report state of files unaffected by a merge. This entry
exists for the case that that changes. This is the only entry in
the enum which is guaranteed to have a specific value: 0, so that
it can be used as a boolean false. */
FSL_MERGE_FCHANGE_NONE = 0,
/**
File was added to (V) from (M).
*/
FSL_MERGE_FCHANGE_ADDED,
/**
File content was copied as-is from (M) to (V).
*/
FSL_MERGE_FCHANGE_COPIED,
/**
File was removed from (V) via (M). a.k.a. it became "unmanaged."
*/
FSL_MERGE_FCHANGE_RM,
/**
Content from (M) was merged into (V).
*/
FSL_MERGE_FCHANGE_MERGED,
/**
Special case of FSL_MERGE_FCHANGE_MERGED. Merge was performed from
(M) to (V) and conflicts were detected. The newly-updated file will
contain conflict markers.
@see fsl_buffer_contains_merge_marker()
*/
FSL_MERGE_FCHANGE_CONFLICT_MERGED,
#if 0
/** Added in the current checkout but also contained in the
updated-to version. The local copy takes precedence.
*/
FSL_MERGE_FCHANGE_CONFLICT_ADDED,
#endif
/**
Added to (V) by (M) but a local unmanaged copy exists.
The local copy is overwritten, per historical fossil(1) convention
(noting that fossil has undo support to allow one to avoid loss of
such a file's contents).
*/
FSL_MERGE_FCHANGE_CONFLICT_ADDED_UNMANAGED,
#if 0
/*
Fossil deletes merged-over removed files, regardless of whether
they're locally edited, so we'll do the same for now (noting that
fossil has undo support which can hypothetically save that case
from data loss). fsl_ckout_update() does not do so, but has extra
infastructure to deal with this. */
/** Edited locally but removed from merged-in version. Local
edits will be left in the checkout tree. */
FSL_MERGE_FCHANGE_CONFLICT_RM,
#endif
/**
Cannot merge if one or both of the update/updating versions of a
file is a symlink.
This case needs re-thinking. fossil(1) simply skips such merges
with a warning but no error. This library has no warning mechanism
other than to pass this code on to the fsl_merge_f() callback,
so that's what we do.
For UPDATE ops, the updated-to version overwrites the previous
version in this case. Why merge doesn't do that isn't clear, but
it's probably because we can have any number of merge parents and
choosing which one to use in the merge/replace case would be
impossible.
*/
FSL_MERGE_FCHANGE_CONFLICT_SYMLINK,
/**
Indicates that a merge of binary content was requested. We
cannot merge binaries, so this indicates that the file in question
was skipped over for merge purposes.
fossil(1) simply skips over, with a warning, binaries during a
merge, so we do the same (for lack of a better option).
*/
FSL_MERGE_FCHANGE_CONFLICT_BINARY,
/**
Indicates that the given file cannot be merged because no common
ancestor can be found for it. This condition is not strictly fatal
but does indicate a problem with the input data. Merging will
continue unless the fsl_merge_f() to which this is reported returns
non-0 to cancel it. This particular status is reported very early
in the fsl_ckout_merge() process, before any files have been
written by the merge, thus the callback can effectively abort the
merge without side-effects by returning non-0 if this status is
reported to it. If the fsl_ckout_merge() call has no callback set,
this case will go silently undiagnosed!
Potential TODO: add a flag to fsl_merge_opt to tell it to treat any
of these cases as merge-fatal.
*/
FSL_MERGE_FCHANGE_CONFLICT_ANCESTOR,
/**
File was renamed in the updated-to version. If a file is both
modified and renamed, it will be reported twice: once for each type
of change. The new name is reported via fsl_merge_state::filename
and the previous name via fsl_merge_state::priorName.
*/
FSL_MERGE_FCHANGE_RENAMED,
/**
This is the number of entries in this enum, for purposes of creating,
e.g. arrays of counters. This entry is never reported via a
fsl_merge_state::fileChangeType.
*/
FSL_MERGE_FCHANGE_count
};
typedef enum fsl_merge_fchange_e fsl_merge_fchange_e;
/** Reqired forward decl. */
typedef struct fsl_merge_opt fsl_merge_opt;
/**
UNDER CONSTRUCTION! INCOMPLETE!
A type for passing state to fsl_merge_f callbacks during
fsl_ckout_merge() processing.
For each step of a merge operation which affects a file,
state reflecting that change is set in one of these objects
and it is passed to the fsl_merge_f implementation supplied by
the caller.
Unlike fsl_ckout_update(), fsl_ckout_merge() reports only files
which are affected by a merge, not unmodified files. Whether
that's a bug or a feature is not yet clear, but the merge algorithm
does not, as is, support reporting unaffected files.
Due to the complexity and intricacy of the merge operation, it is
possible that any given file will get passed to the fsl_merge_f()
callback more than once with a different
fsl_merge_state::fileChangeType value. Most notably, a file which
has been modified and renamed may be passed on one with
FSL_MERGE_FCHANGE_COPIED and once with FSL_MERGE_FCHANGE_RENAMED.
Whether that's a bug or a feature is as-yet undecided, but
(A) fossil(1) does it that way and (B) _not_ doing that would
require some rearchitecting.
*/
struct fsl_merge_state {
/**
The fsl_cx object for which the current merge is running.
*/
fsl_cx * f;
/**
The options object which drives the current fsl_ckout_merge()
run.
*/
fsl_merge_opt const * opt;
/**
The checkout-relative name of the file affected by the merge.
These bytes are invalidated after the fsl_merge_f() callback
returns, so must be copied if the client requires them for later.
*/
char const * filename;
/**
If this->fileChangeType is FSL_MERGE_FCHANGE_RENAMED then
this is the previous name of the file, else it is NULL.
*/
char const * priorName;
/**
Indicates the state of the file currently being merged. Merge
does not support the full range fo fsl_ckup_fchange_e
values. TODO: list which it does or create a new enum which
enumerates only merge-specific change types.
*/
fsl_merge_fchange_e fileChangeType;
/**
Indicates whether the current file was removed. A state of
FSL_CKUP_RM_KEPT and fileChangeType of FSL_MERGE_FCHANGE_RM
indicates that the file has become unmanaged but the local
copy was retained because it was flagged as locally modified.
*/
fsl_ckup_rm_state_e fileRmInfo;
};
typedef struct fsl_merge_state fsl_merge_state;
/**
Callback type for use with fsl_merge_opt and fsl_ckout_merge().
*/
typedef int (*fsl_merge_f)(fsl_merge_state const * const);
/**
Merge type enum for use with fsl_merge_opt::mergeType.
The values of these entries are fossil-magic values for the
`vmerge.id` db field and must stay in sync with fossil's
definition.
*/
enum fsl_merge_type_e {
/**
Indicates a normal merge.
*/
FSL_MERGE_TYPE_NORMAL = 0,
/**
Indicates an "integrate" merge, which tells the next checkin
operation to apply a "closed" tag to the checkin from which this
merge is performed (effectively closing its branch).
Certain merge-time state will force this merge type to silently
behave like FSL_MERGE_TYPE_NORMAL:
- If the being-merged-in content is marked as private.
- If the being-merged-in content is not a leaf.
*/
FSL_MERGE_TYPE_INTEGRATE = -4,
/**
Indicates a cherrypick merge, pulling in only the changes made to a
specific checkin without otherwise inheriting its lineage.
*/
FSL_MERGE_TYPE_CHERRYPICK = -1,
/**
Indicates a backout merge, a reverse cherrypick, backing out any
changes which were added by the corresponding
fsl_merge_opt::mergeRid.
*/
FSL_MERGE_TYPE_BACKOUT = -2
};
typedef enum fsl_merge_type_e fsl_merge_type_e;
/**
UNDER CONSTRUCTION.
Options for use with fsl_ckout_merge().
*/
struct fsl_merge_opt {
/**
The version of the repostitory to merge into the current
checkout. This must be the `blob.rid` of a checkin artifact.
*/
fsl_id_t mergeRid;
/**
The version of the most recent common ancestor. Must normally be
0. The default is calculated automatically based on
this->mergeRid and this->mergeType.
This corresponds to fossil(1)'s `--baseline` merge flag.
fsl_ckout_merge() will fail if this is >0 and it does not refer
to a checkin version or if this->mergeType is
FSL_MERGE_TYPE_CHERRYPICK.
*/
fsl_id_t baselineRid;
/**
Specifies the merge type to perform. Certain merge-internal logic
may override this. Specifically, integrate-merges may be treated
as regular merges, as documented for FSL_MERGE_TYPE_INTEGRATE.
*/
fsl_merge_type_e mergeType;
/**
Gets called once per merge-updated file, passed a fsl_ckup_state
instance with information about the merged file and related
metadata. May be NULL, in which case the merge process will do as
much work as it can, even if that means doing certain
questionable things (such as skipping updates of binary files or
symlinks because it refuses to merge them). As a rule of thumb,
if fossil(1) performs a given "questional" merge features without
generating an error, fsl_ckout_merge() does as well. This
callback gives clients a chance to decide that certain states
_Simply Will Not Stand_ and cancel the merge by returning non-0
(preferably after calling fsl_cx_err_set() on the passed-in
fsl_merge_state::f object).
The callback is called after any on-disk changes are made to
the file, e.g. merging, file permissions, renaming, etc. However,
when this->dryRun is true, filesystem-level changes are skipped.
*/
fsl_merge_f callback;
/**
Client-defined state for use with this->callback.
*/
void * callbackState;
/**
A hint to fsl_ckout_merge() about whether it needs to scan the
checkout for changes. Set this to false ONLY if the calling code
calls fsl_ckout_changes_scan() (or equivalent,
e.g. fsl_vfile_changes_scan()) immediately before calling
fsl_ckout_merge(), as that function requires a non-stale changes
scan in order to function properly.
*/
bool scanForChanges;
/**
If true, the extraction process will "go through the motions" but
will not write any files to disk. It may still perform I/O such
as stat()'ing to see, e.g., if it would have needed to overwrite
a file. When in dry-run, this->callback is still called as if
dry-run mode were not in effect. Thus the on-disk state may not
actually reflect what the callback sees when dry-run mode is
active.
*/
bool dryRun;
/**
This flag is not part of the public API and will be removed
once the merge operation's development has settled down.
*/
unsigned short debug;
/**
TODO:
- How to handle fossil's --binary GLOBPATTERN flag. Plain string
or a glob list object or a stateful predicate function or... ?
We currently rely entirely on the global `binary-glob` setting.
*/
};
/** Initialized-with-defaults fsl_merge_opt structure, intended for
const-copy initialization. */
#define fsl_merge_opt_empty_m \
{-1/*mergeRid*/,0/*baselineRid*/, \
FSL_MERGE_TYPE_NORMAL/*mergeType*/, \
NULL/*callback*/, NULL/*callbackState*/, \
true/*scanForChanges*/, \
false/*dryRun*/,0/*debug*/}
/** Initialized-with-defaults fsl_merge_opt structure, intended for
non-const copy initialization. */
extern const fsl_merge_opt fsl_merge_opt_empty;
/**
UNDER CONSTRUCTION and not yet well-tested.
Performs a "merge" operation on the current checkout, merging in
version opt->mergeRid. If that version has already been merged,
this call has no SCM-related side effects.
Returns 0 on success, any number of non-0 codes on error,
including, _but not limited to_:
- FSL_RC_NOT_A_CKOUT if f has no opened checkout.
- FSL_RC_OOM on allocation error.
- FSL_RC_TYPE if opt->mergeRid or opt->baselineRid do to refer to
a checkin.
- FSL_RC_PHANTOM if a file participating in the merge is
a phantom.
- FSL_RC_RANGE if the to-be-merged-in RID is the same as the
current checkout RID or the same as the pivot/baseline of
the merge.
- FSL_RC_NOT_FOUND if no common ancestor can be found for use as a
basis for the merge.
- FSL_RC_MISUSE if the current checkout is empty (has an RID of 0).
- Any number of DB- or I/O-related codes, as well as codes from
underlying APIs such as fsl_vfile_changes_scan().
For all but the most trivial argument validation errors or
allocation errors, f's error state will be updated with a
description of the problem.
TODOs and potential TODOs:
- There are certain illegal combinations of merge state which may
require adding new result codes for. e.g. a no-op merge.
- Empty directories may be left behind when a merge removes all
files in a directory. fsl_ckout_update() handles that case but
fsl_ckout_merge() currently does not.
*/
FSL_EXPORT int fsl_ckout_merge(fsl_cx * const f, fsl_merge_opt const * const opt);
#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* ORG_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED */