Compare commits

...

16 Commits

Author SHA1 Message Date
Oswald Buddenhagen 76e5f223ee add missing trailing newlines in error() calls 2 months ago
Oswald Buddenhagen d54c22d20e fix IMAP INBOX case normalization 2 months ago
Oswald Buddenhagen 4c2031d616 fix initial build from git 2 months ago
Oswald Buddenhagen 4279aea6a0 generalize AUTHORS section of man page 2 months ago
Oswald Buddenhagen 6fbbcbb2c7 substitute version and date in man pages 2 months ago
Oswald Buddenhagen 8421b3cb22 automate setting package version 2 months ago
Oswald Buddenhagen f467b57a95 generalize GPL exception 2 months ago
Oswald Buddenhagen 7bca6967a7 update some email addresses 2 months ago
Husain Alshehhi 5f81162f5e add tag files to .gitignore 2 months ago
Oswald Buddenhagen ee832951e2 revamp automatic enumeration of power-of-two enumerators 2 months ago
Oswald Buddenhagen 31c504d432 remove redundant argument from BIT_FORMATTER_PROTO() 2 months ago
Oswald Buddenhagen 43271d8fad eliminate commit_cmds driver callback 2 months ago
Oswald Buddenhagen 8b8313997c Revert "actually implement imap_commit_cmds()" 2 months ago
Oswald Buddenhagen 84194a7a9f don't try to create already existing boxes 2 months ago
Oswald Buddenhagen ceb0fa9803 don't try to qsort() NULL array 2 months ago
Oswald Buddenhagen 12e30ce560 cap readsz at buffer size 2 months ago
  1. 13
      .gitignore
  2. 7
      AUTHORS
  3. 4
      LICENSES/LicenseRef-isync-GPL-exception.txt
  4. 2
      Makefile.am
  5. 8
      configure.ac
  6. 3
      src/.gitignore
  7. 29
      src/Makefile.am
  8. 70
      src/bit_enum_gen.pl
  9. 137
      src/common.h
  10. 2
      src/config.c
  11. 86
      src/driver.h
  12. 68
      src/drv_imap.c
  13. 9
      src/drv_maildir.c
  14. 34
      src/drv_proxy.c
  15. 1
      src/drv_proxy_gen.pl
  16. 1
      src/main_sync.c
  17. 4
      src/mbsync.1.in
  18. 2
      src/mdconvert.1.in
  19. 5
      src/socket.c
  20. 51
      src/sync.c
  21. 57
      src/sync.h
  22. 43
      src/sync_p.h
  23. 2
      src/sync_state.c
  24. 36
      version.sh

13
.gitignore vendored

@ -1,6 +1,7 @@
/.autoconf_trace
/ChangeLog
/INSTALL
/VERSION
/autom4te.cache/
/aclocal.m4
/autodefs.h
@ -33,3 +34,15 @@
Makefile
Makefile.in
GPATH
GRTAGS
GSYMS
GTAGS
ID
TAGS
cscope.files
cscope.in.out
cscope.out
cscope.po.out
tags

7
AUTHORS

@ -13,7 +13,7 @@ Lead Developers
Oswald Buddenhagen <ossi@users.sf.net>
- Current maintainer
Michael Elkins <me@mutt.org>
Michael Elkins <me@sigpipe.org>
- Original author
Contributors
@ -22,10 +22,10 @@ Contributors
(Some of these people also contributed bugfixes and optimizations.)
(In chronological order.)
Jeremy Katz <katzj@linuxpower.org>
Jeremy Katz <katzj@fedoraproject.org>
- UseNamespace & UseSSL* options
Daniel Resare <noa@metamatrix.se>
Noa (ex. Daniel) Resare <noa@resare.com>
- Numerous SSL handling improvements
Eivind Eklund <eivind@FreeBSD.org>
@ -82,6 +82,7 @@ Gergely Risko <gergely@risko.hu>
Sung Pae "guns" <self@sungpae.com>
Helmut Grohne <helmut@subdivi.de>
Hugo Haas <hugo@larve.net>
Husain Alshehhi <husain@alshehhi.io>
Jaroslav Suchanek <jaroslav.suchanek@gmail.com>
Jeremie Courreges-Anglas <jca@openbsd.org>
Klemens Nanni <kn@openbsd.org>

4
LICENSES/LicenseRef-isync-GPL-exception.txt

@ -8,5 +8,5 @@ Usage-Guide:
SPDX-License-Identifier: <SPDX-License> WITH LicenseRef-isync-GPL-exception
License-Text:
As a special exception, mbsync may be linked with the OpenSSL library,
despite that library's more restrictive license.
As a special exception, mbsync may be linked with libraries with a more
restrictive license if the only incompatibility are advertising clauses.

2
Makefile.am

@ -4,7 +4,7 @@
SUBDIRS = src
bin_SCRIPTS = mbsync-get-cert
EXTRA_DIST = LICENSES debian isync.spec $(bin_SCRIPTS)
EXTRA_DIST = LICENSES VERSION debian isync.spec $(bin_SCRIPTS)
LOG_PL = \
use POSIX qw(strftime); \

8
configure.ac

@ -2,7 +2,8 @@
# SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
dnl SPDX-License-Identifier: GPL-2.0-or-later
AC_INIT([isync], [1.5.0])
m4_syscmd([./version.sh])
AC_INIT([isync], m4_sinclude([VERSION]))
AC_CONFIG_HEADERS([autodefs.h])
AC_CANONICAL_TARGET
@ -250,7 +251,10 @@ if test "x$have_macos_keychain" != xno; then
AC_SUBST(KEYCHAIN_LIBS, ["-Wl,-framework,Security,-framework,CoreFoundation"])
fi
AC_CONFIG_FILES([Makefile src/Makefile isync.spec])
RELEASE_DATE=`date -r $0 +%F`
AC_SUBST(RELEASE_DATE)
AC_CONFIG_FILES([Makefile src/Makefile src/mbsync.1 src/mdconvert.1 isync.spec])
AC_OUTPUT
AC_MSG_RESULT()

3
src/.gitignore vendored

