diff --git a/garden-service/src/commands/unlink/module.ts b/garden-service/src/commands/unlink/module.ts index 5904765fca..cd8f415d78 100644 --- a/garden-service/src/commands/unlink/module.ts +++ b/garden-service/src/commands/unlink/module.ts @@ -71,7 +71,7 @@ export class UnlinkModuleCommand extends Command { const linkedModuleSources = await removeLinkedSources({ garden, sourceType, names: modules }) - log.info(`Unlinked module(s) ${module}`) + log.info(`Unlinked module(s) ${modules.join(" ")}`) return { result: linkedModuleSources } } diff --git a/garden-service/src/commands/unlink/source.ts b/garden-service/src/commands/unlink/source.ts index 8f7f86f707..34d2f2fc68 100644 --- a/garden-service/src/commands/unlink/source.ts +++ b/garden-service/src/commands/unlink/source.ts @@ -71,7 +71,7 @@ export class UnlinkSourceCommand extends Command { const linkedProjectSources = await removeLinkedSources({ garden, sourceType, names: sources }) - log.info(`Unlinked source(s) ${sources}`) + log.info(`Unlinked source(s) ${sources.join(" ")}`) return { result: linkedProjectSources } } diff --git a/garden-service/src/garden.ts b/garden-service/src/garden.ts index ce68b20d37..bfc5f22075 100644 --- a/garden-service/src/garden.ts +++ b/garden-service/src/garden.ts @@ -220,6 +220,10 @@ export class Garden { const buildDir = await BuildDir.factory(projectRoot, gardenDirPath) const workingCopyId = await getWorkingCopyId(gardenDirPath) + // We always exclude the garden dir + const gardenDirExcludePattern = `${relative(projectRoot, gardenDirPath)}/**/*` + const moduleExcludePatterns = [...((config.modules || {}).exclude || []), gardenDirExcludePattern] + const garden = new this({ projectRoot, projectName, @@ -231,10 +235,10 @@ export class Garden { opts, plugins, providerConfigs: providers, + moduleExcludePatterns, workingCopyId, dotIgnoreFiles: config.dotIgnoreFiles, moduleIncludePatterns: (config.modules || {}).include, - moduleExcludePatterns: (config.modules || {}).exclude, }) as InstanceType return garden @@ -265,7 +269,9 @@ export class Garden { */ async startWatcher(graph: ConfigGraph) { const modules = await graph.getModules() - this.watcher = new Watcher(this, this.log, modules) + const linkedPaths = (await getLinkedSources(this)).map(s => s.path) + const paths = [this.projectRoot, ...linkedPaths] + this.watcher = new Watcher(this, this.log, paths, modules) } private registerPlugin(name: string, moduleOrFactory: RegisterPluginParam) { diff --git a/garden-service/src/util/ext-source-util.ts b/garden-service/src/util/ext-source-util.ts index 42a0f2e053..c64d42a8ac 100644 --- a/garden-service/src/util/ext-source-util.ts +++ b/garden-service/src/util/ext-source-util.ts @@ -60,14 +60,21 @@ export function isModuleLinked(module: Module, garden: Garden) { return !pathIsInside(module.path, garden.projectRoot) && !isPluginModule } -export async function getLinkedSources( - garden: Garden, - type: ExternalSourceType, -): Promise { +/** + * Returns an array of linked sources by type, as read from the local config store. + * Returns all linked sources if typed not specified. + */ +export async function getLinkedSources(garden: Garden, type?: ExternalSourceType): Promise { const localConfig = await garden.configStore.get() - return (type === "project" - ? localConfig.linkedProjectSources - : localConfig.linkedModuleSources) || [] + const linkedModuleSources = localConfig.linkedModuleSources || [] + const linkedProjectSources = localConfig.linkedProjectSources || [] + if (type === "module") { + return linkedModuleSources + } else if (type === "project") { + return linkedProjectSources + } else { + return [...linkedModuleSources, ...linkedProjectSources] + } } export async function addLinkedSources({ garden, sourceType, sources }: { diff --git a/garden-service/src/util/fs.ts b/garden-service/src/util/fs.ts index 657e7c931e..0120647e56 100644 --- a/garden-service/src/util/fs.ts +++ b/garden-service/src/util/fs.ts @@ -21,7 +21,7 @@ import { VcsHandler } from "../vcs/vcs" const VALID_CONFIG_FILENAMES = ["garden.yml", "garden.yaml"] const metadataFilename = "metadata.json" export const defaultDotIgnoreFiles = [".gitignore", ".gardenignore"] -export const fixedExcludes = [".git", ".garden", "debug-info*/**"] +export const fixedExcludes = [".git", ".garden/**/*", "debug-info*/**"] /* Warning: Don't make any async calls in the loop body when using this function, since this may cause diff --git a/garden-service/src/vcs/git.ts b/garden-service/src/vcs/git.ts index 1fb0ddb7e1..51c0301df2 100644 --- a/garden-service/src/vcs/git.ts +++ b/garden-service/src/vcs/git.ts @@ -87,13 +87,14 @@ export class GitHandler extends VcsHandler { * We need to exclude .garden to avoid errors when path is the project root. This happens e.g. for modules * whose config is colocated with the project config, and that don't specify include paths/patterns. */ - lines = await git("ls-files", "-s", "--other", "--exclude=.garden", path) + // FIXME: We should use `garden.gardenDirPath` instead of ".garden" since the dir name can be variable + lines = await git("ls-files", "-s", "--others", "--exclude=.garden", path) // List ignored files from .gardenignore. We need to run ls-files twice to get both tracked and untracked files. - const lsFilesCmd = ["ls-files", "--ignored", ...this.ignoreFiles.map(f => `--exclude-per-directory=${f}`)] - const lsFilesUntrackedCmd = [...lsFilesCmd, "--others"] + const lsIgnoredFiles = ["ls-files", "--ignored", ...this.ignoreFiles.map(f => `--exclude-per-directory=${f}`)] + const lsUntrackedIgnoredFiles = [...lsIgnoredFiles, "--others"] - ignored = flatten(await Bluebird.map([lsFilesCmd, lsFilesUntrackedCmd], async (cmd) => git(...cmd, path))) + ignored = flatten(await Bluebird.map([lsIgnoredFiles, lsUntrackedIgnoredFiles], async (cmd) => git(...cmd, path))) } catch (err) { // if we get 128 we're not in a repo root, so we get no files if (err.code !== 128) { diff --git a/garden-service/src/watch.ts b/garden-service/src/watch.ts index 8710c4d73b..2209a692df 100644 --- a/garden-service/src/watch.ts +++ b/garden-service/src/watch.ts @@ -20,15 +20,17 @@ import { isConfigFilename } from "./util/fs" // IMPORTANT: We must use a single global instance of the watcher, because we may otherwise get // segmentation faults on macOS! See https://github.com/fsevents/fsevents/issues/273 let watcher: FSWatcher | undefined -let projectRoot: string -// The process hangs after tests if we don't do this -registerCleanupFunction("stop watcher", () => { +// Export so that we can clean up the global watcher instance when running tests +export function cleanUp() { if (watcher) { watcher.close() watcher = undefined } -}) +} + +// The process hangs after tests if we don't do this +registerCleanupFunction("stop watcher", cleanUp) export type ChangeHandler = (module: Module | null, configChanged: boolean) => Promise @@ -39,13 +41,11 @@ export type ChangeHandler = (module: Module | null, configChanged: boolean) => P export class Watcher { private watcher: FSWatcher - constructor(private garden: Garden, private log: LogEntry, modules: Module[]) { - projectRoot = this.garden.projectRoot - - this.log.debug(`Watcher: Watching ${projectRoot}`) + constructor(private garden: Garden, private log: LogEntry, paths: string[], modules: Module[]) { + log.debug(`Watcher: Watching paths ${paths.join(", ")}`) if (watcher === undefined) { - watcher = watch(projectRoot, { + watcher = watch(paths, { ignoreInitial: true, persistent: true, }) diff --git a/garden-service/test/unit/src/garden.ts b/garden-service/test/unit/src/garden.ts index 538867f592..996af474c7 100644 --- a/garden-service/test/unit/src/garden.ts +++ b/garden-service/test/unit/src/garden.ts @@ -56,6 +56,15 @@ describe("Garden", () => { expect(garden).to.be.ok }) + it("should always exclude the garden dir", async () => { + const gardenA = await makeTestGardenA() + const gardenCustomDir = await makeTestGarden(getDataDir("test-project-a"), { + gardenDirPath: "custom/garden-dir", + }) + expect(gardenA.moduleExcludePatterns).to.include(".garden/**/*") + expect(gardenCustomDir.moduleExcludePatterns).to.include("custom/garden-dir/**/*") + }) + it("should throw if a project has config files with yaml and yml extensions in the same dir", async () => { const path = getDataDir("test-project-duplicate-yaml-file-extensions") await expectError(async () => makeTestGarden(path), "validation")