{"id":40607216,"url":"https://github.com/efrecon/unforge","last_synced_at":"2026-01-21T05:04:44.602Z","repository":{"id":207652311,"uuid":"719773285","full_name":"efrecon/unforge","owner":"efrecon","description":"Fetch the content of a forge's repository at a given reference into a local directory without git. Doubles as a GitHub Action!","archived":false,"fork":false,"pushed_at":"2025-01-21T19:34:35.000Z","size":75,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-21T20:29:42.784Z","etag":null,"topics":["action","download","git"],"latest_commit_sha":null,"homepage":"","language":"Shell","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/efrecon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2023-11-16T21:47:11.000Z","updated_at":"2025-01-21T19:34:39.000Z","dependencies_parsed_at":"2024-11-21T11:26:57.035Z","dependency_job_id":"8a536441-fcd3-4151-9c13-1dc0586b8b01","html_url":"https://github.com/efrecon/unforge","commit_stats":{"total_commits":34,"total_committers":3,"mean_commits":"11.333333333333334","dds":"0.20588235294117652","last_synced_commit":"988e4a4e35bcdf786b036e2da8d665d37ea36888"},"previous_names":["efrecon/ungit","efrecon/unforge"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/efrecon/unforge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efrecon%2Funforge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efrecon%2Funforge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efrecon%2Funforge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efrecon%2Funforge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/efrecon","download_url":"https://codeload.github.com/efrecon/unforge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efrecon%2Funforge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28627387,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-21T04:47:28.174Z","status":"ssl_error","status_checked_at":"2026-01-21T04:47:22.943Z","response_time":86,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["action","download","git"],"created_at":"2026-01-21T05:04:10.567Z","updated_at":"2026-01-21T05:04:44.589Z","avatar_url":"https://github.com/efrecon.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# unforge\n\nFetch the content of a forge's repository at a given reference into a local\ndirectory. [`unforge`](./unforge.sh) uses the various forge APIs, thus entirely\nbypasses `git`. You will get a snapshot of the repository at that reference,\nwith no history. In most cases, this is [quicker](#speed) than cloning the\nrepository. `unforge` also implements a GitHub action, with a behaviour and\ninputs similar to [actions/checkout], but without the history.\n\n`unforge` can detect that the destination directory belongs to a git repository.\nIn that case it will maintain an index of such snapshots in a file called\n`.unforge` at the root of the repository, preferably. `unforge` automatically\ncaches tarballs in the [XDG] cache to avoid unnecessary downloads. In many\naspects, `unforge` can be a lightweight alternative to git [submodules] or the\ncontributed, abandonware [subtree].\n\nRead further down for a more detailed list of `unforge`'s\n[features](#highlights) and [limitations](#limitations), or jump straight to the\n[examples](#examples).\n\n  [actions/checkout]: https://github.com/actions/checkout\n  [submodules]: https://git-scm.com/book/en/v2/Git-Tools-Submodules\n  [subtree]: https://git.kernel.org/pub/scm/git/git.git/plain/contrib/subtree/git-subtree.txt\n\n## Examples\n\n### Basic Usage\n\n#### Fetch from `main` branch at GitHub\n\nProvided `unforge.sh` is in your `$PATH`, the following command will download\nthe latest content of this repository (`main` branch) into a directory called\n`unforge` under the current directory.\n\n```bash\nunforge.sh add efrecon/unforge\n```\n\nThe `add` command is optional, this means that the command below is similar:\n\n```bash\nunforge.sh efrecon/unforge\n```\n\n#### Specify a branch/tag/reference\n\nThe following command will download the first version ever committed to this\nrepository to the directory `/tmp/unforge`. The reference can either be a branch\nname, a tag or, as in the example, a commit reference.\n\n```bash\nunforge.sh add efrecon/unforge@34bc76507d0e7722811720532587dd6547e8893a /tmp/unforge\n```\n\n#### Download from GitLab\n\nThe following command will download the `renovate/golang-1.x` branch from the\nGitLab Runner project. Verbosity feedback is provided, increase the number of\n`v`s for even more details.\n\n```bash\nunforge.sh -t gitlab -v add gitlab-org/gitlab-runner@renovate/golang-1.x\n```\n\n### Index File\n\nSome of the examples below point explicitly to an index file called `.unforge`.\nIf they were called from a directory contained in a git repository, it is\npossible to omit the `-i` option instead, as it is the default. By default,\n`unforge` will automatically climb up the hierarchy starting from the\ndestination directory to look for the `.unforge` file when adding, installing or\ndeleting.\n\n#### Add a Snapshot\n\nThe following command will download the latest content of this repository\n(`main` branch) into a directory called `unforge` under the current directory.\nIt will *update* the index file called `.unforge` in the current directory to\nremember this association through pinpointing the current commit reference that\ncorresponds to the `main` branch.\n\n```bash\nunforge.sh -i .unforge add efrecon/unforge\n```\n\n#### Unresolved Snapshots\n\nResolving the branch leads to deterministic results: it freezes the content of\nyour dependency to a known good version (and implementation). If you are willing\nto accept changes across the development lifecycle, you could instead run the\nfollowing command.\n\n```bash\nunforge.sh -r '' -i .unforge add efrecon/unforge\n```\n\nWhenever you are willing to accept changes at the `main` branch of the project,\nand now that it is available at the `unforge` sub-directory, you would run the\nfollowing command.\n\n```bash\nunforge.sh -r '' -i .unforge update unforge\n```\n\n#### Install Several Snapshots\n\nEdit the `.unforge` file to the following content:\n\n```text\nunforge https://github.com/efrecon/unforge\n\n# Add (but rename) the gh-action-keepalive project\nactions/keepalive https://github.com/efrecon/gh-action-keepalive\n```\n\nThen, when the following command is run, it will add the `unforge` and\n`actions/keepalive` directories under the current directory.\n\n```bash\nunforge.sh install\n```\n\n`unforge` will automatically climb up the hierarchy in search for the `.unforge`\nindex file that you have created. Since an index file is found and used, files\nand directories will be made read-only. This is to enforce managing the\nsnapshots using `unforge`, and to prevent their heedless modification. Also,\nsince both project specifications didn't specify a branch, both `main` or\n`master` are implied and `unforge` will automatically resolve the first matching\n(default) branch to the current commit reference. The index file will be\nmodified to contain resolved URLs with that reference. Use the `-r` option as\ndescribed [above](#unresolved-snapshots) to keep unresolved URLs in the index\ninstead.\n\n#### Remove a Snapshot\n\nBuilding upon the previous example, the following command will remove the\n`unforge` directory from under the current directory and remove the association\nfrom the index file. In the example below, specifying the `.unforge` index file\nis redundant.\n\n```bash\nunforge.sh -i .unforge remove unforge\n```\n\n### As a GitHub Action\n\n#### Checkout Current Project\n\nCheckout the current project at the current reference, in the current workspace\nat the runner.\n\n```yaml\n- uses: efrecon/unforge\n```\n\n#### Checkout Another Project\n\nCheckout the `efrecon/unforge` project, at a given reference in the current\nworkspace at the runner.\n\n```yaml\n- uses: efrecon/unforge\n  with:\n    repository: efrecon/unforge\n    ref: 34bc76507d0e7722811720532587dd6547e8893a\n```\n\n## Usage\n\n### Script\n\nThe behaviour of [`unforge`](./unforge.sh) is controlled by a series of\nenvironment variables -- all starting with `unforge_` -- and by its command-line\n(short) options. Options have precedence over environment variables. The first\nargument to `unforge` is a command, and this command defaults to `add`. Provided\n`unforge.sh` is in your `$PATH`, run the following command to get help over both\nthe variables, the CLI options and commands.\n\n```bash\nunforge.sh -h\n```\n\n`unforge` recognises the following commands as its first argument, after its\noptions:\n\n+ `add`: Add a snapshot of the repository passed as a first argument to the\n  directory passed as a second argument (optional). If a third argument is\n  provided, it should be a sub-directory of the remote directory to extract from\n  (instead of, otherwise, the root). When an index file is to be maintained, it\n  will remember the association. The index file will contain a relative\n  reference to the destination directory.\n+ `delete` (or `remove`): Remove the directory passed as an argument. When an\n  index file is to be maintained, the association will be lost.\n+ `install`: Install snapshots of all repositories pointed out by the index\n  file, if not already present. When given arguments, only the projects which\n  names are provided in the arguments and found in the index will be installed.\n+ `update`: Update all or some of the projects pointed at by the index file.\n  `update` is an alias for `install` with the `-ff` option, i.e. it will renew\n  the cache and reinstall the project from its downloaded tarfile.\n+ `help`: Print the same help as with the `-h` option and exit.\n\nThis script has minimal dependencies. It has been tested under `bash` and `ash`\nand will be able to download content as long as `curl` (preferred) or `wget`\n(including the busybox version) are available at the `$PATH`.\n\n### GitHub Action\n\nThe GitHub Action uses inputs named after the ones of [actions/checkout]. It is\na composite action that interfaces almost 1-1 the [`unforge`](./unforge.sh)\nimplementation script. For an exact list of inputs, consult the\n[action](./action.yml).\n\n## Highlights\n\n+ Takes either the full URL to a repository, or its owner/name as a first\n  parameter. When only an owner/name is provided, the URL is constructed out of\n  the value of the `-t` option -- `github` by default.\n+ Keeps a cache of downloaded tarballs under a directory called `unforge` in the\n  [XDG] cache directory. Cached tarballs are reused if possible, unless the\n  `-ff` option is provided (yes: twice the `-f` option!).\n+ Will not overwrite the content of the target directory if it already exists,\n  unless the `-f` option is provided.\n+ When APIs provide a way to make the difference, the search order for the\n  reference is: branch name, tag name, pull request, commit reference.\n+ When running against GitHub repositories, you can specify fully qualified\n  references, e.g. starting with `refs/` to bypass the default search order.\n+ When `-f`is provided, wipes the content of the target directory, unless the\n  `UNFORGE_KEEP` variable is set to `1`. Since `unforge` is about obtaining\n  snapshots of target repositories, the (good) default prevents mixing several\n  snapshots into the same target directory.\n+ `-p` can prevent the target directory to be modified by forcing all files and\n  sub-directories to be read-only. This can prevent heedless modification of the\n  snapshots.\n+ Can maintain an index of (relative) directories containing snapshots of added\n  repositories. When using an index, target directory protection is\n  automatically turned on, but only when the index is not under a git\n  repository. This is because `git` does not play well with read-only files.\n+ When run from within a `git` repository, will automatically use a file called\n  `.unforge` at the root of the repository as an index when adding the first\n  time -- and unless specified otherwise.\n+ When run from within a `git` repository, the `main` and `master` branches will\n  be resolved to their current reference in the `.unforge` index. This freezes\n  the imported code at the current moment in time and avoids problems when\n  updating later. The list of branches to resolve can be changed through the\n  option `-r`, and setting that option to an empty string will turn off that\n  behavior.\n+ Automatically detects the default branch of github and gitlab projects.\n+ `unforge` will automatically climb up the hierarchy starting from the\n  destination directory to look for the `.unforge` index file when adding,\n  installing or deleting. This means that while keeping the `.unforge` index\n  file at the root of the git repository is the preferred way, you are free to\n  choose differently.\n+ `unforge` also works with private repositories as long as you can pass an\n  authentication token with the `-T` option.\n+ `unforge` can work with local installations of github or gitlab, through\n  specifying their type as part of the URL scheme, e.g.\n  `https+github://myhost.com/` or `https+gitlab://myhost.com/`.\n\n  [XDG]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html\n\n## Limitations\n\n+ If the target repository contains [submodules], the content of these\n  submodules will not be part of the downloaded tarball, nor the directory\n  snapshot. There might be settings at the forge to include submodules, on a per\n  project basis.\n\n## Why?\n\nThere are a number of scenarios where this can be useful:\n\n+ When you want to have a quick look at the content of a project from the\n  comfort of your favourite editor.\n+ When you want to use neither [submodules], nor [subtree], but still want to\n  use (and maintain over time) another project's tree within yours.\n+ `unforge` implements a rudimentary package manager.\n+ It was fun to write and only took a few hours.\n\n## Speed\n\nOn a large repository, `unforge` is likely to be quicker because all `git`\noperations are run within the remote's forge infrastructure (and file systems).\nFor example, the following timed `git` command:\n\n```bash\ntime git clone -b v2.13.1 --depth 1 https://github.com/tensorflow/tensorflow.git\n```\n\nwill output:\n\n```console\nreal\t0m33.170s\nuser\t0m11.861s\nsys\t0m5.095s\n```\n\nWhile the following matching command, using `unforge` instead:\n\n```bash\ntime unforge.sh tensorflow/tensorflow@v2.13.1\n```\n\nwill output:\n\n```console\nreal\t0m18.650s\nuser\t0m6.428s\nsys\t0m5.105s\n```\n\nWhen run as a GitHub action and against a repository at GitHub, the effect might\nbe the inverse. All the disk operations to create the compressed tarball and\nunpack it on the receiving side will take extra time.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fefrecon%2Funforge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fefrecon%2Funforge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fefrecon%2Funforge/lists"}