Handmade Hero » Forums » Code » Casey'a Version Control System
e1211
The_8th_mage
71 posts
1 project
#4449 Casey'a Version Control System
2 years ago

Does the publicly available version of cmirror can mark the file with the date and version numbers in your file header?
cmuratori
Casey Muratori
802 posts
1 project

Casey Muratori is a programmer at Molly Rocket on the game 1935 and is the host of the educational programming series Handmade Hero.

#4459 Casey'a Version Control System
2 years ago

I don't remember. It may have still been external at that point (ckeyword). It's builtin nowadays.

- Casey
drjeats
29 posts

I enabled the dark theme.

#4491 Casey'a Version Control System
2 years ago Edited by drjeats on Aug. 4, 2015, 6:32 a.m.

Pseudonym73
Not to blow my own horn, but I have the reputation around the office as the person who can unbork a repository after a broken rebase. So definitely use Git; it means more job security for me.


Lol, this is me at work too. It kinda sucks though, because it usually stems from people blindly doing commit, pull, resolve, push. Our team's small, so it doesn't cause massive problems, but then this also means that our history looks like it's 50% merge commits because no one else sees value in learning to do interactive rebase, or anything beyond commit, pull, push, merge, and checkout --theirs/--ours. I'd be grumpy about it if I didn't fundamentally agree with that sentiment.

If version control systems are on the shit-to-fix list along with editors, OSes, and languages/compilers, can I submit feature requests?

  • - Local commits
  • - Let me flag files for temporarily ignoring changes (e.g. git update --skip-worktree) until I try to sync with a change to that file, at which point let me revert all my local changes
  • - Another flag, as an alternate to the one above, that always merges others' changes without prompting me
  • - Sanely-named commands that don't require a dozens of blog posts talking about graph operations to explain them to someone
  • - Large-file-friendly by default
  • - Open source
  • - First-class Windows support from the get-go
  • - SVN / Perforce style subdirectory checkout
  • - Hip Web4.0 company that offers repo hosting and cutesy javascript interfaces to browse your files. Scandals optional.
e1211
The_8th_mage
71 posts
1 project
#4509 Casey'a Version Control System
2 years ago

sorry for all the questions about it, but i can't understand how do i deploy the code at a new computer using cmirror, or returning to a earlier version using it. can it even be done?
Mephisztoe
Christian Jacob
11 posts
#4978 Casey'a Version Control System
1 year, 10 months ago

I like your humor. :)

>> It's builtin nowadays

This sounds as if it was available publicitly. Is it? I don't know. The binary download is tagged with internal Version 740 and version number 1.3c, while the source is tagged with 744 and also 1.3c. However, both doesn't seem to know about ckeyword and on your Website this probably Handy tool is not available for download.

Is it possible that you make the standalone ckeyword tool available as well, or update the cmirror Version that is publicitly available for download?

Cheers,
Christian.
Mephisztoe
Christian Jacob
11 posts
#5002 Casey'a Version Control System
1 year, 10 months ago

Just re-read my post and noted that it sounded sort of harsh. Didn't mean it to be. :) Actually cmirror's just saving me from deep trouble, so I am really glad that it exists; however, I'd be even more glad if ckeyword was available as well. :D
cmuratori
Casey Muratori
802 posts
1 project

Casey Muratori is a programmer at Molly Rocket on the game 1935 and is the host of the educational programming series Handmade Hero.

#5005 Casey'a Version Control System
1 year, 10 months ago

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 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
 95
 96
 97
 98
 99
100
101
102
103
104
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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
204
205
206
207
208
209
210
211
212
213
214
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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
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
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
345
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
/* ========================================================================
   $File: tools/ckeyword/ckeyword.cpp $
   $Date: 2007/04/12 03:22:15AM $
   $Revision: 7 $
   $Creator: Casey Muratori $
   $Notice: $
   ======================================================================== */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <time.h>

#include <sys/stat.h>

#define CMIRROR_MAX_PATH 4096

struct keyword_context
{
    bool SuspectedBinary;
    bool ErrorOnRead;
    bool ErrorOnWrite;
    bool TypoSuspected;
    bool ReplacedSomething;

    bool UseFileKeyword;
    bool UseNoticeKeyword;
    bool UseDateKeyword;
    bool UseRevisionKeyword;

    FILE *SourceFile;
    FILE *DestFile;

    char *SourceFileName;
    char *SourceRepositoryRelativeName;
    char *NoticeText;
    int SourceRevisionIndex;
    struct stat SourceStat;
};

static int
Input(keyword_context &Context)
{
    int Char = fgetc(Context.SourceFile);
    if(Char == EOF)
    {
        if(ferror(Context.SourceFile))
        {
            Context.ErrorOnRead = true;
        }
    }
    else
    {
        if(((Char >= 0) && (Char <= 9)) ||
           ((Char >= 16) && (Char <= 31)))
        {
            Context.SuspectedBinary = true;
        }
    }

    return(Char);
}

