Artifact Content
Not logged in

Artifact 86ce4008279aaf0127390b5acb4a4db223cb30e2

File src/update.c part of check-in [36b96b8616] - Rework the merge algorithm. It now only works for text files. But, it no longer gets confused by line endings (\r\n versus \n) and it reports conflicts. by drh on 2007-11-16 20:42:31.

/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License version 2 as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** General Public License for more details.
** 
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA  02111-1307, USA.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to merge the changes in the current
** checkout into a different version and switch to that version.
*/
#include "config.h"
#include "update.h"
#include <assert.h>

/*
** Return true if artifact rid is a version
*/
int is_a_version(int rid){
  return db_exists("SELECT 1 FROM plink WHERE cid=%d", rid);
}

/*
** COMMAND: update
**
** Usage: %fossil update ?VERSION? ?--force? ?--latest?
**
** The optional argument is a version that should become the current
** version.  If the argument is omitted, then use the leaf of the
** tree that begins with the current version, if there is only a 
** single leaf.  If there are a multiple leaves, the latest is used
** if the --latest flag is present.
**
** This command is different from the "checkout" in that edits are
** not overwritten.  Edits are merged into the new version.
**
** If there are uncommitted edits and the safemerge option is
** enabled then no update will occur unless you provide the 
** --force flag.
*/
void update_cmd(void){
  int vid;              /* Current version */
  int tid=0;            /* Target version - version we are changing to */
  Stmt q;
  int latestFlag;       /* Pick the latest version if true */
  int forceFlag;        /* True force the update */

  latestFlag = find_option("latest",0, 0)!=0;
  forceFlag = find_option("force","f",0)!=0;
  if( g.argc!=3 && g.argc!=2 ){
    usage("?VERSION?");
  }
  db_must_be_within_tree();
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    fossil_fatal("cannot find current version");
  }
  if( db_exists("SELECT 1 FROM vmerge") ){
    fossil_fatal("cannot update an uncommitted merge");
  }
  if( !forceFlag && db_get_int("safemerge", 0) && unsaved_changes() ){
    fossil_fatal("there are uncommitted changes and safemerge is enabled");
  }

  if( g.argc==3 ){
    tid = name_to_rid(g.argv[2]);
    if( tid==0 ){
      fossil_fatal("not a version: %s", g.argv[2]);
    }
    if( !is_a_version(tid) ){
      fossil_fatal("not a version: %s", g.argv[2]);
    }
  }

  if( tid==0 ){
    /* 
    ** Do an autosync pull prior to the update, if autosync is on and they
    ** did not want a specific version (i.e. another branch, a past revision).
    ** By not giving a specific version, they are asking for the latest, thus
    ** pull to get the latest, then update.
    */
    autosync(1);
  }
  
  if( tid==0 ){
    compute_leaves(vid);
    if( !latestFlag && db_int(0, "SELECT count(*) FROM leaves")>1 ){
      db_prepare(&q, 
        "%s "
        "   AND event.objid IN leaves"
        " ORDER BY event.mtime DESC",
        timeline_query_for_tty()
      );
      print_timeline(&q, 100);
      db_finalize(&q);
      fossil_fatal("Multiple descendents");
    }
    tid = db_int(0, "SELECT rid FROM leaves, event"
                    " WHERE event.objid=leaves.rid"
                    " ORDER BY event.mtime DESC"); 
  }

  db_begin_transaction();
  vfile_check_signature(vid);
  undo_begin();
  load_vfile_from_rid(tid);

  /*
  ** 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 PRIMARY KEY,"      /* The filename */
    "  idv INTEGER,"              /* VFILE entry for current version */
    "  idt INTEGER,"              /* VFILE entry for target version */
    "  chnged BOOLEAN,"           /* True if current version has been edited */
    "  ridv INTEGER,"             /* Record ID for current version */
    "  ridt INTEGER "             /* Record ID for target */
    ");"
    "INSERT OR IGNORE INTO fv"
    " SELECT pathname, 0, 0, 0, 0, 0 FROM vfile"
  );
  db_prepare(&q,
    "SELECT id, pathname, rid FROM vfile"
    " WHERE vid=%d", tid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int id = db_column_int(&q, 0);
    const char *fn = db_column_text(&q, 1);
    int rid = db_column_int(&q, 2);
    db_multi_exec(
      "UPDATE fv SET idt=%d, ridt=%d WHERE fn=%Q",
      id, rid, fn
    );
  }
  db_finalize(&q);
  db_prepare(&q,
    "SELECT id, pathname, rid, chnged FROM vfile"
    " WHERE vid=%d", vid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int id = db_column_int(&q, 0);
    const char *fn = db_column_text(&q, 1);
    int rid = db_column_int(&q, 2);
    int chnged = db_column_int(&q, 3);
    db_multi_exec(
      "UPDATE fv SET idv=%d, ridv=%d, chnged=%d WHERE fn=%Q",
      id, rid, chnged, fn
    );
  }
  db_finalize(&q);

  db_prepare(&q, 
    "SELECT fn, idv, ridv, idt, ridt, chnged FROM fv ORDER BY 1"
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    int idv = db_column_int(&q, 1);
    int ridv = db_column_int(&q, 2);
    int idt = db_column_int(&q, 3);
    int ridt = db_column_int(&q, 4);
    int chnged = db_column_int(&q, 5);

    if( idv>0 && ridv==0 && idt>0 ){
      /* Conflict.  This file has been added to the current checkout
      ** but also exists in the target checkout.  Use the current version.
      */
      printf("CONFLICT %s\n", zName);
    }else if( idt>0 && idv==0 ){
      /* File added in the target. */
      printf("ADD %s\n", zName);
      undo_save(zName);
      vfile_to_disk(0, idt, 0);
    }else if( idt>0 && idv>0 && ridt!=ridv && chnged==0 ){
      /* The file is unedited.  Change it to the target version */
      printf("UPDATE %s\n", zName);
      undo_save(zName);
      vfile_to_disk(0, idt, 0);
    }else if( idt==0 && idv>0 ){
      if( chnged ){
        printf("CONFLICT %s\n", zName);
      }else{
        char *zFullPath;
        printf("REMOVE %s\n", zName);
        undo_save(zName);
        zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
        unlink(zFullPath);
        free(zFullPath);
      }
    }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
      /* Merge the changes in the current tree into the target version */
      Blob e, r, t, v;
      int rc;
      char *zFullPath;
      printf("MERGE %s\n", zName);
      undo_save(zName);
      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
      content_get(ridt, &t);
      content_get(ridv, &v);
      blob_zero(&e);
      blob_read_from_file(&e, zFullPath);
      rc = blob_merge(&v, &e, &t, &r);
      if( rc>=0 ){
        blob_write_to_file(&r, zFullPath);
        if( rc>0 ){
          printf("***** %d merge conflicts in %s\n", rc, zName);
        }
      }else{
        printf("***** Cannot merge binary file %s\n", zName);
      }
      free(zFullPath);
      blob_reset(&v);
      blob_reset(&e);
      blob_reset(&t);
      blob_reset(&r);
      
    }
  }
  db_finalize(&q);
  
  /*
  ** Clean up the mid and pid VFILE entries.  Then commit the changes.
  */
  db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid);
  manifest_to_disk(tid);
  db_lset_int("checkout", tid);
  db_end_transaction(0);
}

