コード例 #1
0
ファイル: bundle_.c プロジェクト: sambassett/Fossil-Repo
/*
** Identify a subsection of the check-in tree using command-line switches.
** There must be one of the following switch available:
**
**     --branch BRANCHNAME          All check-ins on the most recent
**                                  instance of BRANCHNAME
**     --from TAG1 [--to TAG2]      Check-in TAG1 and all primary descendants
**                                  up to and including TAG2
**     --checkin TAG                Check-in TAG only
**
** Store the RIDs for all applicable check-ins in the zTab table that
** should already exist.  Invoke fossil_fatal() if any kind of error is
** seen.
*/
void subtree_from_arguments(const char *zTab){
  const char *zBr;
  const char *zFrom;
  const char *zTo;
  const char *zCkin;
  int rid = 0, endRid;

  zBr = find_option("branch",0,1);
  zFrom = find_option("from",0,1);
  zTo = find_option("to",0,1);
  zCkin = find_option("checkin",0,1);
  if( zCkin ){
    if( zFrom ) fossil_fatal("cannot use both --checkin and --from");
    if( zBr ) fossil_fatal("cannot use both --checkin and --branch");
    rid = symbolic_name_to_rid(zCkin, "ci");
    endRid = rid;
  }else{
    endRid = zTo ? name_to_typed_rid(zTo, "ci") : 0;
  }
  if( zFrom ){
    rid = name_to_typed_rid(zFrom, "ci");
  }else if( zBr ){
    rid = name_to_typed_rid(zBr, "br");
  }else if( zCkin==0 ){
    fossil_fatal("need one of: --branch, --from, --checkin");
  }
  db_multi_exec("INSERT OR IGNORE INTO \"%w\" VALUES(%d)", zTab, rid);
  if( rid!=endRid ){
    Blob sql;
    blob_zero(&sql);
    blob_appendf(&sql,
       "WITH RECURSIVE child(rid) AS (VALUES(%d) UNION ALL "
       "  SELECT cid FROM plink, child"
       "   WHERE plink.pid=child.rid"
       "     AND plink.isPrim", rid);
    if( endRid>0 ){
      double endTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d",
                                 endRid);
      blob_appendf(&sql,
        "    AND child.rid!=%d"
        "    AND (SELECT mtime FROM event WHERE objid=plink.cid)<=%.17g",
        endRid, endTime
      );
    }
    if( zBr ){
      blob_appendf(&sql,
         "     AND EXISTS(SELECT 1 FROM tagxref"
                        "  WHERE tagid=%d AND tagtype>0"
                        "    AND value=%Q and rid=plink.cid)",
         TAG_BRANCH, zBr);
    }
    blob_appendf(&sql, ") INSERT OR IGNORE INTO \"%w\" SELECT rid FROM child;",
                 zTab);
    db_multi_exec("%s", blob_str(&sql)/*safe-for-%s*/);
  }
}
コード例 #2
0
ファイル: update.c プロジェクト: digsrc/fossil
/*
** Get the contents of a file within the check-in "revision".  If
** revision==NULL then get the file content for the current checkout.
*/
int historical_version_of_file(
    const char *revision,    /* The check-in containing the file */
    const char *file,        /* Full treename of the file */
    Blob *content,           /* Put the content here */
    int *pIsLink,            /* Set to true if file is link. */
    int *pIsExe,             /* Set to true if file is executable */
    int *pIsBin,             /* Set to true if file is binary */
    int errCode              /* Error code if file not found.  Panic if <= 0. */
) {
    Manifest *pManifest;
    ManifestFile *pFile;
    int rid=0;

    if( revision ) {
        rid = name_to_typed_rid(revision,"ci");
    } else if( !g.localOpen ) {
        rid = name_to_typed_rid(db_get("main-branch","trunk"),"ci");
    } else {
        rid = db_lget_int("checkout", 0);
    }
    if( !is_a_version(rid) ) {
        if( errCode>0 ) return errCode;
        fossil_fatal("no such check-in: %s", revision);
    }
    pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);

    if( pManifest ) {
        pFile = manifest_file_find(pManifest, file);
        if( pFile ) {
            int rc;
            rid = uuid_to_rid(pFile->zUuid, 0);
            if( pIsExe ) *pIsExe = ( manifest_file_mperm(pFile)==PERM_EXE );
            if( pIsLink ) *pIsLink = ( manifest_file_mperm(pFile)==PERM_LNK );
            manifest_destroy(pManifest);
            rc = content_get(rid, content);
            if( rc && pIsBin ) {
                *pIsBin = looks_like_binary(content);
            }
            return rc;
        }
        manifest_destroy(pManifest);
        if( errCode<=0 ) {
            fossil_fatal("file %s does not exist in check-in: %s", file, revision);
        }
    } else if( errCode<=0 ) {
        if( revision==0 ) {
            revision = db_text("current", "SELECT uuid FROM blob WHERE rid=%d", rid);
        }
        fossil_fatal("could not parse manifest for check-in: %s", revision);
    }
    return errCode;
}
コード例 #3
0
/*
** WEBPAGE: tarball
** URL: /tarball/RID.tar.gz
**
** Generate a compressed tarball for a checkin.
** Return that tarball as the HTTP reply content.
*/
void tarball_page(void){
  int rid;
  char *zName, *zRid;
  int nName, nRid;
  Blob tarball;

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(); return; }
  zName = mprintf("%s", PD("name",""));
  nName = strlen(zName);
  zRid = mprintf("%s", PD("uuid","trunk"));
  nRid = strlen(zRid);
  if( nName>7 && fossil_strcmp(&zName[nName-7], ".tar.gz")==0 ){
    /* Special case:  Remove the ".tar.gz" suffix.  */
    nName -= 7;
    zName[nName] = 0;
  }else{
    /* If the file suffix is not ".tar.gz" then just remove the
    ** suffix up to and including the last "." */
    for(nName=strlen(zName)-1; nName>5; nName--){
      if( zName[nName]=='.' ){
        zName[nName] = 0;
        break;
      }
    }
  }
  rid = name_to_typed_rid(nRid?zRid:zName, "ci");
  if( rid==0 ){
    @ Not found
    return;
  }
