Ejemplo n.º 1
0
/*
** Check content that was received with rcvid and return true if any
** fork was created.
*/
int fossil_any_has_fork(int rcvid){
  static Stmt q;
  int fForkSeen = 0;

  if( rcvid==0 ) return 0;
  db_static_prepare(&q,
    "  SELECT pid FROM plink WHERE pid>0 AND isprim"
    "     AND cid IN (SELECT blob.rid FROM blob"
    "   WHERE rcvid=:rcvid)");
  db_bind_int(&q, ":rcvid", rcvid);
  while( !fForkSeen && db_step(&q)==SQLITE_ROW ){
    int pid = db_column_int(&q, 0);
    if( count_nonbranch_children(pid)>1 ){
      compute_leaves(pid,1);
      if( db_int(0, "SELECT count(*) FROM leaves")>1 ){
        int rid = db_int(0, "SELECT rid FROM leaves, event"
                            " WHERE event.objid=leaves.rid"
                            " ORDER BY event.mtime DESC LIMIT 1");
        fForkSeen = fossil_find_nearest_fork(rid, db_open_local(0))!=0;
      }
    }
  }
  db_finalize(&q);
  return fForkSeen;
}
Ejemplo n.º 2
0
/*
** WEBPAGE: brlist
**
** Show a timeline of all branches
*/
void brlist_page(void){
  Stmt q;
  int cnt;
  int showClosed = P("closed")!=0;

  login_check_credentials();
  if( !g.okRead ){ login_needed(); return; }

  style_header(showClosed ? "Closed Branches" : "Open Branches");
  style_submenu_element("Timeline", "Timeline", "brtimeline");
  if( showClosed ){
    style_submenu_element("Open","Open","brlist");
  }else{
    style_submenu_element("Closed","Closed","brlist?closed");
  }
  login_anonymous_available();
  compute_leaves(0, 1);
  style_sidebox_begin("Nomenclature:", "33%");
  @ <ol>
Ejemplo n.º 3
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);
}
Ejemplo n.º 4
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);
    }
}