CVSTrac Legacy Code

Check-in [8c72a44f1e]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Initial support for multiple GIT modules in a single GitTrac instance (i.e. conceptually similar to multiple CVS modules in a single CvsTrac).
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:8c72a44f1e2f11a90a92331f0857d35a45857a20
User & Date: cpb 2015-05-25 16:12:04
Context
2015-06-04
18:55
Need to prefix filenames with the module for browse to work correctly. Fortunately, we can strip it. check-in: 0b1a22dfae user: cpb tags: trunk
2015-05-25
16:12
Initial support for multiple GIT modules in a single GitTrac instance (i.e. conceptually similar to multiple CVS modules in a single CvsTrac). check-in: 8c72a44f1e user: cpb tags: trunk
2015-03-05
17:30
It appears that Firefox recently triggered a bug in our CSS. check-in: 360a8812cf user: root tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to git.c.

32
33
34
35
36
37
38

39
40
41
42
43
44
45
..
52
53
54
55
56
57
58




























59
60
61
62
63
64
65
..
76
77
78
79
80
81
82





83
84
85
86
87
88
89
90
91
92
93
94
95
96
...
116
117
118
119
120
121
122



123

124
125
126
127
128
129
130
...
177
178
179
180
181
182
183
184
185
186
187
188
189




190
191



192
193
194
195
196

197
198
199
200
201
202
203
...
226
227
228
229
230
231
232

233







234
235

236
237
238
239
240
241
242
...
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
...
293
294
295
296
297
298
299
300
301
302
303
304
305











306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325



326


327
328
329
330
331
332
333
334
335
336
337

338
339
340
341
342
343
344
...
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
...
450
451
452
453
454
455
456









457
458
459
460
461
462
463
...
479
480
481
482
483
484
485
486



487
488
489
490
491
492
493
...
581
582
583
584
585
586
587






588
589
590
591
592

593
594
595
596
597
598
599
...
617
618
619
620
621
622
623


























624

625

626
627
628
629
630
631
632
...
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694








695
696
697
698
699
700
701
702
703


704
705
706
707
708
709
710
711
712
713
...
725
726
727
728
729
730
731

732
733
734
735
736
737
738
739
740
741

742
743
744
745
746
747
748
749
...
757
758
759
760
761
762
763
764
765
766


767




768
769
770
771
772

773
774
775
776
777
778
779
780
781
782
783
784
785



786
787
788



789
790
791
792
793
794
795
796
797


798
799
800
801
802
803
804
805
...
829
830
831
832
833
834
835
836
837
838


839
840
841
842





843
844
845
846
847
848
849
...
855
856
857
858
859
860
861
862
863
864

865
866
867
868
869
870
871
872
873
#include <sys/times.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <assert.h>
#include <sys/types.h>
#include <errno.h>
#include <limits.h>  /* for PATH_MAX */

#include "git.h"