コード例 #4
0
/*
** COMMAND: tarball*
**
** Usage: %fossil tarball VERSION OUTPUTFILE [--name DIRECTORYNAME] [-R|--repository REPO]
**
** Generate a compressed tarball for a specified version.  If the --name
** option is used, its argument becomes the name of the top-level directory
** in the resulting tarball.  If --name is omitted, the top-level directory
** named is derived from the project name, the check-in date and time, and
** the artifact ID of the check-in.
*/
void tarball_cmd(void){
  int rid;
  Blob tarball;
  const char *zName;
  zName = find_option("name", 0, 1);
  db_find_and_open_repository(0, 0);
  if( g.argc!=4 ){
    usage("VERSION OUTPUTFILE");
  }
  rid = name_to_typed_rid(g.argv[2], "ci");
  if( rid==0 ){
    fossil_fatal("Checkin not found: %s", g.argv[2]);
    return;
  }

  if( zName==0 ){
    zName = db_text("default-name",
       "SELECT replace(%Q,' ','_') "
          " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) "
          " || substr(blob.uuid, 1, 10)"
       "  FROM event, blob"
       " WHERE event.objid=%d"
       "   AND blob.rid=%d",
       db_get("project-name", "unnamed"), rid, rid
    );
  }
  tarball_of_checkin(rid, &tarball, zName);
  blob_write_to_file(&tarball, g.argv[3]);
  blob_reset(&tarball);
}
コード例 #5
0
/*
** Generates a diff between two versions (zFrom and zTo), using nContext
** content lines in the output. On success, returns a new JSON String
** object. On error it sets g.json's error state and returns NULL.
**
** If fSbs is true (non-0) them side-by-side diffs are used.
**
** If fHtml is true then HTML markup is added to the diff.
*/
cson_value * json_generate_diff(const char *zFrom, const char *zTo,
                                int nContext, char fSbs,
                                char fHtml){
  int fromid;
  int toid;
  int outLen;
  Blob from = empty_blob, to = empty_blob, out = empty_blob;
  cson_value * rc = NULL;
  int flags = (DIFF_CONTEXT_MASK & nContext)
    | (fSbs ? DIFF_SIDEBYSIDE : 0)
    | (fHtml ? DIFF_HTML : 0);
  fromid = name_to_typed_rid(zFrom, "*");
  if(fromid<=0){
      json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
                   "Could not resolve 'from' ID.");
      return NULL;
  }
  toid = name_to_typed_rid(zTo, "*");
  if(toid<=0){
      json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
                   "Could not resolve 'to' ID.");
      return NULL;
  }
  content_get(fromid, &from);
  content_get(toid, &to);
  blob_zero(&out);
  text_diff(&from, &to, &out, 0, flags);
  blob_reset(&from);
  blob_reset(&to);
  outLen = blob_size(&out);
  if(outLen>=0){
    rc = cson_value_new_string(blob_buffer(&out),
                               (unsigned int)blob_size(&out));
  }
  blob_reset(&out);
  return rc;
}
コード例 #6
0
/*
** COMMAND: descendants*
**
** Usage: %fossil descendants ?BASELINE-ID? ?OPTIONS?
**
** Find all leaf descendants of the baseline specified or if the argument
** is omitted, of the baseline currently checked out.
**
** Options:
**    -R|--repository FILE       Extract info from repository FILE
**
** See also: finfo, info, leaves
*/
void descendants_cmd(void){
  Stmt q;
  int base;

  db_find_and_open_repository(0,0);
  if( g.argc==2 ){
    base = db_lget_int("checkout", 0);
  }else{
    base = name_to_typed_rid(g.argv[2], "ci");
  }
  if( base==0 ) return;
  compute_leaves(base, 0);
  db_prepare(&q,
    "%s"
    "   AND event.objid IN (SELECT rid FROM leaves)"
    " ORDER BY event.mtime DESC",
    timeline_query_for_tty()
  );
  print_timeline(&q, -20, 79, 0);
  db_finalize(&q);
}
コード例 #7
0
ファイル: name.c プロジェクト: digsrc/fossil
int name_to_rid(const char *zName){
  return name_to_typed_rid(zName, "*");
}
コード例 #8
0
ファイル: merge_.c プロジェクト: sambassett/Fossil-Repo
/*
** COMMAND: merge
**
** Usage: %fossil merge ?OPTIONS? ?VERSION?
**
** The argument VERSION is a version that should be merged into the
** current checkout.  All changes from VERSION back to the nearest
** common ancestor are merged.  Except, if either of the --cherrypick or
** --backout options are used only the changes associated with the
** single check-in VERSION are merged.  The --backout option causes
** the changes associated with VERSION to be removed from the current
** checkout rather than added.
**
** If the VERSION argument is omitted, then Fossil attempts to find
** a recent fork on the current branch to merge.
**
** Only file content is merged.  The result continues to use the
** file and directory names from the current checkout even if those
** names might have been changed in the branch being merged in.
**
** Other options:
**
**   --baseline BASELINE     Use BASELINE as the "pivot" of the merge instead
**                           of the nearest common ancestor.  This allows
**                           a sequence of changes in a branch to be merged
**                           without having to merge the entire branch.
**
**   --binary GLOBPATTERN    Treat files that match GLOBPATTERN as binary
**                           and do not try to merge parallel changes.  This
**                           option overrides the "binary-glob" setting.
**
**   --case-sensitive BOOL   Override the case-sensitive setting.  If false,
**                           files whose names differ only in case are taken
**                           to be the same file.
**
**   -f|--force              Force the merge even if it would be a no-op.
**
**   --force-missing         Force the merge even if there is missing content.
**
**   --integrate             Merged branch will be closed when committing.
**
**   -n|--dry-run            If given, display instead of run actions
**
**   -v|--verbose            Show additional details of the merge
*/
void merge_cmd(void){
  int vid;              /* Current version "V" */
  int mid;              /* Version we are merging from "M" */
  int pid;              /* The pivot version - most recent common ancestor P */
  int verboseFlag;      /* True if the -v|--verbose option is present */
  int integrateFlag;    /* True if the --integrate option is present */
  int pickFlag;         /* True if the --cherrypick option is present */
  int backoutFlag;      /* True if the --backout option is present */
  int dryRunFlag;       /* True if the --dry-run or -n option is present */
  int forceFlag;        /* True if the --force or -f option is present */
  int forceMissingFlag; /* True if the --force-missing option is present */
  const char *zBinGlob; /* The value of --binary */
  const char *zPivot;   /* The value of --baseline */
  int debugFlag;        /* True if --debug is present */
  int nChng;            /* Number of file name changes */
  int *aChng;           /* An array of file name changes */
  int i;                /* Loop counter */
  int nConflict = 0;    /* Number of conflicts seen */
  int nOverwrite = 0;   /* Number of unmanaged files overwritten */
  Stmt q;


  /* Notation:
  **
  **      V     The current checkout
  **      M     The version being merged in
  **      P     The "pivot" - the most recent common ancestor of V and M.
  */

  undo_capture_command_line();
  verboseFlag = find_option("verbose","v",0)!=0;
  forceMissingFlag = find_option("force-missing",0,0)!=0;
  if( !verboseFlag ){
    verboseFlag = find_option("detail",0,0)!=0; /* deprecated */
  }
  pickFlag = find_option("cherrypick",0,0)!=0;
  integrateFlag = find_option("integrate",0,0)!=0;
  backoutFlag = find_option("backout",0,0)!=0;
  debugFlag = find_option("debug",0,0)!=0;
  zBinGlob = find_option("binary",0,1);
  dryRunFlag = find_option("dry-run","n",0)!=0;
  if( !dryRunFlag ){
    dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */
  }
  forceFlag = find_option("force","f",0)!=0;
  zPivot = find_option("baseline",0,1);
  verify_all_options();
  db_must_be_within_tree();
  if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    fossil_fatal("nothing is checked out");
  }

  /* Find mid, the artifactID of the version to be merged into the current
  ** check-out */
  if( g.argc==3 ){
    /* Mid is specified as an argument on the command-line */
    mid = name_to_typed_rid(g.argv[2], "ci");
    if( mid==0 || !is_a_version(mid) ){
      fossil_fatal("not a version: %s", g.argv[2]);
    }
  }else if( g.argc==2 ){
    /* No version specified on the command-line so pick the most recent
    ** leaf that is (1) not the version currently checked out and (2)
    ** has not already been merged into the current checkout and (3)
    ** the leaf is not closed and (4) the leaf is in the same branch
    ** as the current checkout.
    */
    Stmt q;
    if( pickFlag || backoutFlag || integrateFlag){
      fossil_fatal("cannot use --backout, --cherrypick or --integrate with a fork merge");
    }
    mid = db_int(0,
      "SELECT leaf.rid"
      "  FROM leaf, event"
      " WHERE leaf.rid=event.objid"
      "   AND leaf.rid!=%d"                                /* Constraint (1) */
      "   AND leaf.rid NOT IN (SELECT merge FROM vmerge)"  /* Constraint (2) */
      "   AND NOT EXISTS(SELECT 1 FROM tagxref"            /* Constraint (3) */
                    "     WHERE rid=leaf.rid"
                    "       AND tagid=%d"
                    "       AND tagtype>0)"
      "   AND (SELECT value FROM tagxref"                  /* Constraint (4) */
            "   WHERE tagid=%d AND rid=%d AND tagtype>0) ="
            " (SELECT value FROM tagxref"
            "   WHERE tagid=%d AND rid=leaf.rid AND tagtype>0)"
      " ORDER BY event.mtime DESC LIMIT 1",
      vid, TAG_CLOSED, TAG_BRANCH, vid, TAG_BRANCH
    );
    if( mid==0 ){
      fossil_fatal("no unmerged forks of branch \"%s\"",
        db_text(0, "SELECT value FROM tagxref"
                   " WHERE tagid=%d AND rid=%d AND tagtype>0",
                   TAG_BRANCH, vid)
      );
    }
    db_prepare(&q,
      "SELECT blob.uuid,"
          "   datetime(event.mtime%s),"
          "   coalesce(ecomment, comment),"
          "   coalesce(euser, user)"
      "  FROM event, blob"
      " WHERE event.objid=%d AND blob.rid=%d",
      timeline_utc(), mid, mid
    );
    if( db_step(&q)==SQLITE_ROW ){
      char *zCom = mprintf("Merging fork [%S] at %s by %s: \"%s\"",
            db_column_text(&q, 0), db_column_text(&q, 1),
            db_column_text(&q, 3), db_column_text(&q, 2));
      comment_print(zCom, db_column_text(&q,2), 0, -1, g.comFmtFlags);
      fossil_free(zCom);
    }
    db_finalize(&q);
  }else{
    usage("?OPTIONS? ?VERSION?");
    return;
  }

  if( zPivot ){
    pid = name_to_typed_rid(zPivot, "ci");
    if( pid==0 || !is_a_version(pid) ){
      fossil_fatal("not a version: %s", zPivot);
    }
    if( pickFlag ){
      fossil_fatal("incompatible options: --cherrypick & --baseline");
    }
  }else if( pickFlag || backoutFlag ){
    if( integrateFlag ){
      fossil_fatal("incompatible options: --integrate & --cherrypick or --backout");
    }
    pid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", mid);
    if( pid<=0 ){
      fossil_fatal("cannot find an ancestor for %s", g.argv[2]);
    }
  }else{
    pivot_set_primary(mid);
    pivot_set_secondary(vid);
    db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0");
    while( db_step(&q)==SQLITE_ROW ){
      pivot_set_secondary(db_column_int(&q,0));
    }
    db_finalize(&q);
    pid = pivot_find();
    if( pid<=0 ){
      fossil_fatal("cannot find a common ancestor between the current "
                   "checkout and %s", g.argv[2]);
    }
  }
  if( backoutFlag ){
    int t = pid;
    pid = mid;
    mid = t;
  }
  if( !is_a_version(pid) ){
    fossil_fatal("not a version: record #%d", pid);
  }
  if( !forceFlag && mid==pid ){
    fossil_print("Merge skipped because it is a no-op. "
                 " Use --force to override.\n");
    return;
  }
  if( integrateFlag && !is_a_leaf(mid)){
    fossil_warning("ignoring --integrate: %s is not a leaf", g.argv[2]);
    integrateFlag = 0;
  }
  if( verboseFlag ){
    print_checkin_description(mid, 12, integrateFlag?"integrate:":"merge-from:");
    print_checkin_description(pid, 12, "baseline:");
  }
  vfile_check_signature(vid, CKSIG_ENOTFILE);
  db_begin_transaction();
  if( !dryRunFlag ) undo_begin();
  if( load_vfile_from_rid(mid) && !forceMissingFlag ){
    fossil_fatal("missing content, unable to merge");
  }
  if( load_vfile_from_rid(pid) && !forceMissingFlag ){
    fossil_fatal("missing content, unable to merge");
  }
  if( debugFlag ){
    char *z;
    z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pid);
    fossil_print("P=%d %z\n", pid, z);
    z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid);
    fossil_print("M=%d %z\n", mid, z);
    z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
    fossil_print("V=%d %z\n", vid, z);
  }

  /*
  ** The vfile.pathname field is used to match files against each other.  The
  ** FV table contains one row for each each unique filename in
  ** in the current checkout, the pivot, and the version being merged.
  */
  db_multi_exec(
    "DROP TABLE IF EXISTS fv;"
    "CREATE TEMP TABLE fv("
    "  fn TEXT PRIMARY KEY %s,"   /* The filename */
    "  idv INTEGER,"              /* VFILE entry for current version */
    "  idp INTEGER,"              /* VFILE entry for the pivot */
    "  idm INTEGER,"              /* VFILE entry for version merging in */
    "  chnged BOOLEAN,"           /* True if current version has been edited */
    "  ridv INTEGER,"             /* Record ID for current version */
    "  ridp INTEGER,"             /* Record ID for pivot */
    "  ridm INTEGER,"             /* Record ID for merge */
    "  isexe BOOLEAN,"            /* Execute permission enabled */
    "  fnp TEXT %s,"              /* The filename in the pivot */
    "  fnm TEXT %s,"              /* the filename in the merged version */
    "  islinkv BOOLEAN,"          /* True if current version is a symlink */
    "  islinkm BOOLEAN"           /* True if merged version in is a symlink */
    ");",
    filename_collation(), filename_collation(), filename_collation()
  );

  /* Add files found in V
  */
  db_multi_exec(
    "INSERT OR IGNORE"
    " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)"
    " SELECT pathname, pathname, pathname, id, 0, 0, rid, 0, 0, isexe, chnged "
    " FROM vfile WHERE vid=%d",
    vid
  );

  /*
  ** Compute name changes from P->V
  */
  find_filename_changes(pid, vid, 0, &nChng, &aChng, debugFlag ? "P->V" : 0);
  if( nChng ){
    for(i=0; i<nChng; i++){
      char *z;
      z = db_text(0, "SELECT name FROM filename WHERE fnid=%d", aChng[i*2]);
      db_multi_exec(
        "UPDATE fv SET fnp=%Q, fnm=%Q"
        " WHERE fn=(SELECT name FROM filename WHERE fnid=%d)",
        z, z, aChng[i*2+1]
      );
      free(z);
    }
    fossil_free(aChng);
    db_multi_exec("UPDATE fv SET fnm=fnp WHERE fnp!=fn");
  }

  /* Add files found in P but not in V
  */
  db_multi_exec(
    "INSERT OR IGNORE"
    " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)"
    " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, isexe, 0 "
    "   FROM vfile"
    "  WHERE vid=%d AND pathname %s NOT IN (SELECT fnp FROM fv)",
    pid, filename_collation()
  );

  /*
  ** Compute name changes from P->M
  */
  find_filename_changes(pid, mid, 0, &nChng, &aChng, debugFlag ? "P->M" : 0);
  if( nChng ){
    if( nChng>4 ) db_multi_exec("CREATE INDEX fv_fnp ON fv(fnp)");
    for(i=0; i<nChng; i++){
      db_multi_exec(
        "UPDATE fv SET fnm=(SELECT name FROM filename WHERE fnid=%d)"
        " WHERE fnp=(SELECT name FROM filename WHERE fnid=%d)",
        aChng[i*2+1], aChng[i*2]
      );
    }
    fossil_free(aChng);
  }

  /* Add files found in M but not in P or V.
  */
  db_multi_exec(
    "INSERT OR IGNORE"
    " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)"
    " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, isexe, 0 "
    "   FROM vfile"
    "  WHERE vid=%d"
    "    AND pathname %s NOT IN (SELECT fnp FROM fv UNION SELECT fnm FROM fv)",
    mid, filename_collation()
  );

  /*
  ** Compute the file version ids for P and M.
  */
  db_multi_exec(
    "UPDATE fv SET"
    " idp=coalesce((SELECT id FROM vfile WHERE vid=%d AND fnp=pathname),0),"
    " ridp=coalesce((SELECT rid FROM vfile WHERE vid=%d AND fnp=pathname),0),"
    " idm=coalesce((SELECT id FROM vfile WHERE vid=%d AND fnm=pathname),0),"
    " ridm=coalesce((SELECT rid FROM vfile WHERE vid=%d AND fnm=pathname),0),"
    " islinkv=coalesce((SELECT islink FROM vfile"
                    " WHERE vid=%d AND fnm=pathname),0),"
    " islinkm=coalesce((SELECT islink FROM vfile"
                    " WHERE vid=%d AND fnm=pathname),0)",
    pid, pid, mid, mid, vid, mid
  );

  if( debugFlag ){
    db_prepare(&q,
       "SELECT rowid, fn, fnp, fnm, chnged, ridv, ridp, ridm, "
       "       isexe, islinkv, islinkm FROM fv"
    );
    while( db_step(&q)==SQLITE_ROW ){
       fossil_print("%3d: ridv=%-4d ridp=%-4d ridm=%-4d chnged=%d isexe=%d "
                    " islinkv=%d islinkm=%d\n",
          db_column_int(&q, 0),
          db_column_int(&q, 5),
          db_column_int(&q, 6),
          db_column_int(&q, 7),
          db_column_int(&q, 4),
          db_column_int(&q, 8),
          db_column_int(&q, 9),
          db_column_int(&q, 10));
       fossil_print("     fn  = [%s]\n", db_column_text(&q, 1));
       fossil_print("     fnp = [%s]\n", db_column_text(&q, 2));
       fossil_print("     fnm = [%s]\n", db_column_text(&q, 3));
    }
    db_finalize(&q);
  }

  /*
  ** Find files in M and V but not in P and report conflicts.
  ** The file in M will be ignored.  It will be treated as if it
  ** does not exist.
  */
  db_prepare(&q,
    "SELECT idm FROM fv WHERE idp=0 AND idv>0 AND idm>0"
  );
  while( db_step(&q)==SQLITE_ROW ){
    int idm = db_column_int(&q, 0);
    char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idm);
    fossil_warning("WARNING - no common ancestor: %s", zName);
    free(zName);
    db_multi_exec("UPDATE fv SET idm=0 WHERE idm=%d", idm);
  }
  db_finalize(&q);

  /*
  ** Add to V files that are not in V or P but are in M
  */
  db_prepare(&q,
    "SELECT idm, rowid, fnm FROM fv AS x"
    " WHERE idp=0 AND idv=0 AND idm>0"
  );
  while( db_step(&q)==SQLITE_ROW ){
    int idm = db_column_int(&q, 0);
    int rowid = db_column_int(&q, 1);
    int idv;
    const char *zName;
    char *zFullName;
    db_multi_exec(
      "INSERT INTO vfile(vid,chnged,deleted,rid,mrid,isexe,islink,pathname)"
      "  SELECT %d,%d,0,rid,mrid,isexe,islink,pathname FROM vfile WHERE id=%d",
      vid, integrateFlag?5:3, idm
    );
    idv = db_last_insert_rowid();
    db_multi_exec("UPDATE fv SET idv=%d WHERE rowid=%d", idv, rowid);
    zName = db_column_text(&q, 2);
    zFullName = mprintf("%s%s", g.zLocalRoot, zName);
    if( file_wd_isfile_or_link(zFullName) ){
      fossil_print("ADDED %s (overwrites an unmanaged file)\n", zName);
      nOverwrite++;
    }else{
      fossil_print("ADDED %s\n", zName);
    }
    fossil_free(zFullName);
    if( !dryRunFlag ){
      undo_save(zName);
      vfile_to_disk(0, idm, 0, 0);
    }
  }
  db_finalize(&q);

  /*
  ** Find files that have changed from P->M but not P->V.
  ** Copy the M content over into V.
  */
  db_prepare(&q,
    "SELECT idv, ridm, fn, islinkm FROM fv"
    " WHERE idp>0 AND idv>0 AND idm>0"
    "   AND ridm!=ridp AND ridv=ridp AND NOT chnged"
  );
  while( db_step(&q)==SQLITE_ROW ){
    int idv = db_column_int(&q, 0);
    int ridm = db_column_int(&q, 1);
    const char *zName = db_column_text(&q, 2);
    int islinkm = db_column_int(&q, 3);
    /* Copy content from idm over into idv.  Overwrite idv. */
    fossil_print("UPDATE %s\n", zName);
    if( !dryRunFlag ){
      undo_save(zName);
      db_multi_exec(
        "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d "
        " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, idv
      );
      vfile_to_disk(0, idv, 0, 0);
    }
  }
  db_finalize(&q);

  /*
  ** Do a three-way merge on files that have changes on both P->M and P->V.
  */
  db_prepare(&q,
    "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv"
    " WHERE idp>0 AND idv>0 AND idm>0"
    "   AND ridm!=ridp AND (ridv!=ridp OR chnged)",
    glob_expr("fv.fn", zBinGlob)
  );
  while( db_step(&q)==SQLITE_ROW ){
    int ridm = db_column_int(&q, 0);
    int idv = db_column_int(&q, 1);
    int ridp = db_column_int(&q, 2);
    int ridv = db_column_int(&q, 3);
    int isBinary = db_column_int(&q, 4);
    const char *zName = db_column_text(&q, 5);
    int isExe = db_column_int(&q, 6);
    int islinkv = db_column_int(&q, 7);
    int islinkm = db_column_int(&q, 8);
    int rc;
    char *zFullPath;
    Blob m, p, r;
    /* Do a 3-way merge of idp->idm into idp->idv.  The results go into idv. */
    if( verboseFlag ){
      fossil_print("MERGE %s  (pivot=%d v1=%d v2=%d)\n",
                   zName, ridp, ridm, ridv);
    }else{
      fossil_print("MERGE %s\n", zName);
    }
    if( islinkv || islinkm /* || file_wd_islink(zFullPath) */ ){
      fossil_print("***** Cannot merge symlink %s\n", zName);
      nConflict++;
    }else{
      undo_save(zName);
      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
      content_get(ridp, &p);
      content_get(ridm, &m);
      if( isBinary ){
        rc = -1;
        blob_zero(&r);
      }else{
        unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0;
        rc = merge_3way(&p, zFullPath, &m, &r, mergeFlags);
      }
      if( rc>=0 ){
        if( !dryRunFlag ){
          blob_write_to_file(&r, zFullPath);
          file_wd_setexe(zFullPath, isExe);
        }
        db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv);
        if( rc>0 ){
          fossil_print("***** %d merge conflicts in %s\n", rc, zName);
          nConflict++;
        }
      }else{
        fossil_print("***** Cannot merge binary file %s\n", zName);
        nConflict++;
      }
      blob_reset(&p);
      blob_reset(&m);
      blob_reset(&r);
    }
    db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(%d,%d)",
                  idv,ridm);
  }
  db_finalize(&q);

  /*
  ** Drop files that are in P and V but not in M
  */
  db_prepare(&q,
    "SELECT idv, fn, chnged FROM fv"
    " WHERE idp>0 AND idv>0 AND idm=0"
  );
  while( db_step(&q)==SQLITE_ROW ){
    int idv = db_column_int(&q, 0);
    const char *zName = db_column_text(&q, 1);
    int chnged = db_column_int(&q, 2);
    /* Delete the file idv */
    fossil_print("DELETE %s\n", zName);
    if( chnged ){
      fossil_warning("WARNING: local edits lost for %s\n", zName);
      nConflict++;
    }
    undo_save(zName);
    db_multi_exec(
      "UPDATE vfile SET deleted=1 WHERE id=%d", idv
    );
    if( !dryRunFlag ){
      char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
      file_delete(zFullPath);
      free(zFullPath);
    }
  }
  db_finalize(&q);

  /*
  ** Rename files that have taken a rename on P->M but which keep the same
  ** name o P->V.   If a file is renamed on P->V only or on both P->V and
  ** P->M then we retain the V name of the file.
  */
  db_prepare(&q,
    "SELECT idv, fnp, fnm FROM fv"
    " WHERE idv>0 AND idp>0 AND idm>0 AND fnp=fn AND fnm!=fnp"
  );
  while( db_step(&q)==SQLITE_ROW ){
    int idv = db_column_int(&q, 0);
    const char *zOldName = db_column_text(&q, 1);
    const char *zNewName = db_column_text(&q, 2);
    fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
    undo_save(zOldName);
    undo_save(zNewName);
    db_multi_exec(
      "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)"
      " WHERE id=%d AND vid=%d", zNewName, idv, vid
    );
    if( !dryRunFlag ){
      char *zFullOldPath = mprintf("%s%s", g.zLocalRoot, zOldName);
      char *zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName);
      if( file_wd_islink(zFullOldPath) ){
        symlink_copy(zFullOldPath, zFullNewPath);
      }else{
        file_copy(zFullOldPath, zFullNewPath);
      }
      file_delete(zFullOldPath);
      free(zFullNewPath);
      free(zFullOldPath);
    }
  }
  db_finalize(&q);


  /* Report on conflicts
  */
  if( nConflict ){
    fossil_warning("WARNING: %d merge conflicts", nConflict);
  }
  if( nOverwrite ){
    fossil_warning("WARNING: %d unmanaged files were overwritten",
                   nOverwrite);
  }
  if( dryRunFlag ){
    fossil_warning("REMINDER: this was a dry run -"
                   " no files were actually changed.");
  }

  /*
  ** Clean up the mid and pid VFILE entries.  Then commit the changes.
  */
  db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
  if( pickFlag ){
    db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(-1,%d)",mid);
    /* For a cherry-pick merge, make the default check-in comment the same
    ** as the check-in comment on the check-in that is being merged in. */
    db_multi_exec(
       "REPLACE INTO vvar(name,value)"
       " SELECT 'ci-comment', coalesce(ecomment,comment) FROM event"
       "  WHERE type='ci' AND objid=%d",
       mid
    );
  }else if( backoutFlag ){
    db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(-2,%d)",pid);
  }else if( integrateFlag ){
    db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(-4,%d)",mid);
  }else{
    db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(0,%d)", mid);
  }
  undo_finish();
  db_end_transaction(dryRunFlag);
}
コード例 #9
0
ファイル: branch.c プロジェクト: oopos/fossil
/*
**  fossil branch new    BRANCH-NAME ?ORIGIN-CHECK-IN? ?-bgcolor COLOR?
**  argv0  argv1  argv2  argv3       argv4
*/
void branch_new(void){
  int rootid;            /* RID of the root check-in - what we branch off of */
  int brid;              /* RID of the branch check-in */
  int noSign;            /* True if the branch is unsigned */
  int i;                 /* Loop counter */
  char *zUuid;           /* Artifact ID of origin */
  Stmt q;                /* Generic query */
  const char *zBranch;   /* Name of the new branch */
  char *zDate;           /* Date that branch was created */
  char *zComment;        /* Check-in comment for the new branch */
  const char *zColor;    /* Color of the new branch */
  Blob branch;           /* manifest for the new branch */
  Manifest *pParent;     /* Parsed parent manifest */
  Blob mcksum;           /* Self-checksum on the manifest */
  const char *zDateOvrd; /* Override date string */
  const char *zUserOvrd; /* Override user name */
  int isPrivate = 0;     /* True if the branch should be private */
 
  noSign = find_option("nosign","",0)!=0;
  zColor = find_option("bgcolor","c",1);
  isPrivate = find_option("private",0,0)!=0;
  zDateOvrd = find_option("date-override",0,1);
  zUserOvrd = find_option("user-override",0,1);
  verify_all_options();
  if( g.argc<5 ){
    usage("new BRANCH-NAME CHECK-IN ?-bgcolor COLOR?");
  }
  db_find_and_open_repository(0, 0);  
  noSign = db_get_int("omitsign", 0)|noSign;
  
  /* fossil branch new name */
  zBranch = g.argv[3];
  if( zBranch==0 || zBranch[0]==0 ){
    fossil_panic("branch name cannot be empty");
  }
  if( db_exists(
        "SELECT 1 FROM tagxref"
        " WHERE tagtype>0"
        "   AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%s')",
        zBranch)!=0 ){
    fossil_fatal("branch \"%s\" already exists", zBranch);
  }

  user_select();
  db_begin_transaction();
  rootid = name_to_typed_rid(g.argv[4], "ci");
  if( rootid==0 ){
    fossil_fatal("unable to locate check-in off of which to branch");
  }

  pParent = manifest_get(rootid, CFTYPE_MANIFEST);
  if( pParent==0 ){
    fossil_fatal("%s is not a valid check-in", g.argv[4]);
  }

  /* Create a manifest for the new branch */
  blob_zero(&branch);
  if( pParent->zBaseline ){
    blob_appendf(&branch, "B %s\n", pParent->zBaseline);
  }
  zComment = mprintf("Create new branch named \"%h\"", zBranch);
  blob_appendf(&branch, "C %F\n", zComment);
  zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now");
  blob_appendf(&branch, "D %s\n", zDate);

  /* Copy all of the content from the parent into the branch */
  for(i=0; i<pParent->nFile; ++i){
    blob_appendf(&branch, "F %F", pParent->aFile[i].zName);
    if( pParent->aFile[i].zUuid ){
      blob_appendf(&branch, " %s", pParent->aFile[i].zUuid);
      if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){
        blob_appendf(&branch, " %s", pParent->aFile[i].zPerm);
      }
    }
    blob_append(&branch, "\n", 1);
  }
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid);
  blob_appendf(&branch, "P %s\n", zUuid);
  if( pParent->zRepoCksum ){
    blob_appendf(&branch, "R %s\n", pParent->zRepoCksum);
  }
  manifest_destroy(pParent);

  /* Add the symbolic branch name and the "branch" tag to identify
  ** this as a new branch */
  if( content_is_private(rootid) ) isPrivate = 1;
  if( isPrivate && zColor==0 ) zColor = "#fec084";
  if( zColor!=0 ){
    blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
  }
  blob_appendf(&branch, "T *branch * %F\n", zBranch);
  blob_appendf(&branch, "T *sym-%F *\n", zBranch);
  if( isPrivate ){
    blob_appendf(&branch, "T +private *\n");
    noSign = 1;
  }

  /* Cancel all other symbolic tags */
  db_prepare(&q,
      "SELECT tagname FROM tagxref, tag"
      " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
      "   AND tagtype>0 AND tagname GLOB 'sym-*'"
      " ORDER BY tagname",
      rootid);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zTag = db_column_text(&q, 0);
    blob_appendf(&branch, "T -%F *\n", zTag);
  }
  db_finalize(&q);
  
  blob_appendf(&branch, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin);
  md5sum_blob(&branch, &mcksum);
  blob_appendf(&branch, "Z %b\n", &mcksum);
  if( !noSign && clearsign(&branch, &branch) ){
    Blob ans;
    blob_zero(&ans);
    prompt_user("unable to sign manifest.  continue (y/N)? ", &ans);
    if( blob_str(&ans)[0]!='y' ){
      db_end_transaction(1);
      fossil_exit(1);
    }
  }

  brid = content_put_ex(&branch, 0, 0, 0, isPrivate);
  if( brid==0 ){
    fossil_panic("trouble committing manifest: %s", g.zErrMsg);
  }
  db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
  if( manifest_crosslink(brid, &branch)==0 ){
    fossil_panic("unable to install new manifest");
  }
  assert( blob_is_reset(&branch) );
  content_deltify(rootid, brid, 0);
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid);
  fossil_print("New branch: %s\n", zUuid);
  if( g.argc==3 ){
    fossil_print(
      "\n"
      "Note: the local check-out has not been updated to the new\n"
      "      branch.  To begin working on the new branch, do this:\n"
      "\n"
      "      %s update %s\n",
      fossil_nameofexe(), zBranch
    );
  }


  /* Commit */
  db_end_transaction(0);
  
  /* Do an autosync push, if requested */
  if( !isPrivate ) autosync(AUTOSYNC_PUSH);
}
コード例 #10
0
/*
** WEBPAGE: doc
** URL: /doc?name=BASELINE/PATH
** URL: /doc/BASELINE/PATH
**
** BASELINE can be either a baseline uuid prefix or magic words "tip"
** to mean the most recently checked in baseline or "ckout" to mean the
** content of the local checkout, if any.  PATH is the relative pathname
** of some file.  This method returns the file content.
**
** If PATH matches the patterns *.wiki or *.txt then formatting content
** is added before returning the file.  For all other names, the content
** is returned straight without any interpretation or processing.
*/
void doc_page(void){
  const char *zName;                /* Argument to the /doc page */
  const char *zMime;                /* Document MIME type */
  int vid = 0;                      /* Artifact of baseline */
  int rid = 0;                      /* Artifact of file */
  int i;                            /* Loop counter */
  Blob filebody;                    /* Content of the documentation file */
  char zBaseline[UUID_SIZE+1];      /* Baseline UUID */

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  zName = PD("name", "tip/index.wiki");
  for(i=0; zName[i] && zName[i]!='/'; i++){}
  if( zName[i]==0 || i>UUID_SIZE ){
    zName = "index.html";
    goto doc_not_found;
  }
  memcpy(zBaseline, zName, i);
  zBaseline[i] = 0;
  zName += i;
  while( zName[0]=='/' ){ zName++; }
  if( !file_is_simple_pathname(zName) ){
    int n = strlen(zName);
    if( n>0 && zName[n-1]=='/' ){
      zName = mprintf("%sindex.html", zName);
      if( !file_is_simple_pathname(zName) ){
        goto doc_not_found;
      }
    }else{
      goto doc_not_found;
    }
  }
  if( fossil_strcmp(zBaseline,"ckout")==0 && db_open_local()==0 ){
    sqlite3_snprintf(sizeof(zBaseline), zBaseline, "tip");
  }
  if( fossil_strcmp(zBaseline,"ckout")==0 ){
    /* Read from the local checkout */
    char *zFullpath;
    db_must_be_within_tree();
    zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
    if( !file_isfile(zFullpath) ){
      goto doc_not_found;
    }
    if( blob_read_from_file(&filebody, zFullpath)<0 ){
      goto doc_not_found;
    }
  }else{
    db_begin_transaction();
    if( fossil_strcmp(zBaseline,"tip")==0 ){
      vid = db_int(0, "SELECT objid FROM event WHERE type='ci'"
                      " ORDER BY mtime DESC LIMIT 1");
    }else{
      vid = name_to_typed_rid(zBaseline, "ci");
    }

    /* Create the baseline cache if it does not already exist */
    db_multi_exec(
      "CREATE TABLE IF NOT EXISTS vcache(\n"
      "  vid INTEGER,         -- baseline ID\n"
      "  fname TEXT,          -- filename\n"
      "  rid INTEGER,         -- artifact ID\n"
      "  UNIQUE(vid,fname,rid)\n"
      ")"
    );



    /* Check to see if the documentation file artifact ID is contained
    ** in the baseline cache */
    rid = db_int(0, "SELECT rid FROM vcache"
                    " WHERE vid=%d AND fname=%Q", vid, zName);
    if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
      goto doc_not_found;
    }

    if( rid==0 ){
      Stmt s;
      Manifest *pM;
      ManifestFile *pFile;

      /* Add the vid baseline to the cache */
      if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){
        db_multi_exec("DELETE FROM vcache");
      }
      pM = manifest_get(vid, CFTYPE_MANIFEST);
      if( pM==0 ){
        goto doc_not_found;
      }
      db_prepare(&s,
        "INSERT INTO vcache(vid,fname,rid)"
        " SELECT %d, :fname, rid FROM blob"
        "  WHERE uuid=:uuid",
        vid
      );
      manifest_file_rewind(pM);
      while( (pFile = manifest_file_next(pM,0))!=0 ){
        db_bind_text(&s, ":fname", pFile->zName);
        db_bind_text(&s, ":uuid", pFile->zUuid);
        db_step(&s);
        db_reset(&s);
      }
      db_finalize(&s);
      manifest_destroy(pM);

      /* Try again to find the file */
      rid = db_int(0, "SELECT rid FROM vcache"
                      " WHERE vid=%d AND fname=%Q", vid, zName);
    }
    if( rid==0 ){
      goto doc_not_found;
    }

    /* Get the file content */
    if( content_get(rid, &filebody)==0 ){
      goto doc_not_found;
    }
    db_end_transaction(0);
  }

  /* The file is now contained in the filebody blob.  Deliver the
  ** file to the user 
  */
  zMime = P("mimetype");
  if( zMime==0 ){
    zMime = mimetype_from_name(zName);
  }
  Th_Store("doc_name", zName);
  Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
                                     "  FROM blob WHERE rid=%d", vid));
  Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
                                  " WHERE objid=%d AND type='ci'", vid));
  if( fossil_strcmp(zMime, "application/x-fossil-wiki")==0 ){
    Blob title, tail;
    if( wiki_find_title(&filebody, &title, &tail) ){
      style_header(blob_str(&title));
      wiki_convert(&tail, 0, 0);
    }else{
      style_header("Documentation");
      wiki_convert(&filebody, 0, 0);
    }
    style_footer();
  }else if( fossil_strcmp(zMime, "text/plain")==0 ){
    style_header("Documentation");
    @ <blockquote><pre>
コード例 #11
0
ファイル: update.c プロジェクト: digsrc/fossil
/*
** COMMAND: update
**
** Usage: %fossil update ?OPTIONS? ?VERSION? ?FILES...?
**
** Change the version of the current checkout to VERSION.  Any
** uncommitted changes are retained and applied to the new checkout.
**
** The VERSION argument can be a specific version or tag or branch
** name.  If the VERSION argument is omitted, then the leaf of the
** subtree that begins at the current version is used, if there is
** only a single leaf.  VERSION can also be "current" to select the
** leaf of the current version or "latest" to select the most recent
** check-in.
**
** If one or more FILES are listed after the VERSION then only the
** named files are candidates to be updated, and any updates to them
** will be treated as edits to the current version. Using a directory
** name for one of the FILES arguments is the same as using every
** subdirectory and file beneath that directory.
**
** If FILES is omitted, all files in the current checkout are subject
** to being updated and the version of the current checkout is changed
** to VERSION. Any uncommitted changes are retained and applied to the
** new checkout.
**
** The -n or --dry-run option causes this command to do a "dry run".
** It prints out what would have happened but does not actually make
** any changes to the current checkout or the repository.
**
** The -v or --verbose option prints status information about
** unchanged files in addition to those file that actually do change.
**
** Options:
**   --case-sensitive <BOOL> override case-sensitive setting
**   --debug          print debug information on stdout
**   --latest         acceptable in place of VERSION, update to latest version
**   --force-missing  force update if missing content after sync
**   -n|--dry-run     If given, display instead of run actions
**   -v|--verbose     print status information about all files
**   -W|--width <num> Width of lines (default is to auto-detect). Must be >20
**                    or 0 (= no limit, resulting in a single line per entry).
**
** See also: revert
*/
void update_cmd(void) {
    int vid;              /* Current version */
    int tid=0;            /* Target version - version we are changing to */
    Stmt q;
    int latestFlag;       /* --latest.  Pick the latest version if true */
    int dryRunFlag;       /* -n or --dry-run.  Do a dry run */
    int verboseFlag;      /* -v or --verbose.  Output extra information */
    int forceMissingFlag; /* --force-missing.  Continue if missing content */
    int debugFlag;        /* --debug option */
    int setmtimeFlag;     /* --setmtime.  Set mtimes on files */
    int nChng;            /* Number of file renames */
    int *aChng;           /* Array of file renames */
    int i;                /* Loop counter */
    int nConflict = 0;    /* Number of merge conflicts */
    int nOverwrite = 0;   /* Number of unmanaged files overwritten */
    int nUpdate = 0;      /* Number of changes of any kind */
    int width;            /* Width of printed comment lines */
    Stmt mtimeXfer;       /* Statement to transfer mtimes */
    const char *zWidth;   /* Width option string value */

    if( !internalUpdate ) {
        undo_capture_command_line();
        url_proxy_options();
    }
    zWidth = find_option("width","W",1);
    if( zWidth ) {
        width = atoi(zWidth);
        if( (width!=0) && (width<=20) ) {
            fossil_fatal("-W|--width value must be >20 or 0");
        }
    } else {
        width = -1;
    }
    latestFlag = find_option("latest",0, 0)!=0;
    dryRunFlag = find_option("dry-run","n",0)!=0;
    if( !dryRunFlag ) {
        dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */
    }
    verboseFlag = find_option("verbose","v",0)!=0;
    forceMissingFlag = find_option("force-missing",0,0)!=0;
    debugFlag = find_option("debug",0,0)!=0;
    setmtimeFlag = find_option("setmtime",0,0)!=0;

    /* We should be done with options.. */
    verify_all_options();

    db_must_be_within_tree();
    vid = db_lget_int("checkout", 0);
    user_select();
    if( !dryRunFlag && !internalUpdate ) {
        if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag,
                          db_get_int("autosync-tries", 1)) ) {
            fossil_fatal("Cannot proceed with update");
        }
    }

    /* Create any empty directories now, as well as after the update,
    ** so changes in settings are reflected now */
    if( !dryRunFlag ) ensure_empty_dirs_created();

    if( internalUpdate ) {
        tid = internalUpdate;
    } else if( g.argc>=3 ) {
        if( fossil_strcmp(g.argv[2], "current")==0 ) {
            /* If VERSION is "current", then use the same algorithm to find the
            ** target as if VERSION were omitted. */
        } else if( fossil_strcmp(g.argv[2], "latest")==0 ) {
            /* If VERSION is "latest", then use the same algorithm to find the
            ** target as if VERSION were omitted and the --latest flag is present.
            */
            latestFlag = 1;
        } else {
            tid = name_to_typed_rid(g.argv[2],"ci");
            if( tid==0 || !is_a_version(tid) ) {
                fossil_fatal("no such check-in: %s", g.argv[2]);
            }
        }
    }

    /* If no VERSION is specified on the command-line, then look for a
    ** descendent of the current version.  If there are multiple descendants,
    ** look for one from the same branch as the current version.  If there
    ** are still multiple descendants, show them all and refuse to update
    ** until the user selects one.
    */
    if( tid==0 ) {
        int closeCode = 1;
        compute_leaves(vid, closeCode);
        if( !db_exists("SELECT 1 FROM leaves") ) {
            closeCode = 0;
            compute_leaves(vid, closeCode);
        }
        if( !latestFlag && db_int(0, "SELECT count(*) FROM leaves")>1 ) {
            db_multi_exec(
                "DELETE FROM leaves WHERE rid NOT IN"
                "   (SELECT leaves.rid FROM leaves, tagxref"
                "     WHERE leaves.rid=tagxref.rid AND tagxref.tagid=%d"
                "       AND tagxref.value==(SELECT value FROM tagxref"
                " WHERE tagid=%d AND rid=%d))",
                TAG_BRANCH, TAG_BRANCH, vid
            );
            if( db_int(0, "SELECT count(*) FROM leaves")>1 ) {
                compute_leaves(vid, closeCode);
                db_prepare(&q,
                           "%s "
                           "   AND event.objid IN leaves"
                           " ORDER BY event.mtime DESC",
                           timeline_query_for_tty()
                          );
                print_timeline(&q, -100, width, 0);
                db_finalize(&q);
                fossil_fatal("Multiple descendants");
            }
        }
        tid = db_int(0, "SELECT rid FROM leaves, event"
                     " WHERE event.objid=leaves.rid"
                     " ORDER BY event.mtime DESC");
        if( tid==0 ) tid = vid;
    }

    if( tid==0 ) {
        return;
    }

    db_begin_transaction();
    vfile_check_signature(vid, CKSIG_ENOTFILE);
    if( !dryRunFlag && !internalUpdate ) undo_begin();
    if( load_vfile_from_rid(tid) && !forceMissingFlag ) {
        fossil_fatal("missing content, unable to update");
    };

    /*
    ** The record.fn field is used to match files against each other.  The
    ** FV table contains one row for each each unique filename in
    ** in the current checkout, the pivot, and the version being merged.
    */
    db_multi_exec(
        "DROP TABLE IF EXISTS fv;"
        "CREATE TEMP TABLE fv("
        "  fn TEXT %s PRIMARY KEY,"   /* The filename relative to root */
        "  idv INTEGER,"              /* VFILE entry for current version */
        "  idt INTEGER,"              /* VFILE entry for target version */
        "  chnged BOOLEAN,"           /* True if current version has been edited */
        "  islinkv BOOLEAN,"          /* True if current file is a link */
        "  islinkt BOOLEAN,"          /* True if target file is a link */
        "  ridv INTEGER,"             /* Record ID for current version */
        "  ridt INTEGER,"             /* Record ID for target */
        "  isexe BOOLEAN,"            /* Does target have execute permission? */
        "  deleted BOOLEAN DEFAULT 0,"/* File marked by "rm" to become unmanaged */
        "  fnt TEXT %s"               /* Filename of same file on target version */
        ");",
        filename_collation(), filename_collation()
    );

    /* Add files found in the current version
    */
    db_multi_exec(
        "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged,deleted)"
        " SELECT pathname, pathname, id, 0, rid, 0, isexe, chnged, deleted"
        "   FROM vfile WHERE vid=%d",
        vid
    );

    /* Compute file name changes on V->T.  Record name changes in files that
    ** have changed locally.
    */
    if( vid ) {
        find_filename_changes(vid, tid, 1, &nChng, &aChng, debugFlag ? "V->T": 0);
        if( nChng ) {
            for(i=0; i<nChng; i++) {
                db_multi_exec(
                    "UPDATE fv"
                    "   SET fnt=(SELECT name FROM filename WHERE fnid=%d)"
                    " WHERE fn=(SELECT name FROM filename WHERE fnid=%d) AND chnged",
                    aChng[i*2+1], aChng[i*2]
                );
            }
            fossil_free(aChng);
        }
    }

    /* Add files found in the target version T but missing from the current
    ** version V.
    */
    db_multi_exec(
        "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged)"
        " SELECT pathname, pathname, 0, 0, 0, 0, isexe, 0 FROM vfile"
        "  WHERE vid=%d"
        "    AND pathname %s NOT IN (SELECT fnt FROM fv)",
        tid, filename_collation()
    );

    /*
    ** Compute the file version ids for T
    */
    db_multi_exec(
        "UPDATE fv SET"
        " idt=coalesce((SELECT id FROM vfile WHERE vid=%d AND fnt=pathname),0),"
        " ridt=coalesce((SELECT rid FROM vfile WHERE vid=%d AND fnt=pathname),0)",
        tid, tid
    );

    /*
    ** Add islink information
    */
    db_multi_exec(
        "UPDATE fv SET"
        " islinkv=coalesce((SELECT islink FROM vfile"
        " WHERE vid=%d AND fnt=pathname),0),"
        " islinkt=coalesce((SELECT islink FROM vfile"
        " WHERE vid=%d AND fnt=pathname),0)",
        vid, tid
    );


    if( debugFlag ) {
        db_prepare(&q,
                   "SELECT rowid, fn, fnt, chnged, ridv, ridt, isexe,"
                   "       islinkv, islinkt FROM fv"
                  );
        while( db_step(&q)==SQLITE_ROW ) {
            fossil_print("%3d: ridv=%-4d ridt=%-4d chnged=%d isexe=%d"
                         " islinkv=%d  islinkt=%d\n",
                         db_column_int(&q, 0),
                         db_column_int(&q, 4),
                         db_column_int(&q, 5),
                         db_column_int(&q, 3),
                         db_column_int(&q, 6),
                         db_column_int(&q, 7),
                         db_column_int(&q, 8));
            fossil_print("     fnv = [%s]\n", db_column_text(&q, 1));
            fossil_print("     fnt = [%s]\n", db_column_text(&q, 2));
        }
        db_finalize(&q);
    }

    /* If FILES appear on the command-line, remove from the "fv" table
    ** every entry that is not named on the command-line or which is not
    ** in a directory named on the command-line.
    */
    if( g.argc>=4 ) {
        Blob sql;              /* SQL statement to purge unwanted entries */
        Blob treename;         /* Normalized filename */
        int i;                 /* Loop counter */
        const char *zSep;      /* Term separator */

        blob_zero(&sql);
        blob_append(&sql, "DELETE FROM fv WHERE ", -1);
        zSep = "";
        for(i=3; i<g.argc; i++) {
            file_tree_name(g.argv[i], &treename, 0, 1);
            if( file_wd_isdir(g.argv[i])==1 ) {
                if( blob_size(&treename) != 1 || blob_str(&treename)[0] != '.' ) {
                    blob_append_sql(&sql, "%sfn NOT GLOB '%q/*' ",
                                    zSep /*safe-for-%s*/, blob_str(&treename));
                } else {
                    blob_reset(&sql);
                    break;
                }
            } else {
                blob_append_sql(&sql, "%sfn<>%Q ",
                                zSep /*safe-for-%s*/, blob_str(&treename));
            }
            zSep = "AND ";
            blob_reset(&treename);
        }
        db_multi_exec("%s", blob_sql_text(&sql));
        blob_reset(&sql);
    }

    /*
    ** Alter the content of the checkout so that it conforms with the
    ** target
    */
    db_prepare(&q,
               "SELECT fn, idv, ridv, idt, ridt, chnged, fnt,"
               "       isexe, islinkv, islinkt, deleted FROM fv ORDER BY 1"
              );
    db_prepare(&mtimeXfer,
               "UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)"
               " WHERE id=:idt"
              );
    assert( g.zLocalRoot!=0 );
    assert( strlen(g.zLocalRoot)>0 );
    assert( g.zLocalRoot[strlen(g.zLocalRoot)-1]=='/' );
    while( db_step(&q)==SQLITE_ROW ) {
        const char *zName = db_column_text(&q, 0);  /* The filename from root */
        int idv = db_column_int(&q, 1);             /* VFILE entry for current */
        int ridv = db_column_int(&q, 2);            /* RecordID for current */
        int idt = db_column_int(&q, 3);             /* VFILE entry for target */
        int ridt = db_column_int(&q, 4);            /* RecordID for target */
        int chnged = db_column_int(&q, 5);          /* Current is edited */
        const char *zNewName = db_column_text(&q,6);/* New filename */
        int isexe = db_column_int(&q, 7);           /* EXE perm for new file */
        int islinkv = db_column_int(&q, 8);         /* Is current file is a link */
        int islinkt = db_column_int(&q, 9);         /* Is target file is a link */
        int deleted = db_column_int(&q, 10);        /* Marked for deletion */
        char *zFullPath;                            /* Full pathname of the file */
        char *zFullNewPath;                         /* Full pathname of dest */
        char nameChng;                              /* True if the name changed */

        zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
        zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName);
        nameChng = fossil_strcmp(zName, zNewName);
        nUpdate++;
        if( deleted ) {
            db_multi_exec("UPDATE vfile SET deleted=1 WHERE id=%d", idt);
        }
        if( idv>0 && ridv==0 && idt>0 && ridt>0 ) {
            /* Conflict.  This file has been added to the current checkout
            ** but also exists in the target checkout.  Use the current version.
            */
            fossil_print("CONFLICT %s\n", zName);
            nConflict++;
        } else if( idt>0 && idv==0 ) {
            /* File added in the target. */
            if( file_wd_isfile_or_link(zFullPath) ) {
                fossil_print("ADD %s - overwrites an unmanaged file\n", zName);
                nOverwrite++;
            } else {
                fossil_print("ADD %s\n", zName);
            }
            if( !dryRunFlag && !internalUpdate ) undo_save(zName);
            if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
        } else if( idt>0 && idv>0 && ridt!=ridv && (chnged==0 || deleted) ) {
            /* The file is unedited.  Change it to the target version */
            if( deleted ) {
                fossil_print("UPDATE %s - change to unmanaged file\n", zName);
            } else {
                fossil_print("UPDATE %s\n", zName);
            }
            if( !dryRunFlag && !internalUpdate ) undo_save(zName);
            if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
        } else if( idt>0 && idv>0 && !deleted && file_wd_size(zFullPath)<0 ) {
            /* The file missing from the local check-out. Restore it to the
            ** version that appears in the target. */
            fossil_print("UPDATE %s\n", zName);
            if( !dryRunFlag && !internalUpdate ) undo_save(zName);
            if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
        } else if( idt==0 && idv>0 ) {
            if( ridv==0 ) {
                /* Added in current checkout.  Continue to hold the file as
                ** as an addition */
                db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv);
            } else if( chnged ) {
                /* Edited locally but deleted from the target.  Do not track the
                ** file but keep the edited version around. */
                fossil_print("CONFLICT %s - edited locally but deleted by update\n",
                             zName);
                nConflict++;
            } else {
                fossil_print("REMOVE %s\n", zName);
                if( !dryRunFlag && !internalUpdate ) undo_save(zName);
                if( !dryRunFlag ) file_delete(zFullPath);
            }
        } else if( idt>0 && idv>0 && ridt!=ridv && chnged ) {
            /* Merge the changes in the current tree into the target version */
            Blob r, t, v;
            int rc;
            if( nameChng ) {
                fossil_print("MERGE %s -> %s\n", zName, zNewName);
            } else {
                fossil_print("MERGE %s\n", zName);
            }
            if( islinkv || islinkt /* || file_wd_islink(zFullPath) */ ) {
                fossil_print("***** Cannot merge symlink %s\n", zNewName);
                nConflict++;
            } else {
                unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0;
                if( !dryRunFlag && !internalUpdate ) undo_save(zName);
                content_get(ridt, &t);
                content_get(ridv, &v);
                rc = merge_3way(&v, zFullPath, &t, &r, mergeFlags);
                if( rc>=0 ) {
                    if( !dryRunFlag ) {
                        blob_write_to_file(&r, zFullNewPath);
                        file_wd_setexe(zFullNewPath, isexe);
                    }
                    if( rc>0 ) {
                        fossil_print("***** %d merge conflicts in %s\n", rc, zNewName);
                        nConflict++;
                    }
                } else {
                    if( !dryRunFlag ) {
                        blob_write_to_file(&t, zFullNewPath);
                        file_wd_setexe(zFullNewPath, isexe);
                    }
                    fossil_print("***** Cannot merge binary file %s\n", zNewName);
                    nConflict++;
                }
            }
            if( nameChng && !dryRunFlag ) file_delete(zFullPath);
            blob_reset(&v);
            blob_reset(&t);
            blob_reset(&r);
        } else {
            nUpdate--;
            if( chnged ) {
                if( verboseFlag ) fossil_print("EDITED %s\n", zName);
            } else {
                db_bind_int(&mtimeXfer, ":idv", idv);
                db_bind_int(&mtimeXfer, ":idt", idt);
                db_step(&mtimeXfer);
                db_reset(&mtimeXfer);
                if( verboseFlag ) fossil_print("UNCHANGED %s\n", zName);
            }
        }
        free(zFullPath);
        free(zFullNewPath);
    }
    db_finalize(&q);
    db_finalize(&mtimeXfer);
    fossil_print("%.79c\n",'-');
    if( nUpdate==0 ) {
        show_common_info(tid, "checkout:", 1, 0);
        fossil_print("%-13s None. Already up-to-date\n", "changes:");
    } else {
        show_common_info(tid, "updated-to:", 1, 0);
        fossil_print("%-13s %d file%s modified.\n", "changes:",
                     nUpdate, nUpdate>1 ? "s" : "");
    }

    /* Report on conflicts
    */
    if( !dryRunFlag ) {
        Stmt q;
        int nMerge = 0;
        db_prepare(&q, "SELECT uuid, id FROM vmerge JOIN blob ON merge=rid"
                   " WHERE id<=0");
        while( db_step(&q)==SQLITE_ROW ) {
            const char *zLabel = "merge";
            switch( db_column_int(&q, 1) ) {
            case -1:
                zLabel = "cherrypick merge";
                break;
            case -2:
                zLabel = "backout merge";
                break;
            }
            fossil_warning("uncommitted %s against %S.",
                           zLabel, db_column_text(&q, 0));
            nMerge++;
        }
        db_finalize(&q);
        leaf_ambiguity_warning(tid, tid);

        if( nConflict ) {
            if( internalUpdate ) {
                internalConflictCnt = nConflict;
                nConflict = 0;
            } else {
                fossil_warning("WARNING: %d merge conflicts", nConflict);
            }
        }
        if( nOverwrite ) {
            fossil_warning("WARNING: %d unmanaged files were overwritten",
                           nOverwrite);
        }
        if( nMerge ) {
            fossil_warning("WARNING: %d uncommitted prior merges", nMerge);
        }
    }

    /*
    ** Clean up the mid and pid VFILE entries.  Then commit the changes.
    */
    if( dryRunFlag ) {
        db_end_transaction(1);  /* With --dry-run, rollback changes */
    } else {
        ensure_empty_dirs_created();
        if( g.argc<=3 ) {
            /* All files updated.  Shift the current checkout to the target. */
            db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid);
            checkout_set_all_exe(tid);
            manifest_to_disk(tid);
            db_lset_int("checkout", tid);
        } else {
            /* A subset of files have been checked out.  Keep the current
            ** checkout unchanged. */
            db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
        }
        if( !internalUpdate ) undo_finish();
        if( setmtimeFlag ) vfile_check_signature(tid, CKSIG_SETMTIME);
        db_end_transaction(0);
    }
}