static void
Output(keyword_context &Context, int Char)
{
    if(Char != -1)
    {
        if(fputc(Char, Context.DestFile) == EOF)
        {
            Context.ErrorOnWrite = true;
        }
    }
}

static bool
EndOfFile(keyword_context &Context)
{
    return(feof(Context.SourceFile) != 0);
}

static void
SkipToNextDollarsign(keyword_context &Context, bool StopAtNewline)
{
    int Char = 0;
    while(!EndOfFile(Context) && (Char != '$'))
    {
        Char = Input(Context);
        if(StopAtNewline && ((Char == '\n') || (Char == '\r')))
        {
            Context.TypoSuspected = true;
            
            Output(Context, Char);
            break;
        }
    }

    if(Char != '$')
    {
        Context.TypoSuspected = true;
    }
}

static void
MatchAnchor(keyword_context &Context, bool StopAtNewline)
{
    long OriginalAt = ftell(Context.SourceFile);
    if(OriginalAt >= 0)
    {
        char Keyword[32];
        int MaxKeywordLength = (sizeof(Keyword) / sizeof(Keyword[0]));
        {for(int CharIndex = 0;
             CharIndex < MaxKeywordLength;
             ++CharIndex)
        {
            Keyword[CharIndex] = EndOfFile(Context) ? '\0' : (char)Input(Context);
        }}
        Keyword[MaxKeywordLength - 1] = '\0';
        if(fseek(Context.SourceFile, OriginalAt, SEEK_SET) != 0)
        {
            Context.ErrorOnRead = true;
        }
        
        if(Context.UseFileKeyword &&
           ((strncmp(Keyword, "File: ", 6) == 0) ||
            (strncmp(Keyword, "RCSFile: ", 9) == 0)))
        {
            Context.ReplacedSomething = true;

            fprintf(Context.DestFile, "$File: %s $", Context.SourceRepositoryRelativeName);
            SkipToNextDollarsign(Context, StopAtNewline);
        }
        else if(Context.UseNoticeKeyword &&
                (strncmp(Keyword, "Notice: ", 8) == 0))
        {
            Context.ReplacedSomething = true;

            fprintf(Context.DestFile, "$Notice: ");
            {for(char *I = Context.NoticeText;
                 *I;
                 ++I) 
            {
                if((*I == '\\') &&
                   (*(I + 1) == 'n'))
                {
                    fprintf(Context.DestFile, "\r\n");
                    ++I;
                }
                else
                {
                    fputc(*I, Context.DestFile);
                }
            }}
            fprintf(Context.DestFile, " $");
            SkipToNextDollarsign(Context, StopAtNewline);
        }
        else if(Context.UseDateKeyword &&
                (strncmp(Keyword, "Date: ", 6) == 0))
        {
            Context.ReplacedSomething = true;

            struct tm *TM = localtime(&Context.SourceStat.st_mtime);
            int Hour = 12;
            char *AMPM = "AM";
            if(TM->tm_hour >= 1)
            {
                Hour = TM->tm_hour;
            }
            if(TM->tm_hour >= 12)
            {
                AMPM = "PM";
            }
            if(TM->tm_hour >= 13)
            {
                Hour -= 12; 
            }    
            
            fprintf(Context.DestFile, "$Date: %04d/%02d/%02d %02d:%02d:%02d%s $",
                    TM->tm_year + 1900,
                    TM->tm_mon + 1,
                    TM->tm_mday,
                    Hour,
                    TM->tm_min,
                    TM->tm_sec,
                    AMPM);
            SkipToNextDollarsign(Context, StopAtNewline);
        }
        else if(Context.UseRevisionKeyword &&
                (strncmp(Keyword, "Revision: ", 10) == 0))
        {
            Context.ReplacedSomething = true;

            fprintf(Context.DestFile, "$Revision: %d $", Context.SourceRevisionIndex);
            SkipToNextDollarsign(Context, StopAtNewline);
        }
        else
        {
            Output(Context, '$');
        }
    }
    else
    {
        Context.ErrorOnRead = true;
    }
}

void
ProcessFile(keyword_context &Context)
{
    int InsideCComment = 0;
    bool InsideCPPComment = false;
    
    while(!EndOfFile(Context))
    {
        int Char = Input(Context);
        if((InsideCComment > 0) || InsideCPPComment)
        {
            if(Char == '$')
            {
                MatchAnchor(Context, InsideCComment <= 0);
            }
            else
            {
                if(Char == '*')
                {                
                    Output(Context, Char);
                    Char = Input(Context);
                    if(Char == '/')
                    {
                        --InsideCComment;
                    }
                }
                else if((Char == '\n') || (Char == '\r'))
                {
                    InsideCPPComment = false;
                }

                Output(Context, Char);
            }
        }
        else
        {
            if(Char == '/')
            {
                Output(Context, Char);
                Char = Input(Context);
                if(Char == '/')
                {
                    InsideCPPComment = true;
                }
                else if(Char == '*')
                {
                    ++InsideCComment;
                }
            }

            Output(Context, Char);
        }
    }
}

