Fossil

Check-in [03cc3329]
Login

Check-in [03cc3329]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Steps to prevent accidental forks during a commit when two users try to commit to the same parent at the same time. The first user to do the autosync pull takes a lock which causes the second user to get a "would fork" error, unless the --allow-fork or --force options are used.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 03cc3329384cd0c12eab9969fb77fea2a6ff85ac18a91887abb0f510fe090115
User & Date: drh 2019-06-29 03:17:07
Context
2019-06-29
10:21
Add the --tclsh command to "fossil diff" in order to specify an alternative TCL interpreter. ... (check-in: 9fc945c0 user: drh tags: trunk)
03:17
Steps to prevent accidental forks during a commit when two users try to commit to the same parent at the same time. The first user to do the autosync pull takes a lock which causes the second user to get a "would fork" error, unless the --allow-fork or --force options are used. ... (check-in: 03cc3329 user: drh tags: trunk)
03:14
Add the --save-password option to "fossil clone". Put the command-line options for "fossil clone" in alphabetical order. ... (check-in: 421e6bb3 user: drh tags: trunk)
2019-06-27
03:54
Because the sync status updates all occur on the same line make this client warning show up on its own line so the output doesn't get cluttered. ... (Closed-Leaf check-in: 82d69e7f user: andybradford tags: no-race-commit)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/checkin.c.
2159
2160
2161
2162
2163
2164
2165




2166
2167
2168
2169
2170
2171
2172
2173
    g.markPrivate = 1;
  }

  /*
  ** Autosync if autosync is enabled and this is not a private check-in.
  */
  if( !g.markPrivate ){




    if( autosync_loop(SYNC_PULL, db_get_int("autosync-tries", 1), 1) ){
      fossil_exit(1);
    }
  }

  /* Require confirmation to continue with the check-in if there is
  ** clock skew
  */







>
>
>
>
|







