Example #1
0
File: finfo.c Project: oopos/fossil
/*
** WEBPAGE: finfo
** URL: /finfo?name=FILENAME
**
** Show the change history for a single file.
**
** Additional query parameters:
**
**    a=DATE     Only show changes after DATE
**    b=DATE     Only show changes before DATE
**    n=NUM      Show the first NUM changes only
**    brbg       Background color by branch name
**    ubg        Background color by user name
*/
void finfo_page(void) {
    Stmt q;
    const char *zFilename;
    char zPrevDate[20];
    const char *zA;
    const char *zB;
    int n;
    Blob title;
    Blob sql;
    GraphContext *pGraph;
    int brBg = P("brbg")!=0;
    int uBg = P("ubg")!=0;

    login_check_credentials();
    if( !g.perm.Read ) {
        login_needed();
        return;
    }
    style_header("File History");
    login_anonymous_available();

    zPrevDate[0] = 0;
    zFilename = PD("name","");
    blob_zero(&sql);
    blob_appendf(&sql,
                 "SELECT"
                 " datetime(event.mtime,'localtime'),"            /* Date of change */
                 " coalesce(event.ecomment, event.comment),"      /* Check-in comment */
                 " coalesce(event.euser, event.user),"            /* User who made chng */
                 " mlink.pid,"                                    /* Parent rid */
                 " mlink.fid,"                                    /* File rid */
                 " (SELECT uuid FROM blob WHERE rid=mlink.pid),"  /* Parent file uuid */
                 " (SELECT uuid FROM blob WHERE rid=mlink.fid),"  /* Current file uuid */
                 " (SELECT uuid FROM blob WHERE rid=mlink.mid),"  /* Check-in uuid */
                 " event.bgcolor,"                                /* Background color */
                 " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0"
                 " AND tagxref.rid=mlink.mid)" /* Tags */
                 "  FROM mlink, event"
                 " WHERE mlink.fnid IN (SELECT fnid FROM filename WHERE name=%Q %s)"
                 "   AND event.objid=mlink.mid",
                 TAG_BRANCH,
                 zFilename, filename_collation()
                );
    if( (zA = P("a"))!=0 ) {
        blob_appendf(&sql, " AND event.mtime>=julianday('%q')", zA);
    }
    if( (zB = P("b"))!=0 ) {
        blob_appendf(&sql, " AND event.mtime<=julianday('%q')", zB);
    }
    blob_appendf(&sql," ORDER BY event.mtime DESC /*sort*/");
    if( (n = atoi(PD("n","0")))>0 ) {
        blob_appendf(&sql, " LIMIT %d", n);
    }
    db_prepare(&q, blob_str(&sql));
    blob_reset(&sql);
    blob_zero(&title);
    blob_appendf(&title, "History of ");
    hyperlinked_path(zFilename, &title, 0);
    @ <h2>%b(&title)</h2>
Example #2
0
/*
** 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);
}
Example #3
0
/*
** 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);
    }
}
Example #4
0
/*
** Scans the specified base directory for any directories within it, while
** keeping a count of how many files they each contains, either directly or
** indirectly.
**
** Subdirectories are scanned recursively.
** Omit files named in VFILE.
**
** Directories whose names begin with "." are omitted unless the SCAN_ALL
** flag is set.
**
** Any directories that match the glob patterns pIgnore* are excluded from
** the scan.  Name matching occurs after the first nPrefix characters are
** elided from the filename.
**
** Returns the total number of files found.
*/
int vfile_dir_scan(
  Blob *pPath,           /* Base directory to be scanned */
  int nPrefix,           /* Number of bytes in base directory name */
  unsigned scanFlags,    /* Zero or more SCAN_xxx flags */
  Glob *pIgnore1,        /* Do not add directories that match this GLOB */
  Glob *pIgnore2,        /* Omit directories matching this GLOB too */
  Glob *pIgnore3         /* Omit directories matching this GLOB too */
){
  int result = 0;
  DIR *d;
  int origSize;
  struct dirent *pEntry;
  int skipAll = 0;
  static Stmt ins;
  static Stmt upd;
  static int depth = 0;
  void *zNative;

  origSize = blob_size(pPath);
  if( pIgnore1 || pIgnore2 || pIgnore3 ){
    blob_appendf(pPath, "/");
    if( glob_match(pIgnore1, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1;
    if( glob_match(pIgnore2, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1;
    if( glob_match(pIgnore3, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1;
    blob_resize(pPath, origSize);
  }
  if( skipAll ) return result;

  if( depth==0 ){
    db_multi_exec("DROP TABLE IF EXISTS dscan_temp;"
                  "CREATE TEMP TABLE dscan_temp("
                  "  x TEXT PRIMARY KEY %s, y INTEGER)",
                  filename_collation());
    db_prepare(&ins,
       "INSERT OR IGNORE INTO dscan_temp(x, y) SELECT :file, :count"
       "  WHERE NOT EXISTS(SELECT 1 FROM vfile WHERE"
       " pathname GLOB :file || '/*' %s)", filename_collation()
    );
    db_prepare(&upd,
       "UPDATE OR IGNORE dscan_temp SET y = coalesce(y, 0) + 1"
       "  WHERE x=:file %s",
       filename_collation()
    );
  }
  depth++;

  zNative = fossil_utf8_to_filename(blob_str(pPath));
  d = opendir(zNative);
  if( d ){
    while( (pEntry=readdir(d))!=0 ){
      char *zOrigPath;
      char *zPath;
      char *zUtf8;
      if( pEntry->d_name[0]=='.' ){
        if( (scanFlags & SCAN_ALL)==0 ) continue;
        if( pEntry->d_name[1]==0 ) continue;
        if( pEntry->d_name[1]=='.' && pEntry->d_name[2]==0 ) continue;
      }
      zOrigPath = mprintf("%s", blob_str(pPath));
      zUtf8 = fossil_filename_to_utf8(pEntry->d_name);
      blob_appendf(pPath, "/%s", zUtf8);
      zPath = blob_str(pPath);
      if( glob_match(pIgnore1, &zPath[nPrefix+1]) ||
          glob_match(pIgnore2, &zPath[nPrefix+1]) ||
          glob_match(pIgnore3, &zPath[nPrefix+1]) ){
        /* do nothing */
      }else if( file_wd_isdir(zPath)==1 ){
        if( (scanFlags & SCAN_NESTED) || !vfile_top_of_checkout(zPath) ){
          char *zSavePath = mprintf("%s", zPath);
          int count = vfile_dir_scan(pPath, nPrefix, scanFlags, pIgnore1,
                                     pIgnore2, pIgnore3);
          db_bind_text(&ins, ":file", &zSavePath[nPrefix+1]);
          db_bind_int(&ins, ":count", count);
          db_step(&ins);
          db_reset(&ins);
          fossil_free(zSavePath);
          result += count; /* found X normal files? */
        }
      }else if( file_wd_isfile_or_link(zPath) ){
        db_bind_text(&upd, ":file", zOrigPath);
        db_step(&upd);
        db_reset(&upd);
        result++; /* found 1 normal file */
      }
      fossil_filename_free(zUtf8);
      blob_resize(pPath, origSize);
      fossil_free(zOrigPath);
    }
    closedir(d);
  }
  fossil_filename_free(zNative);

  depth--;
  if( depth==0 ){
    db_finalize(&upd);
    db_finalize(&ins);
  }
  return result;
}
Example #5
0
/*
** Load into table SFILE the name of every ordinary file in
** the directory pPath.   Omit the first nPrefix characters of
** of pPath when inserting into the SFILE table.
**
** Subdirectories are scanned recursively.
** Omit files named in VFILE.
**
** Files whose names begin with "." are omitted unless the SCAN_ALL
** flag is set.
**
** Any files or directories that match the glob patterns pIgnore*
** are excluded from the scan.  Name matching occurs after the
** first nPrefix characters are elided from the filename.
*/
void vfile_scan(
  Blob *pPath,           /* Directory to be scanned */
  int nPrefix,           /* Number of bytes in directory name */
  unsigned scanFlags,    /* Zero or more SCAN_xxx flags */
  Glob *pIgnore1,        /* Do not add files that match this GLOB */
  Glob *pIgnore2         /* Omit files matching this GLOB too */
){
  DIR *d;
  int origSize;
  struct dirent *pEntry;
  int skipAll = 0;
  static Stmt ins;
  static int depth = 0;
  void *zNative;

  origSize = blob_size(pPath);
  if( pIgnore1 || pIgnore2 ){
    blob_appendf(pPath, "/");
    if( glob_match(pIgnore1, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1;
    if( glob_match(pIgnore2, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1;
    blob_resize(pPath, origSize);
  }
  if( skipAll ) return;

  if( depth==0 ){
    db_prepare(&ins,
       "INSERT OR IGNORE INTO sfile(x) SELECT :file"
       "  WHERE NOT EXISTS(SELECT 1 FROM vfile WHERE"
       " pathname=:file %s)", filename_collation()
    );
  }
  depth++;

  zNative = fossil_utf8_to_filename(blob_str(pPath));
  d = opendir(zNative);
  if( d ){
    while( (pEntry=readdir(d))!=0 ){
      char *zPath;
      char *zUtf8;
      if( pEntry->d_name[0]=='.' ){
        if( (scanFlags & SCAN_ALL)==0 ) continue;
        if( pEntry->d_name[1]==0 ) continue;
        if( pEntry->d_name[1]=='.' && pEntry->d_name[2]==0 ) continue;
      }
      zUtf8 = fossil_filename_to_utf8(pEntry->d_name);
      blob_appendf(pPath, "/%s", zUtf8);
      zPath = blob_str(pPath);
      if( glob_match(pIgnore1, &zPath[nPrefix+1]) ||
          glob_match(pIgnore2, &zPath[nPrefix+1]) ){
        /* do nothing */
      }else if( file_wd_isdir(zPath)==1 ){
        if( !vfile_top_of_checkout(zPath) ){
          vfile_scan(pPath, nPrefix, scanFlags, pIgnore1, pIgnore2);
        }
      }else if( file_wd_isfile_or_link(zPath) ){
        if( (scanFlags & SCAN_TEMP)==0 || is_temporary_file(zUtf8) ){
          db_bind_text(&ins, ":file", &zPath[nPrefix+1]);
          db_step(&ins);
          db_reset(&ins);
        }
      }
      fossil_filename_free(zUtf8);
      blob_resize(pPath, origSize);
    }
    closedir(d);
  }
  fossil_filename_free(zNative);

  depth--;
  if( depth==0 ){
    db_finalize(&ins);
  }
}
Example #6
0
/*
** COMMAND: finfo
**
** Usage: %fossil finfo ?OPTIONS? FILENAME
**
** Print the complete change history for a single file going backwards
** in time.  The default mode is -l.
**
** For the -l|--log mode: If "-b|--brief" is specified one line per revision
** is printed, otherwise the full comment is printed.  The "-n|--limit N"
** and "--offset P" options limits the output to the first N changes
** after skipping P changes.
**
** In the -s mode prints the status as <status> <revision>.  This is
** a quick status and does not check for up-to-date-ness of the file.
**
** In the -p mode, there's an optional flag "-r|--revision REVISION".
** The specified version (or the latest checked out version) is printed
** to stdout.  The -p mode is another form of the "cat" command.
**
** Options:
**   -b|--brief           display a brief (one line / revision) summary
**   --case-sensitive B   Enable or disable case-sensitive filenames.  B is a
**                        boolean: "yes", "no", "true", "false", etc.
**   -l|--log             select log mode (the default)
**   -n|--limit N         Display the first N changes (default unlimited).
**                        N<=0 means no limit.
**   --offset P           skip P changes
**   -p|--print           select print mode
**   -r|--revision R      print the given revision (or ckout, if none is given)
**                        to stdout (only in print mode)
**   -s|--status          select status mode (print a status indicator for FILE)
**   -W|--width <num>     Width of lines (default is to auto-detect). Must be
**                        >22 or 0 (= no limit, resulting in a single line per
**                        entry).
**
** See also: artifact, cat, descendants, info, leaves
*/
void finfo_cmd(void){
  db_must_be_within_tree();
  if( find_option("status","s",0) ){
    Stmt q;
    Blob line;
    Blob fname;
    int vid;

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

    if( g.argc!=3 ) usage("-s|--status FILENAME");
    vid = db_lget_int("checkout", 0);
    if( vid==0 ){
      fossil_fatal("no checkout to finfo files in");
    }
    vfile_check_signature(vid, CKSIG_ENOTFILE);
    file_tree_name(g.argv[2], &fname, 0, 1);
    db_prepare(&q,
        "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)"
        "  FROM vfile WHERE vfile.pathname=%B %s",
        &fname, filename_collation());
    blob_zero(&line);
    if( db_step(&q)==SQLITE_ROW ) {
      Blob uuid;
      int isDeleted = db_column_int(&q, 1);
      int isNew = db_column_int(&q,2) == 0;
      int chnged = db_column_int(&q,3);
      int renamed = db_column_int(&q,4);

      blob_zero(&uuid);
      db_blob(&uuid,
           "SELECT uuid FROM blob, mlink, vfile WHERE "
           "blob.rid = mlink.mid AND mlink.fid = vfile.rid AND "
           "vfile.pathname=%B %s",
           &fname, filename_collation()
      );
      if( isNew ){
        blob_appendf(&line, "new");
      }else if( isDeleted ){
        blob_appendf(&line, "deleted");
      }else if( renamed ){
        blob_appendf(&line, "renamed");
      }else if( chnged ){
        blob_appendf(&line, "edited");
      }else{
        blob_appendf(&line, "unchanged");
      }
      blob_appendf(&line, " ");
      blob_appendf(&line, " %10.10s", blob_str(&uuid));
      blob_reset(&uuid);
    }else{
      blob_appendf(&line, "unknown 0000000000");
    }
    db_finalize(&q);
    fossil_print("%s\n", blob_str(&line));
    blob_reset(&fname);
    blob_reset(&line);
  }else if( find_option("print","p",0) ){
    Blob record;
    Blob fname;
    const char *zRevision = find_option("revision", "r", 1);

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

    file_tree_name(g.argv[2], &fname, 0, 1);
    if( zRevision ){
      historical_version_of_file(zRevision, blob_str(&fname), &record, 0,0,0,0);
    }else{
      int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s",
                       &fname, filename_collation());
      if( rid==0 ){
        fossil_fatal("no history for file: %b", &fname);
      }
      content_get(rid, &record);
    }
    blob_write_to_file(&record, "-");
    blob_reset(&record);
    blob_reset(&fname);
  }else{
    Blob line;
    Stmt q;
    Blob fname;
    int rid;
    const char *zFilename;
    const char *zLimit;
    const char *zWidth;
    const char *zOffset;
    int iLimit, iOffset, iBrief, iWidth;

    if( find_option("log","l",0) ){
      /* this is the default, no-op */
    }
    zLimit = find_option("limit","n",1);
    zWidth = find_option("width","W",1);
    iLimit = zLimit ? atoi(zLimit) : -1;
    zOffset = find_option("offset",0,1);
    iOffset = zOffset ? atoi(zOffset) : 0;
    iBrief = (find_option("brief","b",0) == 0);
    if( iLimit==0 ){
      iLimit = -1;
    }
    if( zWidth ){
      iWidth = atoi(zWidth);
      if( (iWidth!=0) && (iWidth<=22) ){
        fossil_fatal("-W|--width value must be >22 or 0");
      }
    }else{
      iWidth = -1;
    }

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

    if( g.argc!=3 ){
      usage("?-l|--log? ?-b|--brief? FILENAME");
    }
    file_tree_name(g.argv[2], &fname, 0, 1);
    rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s",
                 &fname, filename_collation());
    if( rid==0 ){
      fossil_fatal("no history for file: %b", &fname);
    }
    zFilename = blob_str(&fname);
    db_prepare(&q,
        "SELECT DISTINCT b.uuid, ci.uuid, date(event.mtime%s),"
        "       coalesce(event.ecomment, event.comment),"
        "       coalesce(event.euser, event.user),"
        "       (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0"
                                " AND tagxref.rid=mlink.mid)" /* Tags */
        "  FROM mlink, blob b, event, blob ci, filename"
        " WHERE filename.name=%Q %s"
        "   AND mlink.fnid=filename.fnid"
        "   AND b.rid=mlink.fid"
        "   AND event.objid=mlink.mid"
        "   AND event.objid=ci.rid"
        " ORDER BY event.mtime DESC LIMIT %d OFFSET %d",
        timeline_utc(), TAG_BRANCH, zFilename, filename_collation(),
        iLimit, iOffset
    );
    blob_zero(&line);
    if( iBrief ){
      fossil_print("History of %s\n", blob_str(&fname));
    }
    while( db_step(&q)==SQLITE_ROW ){
      const char *zFileUuid = db_column_text(&q, 0);
      const char *zCiUuid = db_column_text(&q,1);
      const char *zDate = db_column_text(&q, 2);
      const char *zCom = db_column_text(&q, 3);
      const char *zUser = db_column_text(&q, 4);
      const char *zBr = db_column_text(&q, 5);
      char *zOut;
      if( zBr==0 ) zBr = "trunk";
      if( iBrief ){
        fossil_print("%s ", zDate);
        zOut = mprintf(
           "[%S] %s (user: %s, artifact: [%S], branch: %s)",
           zCiUuid, zCom, zUser, zFileUuid, zBr);
        comment_print(zOut, zCom, 11, iWidth, g.comFmtFlags);
        fossil_free(zOut);
      }else{
        blob_reset(&line);
        blob_appendf(&line, "%S ", zCiUuid);
        blob_appendf(&line, "%.10s ", zDate);
        blob_appendf(&line, "%8.8s ", zUser);
        blob_appendf(&line, "%8.8s ", zBr);
        blob_appendf(&line,"%-39.39s", zCom );
        comment_print(blob_str(&line), zCom, 0, iWidth, g.comFmtFlags);
      }
    }
    db_finalize(&q);
    blob_reset(&fname);
  }
}
Example #7
0
File: finfo.c Project: oopos/fossil
/*
** COMMAND: finfo
**
** Usage: %fossil finfo ?OPTIONS? FILENAME
**
** Print the complete change history for a single file going backwards
** in time.  The default is -l.
**
** For the -l|--log option: If "-b|--brief" is specified one line per revision
** is printed, otherwise the full comment is printed.  The "--limit N"
** and "--offset P" options limits the output to the first N changes
** after skipping P changes.
**
** In the -s form prints the status as <status> <revision>.  This is
** a quick status and does not check for up-to-date-ness of the file.
**
** In the -p form, there's an optional flag "-r|--revision REVISION".
** The specified version (or the latest checked out version) is printed
** to stdout.
**
** Options:
**   --brief|-b           display a brief (one line / revision) summary
**   --limit N            display the first N changes
**   --log|-l             select log mode (the default)
**   --offset P           skip P changes
**   -p                   select print mode
**   --revision|-r R      print the given revision (or ckout, if none is given)
**                        to stdout (only in print mode)
**   -s                   select status mode (print a status indicator for FILE)
**   --case-sensitive B   Enable or disable case-sensitive filenames.  B is a
**                        boolean: "yes", "no", "true", "false", etc.
**
** See also: artifact, descendants, info, leaves
*/
void finfo_cmd(void) {
    capture_case_sensitive_option();
    db_must_be_within_tree();
    if (find_option("status","s",0)) {
        Stmt q;
        Blob line;
        Blob fname;
        int vid;

        if( g.argc!=3 ) usage("-s|--status FILENAME");
        vid = db_lget_int("checkout", 0);
        if( vid==0 ) {
            fossil_panic("no checkout to finfo files in");
        }
        vfile_check_signature(vid, 1, 0);
        file_tree_name(g.argv[2], &fname, 1);
        db_prepare(&q,
                   "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)"
                   "  FROM vfile WHERE vfile.pathname=%B %s",
                   &fname, filename_collation());
        blob_zero(&line);
        if ( db_step(&q)==SQLITE_ROW ) {
            Blob uuid;
            int isDeleted = db_column_int(&q, 1);
            int isNew = db_column_int(&q,2) == 0;
            int chnged = db_column_int(&q,3);
            int renamed = db_column_int(&q,4);

            blob_zero(&uuid);
            db_blob(&uuid,
                    "SELECT uuid FROM blob, mlink, vfile WHERE "
                    "blob.rid = mlink.mid AND mlink.fid = vfile.rid AND "
                    "vfile.pathname=%B %s",
                    &fname, filename_collation()
                   );
            if( isNew ) {
                blob_appendf(&line, "new");
            } else if( isDeleted ) {
                blob_appendf(&line, "deleted");
            } else if( renamed ) {
                blob_appendf(&line, "renamed");
            } else if( chnged ) {
                blob_appendf(&line, "edited");
            } else {
                blob_appendf(&line, "unchanged");
            }
            blob_appendf(&line, " ");
            blob_appendf(&line, " %10.10s", blob_str(&uuid));
            blob_reset(&uuid);
        } else {
            blob_appendf(&line, "unknown 0000000000");
        }
        db_finalize(&q);
        fossil_print("%s\n", blob_str(&line));
        blob_reset(&fname);
        blob_reset(&line);
    } else if( find_option("print","p",0) ) {
        Blob record;
        Blob fname;
        const char *zRevision = find_option("revision", "r", 1);

        file_tree_name(g.argv[2], &fname, 1);
        if( zRevision ) {
            historical_version_of_file(zRevision, blob_str(&fname), &record, 0, 0, 0);
        } else {
            int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s",
                             &fname, filename_collation());
            if( rid==0 ) {
                fossil_fatal("no history for file: %b", &fname);
            }
            content_get(rid, &record);
        }
        blob_write_to_file(&record, "-");
        blob_reset(&record);
        blob_reset(&fname);
    } else {
        Blob line;
        Stmt q;
        Blob fname;
        int rid;
        const char *zFilename;
        const char *zLimit;
        const char *zOffset;
        int iLimit, iOffset, iBrief;

        if( find_option("log","l",0) ) {
            /* this is the default, no-op */
        }
        zLimit = find_option("limit",0,1);
        iLimit = zLimit ? atoi(zLimit) : -1;
        zOffset = find_option("offset",0,1);
        iOffset = zOffset ? atoi(zOffset) : 0;
        iBrief = (find_option("brief","b",0) == 0);
        if( g.argc!=3 ) {
            usage("?-l|--log? ?-b|--brief? FILENAME");
        }
        file_tree_name(g.argv[2], &fname, 1);
        rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s",
                     &fname, filename_collation());
        if( rid==0 ) {
            fossil_fatal("no history for file: %b", &fname);
        }
        zFilename = blob_str(&fname);
        db_prepare(&q,
                   "SELECT b.uuid, ci.uuid, date(event.mtime,'localtime'),"
                   "       coalesce(event.ecomment, event.comment),"
                   "       coalesce(event.euser, event.user)"
                   "  FROM mlink, blob b, event, blob ci, filename"
                   " WHERE filename.name=%Q %s"
                   "   AND mlink.fnid=filename.fnid"
                   "   AND b.rid=mlink.fid"
                   "   AND event.objid=mlink.mid"
                   "   AND event.objid=ci.rid"
                   " ORDER BY event.mtime DESC LIMIT %d OFFSET %d",
                   zFilename, filename_collation(), iLimit, iOffset
                  );
        blob_zero(&line);
        if( iBrief ) {
            fossil_print("History of %s\n", blob_str(&fname));
        }
        while( db_step(&q)==SQLITE_ROW ) {
            const char *zFileUuid = db_column_text(&q, 0);
            const char *zCiUuid = db_column_text(&q,1);
            const char *zDate = db_column_text(&q, 2);
            const char *zCom = db_column_text(&q, 3);
            const char *zUser = db_column_text(&q, 4);
            char *zOut;
            if( iBrief ) {
                fossil_print("%s ", zDate);
                zOut = sqlite3_mprintf("[%.10s] %s (user: %s, artifact: [%.10s])",
                                       zCiUuid, zCom, zUser, zFileUuid);
                comment_print(zOut, 11, 79);
                sqlite3_free(zOut);
            } else {
                blob_reset(&line);
                blob_appendf(&line, "%.10s ", zCiUuid);
                blob_appendf(&line, "%.10s ", zDate);
                blob_appendf(&line, "%8.8s ", zUser);
                blob_appendf(&line,"%-40.40s\n", zCom );
                comment_print(blob_str(&line), 0, 79);
            }
        }
        db_finalize(&q);
        blob_reset(&fname);
    }
}