@ -1,7 +1,8 @@
/*_enum.h
/drv_proxy.inc
/mbsync
/mbsync.1
/mdconvert
/mdconvert.1
/tst_imap_msgs
/tst_imap_utf7
/tst_msg_cvt

29
src/Makefile.am

@ -20,28 +20,6 @@ drv_proxy.$(OBJEXT): drv_proxy.inc
drv_proxy.inc: $(srcdir)/driver.h $(srcdir)/drv_proxy.c $(srcdir)/drv_proxy_gen.pl
perl $(srcdir)/drv_proxy_gen.pl $(srcdir)/driver.h $(srcdir)/drv_proxy.c drv_proxy.inc
ENUM_GEN = $(srcdir)/bit_enum_gen.pl
$(mbsync_OBJECTS): common_enum.h
common_enum.h: common.h $(ENUM_GEN)
perl $(ENUM_GEN) < $< > $@
$(mbsync_OBJECTS): driver_enum.h
driver_enum.h: driver.h $(ENUM_GEN)
perl $(ENUM_GEN) < $< > $@
$(mbsync_OBJECTS): sync_enum.h
sync_enum.h: sync.h $(ENUM_GEN)
perl $(ENUM_GEN) < $< > $@
sync.$(OBJEXT): sync_c_enum.h
sync_c_enum.h: sync.c $(ENUM_GEN)
perl $(ENUM_GEN) < $< > $@
sync.$(OBJEXT) sync_state.$(OBJEXT): sync_p_enum.h
sync_p_enum.h: sync_p.h $(ENUM_GEN)
perl $(ENUM_GEN) < $< > $@
mdconvert_SOURCES = mdconvert.c
mdconvert_LDADD = $(DB_LIBS)
if with_mdconvert
@ -49,7 +27,10 @@ mdconvert_prog = mdconvert
mdconvert_man = mdconvert.1
endif
in_man = mbsync.1.in mdconvert.1.in
bin_PROGRAMS = mbsync $(mdconvert_prog)
# don't forget to update AC_CONFIG_FILES in configure.ac!
man_MANS = mbsync.1 $(mdconvert_man)
tst_imap_msgs_SOURCES = tst_imap_msgs.c imap_msgs.c util.c
@ -69,6 +50,6 @@ EXTRA_PROGRAMS = tst_timers
exampledir = $(docdir)/examples
example_DATA = mbsyncrc.sample
EXTRA_DIST = bit_enum_gen.pl drv_proxy_gen.pl run-tests.pl $(example_DATA) $(man_MANS)
EXTRA_DIST = drv_proxy_gen.pl run-tests.pl $(example_DATA) $(in_man)
CLEANFILES = *_enum.h drv_proxy.inc
CLEANFILES = drv_proxy.inc

70
src/bit_enum_gen.pl

@ -1,70 +0,0 @@
#!/usr/bin/perl
#
# SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
# SPDX-License-Identifier: GPL-2.0-or-later
#
# mbsync - mailbox synchronizer
#
use strict;
use warnings;
my $in_enum = 0;
my $conts;
while (<>) {
s,\s*(?://.*)?$,,;
if ($in_enum) {
if (/^\)$/) {
$conts =~ s/\s//g;
$conts =~ s/,$//;
my @vals = split(/,/, $conts);
my ($pfx, $pfx1);
for my $e (@vals) {
if (!defined($pfx)) {
$pfx1 = $pfx = ($e =~ /^([A-Z]+_)/) ? $1 : "";
} elsif (length($pfx)) {
$pfx = "" if ((($e =~ /^([A-Z]+_)/) ? $1 : "") ne $pfx);
}
}
my $bit = 1;
my $bitn = 0;
my (@names, @nameos);
my $nameo = 0;
for my $e (@vals) {
my $bits = ($e =~ s/\((\d+)\)$//) ? $1 : 1;
my $n = substr($e, length($pfx));
if ($bits != 1) {
die("Unsupported field size $bits\n") if ($bits != 2);
print "#define $e(b) ($bit << (b))\n";
push @names, "F-".$n, "N-".$n;
my $nl = length($n) + 3;
push @nameos, $nameo, $nameo + $nl;
$nameo += $nl * 2;
} else {
print "#define $e $bit\n";
push @names, $n;
push @nameos, $nameo;
$nameo += length($n) + 1;
}
$bit <<= $bits;
$bitn += $bits;
}
if (length($pfx)) {
print "#define ${pfx}_NUM_BITS $bitn\n";
}
if (length($pfx1)) {
print "#define ${pfx1}_STRINGS \"".join("\\0", @names)."\"\n";
print "#define ${pfx1}_OFFSETS ".join(", ", @nameos)."\n";
}
print "\n";
$in_enum = 0;
} else {
$conts .= $_;
}
} else {
if (/^BIT_ENUM\($/) {
$conts = "";
$in_enum = 1;
}
}
}

137
src/common.h

@ -21,8 +21,6 @@
#include <string.h>
#include <time.h>
#include "common_enum.h"
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
@ -43,10 +41,55 @@ typedef unsigned long ulong;
#define shifted_bit(in, from, to) \
((int)(((uint)(in) / (from > to ? from / to : 1) * (to > from ? to / from : 1)) & to))
#define BIT_ENUM(...)
#define BIT_ENUM_VAL(name) \
name, \
name##_dummy = 2 * name - 1,
#define DEFINE_BIT_ENUM(list) \
enum list { \
list##_dummy, \
list(BIT_ENUM_VAL) \
};
#define PFX_BIT_ENUM_VAL(pfx, name) \
pfx##_##name, \
pfx##_##name##_dummy = 2 * pfx##_##name - 1,
#define PFX_BIT_ENUM_NUM(pfx, name) \
pfx##_##name##_bit,
#define DEFINE_PFX_BIT_ENUM(list) \
enum list { \
list##_dummy, \
list(PFX_BIT_ENUM_VAL) \
}; \
enum { \
list(PFX_BIT_ENUM_NUM) \
list##_num_bits \
};
#define BIT_ENUM_STR(pfx, name) \
stringify(name) "\0"
#define BIT_ENUM_STR_OFF(pfx, name) \
name##_str_off, \
name##_str_off_dummy = name##_str_off + sizeof(stringify(name)) - 1,
#define GET_BIT_ENUM_STR_OFF(pfx, name) \
name##_str_off,
#define PFX_BIT_ENUM_STR(pfx, name) \
stringify(pfx) "_" stringify(name) "\0"
#define PFX_BIT_ENUM_STR_OFF(pfx, name) \
pfx##_##name##_str_off, \
pfx##_##name##_str_end = pfx##_##name##_str_off + sizeof(stringify(pfx) "_" stringify(name)) - 1,
#define static_assert_bits(pfx, type, field) \
static_assert( pfx##__NUM_BITS <= sizeof(((type){ 0 }).field) * 8, \
#define GET_PFX_BIT_ENUM_STR_OFF(pfx, name) \
pfx##_##name##_str_off,
#define static_assert_bits(list, type, field) \
static_assert( list##_num_bits <= sizeof(((type){ 0 }).field) * 8, \
stringify(type) "::" stringify(field) " is too small" )
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
@ -100,30 +143,32 @@ enum {
VERBOSE,
};
BIT_ENUM(
DEBUG_MAILDIR,
DEBUG_NET,
DEBUG_NET_ALL,
DEBUG_SYNC,
DEBUG_MAIN,
DEBUG_DRV,
DEBUG_DRV_ALL,
DEBUG_CRASH,
PROGRESS,
DRYRUN,
EXT_EXIT,
ZERODELAY,
KEEPJOURNAL,
FORCEJOURNAL,
FORCEASYNC(2),
FAKEEXPUNGE,
FAKEDUMBSTORE,
)
#define options_enum(fn) \
fn(DEBUG_MAILDIR) \
fn(DEBUG_NET) \
fn(DEBUG_NET_ALL) \
fn(DEBUG_SYNC) \
fn(DEBUG_MAIN) \
fn(DEBUG_DRV) \
fn(DEBUG_DRV_ALL) \
\
fn(DEBUG_CRASH) \
\
fn(PROGRESS) \
\
fn(DRYRUN) \
\
fn(EXT_EXIT) \
\
fn(ZERODELAY) \
fn(KEEPJOURNAL) \
fn(FORCEJOURNAL) \
fn(FORCEASYNC_F) \
fn(FORCEASYNC_N) \
fn(FAKEEXPUNGE) \
fn(FAKEDUMBSTORE)
DEFINE_BIT_ENUM(options_enum)
#define FORCEASYNC(b) (FORCEASYNC_F << (b))
#define DEBUG_ANY (DEBUG_MAILDIR | DEBUG_NET | DEBUG_SYNC | DEBUG_MAIN | DEBUG_DRV)
#define DEBUG_ALL (DEBUG_ANY | DEBUG_CRASH)
@ -209,34 +254,38 @@ time_t timegm( struct tm *tm );
void fmt_bits( uint bits, uint num_bits, const char *bit_str, const int *bit_off, char *buf );
#define BIT_FORMATTER_RET(name, pfx) \
struct name##_str { char str[sizeof(pfx##__STRINGS)]; };
#define BIT_FORMATTER_RET(name, list, fn) \
enum { list(fn##_OFF) name##_str_len }; \
struct name##_str { char str[name##_str_len]; };
#define BIT_FORMATTER_PROTO(name, pfx, storage) \
#define BIT_FORMATTER_PROTO(name, storage) \
storage struct name##_str ATTR_OPTIMIZE /* force RVO */ \
fmt_##name( uint bits )
#define BIT_FORMATTER_IMPL(name, pfx, storage) \
BIT_FORMATTER_PROTO(name, pfx, storage) \
#define BIT_FORMATTER_IMPL(name, list, fn, storage) \
BIT_FORMATTER_PROTO(name, storage) \
{ \
static const char strings[] = pfx##__STRINGS; \
static const int offsets[] = { pfx##__OFFSETS }; \
static const char strings[] = list(fn); \
static const int offsets[] = { list(GET_##fn##_OFF) }; \
\
struct name##_str buf; \
fmt_bits( bits, as(offsets), strings, offsets, buf.str ); \
return buf; \
}
#define BIT_FORMATTER_FUNCTION(name, pfx) \
BIT_FORMATTER_RET(name, pfx) \
BIT_FORMATTER_IMPL(name, pfx, static)
// Note that this one uses enum labels without prefix ...
#define BIT_FORMATTER_FUNCTION(name, list) \
BIT_FORMATTER_RET(name, list, BIT_ENUM_STR) \
BIT_FORMATTER_IMPL(name, list, BIT_ENUM_STR, static)
#define DECL_BIT_FORMATTER_FUNCTION(name, pfx) \
BIT_FORMATTER_RET(name, pfx) \
BIT_FORMATTER_PROTO(name, pfx, );
// ... while these ones use enum labels with prefix - this
// is not fundamental, but simply because of the use cases.
#define DECL_BIT_FORMATTER_FUNCTION(name, list) \
BIT_FORMATTER_RET(name, list, PFX_BIT_ENUM_STR) \
BIT_FORMATTER_PROTO(name, );
#define DEF_BIT_FORMATTER_FUNCTION(name, pfx) \
BIT_FORMATTER_IMPL(name, pfx, )
#define DEF_BIT_FORMATTER_FUNCTION(name, list) \
BIT_FORMATTER_IMPL(name, list, PFX_BIT_ENUM_STR, )
void *nfmalloc( size_t sz );
void *nfzalloc( size_t sz );

2
src/config.c

@ -22,7 +22,7 @@ char FieldDelimiter = ';';
char FieldDelimiter = ':';
#endif
DEF_BIT_FORMATTER_FUNCTION(ops, OP)
DEF_BIT_FORMATTER_FUNCTION(ops, sync_op_enum)
char *
expand_strdup( const char *s, const conffile_t *cfile )

86
src/driver.h

@ -9,7 +9,6 @@
#define DRIVER_H
#include "config.h"
#include "driver_enum.h"
typedef struct driver driver_t;
@ -36,32 +35,32 @@ extern store_conf_t *stores;
/* For message->flags */
// Keep the MESSAGE_FLAGS in sync (grep that)!
/* The order is according to alphabetical maildir flag sort */
BIT_ENUM(
F_DRAFT, // Draft
F_FLAGGED, // Flagged
F_FORWARDED, // Passed
F_ANSWERED, // Replied
F_SEEN, // Seen
F_DELETED, // Trashed
)
extern const char MsgFlags[F__NUM_BITS];
typedef struct { char str[F__NUM_BITS + 1]; } flag_str_t;
#define msg_flags_enum(fn) \
fn(F, DRAFT) /* Draft */ \
fn(F, FLAGGED) /* Flagged */ \
fn(F, FORWARDED) /* Passed */ \
fn(F, ANSWERED) /* Replied */ \
fn(F, SEEN) /* Seen */ \
fn(F, DELETED) /* Trashed */
DEFINE_PFX_BIT_ENUM(msg_flags_enum)
extern const char MsgFlags[msg_flags_enum_num_bits];
typedef struct { char str[msg_flags_enum_num_bits + 1]; } flag_str_t;
flag_str_t ATTR_OPTIMIZE /* force RVO */ fmt_flags( uchar flags );
flag_str_t ATTR_OPTIMIZE /* force RVO */ fmt_lone_flags( uchar flags );
/* For message->status */
BIT_ENUM(
M_RECENT, // unsyncable flag; maildir_*() depend on this being bit 0
M_DEAD, // expunged
M_EXPUNGE, // for driver_t->close_box()
M_FLAGS, // flags are valid
// The following are only for IMAP FETCH response parsing
M_DATE,
M_SIZE,
M_BODY,
M_HEADER,
)
#define msg_sts_enum(fn) \
fn(M, RECENT) /* unsyncable flag; maildir_*() depend on this being bit 0 */ \
fn(M, DEAD) /* expunged */ \
fn(M, EXPUNGE) /* for driver_t->close_box() */ \
fn(M, FLAGS) /* flags are valid */ \
/* The following are only for IMAP FETCH response parsing */ \
fn(M, DATE) \
fn(M, SIZE) \
fn(M, BODY) \
fn(M, HEADER)
DEFINE_PFX_BIT_ENUM(msg_sts_enum)
#define TUIDL 12
@ -79,29 +78,29 @@ typedef struct message {
MESSAGE(struct message)
} message_t;
static_assert_bits(F, message_t, flags);
static_assert_bits(M, message_t, status);
static_assert_bits(msg_flags_enum, message_t, flags);
static_assert_bits(msg_sts_enum, message_t, status);
// For driver_t->prepare_load_box(), which may amend the passed flags.
// The drivers don't use the first three, but may set them if loading the
// particular range is required to handle some other flag; note that these
// ranges may overlap.
BIT_ENUM(
OPEN_PAIRED, // Paired messages *in* this store.
OPEN_OLD, // Messages that should be already propagated *from* this store.
OPEN_NEW, // Messages (possibly) not yet propagated *from* this store.
OPEN_FIND,
OPEN_FLAGS, // Note that fetch_msg() gets the flags regardless.
OPEN_OLD_SIZE,
OPEN_NEW_SIZE,
OPEN_PAIRED_IDS,
OPEN_APPEND,
OPEN_SETFLAGS,
OPEN_EXPUNGE,
// Expunge only deleted messages we know about. Relies on OPEN_{OLD,NEW,FLAGS}
// being set externally. The driver may unset it if it can't handle it.
OPEN_UID_EXPUNGE,
)
#define open_flags_enum(fn) \
fn(OPEN, PAIRED) /* Paired messages *in* this store. */ \
fn(OPEN, OLD) /* Messages that should be already propagated *from* this store. */ \
fn(OPEN, NEW) /* Messages (possibly) not yet propagated *from* this store. */ \
fn(OPEN, FIND) \
fn(OPEN, FLAGS) /* Note that fetch_msg() gets the flags regardless. */ \
fn(OPEN, OLD_SIZE) \
fn(OPEN, NEW_SIZE) \
fn(OPEN, PAIRED_IDS) \
fn(OPEN, APPEND) \
fn(OPEN, SETFLAGS) \
fn(OPEN, EXPUNGE) \
/* Expunge only deleted messages we know about. Relies on OPEN_{OLD,NEW,FLAGS} */ \
/* being set externally. The driver may unset it if it can't handle it. */ \
fn(OPEN, UID_EXPUNGE)
DEFINE_PFX_BIT_ENUM(open_flags_enum)
#define UIDVAL_BAD ((uint)-1)
@ -122,7 +121,7 @@ typedef struct {
uchar flags;
} msg_data_t;
static_assert_bits(F, msg_data_t, flags);
static_assert_bits(msg_flags_enum, msg_data_t, flags);
#define DRV_OK 0
/* Message went missing, or mailbox is full, etc. */
@ -295,9 +294,6 @@ struct driver {
void (*cancel_cmds)( store_t *ctx,
void (*cb)( void *aux ), void *aux );
/* Commit any pending set_msg_flags() commands. */
void (*commit_cmds)( store_t *ctx );
/* Get approximate amount of memory occupied by the driver. */
uint (*get_memory_usage)( store_t *ctx );

68
src/drv_imap.c

@ -163,8 +163,7 @@ union imap_store {
// Command queue
imap_cmd_t *pending, **pending_append;
imap_cmd_t *in_progress, **in_progress_append;
imap_cmd_t *wait_check, **wait_check_append;
int nexttag, num_in_progress, num_wait_check;
int nexttag, num_in_progress;
uint buffer_mem; // Memory currently occupied by buffers in the queue
// Used during sequential operations like connect
@ -204,7 +203,6 @@ union imap_store {
uint data_len; \
uint uid; /* to identify fetch responses */ \
char high_prio; /* if command is queued, put it at the front of the queue. */ \
char wait_check; /* Don't report success until subsequent CHECK success. */ \
char to_trash; /* we are storing to trash, not current. */ \
char create; /* create the mailbox if we get an error which suggests so. */ \
char failok; /* Don't complain about NO (@1) / BAD (@2) response. */ \
@ -352,8 +350,6 @@ new_imap_cmd( uint size )
static void
done_imap_cmd( imap_store_t *ctx, imap_cmd_t *cmd, int response )
{
if (cmd->param.wait_check)
ctx->num_wait_check--;
if (cmd->param.data) {
free( cmd->param.data );
cmd->param.data = NULL;
@ -482,18 +478,6 @@ flush_imap_cmds( imap_store_t *ctx )
}
}
static void
finalize_checked_imap_cmds( imap_store_t *ctx, int resp )
{
imap_cmd_t *cmd;
while ((cmd = ctx->wait_check)) {
if (!(ctx->wait_check = cmd->next))
ctx->wait_check_append = &ctx->wait_check;
done_imap_cmd( ctx, cmd, resp );
}
}
static void
cancel_pending_imap_cmds( imap_store_t *ctx )
{
@ -527,8 +511,6 @@ submit_imap_cmd( imap_store_t *ctx, imap_cmd_t *cmd )
assert( cmd );
assert( cmd->param.done );
if (cmd->param.wait_check)
ctx->num_wait_check++;
if ((ctx->pending && !cmd->param.high_prio) || !cmd_sendable( ctx, cmd )) {
if (ctx->pending && cmd->param.high_prio) {
cmd->next = ctx->pending;
@ -1548,7 +1530,7 @@ is_inbox( imap_store_t *ctx, const char *arg, int argl )
{
if (!starts_with_upper( arg, argl, "INBOX", 5 ))
return 0;
if (arg[5] && arg[5] != ctx->delimiter[0])
if (argl > 5 && arg[5] != ctx->delimiter[0])
return 0;
return 1;
}
@ -1700,7 +1682,7 @@ prepare_box( char **buf, const imap_store_t *ctx )
if (!memcmp( name, "INBOX", 5 )) {
pfx = "";
} else if (!*pfx) {
error( "IMAP error: cannot use unqualified '%s'. Did you mean INBOX?", name );
error( "IMAP error: cannot use unqualified '%s'. Did you mean INBOX?\n", name );
return -1;
}
}
@ -1943,13 +1925,7 @@ imap_socket_read( void *aux )
imap_ref( ctx );
if (resp == RESP_CANCEL)
imap_invoke_bad_callback( ctx );
if (resp == RESP_OK && cmdp->param.wait_check) {
cmdp->next = NULL;
*ctx->wait_check_append = cmdp;
ctx->wait_check_append = &cmdp->next;
} else {
done_imap_cmd( ctx, cmdp, resp );
}
if (imap_deref( ctx ))
return;
if (ctx->canceling && !ctx->in_progress) {
@ -1972,7 +1948,6 @@ get_cmd_result_p2( imap_store_t *ctx, imap_cmd_t *cmd, int response )
if (response != RESP_OK) {
done_imap_cmd( ctx, ocmd, response );
} else {
assert( !ocmd->param.wait_check );
ctx->uidnext = 1;
if (ocmd->param.to_trash)
ctx->trashnc = TrashKnown;
@ -1993,7 +1968,6 @@ imap_cancel_store( store_t *gctx )
sasl_dispose( &ctx->sasl );
#endif
socket_close( &ctx->conn );
finalize_checked_imap_cmds( ctx, RESP_CANCEL );
cancel_sent_imap_cmds( ctx );
cancel_pending_imap_cmds( ctx );
free( ctx->ns_prefix );
@ -2053,7 +2027,7 @@ imap_free_store( store_t *gctx )
{
imap_store_t *ctx = (imap_store_t *)gctx;
assert( !ctx->pending && !ctx->in_progress && !ctx->wait_check );
assert( !ctx->pending && !ctx->in_progress );
if (ctx->state == SST_BAD) {
imap_cancel_store( gctx );
@ -2164,7 +2138,6 @@ imap_alloc_store( store_conf_t *conf, const char *label )
imap_socket_read, (void (*)(void *))flush_imap_cmds, ctx );
ctx->in_progress_append = &ctx->in_progress;
ctx->pending_append = &ctx->pending;
ctx->wait_check_append = &ctx->wait_check;
gotsrv:
ctx->conf = cfg;
@ -2878,7 +2851,7 @@ imap_select_box( store_t *gctx, const char *name )
{
imap_store_t *ctx = (imap_store_t *)gctx;
assert( !ctx->pending && !ctx->in_progress && !ctx->wait_check );
assert( !ctx->pending && !ctx->in_progress );
reset_imap_messages( &ctx->msgs );
@ -3137,7 +3110,7 @@ imap_load_box( store_t *gctx, uint minuid, uint maxuid, uint finduid, uint pairu
if (i != j)
bl += sprintf( buf + bl, ":%u", excs.data[i] );
}
imap_submit_load( ctx, buf, shifted_bit( ctx->opts, OPEN_PAIRED_IDS, WantMsgids ), sts );
imap_submit_load( ctx, buf, shifted_bit( ctx->opts, (int)OPEN_PAIRED_IDS, WantMsgids ), sts );
}
if (maxuid == UINT_MAX)
maxuid = ctx->uidnext - 1;
@ -3278,9 +3251,7 @@ imap_flags_helper( imap_store_t *ctx, uint uid, char what, int flags,
char buf[256];
buf[imap_make_flags( flags, buf )] = 0;
imap_cmd_t *cmd = imap_refcounted_new_cmd( &sts->gen );
cmd->param.wait_check = 1;
imap_exec( ctx, cmd, imap_set_flags_p2,
imap_exec( ctx, imap_refcounted_new_cmd( &sts->gen ), imap_set_flags_p2,
"UID STORE %u %cFLAGS.SILENT %s", uid, what, buf );
}
@ -3352,7 +3323,7 @@ imap_close_box( store_t *gctx,
{
imap_store_t *ctx = (imap_store_t *)gctx;
assert( !ctx->num_wait_check );
assert( !ctx->pending && !ctx->in_progress );
if (ctx->opts & OPEN_UID_EXPUNGE) {
INIT_REFCOUNTED_STATE(imap_expunge_state_t, sts, cb, aux)
@ -3631,7 +3602,7 @@ imap_list_store( store_t *gctx, int flags,
// path | P [i] | i [P] | P
//
int pfx_is_empty = !*ctx->prefix;
int pfx_is_inbox = !pfx_is_empty && is_inbox( ctx, ctx->prefix, -1 );
int pfx_is_inbox = !pfx_is_empty && is_inbox( ctx, ctx->prefix, strlen( ctx->prefix ) );
if (((flags & (LIST_PATH | LIST_PATH_MAYBE)) || pfx_is_empty) && !pfx_is_inbox && !(ctx->listed & LIST_PATH)) {
ctx->listed |= LIST_PATH;
if (pfx_is_empty)
@ -3672,7 +3643,6 @@ imap_cancel_cmds( store_t *gctx,
{
imap_store_t *ctx = (imap_store_t *)gctx;
finalize_checked_imap_cmds( ctx, RESP_CANCEL );
cancel_pending_imap_cmds( ctx );
if (ctx->in_progress) {
ctx->canceling = 1;
@ -3683,25 +3653,6 @@ imap_cancel_cmds( store_t *gctx,
}
}
/******************* imap_commit_cmds *******************/
static void imap_commit_cmds_p2( imap_store_t *, imap_cmd_t *, int );
static void
imap_commit_cmds( store_t *gctx )
{
imap_store_t *ctx = (imap_store_t *)gctx;
if (ctx->num_wait_check)
imap_exec( ctx, NULL, imap_commit_cmds_p2, "CHECK" );
}
static void
imap_commit_cmds_p2( imap_store_t *ctx, imap_cmd_t *cmd ATTR_UNUSED, int response )
{
finalize_checked_imap_cmds( ctx, response );
}
/******************* imap_get_memory_usage *******************/
static uint
@ -4080,7 +4031,6 @@ struct driver imap_driver = {
imap_trash_msg,
imap_close_box,
imap_cancel_cmds,
imap_commit_cmds,
imap_get_memory_usage,
imap_get_fail_state,
};

9
src/drv_maildir.c

@ -1533,7 +1533,7 @@ maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data, int minimal
}
fstat( fd, &st );
if (st.st_size > INT_MAX) {
error( "Maildir error: %s is too big", buf );
error( "Maildir error: %s is too big\n", buf );
goto mbad;
}
data->len = st.st_size;
@ -1853,12 +1853,6 @@ maildir_cancel_cmds( store_t *gctx ATTR_UNUSED,
cb( aux );
}
static void
maildir_commit_cmds( store_t *gctx )
{
(void) gctx;
}
static uint
maildir_get_memory_usage( store_t *gctx ATTR_UNUSED )
{
@ -1983,7 +1977,6 @@ struct driver maildir_driver = {
maildir_trash_msg,
maildir_close_box,
maildir_cancel_cmds,
maildir_commit_cmds,
maildir_get_memory_usage,
maildir_get_fail_state,
};

34
src/drv_proxy.c

@ -8,7 +8,7 @@
#include "driver.h"
BIT_FORMATTER_FUNCTION(opts, OPEN)
BIT_FORMATTER_FUNCTION(opts, open_flags_enum)
typedef struct gen_cmd gen_cmd_t;
@ -21,7 +21,6 @@ typedef union proxy_store {
driver_t *real_driver;
store_t *real_store;
gen_cmd_t *pending_cmds, **pending_cmds_append;
gen_cmd_t *check_cmds, **check_cmds_append;
wakeup_t wakeup;
uint fake_nextuid;
char is_fake; // Was "created" by dry-run
@ -91,42 +90,25 @@ proxy_wakeup( void *aux )
}
static void
proxy_invoke( gen_cmd_t *cmd, int checked, const char *name )
proxy_invoke( gen_cmd_t *cmd, const char *name )
{
proxy_store_t *ctx = cmd->ctx;
if (ctx->force_async) {
debug( "%s[% 2d] Queue %s%s\n", ctx->label, cmd->tag, name, checked ? " (checked)" : "" );
debug( "%s[% 2d] Queue %s\n", ctx->label, cmd->tag, name );
cmd->next = NULL;
if (checked) {
*ctx->check_cmds_append = cmd;
ctx->check_cmds_append = &cmd->next;
} else {
*ctx->pending_cmds_append = cmd;
ctx->pending_cmds_append = &cmd->next;
conf_wakeup( &ctx->wakeup, 0 );
}
} else {
cmd->queued_cb( cmd );
proxy_cmd_done( cmd );
}
}
static void
proxy_flush_checked_cmds( proxy_store_t *ctx )
{
if (ctx->check_cmds) {
*ctx->pending_cmds_append = ctx->check_cmds;
ctx->pending_cmds_append = ctx->check_cmds_append;
ctx->check_cmds_append = &ctx->check_cmds;
ctx->check_cmds = NULL;
conf_wakeup( &ctx->wakeup, 0 );
}
}
static void
proxy_cancel_queued_cmds( proxy_store_t *ctx )
{
if (ctx->pending_cmds || ctx->check_cmds) {
if (ctx->pending_cmds) {
// This would involve directly invoking the result callbacks with
// DRV_CANCEL, for which we'd need another set of dispatch functions.
// The autotest doesn't need that, so save the effort.
@ -253,7 +235,7 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v
cmd->callback = cb;
cmd->callback_aux = aux;
@assign_state@
proxy_invoke( &cmd->gen, @checked@, "@name@" );
proxy_invoke( &cmd->gen, "@name@" );
}
//# END
@ -382,7 +364,6 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v
//# DEFINE store_msg_fake_cb_args , cmd->to_trash ? 0 : ctx->fake_nextuid++
//# DEFINE store_msg_counted 1
//# DEFINE set_msg_flags_checked 1
//# DEFINE set_msg_flags_print_fmt_args , uid=%u, add=%s, del=%s
//# DEFINE set_msg_flags_print_pass_args , cmd->uid, fmt_flags( cmd->add ).str, fmt_flags( cmd->del ).str
//# DEFINE set_msg_flags_driable 1
@ -397,10 +378,6 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v
//# DEFINE close_box_fake_cb_args , 0
//# DEFINE close_box_counted 1
//# DEFINE commit_cmds_print_args
proxy_flush_checked_cmds( ctx );
//# END
//# DEFINE cancel_cmds_print_cb_args
proxy_cancel_queued_cmds( ctx );
//# END
@ -465,7 +442,6 @@ proxy_alloc_store( store_t *real_ctx, const char *label, int force_async )
ctx->label = label;
ctx->force_async = force_async;
ctx->pending_cmds_append = &ctx->pending_cmds;
ctx->check_cmds_append = &ctx->check_cmds;
ctx->real_driver = real_ctx->driver;
ctx->real_store = real_ctx;
ctx->real_driver->set_callbacks( ctx->real_store,

1
src/drv_proxy_gen.pl

@ -158,7 +158,6 @@ for (@ptypes) {
my $r_pass_args = make_args($r_cmd_args);
$replace{'assign_state'} = $r_pass_args =~ s/([^,]+), /cmd->$1 = $1\;\n/gr;
$replace{'checked'} = '0';
$template = "CALLBACK";
} else {
$pass_args = make_args($cmd_args);

1
src/main_sync.c

@ -186,6 +186,7 @@ filter_boxes( string_list_t *boxes, const char *prefix, string_list_t *patterns
boxarr[num] = NULL;
}
}
if (boxarr)
qsort( boxarr, num, sizeof(*boxarr), cmp_box_names );
return boxarr;
}

4
src/mbsync.1 → src/mbsync.1.in

@ -4,7 +4,7 @@
.\"
.\" mbsync - mailbox synchronizer
.
.TH mbsync 1 "2022 Jun 16"
.TH mbsync 1 @RELEASE_DATE@ "@PACKAGE_STRING@" "User Commands"
.
.SH NAME
mbsync - synchronize IMAP4 and Maildir mailboxes
@ -884,4 +884,4 @@ http://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml
.SH AUTHORS
Originally written by Michael R. Elkins,
rewritten and currently maintained by Oswald Buddenhagen,
contributions by Theodore Y. Ts'o.
contributions by many others; see the AUTHORS file for details.

2
src/mdconvert.1 → src/mdconvert.1.in

@ -3,7 +3,7 @@
.\"
.\" mdconvert - Maildir mailbox UID storage scheme converter
.
.TH mdconvert 1 "2004 Mar 27"
.TH mdconvert 1 @RELEASE_DATE@ "@PACKAGE_STRING@" "User Commands"
.
.SH NAME
mdconvert - Maildir mailbox UID storage scheme converter

5
src/socket.c

@ -908,8 +908,11 @@ socket_fill( conn_t *sock )
// IIR filter for tracking average size of bulk reads.
// We use this to optimize the free space at the end of the
// buffer, hence the factor of 1.5.
if (n >= MIN_BULK_READ)
if (n >= MIN_BULK_READ) {
sock->readsz = (sock->readsz * 3 + n * 3 / 2) / 4;
if (sock->readsz > sizeof(sock->buf))
sock->readsz = sizeof(sock->buf);
}
socket_filled( sock, (uint)n );
}

51
src/sync.c

@ -6,7 +6,6 @@
*/
#include "sync_p.h"
#include "sync_c_enum.h"
channel_conf_t global_conf;
channel_conf_t *channels;
@ -45,24 +44,24 @@ static int check_cancel( sync_vars_t *svars );
cleanup: close(F) & close(N)
*/
BIT_ENUM(
ST_PRESENT,
ST_CONFIRMED,
ST_SELECTED,
ST_FIND_OLD,
ST_LOADED,
ST_SENT_FLAGS,
ST_SENDING_NEW,
ST_SENT_NEW,
ST_FIND_NEW,
ST_FOUND_NEW,
ST_SENT_TRASH,
ST_TRASH_BAD,
ST_CLOSING,
ST_CLOSED,
ST_SENT_CANCEL,
ST_CANCELED,
)
#define sync_sts_enum(fn) \
fn(ST, PRESENT) \
fn(ST, CONFIRMED) \
fn(ST, SELECTED) \
fn(ST, FIND_OLD) \
fn(ST, LOADED) \
fn(ST, SENT_FLAGS) \
fn(ST, SENDING_NEW) \
fn(ST, SENT_NEW) \
fn(ST, FIND_NEW) \
fn(ST, FOUND_NEW) \
fn(ST, SENT_TRASH) \
fn(ST, TRASH_BAD) \
fn(ST, CLOSING) \
fn(ST, CLOSED) \
fn(ST, SENT_CANCEL) \
fn(ST, CANCELED)
DEFINE_PFX_BIT_ENUM(sync_sts_enum)
static uchar
sanitize_flags( uchar tflags, sync_vars_t *svars, int t )
@ -377,10 +376,13 @@ sync_boxes( store_t *ctx[], const char * const names[], int present[], channel_c
sync_ref( svars );
for (t = 0; ; t++) {
info( "Opening %s box %s...\n", str_fn[t], svars->orig_name[t] );
if (present[t] == BOX_ABSENT)
if (present[t] == BOX_ABSENT) {
box_confirmed2( svars, t );
else
} else {
if (present[t] == BOX_PRESENT)
svars->state[t] |= ST_PRESENT;
svars->drv[t]->open_box( ctx[t], box_confirmed, AUX );
}
if (t || check_cancel( svars ))
break;
}
@ -399,6 +401,12 @@ box_confirmed( int sts, uint uidvalidity, void *aux )
if (sts == DRV_OK) {
svars->state[t] |= ST_PRESENT;
svars->newuidval[t] = uidvalidity;
} else if (svars->state[t] & ST_PRESENT) {
error( "Error: channel %s: %s box %s cannot be opened.\n",
svars->chan->name, str_fn[t], svars->orig_name[t] );
svars->ret |= SYNC_FAIL;
cancel_sync( svars );
return;
}
box_confirmed2( svars, t );
}
@ -1364,7 +1372,6 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux
}
}
for (t = 0; t < 2; t++) {
svars->drv[t]->commit_cmds( svars->ctx[t] );
svars->state[t] |= ST_SENT_FLAGS;
msgs_flags_set( svars, t );
if (check_cancel( svars ))

57
src/sync.h

@ -9,44 +9,43 @@
#define SYNC_H
#include "driver.h"
#include "sync_enum.h"
#define F 0 // far side
#define N 1 // near side
BIT_ENUM(
OP_NEW,
OP_OLD,
OP_UPGRADE,
OP_GONE,
OP_FLAGS,
OP_EXPUNGE,
OP_EXPUNGE_SOLO,
OP_CREATE,
OP_REMOVE,
XOP_PUSH,
XOP_PULL,
XOP_HAVE_TYPE, // Aka mode; have at least one of dir and type (see below)
// The following must all have the same bit shift from the corresponding OP_* flags.
XOP_HAVE_EXPUNGE,
XOP_HAVE_EXPUNGE_SOLO,
XOP_HAVE_CREATE,
XOP_HAVE_REMOVE,
// ... until here.
XOP_TYPE_NOOP,
// ... and here again from scratch.
XOP_EXPUNGE_NOOP,
XOP_EXPUNGE_SOLO_NOOP,
XOP_CREATE_NOOP,
XOP_REMOVE_NOOP,
)
#define sync_op_enum(fn) \
fn(OP, NEW) \
fn(OP, OLD) \
fn(OP, UPGRADE) \
fn(OP, GONE) \
fn(OP, FLAGS) \
fn(OP, EXPUNGE) \
fn(OP, EXPUNGE_SOLO) \
fn(OP, CREATE) \
fn(OP, REMOVE) \
\
fn(XOP, PUSH) \
fn(XOP, PULL) \
fn(XOP, HAVE_TYPE) /* Aka mode; have at least one of dir and type (see below) */ \
/* The following must all have the same bit shift from the corresponding OP_* flags. */ \
fn(XOP, HAVE_EXPUNGE) \
fn(XOP, HAVE_EXPUNGE_SOLO) \
fn(XOP, HAVE_CREATE) \
fn(XOP, HAVE_REMOVE) \
/* ... until here. */ \
fn(XOP, TYPE_NOOP) \
/* ... and here again from scratch. */ \
fn(XOP, EXPUNGE_NOOP) \
fn(XOP, EXPUNGE_SOLO_NOOP) \
fn(XOP, CREATE_NOOP) \
fn(XOP, REMOVE_NOOP)
DEFINE_PFX_BIT_ENUM(sync_op_enum)
#define OP_DFLT_TYPE (OP_NEW | OP_UPGRADE | OP_GONE | OP_FLAGS)
#define OP_MASK_TYPE (OP_DFLT_TYPE | OP_OLD) // Asserted in the target side ops
#define XOP_MASK_DIR (XOP_PUSH | XOP_PULL)
DECL_BIT_FORMATTER_FUNCTION(ops, OP)
DECL_BIT_FORMATTER_FUNCTION(ops, sync_op_enum)
typedef struct channel_conf {
struct channel_conf *next;

43
src/sync_p.h

@ -7,23 +7,28 @@
#define DEBUG_FLAG DEBUG_SYNC
#include "sync.h"
#include "sync_p_enum.h"
BIT_ENUM(
S_DEAD, // ephemeral: the entry was killed and should be ignored
S_EXPIRE, // the entry is being expired (expire-side message removal scheduled)
S_EXPIRED, // the entry is expired (expire-side message removal confirmed)
S_NEXPIRE, // temporary: new expiration state
S_PENDING, // the entry is new and awaits propagation (possibly a retry)
S_DUMMY(2), // f/n message is only a placeholder
S_SKIPPED, // pre-1.4 legacy: the entry was not propagated (message is too big)
S_GONE(2), // ephemeral: f/n message has been expunged
S_DEL(2), // ephemeral: f/n message would be subject to non-selective expunge
S_DELETE, // ephemeral: flags propagation is a deletion
S_UPGRADE, // ephemeral: upgrading placeholder, do not apply MaxSize
S_PURGE, // ephemeral: placeholder is being nuked
S_PURGED, // ephemeral: placeholder was nuked
)
#define srec_sts_enum(fn) \
fn(S, DEAD) /* ephemeral: the entry was killed and should be ignored */ \
fn(S, EXPIRE) /* the entry is being expired (expire-side message removal scheduled) */ \
fn(S, EXPIRED) /* the entry is expired (expire-side message removal confirmed) */ \
fn(S, NEXPIRE) /* temporary: new expiration state */ \
fn(S, PENDING) /* the entry is new and awaits propagation (possibly a retry) */ \
fn(S, DUMMY_F) /* f/n message is only a placeholder */ \
fn(S, DUMMY_N) \
fn(S, SKIPPED) /* pre-1.4 legacy: the entry was not propagated (message is too big) */ \
fn(S, GONE_F) /* ephemeral: f/n message has been expunged */ \
fn(S, GONE_N) \
fn(S, DEL_F) /* ephemeral: f/n message would be subject to non-selective expunge */ \
fn(S, DEL_N) \
fn(S, DELETE) /* ephemeral: flags propagation is a deletion */ \
fn(S, UPGRADE) /* ephemeral: upgrading placeholder, do not apply MaxSize */ \
fn(S, PURGE) /* ephemeral: placeholder is being nuked */ \
fn(S, PURGED) /* ephemeral: placeholder was nuked */
DEFINE_PFX_BIT_ENUM(srec_sts_enum)
#define S_DUMMY(b) (S_DUMMY_F << (b))
#define S_GONE(b) (S_GONE_F << (b))
#define S_DEL(b) (S_DEL_F << (b))
// This is the persistent status of the sync record, with regard to the journal.
#define S_LOGGED (S_EXPIRE | S_EXPIRED | S_PENDING | S_DUMMY(F) | S_DUMMY(N) | S_SKIPPED)
@ -38,8 +43,8 @@ typedef struct sync_rec {
char tuid[TUIDL];
} sync_rec_t;
static_assert_bits(F, sync_rec_t, flags);
static_assert_bits(S, sync_rec_t, status);
static_assert_bits(msg_flags_enum, sync_rec_t, flags);
static_assert_bits(srec_sts_enum, sync_rec_t, status);
typedef struct {
int t[2];

2
src/sync_state.c

@ -17,7 +17,7 @@
const char *str_fn[] = { "far side", "near side" }, *str_hl[] = { "push", "pull" };
BIT_FORMATTER_FUNCTION(sts, S)
BIT_FORMATTER_FUNCTION(sts, srec_sts_enum)
static char *
clean_strdup( const char *s )

36
version.sh

@ -0,0 +1,36 @@
#!/bin/sh
# SPDX-FileCopyrightText: (C) 2024 Oswald Buddenhagen <ossi@users.sf.net>
# SPDX-License-Identifier: GPL-2.0-or-later
cd $(dirname $0)
test -e .git || exit
mb=$(git merge-base HEAD "@{upstream}" 2> /dev/null)
if test -z "$mb"; then
# we presume that a failure to find a merge base means no upstream.
# and no upstream may mean detached head in the middle of a rebase
br=$(git branch | sed -n -e 's/^\* (no branch, rebasing \([^\)]*\))$/\1/p')
if test -n "$br"; then
mb=$(git merge-base HEAD "$br@{upstream}" 2> /dev/null)
fi
fi
if test -z "$mb"; then
# still no upstream, so just describe HEAD as-is.
gver=$(git describe --tags HEAD)
else
# find out whether we have local work, and if so, collapse it into
# a single suffix. otherwise, we'd cause pointless rebuilds during
# development.
gver=$(git describe --tags $mb)
lcl=$(git rev-list -n 1 $mb..HEAD)
if test -n "$lcl"; then
gver="$gver-plus"
fi
fi
gver=${gver#v}
pgver=$(cat VERSION 2> /dev/null)
if [ "x$gver" != "x$pgver" ]; then
echo "$gver" > VERSION
fi
Loading…
Cancel
Save