/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
#if !defined(NET_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED)
#define NET_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED
/*
Copyright 2013-2021 Stephan Beal (https://wanderinghorse.net).
Derived heavily from previous work:
Copyright (c) 2013 D. Richard Hipp (https://www.hwaci.com/drh/)
This program is free software; you can redistribute it and/or
modify it under the terms of the Simplified BSD License (also
known as the "2-Clause License" or "FreeBSD License".)
This program is distributed in the hope that it will be useful,
but without any warranty; without even the implied warranty of
merchantability or fitness for a particular purpose.
******************************************************************************
This file declares public APIs for working with checkout-side fossil content.
*/
#include "fossil-db.h" /* MUST come first b/c of config macros */
#include "fossil-repo.h"
#if defined(__cplusplus)
extern "C" {
#endif
/**
Given a fsl_cx with an opened checkout, and a filename, this
function canonicalizes zOrigName to a form suitable for use as
an in-repo filename, _appending_ the results to pOut. If pOut is
NULL, it performs its normal checking but does not write a
result, other than to return 0 for success.
As a special case, if zOrigName refers to the top-level checkout
directory, it resolves to either "." or "./", depending on whether
zOrigName contains a trailing slash.
If relativeToCwd is true then the filename is canonicalized
based on the current working directory (see fsl_getcwd()),
otherwise f's current checkout directory is used as the virtual
root.
If the input name contains a trailing slash, it is retained in
the output sent to pOut except in the top-dir case mentioned
above.
Returns 0 on success, meaning that the value appended to pOut
(if not NULL) is a syntactically valid checkout-relative path.
Returns FSL_RC_RANGE if zOrigName points to a path outside
of f's current checkout root.
Returns FSL_RC_NOT_A_CHECKOUT if f has no checkout opened.
Returns FSL_RC_MISUSE if !f, !zOrigName, FSL_RC_OOM on an
allocation error.
This function does not validate whether or not the file actually
exists, only that its name is potentially valid as a filename
for use in a checkout (though other, downstream rules might prohibit that, e.g.
the filename "..../...." is not valid but is not seen as invalid by
this function). (Reminder to self: we could run the end result through
fsl_is_simple_pathname() to catch that?)
*/
FSL_EXPORT int fsl_checkout_filename_check( fsl_cx * f, char relativeToCwd,
char const * zOrigName, fsl_buffer * pOut );
/**
Adds the given filename to the current checkout vfile list of
files as a to-be-added file, or updates an existing record if
one exists.
If relativeToCwd is true (non-0) then the filename is
resolved/canonicalized based on the current working directory
(see fsl_getcwd()), otherwise f's current checkout directory is
used as the virtual root. This makes a subtle yet important
difference in how the name is resolved. CLI apps which take file
names from the user will generally want to set relativeToCwd to
true. GUI apps, OTOH, will possibly need it to be false,
depending on how they resolve and pass on the filenames.
This function ensures that zFilename gets canonicalized and can
be found under the checkout directory, and fails if no such file
exists (checking against the canonicalized name).
Returns 0 on success, non-0 on error.
Note that unlike fsl_checkout_file_rm(), this routine cannot
recursively add files from a directory name. Fixing that is on
the TODO list.
@see fsl_checkout_file_rm()
*/
FSL_EXPORT int fsl_checkout_file_add( fsl_cx * f, char relativeToCwd, char const * zFilename );
/**
The converse of fsl_checkout_file_add(), this queues a file for
removal from the current checkout. The arguments have identical
meanings as for fsl_checkout_file_add() except that this routine
does not ensure that the resolved filename actually exists - it
only normalizes zFilename into its repository-friendly form.
If recurseDirs is true then if zFilename refers to a directory
then this operation queues all files under that directory
(recursively) for removal. In this case, it is irrelevant
whether or not zFilename ends in a trailing slash or not.
Returns 0 on success, any of a number of non-0 codes on error.
Returns FSL_RC_MISUSE if !f, !zFilename, or !*zFilename.
Returns FSL_RC_NOT_A_CHECKOUT if f has no opened checkout.
@see fsl_checkout_file_add()
*/
FSL_EXPORT int fsl_checkout_file_rm( fsl_cx * f, char relativeToCwd, char const * zFilename,
char recurseDirs );
/**
Change-type flags for use with fsl_checkout_changes_visit() and
friends.
*/
enum fsl_checkout_change_t {
/**
Sentinel placeholder value.
*/
FSL_CKOUT_CHANGE_NONE = 0,
/**
Indicates that a file was modified in some unspecified way.
*/
FSL_CKOUT_CHANGE_MOD,
/**
Indicates that a file was modified as the result of a merge.
*/
FSL_CKOUT_CHANGE_MERGE_MOD,
/**
Indicates that a file was added as the result of a merge.
*/
FSL_CKOUT_CHANGE_MERGE_ADD,
/**
Indicates that a file was modified as the result of an
integrate-merge.
*/
FSL_CKOUT_CHANGE_INTEGRATE_MOD,
/**
Indicates that a file was added as the result of an
integrate-merge.
*/
FSL_CKOUT_CHANGE_INTEGRATE_ADD,
/**
Indicates that a file was added.
*/
FSL_CKOUT_CHANGE_ADDED,
/**
Indicates that a file was removed.
*/
FSL_CKOUT_CHANGE_REMOVED,
/**
Indicates that a file is missing from the local checkout.
*/
FSL_CKOUT_CHANGE_MISSING,
/**
Indicates that a file was renamed.
*/
FSL_CKOUT_CHANGE_RENAMED,
/**
NOT YET USED.
Indicates that a file contains conflict markers.
*/
FSL_CKOUT_CHANGE_CONFLICT,
/**
NOT YET USED.
Indicates that a file in the changes table references
a non-file on disk.
*/
FSL_CKOUT_CHANGE_NOT_A_FILE,
/**
NOT YET USED.
Indicates that a file is part of a cherrypick merge.
*/
FSL_CKOUT_CHANGE_CHERRYPICK,
/**
NOT YET USED.
Indicates that a file is part of a backout.
*/
FSL_CKOUT_CHANGE_BACKOUT
};
typedef enum fsl_checkout_change_t fsl_checkout_change_t;
/**
Sets up the vfile table in f's opened checkout db and scans the
checkout root directory's contents for changes compared to the
pristine checkout state. It records any changes in the vfile
table.
Returns 0 on success, non-0 on error, FSL_RC_MISUSE
if !f.
For compatibility with fossil(1), this routine clears the vfile
table of any entries not related to the current checkout.
TODO: a variant of this which scans only a given file or directory.
@see fsl_checkout_changes_visit()
*/
FSL_EXPORT int fsl_checkout_changes_scan(fsl_cx * f);
/**
A typedef for visitors of checkout status information via
fsl_checkout_changes_visit(). Implementions will receive the
last argument passed to fsl_checkout_changes_visit() as their
first argument. The second argument indicates the type of change
and the third holds the repository-relative name of the file.
If changes is FSL_CKOUT_CHANGE_RENAMED then origName will hold
the original name, else it will be NULL.
Implementations must return 0 on success, non-zero on error. On
error any looping performed by fsl_checkout_changes_visit() will
stop and this function's result code will be returned.
@see fsl_checkout_changes_visit()
*/
typedef int (*fsl_checkout_changes_f)(void * state, fsl_checkout_change_t change,
char const * filename,
char const * origName);
/**
Compares the changes of f's local checkout against repository
version vid (checkout version if vid is negative). For each
change detected it calls visitor(state,...) to report the
change. If visitor() returns non-0, that code is returned from
this function. If doChangeScan is true then
fsl_checkout_changes_scan() is called by this function before
iterating, otherwise it is assumed that the caller has called
that or has otherwise ensured that the checkout db's vfile table
has been populated.
Returns 0 on success.
@see fsl_checkout_changes_scan()
*/
FSL_EXPORT int fsl_checkout_changes_visit( fsl_cx * f, fsl_id_t vid,
char doChangeScan,
fsl_checkout_changes_f visitor,
void * state );
/**
A bitmask of flags for fsl_vfile_changes_scan().
*/
enum fsl_ckout_sig_t {
/**
The empty flags set.
*/
FSL_VFILE_CKSIG_NONE = 0,
/**
Non-file FS objects throw an error. Not yet implemented.
*/
FSL_VFILE_CKSIG_ENOTFILE = 0x001,
/**
Verify file content using sha1sum, regardless of whether or not
file timestamps differ.
*/
FSL_VFILE_CKSIG_SHA1 = 0x002,
/**
For unchanged or changed-by-merge files, set the mtime to last
check-out time, as determined by fsl_mtime_of_manifest_file().
*/
FSL_VFILE_CKSIG_SETMTIME = 0x004,
/**
Indicates that when populating the vfile table, it should be
cleared of entries for other checkins. This is primarily for
compatibility with fossil(1), which generally assumes only a
single checkin's worth of state is in vfile and can get confused
if that is not the case.
*/
FSL_VFILE_CKSIG_CLEAR_VFILE = 0x008
};
/**
This function populates (if needed) the vfile table of f's
checkout db for the given checkin version ID then compares files
listed in it against files in the checkout directory, updating
vfile's status for the current checkout version id as its goes.
If vid is negative then the current checkout's RID is used in
its place (note that 0 is the RID of an initial empty
repository!). cksigFlags must be a bitmask of fsl_ckout_sig_t
values.
Returns 0 on success, non-0 on error.
BUG: this does not properly catch one particular corner-case
change, where a file has been replaced by a same-named non-file
(symlink or directory).
*/
FSL_EXPORT int fsl_vfile_changes_scan(fsl_cx * f, fsl_id_t vid, int cksigFlags);
/**
Adds the given file f's list of "selected" files - the list of
filenames which should be included in the next commit
(see fsl_checkin_commit()).
Warning: if this function is not called before
fsl_checkin_commit(), then fsl_checkin_commit() will select all
modified, added, removed, or renamed files by default.
zName must be a non-empty NUL-terminated string and its bytes are
copied. The filename is canonicalized via
fsl_checkout_filename_check() - see that function for the meaning
of the relativeToCwd parameter.
The resolved name must refer to either a single vfile.pathname
value in the current vfile table (i.e. in the current checkout,
though possibly newly-added and not necessarily committed), or it
must refer to a directory, under which all modified, added,
deleted, or renamed files are queued up for the next commit.
If given a single file name it returns FSL_RC_NOT_FOUND if the
file is not in the current checkout. In this case f's error state
is updated with a description of the problem.
If given a directory name, it does not validate that any changes
are actually detected for queuing up (that detection comes at the
final commit stage).
The change-state of the file(s) is not actually checked by this
function, other than to confirm that the file is indeed listed in
the current checkout. That means that the file may still be
modified by the client before the commit takes place, and the
changes on disk at the point of the fsl_checkin_commit() are the
ones which get saved (or not).
Returns 0 on success, FSL_RC_MISUSE if either pointer is NULL,
or *zName is NUL. Returns FSL_RC_OOM on allocation error.
On error f's error state might (depending on the nature of the
problem) contain more details.
Returns 0 and has no side-effects if zName is already in the
checkin queue. This function honors the
fsl_cx_is_case_sensitive() setting when comparing names but the
check for the repo-level file is case-sensitive! That's arguably
a bug.
@see fsl_checkin_file_is_enqueued()
@see fsl_checkin_file_dequeue()
@see fsl_checkin_discard()
@see fsl_checkin_commit()
*/
FSL_EXPORT int fsl_checkin_file_enqueue(fsl_cx * f, char const * zName,
char relativeToCwd);
/**
The opposite of fsl_checkin_file_enqueue(), then removes the
given file or directory name from f's checkin queue. Returns 0 on
succes. Unlike fsl_checkin_file_enqueue(), this function does
little validation on the input and simply asks the internals to
clean up. Specifically, it does not return an error if this
operation finds no entries to unqueue. If zName is empty or NULL
then ALL files are unqueued from the pending checkin.
If relativeToCwd is true (non-0) then zName is resolved based on
the current directory, otherwise it is resolved based on the
checkout's root directory.
@see fsl_checkin_file_enqueue()
@see fsl_checkin_file_is_enqueued()
@see fsl_checkin_discard()
@see fsl_checkin_commit()
*/
FSL_EXPORT int fsl_checkin_file_dequeue(fsl_cx * f, char const * zName,
char relativeToCwd);
/**
Returns true (non-0) if the file named by zName is in f's current
file checkin queue. If NO files are in the current selection
queue then this routine assumes that ALL files are implicitely
selected. As long as at least once file is enqueud (via
fsl_checkin_file_enqueue()) then this function only returns true
for files which have been explicitly enqueued.
If relativeToCwd then zName is resolved based on the current
directory, otherwise it is resolved based on the checkout's
root directory.
This function returning true does not necessarily indicate that
the file _will_ be checked in at the next commit. If the file has
not been modified at commit-time then it will not be part of the
commit.
This function honors the fsl_cx_is_case_sensitive() setting
when comparing names.
Achtung: this does not resolve directory names like
fsl_checkin_file_enqueue() and fsl_checkin_file_dequeue() do. It
only works with file names.
@see fsl_checkin_file_enqueue()
@see fsl_checkin_file_dequeue()
@see fsl_checkin_discard()
@see fsl_checkin_commit()
*/
FSL_EXPORT int fsl_checkin_file_is_enqueued(fsl_cx * f, char const * zName,
int relativeToCwd);
/**
Discards any state accumulated for a pending checking,
including any files queued via fsl_checkin_file_enqueue()
and tags added via fsl_checkin_T_add().
@see fsl_checkin_file_enqueue()
@see fsl_checkin_file_dequeue()
@see fsl_checkin_file_is_enqueued()
@see fsl_checkin_commit()
@see fsl_checkin_T_add()
*/
FSL_EXPORT void fsl_checkin_discard(fsl_cx * f);
/**
Parameters for fsl_checkin_commit().
Checkins are created in a multi-step process:
- fsl_checkin_file_enqueue() queues up a file or directory for
commit at the next commit.
- fsl_checkin_file_dequeue() removes an entry, allowing
UIs to toggle files in and out of a checkin before
committing it.
- fsl_checkin_file_is_enqueued() can be used to determine whether
a given name is already enqueued or not.
- fsl_checkin_T_add() can be used to T-cards (tags) to a
deck. Branch tags are intended to be applied via the
fsl_checkin_opt::branch member.
- fsl_checkin_discard() can be used to cancel any pending file
enqueuings, effectively cancelling a commit (which can be
re-started by enqueuing another file).
- fsl_checkin_commit() creates a checkin for the list of enqueued
files (defaulting to all modified files in the checkout!). It
takes an object of this type to specify a variety of parameters
for the check.
Note that this API uses the terms "enqueue" and "unqueue" rather
than "add" and "remove" because those both have very specific
(and much different) meanings in the overall SCM scheme.
*/
struct fsl_checkin_opt {
/**
The commit message. May not be empty - the library
forbids empty checkin messages.
*/
char const * message;
/**
The optional mime type for the message. Only set
this if you know what you're doing.
*/
char const * messageMimeType;
/**
The user name for the checkin. If NULL or empty, it defaults to
fsl_cx_user_get(). If that is NULL, a FSL_RC_RANGE error is
triggered.
*/
char const * user;
/**
Don't use this yet - it is not yet tested all that well.
If not NULL, makes the checkin the start of a new branch with
this name.
*/
char const * branch;
/**
If this->branch is not NULL, this is applied as its "bgcolor"
propagating property. If this->branch is NULL then this is
applied as a one-time color tag to the checkin.
It must be NULL, empty, or in a form usable by HTML/CSS,
preferably \#RRGGBB form. Length-0 values are ignored (as if
they were NULL).
*/
char const * bgColor;
/**
If true, the checkin will be marked as private, otherwise it
will be marked as private or public, depending on whether or
not it inherits private content.
*/
char isPrivate;
/**
Whether or not to calculate an R-card. Doing so is very
expensive (memory and I/O) but it adds another layer of
consistency checking to manifest files. In practice, the R-card
is somewhat superfluous and the cost of calculating it has
proven painful on very large repositories. fossil(1) creates an
R-card for all checkins but does not require that one be set
when it reads a manifest.
*/
int calcRCard;
/**
Whether to allow (or try to force) a delta manifest or not. 0
means no deltas allowed - it will generate a baseline
manifest. Greater than 0 forces generation of a delta if
possible (if one can be readily found) even if doing so would not
save a notable amount of space. Less than 0 means to
decide via some heuristics.
A "readily available" baseline means either the current
checkout is a baseline or has a baseline. In either case, we
can use that as a baseline for a delta. i.e. a baseline
"should" basically available except on the initial checkin,
which has neither a parent checkin nor a baseline.
The current behaviour for "auto-detect" mode is: it will
generate a delta if a baseline is "readily available." Once it calculates a delta form, it calculates
whether that form saves any appreciable space/overhead compared
to whether a baseline manifest was generated. If so, it
discards the delta and re-generates the manifest as a
baseline. The "force" behaviour (deltaPolicy>0) bypasses the
"is it too big?" test, and is only intended for testing, not
real-life use.
Caveat: if the repository has the "forbid-delta-manifests" set to
a true value, this option is ignored: that setting takes
priority.
*/
int deltaPolicy;
/**
Tells the checkin to close merged-in branches (merge type of
0). INTEGRATE merges (type=-4) are always closed by a
checkin. This does not apply to CHERRYPICK (type=-1) and
BACKOUT (type=-2) merges.
*/
int integrate;
/**
Time of the checkin. If 0 or less, the current time
is used.
*/
double julianTime;
/**
If this is not NULL then the committed manifest will include a
tag which closes the branch. The value of this string will be
the value of the "closed" tag, and the value may be an empty
string. The intention is that this gets set to a comment about
why the branch is closed, but it is in no way mandatory.
*/
char const * closeBranch;
/**
Tells fsl_checkin_commit() to dump the generated manifest to
this file. Intended only for debugging and testing. Checking in
will fail if this file cannot be opened for writing.
*/
char const * dumpManifestFile;
/*
fossil(1) has many more options. We might want to wrap some of
it up in the "incremental" state (f->ckin.mf).
TODOs:
A callback mechanism which supports the user cancelling
the checkin. It is (potentially) needed for ops like
confirming the commit of CRNL-only changes.
*/
};
/**
Empty-initialized fsl_checkin_opt instance, intended for use in
const-copy constructing.
*/
#define fsl_checkin_opt_empty_m { \
NULL/*message*/, \
NULL/*messageMimeType*/, \
NULL/*user*/, \
NULL/*branch*/, \
NULL/*bgColor*/, \
0/*isPrivate*/, \
1/*calcRCard*/, \
-1/*deltaPolicy*/, \
0/*integrate*/, \
0.0/*julianTime*/, \
NULL/*closeBranch*/, \
NULL/*dumpManifestFile*/ \
}
/**
Empty-initialized fsl_checkin_opt instance, intended for use in
copy-constructing. It is important that clients copy this value
(or fsl_checkin_opt_empty_m) to cleanly initialize their
fsl_checkin_opt instances, as this may set default values which
(e.g.) a memset() would not.
*/
FSL_EXPORT const fsl_checkin_opt fsl_checkin_opt_empty;
/**
Do not use - under construction/testing. Very dangerous. Stay
away. If you choose to ignore this warning, then read on...
This creates a "checkin manifest" for the current checkout.
Its primary inputs is a list of files to commit. This list
is provided by the client by calling fsl_checkin_file_enqueue()
one or more times. If no files are explicitely selected
(enqueued) then it calculates which local files have changed vs
the current checkout and selects all of those.
Non-file inputs are provided via the opt parameter.
Tip: to implement a "dry-run" mode, simply wrap this call in a
transaction started on the fsl_cx_db_checkout() db handle
(passing it to fsl_db_transaction_begin()), then, after this
call, either cal; fsl_db_transaction_rollback() (to implement
dry-run mode) or fsl_db_transaction_commit() (for "wet-run"
mode). If this function returns non-0, such a transaction should
_always_ be rolled back!
On success, it returns 0 and...
- If newRid is not NULL, it is assigned the new checkin's RID
value.
- If newUuid is not NULL, it is assigned the new checkin's UUID
value. Ownership of the bytes is passed to the caller, who must
eventually pass them to fsl_free() to free them.
On error non-0 is returned and f's error state may (depending on
the nature of the problem) contain details about the problem.
Note, however, that any error codes returned here may have
arrived from several layers down in the internals, and may not
have a single specific interpretation here. When
possible/practical, f's error state gets updated with a
human-readable description of the problem.
ACHTUNG: all pending checking state is cleaned if this function
fails for any reason other than basic argument validation. This
means any queued files or tags need to be re-applied if the
client wants to try again. That is somewhat of a bummer, but this
behaviour is the only way we can ensure that then the pending
checkin state does not get garbled on a second use. When in doubt
about the state, the client should call fsl_checkin_discard() to
clear it before try to re-commit. (Potential TODO: add a
success/fail state flag to the checkin state and only clean up on
success? OTOH, since something in the state likely caused the
problem, we might not want to do that.)
This operation does all of its db-related work i a transaction,
so it rolls back any db changes if it fails.
Some of the more notable, potentially not obvious, error
conditions:
- Trying to commit against a closed leaf: FSL_RC_MISUSE
- An empty/NULL user name or commit message, or no files were
selected which actually changed: FSL_RC_RANGE. In these cases
f's error state describes the problem.
- Some resource is not found (e.g. an expected RID/UUID could not
be resolved): FSL_RC_NOT_FOUND. This would generally indicate
some sort of data consistency problem. i.e. it's quite possible
very bad if this is returned.
BUGS:
- It cannot currently properly distinguish a "no-op" commit, one in
which no files were modified. If, e.g.
@see fsl_checkin_file_enqueue()
@see fsl_checkin_file_dequeue()
@see fsl_checkin_discard()
@see fsl_checkin_T_add()
*/
FSL_EXPORT int fsl_checkin_commit(fsl_cx * f, fsl_checkin_opt const * opt,
fsl_id_t * newRid, fsl_uuid_str * newUuid);
/**
Works like fsl_deck_T_add(), adding the given tag information to
the pending checkin state. Returns 0 on success, non-0 on
error. A checkin may, in principal, have any number of tags, and
this may be called any number of times to add new tags to the
pending commit. This list of tags gets cleared by a successful
fsl_checkin_commit() or by fsl_checkin_discard().
@see fsl_checkin_file_enqueue()
@see fsl_checkin_file_dequeue()
@see fsl_checkin_commit()
@see fsl_checkin_discard()
*/
FSL_EXPORT int fsl_checkin_T_add( fsl_cx * f, fsl_tagtype_e tagType,
fsl_uuid_cstr uuid, char const * name,
char const * value);
#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* NET_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED */