mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
418 lines
13 KiB
418 lines
13 KiB
// out - checkout the specified files
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
#include "messages.h"
|
|
EnableAssert
|
|
|
|
#define cchPrompt 17
|
|
|
|
private void MarkIn(P3(AD *pad, NE *pneFiles, NE **pneBroken));
|
|
private F FOutMarked(P1(AD *));
|
|
private F FMultiOut(P2(AD *, FI far *));
|
|
private F FOutOldFiles(P2(AD *, NE *));
|
|
|
|
F
|
|
FOutInit(
|
|
AD *pad)
|
|
{
|
|
CheckProjectDiskSpace(pad, cbProjectFreeMin);
|
|
|
|
if (pad->flags & flagOutCopy) {
|
|
// check that no times were specified
|
|
if (FAnyFileTimes(pad->pneArgs) || pad->tdMin.tdt != tdtNone) {
|
|
Error("cannot use -c with any time specification\n");
|
|
Usage(pad);
|
|
}
|
|
}
|
|
|
|
// If out -b, clear fFiles flag, so we are called for each directory,
|
|
// with an empty pad->pneFiles.
|
|
|
|
if (pad->flags&flagOutBroken)
|
|
pad->pecmd->gl &= ~fglFiles;
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
F
|
|
FOutDir(
|
|
AD *pad)
|
|
{
|
|
F fOk;
|
|
NE *pneFiles;
|
|
NE *pneBroken;
|
|
LCK lck;
|
|
|
|
// if the user specified -n, don't allow this operation while others
|
|
// are ssync'ing or out'ing.
|
|
|
|
lck = (pad->flags&flagOutSerial) ? lckAll : lckEd;
|
|
|
|
if (!FLoadStatus(pad, lck, flsNone) || !FHaveCurDir(pad))
|
|
return fFalse;
|
|
|
|
// check out the specified files which are checked-in and read-only,
|
|
// make a list of the other files
|
|
|
|
if (pad->pneFiles) {
|
|
pneFiles = pad->pneFiles;
|
|
} else {
|
|
AssertF(pad->flags&flagOutBroken);
|
|
pneFiles = PneLstBroken(pad);
|
|
}
|
|
MarkIn(pad, pneFiles, &pneBroken);
|
|
fOk = FSyncMarked(pad, NULL) && FOutMarked(pad);
|
|
|
|
// check out the other files (check out an old version of a file,
|
|
// or retroactively check out a broken-linked "checked-in" file.
|
|
|
|
if (fOk)
|
|
fOk = FOutOldFiles(pad, pneBroken);
|
|
|
|
FreeNe(pneBroken);
|
|
if (!pad->pneFiles)
|
|
FreeNe(pneFiles);
|
|
|
|
// Check if the local version files should be updated.
|
|
CheckLocalVersion(pad);
|
|
SyncVerH(pad, NULL);
|
|
|
|
FlushStatus(pad);
|
|
return fOk;
|
|
}
|
|
|
|
|
|
// Mark those files in pneFiles which can be simply checked out.
|
|
// Return (in *ppneList) a new list of those files which are broken linked or
|
|
// specify a particular old version.
|
|
|
|
private void
|
|
MarkIn(
|
|
AD *pad,
|
|
NE *pneFiles,
|
|
NE **ppneList
|
|
)
|
|
{
|
|
NE *pne;
|
|
FI far *pfi;
|
|
FS far *pfs;
|
|
F fExists;
|
|
NE **ppneLast;
|
|
|
|
*ppneList = 0;
|
|
InitAppendNe(&ppneLast, ppneList);
|
|
|
|
UnMarkAll(pad);
|
|
|
|
ForEachNe(pne, pneFiles) {
|
|
if (!FLookupSz(pad, SzOfNe(pne), &pfi, &fExists)) {
|
|
if (!pne->fWild)
|
|
Warn("%&C/%s does not exist\n", pad, SzOfNe(pne));
|
|
continue;
|
|
}
|
|
|
|
if (pfi->fk == fkVersion) {
|
|
Warn("can't check out version file %&C/F\n", pad, pfi);
|
|
continue;
|
|
}
|
|
|
|
pfs = PfsForPfi(pad, pad->iedCur, pfi);
|
|
|
|
switch (pfs->fm) {
|
|
default:
|
|
FatalError(szBadFileFormat, pad, pfi);
|
|
|
|
case fmGhost:
|
|
case fmIn:
|
|
case fmCopyIn:
|
|
case fmAdd:
|
|
if (pad->flags & flagOutCopy) {
|
|
Error("%&C/F is not checked out\n", pad, pfi);
|
|
} else
|
|
if (pfi->fk == fkDir) {
|
|
AssertF(pfs->fm != fmCopyIn);
|
|
Error("%&C/F not checked out; it is a directory\n", pad, pfi);
|
|
} else
|
|
if (!FMultiOut(pad, pfi)) {
|
|
// can't check out, already multiply checked out
|
|
;
|
|
} else
|
|
if (pne->u.tdNe.tdt != tdtNone) {
|
|
AppendNe(&ppneLast, PneCopy(pne));
|
|
} else
|
|
if (pfs->fm != fmAdd && pfs->fm != fmGhost && FBroken(pad,pfi,pfs,fTrue)) {
|
|
AppendNe(&ppneLast, PneCopy(pne));
|
|
} else
|
|
if (pfs->fm == fmCopyIn) {
|
|
if (pad->flags&flagOutBroken) {
|
|
Warn("checking out %&F@v%d\n", pad, pfi, pfs->fv);
|
|
AppendNe(&ppneLast, PneCopy(pne));
|
|
} else
|
|
if ((pad->flags & flagOutCurrent) == 0 &&
|
|
FQueryApp("new version of %&C/F is available",
|
|
"update now", pad, pfi)) {
|
|
// do the copyin -> in transition here
|
|
// so we don't have to copy the file
|
|
// twice. This code depends on the
|
|
// code in FSyncMarked for fmCopyIn.
|
|
|
|
pfs->fm = fmIn;
|
|
pfi->fMarked = fTrue;
|
|
pfs->fv = pfi->fv;
|
|
} else
|
|
if ((pad->flags & flagOutCurrent) != 0 ||
|
|
FQueryUser("check out %&C/F@v%d? ", pad, pfi, pfs->fv)) {
|
|
AppendNe(&ppneLast, PneCopy(pne));
|
|
}
|
|
} else {
|
|
if (pfs->fm == fmGhost)
|
|
pfs->fm = fmAdd;
|
|
pfi->fMarked = fTrue;
|
|
}
|
|
break;
|
|
|
|
case fmDelIn:
|
|
case fmDelOut:
|
|
Error("%&C/F is to be deleted\n", pad, pfi);
|
|
break;
|
|
|
|
case fmOut:
|
|
case fmMerge:
|
|
case fmVerify:
|
|
case fmConflict:
|
|
if (pad->flags & flagOutCopy)
|
|
pfi->fMarked = fTrue;
|
|
else
|
|
Error("%&C/F already checked out\n", pad, pfi);
|
|
break;
|
|
|
|
case fmNonExistent:
|
|
Error("%&C/F no longer exists\n", pad, pfi);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// return fTrue if no files are checked out, or if the user indicates we can
|
|
// checkout the file regardless
|
|
private F
|
|
FMultiOut(
|
|
AD *pad,
|
|
FI far *pfi
|
|
)
|
|
{
|
|
char sz[2*cchLineMax];
|
|
|
|
if (pfi->fk == fkText || pfi->fk == fkUnicode)
|
|
SzPrint(sz, "%&C/F checked out to ", pad, pfi);
|
|
else
|
|
SzPrint(sz, "%&C/F (%s, can't merge) checked out to ", pad, pfi,
|
|
mpfksz[pfi->fk]);
|
|
|
|
return (!FOutUsers(sz, sizeof(sz)-cchPrompt, pad, pfi) ||
|
|
(pad->flags & flagOutCurrent) != 0 ||
|
|
FQueryApp("%s", "checkout anyway", sz));
|
|
}
|
|
|
|
|
|
// checkout the marked files
|
|
private F
|
|
FOutMarked(
|
|
AD *pad)
|
|
{
|
|
register FI far *pfi;
|
|
register FS far *pfs;
|
|
register FI far *pfiMac;
|
|
|
|
// The marked files were synced and must be fmIn or an out mode
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++) {
|
|
if (!pfi->fMarked)
|
|
continue;
|
|
|
|
pfs = PfsForPfi(pad, pad->iedCur, pfi);
|
|
|
|
switch(pfs->fm) {
|
|
default:
|
|
FatalError(szBadFileFormat, pad, pfi);
|
|
|
|
case fmIn:
|
|
pfs->fm = fmOut;
|
|
LocalCopy(pad, pfi);
|
|
break;
|
|
|
|
case fmOut:
|
|
// must be out -c
|
|
AssertF(pad->flags&flagOutCopy);
|
|
LocalCopy(pad, pfi);
|
|
break;
|
|
|
|
case fmMerge:
|
|
case fmVerify:
|
|
case fmConflict:
|
|
// must be out -c
|
|
AssertF(pad->flags&flagOutCopy);
|
|
Warn("restoring %&C/F to %&F@v%d\n", pad, pfi,
|
|
pad, pfi, pfs->fv);
|
|
LocalBase(pad, pfi, pfs, fFalse);
|
|
pfs->fm = fmMerge;
|
|
break;
|
|
}
|
|
}
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
// "Send me your tired old files, your broken files, your huddled masses
|
|
// yearning to be checked out..."
|
|
|
|
static char szBroke[] = "%&C/F is writable and may have changed before check out";
|
|
|
|
private F
|
|
FOutOldFiles(
|
|
AD *pad,
|
|
NE *pneList
|
|
)
|
|
{
|
|
NE *pne;
|
|
F fOK = fTrue;
|
|
|
|
ForEachNe(pne, pneList) {
|
|
FI far *pfi;
|
|
FS far *pfs;
|
|
F fExists;
|
|
F fTime;
|
|
F fNeedLocalBase = fFalse;
|
|
TD td;
|
|
BI bi;
|
|
PTH pthBase[cchPthMax];
|
|
char szFile[cchFileMax+1];
|
|
FV fv;
|
|
|
|
// Find pfi and pfs of file. We have already checked it
|
|
// has an "in" mode.
|
|
|
|
if (!FLookupSz(pad, SzOfNe(pne), &pfi, &fExists))
|
|
AssertF(fFalse);
|
|
pfs = PfsForPfi(pad, pad->iedCur, pfi);
|
|
AssertF(pfs->fm == fmIn || pfs->fm == fmCopyIn ||
|
|
pfs->fm == fmAdd || pfs->fm == fmGhost);
|
|
|
|
td = pne->u.tdNe;
|
|
fTime = td.tdt != tdtNone;
|
|
if (!fTime) {
|
|
// No time, might be broken link or just a copy-in
|
|
// file. Do a LocalCopy if not doing out -b.
|
|
|
|
if (FBroken(pad, pfi, pfs, fTrue)) {
|
|
// make the users current broken-link version
|
|
// the checked out copy with BreakFi.
|
|
|
|
if (pad->flags&flagOutBroken) {
|
|
Warn("local version of %&C/F has probably been changed\n", pad, pfi);
|
|
|
|
// if version user has isn't in ssync, then warn and give
|
|
// current version number for the file.
|
|
|
|
if (pfs->fv != pfi->fv)
|
|
Warn("checking out %&F@v%d\n", pad, pfi, pfs->fv);
|
|
BreakFi(pad, pfi);
|
|
} else {
|
|
// File is broken && not -b. First ask the user if he
|
|
// wants to check out the private version as is. If
|
|
// he answers no, we ask if he wants us to check out the
|
|
// current master version. If not, no action occurrs.
|
|
|
|
if (FQueryApp(szBroke, "check out local version", pad, pfi)) {
|
|
Warn("checking out %&F@v%d\n", pad, pfi, pfs->fv);
|
|
BreakFi(pad, pfi);
|
|
} else
|
|
if (FQueryApp(szBroke, "check out current master version", pad, pfi)) {
|
|
LocalCopy(pad, pfi);
|
|
pfs->fv = pfi->fv;
|
|
} else {
|
|
// no action, go to next file
|
|
Warn("%&C/F not checked out\n", pad, pfi);
|
|
continue;
|
|
}
|
|
}
|
|
} else {
|
|
// for security in this case, we'd like to ensure that the
|
|
// user gets a fresh copy of the base file. Since the base
|
|
// file does not yet exist, we set the fNeedLocalBase flag for
|
|
// later use.
|
|
|
|
fNeedLocalBase = fTrue;
|
|
}
|
|
|
|
if (pfs->fm == fmIn) {
|
|
// no need to generate base, file is current
|
|
AssertF(pfs->fv == pfi->fv);
|
|
pfs->fm = fmOut;
|
|
continue;
|
|
}
|
|
|
|
if (pfi->fk == fkUnrec && pad->flags&flagOutBroken) {
|
|
Warn("base file for %&C/F could not be generated\n", pad, pfi);
|
|
pfs->fm = fmOut;
|
|
continue;
|
|
}
|
|
|
|
// base file to generate is file@v<pfs->fv>
|
|
td.tdt = tdtFV;
|
|
td.u.fv = pfs->fv;
|
|
}
|
|
|
|
// Generate base file and set mode to merge. Note that we use
|
|
// FUnmergeSrc to determine the fv. It would be possible to compare
|
|
// this fv with the current pfs->fv and pfi->fv, to perhaps avoid
|
|
// putting the file into the merge state.
|
|
//
|
|
// I don't do this for two reasons:
|
|
// 1. It is always safe, if not maximally efficient, to put the user
|
|
// into the merge state, and avoids any problems which could occur
|
|
// if the log file isn't entirely intact due to lossage/trunclogs.
|
|
// 2. It is unlikely somebody will be silly enough to check out the
|
|
// current version by time.
|
|
|
|
// Actually, it turned out 2. was an incorrect assumption and
|
|
// subsequently 1., while "safe", was a performance hit. Consequently,
|
|
// we now only do what is necessary.
|
|
|
|
bi = BiAlloc(pad);
|
|
PthForBase(pad, bi, pthBase);
|
|
SzCopyNm(szFile, pfi->nmFile, cchFileMax);
|
|
if (FUnmergeSrc(pad, szFile, td, &fv, permRO, pthBase)) {
|
|
// BUG: if td.tdt != tdtFV, we are scanning backwards
|
|
// through the log for the fv. When we hit the right td,
|
|
// we return the last fv we saw -- this is actually fv+1.
|
|
// If we didn't encounter the file at all (ie: it hadn't
|
|
// changed since the given td), the fv is zero.
|
|
|
|
// REVIEW: fix fv == 0 bug.
|
|
if (fv && td.tdt != tdtFV)
|
|
fv--;
|
|
|
|
if (pfi->fv != fv)
|
|
pfs->fm = fmMerge;
|
|
else
|
|
pfs->fm = fmOut;
|
|
|
|
pfs->bi = bi;
|
|
pfs->fv = fv;
|
|
|
|
// if out -t (or out file@time), or !broken, copy file locally
|
|
|
|
if (fTime || fNeedLocalBase)
|
|
LocalBase(pad, pfi, pfs, fFalse);
|
|
} else {
|
|
Error("%&C/F not checked out\n", pad, pfi);
|
|
fOK = fFalse;
|
|
}
|
|
}
|
|
|
|
return fOK;
|
|
}
|