static void err_pipe(const char* zMsg,const char* zCmd){
  int nErr = 0;
  error_init(&nErr);
  @ <b class="error">%h(zMsg)</b>
  @ <li><p>Unable to execute the following command:
................................................................................

static int next_cnum(){
  char *zResult = db_short_query("SELECT max(cn) FROM chng");
  int next_cnum = zResult ? atoi(zResult)+1 : 0;
  if(zResult) free(zResult);
  return next_cnum;
}





























/*
** If *nDate==0, it's usually because the commit wasn't correctly read. A NULL
** return code just means that the commit could be the root object.
*/
static char **git_read_commit(
  const char *zGitDir,
................................................................................
  int nParent = 0;
  int nMaxParent = 0;
  int nComment = 0;
  char zCommitter[100];
  char zAuthor[100];
  char zComment[10000];






  assert(nDate);

  *pzAuthor = 0;
  *pzComment = 0;

  zCmd = mprintf("GIT_DIR='%s' git cat-file commit '%s' 2>&1",
                 zGitDir, zObject);
  if( zCmd==0 ) return 0;

  in = popen(zCmd, "r");
  if( in==0 ){
    err_pipe("Reading commit",zCmd);
    free(zCmd);
    return 0;
................................................................................
        if( nParent+2 >= nMaxParent ){
          nMaxParent = (nParent+2) * 2;
          azParents = realloc(azParents, sizeof(char*)*nMaxParent);
          if( azParents==0 ) common_err("%s",strerror(errno));
        }

        sscanf(&zLine[7],"%50[0-9a-fA-F]",zParent);



        azParents[nParent++] = strdup(zParent);

        azParents[nParent] = 0;

      }else if( 0==strncmp(zLine,"author ",7) ){
        sscanf(&zLine[7],"%90[^<]%*[^>]>",zAuthor);
      }else if( 0==strncmp(zLine,"committer ",10) ){
        sscanf(&zLine[10],"%90[^<]%*[^>]> %d",zCommitter,nDate);
      }
................................................................................
static void git_ingest_commit_chng(
  const char *zGitDir,
  int cn,
  const char *zCommit,
  time_t nDate,
  const char *zAuthor,
  const char *zComment,
  const char *zPrevVers,
  int skipInsertFile
){
  FILE *in = 0;
  char zLine[PATH_MAX*3];
  int nFiles = 0;





  if( zPrevVers==0 || zPrevVers[0]==0 ){



    /* Initial commit, hence no parent(s) to compare against. That means just a
    ** straight tree list
    */

    char *zCmd = mprintf("GIT_DIR='%s' git ls-tree -r '%s'", zGitDir, zCommit);

    in = popen(zCmd,"r");
    if( in==0 ){
      err_pipe("Reading tree",zCmd);
      return;
    }
    free(zCmd);

................................................................................
    /* Now get the list of changed files and turn them into FILE
    ** and FILECHNG records.  git-diff-tree is disgustingly PERFECT for
    ** this. Compared to the hassles one has to go through with CVS or
    ** Subversion to find out what's in a change tree, it's just mind
    ** blowing how ideal this is.  FIXME: we're not handling renames or
    ** copies right now. When/if we do, add in the "-C -M" flags.
    */









    char *zCmd = mprintf("GIT_DIR='%s' git diff-tree -r -t '%s' '%s'",
                         zGitDir, zPrevVers, zCommit);

    in = popen(zCmd,"r");
    if( in==0 ){
      err_pipe("Reading tree",zCmd);
      return;
    }
    free(zCmd);

................................................................................
            cn, zPath, zCommit, nIns, nDel);
          nFiles ++;
        }else if( cStatus=='D' ){
          db_execute(
            "INSERT INTO "
            "       filechng(cn,filename,vers,prevvers,chngtype,nins,ndel) "
            "VALUES(%d,'%q','%s','%s',2,%d,%d)",
            cn, zPath, zCommit, zPrevVers, nIns, nDel);
          nFiles ++;
        }else{
          db_execute(
            "INSERT INTO "
            "       filechng(cn,filename,vers,prevvers,chngtype,nins,ndel) "
            "VALUES(%d,'%q','%s','%s',0,%d,%d)",
            cn, zPath, zCommit, zPrevVers, nIns, nDel);
          nFiles ++;
        }
      }
    }
  }
  assert(in);
  pclose(in);
................................................................................
      cn, nDate, zAuthor, zComment
    );
    xref_add_checkin_comment(cn, zComment);
  }
}

static char *add_merge_to_comment( const char *zGitDir,
  char* zComment, const char *zHead, const char *zRemote
) {
  int rc;
  int nMerged = 0;
  char zBase[100];
  FILE *in;











  char *zCmd = mprintf("GIT_DIR='%s' git merge-base '%s' '%s'",
                       zGitDir, zHead, zRemote );
  in = popen(zCmd,"r");
  if( in==0 ){
    err_pipe("Reading tree",zCmd);
    return zComment;
  }
  free(zCmd);

  zBase[0]=0;
  rc = fscanf(in, "%50[a-fA-F0-9]", zBase);
  pclose(in);

  if( 1==rc ){
    char zCommit[100];

    /* Got a common base for the merge.
    ** What we do with this is get the list of object/cn pairs from
    ** zRemote to zBase's parent and tack them on to the comment.
    */



    strncpy(zCommit,zRemote,sizeof(zCommit));


    while( zCommit[0] && strcmp(zCommit,zBase) ){
      char *z;
      char **azChng = db_query("SELECT cn,prevvers FROM filechng "
                               "WHERE vers='%q' LIMIT 1", zCommit);
      if( azChng[0]==0 ) break; /* maybe pruned? */
      
      z = mprintf("[%d], %s", atoi(azChng[0]), zComment);
      if(zComment) free(zComment);
      zComment = z;

      strncpy(zCommit, azChng[1] ? azChng[1] : "", sizeof(zCommit));


      nMerged ++;
    }
  }

  if( nMerged ){
    /* Prefix the message with "Merged" */
................................................................................

  return nNew + 1;
}

/*
** Read in the git references and turn them into new CHNG records.
*/
static int git_read_refs(const char *zGitDir){
  FILE *in;
  int nCommits = 0;
  char zLine[PATH_MAX+200];
  char zObject[100];
  char zName[PATH_MAX];
  char *zCmd;
  char *zOldObject;
  const char* zFormat = "%(objecttype) %(objectname) %(*objectname) %(refname)";

  zCmd = mprintf("GIT_DIR='%s' git for-each-ref --format='%s' 2>&1",
                 zGitDir, zFormat);
  if( zCmd==0 ) return 0;

  in = popen(zCmd, "r");
  if( in==0 ){
    err_pipe("Reading refs",zCmd);
    free(zCmd);
    return 0;
................................................................................
    } else if( !strncmp(zLine, "tag ", 4) ){
      /* The first SHA is the tag, we want the second */
      sscanf(&zLine[4], "%*[0-9a-fA-F] %50[0-9a-fA-F] %[^\n]", zObject, zName );
    } else {
      /* some kind of newfangled refs? */
      continue;
    }










    /* It'd be nice to delete refs which go away, so keep track of the
    ** ones we've seen.
    */
    db_execute("INSERT INTO seenrefs(name) VALUES('%q')", zName );

    /*
................................................................................
      /* ingest the commit tree */
      nCommits += git_ingest_commit_tree(zGitDir,zObject);
    }

    if(zOldObject) free(zOldObject);
  }

  pclose(in);




  return nCommits;
}