2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
    g.markPrivate = 1;
  }

  /*
  ** Autosync if autosync is enabled and this is not a private check-in.
  */
  if( !g.markPrivate ){
    int syncFlags = SYNC_PULL;
    if( vid!=0 && !allowFork && !forceFlag ){
      syncFlags |= SYNC_CKIN_LOCK;
    }
    if( autosync_loop(syncFlags, db_get_int("autosync-tries", 1), 1) ){
      fossil_exit(1);
    }
  }

  /* Require confirmation to continue with the check-in if there is
  ** clock skew
  */
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
        && db_exists("SELECT 1 from event where type='ci'") ){
        fossil_fatal("Would fork.  \"update\" first, use --branch, or --allow-fork.\n"
          "See https://fossil-scm.org/fossil/doc/trunk/www/branching.wiki#branching");
      }
      sCiInfo.zBranch = db_get("main-branch", "trunk");
    }
  }else if( sCiInfo.zBranch==0 && allowFork==0 && forceFlag==0
    && g.markPrivate==0 && !is_a_leaf(vid)
  ){
    /* Can't avoid duplicating this string because some C compilers
    ** refuse to see static const char zErr[] = "... as "constant"
    ** enough for a printf() style format string.  (e.g. Clang 10)
    */
    fossil_fatal("Would fork.  \"update\" first, use --branch or, --allow-fork.\n"
      "See https://fossil-scm.org/fossil/doc/trunk/www/branching.wiki#branching");







|







2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
        && db_exists("SELECT 1 from event where type='ci'") ){
        fossil_fatal("Would fork.  \"update\" first, use --branch, or --allow-fork.\n"
          "See https://fossil-scm.org/fossil/doc/trunk/www/branching.wiki#branching");
      }
      sCiInfo.zBranch = db_get("main-branch", "trunk");
    }
  }else if( sCiInfo.zBranch==0 && allowFork==0 && forceFlag==0
    && g.markPrivate==0 && (g.ckinLockFail || !is_a_leaf(vid))
  ){
    /* Can't avoid duplicating this string because some C compilers
    ** refuse to see static const char zErr[] = "... as "constant"
    ** enough for a printf() style format string.  (e.g. Clang 10)
    */
    fossil_fatal("Would fork.  \"update\" first, use --branch or, --allow-fork.\n"
      "See https://fossil-scm.org/fossil/doc/trunk/www/branching.wiki#branching");
Changes to src/main.c.
188
189
190
191
192
193
194

195
196
197
198
199
200
201
  int th1Flags;           /* The TH1 integration state flags */
  FILE *httpIn;           /* Accept HTTP input from here */
  FILE *httpOut;          /* Send HTTP output here */
  int xlinkClusterOnly;   /* Set when cloning.  Only process clusters */
  int fTimeFormat;        /* 1 for UTC.  2 for localtime.  0 not yet selected */
  int *aCommitFile;       /* Array of files to be committed */
  int markPrivate;        /* All new artifacts are private if true */

  int clockSkewSeen;      /* True if clocks on client and server out of sync */
  int wikiFlags;          /* Wiki conversion flags applied to %W */
  char isHTTP;            /* True if server/CGI modes, else assume CLI. */
  char javascriptHyperlink; /* If true, set href= using script, not HTML */
  Blob httpHeader;        /* Complete text of the HTTP request header */
  UrlData url;            /* Information about current URL */
  const char *zLogin;     /* Login name.  NULL or "" if not logged in. */







>







188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
  int th1Flags;           /* The TH1 integration state flags */
  FILE *httpIn;           /* Accept HTTP input from here */
  FILE *httpOut;          /* Send HTTP output here */
  int xlinkClusterOnly;   /* Set when cloning.  Only process clusters */
  int fTimeFormat;        /* 1 for UTC.  2 for localtime.  0 not yet selected */
  int *aCommitFile;       /* Array of files to be committed */
  int markPrivate;        /* All new artifacts are private if true */
  int ckinLockFail;       /* Check-in lock failure received from server */
  int clockSkewSeen;      /* True if clocks on client and server out of sync */
  int wikiFlags;          /* Wiki conversion flags applied to %W */
  char isHTTP;            /* True if server/CGI modes, else assume CLI. */
  char javascriptHyperlink; /* If true, set href= using script, not HTML */
  Blob httpHeader;        /* Complete text of the HTTP request header */
  UrlData url;            /* Information about current URL */
  const char *zLogin;     /* Login name.  NULL or "" if not logged in. */
Changes to src/xfer.c.
1543
1544
1545
1546
1547
1548
1549




















































1550
1551
1552
1553
1554
1555
1556
          }else if( g.perm.Read ){
            @ pragma uv-pull-only
            send_unversioned_catalog(&xfer);
          }
        }
        uvCatalogSent = 1;
      }




















































    }else

    /* Unknown message
    */
    {
      cgi_reset_content();
      @ error bad\scommand:\s%F(blob_str(&xfer.line))







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
          }else if( g.perm.Read ){
            @ pragma uv-pull-only
            send_unversioned_catalog(&xfer);
          }
        }
        uvCatalogSent = 1;
      }

      /*   pragma ci-lock CHECKIN-HASH CLIENT-ID
      **
      ** The client wants to make non-branch commit against the check-in
      ** identified by CHECKIN-HASH.  The server will remember this and
      ** subsequent ci-lock request from different clients will generate
      ** a ci-lock-fail pragma in the reply.
      */
      if( blob_eq(&xfer.aToken[1], "ci-lock")
       && xfer.nToken==4
       && blob_is_hname(&xfer.aToken[2])
      ){
        Stmt q;
        sqlite3_int64 iNow = time(0);
        int seenFault = 0;
        db_prepare(&q,
          "SELECT json_extract(value,'$.login'),"
          "       mtime,"
          "       json_extract(value,'$.clientid'),"
          "       (SELECT rid FROM blob WHERE uuid=substr(name,9)),"
          "       name"
          " FROM config WHERE name GLOB 'ci-lock-*'"
        );
        while( db_step(&q)==SQLITE_ROW ){
          int x = db_column_int(&q,3);
          const char *zName = db_column_text(&q,4);
          if( db_column_int64(&q,1)<iNow-3600*24 || !is_a_leaf(x) ){
            /* check-in locks expire after 24 hours, or when the check-in
            ** is no longer a leaf */
            db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
            continue;
          }
          if( fossil_strcmp(zName+8, blob_str(&xfer.aToken[2]))==0 ){
            const char *zClientId = db_column_text(&q, 2);
            const char *zLogin = db_column_text(&q,0);
            sqlite3_int64 mtime = db_column_int64(&q, 1);
            if( fossil_strcmp(zClientId, blob_str(&xfer.aToken[3]))!=0 ){
              @ pragma ci-lock-fail %F(zLogin) %lld(mtime)
            }
            seenFault = 1;
          }
        }
        db_finalize(&q);
        if( !seenFault ){
          db_multi_exec(
            "REPLACE INTO config(name,value,mtime)"
            "VALUES('ci-lock-%q',json_object('login',%Q,'clientid',%Q),now())",
            blob_str(&xfer.aToken[2]), g.zLogin,
            blob_str(&xfer.aToken[3])
          );
        }
      }
    }else

    /* Unknown message
    */
    {
      cgi_reset_content();
      @ error bad\scommand:\s%F(blob_str(&xfer.line))
1653
1654
1655
1656
1657
1658
1659

1660
1661
1662
1663
1664
1665
1666
#define SYNC_RESYNC         0x0020    /* --verily */
#define SYNC_UNVERSIONED    0x0040    /* Sync unversioned content */
#define SYNC_UV_REVERT      0x0080    /* Copy server unversioned to client */
#define SYNC_FROMPARENT     0x0100    /* Pull from the parent project */
#define SYNC_UV_TRACE       0x0200    /* Describe UV activities */
#define SYNC_UV_DRYRUN      0x0400    /* Do not actually exchange files */
#define SYNC_IFABLE         0x0800    /* Inability to sync is not fatal */

#endif

/*
** Floating-point absolute value
*/
static double fossil_fabs(double x){
  return x>0.0 ? x : -x;







>







1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
#define SYNC_RESYNC         0x0020    /* --verily */
#define SYNC_UNVERSIONED    0x0040    /* Sync unversioned content */
#define SYNC_UV_REVERT      0x0080    /* Copy server unversioned to client */
#define SYNC_FROMPARENT     0x0100    /* Pull from the parent project */
#define SYNC_UV_TRACE       0x0200    /* Describe UV activities */
#define SYNC_UV_DRYRUN      0x0400    /* Do not actually exchange files */
#define SYNC_IFABLE         0x0800    /* Inability to sync is not fatal */
#define SYNC_CKIN_LOCK      0x1000    /* Lock the current check-in */
#endif

/*
** Floating-point absolute value
*/
static double fossil_fabs(double x){
  return x>0.0 ? x : -x;
1706
1707
1708
1709
1710
1711
1712

1713
1714
1715
1716
1717
1718
1719
  double rSkew = 0.0;     /* Maximum time skew */
  int uvHashSent = 0;     /* The "pragma uv-hash" message has been sent */
  int uvDoPush = 0;       /* Generate uvfile messages to send to server */
  int nUvGimmeSent = 0;   /* Number of uvgimme cards sent on this cycle */
  int nUvFileRcvd = 0;    /* Number of uvfile cards received on this cycle */
  sqlite3_int64 mtime;    /* Modification time on a UV file */
  int autopushFailed = 0; /* Autopush following commit failed if true */


  if( db_get_boolean("dont-push", 0) ) syncFlags &= ~SYNC_PUSH;
  if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE|SYNC_UNVERSIONED))==0
     && configRcvMask==0 && configSendMask==0 ) return 0;
  if( syncFlags & SYNC_FROMPARENT ){
    configRcvMask = 0;
    configSendMask = 0;







>







1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
  double rSkew = 0.0;     /* Maximum time skew */
  int uvHashSent = 0;     /* The "pragma uv-hash" message has been sent */
  int uvDoPush = 0;       /* Generate uvfile messages to send to server */
  int nUvGimmeSent = 0;   /* Number of uvgimme cards sent on this cycle */
  int nUvFileRcvd = 0;    /* Number of uvfile cards received on this cycle */
  sqlite3_int64 mtime;    /* Modification time on a UV file */
  int autopushFailed = 0; /* Autopush following commit failed if true */
  const char *zCkinLock;  /* Name of check-in to lock.  NULL for none */

  if( db_get_boolean("dont-push", 0) ) syncFlags &= ~SYNC_PUSH;
  if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE|SYNC_UNVERSIONED))==0
     && configRcvMask==0 && configSendMask==0 ) return 0;
  if( syncFlags & SYNC_FROMPARENT ){
    configRcvMask = 0;
    configSendMask = 0;
1747
1748
1749
1750
1751
1752
1753








1754
1755
1756
1757
1758
1759
1760
  origConfigRcvMask = 0;


  /* Send the send-private pragma if we are trying to sync private data */
  if( syncFlags & SYNC_PRIVATE ){
    blob_append(&send, "pragma send-private\n", -1);
  }









  /* When syncing unversioned files, create a TEMP table in which to store
  ** the names of files that need to be sent from client to server.
  **
  ** The initial assumption is that all unversioned files need to be sent
  ** to the other side.  But "uvigot" cards received back from the remote
  ** side will normally cause many of these entries to be removed since they







>
>
>
>
>
>
>
>







1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
  origConfigRcvMask = 0;


  /* Send the send-private pragma if we are trying to sync private data */
  if( syncFlags & SYNC_PRIVATE ){
    blob_append(&send, "pragma send-private\n", -1);
  }

  /* Figure out which check-in to lock */
  if( syncFlags & SYNC_CKIN_LOCK ){
    int vid = db_lget_int("checkout",0);
    zCkinLock = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
  }else{
    zCkinLock = 0;
  }

  /* When syncing unversioned files, create a TEMP table in which to store
  ** the names of files that need to be sent from client to server.
  **
  ** The initial assumption is that all unversioned files need to be sent
  ** to the other side.  But "uvigot" cards received back from the remote
  ** side will normally cause many of these entries to be removed since they
1909
1910
1911
1912
1913
1914
1915












1916
1917
1918
1919
1920
1921
1922
          }
          if( blob_size(xfer.pOut)>xfer.mxSend ) break;
        }
        db_finalize(&uvq);
        if( rc==SQLITE_DONE ) uvDoPush = 0;
      }
    }













    /* Append randomness to the end of the message.  This makes all
    ** messages unique so that that the login-card nonce will always
    ** be unique.
    */
    zRandomness = db_text(0, "SELECT hex(randomblob(20))");
    blob_appendf(&send, "# %s\n", zRandomness);







>
>
>
>
>
>
>
>
>
>
>
>







1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
          }
          if( blob_size(xfer.pOut)>xfer.mxSend ) break;
        }
        db_finalize(&uvq);
        if( rc==SQLITE_DONE ) uvDoPush = 0;
      }
    }

    /* Lock the current check-out */
    if( zCkinLock ){
      const char *zClientId;
      zClientId = db_lget("client-id", 0);
      if( zClientId==0 ){
        zClientId = db_text(0, "SELECT lower(hex(randomblob(20)))");
        db_lset("client-id", zClientId);
      }
      blob_appendf(&send, "pragma ci-lock %s %s\n", zCkinLock, zClientId);
      zCkinLock = 0;
    }

    /* Append randomness to the end of the message.  This makes all
    ** messages unique so that that the login-card nonce will always
    ** be unique.
    */
    zRandomness = db_text(0, "SELECT hex(randomblob(20))");
    blob_appendf(&send, "# %s\n", zRandomness);
