{"id":18602988,"url":"https://github.com/devlooped/dotnet-file","last_synced_at":"2025-08-20T04:31:36.758Z","repository":{"id":41219795,"uuid":"244778416","full_name":"devlooped/dotnet-file","owner":"devlooped","description":"Download, update and sync loose files from URLs","archived":false,"fork":false,"pushed_at":"2024-11-20T01:06:56.000Z","size":351,"stargazers_count":55,"open_issues_count":4,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-12-08T14:40:59.586Z","etag":null,"topics":["dotnet","dotnet-tool"],"latest_commit_sha":null,"homepage":"https://clarius.org/dotnet-file","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devlooped.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"devlooped"}},"created_at":"2020-03-04T01:16:21.000Z","updated_at":"2024-10-31T22:25:54.000Z","dependencies_parsed_at":"2024-04-29T06:43:11.406Z","dependency_job_id":"54b6be41-6bb7-4677-abe7-99d8cd2a82cb","html_url":"https://github.com/devlooped/dotnet-file","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2Fdotnet-file","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2Fdotnet-file/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2Fdotnet-file/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2Fdotnet-file/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devlooped","download_url":"https://codeload.github.com/devlooped/dotnet-file/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230394228,"owners_count":18218707,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotnet","dotnet-tool"],"created_at":"2024-11-07T02:13:10.493Z","updated_at":"2025-08-20T04:31:36.742Z","avatar_url":"https://github.com/devlooped.png","language":"C#","funding_links":["https://github.com/sponsors/devlooped","https://github.com/sponsors"],"categories":[],"sub_categories":[],"readme":"![Icon](https://raw.githubusercontent.com/devlooped/dotnet-file/main/docs/img/icon-32.png) dotnet-file\n============\n\n[![Version](https://img.shields.io/nuget/v/dotnet-file.svg?color=royalblue)](https://www.nuget.org/packages/dotnet-file)\n[![Downloads](https://img.shields.io/nuget/dt/dotnet-file.svg?color=darkmagenta)](https://www.nuget.org/packages/dotnet-file)\n[![License](https://img.shields.io/github/license/devlooped/dotnet-file.svg?color=blue)](https://github.com/devlooped/dotnet-file/blob/master/LICENSE)\n\n\u003c!-- #content --\u003e\nA dotnet global tool for downloading and updating loose files from arbitrary URLs.\n\nInstalling or updating (same command can be used for both):\n\n```\ndotnet tool update -g dotnet-file\n```\n\nTo get the CI version:\n\n```\ndotnet tool update -g dotnet-file --no-cache --add-source https://pkg.kzu.app/index.json\n```\n\nUsage:\n\n    dotnet file [add|changes|delete|list|sync|update] [file or url]*\n\n    Actions\n        add        downloads a file or GitHub repository or directory from a URL\n        changes    checks remote URLs for changes and lists the status of local files\n        delete     deletes a file and its corresponding config entry from the local directory\n        init       initializes the local directory from one or more remote .netconfig files\n        list       lists the config entries and the status of their corresponding files\n        sync       synchronizes with remote URLs, deleting local files and directories as needed\n        update     updates local files from remote URLs, does not prune deleted remote files\n\n    Status\n      = \u003c- [url]   remote file equals local file\n      ✓ \u003c- [url]   local file updated with remote file\n      ^ \u003c- [url]   remote file is newer (ETags mismatch)\n      ? \u003c- [url]   local file not found for remote file\n      x \u003c- [url]   error processing the entry\n\n\nAll arguments after the action are tried for URL parsing automatically to \ndisambiguate file (local path) from url (remote file location).\n\nExamples:\n\n    dotnet file init [url]          // seeds the current directory with all files/URLs listed in a remote URL\n    dotnet file add [url]           // downloads a file to the current directory and adds its URL+ETag in dotnet-config\n    dotnet file add [url] [file]    // downloads the url to the (relative) file local path specifed and adds\n                                    // its URL+ETag in dotnet-config\n    dotnet file add [url] .         // downloads the url to the current directory and stores URL+ETag in dotnet-config, \n                                    // ignoring the source directory structure\n    dotnet file add [url] docs/     // downloads the url to the specified directory, preserving the source directory structure\n    dotnet file add [url] docs/.    // downloads the url to the specified directory, flattening the source directory structure\n    dotnet file update [file]       // updates a specific file, based on its dotnet-config configuration\n    dotnet file update [url]        // updates a specific file by its url, based on its dotnet-config configuration\n    dotnet file update              // updates all recorded files, according to the dotnet-config configuration\n    dotnet file sync                // just like update, but also prunes files/folders removed from their remote urls\n    dotnet file delete [file]       // deletes a local file and its entry in .netconfig\n    dotnet file list                // lists all configured files\n    dotnet file changes             // lists all configured files and their status with regards to the configured \n                                    // remote URL and ETag matching\n\nThe target path is determined by the following rules:\n* If `[file]` = `.`: download to the current directory, ignoring the source directory structure.\n* If `[url]` ends with `/`: download to the current directory, preserving the source directory structure \n  from that point onwards (i.e. for GitHub tree/dir URLs)\n* Otherwise, match the directory structure of the source file.  \n\nAfter downloading a file, a new entry is created in a local `.netconfig` file, which\nleverages [dotnet config](https://github.com/kzu/dotnet-config):\n\n    [file \"relative file path\"]\n      url = [url]\n      etag = [etag]\n\nThis information is used to later update the file contents if necessary, by issuing a \nconditional http get to retrieve updates. It’s generally advisable to commit the .netconfig file \nto source control, so that updating is simply a matter of running `dotnet file update`. \n\n\u003e Note: `dotnet file update [url]` behaves just like `dotnet file add [url]` when a matching \n\u003e entry for the file isn't found in the `.netconfig` file.\n\nIf you want to skip further synchronization of a file, you can add `skip` to the entire: \n\n    [file \"readme.md\"]\n      url = [url]\n      skip\n\nSymbols are used to denote actions (pending or performed) on files:\n\n* `√`: file has no pending updated (ETag matches) or it was just downloaded successfully.\n* `^`: file has pending updates (ETag doesn't match the remote).\n* `=`: no changes to file were necessary in an update\n* `?`: file not found locally. A new version can be downloaded from the remote.\n* `x`: could not update file or refresh ETag status, with reason noted in subsequent line.\n\nDownloading entire repositories or specific directories within them is supported through the \n[GitHub CLI](https://cli.github.com/manual/installation), which must be installed previously. \nYou can verify you have the property authentication and access in place by running the following \nGH CLI command:\n\n    gh repo view org/repo\n\nIf you can view the output (would be the README from the repo), you can download files from it\nwith `dotnet-file`.\n\n\nPrivate repositories are supported from GitHub and BitBucket through the \n[Git Credentials Manager Core](https://github.blog/2020-07-02-git-credential-manager-core-building-a-universal-authentication-experience/) \nproject.\n\nWhen adding a file, it's possible to customize the local file location by specifying an absolute \nor relative file path, as follows:\n\n* `src/External/File.cs`: fully custom target file path, doesn't need to match source URI file name \n  or directory structure.\n* `src/External/`: use the given directory as the base directory, but otherwise preserve the source \n  URI directory structure and file name.\n* `src/External/.`: download to the given directory, without recreating source URI directory structure, \n  using the source file name only.\n* `.` (a single dot character as the target path): download to the current directory, don't preserve \n  source URI directory structure, use source file name only.\n\n\nConcrete examples:\n\n    \u003e dotnet file add https://github.com/devlooped/dotnet-file/blob/main/azure-pipelines.yml\n    azure-pipelines.yml √ \u003c- https://github.com/devlooped/dotnet-file/blob/main/azure-pipelines.yml\n\n    \u003e dotnet file add https://github.com/devlooped/dotnet-file/blob/main/docs/img/icon.png img/icon.png\n    img/icon.png √ \u003c- https://github.com/devlooped/dotnet-file/blob/main/docs/img/icon.png\n\n    \u003e dotnet file list\n    azure-pipelines.yml = \u003c- https://github.com/devlooped/dotnet-file/blob/main/azure-pipelines.yml\n    img/icon.png        = \u003c- https://github.com/devlooped/dotnet-file/blob/main/docs/img/icon.png\n\n    \u003e del img\\icon.png\n    \u003e dotnet file list\n    azure-pipelines.yml = \u003c- https://github.com/devlooped/dotnet-file/blob/main/azure-pipelines.yml\n    img/icon.png        ? \u003c- https://github.com/devlooped/dotnet-file/blob/main/docs/img/icon.png\n\n    # missing file downloaded successfully\n    \u003e dotnet file update\n    azure-pipelines.yml = \u003c- https://github.com/devlooped/dotnet-file/blob/main/azure-pipelines.yml\n    img/icon.png        √ \u003c- https://github.com/devlooped/dotnet-file/blob/main/docs/img/icon.png\n\n    # file updated on remote, changes detected\n    \u003e dotnet file changes\n    azure-pipelines.yml ^ \u003c- https://github.com/devlooped/dotnet-file/blob/main/azure-pipelines.yml\n    img/icon.png        = \u003c- https://github.com/devlooped/dotnet-file/blob/main/docs/img/icon.png\n\n    # file renamed or deleted from remote\n    \u003e dotnet file changes\n    azure-pipelines.yml = \u003c- https://github.com/devlooped/dotnet-file/raw/main/azure-pipelines.yml\n    img/icon.png        x \u003c- https://github.com/devlooped/dotnet-file/blob/main/docs/img/icon.png\n                             404: Not Found\n\n    # download entire directory to local dir matching remote folder structure\n    \u003e dotnet file add https://github.com/dotnet/runtime/tree/main/docs/coding-guidelines/api-guidelines\n    api-guidelines  =\u003e fetching via gh cli...\n    docs/coding-guidelines/api-guidelines/README.md        √ \u003c- https://github.com/dotnet/runtime/main/docs/coding-guidelines/api-guidelines/README.md\n    docs/coding-guidelines/api-guidelines/System.Memory.md √ \u003c- https://github.com/dotnet/runtime/main/docs/coding-guidelines/api-guidelines/System.Memory.md\n    docs/coding-guidelines/api-guidelines/nullability.md   √ \u003c- https://github.com/dotnet/runtime/main/docs/coding-guidelines/api-guidelines/nullability.md\n    ...\n\n    # download entire directory to a local subdirectory, from where dir structure will match remote structure\n    \u003e dotnet file add https://github.com/dotnet/runtime/tree/main/docs/coding-guidelines/api-guidelines external/dotnet/\n    external/dotnet/ =\u003e fetching via gh cli...\n    external/dotnet/docs/coding-guidelines/api-guidelines/README.md        √ \u003c- https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/api-guidelines/README.md\n    external/dotnet/docs/coding-guidelines/api-guidelines/System.Memory.md √ \u003c- https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/api-guidelines/System.Memory.md\n    external/dotnet/docs/coding-guidelines/api-guidelines/nullability.md   √ \u003c- https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/api-guidelines/nullability.md\n    ...\n\n\u003c!-- #content --\u003e\n\n# Dogfooding\n\n[![CI Status](https://github.com/devlooped/dotnet-file/workflows/build/badge.svg?branch=main)](https://github.com/devlooped/dotnet-file/actions?query=branch%3Amain+workflow%3Abuild+)\n[![CI Version](https://img.shields.io/endpoint?url=https://shields.kzu.app/vpre/dotnet-file/main\u0026label=nuget.ci\u0026color=brightgreen)](https://pkg.kzu.app/index.json)\n\nWe also produce CI packages from branches and pull requests so you can dogfood builds as quickly as they are produced. \n\nThe CI feed is `https://pkg.kzu.app/index.json`. \n\nThe versioning scheme for packages is:\n\n- PR builds: *42.42.42-pr*`[NUMBER]`\n- Branch builds: *42.42.42-*`[BRANCH]`.`[COMMITS]`\n\n\u003c!-- include https://github.com/devlooped/sponsors/raw/main/footer.md --\u003e\n# Sponsors \n\n\u003c!-- sponsors.md --\u003e\n[![Clarius Org](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/clarius.png \"Clarius Org\")](https://github.com/clarius)\n[![MFB Technologies, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MFB-Technologies-Inc.png \"MFB Technologies, Inc.\")](https://github.com/MFB-Technologies-Inc)\n[![Torutek](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/torutek-gh.png \"Torutek\")](https://github.com/torutek-gh)\n[![DRIVE.NET, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/drivenet.png \"DRIVE.NET, Inc.\")](https://github.com/drivenet)\n[![Keith Pickford](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Keflon.png \"Keith Pickford\")](https://github.com/Keflon)\n[![Thomas Bolon](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tbolon.png \"Thomas Bolon\")](https://github.com/tbolon)\n[![Kori Francis](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/kfrancis.png \"Kori Francis\")](https://github.com/kfrancis)\n[![Toni Wenzel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/twenzel.png \"Toni Wenzel\")](https://github.com/twenzel)\n[![Uno Platform](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/unoplatform.png \"Uno Platform\")](https://github.com/unoplatform)\n[![Reuben Swartz](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/rbnswartz.png \"Reuben Swartz\")](https://github.com/rbnswartz)\n[![Jacob Foshee](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jfoshee.png \"Jacob Foshee\")](https://github.com/jfoshee)\n[![](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Mrxx99.png \"\")](https://github.com/Mrxx99)\n[![Eric Johnson](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/eajhnsn1.png \"Eric Johnson\")](https://github.com/eajhnsn1)\n[![David JENNI](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/davidjenni.png \"David JENNI\")](https://github.com/davidjenni)\n[![Jonathan ](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Jonathan-Hickey.png \"Jonathan \")](https://github.com/Jonathan-Hickey)\n[![Charley Wu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/akunzai.png \"Charley Wu\")](https://github.com/akunzai)\n[![Ken Bonny](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KenBonny.png \"Ken Bonny\")](https://github.com/KenBonny)\n[![Simon Cropp](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/SimonCropp.png \"Simon Cropp\")](https://github.com/SimonCropp)\n[![agileworks-eu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/agileworks-eu.png \"agileworks-eu\")](https://github.com/agileworks-eu)\n[![Zheyu Shen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/arsdragonfly.png \"Zheyu Shen\")](https://github.com/arsdragonfly)\n[![Vezel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/vezel-dev.png \"Vezel\")](https://github.com/vezel-dev)\n[![ChilliCream](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/ChilliCream.png \"ChilliCream\")](https://github.com/ChilliCream)\n[![4OTC](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/4OTC.png \"4OTC\")](https://github.com/4OTC)\n[![Vincent Limo](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/v-limo.png \"Vincent Limo\")](https://github.com/v-limo)\n[![Jordan S. Jones](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jordansjones.png \"Jordan S. Jones\")](https://github.com/jordansjones)\n[![domischell](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/DominicSchell.png \"domischell\")](https://github.com/DominicSchell)\n[![Justin Wendlandt](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jwendl.png \"Justin Wendlandt\")](https://github.com/jwendl)\n[![Adrian Alonso](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/adalon.png \"Adrian Alonso\")](https://github.com/adalon)\n[![Michael Hagedorn](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Eule02.png \"Michael Hagedorn\")](https://github.com/Eule02)\n[![](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/henkmartijn.png \"\")](https://github.com/henkmartijn)\n[![Sebastien Lebreton](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/sailro.png \"Sebastien Lebreton\")](https://github.com/sailro)\n[![torutek](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/torutek.png \"torutek\")](https://github.com/torutek)\n\n\n\u003c!-- sponsors.md --\u003e\n\n[![Sponsor this project](https://raw.githubusercontent.com/devlooped/sponsors/main/sponsor.png \"Sponsor this project\")](https://github.com/sponsors/devlooped)\n\u0026nbsp;\n\n[Learn more about GitHub Sponsors](https://github.com/sponsors)\n\n\u003c!-- https://github.com/devlooped/sponsors/raw/main/footer.md --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevlooped%2Fdotnet-file","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevlooped%2Fdotnet-file","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevlooped%2Fdotnet-file/lists"}