static void git_update_refs(){
  int i;
  char **azRefs;
................................................................................
**
** If any errors occur, output an error page and exit.
**
** If the "isReread" flag is set, it means that the history file is being
** reread to pick up changes that we may have missed earlier. Probably
** futile with GIT, since we can only work back from known refs and we
** pick those up automatically, anyways.






*/
static int git_history_update(int isReread){
  const char *zRoot;
  int nOldSize = 0;
  int nNewRevs;


  db_execute("BEGIN");

  /* Get the path to local repository and last revision number we have in db
   * If there's no repository defined, bail and wait until the admin sets one.
  */
  zRoot = db_config("cvsroot","");
................................................................................
    /* Ref checks should be really quick since they happen on every update.
    ** This index improves performance by at least an order of magnitude.
    */
    db_execute("CREATE INDEX IF NOT EXISTS git_idx1 ON chng(branch,directory)");
    db_execute("CREATE INDEX IF NOT EXISTS git_idx2 ON filechng(vers)");
  }



























  /* Read the refs and ingest the commit tree */

  nNewRevs = git_read_refs(zRoot);


  if( nNewRevs==0 ) {
    /* Might be a little inefficient to call this each time, but since
    ** branching/tagging operations can be done independent from commits,
    ** and we _want_ to know about branches and tags, how else can we do
    ** it?
    */
................................................................................
  db_execute("COMMIT;");

  return 0;
}

static int git_history_reconstruct(void) {
  /* clean out refs */
  db_execute("DELETE FROM chng WHERE milestone>0 AND branch LIKE 'refs/%%'");
  return 0;
}

/*
** Diff two versions of a file, handling all exceptions.
**
** If oldVersion is NULL, then this function will output the
** text of version newVersion of the file instead of doing
** a diff.
*/
static int git_diff_versions(
  const char *oldVersion,
  const char *newVersion,
  const char *zFile
){
  char *zCmd;
  FILE *in;
  const char *azSubst[16];
  const char *zTemplate;









  /* Find the command used to compute the file difference.*/
  azSubst[0] = "F";
  azSubst[1] = zFile;
  azSubst[2] = "V1";
  azSubst[3] = oldVersion;
  azSubst[4] = "V2";
  azSubst[5] = newVersion;
  azSubst[6] = "RP";


  azSubst[7] = db_config("cvsroot", "");
  azSubst[8] = "V";
  azSubst[9] = newVersion;
  azSubst[10] = 0;
			  
  zTemplate = db_config(
    "filediff", 
    OS_VAL( "GIT_DIR='%RP' git diff --full-index "
              "-t -p -r '%V1' '%V2' -- '%F' 2>/dev/null",
	          "GIT_DIR=\"%RP\" git diff --full-index "
................................................................................
  pclose(in);
  
  return 0;
}

static char *git_get_blob(
  const char *zGitDir,

  const char *zTreeish,
  const char* zPath
){
  FILE *in;
  char zLine[PATH_MAX*2];
  char *zCmd;

  if( zTreeish==0 || zTreeish[0]==0 || zPath==0 || zPath[0]==0 ) return 0;
    
  zCmd = mprintf("GIT_DIR='%s' git ls-tree -r '%s' '%s'", zGitDir,

                 quotable_string(zTreeish), quotable_string(zPath));
  in = popen(zCmd,"r");
  if( in==0 ){
    err_pipe("Reading tree",zCmd);
    return 0;
  }
  free(zCmd);

................................................................................
    if( !strcmp(zType,"blob") ){
      return strdup(zObject);
    }
  }
  return 0;
}

static int git_dump_version(const char *zVersion, const char *zFile,int bRaw){
  int rc = -1;
  char *zCmd;


  const char *zRoot = db_config("cvsroot","");




  const char *zBlob = git_get_blob(zRoot, zVersion, zFile);
  if( zBlob==0 ) return -1;

  zCmd = mprintf("GIT_DIR='%s' git cat-file blob '%s' 2>/dev/null",
                zRoot, zBlob);

  rc = common_dumpfile( zCmd, zVersion, zFile, bRaw );
  free(zCmd);

  return rc;
}

static int git_diff_chng(int cn, int bRaw){
  char **azRev;
  char *zCmd;
  char zLine[2000];
  FILE *in;
  const char *azSubst[16];
  const char *zTemplate;



  
  azRev = db_query("SELECT vers,prevvers FROM filechng WHERE cn=%d", cn);
  if( !azRev || !azRev[0] ) return -1; /* Invalid check-in number */



  
  /* Find the command used to compute the file difference.*/
  azSubst[0] = "F";
  azSubst[1] = "";
  azSubst[2] = "V1";
  azSubst[3] = azRev[1];
  azSubst[4] = "V2";
  azSubst[5] = azRev[0];
  azSubst[6] = "RP";


  azSubst[7] = db_config("cvsroot", "");
  azSubst[8] = "V";
  azSubst[9] = azRev[0];
  azSubst[10] = 0;
			  
  zTemplate = 
    OS_VAL( "GIT_DIR='%RP' git diff --full-index -t "
              "-p -r '%V1' '%V2' -- '%F' 2>/dev/null",
................................................................................
    @ </div>
  }
  pclose(in);
  
  return 0;
}

static int git_download(const char *zDir, const char *zVersion, int compression){
  FILE *in;
  char *zCmd, zBuf[1024], *zCompression = "", *zFormat = "";


  size_t nRead;
  const char *zRoot = db_config("cvsroot","");
  if( zRoot[0]==0 ) return -1;






  switch( compression ){
  case COMPRESSION_TAR:
    zFormat = "--format=tar";
    break;
  case COMPRESSION_TAR_GZ:
    zFormat = "--format=tar";
    zCompression = " | gzip";
................................................................................
  case COMPRESSION_ZIP:
    zFormat = "--format=zip";
    break;
  default:
    return -1;
  }

  zCmd = mprintf(OS_VAL("GIT_DIR='%s' git archive %s %s %s%s 2>/dev/null",
                        "GIT_DIR=\"%s\" git archive %s %s %s%s 2>NUL"),
                        quotable_string(zRoot),

                        quotable_string(zFormat),
                        zVersion ? quotable_string(zVersion) : "HEAD",
                        zDir ? quotable_string(zDir) : "",
                        quotable_string(zCompression));
  in = popen(zCmd, OS_VAL("r","rb"));
  if( in==0 ){
    return -1;
  }
  free(zCmd);







>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>





|
|







 







>
>
>
|
>







 







|





>
>
>
>

<
>
>
>




|
>







 







>

>
>
>
>
>
>
>
|
<
>







 







|






|







 







|





>
>
>
>
>
>
>
>
>
>
>
|
|












|





>
>
>
|
>
>











>







 







|









|
|







 







>
>
>
>
>
>
>
>
>







 







|
>
>
>







 







>
>
>
>
>
>





>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
>







 







|











|
|






>
>
>
>
>
>
>
>





|

|

>
>
|

|







 







>
|






|

|
>
|







 







|


>
>

>
>
>
>
|


|
<
>
|












>
>
>



>
>
>





|

|

>
>
|







 







|


>
>




>
>
>
>
>







 







|
|

>

|







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
..
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
...
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
...
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
...
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232

233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
...
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287

288
289
290
291
292
293
294
295
...
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
...
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
...
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
...
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
...
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
...
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
...
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
...
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
...
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
...
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906

907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
...
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
....
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
#include <sys/times.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <assert.h>
#include <sys/types.h>
#include <errno.h>
#include <limits.h>  /* for PATH_MAX */
#include <dirent.h> /* for scandir() */
#include "git.h"

static void err_pipe(const char* zMsg,const char* zCmd){
  int nErr = 0;
  error_init(&nErr);
  @ <b class="error">%h(zMsg)</b>
  @ <li><p>Unable to execute the following command:
................................................................................

static int next_cnum(){
  char *zResult = db_short_query("SELECT max(cn) FROM chng");
  int next_cnum = zResult ? atoi(zResult)+1 : 0;
  if(zResult) free(zResult);
  return next_cnum;
}

/* Parse a GIT object ident of the form [<module>:]<rev> into component
** parts.
*/
static void parse_git_object( const char* zObj,
  char* zModule,
  int nModule,
  char* zVers,
  int nVers
) {
  if( zObj ) {
    const char* zC = strchr(zObj,':');
    if( zC ) {
      int nLen = zC - zObj;
      if( nLen >= nModule ) nLen = nModule-1;
      strncpy( zModule, zObj, nLen );
      zModule[nLen] = 0;
      strncpy( zVers, zC+1, nVers );
      zVers[nVers-1] = 0;
    } else {
      zModule[0] = 0;
      strncpy( zVers, zObj, nVers );
      zVers[nVers-1] = 0;
    }
  } else {
    zModule[0] = zVers[0] = 0;
  }
}

/*
** If *nDate==0, it's usually because the commit wasn't correctly read. A NULL
** return code just means that the commit could be the root object.
*/
static char **git_read_commit(
  const char *zGitDir,
................................................................................
  int nParent = 0;
  int nMaxParent = 0;
  int nComment = 0;
  char zCommitter[100];
  char zAuthor[100];
  char zComment[10000];

  char zVers[100];
  char zModule[PATH_MAX];

  parse_git_object( zObject, zModule, sizeof(zModule), zVers, sizeof(zVers) );

  assert(nDate);

  *pzAuthor = 0;
  *pzComment = 0;

  zCmd = mprintf("GIT_DIR='%s/%s' git cat-file commit '%s' 2>&1",
                 zGitDir, zModule, zVers);
  if( zCmd==0 ) return 0;

  in = popen(zCmd, "r");
  if( in==0 ){
    err_pipe("Reading commit",zCmd);
    free(zCmd);
    return 0;
................................................................................
        if( nParent+2 >= nMaxParent ){
          nMaxParent = (nParent+2) * 2;
          azParents = realloc(azParents, sizeof(char*)*nMaxParent);
          if( azParents==0 ) common_err("%s",strerror(errno));
        }

        sscanf(&zLine[7],"%50[0-9a-fA-F]",zParent);
        if( zModule[0] ) {
          azParents[nParent++] = mprintf("%s:%s", zModule, zParent);
        } else {
          azParents[nParent++] = strdup(zParent);
        }
        azParents[nParent] = 0;

      }else if( 0==strncmp(zLine,"author ",7) ){
        sscanf(&zLine[7],"%90[^<]%*[^>]>",zAuthor);
      }else if( 0==strncmp(zLine,"committer ",10) ){
        sscanf(&zLine[10],"%90[^<]%*[^>]> %d",zCommitter,nDate);
      }
................................................................................
static void git_ingest_commit_chng(
  const char *zGitDir,
  int cn,
  const char *zCommit,
  time_t nDate,
  const char *zAuthor,
  const char *zComment,
  const char *zPrevCommit,
  int skipInsertFile
){
  FILE *in = 0;
  char zLine[PATH_MAX*3];
  int nFiles = 0;
  char zModule[PATH_MAX];
  char zVers[100];
  char zPModule[PATH_MAX];
  char zPrevVers[100];


  parse_git_object( zCommit, zModule, sizeof(zModule), zVers, sizeof(zVers) );

  if( zPrevCommit==0 || zPrevCommit[0]==0 ){
    /* Initial commit, hence no parent(s) to compare against. That means just a
    ** straight tree list
    */

    char *zCmd = mprintf("GIT_DIR='%s/%s' git ls-tree -r '%s'", zGitDir,
      zModule, zVers);
    in = popen(zCmd,"r");
    if( in==0 ){
      err_pipe("Reading tree",zCmd);
      return;
    }
    free(zCmd);

................................................................................
    /* Now get the list of changed files and turn them into FILE
    ** and FILECHNG records.  git-diff-tree is disgustingly PERFECT for
    ** this. Compared to the hassles one has to go through with CVS or
    ** Subversion to find out what's in a change tree, it's just mind
    ** blowing how ideal this is.  FIXME: we're not handling renames or
    ** copies right now. When/if we do, add in the "-C -M" flags.
    */
    char *zCmd;

    parse_git_object( zPrevCommit, zPModule, sizeof(zPModule),
      zPrevVers, sizeof(zPrevVers) );

    if( strcmp(zPModule,zModule) ) {
      /* FIXME: shouldn't ever happen */
    }

    zCmd = mprintf("GIT_DIR='%s/%s' git diff-tree -r -t '%s' '%s'",

                         zGitDir, zModule, zPrevVers, zVers);
    in = popen(zCmd,"r");
    if( in==0 ){
      err_pipe("Reading tree",zCmd);
      return;
    }
    free(zCmd);

................................................................................
            cn, zPath, zCommit, nIns, nDel);
          nFiles ++;
        }else if( cStatus=='D' ){
          db_execute(
            "INSERT INTO "
            "       filechng(cn,filename,vers,prevvers,chngtype,nins,ndel) "
            "VALUES(%d,'%q','%s','%s',2,%d,%d)",
            cn, zPath, zCommit, zPrevCommit, nIns, nDel);
          nFiles ++;
        }else{
          db_execute(
            "INSERT INTO "
            "       filechng(cn,filename,vers,prevvers,chngtype,nins,ndel) "
            "VALUES(%d,'%q','%s','%s',0,%d,%d)",
            cn, zPath, zCommit, zPrevCommit, nIns, nDel);
          nFiles ++;
        }
      }
    }
  }
  assert(in);
  pclose(in);
................................................................................
      cn, nDate, zAuthor, zComment
    );
    xref_add_checkin_comment(cn, zComment);
  }
}

