├── .gitignore ├── .shellcheckrc ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── configure-git.sh ├── git-sync.sh ├── open-repo.sh ├── repo.conf.example ├── scripts ├── log.sh ├── pull.sh ├── push.sh ├── status.sh └── sync.sh ├── setup-interactive.sh ├── setup-multi-repo.sh └── setup-single-repo.sh /.gitignore: -------------------------------------------------------------------------------- 1 | repo.conf 2 | 3 | # Visual Studio Code 4 | .vscode/* 5 | !.vscode/settings.json 6 | -------------------------------------------------------------------------------- /.shellcheckrc: -------------------------------------------------------------------------------- 1 | external-sources=true 2 | 3 | # Don't suggest read without -r will mangle backslashes 4 | disable=SC2162 5 | 6 | # Don't suggest can't follow non-constant source 7 | disable=SC1090 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "shellcheck.ignorePatterns": { 3 | "**/git-sync.sh": true 4 | } 5 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## 2024-05-12 6 | 7 | Big refactoring of the setup scripts: now three different scripts are provided you can choose from. 8 | 9 | Breaking change: 10 | In a multi-repo setup the scripts get renamed using the name of the git repo (e.g. `sync.sh` -> `sync_notes.sh`). Therefore the widgets and tasks inside of Tasker may have to be modified accordingly. 11 | There should be no breaking change in a single-repo setup. 12 | 13 | ## 2024-01-08 14 | 15 | - many bug fixes related to using and checking the path to a git repo 16 | 17 | ## 2023-12-28 18 | 19 | Breaking changes: 20 | 21 | - Path provided in `repo.conf` through `GH_REPO` has changed from relative path to an absolute path and it was renamed to `GIT_REPO_PATH`. Example: GIT_REPO_PATH=~/storage/shared/git/notes 22 | 23 | Changes: 24 | 25 | - support for multi-repo setup 26 | - enhance setup scripts to make the setup more user friendly 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | I'm happy to accept pull requests! 4 | 5 | When modifying a shell script, please make sure that there are no errors or warnings when checked with [ShellCheck](https://github.com/koalaman/shellcheck/). You can use ShellCheck in many different ways. I personally use the [ShellCheck for Visual Studio Code extension](https://github.com/vscode-shellcheck/vscode-shellcheck). 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Termux scripts for syncing Git repositories 2 | 3 | This repo includes scripts to simplify the setup of syncing Git repositories on Android devices using [Termux](https://termux.dev/). Termux is an Android terminal emulator and Linux environment app. 4 | 5 | The original purpose for me was to auto-sync my [Obsidian](https://obsidian.md/) vault between devices with Git. 6 | 7 | The idea to use Termux for automatic syncing came from [Rene Schallner](https://github.com/renerocksai): [Syncing your Obsidian vault to Android via an encrypted GitHub repository](https://renerocks.ai/blog/obsidian-encrypted-github-android/#shortcuts-for-committing-pushing-and-pulling) 8 | 9 | For syncing I'm now using the script `git-sync.sh` by Simon Thum: [simonthum/git-sync](https://github.com/simonthum/git-sync) 10 | 11 | ## Why use Termux? 12 | 13 | There are many ways to sync git repositories with Android devices. However, in my experience, this setup with git inside the Termux environment is the most flexible and reliable. 14 | 15 | Alternatives: 16 | 17 | - Obsidian community plugin [Obsidian Git](https://github.com/denolehov/obsidian-git): Great on desktop, but the mobile version has some [limitations](https://github.com/denolehov/obsidian-git#mobile) that make it a bad choice for me. 18 | - Android app [MGit](https://github.com/maks/MGit/): No auto-sync in the background possible, has problems with storage permissions (see [issue 620](https://github.com/maks/MGit/issues/620)). 19 | - Android app [GitJournal](https://github.com/GitJournal/GitJournal): No auto-sync in the background possible. 20 | - [Syncthing](https://syncthing.net/): Generally a great solution for syncing! However, in my opinion you shouldn't use Syncthing to sync a Git repository. Also, I don't like having a file synced multiple times in the background while I'm editing it. 21 | 22 | ## Setup 23 | 24 | ### Preparation 25 | 26 | - Install [Tasker](https://tasker.joaoapps.com/download.html), [Termux](https://f-droid.org/en/packages/com.termux/), [Termux Widget](https://f-droid.org/en/packages/com.termux.widget/) and [Termux Tasker](https://f-droid.org/packages/com.termux.tasker/) 27 | - Inside Termux, install some required packages: 28 | 29 | ```sh 30 | pkg update && pkg upgrade 31 | pkg install git openssh rsync 32 | ``` 33 | 34 | - Install an editor of your choice. See [available text editors in Termux](https://wiki.termux.com/wiki/Text_Editors). We use nano here as the default: 35 | 36 | ```sh 37 | pkg install nano 38 | ``` 39 | 40 | If you don't want to use nano, you have to change the default editor for Git in `configure-git.sh`. If you want to customize nano you can find [here a how-to for Termux](https://github.com/SignsAriyo/How-To-Customize-the-Nano-Editor-on-Termux). 41 | 42 | - Give Termux access to your storage ([Termux-setup-storage](https://wiki.termux.com/wiki/Termux-setup-storage)): 43 | 44 | ```sh 45 | termux-setup-storage 46 | ``` 47 | 48 | ### Setup SSH 49 | 50 | #### Accessing Termux from your computer (optional) 51 | 52 | _If you set up remote access to Termux on your Android device, the following steps will be easier._ 53 | 54 | [Termux Remote Access](https://wiki.termux.com/wiki/Remote_Access) 55 | 56 | Start OpenSSH server in Termux: 57 | 58 | ```sh 59 | sshd 60 | ``` 61 | 62 | Access your Termux session from another device (change the IP address to the address of your device, e.g. use `ifconfig`): 63 | 64 | ```sh 65 | ssh -p 8022 192.168.0.108 66 | ``` 67 | 68 | #### Accessing remote Git repository 69 | 70 | If you want to use SSH for accessing your remote Git repositories, create a new key pair: 71 | 72 | ```sh 73 | ssh-keygen -t ed25519 -C "your optional comment" 74 | eval "$(ssh-agent -s)" # start the ssh-agent in the background 75 | ssh-add ~/.ssh/id_ed25519 76 | cat ~/.ssh/id_ed25519.pub 77 | ``` 78 | 79 | Copy the public key to your Git remote server (GitHub, ...). 80 | 81 | ### Setup git 82 | 83 | ```sh 84 | git config --global user.email "you@example.com" 85 | git config --global user.name "Your Name" 86 | ``` 87 | 88 | Clone this repo inside Termux: 89 | 90 | ```sh 91 | cd ~ 92 | git clone https://github.com/davidkopp/termux-scripts.git 93 | cd termux-scripts 94 | ``` 95 | 96 | ### Setup sync 97 | 98 | _Note: During the setup some changes are made to your git configuration. If you want other options, modify the script 'configure-git.sh'._ 99 | 100 | First ensure that all scripts are executable: 101 | 102 | ```sh 103 | chmod +x *.sh 104 | ``` 105 | 106 | You can choose between the three different ways of setting up your repository/repositories: 107 | 108 | - Interactive mode (`setup-interactive.sh`) 109 | - provides the possibility to clone a Git repository 110 | - can also be used if a local Git repository already exists 111 | - can be used for both, single-repo setup and multi-repo setup 112 | - Single-repo setup (`setup-single-repo.sh`) 113 | - Git repository has to already exist locally 114 | - path to this single repo is stored in a config file called `repo.conf` in your home directory 115 | - Multi-repo setup (`setup-multi-repo.sh`) 116 | - Git repository has to already exist locally 117 | - no central config file, path to the respective repository must be provided as an argument for each script later on (e.g. `sync.sh ~/storage/shared/git/notes`) 118 | 119 | #### Single-repo setup 120 | 121 | Single-repo setup means that you only have one Git repository that you want to sync. The path to this single repo is stored in a config file called `repo.conf` in your home directory. 122 | 123 | You can either use an interactive mode (`setup-interactive.sh`) or directly provide the required parameters as arguments to the script `setup-single-repo.sh`. 124 | 125 | ```sh 126 | ./setup-single-repo.sh path-to-repo branch-name 127 | ``` 128 | 129 | Arguments: 130 | 131 | - `path-to-repo`: local path to git repo (required) 132 | - `branch-name`: git branch name (optional, default "main") 133 | 134 | Example with real values: 135 | 136 | ```sh 137 | ./setup-single-repo.sh ~/storage/shared/git/notes main 138 | ``` 139 | 140 | Now you are done with the setup in Termux. 141 | 142 | You can now open your launcher's widget menu, select Termux:Widget and place the widget for a script on your home screen. If you want to setup automatic sync for a repository see the section [Setup automatic sync](#setup-automatic-sync) below. 143 | 144 | #### Multi-repo setup 145 | 146 | Multi-repo setup means that you have multiple Git repositories that you want to sync. 147 | The path to a single repository must be provided as an argument for each script later on (e.g. `sync.sh ~/storage/shared/git/notes`). 148 | 149 | You can either use an interactive mode (`setup-interactive.sh`) or directly provide the required parameters as arguments to the script `setup-multi-repo.sh`. 150 | 151 | ```sh 152 | ./setup-multi-repo.sh path-to-repo branch-name repo-name 153 | ``` 154 | 155 | Arguments: 156 | 157 | - `path-to-repo`: local path to git repo (required) 158 | - `branch-name`: git branch name (optional, default "main") 159 | - `repo-name`: name used as a suffix for script names (optional, if not provided the name of the repo is used) 160 | 161 | Example with real values: 162 | 163 | ```sh 164 | ./setup-multi-repo.sh ~/storage/shared/git/notes main notes 165 | ``` 166 | 167 | Now you are done with the setup of one repository. If you want to setup another repository, just repeat the same steps and provide a different path to the script. 168 | 169 | You can now open your launcher's widget menu, select Termux:Widget and place the widget for a script on your home screen. If you want to setup automatic sync for a repository see the section [Setup automatic sync](#setup-automatic-sync) below. 170 | 171 | ### Setup automatic sync 172 | 173 | To automatically commit and sync changes I use the Termux add-on [Termux:Tasker](https://github.com/termux/termux-tasker). 174 | 175 | Tasker profile configuration (simplified): 176 | 177 | - Trigger: 178 | - Every 1h 179 | - Wifi Connected 180 | - Task "Git Sync": 181 | - Flash with text (e.g. "Git sync") 182 | - Termux: 183 | - Executable: `sync.sh` 184 | - Arguments (not used anymore, was used in the past to provide a path in a multi-repo setup) 185 | - ✔ Wait for result for commands 186 | - Timeout: 30 seconds 187 | 188 | Mathis Gauthey has also published a great tutorial with some helpful screenshots: [How to Use Obsidian Git Sync on Android](https://mathisgauthey.github.io/how-to-use-obsidian-git-sync-on-android/) 189 | 190 | As an alternative you can also setup a cronjob (see [here](https://forum.obsidian.md/t/guide-using-git-to-sync-your-obsidian-vault-on-android-devices/41887) for advice). 191 | -------------------------------------------------------------------------------- /configure-git.sh: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/bash 2 | 3 | # Ensure check if directory is safe is disabled, because in Termux we have a shared environment! 4 | git config --global safe.directory '*' 5 | 6 | # Set nano as default editor 7 | git config --global core.editor "nano" 8 | 9 | # To avoid conflicts between Linux and Windows, set git file mode setting to false: 10 | git config core.fileMode false 11 | 12 | # Configure branch `main` for sync: 13 | git config "branch.${GIT_BRANCH_NAME}.sync" true 14 | 15 | # Automatically add new (untracked) files and sync them: 16 | git config "branch.${GIT_BRANCH_NAME}.syncNewFiles" true 17 | 18 | # Set commit message: 19 | git config "branch.${GIT_BRANCH_NAME}.syncCommitMsg" "android on \$(printf '%(%Y-%m-%d %H:%M:%S)T\\n' -1)" 20 | -------------------------------------------------------------------------------- /git-sync.sh: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/bash 2 | # 3 | # git-sync 4 | # 5 | # synchronize tracking repositories 6 | # 7 | # 2012-20 by Simon Thum and contributors 8 | # Licensed as: CC0 9 | # 10 | # This script intends to sync via git near-automatically 11 | # in "tracking" repositories where a nice history is not 12 | # crucial, but having one at all is. 13 | # 14 | # Unlike the myriad of scripts to do just that already available, 15 | # it follows the KISS principle: It is small, requires nothing but 16 | # git and bash, but does not even try to shield you from git. 17 | # 18 | # Mode sync (default) 19 | # 20 | # Sync will likely get from you from a dull normal git repo with trivial 21 | # changes to an updated dull normal git repo equal to origin. No more, 22 | # no less. The intent is to do everything that's needed to sync 23 | # automatically, and resort to manual intervention as soon 24 | # as something non-trivial occurs. It is designed to be safe 25 | # in that it will likely refuse to do anything not known to 26 | # be safe. 27 | # 28 | # Mode check 29 | # 30 | # Check only performs the basic checks to make sure the repository 31 | # is in an orderly state to continue syncing, i.e. committing 32 | # changes, pull etc. without losing any data. When check returns 33 | # 0, sync can start immediately. This does not, however, indicate 34 | # that syncing is at all likely to succeed. 35 | 36 | # command used to auto-commit file modifications 37 | DEFAULT_AUTOCOMMIT_CMD="git add -u ; git commit -m \"%message\";" 38 | 39 | # command used to auto-commit all changes 40 | ALL_AUTOCOMMIT_CMD="git add -A ; git commit -m \"%message\";" 41 | 42 | # default commit message substituted into autocommit commands 43 | DEFAULT_AUTOCOMMIT_MSG="changes from $(uname -n) on $(date)" 44 | 45 | 46 | # AUTOCOMMIT_CMD="echo \"Please commit or stash pending changes\"; exit 1;" 47 | # TODO mode for stash push & pop 48 | 49 | print_usage() { 50 | cat << EOF 51 | usage: $0 [-h] [-n] [-s] [MODE] 52 | 53 | Synchronize the current branch to a remote backup 54 | MODE may be either "sync" (the default) or "check", to verify that the branch is ready to sync 55 | 56 | OPTIONS: 57 | -h Show this message 58 | -n Commit new files even if branch.\$branch_name.syncNewFiles isn't set 59 | -s Sync the branch even if branch.\$branch_name.sync isn't set 60 | EOF 61 | } 62 | sync_new_files_anyway="false" 63 | sync_anyway="false" 64 | 65 | while getopts "hns" opt ; do 66 | case $opt in 67 | h ) 68 | print_usage 69 | exit 0 70 | ;; 71 | n ) 72 | sync_new_files_anyway="true" 73 | ;; 74 | s ) 75 | sync_anyway="true" 76 | ;; 77 | esac 78 | done 79 | shift $((OPTIND-1)) 80 | 81 | # 82 | # utility functions, some adapted from git bash completion 83 | # 84 | 85 | __log_msg() 86 | { 87 | echo git-sync: $1 88 | } 89 | 90 | # echo the git dir 91 | __gitdir() 92 | { 93 | if [ "true" = "$(git rev-parse --is-inside-work-tree "$PWD" | head -1)" ]; then 94 | git rev-parse --git-dir "$PWD" 2>/dev/null 95 | fi 96 | } 97 | 98 | # echos repo state 99 | git_repo_state () 100 | { 101 | local g="$(__gitdir)" 102 | if [ -n "$g" ]; then 103 | if [ -f "$g/rebase-merge/interactive" ]; then 104 | echo "REBASE-i" 105 | elif [ -d "$g/rebase-merge" ]; then 106 | echo "REBASE-m" 107 | else 108 | if [ -d "$g/rebase-apply" ]; then 109 | echo "AM/REBASE" 110 | elif [ -f "$g/MERGE_HEAD" ]; then 111 | echo "MERGING" 112 | elif [ -f "$g/CHERRY_PICK_HEAD" ]; then 113 | echo "CHERRY-PICKING" 114 | elif [ -f "$g/BISECT_LOG" ]; then 115 | echo "BISECTING" 116 | fi 117 | fi 118 | if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then 119 | if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then 120 | echo "|BARE" 121 | else 122 | echo "|GIT_DIR" 123 | fi 124 | elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then 125 | git diff --no-ext-diff --quiet --exit-code || echo "|DIRTY" 126 | # if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then 127 | # git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$" 128 | # fi 129 | # 130 | # if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then 131 | # if [ -n "$(git ls-files --others --exclude-standard)" ]; then 132 | # u="%" 133 | # fi 134 | # fi 135 | # 136 | # if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then 137 | # __git_ps1_show_upstream 138 | # fi 139 | fi 140 | else 141 | echo "NOGIT" 142 | fi 143 | } 144 | 145 | # check if we only have untouched, modified or (if configured) new files 146 | check_initial_file_state() 147 | { 148 | local syncNew="$(git config --get --bool branch.$branch_name.syncNewFiles)" 149 | if [[ "true" == "$syncNew" || "true" == "$sync_new_files_anyway" ]]; then 150 | # allow for new files 151 | if [ ! -z "$(git status --porcelain | grep -E '^[^ \?][^M\?] *')" ]; then 152 | echo "NonNewOrModified" 153 | fi 154 | else 155 | # also bail on new files 156 | if [ ! -z "$(git status --porcelain | grep -E '^[^ ][^M] *')" ]; then 157 | echo "NotOnlyModified" 158 | fi 159 | fi 160 | } 161 | 162 | # look for local changes 163 | # used to decide if autocommit should be invoked 164 | local_changes() 165 | { 166 | if [ ! -z "$(git status --porcelain | grep -E '^(\?\?|[MARC] |[ MARC][MD])*')" ]; then 167 | echo "LocalChanges" 168 | fi 169 | } 170 | 171 | # determine sync state of repository, i.e. how the remote relates to our HEAD 172 | sync_state() 173 | { 174 | local count="$(git rev-list --count --left-right $remote_name/$branch_name...HEAD)" 175 | 176 | case "$count" in 177 | "") # no upstream 178 | echo "noUpstream" 179 | false 180 | ;; 181 | "0 0") 182 | echo "equal" 183 | true 184 | ;; 185 | "0 "*) 186 | echo "ahead" 187 | true 188 | ;; 189 | *" 0") 190 | echo "behind" 191 | true 192 | ;; 193 | *) 194 | echo "diverged" 195 | true 196 | ;; 197 | esac 198 | } 199 | 200 | # exit, issue warning if not in sync 201 | exit_assuming_sync() { 202 | if [ "equal" == "$(sync_state)" ] ; then 203 | __log_msg "In sync, all fine." 204 | exit 0; 205 | else 206 | __log_msg "Synchronization FAILED! You should definitely check your repository carefully!" 207 | __log_msg "(Possibly a transient network problem? Please try again in that case.)" 208 | exit 3 209 | fi 210 | } 211 | 212 | # 213 | # Here git-sync actually starts 214 | # 215 | 216 | # first some sanity checks 217 | rstate="$(git_repo_state)" 218 | if [[ -z "$rstate" || "|DIRTY" = "$rstate" ]]; then 219 | __log_msg "Preparing. Repo in $(__gitdir)" 220 | elif [[ "NOGIT" = "$rstate" ]] ; then 221 | __log_msg "No git repository detected. Exiting." 222 | exit 128 # matches git's error code 223 | else 224 | __log_msg "Git repo state considered unsafe for sync: $(git_repo_state)" 225 | exit 2 226 | fi 227 | 228 | # determine the current branch (thanks to stackoverflow) 229 | branch_name=$(git symbolic-ref -q HEAD) 230 | branch_name=${branch_name##refs/heads/} 231 | 232 | if [ -z "$branch_name" ] ; then 233 | __log_msg "Syncing is only possible on a branch." 234 | git status 235 | exit 2 236 | fi 237 | 238 | # while at it, determine the remote to operate on 239 | remote_name=$(git config --get branch.$branch_name.pushRemote) 240 | if [ -z "$remote_name" ] ; then 241 | remote_name=$(git config --get remote.pushDefault) 242 | fi 243 | if [ -z "$remote_name" ] ; then 244 | remote_name=$(git config --get branch.$branch_name.remote) 245 | fi 246 | 247 | if [ -z "$remote_name" ] ; then 248 | __log_msg "the current branch does not have a configured remote." 249 | echo 250 | __log_msg "Please use" 251 | echo 252 | __log_msg " git branch --set-upstream-to=[remote_name]/$branch_name" 253 | echo 254 | __log_msg "replacing [remote_name] with the name of your remote, i.e. - origin" 255 | __log_msg "to set the remote tracking branch for git-sync to work" 256 | exit 2 257 | fi 258 | 259 | # check if current branch is configured for sync 260 | if [[ "true" != "$(git config --get --bool branch.$branch_name.sync)" && "true" != "$sync_anyway" ]] ; then 261 | echo 262 | __log_msg "Please use" 263 | echo 264 | __log_msg " git config --bool branch.$branch_name.sync true" 265 | echo 266 | __log_msg "to enlist branch $branch_name for synchronization." 267 | __log_msg "Branch $branch_name has to have a same-named remote branch" 268 | __log_msg "for git-sync to work." 269 | echo 270 | __log_msg "(If you don't know what this means, you should change that" 271 | __log_msg "before relying on this script. You have been warned.)" 272 | echo 273 | exit 1 274 | fi 275 | 276 | # determine mode 277 | if [[ -z "$1" || "$1" == "sync" ]]; then 278 | mode="sync" 279 | elif [[ "check" == "$1" ]]; then 280 | mode="check" 281 | else 282 | __log_msg "Mode $1 not recognized" 283 | exit 100 284 | fi 285 | 286 | __log_msg "Mode $mode" 287 | 288 | __log_msg "Using $remote_name/$branch_name" 289 | 290 | # check for intentionally unhandled file states 291 | if [ ! -z "$(check_initial_file_state)" ] ; then 292 | __log_msg "There are changed files you should probably handle manually." 293 | git status 294 | exit 1 295 | fi 296 | 297 | # if in check mode, this is all we need to know 298 | if [ $mode == "check" ] ; then 299 | __log_msg "check OK; sync may start." 300 | exit 0 301 | fi 302 | 303 | # check if we have to commit local changes, if yes, do so 304 | if [ ! -z "$(local_changes)" ]; then 305 | autocommit_cmd="" 306 | config_autocommit_cmd="$(git config --get branch.$branch_name.autocommitscript)" 307 | 308 | # discern the three ways to auto-commit 309 | if [ ! -z "$config_autocommit_cmd" ]; then 310 | autocommit_cmd="$config_autocommit_cmd" 311 | elif [[ "true" == "$(git config --get --bool branch.$branch_name.syncNewFiles)" || "true" == "$sync_new_files_anyway" ]]; then 312 | autocommit_cmd=${ALL_AUTOCOMMIT_CMD} 313 | else 314 | autocommit_cmd=${DEFAULT_AUTOCOMMIT_CMD} 315 | fi 316 | 317 | commit_msg="$(git config --get branch.$branch_name.syncCommitMsg)" 318 | if [ "" == "$commit_msg" ]; then 319 | commit_msg=${DEFAULT_AUTOCOMMIT_MSG} 320 | fi 321 | autocommit_cmd=$(echo "$autocommit_cmd" | sed "s/%message/$commit_msg/") 322 | 323 | __log_msg "Committing local changes using ${autocommit_cmd}" 324 | eval $autocommit_cmd 325 | 326 | # after autocommit, we should be clean 327 | rstate="$(git_repo_state)" 328 | if [[ ! -z "$rstate" ]]; then 329 | __log_msg "Auto-commit left uncommitted changes. Please add or remove them as desired and retry." 330 | exit 1 331 | fi 332 | fi 333 | 334 | # fetch remote to get to the current sync state 335 | # TODO make fetching/pushing optional 336 | __log_msg "Fetching from $remote_name/$branch_name" 337 | git fetch $remote_name $branch_name 338 | if [ $? != 0 ] ; then 339 | __log_msg "git fetch $remote_name returned non-zero. Likely a network problem; exiting." 340 | exit 3 341 | fi 342 | 343 | case "$(sync_state)" in 344 | "noUpstream") 345 | __log_msg "Strange state, you're on your own. Good luck." 346 | exit 2 347 | ;; 348 | "equal") 349 | exit_assuming_sync 350 | ;; 351 | "ahead") 352 | __log_msg "Pushing changes..." 353 | git push $remote_name $branch_name:$branch_name 354 | if [ $? == 0 ]; then 355 | exit_assuming_sync 356 | else 357 | __log_msg "git push returned non-zero. Likely a connection failure." 358 | exit 3 359 | fi 360 | ;; 361 | "behind") 362 | __log_msg "We are behind, fast-forwarding..." 363 | git merge --ff --ff-only $remote_name/$branch_name 364 | if [ $? == 0 ]; then 365 | exit_assuming_sync 366 | else 367 | __log_msg "git merge --ff --ff-only returned non-zero ($?). Exiting." 368 | exit 2 369 | fi 370 | ;; 371 | "diverged") 372 | __log_msg "We have diverged. Trying to rebase..." 373 | git rebase $remote_name/$branch_name 374 | if [[ $? == 0 && -z "$(git_repo_state)" && "ahead" == "$(sync_state)" ]] ; then 375 | __log_msg "Rebasing went fine, pushing..." 376 | git push $remote_name $branch_name:$branch_name 377 | exit_assuming_sync 378 | else 379 | __log_msg "Rebasing failed, likely there are conflicting changes. Resolve them and finish the rebase before repeating git-sync." 380 | exit 1 381 | fi 382 | # TODO: save master, if rebasing fails, make a branch of old master 383 | ;; 384 | esac 385 | -------------------------------------------------------------------------------- /open-repo.sh: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/bash 2 | 3 | # Variable 'GIT_REPO_PATH' must be set either by providing it via $HOME/repo.conf or by providing it as an argument to this script (e.g. `open-repo.sh ~/storage/shared/git/notes`) 4 | if [[ $# -gt 0 ]]; then 5 | GIT_REPO_PATH=$1 6 | elif [[ -e "$HOME/repo.conf" ]]; then 7 | # shellcheck source=repo.conf.example 8 | source "$HOME/repo.conf" 9 | if [[ -z "${GIT_REPO_PATH}" ]]; then 10 | echo 'Config file '"$HOME"/repo.conf' exists, but mandatory variable GIT_REPO_PATH is not set!' 11 | exit 1 12 | fi 13 | fi 14 | 15 | if [[ -z "${GIT_REPO_PATH}" ]]; then 16 | echo "Path to Git repository not provided! Usage: $(basename "$0") git-path" 17 | exit 1 18 | fi 19 | 20 | canonical_path_to_repo=$(readlink -f "${GIT_REPO_PATH/\~/$HOME}") 21 | if [[ ! -d "${canonical_path_to_repo}" ]]; then 22 | echo "Provided path '${canonical_path_to_repo}' does not exist!" 23 | exit 1 24 | fi 25 | 26 | cd "${canonical_path_to_repo}" || (echo "cd into git dir failed!" && exit 1) 27 | 28 | if [[ ! -d "$(git rev-parse --git-dir 2>/dev/null)" ]]; then 29 | echo "Provided path '${PWD}' is not a Git repository!" 30 | exit 1 31 | fi 32 | 33 | echo "Using git repository at ${canonical_path_to_repo}" 34 | -------------------------------------------------------------------------------- /repo.conf.example: -------------------------------------------------------------------------------- 1 | ### Configuration for syncing a single git repository ### 2 | 3 | # This file has to be placed in your home directory. 4 | # The copying to your home directory is part of the script setup-single-repo.sh. 5 | 6 | # The variable GIT_REPO_PATH is mandatory in a single-repo git setup. 7 | # It must be set to the absolute path to your local Git repository. 8 | # It gets set by executing setup-single-repo.sh. 9 | # Full path required, e.g. ~/storage/shared/git/notes 10 | GIT_REPO_PATH= 11 | -------------------------------------------------------------------------------- /scripts/log.sh: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/bash 2 | 3 | if ! source "$HOME/open-repo.sh" "$1" 4 | then 5 | echo "Open repo failed!" 6 | read -n 1 -s -r -t 10 -p "Press any key to exit" 7 | fi 8 | 9 | git log 10 | 11 | # Wait for user input so the user is able see the logs 12 | read -n 1 -s -r -p "Press any key to exit" 13 | -------------------------------------------------------------------------------- /scripts/pull.sh: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/bash 2 | 3 | if ! source "$HOME/open-repo.sh" "$1" 4 | then 5 | echo "Open repo failed!" 6 | read -n 1 -s -r -t 10 -p "Press any key to exit" 7 | fi 8 | 9 | git pull 10 | 11 | # Wait some seconds so the user is able see what was done 12 | read -n 1 -s -r -t 5 -p "Press any key to exit" 13 | -------------------------------------------------------------------------------- /scripts/push.sh: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/bash 2 | 3 | if ! source "$HOME/open-repo.sh" "$1" 4 | then 5 | echo "Open repo failed!" 6 | read -n 1 -s -r -t 10 -p "Press any key to exit" 7 | fi 8 | 9 | git add -A 10 | git commit -m "android on $(date +%Y-%m-%d" "%H:%M:%S)" 11 | git push 12 | 13 | # Wait some seconds so the user is able see what was done 14 | read -n 1 -s -r -t 5 -p "Press any key to exit" 15 | -------------------------------------------------------------------------------- /scripts/status.sh: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/bash 2 | 3 | if ! source "$HOME/open-repo.sh" "$1" 4 | then 5 | echo "Open repo failed!" 6 | read -n 1 -s -r -t 10 -p "Press any key to exit" 7 | fi 8 | 9 | git status -v 10 | 11 | # Wait for user input so the user is able see the status 12 | read -n 1 -s -r -p "Press any key to exit" 13 | -------------------------------------------------------------------------------- /scripts/sync.sh: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/bash 2 | 3 | if ! source "$HOME/open-repo.sh" "$1" 4 | then 5 | echo "Open repo failed!" 6 | read -n 1 -s -r -t 10 -p "Press any key to exit" 7 | fi 8 | 9 | echo -e "Syncing repo '${PWD##*/}'...\n" 10 | if ! exec "$HOME/termux-scripts/git-sync.sh" 11 | then 12 | echo "Git sync failed!" 13 | read -n 1 -s -r -t 10 -p "Press any key to exit" 14 | fi 15 | -------------------------------------------------------------------------------- /setup-interactive.sh: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/bash 2 | 3 | # Default path used for cloning a new repository 4 | BASE_PATH_FOR_REPO_CLONING="$HOME/storage/shared/git" 5 | 6 | MY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 7 | 8 | echo "Do you want to clone a new repository (1) or provide a path to an already existing git repository on your device (2)?" 9 | read -p "Enter your choice (1 or 2): " choice 10 | case $choice in 11 | 1) 12 | # Clone a new git repository 13 | canonical_base_path=$(readlink -f "${BASE_PATH_FOR_REPO_CLONING}") 14 | echo "Git clone URL (repo will be cloned to '${canonical_base_path}/REPO_NAME'):" 15 | read GIT_REPO_URL 16 | echo "" 17 | mkdir -p "${canonical_base_path}" 18 | cd "${canonical_base_path}" || (echo "cd ${canonical_base_path} failed!" && exit 1) 19 | REPO_NAME="$(basename "$GIT_REPO_URL" .git)" 20 | GIT_REPO_PATH=$(readlink -f "${PWD}/${REPO_NAME}") 21 | if [[ -d $GIT_REPO_PATH ]]; then 22 | echo "Directory '${GIT_REPO_PATH}' already exists! Skip cloning of git repository ${GIT_REPO_URL}. Try to use existing directory instead." 23 | else 24 | if ! git clone "$GIT_REPO_URL"; then 25 | echo "Git clone of '$GIT_REPO_URL' failed!" 26 | exit 1 27 | fi 28 | echo "Git repository cloned to: ${GIT_REPO_PATH}" 29 | fi 30 | ;; 31 | 2) 32 | # Use an existing local Git repository 33 | echo "Path to your local Git repository (full path required):" 34 | read path_to_repo 35 | canonical_path_to_repo=$(readlink -f "${path_to_repo/\~/$HOME}") 36 | if [[ ! -d "${canonical_path_to_repo}" ]]; then 37 | echo "Provided git repo path '${canonical_path_to_repo}' does not exist!" 38 | exit 1 39 | fi 40 | GIT_REPO_PATH=$canonical_path_to_repo 41 | ;; 42 | *) 43 | echo "Invalid choice. Please enter 1 or 2." 44 | exit 1 45 | ;; 46 | esac 47 | 48 | # Ask for branch name, default is main 49 | if [[ -z "${GIT_BRANCH_NAME}" ]]; then 50 | echo -e "\nWhich branch do you want to use for syncing? (if none is provided, 'main' is used)" 51 | read GIT_BRANCH_NAME 52 | fi 53 | 54 | # Single or multi repo setup? 55 | echo -e "Do you want to setup sync for only one repository or for multiple ones?\nOne: (1)\nMultiple: (2)" 56 | read -p "Enter your choice (1 or 2): " choice 57 | case $choice in 58 | 1) 59 | source "$MY_DIR/setup-single-repo.sh" "${GIT_REPO_PATH}" "${GIT_BRANCH_NAME}" 60 | ;; 61 | 2) 62 | source "$MY_DIR/setup-multi-repo.sh" "${GIT_REPO_PATH}" "${GIT_BRANCH_NAME}" 63 | ;; 64 | esac 65 | -------------------------------------------------------------------------------- /setup-multi-repo.sh: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/bash 2 | 3 | # Arguments: 4 | # $1: local path to git repo (required) 5 | # $2: git branch name (optional, default "main") 6 | # $3: repo name (optional, default extracted repo name, used as a suffix for script names) 7 | 8 | GIT_REPO_PATH=$1 9 | GIT_BRANCH_NAME=$2 10 | REPO_NAME=$3 11 | 12 | if [[ -z "${GIT_REPO_PATH}" ]]; then 13 | echo -e "Path to local Git repository not provided!\nUsage: $(basename "$0") git-path\nAs an alternative you can also use the interactive setup script: setup-interactive.sh" 14 | exit 1 15 | fi 16 | if [[ -z "${GIT_BRANCH_NAME}" ]]; then 17 | GIT_BRANCH_NAME=main 18 | fi 19 | if [[ -z "${REPO_NAME}" ]]; then 20 | REPO_NAME=$(basename "$GIT_REPO_PATH") 21 | fi 22 | 23 | MY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 24 | 25 | ########################################### 26 | 27 | # First copy the latest version of open-repo.sh to home 28 | cp "$MY_DIR/open-repo.sh" "$HOME/open-repo.sh" 29 | chmod +x "$HOME/open-repo.sh" 30 | 31 | # shellcheck source=open-repo.sh 32 | if ! source "$HOME/open-repo.sh" "${GIT_REPO_PATH}" 33 | then 34 | echo "Open repo '${GIT_REPO_PATH}' failed!" 35 | exit 1 36 | fi 37 | 38 | if [[ ! $(git branch --list "${GIT_BRANCH_NAME}") ]]; then 39 | echo "Git branch '${GIT_BRANCH_NAME}' does not exist!" 40 | exit 1 41 | fi 42 | 43 | # Configure git repository 44 | source "$MY_DIR/configure-git.sh" 45 | 46 | # Setup scripts for Termux:Widget and Termux:Tasker 47 | rsync -a --delete "$MY_DIR/scripts/" "$MY_DIR/temp/" 48 | cd "$MY_DIR/temp/" || exit 1 49 | for file in *.sh; do 50 | sed -i "s|\$1|${GIT_REPO_PATH}|g" "$file" 51 | filename=$(basename "$file" .sh) 52 | new_filename="${filename}_${REPO_NAME}.sh" 53 | mv "$file" "$new_filename" 54 | done 55 | 56 | mkdir -p "$HOME/.shortcuts" 57 | chmod 700 -R "$HOME/.shortcuts" 58 | rsync -r "$MY_DIR/temp/" "$HOME/.shortcuts/" 59 | chmod +x "$HOME"/.shortcuts/*.sh 60 | 61 | mkdir -p "$HOME/.termux/tasker" 62 | chmod 700 -R "$HOME/.termux" 63 | rsync -r "$MY_DIR/temp/" "$HOME/.termux/tasker/" 64 | chmod +x "$HOME"/.termux/tasker/*.sh 65 | 66 | cd "$MY_DIR" || exit 1 67 | rm -r "$MY_DIR/temp" 68 | 69 | echo "Setup auto-sync of '${REPO_NAME}' was successful! (multi-repo setup)" 70 | -------------------------------------------------------------------------------- /setup-single-repo.sh: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/bash 2 | 3 | # Arguments: 4 | # $1: local path to git repo (only required, if ~/repo.conf does not exist yet) 5 | # $2: git branch name (optional, default "main") 6 | 7 | GIT_REPO_PATH=$1 8 | GIT_BRANCH_NAME=$2 9 | 10 | if [[ -z "${GIT_BRANCH_NAME}" ]]; then 11 | GIT_BRANCH_NAME=main 12 | fi 13 | 14 | ########################################### 15 | 16 | MY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 17 | 18 | # First copy the latest version of open-repo.sh to home 19 | cp "$MY_DIR/open-repo.sh" "$HOME/open-repo.sh" 20 | chmod +x "$HOME/open-repo.sh" 21 | 22 | # If repo.conf already exists, try to use it 23 | if [[ -e "$HOME/repo.conf" ]]; then 24 | echo 'Config file '"$HOME"/repo.conf' already exists. Try to use it ...' 25 | if grep -q "GH_REPO" "$HOME/repo.conf"; then 26 | echo 'Warning: File '"$HOME"/repo.conf' is using an outdated format! Consider deleting it ("rm ~/repo.conf") and rerun this script!' 27 | exit 1 28 | fi 29 | 30 | # shellcheck source=open-repo.sh 31 | if ! source "$HOME/open-repo.sh" 32 | then 33 | echo "Open repo with path defined in '"$HOME"/repo.conf' failed!" 34 | exit 1 35 | fi 36 | # Otherwise create new config file 37 | elif [[ -n "${GIT_REPO_PATH}" ]]; then 38 | cp "${MY_DIR}/repo.conf.example" "$HOME/repo.conf" 39 | sed -i "s|GIT_REPO_PATH=|GIT_REPO_PATH=${GIT_REPO_PATH}|" "$HOME/repo.conf" 40 | 41 | # shellcheck source=open-repo.sh 42 | if ! source "$HOME/open-repo.sh" "${GIT_REPO_PATH}" 43 | then 44 | echo "Open repo '${GIT_REPO_PATH}' failed!" 45 | exit 1 46 | fi 47 | else 48 | echo -e "Path to local Git repository not provided!\nUsage: $(basename "$0") git-path\nAs an alternative you can also use the interactive setup script: setup-interactive.sh" 49 | exit 1 50 | fi 51 | 52 | if [[ ! $(git branch --list "${GIT_BRANCH_NAME}") ]]; then 53 | echo "Git branch '${GIT_BRANCH_NAME}' does not exist!" 54 | exit 1 55 | fi 56 | 57 | # Configure git repository 58 | source "$MY_DIR/configure-git.sh" 59 | 60 | # Setup scripts for Termux:Widget 61 | mkdir -p "$HOME/.shortcuts" 62 | chmod 700 -R "$HOME/.shortcuts" 63 | rsync -r "$MY_DIR/scripts/" "$HOME/.shortcuts/" 64 | chmod +x "$HOME"/.shortcuts/*.sh 65 | 66 | # Setup scripts for Termux:Tasker 67 | mkdir -p "$HOME/.termux/tasker" 68 | chmod 700 -R "$HOME/.termux" 69 | rsync -r "$MY_DIR/scripts/" "$HOME/.termux/tasker/" 70 | chmod +x "$HOME"/.termux/tasker/*.sh 71 | 72 | REPO_NAME=$(basename "$GIT_REPO_PATH") 73 | 74 | echo "Setup auto-sync of '${REPO_NAME}' was successful! (single-repo setup)" 75 | --------------------------------------------------------------------------------