/*
** COMMAND: revert
**
** Usage: %fossil revert ?--yes? ?-r REVISION? FILE
**
** Revert to the current repository version of FILE. This
** command will confirm your operation, unless you do so
** at the command line via the -yes option.
**/
void revert_cmd(void){
  const char *zFile;
  const char *zRevision;
  Blob fname;
  Blob record;
  Blob ans;
  int rid = 0, yesRevert;
  
  yesRevert = find_option("yes", "y", 0)!=0;
  zRevision = find_option("revision", "r", 1);
  verify_all_options();
  
  if( g.argc<3 ){
    usage("?OPTIONS FILE");
  }
  db_must_be_within_tree();
  
  zFile = g.argv[g.argc-1];

  if( !file_tree_name(zFile, &fname) ){
    fossil_panic("unknown file: %s", zFile);
  }
  
  if( yesRevert==0 ){
    char *prompt = mprintf("revert file %B? this will"
                           " destroy local changes [y/N]? ",
                           &fname);
    blob_zero(&ans);
    prompt_user(prompt, &ans);
    if( blob_str(&ans)[0]=='y' ){
      yesRevert = 1;
    }
  }

  if( yesRevert==1 && zRevision!=0 ){
    content_get_historical_file(zRevision, zFile, &record);
  }else if( yesRevert==1 ){
    rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B", &fname);
    if( rid==0 ){
      fossil_panic("no history for file: %b", &fname);
    }
    content_get(rid, &record);
  }
  
  if( yesRevert==1 ){
    blob_write_to_file(&record, blob_str(&fname));
    printf("%s reverted\n", blob_str(&fname));
    blob_reset(&record);
    blob_reset(&fname);
  }else{
    printf("revert canceled\n");
  }
}