static char *add_merge_to_comment( const char *zGitDir,
  char* zComment, const char *zHeadCommit, const char *zRemoteCommit
) {
  int rc;
  int nMerged = 0;
  char zBase[100];
  FILE *in;
  char *zCmd;

  char zModule[PATH_MAX];
  char zHead[100];
  char zRemote[100];

  parse_git_object( zHeadCommit, zModule, sizeof(zModule),
    zHead, sizeof(zHead) );
  parse_git_object( zRemoteCommit, zModule, sizeof(zModule),
    zRemote, sizeof(zRemote) );
  
  zCmd = mprintf("GIT_DIR='%s/%s' git merge-base '%s' '%s'",
                       zGitDir, zModule, zHead, zRemote );
  in = popen(zCmd,"r");
  if( in==0 ){
    err_pipe("Reading tree",zCmd);
    return zComment;
  }
  free(zCmd);

  zBase[0]=0;
  rc = fscanf(in, "%50[a-fA-F0-9]", zBase);
  pclose(in);

  if( 1==rc ){
    char zCommit[PATH_MAX];

    /* Got a common base for the merge.
    ** What we do with this is get the list of object/cn pairs from
    ** zRemote to zBase's parent and tack them on to the comment.
    */
    if( zModule[0] ) {
      snprintf(zCommit,sizeof(zCommit),"%s:%s",zModule,zRemote);
    } else {
      strncpy(zCommit,zRemote,sizeof(zCommit));
      zCommit[sizeof(zCommit)-1] = 0;
    }
    while( zCommit[0] && strcmp(zCommit,zBase) ){
      char *z;
      char **azChng = db_query("SELECT cn,prevvers FROM filechng "
                               "WHERE vers='%q' LIMIT 1", zCommit);
      if( azChng[0]==0 ) break; /* maybe pruned? */
      
      z = mprintf("[%d], %s", atoi(azChng[0]), zComment);
      if(zComment) free(zComment);
      zComment = z;

      strncpy(zCommit, azChng[1] ? azChng[1] : "", sizeof(zCommit));
      zCommit[sizeof(zCommit)-1] = 0;

      nMerged ++;
    }
  }

  if( nMerged ){
    /* Prefix the message with "Merged" */
................................................................................

  return nNew + 1;
}

/*
** Read in the git references and turn them into new CHNG records.
*/
static int git_read_refs(const char *zGitDir, const char* zModule){
  FILE *in;
  int nCommits = 0;
  char zLine[PATH_MAX+200];
  char zObject[100];
  char zName[PATH_MAX];
  char *zCmd;
  char *zOldObject;
  const char* zFormat = "%(objecttype) %(objectname) %(*objectname) %(refname)";

  zCmd = mprintf("GIT_DIR='%s/%s' git for-each-ref --format='%s' 2>&1",
                 zGitDir, zModule ? zModule : "", zFormat);
  if( zCmd==0 ) return 0;

  in = popen(zCmd, "r");
  if( in==0 ){
    err_pipe("Reading refs",zCmd);
    free(zCmd);
    return 0;
................................................................................
    } else if( !strncmp(zLine, "tag ", 4) ){
      /* The first SHA is the tag, we want the second */
      sscanf(&zLine[4], "%*[0-9a-fA-F] %50[0-9a-fA-F] %[^\n]", zObject, zName );
    } else {
      /* some kind of newfangled refs? */
      continue;
    }

    /* Things are easier if we build the working object now */
    if( zModule ) {
      char zTemp[PATH_MAX];
      strcpy( zTemp, zName );
      snprintf(zName,sizeof(zName),"%s:%s", zModule, zTemp);
      strcpy( zTemp, zObject );
      snprintf(zObject,sizeof(zObject),"%s:%s", zModule, zTemp);
    }

    /* It'd be nice to delete refs which go away, so keep track of the
    ** ones we've seen.
    */
    db_execute("INSERT INTO seenrefs(name) VALUES('%q')", zName );

    /*
................................................................................
      /* ingest the commit tree */
      nCommits += git_ingest_commit_tree(zGitDir,zObject);
    }

    if(zOldObject) free(zOldObject);
  }

  if( pclose(in) ) {
    err_pipe("Closing read_refs pipe",zLine);
    return 0;
  }

  return nCommits;
}

