{"id":14064928,"url":"https://github.com/ovk/dotref","last_synced_at":"2025-09-11T09:33:33.311Z","repository":{"id":45202932,"uuid":"322885242","full_name":"ovk/dotref","owner":"ovk","description":"Simple tool to manage dotfiles","archived":false,"fork":false,"pushed_at":"2022-08-14T19:25:05.000Z","size":152,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-08-24T10:50:56.371Z","etag":null,"topics":["command-line","commandline","dotfile","dotfile-manager","dotfiles","dotfiles-linux","dotref","python","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","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/ovk.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}},"created_at":"2020-12-19T16:04:24.000Z","updated_at":"2024-04-06T00:08:25.000Z","dependencies_parsed_at":"2022-07-25T22:32:54.888Z","dependency_job_id":null,"html_url":"https://github.com/ovk/dotref","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/ovk/dotref","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ovk%2Fdotref","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ovk%2Fdotref/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ovk%2Fdotref/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ovk%2Fdotref/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ovk","download_url":"https://codeload.github.com/ovk/dotref/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ovk%2Fdotref/sbom","scorecard":{"id":715528,"data":{"date":"2025-08-11","repo":{"name":"github.com/ovk/dotref","commit":"43b9279123fd0bb74a9bffd14be35f9a65deac23"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/22 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release-aur.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/ovk/dotref/release-aur.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release-aur.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/ovk/dotref/release-aur.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release-pypi.yml:9: update your workflow using https://app.stepsecurity.io/secureworkflow/ovk/dotref/release-pypi.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release-pypi.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/ovk/dotref/release-pypi.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/ovk/dotref/tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/ovk/dotref/tests.yml/master?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/release-pypi.yml:16","Warn: pipCommand not pinned by hash: .github/workflows/release-pypi.yml:17","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:19","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:20","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:21","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:22","Info:   0 out of   5 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of   6 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/release-aur.yml:1","Warn: no topLevel permission defined: .github/workflows/release-pypi.yml:1","Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 10 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T09:30:54.042Z","repository_id":45202932,"created_at":"2025-08-22T09:30:54.042Z","updated_at":"2025-08-22T09:30:54.042Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274609465,"owners_count":25316621,"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","status":"online","status_checked_at":"2025-09-11T02:00:13.660Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["command-line","commandline","dotfile","dotfile-manager","dotfiles","dotfiles-linux","dotref","python","python3"],"created_at":"2024-08-13T07:04:11.009Z","updated_at":"2025-09-11T09:33:33.271Z","avatar_url":"https://github.com/ovk.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# dotref\n\n[![Tests](https://github.com/ovk/dotref/actions/workflows/tests.yml/badge.svg)](https://github.com/ovk/dotref/actions/workflows/tests.yml)\n[![Coverage Status](https://coveralls.io/repos/github/ovk/dotref/badge.svg?branch=master)](https://coveralls.io/github/ovk/dotref?branch=master)\n[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/ovk/dotref.svg?logo=lgtm\u0026logoWidth=18)](https://lgtm.com/projects/g/ovk/dotref/context:python)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/dotref)](https://pypi.python.org/pypi/dotref)\n\n\n`dotref` is a simple tool to manage dotfiles across multiple devices.\nIt supports creating directories, symlinks, templating and hierarchical configuration profiles to keep things DRY.\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003cimg width=\"600\" src=\"./demo.svg\"\u003e\n\u003c/p\u003e\n\n# Feature Highlight\n- Create symlinks to dotfiles and directories\n- Create directories\n- Configuration files templating\n- Hierarchical configuration profiles to easily manage dotfiles across multiple devices\n\n## Why another dotfile manager?\nWhile there are many dotfile managers available, most of them are either too complex (try to do everything, often in opinionated ways)\nor way to limited.\nDotref is an attempt to make simple but useful tool, that focuses just on dotfile management (\"do one thing and do it well\").\n\nSome highlight of what makes dotref different from some other tools:\n\n- Dotfiles are managed according to hierarchical profiles (that are just simple JSON files).\nThat was the main reason for the creation of dotref, as at least at the moment it was originally created, no other tool could do that.\nThis makes it very easy to manage configuration for multiple devices (e.g. servers, desktops, virtual machines) while following DRY principle.\n- Dotref is a single Python script without any dependencies on third-party tools or libraries.\nThe whole script is just a few hundred lines long and can be easily reviewed and audited by anyone with basic Python knowledge.\n- Dotref doesn't make any assumptions about where dotfiles will be stored.\nFor example, while many people keep them in a Git repository, they may as well be stored on Dropbox or Nextcloud.\n\n# Overview\nDotref works mostly by just creating symlinks to your dotfiles.\nHow does it know what to link where? This is described in dotref profile files.\n\n## Profiles\nA profile file is a simple JSON file that describes what you want dotref to do with your dotfiles.\n\nBy default, all profiles are stored in a `dotref` directory, inside the directory containing your dotfiles.\nIt's often useful to keep dotfiles in a Git (or other VCS) repository and the dotref profiles should be kept there as well.\n\nProfile files can be created manually in your favorite text editor and they have the following structure:\n\n```json\n{\n  \"extends\": \"... List of parent profile names ...\"\n  \"vars\":    \"... Object with variables for templating ...\",\n  \"create\":  \"... List of directories to create ...\",\n  \"link\":    \"... List of symlinks to create ...\",\n  \"template\":\"... List of templates to render ...\"\n}\n```\n\nProfile name is simply its filename without the `.json` extension.\n\nLet's take a look at profile structure:\n\n- `extends` is a list of parent profile names.\nIf a profile *Child* extends profile *Root* then everything from the *Root* profile will also be available in the *Child* profile.\nIf there's an overlap (for example, a child defines a variable with the same name), the *Child* properties will override its parent's properties.\n`extends` field can be omitted for a root profiles.\n- `vars` is a JSON object with variables for template substitution. An example: `\"vars\": { \"email\": \"john.doe@example.com\" }`.\n- `create` is a list of JSON objects describing what directories need to be created.\nEach object must have a `name` field with the directory name and can optionally have `mode` with a string containing octal directory permissions.\nAn example: `\"create\": [ { \"name\": \"~/games\", \"mode\": \"750\" } ]`.\nNote that `mode` is always affected by `umask` and if not specified the default value is `777`.\n- `link` is a list of JSON objects describing symlinks that need to be created.\nEach object must have `src` and `dst` fields representing link source and destination.\nAn example: `\"link\": [ { \"src\": \"bashrc\", \"dst\": \"~/.bashrc\" } ]` will create a `~/.bashrc` symlink pointing to the `bashrc` file in the current directory.\n- `template` is a list of JSON objects describing what templates to render.\nEach object must have `src` and `dst` fields representing template source file and its rendered file destination.\nAn example: `\"template\": [ { \"src\": \"bashrc.tpl\", \"dst\": \"~/.bashrc\" } ]` will create a `~/.bashrc` file by rendering a template file `bashrc.tpl`.\n\n### Profile hierarchy\nHaving profile inheritance allows to avoid copy-pasting the same configuration when managing multiple devices.\nIt can be best explained on example: one can imagine a `base` profile which symlinks configuration for some common tools, shell, text editor, etc.\nWe can create two other profiles that extend `base`: a `server` profile with extra configurations that only relevant on a server,\nand a `desktop` one for desktops.\nAs a parallel hierarchy, we could create two profiles: `home` and `work`, each defining variables relevant to corresponding environment.\nThen the final profiles like `home_server`, `work_laptop` or `home_laptop` could just extend corresponding profiles.\nThe whole hierarchy in this case can be visualized as follows:\n\n```\nhome_server        home_laptop    work_laptop\n    |                  |              |\n    |\\                / \\            /|\n    | \\              /   \\          / |\n    |  \\            /     \\        /  |\n    |   \\          /       \\      /   |\n server  \\- home -/         desktop   work\n    \\                         /\n     \\                       /\n      \\------- base --------/\n```\n\n## Templating\nTemplating in dotref is simply a variable substitution in any text file.\nA template file is \"rendered\" when all of its variable placeholders (denoted with leading `$` sign) replaced with actual variable values from the desired profile.\n\nTemplating is based on Python's built-in [templates](https://docs.python.org/3/library/string.html#template-strings).\n\nHere's an example of templating Git configuration file:\n\n```\n[user]\n    name = $git_name\n    email = $email\n    signingKey = $git_key\n```\n\nHere `git_name`, `email` and `git_key` are variables that must be defined in a profile.\n\nIf a template uses undefined variable, it will throw an error when this template is used.\n\n\u003e **Note**\n\u003e Templating can also be used to just copy a file to a destination. If a file doesn't have any variables, it will be copied as-is.\n\u003e This can be convenient for cases when symbolic links are undesirable.\n\n## Commands\nDotref must be invoked with a specific command, which is one of: `version`, `profiles`, `init`, `status`, `sync`, `unlink`.\nThe only exception is when called with `-h` flag, which prints help and doesn't require a command.\n\n### Version\nThe `version` command just prints current dotref version.\n\n### Profiles\nThe `profiles` command prints a list of all profiles in a given repository.\nBy default, profile files are searched in the `dotref` subdirectory in a current directory, but this can be specified using the `d DOTDIR, --dotdir DOTDIR` argument.\n\nThe `profiles` command can also show detailed information about a single profile, when invoked with `-p PROFILE, --profile PROFILE` argument.\nIt will show ancestors tree of the profile and detailed info about which parts of the configuration were taken from which profile.\n\n### Init\nThe `init` command selects and remembers what profile should be used on a given system.\nThus it must always be invoked with the `-p PROFILE, --profile PROFILE` argument.\n\nThe selected profile is stored in a file called dotref statefile, under the `dotref` directory.\nThis file is named `.dotref.json` by default, but this can be overridden using the `-s STATEFILE, --statefile STATEFILE` argument.\n\nThe `init` command can be used to change the current profile but this should be done with caution,\nas applying one profile on top of another can lead to unexpected results.\nIt's best to first do `dotref unlink` before running `dotref init` again.\n\n### Status\nThe `status` command checks if the system configuration matches the current profile.\nIt's somewhat similar to `git status` and for every entry of a profile (including all it's ancestors) it will show the status of the entry.\n\nMore about statuses below.\n\n### Sync\nThe `sync` command tries to bring the system in the desired state described by the current profile.\nThis command is idempotent, so it can be safely executed multiple times and if the system is already in the desired state it will only print status without doing anything.\n\nA profile entry (directory, symlink or template) can be in one of the following states:\n\n- `OK`: entry already in the desired state, no changes were made\n- `MISSING`: entry doesn't exist in its destination\n- `CREATED`: directory was created successfully\n- `LINKED`: symlink was created successfully\n- `UNLINKED`: symlink was removed successfully\n- `RENDERED`: template was rendered to its destination successfully\n- `DIFFERS`: rendered template version differs from the actual template\n- `CONFLICT`: a conflicting object (file/directory/symlink) already exists at the destination\n\nIt's important to mention that all conflicts must be resolved manually.\nFor example, if dotref is told to link `~/.bashrc` but it already exists and it's not a symlink to the desired file - this will cause a conflict,\nand it's up to the user to decide what to do with the existing file.\n\nThe `sync` operation processes profile entries in the following order:\n\n- First all directories are created\n- Links are created after the directories, since a link destination could be inside a created directory\n- Templates are rendered after the links and directories are created, since template destination could be in any of them\n\n### Unlink\nThe `unlink` command is the opposite of `sync` - it tries to safely remove everything that's described in the current profile and its ancestors.\nThe `unlink` operation is very conservative and it won't remove created directories or rendered templates (unless they exactly match to the actual template).\n\n# Quick Start\nLet's assume you have a `dotfiles` directory with the following files in it:\n\n```\n$ ls\nbashrc  gitconfig.tpl  i3config\n```\n\nThe `bashrc` and `i3config` represent your config files for Bash and i3 respectively.\nThe `gitconfig.tpl` file is a dotref template for the main Git config file:\n\n```\n$ cat gitconfig.tpl\n[user]\n    name = $git_name\n    email = $email\n```\n\nAs you can see, it uses two variables: `git_name` and `email`.\n\n## Creating profiles\nThe first step is to create a dotref profiles directory and a root profile:\n\n```\nmkdir dotref\n```\n\nLet's create `dotref/base.json` file with the following content:\n\n```json\n{\n  \"vars\": {\n    \"git_name\": \"jdoe\",\n    \"email\": \"john.doe@example.com\"\n  },\n  \"create\": [\n    { \"name\": \"~/.config/git\" }\n  ],\n  \"link\": [\n    { \"src\": \"bashrc\", \"dst\": \"~/.bashrc\" }\n  ],\n  \"template\": [\n    { \"src\": \"gitconfig.tpl\", \"dst\": \"~/.config/git/config\" }\n  ]\n}\n```\n\nHere the `vars` block just defines values for our template variables.\nThe `create` block is to create a `~/.config/git` directory, if it doesn't already exist.\nThe `link` tells dotref we want to create a symlink `~/.bashrc` that will point at our `bashrc` file.\nAnd finally the `template` block instructs dotref to render a template file `gitconfig.tpl` at the `~/.config/git/config` destination.\n\nNow let's create a `dotref/desktop.json` file with the following content:\n\n```json\n{\n  \"extends\": [ \"base\" ],\n  \"link\": [\n    { \"src\": \"i3config\", \"dst\": \"~/.config/i3\" }\n  ]\n}\n```\n\nThe `\"extends\": [ \"base\" ]` line tells that this profile *extends* `base` profile,\nso everything specified in the `base` profile will be also available in the `desktop` profile.\nAnd the `link` block just tells that we want a `~/.config/i3` symlink to point to the `i3config` file.\n\nWe can see the merged view (view that combines profile entries from all of its ancestors)\nof the `desktop` profile with the `dotref profiles` command:\n\n```\n$ dotref profiles -p desktop\ndesktop\n+-- base\n\nVariables:\n    git_name: jdoe (base)\n    email:    john.doe@example.com (base)\n\nCreate:\n    ~/.config/git: default mode (base)\n\nLink:\n    i3config: ~/.config/i3\n    bashrc:   ~/.bashrc (base)\n\nTemplate:\n    gitconfig.tpl: ~/.config/git/config (base)\n```\n\n## Selecting profile\nBefore applying a profile we need to select what profile we want to use on the system.\nThis is done with `dotref init` command:\n\n```\n\u003e dotref init -p desktop\nSuccessfully initialized to use profile desktop\n```\n\nDotref remembers the current profile by storing it in the `dotref/.dotref.json` file, so we don't need to specify it again with every command.\n\n## Applying profile\nBefore applying our profile let's check the current status of the system with the `dotref status` command:\n\n```\n$ dotref status\nProfile: desktop\n\nCreate:\n    [MISSING]  ~/.config/git\n\nLink:\n    [MISSING]  ./i3config  -\u003e  ~/.config/i3\n    [MISSING]  ./bashrc    -\u003e  ~/.bashrc\n\nTemplate:\n    [MISSING]  ./gitconfig.tpl  -\u003e  ~/.config/git/config\n```\n\nThis output tells us that `~/.config/git` directory is missing, as well as the config files for Bash and i3,\nand the git config template is missing as well.\n\nTo apply the profile run `dotref sync`:\n\n```\n$ dotref sync\nProfile: desktop\n\nCreate:\n    [CREATED]  ~/.config/git\n\nLink:\n    [LINKED]   ./i3config  -\u003e  ~/.config/i3\n    [LINKED]   ./bashrc    -\u003e  ~/.bashrc\n\nTemplate:\n    [RENDERED] ./gitconfig.tpl  -\u003e  ~/.config/git/config\n\nsync completed successfully and no conflicts were detected\n```\n\nHere the dotref tells us that it did everything we asked it to do.\nLet's try and read git config:\n\n```\n$ cat ~/.config/git/config\n[user]\n    name = jdoe\n    email = john.doe@example.com\n```\n\nWe can run `dotref sync` again:\n\n```\n$ dotref sync\nProfile: desktop\n\nCreate:\n    [OK]       ~/.config/git\n\nLink:\n    [OK]       ./i3config  -\u003e  ~/.config/i3\n    [OK]       ./bashrc    -\u003e  ~/.bashrc\n\nTemplate:\n    [OK]       ./gitconfig.tpl  -\u003e  ~/.config/git/config\n\nsync completed successfully and no conflicts were detected\n```\n\nThe `OK` states tell us that the dotref didn't do anything since the system is already in the desired state.\nLet's now edit the `gitconfig.tpl` file to make it looks like this:\n\n```\n[user]\n    name = $git_name\n    email = $email\n\n[core]\n    editor = nvim\n```\n\nand run `dotref sync` again:\n\n```\n$ dotref sync\nProfile: desktop\n\nCreate:\n    [OK]       ~/.config/git\n\nLink:\n    [OK]       ./i3config  -\u003e  ~/.config/i3\n    [OK]       ./bashrc    -\u003e  ~/.bashrc\n\nTemplate:\n    [RENDERED] ./gitconfig.tpl  -\u003e  ~/.config/git/config\n\nsync completed successfully and no conflicts were detected\n```\n\nDotref realized that the Git config template was changed and re-rendered it.\n\n## Unlinking profile\nTo remove symlinks and rendered templates that dotref have created run `dotref unlink` command:\n\n```\n$ dotref unlink\nProfile: desktop\n\nLink:\n    [UNLINKED] ./i3config  -\u003e  ~/.config/i3\n    [UNLINKED] ./bashrc    -\u003e  ~/.bashrc\n\nTemplate:\n    [UNLINKED] ./gitconfig.tpl  -\u003e  ~/.config/git/config\n\nunlink completed successfully and no conflicts were detected\n```\n\nSimilarly to `sync`, the `unlink` command is idempotent and can be safely executed multiple times.\n\n## Example\nFor a real-world example see [ovk/dotfiles](https://github.com/ovk/dotfiles) repository.\n\n# CLI Usage\n```\nusage: dotref [-h] [-p PROFILE] [-d DOTDIR] [-s STATEFILE] [-v] {init,sync,unlink,status,profiles,version}\n\nSimple tool to manage dotfiles\n\npositional arguments:\n  {init,sync,unlink,status,profiles,version}\n                        Command to execute\n\noptions:\n  -h, --help            show this help message and exit\n  -p PROFILE, --profile PROFILE\n                        Name of the profile to use for init and profiles commands. Use profiles to see\n                        all available profiles.\n  -d DOTDIR, --dotdir DOTDIR\n                        Directory containing dotref profiles and state file (default: dotref)\n  -s STATEFILE, --statefile STATEFILE\n                        Name of the state file in which dotref will keep current profile and settings (default:\n                        .dotref.json in the DOTDIR directory)\n  -v, --verbose         Produce more verbose output\n```\n\n# Terminal Colors\nBy default dotref will use ANSI colors if possible.\nThis can be disabled be setting either `NO_COLOR` or `DOTREF_NO_COLOR` environment variables\n(as per [no-color](https://no-color.org/) convention).\n\n# Installation\nDotref's only dependency is Python 3.6 or newer.\n\n## Manual installation\nDotref is just a single Python file with no dependencies, and can be easily installed manually:\n\n```\ncurl https://raw.githubusercontent.com/ovk/dotref/master/dotref -o dotref\nchmod +x dotref\nsudo mv dotref /usr/local/bin\n```\n\n## PyPi\nDotref is also available as PyPi package [dotref](https://pypi.org/project/dotref/).\n\nIt can be installed with Pip:\n\n```\nsudo pip install dotref\n```\n\n## Arch Linux AUR Package\nFor Arch Linux, dotref can be installed from the AUR [dotref](https://aur.archlinux.org/packages/dotref/) package.\n\n## Windows\nDotref works on Windows and has been tested in WSL2, MSYS2 and Cygwin (it may work in other environments as well).\nIt can be either installed manually or with `pip`.\n\n## Shell Completion\nArch package includes shell auto-completion for Bash and Fish shells, which will be installed automatically.\nFor other types of installations, the completion [files](https://github.com/ovk/dotref/tree/master/completions)\ncan be downloaded and installed manually, if desired.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fovk%2Fdotref","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fovk%2Fdotref","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fovk%2Fdotref/lists"}