2269
2270
2271
2272
2273
2274
2275















2276
2277
2278
2279
2280
2281
2282
        ** does accept new unversioned content, it sends "uv-push-ok".
        */
        if( blob_eq(&xfer.aToken[1], "uv-pull-only") ){
          if( syncFlags & SYNC_UV_REVERT ) uvDoPush = 1;
        }else if( blob_eq(&xfer.aToken[1], "uv-push-ok") ){
          uvDoPush = 1;
        }















      }else

      /*   error MESSAGE
      **
      ** Report an error and abandon the sync session.
      **
      ** Except, when cloning we will sometimes get an error on the







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
        ** does accept new unversioned content, it sends "uv-push-ok".
        */
        if( blob_eq(&xfer.aToken[1], "uv-pull-only") ){
          if( syncFlags & SYNC_UV_REVERT ) uvDoPush = 1;
        }else if( blob_eq(&xfer.aToken[1], "uv-push-ok") ){
          uvDoPush = 1;
        }

        /*    pragma ci-lock-fail  USER-HOLDING-LOCK  LOCK-TIME
        **
        ** The server generates this message when a "pragma ci-lock"
        ** is attempted on a check-in for which there is an existing
        ** lock.  USER-HOLDING-LOCK is the name of the user who originated
        ** the lock, and LOCK-TIME is the timestamp (seconds since 1970)
        ** when the lock was taken.
        */
        else if( blob_eq(&xfer.aToken[1], "ci-lock-fail") && xfer.nToken==4 ){
          char *zUser = blob_terminate(&xfer.aToken[2]);
          defossilize(zUser);
          fossil_print("\nParent check-in locked by %s\n", zUser);
          g.ckinLockFail = 1;
        }
      }else

      /*   error MESSAGE
      **
      ** Report an error and abandon the sync session.
      **
      ** Except, when cloning we will sometimes get an error on the