Fossil

Artifact Content
Login

Artifact 99f393dce665c7907cd6eb30b6b7f77e011bf62c:


     1  /*
     2  ** Copyright (c) 2007 D. Richard Hipp
     3  **
     4  ** This program is free software; you can redistribute it and/or
     5  ** modify it under the terms of the Simplified BSD License (also
     6  ** known as the "2-Clause License" or "FreeBSD License".)
     7  
     8  ** This program is distributed in the hope that it will be useful,
     9  ** but without any warranty; without even the implied warranty of
    10  ** merchantability or fitness for a particular purpose.
    11  **
    12  ** Author contact information:
    13  **   drh@hwaci.com
    14  **   http://www.hwaci.com/drh/
    15  **
    16  *******************************************************************************
    17  **
    18  ** This file contains code used to create new branches within a repository.
    19  */
    20  #include "config.h"
    21  #include "branch.h"
    22  #include <assert.h>
    23  
    24  /*
    25  **  fossil branch new    BRANCH-NAME ?ORIGIN-CHECK-IN? ?-bgcolor COLOR?
    26  **  argv0  argv1  argv2  argv3       argv4
    27  */
    28  void branch_new(void){
    29    int rootid;            /* RID of the root check-in - what we branch off of */
    30    int brid;              /* RID of the branch check-in */
    31    int noSign;            /* True if the branch is unsigned */
    32    int i;                 /* Loop counter */
    33    char *zUuid;           /* Artifact ID of origin */
    34    Stmt q;                /* Generic query */
    35    const char *zBranch;   /* Name of the new branch */
    36    char *zDate;           /* Date that branch was created */
    37    char *zComment;        /* Check-in comment for the new branch */
    38    const char *zColor;    /* Color of the new branch */
    39    Blob branch;           /* manifest for the new branch */
    40    Manifest *pParent;     /* Parsed parent manifest */
    41    Blob mcksum;           /* Self-checksum on the manifest */
    42    const char *zDateOvrd; /* Override date string */
    43    const char *zUserOvrd; /* Override user name */
    44   
    45    noSign = find_option("nosign","",0)!=0;
    46    zColor = find_option("bgcolor","c",1);
    47    zDateOvrd = find_option("date-override",0,1);
    48    zUserOvrd = find_option("user-override",0,1);
    49    verify_all_options();
    50    if( g.argc<5 ){
    51      usage("new BRANCH-NAME CHECK-IN ?-bgcolor COLOR?");
    52    }
    53    db_find_and_open_repository(0, 0);  
    54    noSign = db_get_int("omitsign", 0)|noSign;
    55    
    56    /* fossil branch new name */
    57    zBranch = g.argv[3];
    58    if( zBranch==0 || zBranch[0]==0 ){
    59      fossil_panic("branch name cannot be empty");
    60    }
    61    if( db_exists(
    62          "SELECT 1 FROM tagxref"
    63          " WHERE tagtype>0"
    64          "   AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%s')",
    65          zBranch)!=0 ){
    66      fossil_fatal("branch \"%s\" already exists", zBranch);
    67    }
    68  
    69    user_select();
    70    db_begin_transaction();
    71    rootid = name_to_rid(g.argv[4]);
    72    if( rootid==0 ){
    73      fossil_fatal("unable to locate check-in off of which to branch");
    74    }
    75  
    76    pParent = manifest_get(rootid, CFTYPE_MANIFEST);
    77    if( pParent==0 ){
    78      fossil_fatal("%s is not a valid check-in", g.argv[4]);
    79    }
    80  
    81    /* Create a manifest for the new branch */
    82    blob_zero(&branch);
    83    if( pParent->zBaseline ){
    84      blob_appendf(&branch, "B %s\n", pParent->zBaseline);
    85    }
    86    zComment = mprintf("Create new branch named \"%h\"", zBranch);
    87    blob_appendf(&branch, "C %F\n", zComment);
    88    zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now");
    89    blob_appendf(&branch, "D %s\n", zDate);
    90  
    91    /* Copy all of the content from the parent into the branch */
    92    for(i=0; i<pParent->nFile; ++i){
    93      blob_appendf(&branch, "F %F", pParent->aFile[i].zName);
    94      if( pParent->aFile[i].zUuid ){
    95        blob_appendf(&branch, " %s", pParent->aFile[i].zUuid);
    96        if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){
    97          blob_appendf(&branch, " %s", pParent->aFile[i].zPerm);
    98        }
    99      }
   100      blob_append(&branch, "\n", 1);
   101    }
   102    zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid);
   103    blob_appendf(&branch, "P %s\n", zUuid);
   104    blob_appendf(&branch, "R %s\n", pParent->zRepoCksum);
   105    manifest_destroy(pParent);
   106  
   107    /* Add the symbolic branch name and the "branch" tag to identify
   108    ** this as a new branch */
   109    if( zColor!=0 ){
   110      blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
   111    }
   112    blob_appendf(&branch, "T *branch * %F\n", zBranch);
   113    blob_appendf(&branch, "T *sym-%F *\n", zBranch);
   114  
   115    /* Cancel all other symbolic tags */
   116    db_prepare(&q,
   117        "SELECT tagname FROM tagxref, tag"
   118        " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
   119        "   AND tagtype>0 AND tagname GLOB 'sym-*'"
   120        " ORDER BY tagname",
   121        rootid);
   122    while( db_step(&q)==SQLITE_ROW ){
   123      const char *zTag = db_column_text(&q, 0);
   124      blob_appendf(&branch, "T -%F *\n", zTag);
   125    }
   126    db_finalize(&q);
   127    
   128    blob_appendf(&branch, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin);
   129    md5sum_blob(&branch, &mcksum);
   130    blob_appendf(&branch, "Z %b\n", &mcksum);
   131    if( !noSign && clearsign(&branch, &branch) ){
   132      Blob ans;
   133      blob_zero(&ans);
   134      prompt_user("unable to sign manifest.  continue (y/N)? ", &ans);
   135      if( blob_str(&ans)[0]!='y' ){
   136        db_end_transaction(1);
   137        fossil_exit(1);
   138      }
   139    }
   140  
   141    brid = content_put(&branch, 0, 0, 0);
   142    if( brid==0 ){
   143      fossil_panic("trouble committing manifest: %s", g.zErrMsg);
   144    }
   145    db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
   146    if( manifest_crosslink(brid, &branch)==0 ){
   147      fossil_panic("unable to install new manifest");
   148    }
   149    content_deltify(rootid, brid, 0);
   150    zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid);
   151    printf("New branch: %s\n", zUuid);
   152    if( g.argc==3 ){
   153      printf(
   154        "\n"
   155        "Note: the local check-out has not been updated to the new\n"
   156        "      branch.  To begin working on the new branch, do this:\n"
   157        "\n"
   158        "      %s update %s\n",
   159        fossil_nameofexe(), zBranch
   160      );
   161    }
   162  
   163  
   164    /* Commit */
   165    db_end_transaction(0);
   166    
   167    /* Do an autosync push, if requested */
   168    autosync(AUTOSYNC_PUSH);
   169  }
   170  
   171  /*
   172  ** COMMAND: branch
   173  **
   174  ** Usage: %fossil branch SUBCOMMAND ... ?-R|--repository FILE?
   175  **
   176  ** Run various subcommands to manage branches of the open repository or
   177  ** of the repository identified by the -R or --repository option.
   178  **
   179  **    %fossil branch new BRANCH-NAME BASIS ?-bgcolor COLOR? 
   180  **
   181  **        Create a new branch BRANCH-NAME off of check-in BASIS.
   182  **        You can optionally give the branch a default color.
   183  **
   184  **    %fossil branch list
   185  **
   186  **        List all branches
   187  **
   188  */
   189  void branch_cmd(void){
   190    int n;
   191    const char *zCmd = "list";
   192    db_find_and_open_repository(0, 0);
   193    if( g.argc<2 ){
   194      usage("new|list ...");
   195    }
   196    if( g.argc>=3 ) zCmd = g.argv[2];
   197    n = strlen(zCmd);
   198    if( strncmp(zCmd,"new",n)==0 ){
   199      branch_new();
   200    }else if( strncmp(zCmd,"list",n)==0 ){
   201      Stmt q;
   202      int vid;
   203      char *zCurrent = 0;
   204  
   205      if( g.localOpen ){
   206        vid = db_lget_int("checkout", 0);
   207        zCurrent = db_text(0, "SELECT value FROM tagxref"
   208                              " WHERE rid=%d AND tagid=%d", vid, TAG_BRANCH);
   209      }
   210      compute_leaves(0, 1);
   211      db_prepare(&q,
   212        "SELECT DISTINCT value FROM tagxref"
   213        " WHERE tagid=%d AND value NOT NULL AND rid IN leaves"
   214        " ORDER BY value /*sort*/",
   215        TAG_BRANCH
   216      );
   217      while( db_step(&q)==SQLITE_ROW ){
   218        const char *zBr = db_column_text(&q, 0);
   219        int isCur = zCurrent!=0 && fossil_strcmp(zCurrent,zBr)==0;
   220        printf("%s%s\n", (isCur ? "* " : "  "), zBr);
   221      }
   222      db_finalize(&q);
   223    }else{
   224      fossil_panic("branch subcommand should be one of: "
   225                   "new list");
   226    }
   227  }
   228  
   229  /*
   230  ** WEBPAGE: brlist
   231  **
   232  ** Show a timeline of all branches
   233  */
   234  void brlist_page(void){
   235    Stmt q;
   236    int cnt;
   237    int showClosed = P("closed")!=0;
   238  
   239    login_check_credentials();
   240    if( !g.okRead ){ login_needed(); return; }
   241  
   242    style_header(showClosed ? "Closed Branches" : "Open Branches");
   243    style_submenu_element("Timeline", "Timeline", "brtimeline");
   244    if( showClosed ){
   245      style_submenu_element("Open","Open","brlist");
   246    }else{
   247      style_submenu_element("Closed","Closed","brlist?closed");
   248    }
   249    login_anonymous_available();
   250    compute_leaves(0, 1);
   251    style_sidebox_begin("Nomenclature:", "33%");
   252    @ <ol>
   253    @ <li> An <div class="sideboxDescribed"><a href="brlist">
   254    @ open branch</a></div> is a branch that has one or
   255    @ more <a href="leaves">open leaves.</a>
   256    @ The presence of open leaves presumably means
   257    @ that the branch is still being extended with new check-ins.</li>
   258    @ <li> A <div class="sideboxDescribed"><a href="brlist?closed">
   259    @ closed branch</a></div> is a branch with only
   260    @ <div class="sideboxDescribed"><a href="leaves?closed">
   261    @ closed leaves</a></div>.
   262    @ Closed branches are fixed and do not change (unless they are first
   263    @ reopened)</li>
   264    @ </ol>
   265    style_sidebox_end();
   266  
   267    cnt = 0;
   268    if( !showClosed ){
   269      db_prepare(&q,
   270        "SELECT DISTINCT value FROM tagxref"
   271        " WHERE tagid=%d AND value NOT NULL"
   272        "   AND rid IN leaves"
   273        " ORDER BY value /*sort*/",
   274        TAG_BRANCH
   275      );
   276    }else{
   277      db_prepare(&q,
   278        "SELECT value FROM tagxref"
   279        " WHERE tagid=%d AND value NOT NULL"
   280        " EXCEPT "
   281        "SELECT value FROM tagxref"
   282        " WHERE tagid=%d AND value NOT NULL"
   283        "   AND rid IN leaves"
   284        " ORDER BY value /*sort*/",
   285        TAG_BRANCH, TAG_BRANCH
   286      );
   287    }
   288    while( db_step(&q)==SQLITE_ROW ){
   289      const char *zBr = db_column_text(&q, 0);
   290      if( cnt==0 ){
   291        if( showClosed ){
   292          @ <h2>Closed Branches:</h2>
   293        }else{
   294          @ <h2>Open Branches:</h2>
   295        }
   296        @ <ul>
   297        cnt++;
   298      }
   299      if( g.okHistory ){
   300        @ <li><a href="%s(g.zTop)/timeline?r=%T(zBr)">%h(zBr)</a></li>
   301      }else{
   302        @ <li><b>%h(zBr)</b></li>
   303      }
   304    }
   305    if( cnt ){
   306      @ </ul>
   307    }
   308    db_finalize(&q);
   309    @ <script  type="text/JavaScript">
   310    @ function xin(id){
   311    @ }
   312    @ function xout(id){
   313    @ }
   314    @ </script>
   315    style_footer();
   316  }
   317  
   318  /*
   319  ** This routine is called while for each check-in that is rendered by
   320  ** the timeline of a "brlist" page.  Add some additional hyperlinks
   321  ** to the end of the line.
   322  */
   323  static void brtimeline_extra(int rid){
   324    Stmt q;
   325    if( !g.okHistory ) return;
   326    db_prepare(&q, 
   327      "SELECT substr(tagname,5) FROM tagxref, tag"
   328      " WHERE tagxref.rid=%d"
   329      "   AND tagxref.tagid=tag.tagid"
   330      "   AND tagxref.tagtype>0"
   331      "   AND tag.tagname GLOB 'sym-*'",
   332      rid
   333    );
   334    while( db_step(&q)==SQLITE_ROW ){
   335      const char *zTagName = db_column_text(&q, 0);
   336      @ <a href="%s(g.zTop)/timeline?r=%T(zTagName)">[timeline]</a>
   337    }
   338    db_finalize(&q);
   339  }
   340  
   341  /*
   342  ** WEBPAGE: brtimeline
   343  **
   344  ** Show a timeline of all branches
   345  */
   346  void brtimeline_page(void){
   347    Stmt q;
   348  
   349    login_check_credentials();
   350    if( !g.okRead ){ login_needed(); return; }
   351  
   352    style_header("Branches");
   353    style_submenu_element("List", "List", "brlist");
   354    login_anonymous_available();
   355    @ <h2>The initial check-in for each branch:</h2>
   356    db_prepare(&q,
   357      "%s AND blob.rid IN (SELECT rid FROM tagxref"
   358      "                     WHERE tagtype>0 AND tagid=%d AND srcid!=0)"
   359      " ORDER BY event.mtime DESC",
   360      timeline_query_for_www(), TAG_BRANCH
   361    );
   362    www_print_timeline(&q, 0, 0, 0, brtimeline_extra);
   363    db_finalize(&q);
   364    @ <script  type="text/JavaScript">
   365    @ function xin(id){
   366    @ }
   367    @ function xout(id){
   368    @ }
   369    @ </script>
   370    style_footer();
   371  }