int
Go(keyword_context &Context)
{
    int Result = -1;

    char DeleteFileName[CMIRROR_MAX_PATH];
    sprintf(DeleteFileName, "%s_DELETE", Context.SourceFileName);

    char DestFileName[CMIRROR_MAX_PATH];
    sprintf(DestFileName, "%s_KEYWORDS", Context.SourceFileName);
    
    stat(Context.SourceFileName, &Context.SourceStat);
    Context.SourceFile = fopen(Context.SourceFileName, "rb");
    Context.DestFile = fopen(DestFileName, "wb");
    if(Context.SourceFile && Context.DestFile)
    {
        ProcessFile(Context);

        Result = 0;

        if(Context.SuspectedBinary)
        {
            printf("CKeyword: file appears to binary.\n");
            Result = -1;
        }

        if(Context.ErrorOnRead)
        {
            printf("CKeyword: error reading file %s.\n", Context.SourceFileName);
            Result = -1;
        }

        if(Context.ErrorOnWrite)
        {
            printf("CKeyword: error writing file.\n");
            Result = -1;
        }

        if(Context.TypoSuspected)
        {
            printf("CKeyword: typo suspected.\n");
            Result = -1;
        }
    }
    else
    {
        printf("CKeyword: Cannot open files\n");
    }
    fclose(Context.SourceFile);
    fclose(Context.DestFile);

    if((Result == 0) && Context.ReplacedSomething)
    {
        if(rename(Context.SourceFileName, DeleteFileName) == 0)
        {
            if(rename(DestFileName, Context.SourceFileName) == 0)
            {
                unlink(DeleteFileName);
            }
            else
            {
                printf("CKeyword: Unable to replace source file with keyword-stamped source file.\n");
                rename(DeleteFileName, Context.SourceFileName);
                unlink(DestFileName);
            }
        }
        else
        {
            printf("CKeyword: Unable to move source file for keyword replacement.\n");
            unlink(DestFileName);
        }
    }
    else
    {
        unlink(DestFileName);
    }

    return(Result);
}

int
main(int ArgCount, char **Args)
{
    int Result = -1;

    keyword_context Context = {0};
        
    if(ArgCount == 4)
    {
        Context.SourceFileName = Args[1];
        Context.SourceRepositoryRelativeName = Args[2];
        Context.SourceRevisionIndex = atoi(Args[3]);

        Context.UseFileKeyword = true;
        Context.UseDateKeyword = true;
        Context.UseRevisionKeyword = true;

        Result = Go(Context);
    }
    else if(ArgCount == 3)
    {
        Context.SourceFileName = Args[1];
        Context.NoticeText = Args[2];

        Context.UseNoticeKeyword = true;

        Result = Go(Context);
    }
    else
    {
        printf("CKeyword: Needs either 2 or 3 arguments\n");
    }

    return(Result);
}


- Casey
RJTracer
Lachlan Easton
14 posts

Technically a Programmer

#5044 Casey'a Version Control System
1 year, 9 months ago

Suddenly a relevant xkcd was called into existence.
arnir
1 posts
#8811 Casey'a Version Control System
10 months, 2 weeks ago

Unfortunately, Cmirror has been removed from mollyrocket.com.
Is someone able to upload the source code and binary files somewhere else, for example github?
Thanks in advance.
timothy.wright
Timothy Wright
75 posts
1 project
#8839 Casey'a Version Control System
10 months, 2 weeks ago

Why is this not a Handmade project? I would drop git faster than you can say "git commit -am 'wtf'".
Borealis
8 posts
#9059 Casey'a Version Control System
9 months, 4 weeks ago

Casey, would you mind making the source code of CMirror publicly available again? Even version 1.3c from the old website would be fine, in case you don't want to share the latest one.
Mephisztoe
Christian Jacob
11 posts
#12532 Casey'a Version Control System
1 month ago

I kindly asked Casey to provide the sources for cmirror again. He states that the latest release was public domain so anyone can virtually do anything with it - even share. However, since I actually seem to have lost the archive, I asked him to see if he could send it to me, since the page on mollyrocket.com is gone and even archive.org did not backup the zip files.

However, Casey left v1 behind and did a complete rework of cmirror. So he is using v2 (which seems to be not public domain) and since he also wrote the tool only for himself, he does not know if the v1 Version is still lying somewhere around.

Can't blame him for that. :)

However, maybe he finds it and sends me the archive. If this is the case, I will put up a public guthub repository and notify you guys.

Also, if anyone of you happens to have the source archive, please let me know. :)

Cheers,
Christian.
godratio
Ray Garner
40 posts
#12535 Casey'a Version Control System
1 month ago

Would love to get my grubby little hands on that.
Borealis
8 posts
#12536 Casey'a Version Control System
1 month ago

Here's a copy of the original archive:

https://www.dropbox.com/s/dkme78wv3qn9ap5/744.zip?dl=1
godratio
Ray Garner
40 posts
#12539 Casey'a Version Control System
1 month ago

Thanks you little code archiver you.
Was a good code read.