static void git_update_refs(){
  int i;
  char **azRefs;
................................................................................
**
** If any errors occur, output an error page and exit.
**
** If the "isReread" flag is set, it means that the history file is being
** reread to pick up changes that we may have missed earlier. Probably
** futile with GIT, since we can only work back from known refs and we
** pick those up automatically, anyways.
**
** Multiple module handling... if cvsroot isn't a git repo in itself,
** we treat it as a directory of git repos. Functionally, this should
** Just Work, but to disambiguate we need to ensure that all object
** identifiers (heads, tags, commits, etc) are prefixed by the module
** name.
*/
static int git_history_update(int isReread){
  const char *zRoot;
  int nOldSize = 0;
  int nNewRevs;
  char zCmd[PATH_MAX];

  db_execute("BEGIN");

  /* Get the path to local repository and last revision number we have in db
   * If there's no repository defined, bail and wait until the admin sets one.
  */
  zRoot = db_config("cvsroot","");
................................................................................
    /* Ref checks should be really quick since they happen on every update.
    ** This index improves performance by at least an order of magnitude.
    */
    db_execute("CREATE INDEX IF NOT EXISTS git_idx1 ON chng(branch,directory)");
    db_execute("CREATE INDEX IF NOT EXISTS git_idx2 ON filechng(vers)");
  }

  /* Is it a standalone repo, or a module tree? */
  snprintf(zCmd,sizeof(zCmd),
           "GIT_DIR='%s' git rev-parse >/dev/null 2>&1", zRoot);
  int nRes = system(zCmd);
  if( nRes ) {
    /* Something went wrong, likely not a git repo. Process it as a tree
    ** of repositories.
    */
    struct dirent **namelist;
    int n, i;
    n = scandir( zRoot, &namelist, NULL, alphasort );
    if( n < 0 ) {
      err_pipe("Looking for modules","scandir()");
      db_execute("COMMIT");
      return 0;
    }
    for( i = 0; i < n; i ++ ) {
      if( !(namelist[i]->d_type&(DT_UNKNOWN|DT_DIR)) ) continue;
      if( namelist[i]->d_name[0] == '.' ) continue;

      /* Note that this is going to fail if they're not bare repos */
      nNewRevs += git_read_refs(zRoot,namelist[i]->d_name);
    }
    free(namelist);
  } else {
    /* It's a git repository, which means we're likely not using
    ** multiple modules. Read the refs and ingest the commit tree.
    */
    nNewRevs = git_read_refs(zRoot,NULL);
  }

  if( nNewRevs==0 ) {
    /* Might be a little inefficient to call this each time, but since
    ** branching/tagging operations can be done independent from commits,
    ** and we _want_ to know about branches and tags, how else can we do
    ** it?
    */
................................................................................
  db_execute("COMMIT;");

  return 0;
}

static int git_history_reconstruct(void) {
  /* clean out refs */
  db_execute("DELETE FROM chng WHERE milestone>0 AND branch LIKE '%%refs/%%'");
  return 0;
}

/*
** Diff two versions of a file, handling all exceptions.
**
** If oldVersion is NULL, then this function will output the
** text of version newVersion of the file instead of doing
** a diff.
*/
static int git_diff_versions(
  const char *oldCommit,
  const char *newCommit,
  const char *zFile
){
  char *zCmd;
  FILE *in;
  const char *azSubst[16];
  const char *zTemplate;
  char zModule[PATH_MAX];
  char zOldVers[100];
  char zNewVers[100];

  parse_git_object( oldCommit, zModule, sizeof(zModule),
     zOldVers, sizeof(zOldVers) );
  parse_git_object( newCommit, zModule, sizeof(zModule),
     zNewVers, sizeof(zNewVers) );

  /* Find the command used to compute the file difference.*/
  azSubst[0] = "F";
  azSubst[1] = zFile;
  azSubst[2] = "V1";
  azSubst[3] = zOldVers;
  azSubst[4] = "V2";
  azSubst[5] = zNewVers;
  azSubst[6] = "RP";
  azSubst[7] = zModule[0]
    ? mprintf("%s/%s", db_config("cvsroot", ""), zModule)
    : db_config("cvsroot", "");
  azSubst[8] = "V";
  azSubst[9] = zNewVers;
  azSubst[10] = 0;
			  
  zTemplate = db_config(
    "filediff", 
    OS_VAL( "GIT_DIR='%RP' git diff --full-index "
              "-t -p -r '%V1' '%V2' -- '%F' 2>/dev/null",
	          "GIT_DIR=\"%RP\" git diff --full-index "
................................................................................
  pclose(in);
  
  return 0;
}

static char *git_get_blob(
  const char *zGitDir,
  const char* zModule,
  const char *zCommit,
  const char* zPath
){
  FILE *in;
  char zLine[PATH_MAX*2];
  char *zCmd;

  if( zCommit==0 || zCommit[0]==0 || zPath==0 || zPath[0]==0 ) return 0;
    
  zCmd = mprintf("GIT_DIR='%s/%s' git ls-tree -r '%s' '%s'",
                 zGitDir, zModule ? quotable_string(zModule) : "",
                 quotable_string(zCommit), quotable_string(zPath));
  in = popen(zCmd,"r");
  if( in==0 ){
    err_pipe("Reading tree",zCmd);
    return 0;
  }
  free(zCmd);

................................................................................
    if( !strcmp(zType,"blob") ){
      return strdup(zObject);
    }
  }
  return 0;
}

static int git_dump_version(const char *zCommit, const char *zFile,int bRaw){
  int rc = -1;
  char *zCmd;
  char zModule[PATH_MAX];
  char zVers[100];
  const char *zRoot = db_config("cvsroot","");
  const char *zBlob;

  parse_git_object(zCommit, zModule, sizeof(zModule), zVers, sizeof(zVers));

  zBlob = git_get_blob(zRoot, zModule, zVers, zFile);
  if( zBlob==0 ) return -1;

  zCmd = mprintf("GIT_DIR='%s/%s' git cat-file blob '%s' 2>/dev/null",

                zRoot, quotable_string(zModule), zBlob);
  rc = common_dumpfile( zCmd, zCommit, zFile, bRaw );
  free(zCmd);

  return rc;
}

static int git_diff_chng(int cn, int bRaw){
  char **azRev;
  char *zCmd;
  char zLine[2000];
  FILE *in;
  const char *azSubst[16];
  const char *zTemplate;
  char zModule[PATH_MAX];
  char zOld[100];
  char zNew[100];
  
  azRev = db_query("SELECT vers,prevvers FROM filechng WHERE cn=%d", cn);
  if( !azRev || !azRev[0] ) return -1; /* Invalid check-in number */

  parse_git_object( azRev[0], zModule, sizeof(zModule), zNew, sizeof(zNew));
  parse_git_object( azRev[1], zModule, sizeof(zModule), zOld, sizeof(zOld));
  
  /* Find the command used to compute the file difference.*/
  azSubst[0] = "F";
  azSubst[1] = "";
  azSubst[2] = "V1";
  azSubst[3] = zOld;
  azSubst[4] = "V2";
  azSubst[5] = zNew;
  azSubst[6] = "RP";
  azSubst[7] = zModule[0]
               ? mprintf("%s/%s", db_config("cvsroot", ""), zModule)
               : db_config("cvsroot", "");
  azSubst[8] = "V";
  azSubst[9] = azRev[0];
  azSubst[10] = 0;
			  
  zTemplate = 
    OS_VAL( "GIT_DIR='%RP' git diff --full-index -t "
              "-p -r '%V1' '%V2' -- '%F' 2>/dev/null",
................................................................................
    @ </div>
  }
  pclose(in);
  
  return 0;
}

static int git_download(const char *zDir, const char *zCommit, int compression){
  FILE *in;
  char *zCmd, zBuf[1024], *zCompression = "", *zFormat = "";
  char zModule[PATH_MAX];
  char zRev[100];
  size_t nRead;
  const char *zRoot = db_config("cvsroot","");
  if( zRoot[0]==0 ) return -1;

  /* Note: if zCommit==NULL (which is technically allowed), things will
  ** probably not work well.
  */
  parse_git_object( zCommit, zModule, sizeof(zModule), zRev, sizeof(zRev) );

  switch( compression ){
  case COMPRESSION_TAR:
    zFormat = "--format=tar";
    break;
  case COMPRESSION_TAR_GZ:
    zFormat = "--format=tar";
    zCompression = " | gzip";
................................................................................
  case COMPRESSION_ZIP:
    zFormat = "--format=zip";
    break;
  default:
    return -1;
  }

  zCmd = mprintf(OS_VAL("GIT_DIR='%s/%s' git archive %s %s %s%s 2>/dev/null",
                        "GIT_DIR=\"%s/%s\" git archive %s %s %s%s 2>NUL"),
                        quotable_string(zRoot),
                        quotable_string(zModule),
                        quotable_string(zFormat),
                        zCommit ? quotable_string(zRev) : "HEAD",
                        zDir ? quotable_string(zDir) : "",
                        quotable_string(zCompression));
  in = popen(zCmd, OS_VAL("r","rb"));
  if( in==0 ){
    return -1;
  }
  free(zCmd);

Changes to timeline.c.

864
865
866
867
868
869
870




871
872





873
874
875
876
877
878
879
/*
** Make a file revision number printable. Mostly needed for things like
** git where object identifiers are insanely long.
*/
char *printable_vers(const char *zVers){
  if( zVers ){
    int nLen = strlen(zVers);




    if( nLen<=10 ){
      return mprintf("<span class=\"vers\">%h</span>",zVers);





    }
    return mprintf("<span class=\"vers\" title=\"%h\">%8.8h</span>",
                   zVers, zVers );
  }else{
    return strdup("");
  }
}







>
>
>
>


>
>
>
>
>







864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
/*
** Make a file revision number printable. Mostly needed for things like
** git where object identifiers are insanely long.
*/
char *printable_vers(const char *zVers){
  if( zVers ){
    int nLen = strlen(zVers);
    const char* zC = strchr(zVers,':');
    if( zC ) {
      nLen = strlen(zC+1);
    }
    if( nLen<=10 ){
      return mprintf("<span class=\"vers\">%h</span>",zVers);
    } else if( zC ) {
      return mprintf("<span class=\"vers\" title=\"%h\">%*.*h</span>",
                     zVers,
                     (zC - zVers) + 9, (zC - zVers) + 9,
                     zVers );
    }
    return mprintf("<span class=\"vers\" title=\"%h\">%8.8h</span>",
                   zVers, zVers );
  }else{
    return strdup("");
  }
}