From f0b9e18e54e6502ae6fe623d937fb0f4b40d4368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20=C5=BBurawik?= Date: Mon, 26 Aug 2024 04:38:15 +0200 Subject: [PATCH 01/62] Add pathspec support in `git-missing` (#1156) * feat: Add pathspec support in git-missing Allow to specify a path to limit the commit difference list. This improvement allows users to focus on changes in specific directories or files when comparing branches for missing commits. * refactor: Improve pathspec handling in git-missing - Change pathspec from string to array to support multiple pathspecs - Remove unnecessary 'shift' command in argument processing loop - Simplify git log command execution by using a single codepath * chore: Update git-missing docs * chore: Fix a typo in git-missing docs --- Commands.md | 8 +++++++- bin/git-missing | 15 +++++++++++++-- man/git-missing.1 | 31 ++++++++++++++++++++++++++----- man/git-missing.html | 29 ++++++++++++++++++++++------- man/git-missing.md | 20 +++++++++++++++++--- 5 files changed, 85 insertions(+), 18 deletions(-) diff --git a/Commands.md b/Commands.md index af72af0..4ea5520 100644 --- a/Commands.md +++ b/Commands.md @@ -1296,7 +1296,7 @@ Creates a zip archive of the current git repository. The name of the archive wil ## git missing -Print out which commits are on one branch or the other but not both. +Print out which commits are on one branch or the other but not both. Optionally, you can specify a path to limit the comparison to a specific directory or file. ```bash $ git missing master @@ -1304,6 +1304,12 @@ $ git missing master > 97ef387 only on master ``` +```bash +$ git missing master -- src/ +< ed52989 only on current branch, in src/ directory +> 7988c4b only on master, in src/ directory +``` + ## git lock Lock a local file `filename`: diff --git a/bin/git-missing b/bin/git-missing index 9569468..32fbdab 100755 --- a/bin/git-missing +++ b/bin/git-missing @@ -1,7 +1,7 @@ #!/usr/bin/env bash usage() { - echo 1>&2 "usage: git missing [] []" + echo 1>&2 "usage: git missing [] [] [[--] ...]" } if [ "${#}" -lt 1 ] @@ -12,9 +12,20 @@ fi declare -a git_log_args=() declare -a branches=() +declare -a pathspec=() +declare parse_path=false + for arg in "$@" ; do + if [[ $parse_path == true ]]; then + pathspec+=("$@") + break + fi + case "$arg" in + --) + parse_path=true + ;; --*) git_log_args+=( "$arg" ) ;; @@ -38,4 +49,4 @@ else exit 1 fi -git log "${git_log_args[@]}" "$firstbranch"..."$secondbranch" --format="%m %h %s" --left-right +git log "${git_log_args[@]}" "$firstbranch"..."$secondbranch" --format="%m %h %s" --left-right -- "${pathspec[@]}" diff --git a/man/git-missing.1 b/man/git-missing.1 index 2e82e7a..413880d 100644 --- a/man/git-missing.1 +++ b/man/git-missing.1 @@ -1,16 +1,16 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "GIT\-MISSING" "1" "April 2018" "" "Git Extras" +.TH "GIT\-MISSING" "1" "August 2024" "" "Git Extras" . .SH "NAME" \fBgit\-missing\fR \- Show commits missing from another branch . .SH "SYNOPSIS" -\fBgit\-missing\fR [] [] +\fBgit\-missing\fR [] [] [[\-\-] \.\.\.] . .SH "DESCRIPTION" -Shows commits that are in either of two branches but not both\. Useful for seeing what would come across in a merge or push\. +Shows commits that are in either of two branches but not both\. Useful for seeing what would come across in a merge or push\. Optionally, the comparison can be limited to specific paths\. . .SH "OPTIONS" [] @@ -30,6 +30,12 @@ Second branch to compare\. .P Any flags that should be passed to \'git log\', such as \-\-no\-merges\. . +.P +[[\-\-] \.\.\.] +. +.P +Optional path specifications (pathspec) to limit the comparison to specific files or directories\. For more details about the pathspec syntax, see the pathspec entry in gitglossary[7] \fIhttps://git\-scm\.com/docs/gitglossary#Documentation/gitglossary\.txt\-aiddefpathspecapathspec\fR\. +. .SH "EXAMPLES" Show commits on either my current branch or master but not both: . @@ -60,11 +66,26 @@ $ git missing foo bar . .IP "" 0 . +.P +Show commits on either my current branch or master but not both, limited to the src/ directory: +. +.IP "" 4 +. +.nf + +$ git missing master \-\- src/ +< ed52989 only on current checked out branch, in src/ directory +> 7988c4b only on master, in src/ directory +. +.fi +. +.IP "" 0 +. .SH "AUTHOR" Written by Nate Jones <\fInate@endot\.org\fR> . .SH "REPORTING BUGS" -<\fIhttp://github\.com/tj/git\-extras/issues\fR> +<\fIhttps://github\.com/tj/git\-extras/issues\fR> . .SH "SEE ALSO" -<\fIhttp://github\.com/tj/git\-extras\fR> +<\fIhttps://github\.com/tj/git\-extras\fR> diff --git a/man/git-missing.html b/man/git-missing.html index 427d924..d82a07c 100644 --- a/man/git-missing.html +++ b/man/git-missing.html @@ -76,12 +76,13 @@

SYNOPSIS

-

git-missing [<first branch>] <second branch> [<git log options>]

+

git-missing [<first branch>] <second branch> [<git log options>] [[--] <path>...]

DESCRIPTION

-

Shows commits that are in either of two branches but not both. Useful for - seeing what would come across in a merge or push.

+

Shows commits that are in either of two branches but not both. Useful for + seeing what would come across in a merge or push. Optionally, the comparison + can be limited to specific paths.

OPTIONS

@@ -97,6 +98,12 @@

Any flags that should be passed to 'git log', such as --no-merges.

+

[[--] <path>...]

+ +

Optional path specifications (pathspec) to limit the comparison to specific + files or directories. For more details about the pathspec syntax, see the + pathspec entry in gitglossary[7].

+

EXAMPLES

Show commits on either my current branch or master but not both:

@@ -113,22 +120,30 @@ > f38797e only on bar +

Show commits on either my current branch or master but not both, limited to the + src/ directory:

+ +
$ git missing master -- src/
+< ed52989 only on current checked out branch, in src/ directory
+> 7988c4b only on master, in src/ directory
+
+

AUTHOR

-

Written by Nate Jones <nate@endot.org>

+

Written by Nate Jones <nate@endot.org>

REPORTING BUGS

-

<http://github.com/tj/git-extras/issues>

+

<https://github.com/tj/git-extras/issues>

SEE ALSO

-

<http://github.com/tj/git-extras>

+

<https://github.com/tj/git-extras>

  1. -
  2. April 2018
  3. +
  4. August 2024
  5. git-missing(1)
diff --git a/man/git-missing.md b/man/git-missing.md index 6f4ff65..8b49430 100644 --- a/man/git-missing.md +++ b/man/git-missing.md @@ -3,12 +3,13 @@ git-missing(1) -- Show commits missing from another branch ## SYNOPSIS -`git-missing` [<first branch>] <second branch> [<git log options>] +`git-missing` [<first branch>] <second branch> [<git log options>] [[--] <path>...] ## DESCRIPTION - Shows commits that are in either of two branches but not both. Useful for - seeing what would come across in a merge or push. + Shows commits that are in either of two branches but not both. Useful for + seeing what would come across in a merge or push. Optionally, the comparison + can be limited to specific paths. ## OPTIONS @@ -24,6 +25,12 @@ git-missing(1) -- Show commits missing from another branch Any flags that should be passed to 'git log', such as --no-merges. + [[--] <path>...] + + Optional path specifications (pathspec) to limit the comparison to specific + files or directories. For more details about the pathspec syntax, see the + pathspec entry in [gitglossary[7]](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec). + ## EXAMPLES Show commits on either my current branch or master but not both: @@ -38,6 +45,13 @@ git-missing(1) -- Show commits missing from another branch < b8f0d14 only on foo > f38797e only on bar + Show commits on either my current branch or master but not both, limited to the + src/ directory: + + $ git missing master -- src/ + < ed52989 only on current checked out branch, in src/ directory + > 7988c4b only on master, in src/ directory + ## AUTHOR Written by Nate Jones <> From 2c161aae024560d36931414e35c7ea3a25ae252f Mon Sep 17 00:00:00 2001 From: Revisor Date: Wed, 4 Sep 2024 07:59:35 +0200 Subject: [PATCH 02/62] Update instructions for the OpenSUSE installation (#1157) --- Installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Installation.md b/Installation.md index 2e82a69..174e8e0 100644 --- a/Installation.md +++ b/Installation.md @@ -41,10 +41,10 @@ $ sudo dnf install git-extras ### openSUSE -Substitute your openSUSE version in the command below (in this case we are considering openSUSE Leap 15.2): +Substitute your openSUSE version in the command below (in this case we are considering openSUSE Leap 15.6): ```bash -$ sudo zypper ar https://download.opensuse.org/repositories/devel:/tools:/scm/openSUSE_Leap_15.2/devel:tools:scm.repo +$ sudo zypper ar https://download.opensuse.org/repositories/devel:/tools:/scm/15.6/devel:tools:scm.repo ``` and install it: From f0671e27336258456f0ec2d2dac0f7b5ad646fe0 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Mon, 16 Sep 2024 12:19:03 -0700 Subject: [PATCH 03/62] Update git-repl.md: typo: "let's" for "lets" (#1158) --- man/git-repl.1 | 2 +- man/git-repl.html | 2 +- man/git-repl.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/man/git-repl.1 b/man/git-repl.1 index c7ab7a8..b3040f2 100644 --- a/man/git-repl.1 +++ b/man/git-repl.1 @@ -10,7 +10,7 @@ \fBgit\-repl\fR . .SH "DESCRIPTION" -Git read\-eval\-print\-loop\. Let\'s you run \fBgit\fR commands without typing \'git\'\. +Git read\-eval\-print\-loop\. Lets you run \fBgit\fR commands without typing \'git\'\. . .P Commands can be prefixed with an exclamation mark (!) to be interpreted as a regular command\. diff --git a/man/git-repl.html b/man/git-repl.html index e669a1b..f24f7ce 100644 --- a/man/git-repl.html +++ b/man/git-repl.html @@ -80,7 +80,7 @@

DESCRIPTION

-

Git read-eval-print-loop. Let's you run git commands without typing 'git'.

+

Git read-eval-print-loop. Lets you run git commands without typing 'git'.

Commands can be prefixed with an exclamation mark (!) to be interpreted as a regular command.

diff --git a/man/git-repl.md b/man/git-repl.md index cfbfeb0..132533d 100644 --- a/man/git-repl.md +++ b/man/git-repl.md @@ -7,7 +7,7 @@ git-repl(1) -- git read-eval-print-loop ## DESCRIPTION - Git read-eval-print-loop. Let's you run `git` commands without typing 'git'. + Git read-eval-print-loop. Lets you run `git` commands without typing 'git'. Commands can be prefixed with an exclamation mark (!) to be interpreted as a regular command. From da03b7b133f44a8f1bcf68c54dd6487a0c3957ae Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Wed, 18 Sep 2024 20:17:00 -0700 Subject: [PATCH 04/62] Update git-bulk.md: use correct stylization in synopsis (#1163) * Update git-bulk.md: use correct stylization in synopsis * update derived documents for git-bulk --- man/git-bulk.1 | 67 +++++++++---------------------------------- man/git-bulk.html | 72 +++++++++++++++++++++++------------------------ man/git-bulk.md | 12 ++++---- 3 files changed, 56 insertions(+), 95 deletions(-) diff --git a/man/git-bulk.1 b/man/git-bulk.1 index b1dc523..1266608 100644 --- a/man/git-bulk.1 +++ b/man/git-bulk.1 @@ -1,104 +1,69 @@ -.\" generated with Ronn/v0.7.3 -.\" http://github.com/rtomayko/ronn/tree/0.7.3 -. -.TH "GIT\-BULK" "1" "August 2020" "" "Git Extras" -. +.\" generated with Ronn-NG/v0.9.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.9.1 +.TH "GIT\-BULK" "1" "September 2024" "" "Git Extras" .SH "NAME" \fBgit\-bulk\fR \- Run git commands on multiple repositories -. .SH "SYNOPSIS" -git bulk [\-g] ([\-a]|[\-w ]) -. +\fBgit\-bulk\fR [\-g] ([\-a]|[\-w .br -git bulk \-\-addworkspace (\-\-from ) -. +\fBgit\-bulk\fR \-\-addworkspace .br -git bulk \-\-removeworkspace -. +\fBgit\-bulk\fR \-\-removeworkspace .br -git bulk \-\-addcurrent -. +\fBgit\-bulk\fR \-\-addcurrent .br -git bulk \-\-purge -. +\fBgit\-bulk\fR \-\-purge .br -git bulk \-\-listall -. +\fBgit\-bulk\fR \-\-listall .SH "DESCRIPTION" git bulk adds convenient support for operations that you want to execute on multiple git repositories\. -. -.IP "\(bu" 4 +.IP "\[ci]" 4 simply register workspaces that contain multiple git repos in their directory structure -. -.IP "\(bu" 4 +.IP "\[ci]" 4 run any git command on the repositories of the registered workspaces in one command to \fBgit bulk\fR -. -.IP "\(bu" 4 +.IP "\[ci]" 4 use the "guarded mode" to check on each execution -. .IP "" 0 -. .SH "OPTIONS" \-a -. .P Run a git command on all workspaces and their repositories\. -. .P \-g -. .P Ask the user for confirmation on every execution\. -. .P \-w -. .P Run the git command on the specified workspace\. The workspace must be registered\. -. .P -. .P Any git Command you wish to execute on the repositories\. -. .P -\-\-addworkspace (\-\-from get registered under this workspace with the name \. must be absolute path\. -. .P -With option \'\-\-from\' the URL to a single repository or a file containing multiple URLs can be added and they will be cloned directly into the workspace\. Suitable for the initial setup of a multi\-repo project\. -. +With option '\-\-from' the URL to a single repository or a file containing multiple URLs can be added and they will be cloned directly into the workspace\. Suitable for the initial setup of a multi\-repo project\. .P \-\-removeworkspace -. .P Remove the workspace with the logical name \. -. .P \-\-addcurrent -. .P Adds the current directory as workspace to git bulk operations\. The workspace is referenced with its logical name \. -. .P git bulk \-\-purge -. .P Removes all defined repository locations\. -. .P git bulk \-\-listall -. .P List all registered repositories\. -. .SH "EXAMPLES" -. .nf - Register a workspace so that git bulk knows about it: $ git bulk \-\-addworkspace personal ~/workspaces/personal @@ -142,14 +107,10 @@ $ git bulk \-\-removeworkspace personal Remove all registered workspaces: $ git bulk \-\-purge -. .fi -. .SH "AUTHOR" Written by Niklas Schlimm <\fIns103@hotmail\.de\fR> -. .SH "REPORTING BUGS" -. .SH "SEE ALSO" <\fIhttps://github\.com/tj/git\-extras\fR> diff --git a/man/git-bulk.html b/man/git-bulk.html index c805fff..de121b1 100644 --- a/man/git-bulk.html +++ b/man/git-bulk.html @@ -1,8 +1,8 @@ - - + + git-bulk(1) - Run git commands on multiple repositories + + + +
+ + + +
    +
  1. git-continue(1)
  2. +
  3. Git Extras
  4. +
  5. git-continue(1)
  6. +
+ + + +

NAME

+

+ git-continue - Continue current git operation +

+

SYNOPSIS

+ +

git-continue

+ +

DESCRIPTION

+ +

Continue current git revert, rebase, merge or cherry-pick process.

+ +

OPTIONS

+ +

There are no options, it just continues current operation.

+ +

EXAMPLES

+ +

git-continue

+ +

AUTHOR

+ +

Written by oikarinen

+ +

REPORTING BUGS

+ +

<https://github.com/tj/git-extras/issues>

+ +

SEE ALSO

+ +

<https://github.com/tj/git-extras>

+ +
    +
  1. +
  2. November 2024
  3. +
  4. git-continue(1)
  5. +
+ +
+ + diff --git a/man/git-continue.md b/man/git-continue.md new file mode 100644 index 0000000..439a95c --- /dev/null +++ b/man/git-continue.md @@ -0,0 +1,30 @@ +git-continue(1) -- Continue current git operation +================================ + +## SYNOPSIS + +`git-continue` + +## DESCRIPTION + + Continue current git revert, rebase, merge or cherry-pick process. + +## OPTIONS + + There are no options, it just continues current operation. + +## EXAMPLES + + `git-continue` + +## AUTHOR + +Written by oikarinen + +## REPORTING BUGS + +<> + +## SEE ALSO + +<> diff --git a/man/git-extras.1 b/man/git-extras.1 index c7d4af0..8f09cbf 100644 --- a/man/git-extras.1 +++ b/man/git-extras.1 @@ -49,6 +49,8 @@ Change the default branch to \fB$BRANCH\fR\. If \fBgit\-extras\.default\-branch\ .IP "\[ci]" 4 \fBgit\-commits\-since(1)\fR Show commit logs since some date .IP "\[ci]" 4 +\fBgit\-continue(1)\fR Continue current git operation +.IP "\[ci]" 4 \fBgit\-contrib(1)\fR Show user's contributions .IP "\[ci]" 4 \fBgit\-count(1)\fR Show commit count diff --git a/man/git-extras.html b/man/git-extras.html index 00ed5d0..36448ef 100644 --- a/man/git-extras.html +++ b/man/git-extras.html @@ -69,7 +69,7 @@
  • git-extras(1)
  • - +

    NAME

    @@ -131,6 +131,8 @@

  • git-commits-since(1) Show commit logs since some date
  • +git-continue(1) Continue current git operation
  • +
  • git-contrib(1) Show user's contributions
  • git-count(1) Show commit count
  • diff --git a/man/git-extras.md b/man/git-extras.md index 8ea9d88..c53d708 100644 --- a/man/git-extras.md +++ b/man/git-extras.md @@ -40,6 +40,7 @@ git-extras(1) -- Awesome GIT utilities - **git-clear(1)** Rigorously clean up a repository - **git-coauthor(1)** Add a co-author to the last commit - **git-commits-since(1)** Show commit logs since some date + - **git-continue(1)** Continue current git operation - **git-contrib(1)** Show user's contributions - **git-count(1)** Show commit count - **git-cp(1)** Copy a file keeping its history diff --git a/man/index.txt b/man/index.txt index 21c6503..c4c7168 100644 --- a/man/index.txt +++ b/man/index.txt @@ -13,6 +13,7 @@ git-clear(1) git-clear git-coauthor(1) git-coauthor git-commits-since(1) git-commits-since git-contrib(1) git-contrib +git-continue(1) git-continue git-count(1) git-count git-cp(1) git-cp git-create-branch(1) git-create-branch diff --git a/tests/README.md b/tests/README.md index d97c077..7c60a07 100644 --- a/tests/README.md +++ b/tests/README.md @@ -22,7 +22,8 @@ It is done or go without `poetry`, 1. Install python >= 3.11 2. Install pytest >= 8.1.2 3. Install gitpython >= 3.1.43 -4. Run `pytest` +4. Install testpath >= 0.6.0 +5. Run `pytest` The second way maybe blocked the some missing dependencies at someday, so the first one is recommended. diff --git a/tests/conftest.py b/tests/conftest.py index 4f9e0db..3b31908 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,8 +7,8 @@ from helper import TempRepository def create_repo(dirname=None): repo = TempRepository(dirname) - repo.create_tmp_file() - repo.create_tmp_file() + repo.create_tmp_file() # tmp_file_a + repo.create_tmp_file() # tmp_file_b repo.switch_cwd_under_repo() return repo @@ -35,3 +35,11 @@ def named_temp_repo(request): init_repo_git_status(repo) yield repo repo.teardown() + + +@pytest.fixture(scope="function") +def temp_repo_clean(): + """Create a temporary repository that is reset for each function call.""" + repo = create_repo() + init_repo_git_status(repo) + return repo diff --git a/tests/test_git_continue.py b/tests/test_git_continue.py new file mode 100644 index 0000000..8fd7d5e --- /dev/null +++ b/tests/test_git_continue.py @@ -0,0 +1,84 @@ +from git import GitCommandError + + +class TestGitContinue: + + @classmethod + def _init_repo(cls, repo): + git = repo.get_repo_git() + tmp_file = repo.get_file(0) + git.branch("A") + git.branch("B") + git.branch("C") + git.checkout("A") + repo.writefile(tmp_file, "a") + git.add(".") + git.commit("-m", "A") + git.checkout("B") + repo.writefile(tmp_file, "b") + git.add(".") + git.commit("-m", "B") + + def test_init(self, temp_repo_clean): + TestGitContinue._init_repo(temp_repo_clean) + git = temp_repo_clean.get_repo_git() + git.status() + + def test_cherry_pick(self, temp_repo_clean): + TestGitContinue._init_repo(temp_repo_clean) + git = temp_repo_clean.get_repo_git() + try: + git.cherry_pick("A") + except GitCommandError as err: + print(err) + result = git.status() + assert "Unmerged path" in result + git.add(".") + temp_repo_clean.invoke_extras_command("continue") + result = git.status() + assert "nothing to commit, working tree clean" in result + + def test_merge(self, temp_repo_clean): + TestGitContinue._init_repo(temp_repo_clean) + git = temp_repo_clean.get_repo_git() + try: + git.merge("A") + except GitCommandError as err: + print(err) + result = git.status() + assert "Unmerged path" in result + git.add(".") + git.commit("-m", "resolve conflict") + temp_repo_clean.invoke_extras_command("continue") + result = git.status() + assert "nothing to commit, working tree clean" in result + + def test_rebase(self, temp_repo_clean): + TestGitContinue._init_repo(temp_repo_clean) + git = temp_repo_clean.get_repo_git() + try: + git.rebase("A") + except GitCommandError as err: + print(err) + result = git.status() + assert "Unmerged path" in result + git.add(".") + git.commit("-m", "resolve conflict") + temp_repo_clean.invoke_extras_command("continue") + result = git.status() + assert "nothing to commit, working tree clean" in result + + def test_revert(self, temp_repo_clean): + TestGitContinue._init_repo(temp_repo_clean) + git = temp_repo_clean.get_repo_git() + try: + git.revert("A") + except GitCommandError as err: + print(err) + result = git.status() + assert "Unmerged path" in result + git.add(".") + git.commit("-m", "resolve conflict") + temp_repo_clean.invoke_extras_command("continue") + result = git.status() + assert "nothing to commit, working tree clean" in result From 1345c5977d5cb78d7a6d609222edd489e8f049ff Mon Sep 17 00:00:00 2001 From: Edwin Kofler Date: Thu, 28 Nov 2024 18:39:09 -0800 Subject: [PATCH 21/62] Delete etc/test.fish (#1185) --- etc/test.fish | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 etc/test.fish diff --git a/etc/test.fish b/etc/test.fish deleted file mode 100644 index 159e97d..0000000 --- a/etc/test.fish +++ /dev/null @@ -1,20 +0,0 @@ -complete -e blah - -function __fish_git_arg_number -a number - set -l cmd (commandline -opc) - test (count $cmd) -eq $number -end - -function __fish_git_extra_coauthor_name - printf '%s\n' 'a' 'apple' 'ann' -end - -function __fish_git_extra_coauthor_email - set -l cmd (commandline -opc) - - set -l value $cmd[3] - printf '%s\n' 'n' 'n1' 'n2' "$value" -end - -complete -c blah -f -n '__fish_git_using_command coauthor; and __fish_git_arg_number 2' -a '(__fish_git_extra_coauthor_name)' -complete -c blah -f -n '__fish_git_using_command coauthor; and __fish_git_arg_number 3' -a '(__fish_git_extra_coauthor_email)' From 3db275cbe2426624b8a7c339dfe6b91ef36ab347 Mon Sep 17 00:00:00 2001 From: oikarinen <7252104+oikarinen@users.noreply.github.com> Date: Fri, 29 Nov 2024 04:39:42 +0200 Subject: [PATCH 22/62] fix(ci): use poetry (#1183) For managing the python dependencies, running the unit tests and spellchecker. This makes it easier for everyone to have same environment as CI. Also includes few cosmetic edits for style for the ci.yml --- .github/workflows/ci.yml | 42 ++++++++++++++++++++++++++++------------ tests/poetry.lock | 29 ++++++++++++++++++++------- tests/pyproject.toml | 7 ++++++- 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4a0393..ba5557a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: branches: [main] jobs: lint: - runs-on: 'ubuntu-latest' + runs-on: ubuntu-latest steps: - name: Check out code. uses: actions/checkout@v4 @@ -44,26 +44,46 @@ jobs: steps: - name: Check out code. uses: actions/checkout@v4 + - name: Install poetry + run: pip install poetry + - name: Set up Python + uses: actions/setup-python@v5 + with: + cache: 'poetry' + cache-dependency-path: "tests/pyproject.toml" + python-version-file: "tests/pyproject.toml" + - name: Install dependencies + run: | + cd tests || exit + poetry install --only dev - name: spell check run: | - pip install codespell==2.2 - git grep --cached -l '' | grep -v -e 'History\.md' -e 'AUTHORS' -e 'man/.*\.1' -e 'man/.*\.html' | xargs codespell --ignore-words=.github/.ignore_words + cd tests + git grep --cached -l '' .. | \ + grep -v -e 'History\.md' -e 'AUTHORS' -e 'man/.*\.1' -e 'man/.*\.html' | \ + xargs poetry run codespell --ignore-words=../.github/.ignore_words test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Python 3.12 + - name: Install poetry + run: pip install poetry + - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + cache: 'poetry' + cache-dependency-path: "tests/pyproject.toml" + python-version-file: "tests/pyproject.toml" - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install pytest==8.1.2 GitPython==3.1.43 testpath==0.6.0 + cd tests || exit + poetry install --only test - name: Unit test - run: make test + run: | + cd tests + poetry run pytest build: strategy: @@ -78,11 +98,9 @@ jobs: uses: actions/checkout@v4 - name: Linux Install if: matrix.platform == 'ubuntu-latest' - run: | - sudo apt-get install -y bsdmainutils + run: sudo apt-get install -y bsdmainutils - name: Script - run: | - ./check_integrity.sh + run: ./check_integrity.sh - name: Brew release if: matrix.platform == 'macos-latest' run: | diff --git a/tests/poetry.lock b/tests/poetry.lock index 5fa5e29..bb52a52 100644 --- a/tests/poetry.lock +++ b/tests/poetry.lock @@ -1,4 +1,19 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. + +[[package]] +name = "codespell" +version = "2.2.0" +description = "Codespell" +optional = false +python-versions = ">=3.6" +files = [ + {file = "codespell-2.2.0-py3-none-any.whl", hash = "sha256:3cc3fcb484a8302683add19e7d11504c79c79b10d4ea0675409417a044b27374"}, + {file = "codespell-2.2.0.tar.gz", hash = "sha256:3dce0cd1348d277f8d934d1d4dcbbf510f9ddfd1b9005e9b25fb983189962561"}, +] + +[package.extras] +dev = ["check-manifest", "flake8", "pytest", "pytest-cov", "pytest-dependency"] +hard-encoding-detection = ["chardet"] [[package]] name = "colorama" @@ -56,13 +71,13 @@ files = [ [[package]] name = "packaging" -version = "24.0" +version = "24.2" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] @@ -127,5 +142,5 @@ test = ["pytest"] [metadata] lock-version = "2.0" -python-versions = "^3.11" -content-hash = "e73f9840c5f034acbd9b1ebc082e7ce4600ac3235991e57cd825c261a5f2e14e" +python-versions = "^3.12" +content-hash = "e730a1e6e7fd2f51858e8c8cfa8b56d606eadd7a0a629ca8af078904721a5159" diff --git a/tests/pyproject.toml b/tests/pyproject.toml index f12d487..f04e61d 100644 --- a/tests/pyproject.toml +++ b/tests/pyproject.toml @@ -8,11 +8,16 @@ license = "MIT" readme = "README.md" [tool.poetry.dependencies] -python = "^3.11" +python = "^3.12" + +[tool.poetry.group.test.dependencies] pytest = "8.1.2" gitpython = "3.1.43" testpath = "0.6.0" +[tool.poetry.group.dev.dependencies] +codespell = "2.2" + [tool.pytest.ini_options] minversion = "7.4" addopts = "-ra -q" From fdaca2cd267f01d27d8db30441532233c4ca92f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 10:06:50 +0800 Subject: [PATCH 23/62] chore(deps): bump astral-sh/ruff-action from 1 to 2 (#1188) Bumps [astral-sh/ruff-action](https://github.com/astral-sh/ruff-action) from 1 to 2. - [Release notes](https://github.com/astral-sh/ruff-action/releases) - [Commits](https://github.com/astral-sh/ruff-action/compare/v1...v2) --- updated-dependencies: - dependency-name: astral-sh/ruff-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba5557a..5689a17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: # NOTE: use env to pass the output in order to avoid possible injection attacks FILES: "${{ steps.files.outputs.added_modified }}" - name: Lint and format Python with Ruff - uses: astral-sh/ruff-action@v1 + uses: astral-sh/ruff-action@v2 typo: runs-on: ubuntu-latest From e691826b4b5fcb15dcee650e3948d00b9e58836c Mon Sep 17 00:00:00 2001 From: oikarinen <7252104+oikarinen@users.noreply.github.com> Date: Sat, 21 Dec 2024 09:16:39 +0200 Subject: [PATCH 24/62] Fix all ShellCheck errors and add to CI (#1179) --- .github/workflows/ci.yml | 2 ++ bin/git-create-branch | 2 +- bin/git-fork | 2 +- bin/git-guilt | 8 +++++--- bin/git-obliterate | 8 ++++---- bin/git-repl | 2 ++ bin/git-scp | 13 +++++++------ 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5689a17..8eceaa3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,8 @@ jobs: env: # NOTE: use env to pass the output in order to avoid possible injection attacks FILES: "${{ steps.files.outputs.added_modified }}" + - name: Shellcheck + run: shellcheck --severity=error bin/* ./*.sh - name: Lint and format Python with Ruff uses: astral-sh/ruff-action@v2 diff --git a/bin/git-create-branch b/bin/git-create-branch index c11c4ea..369a5f8 100755 --- a/bin/git-create-branch +++ b/bin/git-create-branch @@ -39,7 +39,7 @@ then REMOTE=origin fi -test -z $BRANCH && echo "branch argument required." 1>&2 && exit 1 +test -z "$BRANCH" && echo "branch argument required." 1>&2 && exit 1 if [[ -n $REMOTE ]] then diff --git a/bin/git-fork b/bin/git-fork index 3d3742f..2242c4a 100755 --- a/bin/git-fork +++ b/bin/git-fork @@ -61,7 +61,7 @@ else # clone forked repo into current dir git clone "${remote_prefix}${user}/${project}.git" "$project" # add reference to origin fork so can merge in upstream changes - cd "$project" + cd "$project" || exit git remote add upstream "${remote_prefix}${owner}/${project}.git" git fetch upstream fi diff --git a/bin/git-guilt b/bin/git-guilt index 3fe644a..30e2e0a 100755 --- a/bin/git-guilt +++ b/bin/git-guilt @@ -31,7 +31,7 @@ do esac done -cd "$(git-root)" # cd for git blame +cd "$(git-root)" || exit # cd for git blame MERGED_LOG=$(git_extra_mktemp) if [[ $EMAIL == '-e' ]] then @@ -44,9 +44,11 @@ for file in $(git diff --name-only "$@") do test -n "$DEBUG" && echo "git blame $file" # $1 - since $2 - until + # shellcheck disable=SC2086 git blame $NOT_WHITESPACE --line-porcelain "$1" -- "$file" 2> /dev/null | LC_ALL=C sed -n "$PATTERN" | sort | uniq -c | LC_ALL=C sed 's/^\(.\)/- \1/' >> "$MERGED_LOG" # if $2 not given, use current commit as "until" + # shellcheck disable=SC2086 git blame $NOT_WHITESPACE --line-porcelain "${2-@}" -- "$file" 2> /dev/null | LC_ALL=C sed -n "$PATTERN" | sort | uniq -c | LC_ALL=C sed 's/^\(.\)/+ \1/' >> "$MERGED_LOG" done @@ -71,7 +73,7 @@ END { printf("%d %s\n", contributors[people], people) } } -}' $MERGED_LOG | sort -nr | # only gawk supports built-in sort function +}' "$MERGED_LOG" | sort -nr | # only gawk supports built-in sort function while read -r line do people=${line#* } @@ -103,7 +105,7 @@ do do printf "-" done - printf "(%s)" $num + printf "(%s)" "$num" else for (( i = 0; i > num; i-- )) do diff --git a/bin/git-obliterate b/bin/git-obliterate index 992e0eb..83e4ca1 100755 --- a/bin/git-obliterate +++ b/bin/git-obliterate @@ -9,15 +9,15 @@ do file="$file"' '"$i" shift done -test -n "$*" && range="$*" +test -n "$*" && range=("$@") test -z "$file" && echo "file required." 1>&2 && exit 1 -if [ -z "$range" ] +if [ -z "${range[*]}" ] then git filter-branch -f --index-filter "git rm -r --cached ""$file"" --ignore-unmatch" \ --prune-empty --tag-name-filter cat -- --all else - # don't quote $range so that we can forward multiple rev-list arguments + # $range is an array so that we can forward multiple rev-list arguments git filter-branch -f --index-filter "git rm -r --cached ""$file"" --ignore-unmatch" \ - --prune-empty --tag-name-filter cat -- $range + --prune-empty --tag-name-filter cat -- "${range[@]}" fi diff --git a/bin/git-repl b/bin/git-repl index a136879..a706de3 100755 --- a/bin/git-repl +++ b/bin/git-repl @@ -44,8 +44,10 @@ while true; do esac if [[ $cmd == !* ]]; then + # shellcheck disable=SC2086 eval ${cmd:1} elif [[ $cmd == git* ]]; then + # shellcheck disable=SC2086 eval $cmd else eval git "$cmd" diff --git a/bin/git-scp b/bin/git-scp index 52d4d14..7a10a1f 100755 --- a/bin/git-scp +++ b/bin/git-scp @@ -59,7 +59,7 @@ function php_lint() function _dos2unix() { - command -v dos2unix > /dev/null && dos2unix $@ + command -v dos2unix > /dev/null && dos2unix "$@" return 0 } @@ -68,8 +68,8 @@ function _sanitize() git config --get-all extras.scp.sanitize | while read -r i do case $i in - php_lint) php_lint $@;; # git config --global --add extras.scp.sanitize php_lint - dos2unix) _dos2unix $@;; # git config --global --add extras.scp.sanitize dos2unix + php_lint) php_lint "$@";; # git config --global --add extras.scp.sanitize php_lint + dos2unix) _dos2unix "$@";; # git config --global --add extras.scp.sanitize dos2unix esac done return $? @@ -107,6 +107,7 @@ function scp_and_stage if [ -n "$list" ] then local _TMP=${0///} + # shellcheck disable=SC2086 echo "$list" > "$_TMP" && _sanitize $list && _info "Pushing to $remote ($(git config "remote.$remote.url"))" && @@ -131,7 +132,7 @@ function reverse_scp() shift local _TMP=${0///} - echo $@ > "$_TMP" && + echo "$@" > "$_TMP" && rsync -rlDv --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./ && rm "$_TMP" } @@ -173,8 +174,8 @@ case $(basename "$0") in git-scp) case $1 in ''|-h|'?'|help|--help) shift; _test_git_scp; _usage "$@";; - *) scp_and_stage $@;; + *) scp_and_stage "$@";; esac ;; - git-rscp) reverse_scp $@;; + git-rscp) reverse_scp "$@";; esac From df53711fc75d4969a67fe4a024f462717d6a18d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 Dec 2024 18:13:50 -0800 Subject: [PATCH 25/62] chore(deps): bump astral-sh/ruff-action from 2 to 3 (#1189) Bumps [astral-sh/ruff-action](https://github.com/astral-sh/ruff-action) from 2 to 3. - [Release notes](https://github.com/astral-sh/ruff-action/releases) - [Commits](https://github.com/astral-sh/ruff-action/compare/v2...v3) --- updated-dependencies: - dependency-name: astral-sh/ruff-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8eceaa3..7e963ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: - name: Shellcheck run: shellcheck --severity=error bin/* ./*.sh - name: Lint and format Python with Ruff - uses: astral-sh/ruff-action@v2 + uses: astral-sh/ruff-action@v3 typo: runs-on: ubuntu-latest From 82cc37d0f5fcfc5aae8ba631a4054a4701e4a557 Mon Sep 17 00:00:00 2001 From: Pierre Ayoub Date: Thu, 20 Feb 2025 08:41:42 +0100 Subject: [PATCH 26/62] fix(git-bulk): fix a bad integer expression (#1198) Fix a logic error inside the `allowedargcount()` function. This function may be called with one or two arguments. However, no default values are assigned to `$1` and `$2` that are used inside a numerical comparison. Therefore, when using a bad number of arguments for the following lines: ``` listall|purge) allowedargcount 1;; addcurrent|removeworkspace) allowedargcount 2;; ``` Then, we would get the error `[: : integer expression expected`. To fix this, we assign the 0 default value to `$1` and `$2`, such that we trigger the error message destined to the user without any integer error when there is a bad number of argument and that the function is called with only 1 argument instead of 2. --- bin/git-bulk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/git-bulk b/bin/git-bulk index e3bdd66..48c1954 100755 --- a/bin/git-bulk +++ b/bin/git-bulk @@ -129,7 +129,7 @@ function wsnameToCurrent () { # helper to check number of arguments. function allowedargcount () { - if [ "$paramcount" -ne "$1" ] && [ "$paramcount" -ne "$2" ]; then + if [ "$paramcount" -ne "${1:-0}" ] && [ "$paramcount" -ne "${2:-0}" ]; then echo 1>&2 "error: wrong number of arguments" && usage; exit 1; fi From 1a9b0c2ab46b6868f2e408c1b48f02c9fd499a30 Mon Sep 17 00:00:00 2001 From: Pierre Ayoub Date: Thu, 20 Feb 2025 08:47:27 +0100 Subject: [PATCH 27/62] fix(git-bulk): quiet find errors by default (#1196) --- bin/git-bulk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/git-bulk b/bin/git-bulk index 48c1954..1b54e20 100755 --- a/bin/git-bulk +++ b/bin/git-bulk @@ -146,7 +146,7 @@ function executBulkOp () { local actual=$PWD [ "${quiet?}" != "true" ] && echo 1>&2 "Executing bulk operation in workspace ${inverse}$actual${reset}" - allGitFolders=( $(eval find -L . -name ".git") ) + allGitFolders=( $(eval find -L . -name ".git" 2>/dev/null) ) for line in "${allGitFolders[@]}"; do local gitrepodir=${line::${#line}-5} # cut the .git part of find results to have the root git directory of that repository From 8ce63003c03e423956a5d9be42f5c503d908b88d Mon Sep 17 00:00:00 2001 From: Pierre Ayoub Date: Wed, 26 Feb 2025 05:33:16 +0100 Subject: [PATCH 28/62] fix(git-bulk): fix workspace selection when cd fails (#1197) * fix(git-bulk): fix workspace selection when cd fails `cd` may fails for multiple reasons: - mistake when editing `.gitconfig` manually - previously existing workspace that have been removed - ... Currently, if `cd` fails, the `BulkOp` continue its execution ... in the workspace defined in a higher directory that where the user, despite the user specified a specific workspace (`-w`). The user should be noticed of a failed `cd` (this is really not expected for a valid configuration) and the operations should stop to not execute something unexpected. * fix(git-bulk): replace weak eval for better variable substitution Get rid of poor `eval` syntax because they are vulnerable to command injection, which may have unexpected side effects. However, they enabled a useful feature: using environment variable (*e.g.*, defined in a `.bashrc`) inside the `.gitconfig` to use dynamic paths as `bulk` workspaces. As such, I keep this feature possible by using the Bash's ${!VAR} syntax, which allows to get the value of one variable using the name of a another variable. However, arbitrary command injection is not possible anymore. * fix(git-bulk): missing check about empty environnement variable * style(git-bulk): typo * docs(Commands.md): git-bulk env var feature * docs(man/git-bulk): git-bulk env var feature * docs(man/git-bulk): mention .gitconfig for config storage * style(man/git-bulk): typo * docs(man/git-bulk): run make/ronn for .1 and .html --- Commands.md | 11 ++++++++++- bin/git-bulk | 16 +++++++++++++--- man/git-bulk.1 | 12 ++++++++++-- man/git-bulk.html | 16 ++++++++++++++-- man/git-bulk.md | 10 +++++++++- man/index.txt | 2 +- 6 files changed, 57 insertions(+), 10 deletions(-) diff --git a/Commands.md b/Commands.md index de98cef..1085ba7 100644 --- a/Commands.md +++ b/Commands.md @@ -280,11 +280,20 @@ usage: git bulk [-g] ([-a]|[-w ]) git bulk --listall ``` - Register a workspace so that `git bulk` knows about it (notice that must be absolute path): + Register a workspace so that `git bulk` knows about it (it will be registered in your `.gitconfig`): ```bash $ git bulk --addworkspace personal ~/workspaces/personal ``` + + Notice that `` must be an absolute path (or an environment variable pointing to an absolute path). + In the case of a **single quoted environment variable**, it will be dereferenced at `git-bulk` runtime, suitable for dynamic workspaces (*e.g.*, defined in your `.bashrc`). + As an illustration: + +```bash +$ git bulk --addworkspace personal '$PERSONAL_WORKSPACE' +``` + With option `--from` the URL to a single repository or a file containing multiple URLs can be added and they will be cloned directly into the workspace. Suitable for the initial setup of a multi-repo project. ```bash diff --git a/bin/git-bulk b/bin/git-bulk index 1b54e20..ab5898e 100755 --- a/bin/git-bulk +++ b/bin/git-bulk @@ -111,7 +111,17 @@ function checkWSName () { # parse out wsname from workspacespec function parseWsName () { local wsspec="$1" + # Get the workspace value from its specification in the `.gitconfig`. + # May be an absolute path or a variable name of the form: `$VARNAME` rwsdir=${wsspec#* } + if [[ ${rwsdir:0:1} == '$' ]]; then + # Dereference the `rwsdir` value which is a variable name. + rwsdir_varname=${rwsdir:1} + rwsdir=${!rwsdir_varname} + if [[ -z "${rwsdir}" ]]; then + echo 1>&2 "error: bad environment variable: $rwsdir_varname" && exit 1 + fi + fi rwsname=${wsspec#*.} && rwsname=${rwsname%% *} } @@ -142,7 +152,7 @@ function executBulkOp () { listall | while read -r workspacespec; do parseWsName "$workspacespec" if [[ -n $wsname ]] && [[ $rwsname != "$wsname" ]]; then continue; fi - eval cd "\"$rwsdir\"" + cd "$rwsdir" || exit 1 local actual=$PWD [ "${quiet?}" != "true" ] && echo 1>&2 "Executing bulk operation in workspace ${inverse}$actual${reset}" @@ -150,11 +160,11 @@ function executBulkOp () { for line in "${allGitFolders[@]}"; do local gitrepodir=${line::${#line}-5} # cut the .git part of find results to have the root git directory of that repository - eval cd "\"$gitrepodir\"" # into git repo location + cd "$gitrepodir" || exit 1 # into git repo location local curdir=$PWD local leadingpath=${curdir#"${actual}"} guardedExecution "$@" - eval cd "\"$rwsdir\"" # back to origin location of last find command + cd "$rwsdir" || exit 1 # back to origin location of last find command done done } diff --git a/man/git-bulk.1 b/man/git-bulk.1 index 1266608..69a6a38 100644 --- a/man/git-bulk.1 +++ b/man/git-bulk.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "GIT\-BULK" "1" "September 2024" "" "Git Extras" +.TH "GIT\-BULK" "1" "February 2025" "" "Git Extras" .SH "NAME" \fBgit\-bulk\fR \- Run git commands on multiple repositories .SH "SYNOPSIS" @@ -64,10 +64,14 @@ git bulk \-\-listall List all registered repositories\. .SH "EXAMPLES" .nf -Register a workspace so that git bulk knows about it: +Register a workspace so that git bulk knows about it using an absolute path: $ git bulk \-\-addworkspace personal ~/workspaces/personal +Or register a workspace using an environment variable pointing to an absolute path: + +$ git bulk \-\-addworkspace personal '$PERSONAL_WORKSPACE' + Use option \-\-from in order to directly clone a repository or multiple repositories $ git bulk \-\-addworkspace personal ~/workspaces/personal \-\-from https://github\.com/tj/git\-extras\.git @@ -108,6 +112,10 @@ Remove all registered workspaces: $ git bulk \-\-purge .fi +.SH "FILES" +.IP "\[ci]" 4 +\fB\.gitconfig\fR: Store the \fBgit\-bulk\fR registered workspaces under the \fBbulkworkspaces\fR key\. +.IP "" 0 .SH "AUTHOR" Written by Niklas Schlimm <\fIns103@hotmail\.de\fR> .SH "REPORTING BUGS" diff --git a/man/git-bulk.html b/man/git-bulk.html index de121b1..7fe7689 100644 --- a/man/git-bulk.html +++ b/man/git-bulk.html @@ -58,6 +58,7 @@ DESCRIPTION OPTIONS EXAMPLES + FILES AUTHOR REPORTING BUGS SEE ALSO @@ -137,10 +138,14 @@

    EXAMPLES

    -
    Register a workspace so that git bulk knows about it:
    +
    Register a workspace so that git bulk knows about it using an absolute path:
     
     $ git bulk --addworkspace personal ~/workspaces/personal
     
    +Or register a workspace using an environment variable pointing to an absolute path:
    +
    +$ git bulk --addworkspace personal '$PERSONAL_WORKSPACE'
    +
     Use option --from in order to directly clone a repository or multiple repositories 
     
     $ git bulk --addworkspace personal ~/workspaces/personal --from https://github.com/tj/git-extras.git
    @@ -182,6 +187,13 @@ Remove all registered workspaces:
     $ git bulk --purge
     
    +

    FILES

    + +
      +
    • +.gitconfig: Store the git-bulk registered workspaces under the bulkworkspaces key.
    • +
    +

    AUTHOR

    Written by Niklas Schlimm <ns103@hotmail.de>

    @@ -196,7 +208,7 @@ $ git bulk --purge
    1. -
    2. September 2024
    3. +
    4. February 2025
    5. git-bulk(1)
    diff --git a/man/git-bulk.md b/man/git-bulk.md index 80edda4..4dca4d3 100644 --- a/man/git-bulk.md +++ b/man/git-bulk.md @@ -60,10 +60,14 @@ git bulk adds convenient support for operations that you want to execute on mult ## EXAMPLES - Register a workspace so that git bulk knows about it: + Register a workspace so that git bulk knows about it using an absolute path: $ git bulk --addworkspace personal ~/workspaces/personal + Or register a workspace using an environment variable pointing to an absolute path: + + $ git bulk --addworkspace personal '$PERSONAL_WORKSPACE' + Use option --from in order to directly clone a repository or multiple repositories $ git bulk --addworkspace personal ~/workspaces/personal --from https://github.com/tj/git-extras.git @@ -104,6 +108,10 @@ git bulk adds convenient support for operations that you want to execute on mult $ git bulk --purge +## FILES + +- `.gitconfig`: Store the `git-bulk` registered workspaces under the `bulkworkspaces` key. + ## AUTHOR Written by Niklas Schlimm <> diff --git a/man/index.txt b/man/index.txt index c4c7168..5e98b17 100644 --- a/man/index.txt +++ b/man/index.txt @@ -12,8 +12,8 @@ git-clear-soft(1) git-clear-soft git-clear(1) git-clear git-coauthor(1) git-coauthor git-commits-since(1) git-commits-since -git-contrib(1) git-contrib git-continue(1) git-continue +git-contrib(1) git-contrib git-count(1) git-count git-cp(1) git-cp git-create-branch(1) git-create-branch From 64687599ae2de65ab937f1b80571c12e4f74015f Mon Sep 17 00:00:00 2001 From: Pierre Ayoub Date: Wed, 26 Feb 2025 05:34:04 +0100 Subject: [PATCH 29/62] docs(git-bulk): Add zsh completion (#1190) * docs(git-bulk): Add zsh completion * docs(git-bulk): Harmonize help strings * docs(git-bulk): simplify previous bad logic The previous logic was not allowing to use multiple options (which is often the case). This was bad because this is my first time with zsh automcompletion. Without it, the user can use any flag combination -- but at least, he has the freedom. --- etc/git-extras-completion.zsh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/etc/git-extras-completion.zsh b/etc/git-extras-completion.zsh index 05f271b..9e1c18d 100644 --- a/etc/git-extras-completion.zsh +++ b/etc/git-extras-completion.zsh @@ -92,6 +92,13 @@ __gitex_submodule_names() { _wanted submodule-names expl submodule-name compadd $* - $submodule_names } +__gitex_workspace_names() { + local expl + declare -a workspace_names + workspace_names=($(git bulk --listall | awk '{print $1}' | cut -d "." -f 2)) + __gitex_command_successful || return + _wanted workspace-names expl workspace-names compadd $* - $workspace_names +} __gitex_author_names() { local expl @@ -122,6 +129,20 @@ _git-brv() { '(-r --reverse)'{-r,--reverse}'[reverse order]' } +_git-bulk() { + _arguments \ + '-a[Run a git command on all workspaces and their repositories.]' \ + '-g[Ask the user for confirmation on every execution (guarded mode).]' \ + '-w[Run the git command on the specified workspace.]:workspace-name:__gitex_workspace_names' \ + '-q[Suppress bulk output about current execution (quiet mode).]' \ + '--addworkspace[Register a workspace for bulk operations.]' \ + '--removeworkspace[Remove the specified workspace.]:workspace-name:__gitex_workspace_names' \ + '--addcurrent[Adds the current directory as workspace to git bulk operations]' \ + '--purge[Removes all defined repository locations.]' \ + '--listall[List all registered repositories.]' \ + '--help[Show the help.]' +} + _git-changelog() { _arguments \ '(-l --list)'{-l,--list}'[list commits]' \ From 8eed5f49ad8e7fbed7c72b11cc27afadd6fee91d Mon Sep 17 00:00:00 2001 From: Pierre Ayoub Date: Thu, 27 Feb 2025 07:18:16 +0100 Subject: [PATCH 30/62] feat(git-bulk): add new option to no follow symlinks (#1194) * feat(git-bulk): add --no-follow-symlinks flag * docs(git-bulk): add --no-follow-symlinks description * docs(man): make ronn * docs(git-bulk): put --no-follow-symlinks at right place in usage() * fix(git-bulk): use readarray for find command 1. Use `readarray` such that we can handle paths with spaces 2. Remove the unnecessary `eval` --- bin/git-bulk | 15 +++++++++++++-- man/git-bulk.1 | 6 +++++- man/git-bulk.html | 6 +++++- man/git-bulk.md | 6 +++++- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/bin/git-bulk b/bin/git-bulk index ab5898e..fe58e48 100755 --- a/bin/git-bulk +++ b/bin/git-bulk @@ -9,12 +9,13 @@ guardedmode=false singlemode=false allwsmode=false quiet=false +no_follow_symlinks=false # # print usage message # usage() { - echo 1>&2 "usage: git bulk [-q|--quiet] [-g] ([-a]|[-w ]) " + echo 1>&2 "usage: git bulk [--no-follow-symlinks] [-q|--quiet] [-g] ([-a]|[-w ]) " echo 1>&2 " git bulk --addworkspace (--from )" echo 1>&2 " git bulk --removeworkspace " echo 1>&2 " git bulk --addcurrent " @@ -156,7 +157,15 @@ function executBulkOp () { local actual=$PWD [ "${quiet?}" != "true" ] && echo 1>&2 "Executing bulk operation in workspace ${inverse}$actual${reset}" - allGitFolders=( $(eval find -L . -name ".git" 2>/dev/null) ) + # build `find` flags depending on command-line options + local find_flags=() + if [[ "$no_follow_symlinks" == true ]]; then + find_flags+=(-P) + else + find_flags+=(-L) + fi + # find all git repositories under the workspace on which we want to operate + readarray allGitFolders < <(find "${find_flags[@]}" . -name ".git" 2>/dev/null) for line in "${allGitFolders[@]}"; do local gitrepodir=${line::${#line}-5} # cut the .git part of find results to have the root git directory of that repository @@ -182,6 +191,8 @@ while [ "${#}" -ge 1 ] ; do butilcommand="${1:2}" && break ;; --removeworkspace|--addcurrent|--addworkspace) butilcommand="${1:2}" && wsname="$2" && wsdir="$3" && if [ "$4" == "--from" ]; then source="$5"; fi && break ;; + --no-follow-symlinks) + no_follow_symlinks=true ;; -a) allwsmode=true ;; -g) diff --git a/man/git-bulk.1 b/man/git-bulk.1 index 69a6a38..0ed0a14 100644 --- a/man/git-bulk.1 +++ b/man/git-bulk.1 @@ -4,7 +4,7 @@ .SH "NAME" \fBgit\-bulk\fR \- Run git commands on multiple repositories .SH "SYNOPSIS" -\fBgit\-bulk\fR [\-g] ([\-a]|[\-w +\fBgit\-bulk\fR [\-g] [\-\-no\-follow\-symlinks] ([\-a]|[\-w .br \fBgit\-bulk\fR \-\-addworkspace .br @@ -33,6 +33,10 @@ Run a git command on all workspaces and their repositories\. .P Ask the user for confirmation on every execution\. .P +\-\-no\-follow\-symlinks +.P +Do not traverse symbolic links under the workspace when searching for git repositories\. +.P \-w .P Run the git command on the specified workspace\. The workspace must be registered\. diff --git a/man/git-bulk.html b/man/git-bulk.html index 7fe7689..66b0e17 100644 --- a/man/git-bulk.html +++ b/man/git-bulk.html @@ -78,7 +78,7 @@

    SYNOPSIS

    -

    git-bulk [-g] ([-a]|[-w ])
    +

    git-bulk [-g] [--no-follow-symlinks] ([-a]|[-w ])
    git-bulk --addworkspace (--from )
    git-bulk --removeworkspace <ws-name>
    git-bulk --addcurrent <ws-name>
    @@ -106,6 +106,10 @@

    Ask the user for confirmation on every execution.

    +

    --no-follow-symlinks

    + +

    Do not traverse symbolic links under the workspace when searching for git repositories.

    +

    -w <ws-name>

    Run the git command on the specified workspace. The workspace must be registered.

    diff --git a/man/git-bulk.md b/man/git-bulk.md index 4dca4d3..f5550d5 100644 --- a/man/git-bulk.md +++ b/man/git-bulk.md @@ -3,7 +3,7 @@ git-bulk(1) -- Run git commands on multiple repositories ## SYNOPSIS -`git-bulk` [-g] ([-a]|[-w <ws-name>]) <git command>
    +`git-bulk` [-g] [--no-follow-symlinks] ([-a]|[-w <ws-name>]) <git command>
    `git-bulk` --addworkspace <ws-name> <ws-root-directory> (--from <URL or file>)
    `git-bulk` --removeworkspace <ws-name>
    `git-bulk` --addcurrent <ws-name>
    @@ -28,6 +28,10 @@ git bulk adds convenient support for operations that you want to execute on mult Ask the user for confirmation on every execution. + --no-follow-symlinks + + Do not traverse symbolic links under the workspace when searching for git repositories. + -w <ws-name> Run the git command on the specified workspace. The workspace must be registered. From a841845d9a1465cc6d9b65b53baa2c71b8da1eeb Mon Sep 17 00:00:00 2001 From: Pierre Ayoub Date: Thu, 27 Feb 2025 07:25:55 +0100 Subject: [PATCH 31/62] Feat: allow git-summary showing full path of repository (#1193) * feat(git-summary): add --full-path option * docs(git-summary): add doc for --full-path option * docs(man): make using ronn * docs(git-extras-completion.zsh): add --full-path option of git-summary --- bin/git-summary | 11 ++++++++++- etc/git-extras-completion.zsh | 1 + man/git-summary.1 | 6 +++++- man/git-summary.html | 6 +++++- man/git-summary.md | 4 ++++ 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/bin/git-summary b/bin/git-summary index f90adf1..b69a9ae 100755 --- a/bin/git-summary +++ b/bin/git-summary @@ -3,12 +3,16 @@ cd "$(git root)" || { echo "Can't cd to top level directory";exit 1; } +PROJECT_FULL_PATH= SUMMARY_BY_LINE= DEDUP_BY_EMAIL= MERGES_ARG= OUTPUT_STYLE= for arg in "$@"; do case "$arg" in + --full-path) + PROJECT_FULL_PATH=1 + ;; --line) SUMMARY_BY_LINE=1 ;; @@ -51,7 +55,12 @@ if [ -n "$SUMMARY_BY_LINE" ]; then else [ $# -ne 0 ] && commit=$* fi -project=${PWD##*/} + +if [[ -n "$PROJECT_FULL_PATH" ]]; then + project=${PWD/${HOME}/\~} +else + project=${PWD##*/} +fi # # get date for the given diff --git a/etc/git-extras-completion.zsh b/etc/git-extras-completion.zsh index 9e1c18d..63e77f5 100644 --- a/etc/git-extras-completion.zsh +++ b/etc/git-extras-completion.zsh @@ -370,6 +370,7 @@ _git-standup() { } _git-summary() { + _arguments '--full-path[show repository full path]' _arguments '--line[summarize with lines rather than commits]' _arguments '--dedup-by-email[remove duplicate users by the email address]' _arguments '--no-merges[exclude merge commits]' diff --git a/man/git-summary.1 b/man/git-summary.1 index 87a3085..a5594be 100644 --- a/man/git-summary.1 +++ b/man/git-summary.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "GIT\-SUMMARY" "1" "June 2023" "" "Git Extras" +.TH "GIT\-SUMMARY" "1" "February 2025" "" "Git Extras" .SH "NAME" \fBgit\-summary\fR \- Show repository summary .SH "SYNOPSIS" @@ -38,6 +38,10 @@ $ git summary \-\-dedup\-by\-email .P Exclude merge commits\. .P +\-\-full\-path +.P +Show the full path of the repository instead of its directory name\. +.P \-\-line .P Summarize with lines other than commits\. When \fB\-\-line\fR is specified, the last argument is treated as \. diff --git a/man/git-summary.html b/man/git-summary.html index ffcd497..8122b82 100644 --- a/man/git-summary.html +++ b/man/git-summary.html @@ -114,6 +114,10 @@ $ git summary --dedup-by-email

    Exclude merge commits.

    +

    --full-path

    + +

    Show the full path of the repository instead of its directory name.

    +

    --line

    Summarize with lines other than commits. @@ -221,7 +225,7 @@ git-extras / age: 13 years / last active: 7 hours ago / active on 807 days / com

    1. -
    2. June 2023
    3. +
    4. February 2025
    5. git-summary(1)
    diff --git a/man/git-summary.md b/man/git-summary.md index 11ca4bb..708586d 100644 --- a/man/git-summary.md +++ b/man/git-summary.md @@ -39,6 +39,10 @@ Shows a summary of the repository or a path within it. Exclude merge commits. + --full-path + + Show the full path of the repository instead of its directory name. + --line Summarize with lines other than commits. From 18d101732552c0bfba53cccb572de4f95c4ae9ae Mon Sep 17 00:00:00 2001 From: Pierre Ayoub Date: Wed, 5 Mar 2025 03:39:41 +0100 Subject: [PATCH 32/62] feat(git-bulk): add new option to not follow hidden directories (#1195) * feat(git-bulk): add --no-follow-hidden flag * docs(git-bulk): add --no-follow-hidden description * docs(man): make ronn * docs(git-bulk): put --no-follow-hidden at right place in usage() * refactor(git-bulk): logic optimization Remove unnecessary subshell Co-authored-by: Edwin Kofler * fix(git-bulk): bad test operator With the Bash' regexp matching operator `=~`, we need to use the Bash conditional expression evaluation command `[[ ]]`. * docs(completion.zsh): add --no-follow-hidden and --no-follow-symlink --------- Co-authored-by: Pierre Ayoub Co-authored-by: Edwin Kofler --- bin/git-bulk | 10 ++++++++-- etc/git-extras-completion.zsh | 2 ++ man/git-bulk.1 | 6 +++++- man/git-bulk.html | 6 +++++- man/git-bulk.md | 6 +++++- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/bin/git-bulk b/bin/git-bulk index fe58e48..1f414ea 100755 --- a/bin/git-bulk +++ b/bin/git-bulk @@ -10,12 +10,13 @@ singlemode=false allwsmode=false quiet=false no_follow_symlinks=false +no_follow_hidden=false # # print usage message # usage() { - echo 1>&2 "usage: git bulk [--no-follow-symlinks] [-q|--quiet] [-g] ([-a]|[-w ]) " + echo 1>&2 "usage: git bulk [--no-follow-symlinks] [--no-follow-hidden] [-q|--quiet] [-g] ([-a]|[-w ]) " echo 1>&2 " git bulk --addworkspace (--from )" echo 1>&2 " git bulk --removeworkspace " echo 1>&2 " git bulk --addcurrent " @@ -172,7 +173,10 @@ function executBulkOp () { cd "$gitrepodir" || exit 1 # into git repo location local curdir=$PWD local leadingpath=${curdir#"${actual}"} - guardedExecution "$@" + # do not execute if we do not want to consider a ".git" directory under a hidden directory + if [ $no_follow_hidden = false ] || ! [[ "$leadingpath" =~ "/." ]]; then + guardedExecution "$@" + fi cd "$rwsdir" || exit 1 # back to origin location of last find command done done @@ -193,6 +197,8 @@ while [ "${#}" -ge 1 ] ; do butilcommand="${1:2}" && wsname="$2" && wsdir="$3" && if [ "$4" == "--from" ]; then source="$5"; fi && break ;; --no-follow-symlinks) no_follow_symlinks=true ;; + --no-follow-hidden) + no_follow_hidden=true ;; -a) allwsmode=true ;; -g) diff --git a/etc/git-extras-completion.zsh b/etc/git-extras-completion.zsh index 63e77f5..cbe72e1 100644 --- a/etc/git-extras-completion.zsh +++ b/etc/git-extras-completion.zsh @@ -135,6 +135,8 @@ _git-bulk() { '-g[Ask the user for confirmation on every execution (guarded mode).]' \ '-w[Run the git command on the specified workspace.]:workspace-name:__gitex_workspace_names' \ '-q[Suppress bulk output about current execution (quiet mode).]' \ + '--no-follow-symlinks[Do not traverse symbolic links when searching for git repositories.]' \ + '--no-follow-hidden[Do not traverse hidden directories when searching for git repositories.]' \ '--addworkspace[Register a workspace for bulk operations.]' \ '--removeworkspace[Remove the specified workspace.]:workspace-name:__gitex_workspace_names' \ '--addcurrent[Adds the current directory as workspace to git bulk operations]' \ diff --git a/man/git-bulk.1 b/man/git-bulk.1 index 0ed0a14..64a7d9b 100644 --- a/man/git-bulk.1 +++ b/man/git-bulk.1 @@ -4,7 +4,7 @@ .SH "NAME" \fBgit\-bulk\fR \- Run git commands on multiple repositories .SH "SYNOPSIS" -\fBgit\-bulk\fR [\-g] [\-\-no\-follow\-symlinks] ([\-a]|[\-w +\fBgit\-bulk\fR [\-g] [\-\-no\-follow\-symlinks] [\-\-no\-follow\-hidden] ([\-a]|[\-w .br \fBgit\-bulk\fR \-\-addworkspace .br @@ -37,6 +37,10 @@ Ask the user for confirmation on every execution\. .P Do not traverse symbolic links under the workspace when searching for git repositories\. .P +\-\-no\-follow\-hidden +.P +Do not traverse hidden (dotted) directories under the workspace when searching for git repositories\. +.P \-w .P Run the git command on the specified workspace\. The workspace must be registered\. diff --git a/man/git-bulk.html b/man/git-bulk.html index 66b0e17..b5165be 100644 --- a/man/git-bulk.html +++ b/man/git-bulk.html @@ -78,7 +78,7 @@

    SYNOPSIS

    -

    git-bulk [-g] [--no-follow-symlinks] ([-a]|[-w ])
    +

    git-bulk [-g] [--no-follow-symlinks] [--no-follow-hidden] ([-a]|[-w ])
    git-bulk --addworkspace (--from )
    git-bulk --removeworkspace <ws-name>
    git-bulk --addcurrent <ws-name>
    @@ -110,6 +110,10 @@

    Do not traverse symbolic links under the workspace when searching for git repositories.

    +

    --no-follow-hidden

    + +

    Do not traverse hidden (dotted) directories under the workspace when searching for git repositories.

    +

    -w <ws-name>

    Run the git command on the specified workspace. The workspace must be registered.

    diff --git a/man/git-bulk.md b/man/git-bulk.md index f5550d5..68ac35c 100644 --- a/man/git-bulk.md +++ b/man/git-bulk.md @@ -3,7 +3,7 @@ git-bulk(1) -- Run git commands on multiple repositories ## SYNOPSIS -`git-bulk` [-g] [--no-follow-symlinks] ([-a]|[-w <ws-name>]) <git command>
    +`git-bulk` [-g] [--no-follow-symlinks] [--no-follow-hidden] ([-a]|[-w <ws-name>]) <git command>
    `git-bulk` --addworkspace <ws-name> <ws-root-directory> (--from <URL or file>)
    `git-bulk` --removeworkspace <ws-name>
    `git-bulk` --addcurrent <ws-name>
    @@ -32,6 +32,10 @@ git bulk adds convenient support for operations that you want to execute on mult Do not traverse symbolic links under the workspace when searching for git repositories. + --no-follow-hidden + + Do not traverse hidden (dotted) directories under the workspace when searching for git repositories. + -w <ws-name> Run the git command on the specified workspace. The workspace must be registered. From aadf5f874be3d1b77c0ec47c9fc3e52a4057ac7a Mon Sep 17 00:00:00 2001 From: Edwin Kofler Date: Wed, 12 Mar 2025 03:14:53 -0700 Subject: [PATCH 33/62] Add stale bot for old PRs (#1186) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add stale bot for old PRs * Update message * Update .github/workflows/stale.yaml Co-authored-by: 罗泽轩 * Fix stale workflow * Update .github/workflows/stale.yaml Co-authored-by: 罗泽轩 --------- Co-authored-by: 罗泽轩 --- .github/workflows/stale.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/stale.yaml diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 0000000..3c6a1d2 --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,18 @@ +name: "Maintenance: Close Stale PRs" +on: + schedule: + - cron: "30 1 * * *" + +permissions: + pull-requests: "write" + +jobs: + stale: + runs-on: "ubuntu-latest" + if: github.repository_owner == 'tj' + steps: + - uses: "actions/stale@v9" + with: + close-pr-message: "This PR was closed because it has been stalled for 365 days with no activity. Feel free to make a new PR if you wish to continue" + days-before-pr-stale: 350 + days-before-pr-close: 15 From 18ecffef5e75277f5d8a07f21227dc39450256e0 Mon Sep 17 00:00:00 2001 From: Edwin Kofler Date: Wed, 12 Mar 2025 03:15:37 -0700 Subject: [PATCH 34/62] Implement half of tests in Bats (#1187) * Initial scaffold * Finish first half of Bats tests * Add Bats to CI * Fix CI * Fix CI * Fix configuration to set git name and email * Fix `GIT_CONFIG_{KEY,VALUE}` indexing * Fix Git config override precedence * Remove unused and untested function --- .editorconfig | 8 +++ .github/workflows/ci.yml | 15 ++++- .gitmodules | 3 + tests/git-abort.bats | 93 +++++++++++++++++++++++++++++++ tests/git-alias.bats | 106 ++++++++++++++++++++++++++++++++++++ tests/git-archive-file.bats | 66 ++++++++++++++++++++++ tests/git-authors.bats | 52 ++++++++++++++++++ tests/test_util.sh | 26 +++++++++ vendor/bats-all | 1 + 9 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 .gitmodules create mode 100644 tests/git-abort.bats create mode 100644 tests/git-alias.bats create mode 100644 tests/git-archive-file.bats create mode 100644 tests/git-authors.bats create mode 100644 tests/test_util.sh create mode 160000 vendor/bats-all diff --git a/.editorconfig b/.editorconfig index bc1a4e7..b715678 100644 --- a/.editorconfig +++ b/.editorconfig @@ -20,3 +20,11 @@ trim_trailing_whitespace = false [*.py] indent_size = 4 + +[*.bats] +indent_style = tab +indent_size = 4 + +[tests/*.sh] +indent_style = tab +indent_size = 4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e963ef..b0257c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,22 +70,33 @@ jobs: steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install poetry run: pip install poetry - name: Set up Python uses: actions/setup-python@v5 with: + python-version: '3.12' cache: 'poetry' cache-dependency-path: "tests/pyproject.toml" python-version-file: "tests/pyproject.toml" - - name: Install dependencies + - name: Install Python Dependencies run: | cd tests || exit poetry install --only test - - name: Unit test + - name: Setup Bats + id: setup-bats + uses: bats-core/bats-action@3.0.0 + - name: Test with Pytest run: | cd tests poetry run pytest + - name: Test with Bats + env: + BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }} + TERM: xterm + run: bats ./tests build: strategy: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..33dec90 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/bats-all"] + path = vendor/bats-all + url = https://github.com/hyperupcall/bats-all diff --git a/tests/git-abort.bats b/tests/git-abort.bats new file mode 100644 index 0000000..94f9f8d --- /dev/null +++ b/tests/git-abort.bats @@ -0,0 +1,93 @@ +# shellcheck shell=bash + +source "$BATS_TEST_DIRNAME/test_util.sh" + +setup_file() { + test_util.setup_file +} + +setup() { + test_util.cd_test + + git init + git commit --allow-empty -m "Initial commit" + git branch A + git branch B + git checkout A + printf '%s\n' 'a' > tmpfile + git add . + git commit -m A + git checkout B + printf '%s\n' 'b' > tmpfile + git add . + git commit -m B + git status +} + +@test "cherry pick" { + run git cherry-pick A + assert_failure + + run git status + assert_line -p 'You are currently cherry-picking commit' + assert_line -p 'Unmerged paths:' + assert_success + + run git abort + assert_success + + run git status + assert_line -p 'nothing to commit, working tree clean' + assert_success +} + +@test "merge" { + run git merge A + assert_failure + + run git status + assert_line -p 'You have unmerged paths' + assert_line -p 'Unmerged paths:' + assert_success + + run git abort + assert_success + + run git status + assert_line -p 'nothing to commit, working tree clean' + assert_success +} + +@test "rebase" { + run git rebase A + assert_failure + + run git status + assert_line -p 'You are currently rebasing branch' + assert_line -p 'Unmerged paths:' + assert_success + + run git abort + assert_success + + run git status + assert_line -p 'nothing to commit, working tree clean' + assert_success +} + +@test "revert" { + run git revert A + assert_failure + + run git status + assert_line -p 'You are currently reverting commit' + assert_line -p 'Unmerged paths:' + assert_success + + run git abort + assert_success + + run git status + assert_line -p 'nothing to commit, working tree clean' + assert_success +} diff --git a/tests/git-alias.bats b/tests/git-alias.bats new file mode 100644 index 0000000..fa210a7 --- /dev/null +++ b/tests/git-alias.bats @@ -0,0 +1,106 @@ +# shellcheck shell=bash + +source "$BATS_TEST_DIRNAME/test_util.sh" + +setup_file() { + test_util.setup_file +} + +setup() { + test_util.cd_test + + git init + git config --global alias.globalalias status + git config --global alias.x status + git config --local alias.localalias status + git config --local alias.y status +} + +@test "list all works" { + run git alias + assert_output - <<-'EOF' + globalalias = status + localalias = status + x = status + y = status +EOF + assert_success +} + +@test "list all globally works" { + run git alias --global + assert_output - <<-'EOF' + globalalias = status + x = status +EOF + assert_success +} + +@test "list all locally works" { + run git alias --local + assert_output - <<-'EOF' + localalias = status + y = status +EOF + assert_success +} + +@test "search globally works" { + run git alias --global global + assert_output - <<-'EOF' + globalalias = status +EOF + assert_success + + run git alias --global local + assert_output '' + assert_failure +} + +@test "search locally works" { + run git alias --local local + assert_output - <<-'EOF' + localalias = status +EOF + assert_success + + run git alias --local global + assert_output '' + assert_failure +} + +@test "get alias globally and defaultly" { + run git alias globalalias + assert_output - <<-'EOF' + globalalias = status +EOF + assert_success +} + +@test "set alias globally and defaultly" { + git alias globalalias diff + run git alias diff + assert_output - <<-'EOF' + globalalias = diff +EOF + assert_success +} + +@test "get alias locally" { + run git alias --local localalias + assert_output - <<-'EOF' + localalias = status +EOF + assert_success +} + +@test "set alias locally" { + git alias --local localalias diff + run git alias + assert_output - <<-'EOF' + globalalias = status + localalias = diff + x = status + y = status +EOF +} diff --git a/tests/git-archive-file.bats b/tests/git-archive-file.bats new file mode 100644 index 0000000..76cffd6 --- /dev/null +++ b/tests/git-archive-file.bats @@ -0,0 +1,66 @@ +# shellcheck shell=bash + +source "$BATS_TEST_DIRNAME/test_util.sh" + +setup_file() { + test_util.setup_file +} + +setup() { + test_util.cd_test + + git init + printf '%s\n' 'data' > tmpfile + git add . + git commit -m 'test: add data' + git tag 0.1.0 -m 'bump: 0.1.0' +} + +@test "archive file on tags branch" { + git checkout -b tags0.1.0 + run git archive-file + assert_success + + local describe_output= + describe_output=$(git describe) + assert_file_exists "${PWD##*/}.$describe_output.zip" +} + +@test "archive file on any not tags branch without default branch" { + git checkout -b not-tags-branch + run git archive-file + assert_success + + local describe_output= + describe_output=$(git describe --always --long) + assert_file_exists "${PWD##*/}.$describe_output.not-tags-branch.zip" +} + +@test "archive file on any not tags branch with default branch" { + skip "Not working as expected" + + run git archive-file + assert_success + + local describe_output= + describe_output=$(git describe --always --long) + assert_file_exists "${PWD##*/}.$describe_output.zip" +} + +@test "archive file on branch name has slash" { + git checkout -b feature/slash + run git archive-file + assert_success + + local describe_output= + describe_output=$(git describe --always --long) + assert_file_exists "${PWD##*/}.$describe_output.feature-slash.zip" +} + +@test "archive file on dirname has backslash" { + skip +} + +@test "archive file on tag name has slash" { + skip +} diff --git a/tests/git-authors.bats b/tests/git-authors.bats new file mode 100644 index 0000000..203a882 --- /dev/null +++ b/tests/git-authors.bats @@ -0,0 +1,52 @@ +# shellcheck shell=bash + +source "$BATS_TEST_DIRNAME/test_util.sh" + + +setup_file() { + test_util.setup_file +} + +setup() { + test_util.cd_test + + git init + GIT_CONFIG_VALUE_0='test@example.com' + GIT_CONFIG_VALUE_1='test' + printf '%s\n' 'A' > tmpfile + git add . + git commit -m 'test: add data A' + GIT_CONFIG_VALUE_0='testagain@example.com' + GIT_CONFIG_VALUE_1='testagain' + printf '%s\n' 'B' > tmpfile + git add . + git commit -m 'test: add data B' +} + +@test "output authors has email without any parameter" { + run git authors + assert_success + + local content=$(\ntestagain ' +} + +@test "list authors has email defaultly" { + run git authors --list + assert_output $'test \ntestagain ' + assert_success + + run git authors -l + assert_output $'test \ntestagain ' + assert_success +} + +@test "list authors has no email" { + run git authors --list --no-email + assert_output $'test\ntestagain' + assert_success + + run git authors -l --no-email + assert_output $'test\ntestagain' + assert_success +} diff --git a/tests/test_util.sh b/tests/test_util.sh new file mode 100644 index 0000000..73ca915 --- /dev/null +++ b/tests/test_util.sh @@ -0,0 +1,26 @@ +# shellcheck shell=bash + +source "$BATS_TEST_DIRNAME/../vendor/bats-all/load.bash" + +test_util.setup_file() { + cd "$BATS_FILE_TMPDIR" + + export GIT_CONFIG_NOSYSTEM=1 + export GIT_CONFIG_GLOBAL="$PWD/git_config" + export GIT_CONFIG_COUNT=3 + export GIT_CONFIG_KEY_0="user.email" + export GIT_CONFIG_VALUE_0="name@example.com" + export GIT_CONFIG_KEY_1="user.name" + export GIT_CONFIG_VALUE_1="Name" + # This removes default warning about default "master" branch on some Git versions. + export GIT_CONFIG_KEY_2="init.defaultBranch" + export GIT_CONFIG_VALUE_2="main" + + # Append to path so that we can access all commands included from git-extras + # TODO: This currently breaks with commands that are included in "not_needed_git_repo" etc. + PATH="$BATS_TEST_DIRNAME/../bin:$PATH" +} + +test_util.cd_test() { + cd "$BATS_TEST_TMPDIR" +} diff --git a/vendor/bats-all b/vendor/bats-all new file mode 160000 index 0000000..a16a4a2 --- /dev/null +++ b/vendor/bats-all @@ -0,0 +1 @@ +Subproject commit a16a4a2cfa744878992a5f5d3f206319dba54158 From c49ca70814f4169ee18f9a67ec15f9e3604fc063 Mon Sep 17 00:00:00 2001 From: Edwin Kofler Date: Tue, 25 Mar 2025 20:19:30 -0700 Subject: [PATCH 35/62] Mostly finish pytest to Bats conversion (#1200) * Mostly finish pytest to Bats conversion * Add Bats version note to testing docs * Move Bats CI check to separate job --- .github/workflows/ci.yml | 20 ++++-- .gitignore | 1 + tests/README.md | 36 +++++++++-- tests/bin/open | 2 + tests/bin/powershell.exe | 2 + tests/bin/start | 2 + tests/bin/xdg-open | 2 + tests/git-abort.bats | 10 +-- tests/git-alias.bats | 2 +- tests/git-archive-file.bats | 2 +- tests/git-authors.bats | 15 +++-- tests/git-browse-ci.bats | 117 ++++++++++++++++++++++++++++++++++ tests/git-browse.bats | 123 ++++++++++++++++++++++++++++++++++++ tests/git-continue.bats | 92 +++++++++++++++++++++++++++ tests/test_util.sh | 14 ++-- 15 files changed, 409 insertions(+), 31 deletions(-) create mode 100755 tests/bin/open create mode 100755 tests/bin/powershell.exe create mode 100755 tests/bin/start create mode 100755 tests/bin/xdg-open create mode 100644 tests/git-browse-ci.bats create mode 100644 tests/git-browse.bats create mode 100755 tests/git-continue.bats diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0257c9..1dc305a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,9 +65,9 @@ jobs: grep -v -e 'History\.md' -e 'AUTHORS' -e 'man/.*\.1' -e 'man/.*\.html' | \ xargs poetry run codespell --ignore-words=../.github/.ignore_words - test: + test-pytest: + name: 'Test with Pytest' runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 with: @@ -85,13 +85,23 @@ jobs: run: | cd tests || exit poetry install --only test - - name: Setup Bats - id: setup-bats - uses: bats-core/bats-action@3.0.0 - name: Test with Pytest run: | cd tests poetry run pytest + + test-bats: + name: 'Test with Bats' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Setup Bats + id: setup-bats + uses: bats-core/bats-action@3.0.0 + with: + bats-version: 'v1.8.1' - name: Test with Bats env: BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }} diff --git a/.gitignore b/.gitignore index a230a78..294ece4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .venv/ __pycache__/ +coverage/ diff --git a/tests/README.md b/tests/README.md index 7c60a07..836021d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,7 +1,28 @@ -# Test for git-extras -The git-extras has its own testcases now, and the more is on the way! So let's introduce it. +# Testing -We choose python to help us to reach to other shore cause **python is life saver**. +Originally, the tests were written in pytest. However, tests are in the process of being converted to Bats so coverage can be calculated. + +## Bats Testing + +We require a somewhat recent version of Bats. Version v1.8.1 is tested in CI. Once it is installed, the tests can be executed like so: + +```sh +bats ./tests +``` + +We highly recommend adding tests for new features and fixes. + +### Code Coverage + +Coverage can be calculated with [bashcov](https://github.com/infertux/bashcov) like so: + +```sh +bashcov -- bats ./tests +``` + +By default, the report will be generated in `./coverage/index.html`. + +## Python Testing The test part depends on: @@ -12,7 +33,8 @@ The test part depends on: So the versions are higher than above is recommended. -# How to run the tests +### How to run the tests + 1. Install `poetry` 2. Install the dependencies via `poetry install` 3. Run `poetry run pytest` @@ -27,7 +49,8 @@ It is done or go without `poetry`, The second way maybe blocked the some missing dependencies at someday, so the first one is recommended. -# What and how to create a unit test +### What and how to create a unit test + One command has a unit test, because one `git-*` command is just do one thing, so we can eat a piece of `git-*` command in one time. For example, @@ -39,7 +62,8 @@ For example, * `named_temp_repo` is just same as `temp_repo` except the custom directory renaming. 4. Loop the third step until the 100% coverage of the function of the `git-alias` -# References +### References + * [poetry](https://github.com/python-poetry/poetry) * [pytest](https://github.com/pytest-dev/pytest/) * [git python](https://github.com/gitpython-developers/GitPython) diff --git a/tests/bin/open b/tests/bin/open new file mode 100755 index 0000000..2be3eaf --- /dev/null +++ b/tests/bin/open @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +printf '%s\n' "open $*" diff --git a/tests/bin/powershell.exe b/tests/bin/powershell.exe new file mode 100755 index 0000000..aee36a1 --- /dev/null +++ b/tests/bin/powershell.exe @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +printf '%s\n' "powershell.exe $*" diff --git a/tests/bin/start b/tests/bin/start new file mode 100755 index 0000000..89eeebd --- /dev/null +++ b/tests/bin/start @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +printf '%s\n' "start $*" diff --git a/tests/bin/xdg-open b/tests/bin/xdg-open new file mode 100755 index 0000000..951b999 --- /dev/null +++ b/tests/bin/xdg-open @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +printf '%s\n' "xdg-open $*" diff --git a/tests/git-abort.bats b/tests/git-abort.bats index 94f9f8d..73e8302 100644 --- a/tests/git-abort.bats +++ b/tests/git-abort.bats @@ -9,7 +9,7 @@ setup_file() { setup() { test_util.cd_test - git init + test_util.git_init git commit --allow-empty -m "Initial commit" git branch A git branch B @@ -24,7 +24,7 @@ setup() { git status } -@test "cherry pick" { +@test "works with cherry pick" { run git cherry-pick A assert_failure @@ -41,7 +41,7 @@ setup() { assert_success } -@test "merge" { +@test "works with merge" { run git merge A assert_failure @@ -58,7 +58,7 @@ setup() { assert_success } -@test "rebase" { +@test "works with rebase" { run git rebase A assert_failure @@ -75,7 +75,7 @@ setup() { assert_success } -@test "revert" { +@test "works with revert" { run git revert A assert_failure diff --git a/tests/git-alias.bats b/tests/git-alias.bats index fa210a7..bb15410 100644 --- a/tests/git-alias.bats +++ b/tests/git-alias.bats @@ -9,7 +9,7 @@ setup_file() { setup() { test_util.cd_test - git init + test_util.git_init git config --global alias.globalalias status git config --global alias.x status git config --local alias.localalias status diff --git a/tests/git-archive-file.bats b/tests/git-archive-file.bats index 76cffd6..b7f1849 100644 --- a/tests/git-archive-file.bats +++ b/tests/git-archive-file.bats @@ -9,7 +9,7 @@ setup_file() { setup() { test_util.cd_test - git init + test_util.git_init printf '%s\n' 'data' > tmpfile git add . git commit -m 'test: add data' diff --git a/tests/git-authors.bats b/tests/git-authors.bats index 203a882..2fafffa 100644 --- a/tests/git-authors.bats +++ b/tests/git-authors.bats @@ -10,17 +10,22 @@ setup_file() { setup() { test_util.cd_test - git init - GIT_CONFIG_VALUE_0='test@example.com' - GIT_CONFIG_VALUE_1='test' + test_util.git_init + + git config user.name 'test' + git config user.email 'test@example.com' printf '%s\n' 'A' > tmpfile git add . git commit -m 'test: add data A' - GIT_CONFIG_VALUE_0='testagain@example.com' - GIT_CONFIG_VALUE_1='testagain' + + git config user.name 'testagain' + git config user.email 'testagain@example.com' printf '%s\n' 'B' > tmpfile git add . git commit -m 'test: add data B' + + # git config unset user.name + # git config unset user.email } @test "output authors has email without any parameter" { diff --git a/tests/git-browse-ci.bats b/tests/git-browse-ci.bats new file mode 100644 index 0000000..fee89fc --- /dev/null +++ b/tests/git-browse-ci.bats @@ -0,0 +1,117 @@ +# shellcheck shell=bash + +source "$BATS_TEST_DIRNAME/test_util.sh" + +setup_file() { + test_util.setup_file + + PATH="$BATS_TEST_DIRNAME/bin:$PATH" +} + +setup() { + test_util.cd_test + + test_util.git_init +} + +get_ci_uri() { + local mode=$1 + + if [ "$mode" = 'github' ]; then + REPLY="https://github.com/tj/git-extras/actions" + elif [ "$mode" = 'gitlab' ]; then + REPLY="https://gitlab.com/tj/git-extras/-/pipelines" + elif [ "$mode" = 'bitbucket' ]; then + REPLY="https://bitbucket.org/tj/git-extras/addon/pipelines/home" + fi +} + +@test "works with mac and github" { + get_ci_uri 'github' + local expected_url=$REPLY + + git remote add upstream https://github.com/tj/git-extras + OSTYPE=darwin run git browse-ci upstream + assert_output "open $expected_url" + assert_success +} + +@test "works with mac and gitlab" { + get_ci_uri 'gitlab' + local expected_url=$REPLY + + git remote add upstream https://gitlab.com/tj/git-extras + OSTYPE=darwin run git browse-ci upstream + assert_output "open $expected_url" + assert_success +} + +@test "works with mac and bitbucket" { + get_ci_uri 'bitbucket' + local expected_url=$REPLY + + git remote add upstream https://bitbucket.org/tj/git-extras + OSTYPE=darwin run git browse-ci upstream + assert_output "open $expected_url" + assert_success +} + +@test "works with windows and github" { + get_ci_uri 'github' + local expected_url=$REPLY + + git remote add upstream https://github.com/tj/git-extras + OSTYPE=msys run git browse-ci upstream + assert_output "start $expected_url" + assert_success +} + +@test "works with windows and gitlab" { + get_ci_uri 'gitlab' + local expected_url=$REPLY + + git remote add upstream https://gitlab.com/tj/git-extras + OSTYPE=msys run git browse-ci upstream + assert_output "start $expected_url" + assert_success +} + +@test "works with windows and bitbucket" { + get_ci_uri 'bitbucket' + local expected_url=$REPLY + + git remote add upstream https://bitbucket.org/tj/git-extras + OSTYPE=msys run git browse-ci upstream + assert_output "start $expected_url" + assert_success +} + +@test "works with linux and github" { + get_ci_uri 'github' + local expected_url=$REPLY + + git remote add upstream https://github.com/tj/git-extras + OSTYPE=linux-gnu run git browse-ci upstream + assert_output "xdg-open $expected_url" + assert_success +} + +@test "works with linux and gitlab" { + get_ci_uri 'gitlab' + local expected_url=$REPLY + + git remote add upstream https://gitlab.com/tj/git-extras + OSTYPE=linux-gnu run git browse-ci upstream + assert_output "xdg-open $expected_url" + assert_success +} + +@test "works with linux and bitbucket" { + get_ci_uri 'bitbucket' + local expected_url=$REPLY + + git remote add upstream https://bitbucket.org/tj/git-extras + OSTYPE=linux-gnu run git browse-ci upstream + assert_output "xdg-open $expected_url" + assert_success +} diff --git a/tests/git-browse.bats b/tests/git-browse.bats new file mode 100644 index 0000000..190ef00 --- /dev/null +++ b/tests/git-browse.bats @@ -0,0 +1,123 @@ +# shellcheck shell=bash + +source "$BATS_TEST_DIRNAME/test_util.sh" + +setup_file() { + test_util.setup_file + + PATH="$BATS_TEST_DIRNAME/bin:$PATH" +} + +setup() { + test_util.cd_test + + test_util.git_init + touch ./browse_this + git add ./browse_this + git commit -m 'Add test file' +} + +get_file_uri() { + local mode=$1 + local filename=$2 + + local commit_hash= + commit_hash=$(git rev-parse HEAD) + if [ "$mode" = 'github' ]; then + REPLY="https://github.com/tj/git-extras/blob/$commit_hash/${filename}" + elif [ "$mode" = 'gitlab' ]; then + REPLY="https://gitlab.com/tj/git-extras/-/blob/${commit_hash}/${filename}" + elif [ "$mode" = 'bitbucket' ]; then + REPLY="https://bitbucket.org/tj/git-extras/src/${commit_hash}/${filename}" + fi +} + +@test "works with mac and github" { + get_file_uri github ./browse_this + local expected_url=$REPLY + + git remote add upstream https://github.com/tj/git-extras + OSTYPE=darwin run git browse upstream ./browse_this + assert_output "open $expected_url" + assert_success +} + +@test "works with mac and gitlab" { + get_file_uri gitlab ./browse_this + local expected_url=$REPLY + + git remote add upstream https://gitlab.com/tj/git-extras + OSTYPE=darwin run git browse upstream ./browse_this + assert_output "open $expected_url" + assert_success +} + +@test "works with mac and bitbucket" { + get_file_uri bitbucket ./browse_this + local expected_url=$REPLY + + git remote add upstream https://bitbucket.org/tj/git-extras + OSTYPE=darwin run git browse upstream ./browse_this + assert_output "open $expected_url" + assert_success +} + +@test "works with windows and github" { + get_file_uri github ./browse_this + local expected_url=$REPLY + + git remote add upstream https://github.com/tj/git-extras + OSTYPE=msys run git browse upstream ./browse_this + assert_output "start $expected_url" + assert_success +} + +@test "works with windows and gitlab" { + get_file_uri gitlab ./browse_this + local expected_url=$REPLY + + git remote add upstream https://gitlab.com/tj/git-extras + OSTYPE=msys run git browse upstream ./browse_this + assert_output "start $expected_url" + assert_success +} + +@test "works with windows and bitbucket" { + get_file_uri bitbucket ./browse_this + local expected_url=$REPLY + + git remote add upstream https://bitbucket.org/tj/git-extras + OSTYPE=msys run git browse upstream ./browse_this + assert_output "start $expected_url" + assert_success +} + +@test "works with linux and github" { + get_file_uri github ./browse_this + local expected_url=$REPLY + + git remote add upstream https://github.com/tj/git-extras + OSTYPE=linux-gnu run git browse upstream ./browse_this + assert_output "xdg-open $expected_url" + assert_success +} + +@test "works with linux and gitlab" { + get_file_uri gitlab ./browse_this + local expected_url=$REPLY + + git remote add upstream https://gitlab.com/tj/git-extras + OSTYPE=linux-gnu run git browse upstream ./browse_this + assert_output "xdg-open $expected_url" + assert_success +} + +@test "works with linux and bitbucket" { + get_file_uri bitbucket ./browse_this + local expected_url=$REPLY + + git remote add upstream https://bitbucket.org/tj/git-extras + OSTYPE=linux-gnu run git browse upstream ./browse_this + assert_output "xdg-open $expected_url" + assert_success +} diff --git a/tests/git-continue.bats b/tests/git-continue.bats new file mode 100755 index 0000000..da916a8 --- /dev/null +++ b/tests/git-continue.bats @@ -0,0 +1,92 @@ +#!/usr/bin/env bats + +source "$BATS_TEST_DIRNAME/test_util.sh" + +setup_file() { + test_util.setup_file +} + +setup() { + test_util.cd_test + + test_util.git_init + git commit -m 'Initial commit' --allow-empty + + git switch -c A main + printf '%s\n' 'a' >> ./tmp_file + git add ./tmp_file + git commit -m 'A' + + git switch -c B main + printf '%s\n' 'b' >> ./tmp_file + git add ./tmp_file + git commit -m 'B' +} + +@test "works with cherry pick" { + run git cherry-pick A + assert_failure + + run git status + assert_line -p 'Unmerged paths:' + assert_success + + git add . + GIT_EDITOR=cat run git continue + assert_success + + run git status + assert_line -p 'nothing to commit, working tree clean' + assert_success +} + +@test "works with merge" { + run git merge A + assert_failure + + run git status + assert_line -p 'Unmerged paths:' + assert_success + + git add . + GIT_EDITOR=cat run git continue + assert_success + + run git status + assert_line -p 'nothing to commit, working tree clean' + assert_success +} + +@test "works with rebase" { + run git rebase A + assert_failure + + run git status + assert_line -p 'Unmerged paths:' + assert_success + + git add . + GIT_EDITOR=cat run git continue + assert_success + + run git status + assert_line -p 'nothing to commit, working tree clean' + assert_success +} + +@test "works with revert" { + run git revert A + assert_failure + + run git status + assert_line -p 'Unmerged paths:' + assert_success + + git add . + GIT_EDITOR=cat run git continue + assert_failure # TODO: Git seems to do nothing and error out? + + run git status + assert_line -p 'nothing to commit, working tree clean' + assert_success +} diff --git a/tests/test_util.sh b/tests/test_util.sh index 73ca915..4c85773 100644 --- a/tests/test_util.sh +++ b/tests/test_util.sh @@ -7,14 +7,6 @@ test_util.setup_file() { export GIT_CONFIG_NOSYSTEM=1 export GIT_CONFIG_GLOBAL="$PWD/git_config" - export GIT_CONFIG_COUNT=3 - export GIT_CONFIG_KEY_0="user.email" - export GIT_CONFIG_VALUE_0="name@example.com" - export GIT_CONFIG_KEY_1="user.name" - export GIT_CONFIG_VALUE_1="Name" - # This removes default warning about default "master" branch on some Git versions. - export GIT_CONFIG_KEY_2="init.defaultBranch" - export GIT_CONFIG_VALUE_2="main" # Append to path so that we can access all commands included from git-extras # TODO: This currently breaks with commands that are included in "not_needed_git_repo" etc. @@ -24,3 +16,9 @@ test_util.setup_file() { test_util.cd_test() { cd "$BATS_TEST_TMPDIR" } + +test_util.git_init() { + git init --initial-branch main + git config user.name 'Name' + git config user.email 'name@example.com' +} From 783812b627eeeb06317e629a44539f2a8ca527a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 00:23:42 +0000 Subject: [PATCH 36/62] chore(deps): bump bats-core/bats-action from 3.0.0 to 3.0.1 Bumps [bats-core/bats-action](https://github.com/bats-core/bats-action) from 3.0.0 to 3.0.1. - [Release notes](https://github.com/bats-core/bats-action/releases) - [Commits](https://github.com/bats-core/bats-action/compare/3.0.0...3.0.1) --- updated-dependencies: - dependency-name: bats-core/bats-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1dc305a..348c4ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,7 +99,7 @@ jobs: submodules: recursive - name: Setup Bats id: setup-bats - uses: bats-core/bats-action@3.0.0 + uses: bats-core/bats-action@3.0.1 with: bats-version: 'v1.8.1' - name: Test with Bats From c63b46a33b41fafa15b0d0da67e4a6a1dd65608c Mon Sep 17 00:00:00 2001 From: Andrew Sullivan Cant Date: Wed, 14 May 2025 05:05:41 -0400 Subject: [PATCH 37/62] add git-wip and git-unwip (#669) * Upgrade codespell to v2.4.0 It is a lastet verison, and is needed so that I can use the inline ignore support in a future commit. After updating tests/pyproject.toml I ran the following commands: ``` cd tests poetry lock --no-update poetry install ``` I also fixed a spelling error in man/git-summary.md, which was not caught by the old version of codespell. After updating the file I also updated the related files by running the following commands: ``` cd man make -C .. man/git-summary.{1,html} ``` * add git-wip and git-unwip Updated/re-built the documentation with: ``` cd man make -C .. man/git-unwip.{1,html} make -C .. man/git-wip.{1,html} make -C .. man/git-extras.{1,html} ``` --- .github/.ignore_words | 1 + Commands.md | 20 +++++ bin/git-unwip | 14 +++ bin/git-wip | 8 ++ etc/git-extras-completion.zsh | 4 +- man/git-extras.1 | 158 +++++++++++++++++----------------- man/git-extras.html | 12 ++- man/git-extras.md | 2 + man/git-summary.1 | 7 +- man/git-summary.html | 22 +++-- man/git-summary.md | 4 +- man/git-unwip.1 | 31 +++++++ man/git-unwip.html | 122 ++++++++++++++++++++++++++ man/git-unwip.md | 36 ++++++++ man/git-wip.1 | 31 +++++++ man/git-wip.html | 122 ++++++++++++++++++++++++++ man/git-wip.md | 36 ++++++++ man/index.txt | 2 + tests/poetry.lock | 18 ++-- tests/pyproject.toml | 2 +- 20 files changed, 549 insertions(+), 103 deletions(-) create mode 100755 bin/git-unwip create mode 100755 bin/git-wip create mode 100644 man/git-unwip.1 create mode 100644 man/git-unwip.html create mode 100644 man/git-unwip.md create mode 100644 man/git-wip.1 create mode 100644 man/git-wip.html create mode 100644 man/git-wip.md diff --git a/.github/.ignore_words b/.github/.ignore_words index 8aad33d..35d9297 100644 --- a/.github/.ignore_words +++ b/.github/.ignore_words @@ -1 +1,2 @@ gool +Cant diff --git a/Commands.md b/Commands.md index 1085ba7..fa59614 100644 --- a/Commands.md +++ b/Commands.md @@ -75,6 +75,8 @@ - [`git undo`](#git-undo) - [`git unlock`](#git-unlock) - [`git utimes`](#git-utimes) + - [`git unwip`](#git-unwip) + - [`git wip`](#git-wip) ## git extras @@ -1637,3 +1639,21 @@ Commits changes with a generated message. ## git continue Continue current revert, rebase, merge or cherry-pick, without the need to find exact command in history. + +## git wip + +Create a Work In Progress(WIP) commit, which will include all changes in the +working directory. (i.e., changes to existing files, new files, removed files) + +```bash +$ git wip +``` + +## git unwip + +Undo a Work In Progress(WIP) commit and put all of those changes back into the +working directory. + +```bash +$ git unwip +``` diff --git a/bin/git-unwip b/bin/git-unwip new file mode 100755 index 0000000..4e772b0 --- /dev/null +++ b/bin/git-unwip @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Based on scripts from git-utils: +# * https://github.com/ddollar/git-utils/blob/master/git-unwip + +# Check if the last commit is a 'WIP' commit +LAST_COMMIT=`git log -1 --pretty=%B | tr -d '[:space:]'` + +if [ 'WIP' != $LAST_COMMIT ]; then + echo 'Last commit is not a WIP commit, so it will not be unWIP-ed.' + exit 1 +fi + +git undo --soft diff --git a/bin/git-wip b/bin/git-wip new file mode 100755 index 0000000..a25d68f --- /dev/null +++ b/bin/git-wip @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# Based on scripts from git-utils: +# * https://github.com/ddollar/git-utils/blob/master/git-wip +# * https://github.com/ddollar/git-utils/blob/master/git-addremove + +git add --all +git commit --all --message="WIP" diff --git a/etc/git-extras-completion.zsh b/etc/git-extras-completion.zsh index cbe72e1..13957f0 100644 --- a/etc/git-extras-completion.zsh +++ b/etc/git-extras-completion.zsh @@ -464,4 +464,6 @@ zstyle ':completion:*:*:git:*' user-commands $existing_user_commands \ touch:'touch and add file to the index' \ undo:'remove latest commits' \ unlock:'unlock a file excluded from version control' \ - utimes:'change files modification time to their last commit date' + utimes:'change files modification time to their last commit date' \ + unwip:'undo a WIP commit' \ + wip:'create a WIP commit' diff --git a/man/git-extras.1 b/man/git-extras.1 index 8f09cbf..cba16e0 100644 --- a/man/git-extras.1 +++ b/man/git-extras.1 @@ -1,6 +1,6 @@ -.\" generated with Ronn-NG/v0.9.1 -.\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "GIT\-EXTRAS" "1" "September 2024" "" "Git Extras" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "GIT\-EXTRAS" "1" "May 2025" "" "Git Extras" .SH "NAME" \fBgit\-extras\fR \- Awesome GIT utilities .SH "SYNOPSIS" @@ -22,154 +22,158 @@ Self update\. .P Change the default branch to \fB$BRANCH\fR\. If \fBgit\-extras\.default\-branch\fR isn't set, \fBinit\.defaultBranch\fR is used instead\. If none of them are set it defaults to \fBmain\fR\. .SH "COMMANDS" -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-abort(1)\fR Abort current git operation -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-alias(1)\fR Define, search and show aliases -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-archive\-file(1)\fR Export the current HEAD of the git repository to an archive -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-authors(1)\fR Generate authors report -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-browse\-ci(1)\fR \fIView the web page for the current repository\fR -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-browse(1)\fR \fIView the web page for the current repository\fR -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-brv(1)\fR List branches sorted by their last commit date -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-bulk(1)\fR Run git commands on multiple repositories -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-changelog(1)\fR Generate a changelog report -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-clear\-soft(1)\fR Soft clean up a repository -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-clear(1)\fR Rigorously clean up a repository -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-coauthor(1)\fR Add a co\-author to the last commit -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-commits\-since(1)\fR Show commit logs since some date -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-continue(1)\fR Continue current git operation -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-contrib(1)\fR Show user's contributions -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-count(1)\fR Show commit count -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-cp(1)\fR Copy a file keeping its history -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-create\-branch(1)\fR Create branches -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-delete\-branch(1)\fR Delete branches -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-delete\-merged\-branches(1)\fR Delete merged branches -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-delete\-squashed\-branches(1)\fR Delete branches that were squashed -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-delete\-submodule(1)\fR Delete submodules -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-delete\-tag(1)\fR Delete tags -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-delta(1)\fR Lists changed files -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-effort(1)\fR Show effort statistics on file(s) -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-feature(1)\fR Create/Merge feature branch -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-force\-clone(1)\fR overwrite local repositories with clone -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-fork(1)\fR Fork a repo on github -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-fresh\-branch(1)\fR Create fresh branches -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-get(1)\fR Clone a Git repository under a configured directory -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-gh\-pages(1)\fR Create the GitHub Pages branch -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-graft(1)\fR Merge and destroy a given branch -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-guilt(1)\fR calculate change between two revisions -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-ignore\-io(1)\fR Get sample gitignore file -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-ignore(1)\fR Add \.gitignore patterns -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-info(1)\fR Returns information on current repository -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-local\-commits(1)\fR List local commits -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-lock(1)\fR Lock a file excluded from version control -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-locked(1)\fR ls files that have been locked -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-magic(1)\fR Automate add/commit/push routines -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-merge\-into(1)\fR Merge one branch into another -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-merge\-repo(1)\fR Merge two repo histories -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-missing(1)\fR Show commits missing from another branch -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-mr(1)\fR Checks out a merge request locally -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-obliterate(1)\fR rewrite past commits to remove some files -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-paste(1)\fR Send patches to pastebin for chat conversations -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-pr(1)\fR Checks out a pull request locally -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-psykorebase(1)\fR Rebase a branch with a merge commit -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-pull\-request(1)\fR Create pull request for GitHub project -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-reauthor(1)\fR Rewrite history to change author's identity -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-rebase\-patch(1)\fR Rebases a patch -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-release(1)\fR Commit, tag and push changes to the repository -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-rename\-branch(1)\fR rename local branch and push to remote -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-rename\-file(1)\fR Rename a file or directory and ensure Git recognizes the change, regardless of filesystem case\-sensitivity\. -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-rename\-remote(1)\fR Rename a remote -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-rename\-tag(1)\fR Rename a tag -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-repl(1)\fR git read\-eval\-print\-loop -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-reset\-file(1)\fR Reset one file -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-root(1)\fR show path of root -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-scp(1)\fR Copy files to SSH compatible \fBgit\-remote\fR -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-sed(1)\fR replace patterns in git\-controlled files -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-setup(1)\fR Set up a git repository -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-show\-merged\-branches(1)\fR Show merged branches -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-show\-tree(1)\fR show branch tree of commit history -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-show\-unmerged\-branches(1)\fR Show unmerged branches -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-squash(1)\fR squash N last changes up to a ref'ed commit -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-stamp(1)\fR Stamp the last commit message -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-standup(1)\fR Recall the commit history -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-summary(1)\fR Show repository summary -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-sync(1)\fR Sync local branch with remote branch -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-touch(1)\fR Touch and add file to the index -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-undo(1)\fR Remove latest commits -.IP "\[ci]" 4 +.IP "\(bu" 4 \fBgit\-unlock(1)\fR Unlock a file excluded from version control -.IP "\[ci]" 4 +.IP "\(bu" 4 +\fBgit\-unwip(1)\fR Undo a Work In Progress commit +.IP "\(bu" 4 \fBgit\-utimes(1)\fR Change files modification time to their last commit date +.IP "\(bu" 4 +\fBgit\-wip(1)\fR Create a Work In Progress commit .IP "" 0 .SH "AUTHOR" Written by Tj Holowaychuk <\fItj@vision\-media\.ca\fR> diff --git a/man/git-extras.html b/man/git-extras.html index 36448ef..1688eaf 100644 --- a/man/git-extras.html +++ b/man/git-extras.html @@ -1,8 +1,8 @@ - - + + git-extras(1) - Awesome GIT utilities + + + +
    + + + +
      +
    1. git-unwip(1)
    2. +
    3. Git Extras
    4. +
    5. git-unwip(1)
    6. +
    + + + +

    NAME

    +

    + git-unwip - Undo a Work In Progress commit +

    +

    SYNOPSIS

    + +

    git-unwip

    + +

    DESCRIPTION

    + +

    Undo a Work In Progress commit.

    + +

    OPTIONS

    + +

    None

    + +

    EXAMPLES

    + +

    Create a WIP commit which stores all changes in the working directory.

    + +
    $ git wip
    +
    + +

    Later on, undo the commit and continue making changes.

    + +
    $ git unwip
    +
    + +

    AUTHOR

    + +

    Written by Andrew Sullivan Cant <mail@andrewsullivancant.ca>

    + +

    REPORTING BUGS

    + +

    <https://github.com/tj/git-extras/issues>

    + +

    SEE ALSO

    + +

    <https://github.com/tj/git-extras>

    + +
      +
    1. +
    2. May 2025
    3. +
    4. git-unwip(1)
    5. +
    + +
    + + diff --git a/man/git-unwip.md b/man/git-unwip.md new file mode 100644 index 0000000..6d4d961 --- /dev/null +++ b/man/git-unwip.md @@ -0,0 +1,36 @@ +git-unwip(1) -- Undo a Work In Progress commit +================================ + +## SYNOPSIS + +`git-unwip` + +## DESCRIPTION + + Undo a Work In Progress commit. + +## OPTIONS + + None + +## EXAMPLES + + Create a WIP commit which stores all changes in the working directory. + + $ git wip + + Later on, undo the commit and continue making changes. + + $ git unwip + +## AUTHOR + +Written by Andrew Sullivan Cant <> + +## REPORTING BUGS + +<> + +## SEE ALSO + +<> diff --git a/man/git-wip.1 b/man/git-wip.1 new file mode 100644 index 0000000..3d1e326 --- /dev/null +++ b/man/git-wip.1 @@ -0,0 +1,31 @@ +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "GIT\-WIP" "1" "May 2025" "" "Git Extras" +.SH "NAME" +\fBgit\-wip\fR \- Create a Work In Progress commit +.SH "SYNOPSIS" +\fBgit\-wip\fR +.SH "DESCRIPTION" +Create a Work In Progress commit, include all files in the working directory\. +.SH "OPTIONS" +None +.SH "EXAMPLES" +Create a WIP commit which stores all changes in the working directory\. +.IP "" 4 +.nf +$ git wip +.fi +.IP "" 0 +.P +Later on, undo the commit and continue making changes\. +.IP "" 4 +.nf +$ git unwip +.fi +.IP "" 0 +.SH "AUTHOR" +Written by Andrew Sullivan Cant <\fImail@andrewsullivancant\.ca\fR> +.SH "REPORTING BUGS" +<\fIhttps://github\.com/tj/git\-extras/issues\fR> +.SH "SEE ALSO" +<\fIhttps://github\.com/tj/git\-extras\fR> diff --git a/man/git-wip.html b/man/git-wip.html new file mode 100644 index 0000000..b640255 --- /dev/null +++ b/man/git-wip.html @@ -0,0 +1,122 @@ + + + + + + git-wip(1) - Create a Work In Progress commit + + + + +
    + + + +
      +
    1. git-wip(1)
    2. +
    3. Git Extras
    4. +
    5. git-wip(1)
    6. +
    + + + +

    NAME

    +

    + git-wip - Create a Work In Progress commit +

    +

    SYNOPSIS

    + +

    git-wip

    + +

    DESCRIPTION

    + +

    Create a Work In Progress commit, include all files in the working directory.

    + +

    OPTIONS

    + +

    None

    + +

    EXAMPLES

    + +

    Create a WIP commit which stores all changes in the working directory.

    + +
    $ git wip
    +
    + +

    Later on, undo the commit and continue making changes.

    + +
    $ git unwip
    +
    + +

    AUTHOR

    + +

    Written by Andrew Sullivan Cant <mail@andrewsullivancant.ca>

    + +

    REPORTING BUGS

    + +

    <https://github.com/tj/git-extras/issues>

    + +

    SEE ALSO

    + +

    <https://github.com/tj/git-extras>

    + +
      +
    1. +
    2. May 2025
    3. +
    4. git-wip(1)
    5. +
    + +
    + + diff --git a/man/git-wip.md b/man/git-wip.md new file mode 100644 index 0000000..58bc19b --- /dev/null +++ b/man/git-wip.md @@ -0,0 +1,36 @@ +git-wip(1) -- Create a Work In Progress commit +================================ + +## SYNOPSIS + +`git-wip` + +## DESCRIPTION + + Create a Work In Progress commit, include all files in the working directory. + +## OPTIONS + + None + +## EXAMPLES + + Create a WIP commit which stores all changes in the working directory. + + $ git wip + + Later on, undo the commit and continue making changes. + + $ git unwip + +## AUTHOR + +Written by Andrew Sullivan Cant <> + +## REPORTING BUGS + +<> + +## SEE ALSO + +<> diff --git a/man/index.txt b/man/index.txt index 5e98b17..4f24ad0 100644 --- a/man/index.txt +++ b/man/index.txt @@ -73,4 +73,6 @@ git-sync(1) git-sync git-touch(1) git-touch git-undo(1) git-undo git-unlock(1) git-unlock +git-unwip(1) git-unwip git-utimes(1) git-utimes +git-wip(1) git-wip diff --git a/tests/poetry.lock b/tests/poetry.lock index bb52a52..cef29c8 100644 --- a/tests/poetry.lock +++ b/tests/poetry.lock @@ -1,19 +1,21 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "codespell" -version = "2.2.0" -description = "Codespell" +version = "2.4.0" +description = "Fix common misspellings in text files" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "codespell-2.2.0-py3-none-any.whl", hash = "sha256:3cc3fcb484a8302683add19e7d11504c79c79b10d4ea0675409417a044b27374"}, - {file = "codespell-2.2.0.tar.gz", hash = "sha256:3dce0cd1348d277f8d934d1d4dcbbf510f9ddfd1b9005e9b25fb983189962561"}, + {file = "codespell-2.4.0-py3-none-any.whl", hash = "sha256:b4c5b779f747dd481587aeecb5773301183f52b94b96ed51a28126d0482eec1d"}, + {file = "codespell-2.4.0.tar.gz", hash = "sha256:587d45b14707fb8ce51339ba4cce50ae0e98ce228ef61f3c5e160e34f681be58"}, ] [package.extras] -dev = ["check-manifest", "flake8", "pytest", "pytest-cov", "pytest-dependency"] +dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"] hard-encoding-detection = ["chardet"] +toml = ["tomli"] +types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] [[package]] name = "colorama" @@ -143,4 +145,4 @@ test = ["pytest"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "e730a1e6e7fd2f51858e8c8cfa8b56d606eadd7a0a629ca8af078904721a5159" +content-hash = "fcf0ba3dd6655735da5c1e21ba32b94011d32bc5a30fbdaf31146d3820c1442a" diff --git a/tests/pyproject.toml b/tests/pyproject.toml index f04e61d..3d017cb 100644 --- a/tests/pyproject.toml +++ b/tests/pyproject.toml @@ -16,7 +16,7 @@ gitpython = "3.1.43" testpath = "0.6.0" [tool.poetry.group.dev.dependencies] -codespell = "2.2" +codespell = "2.4" [tool.pytest.ini_options] minversion = "7.4" From 3ddc315a76a0612c2122f3c73c00729f9f8869f9 Mon Sep 17 00:00:00 2001 From: Edwin Kofler Date: Thu, 5 Jun 2025 20:10:50 -0700 Subject: [PATCH 38/62] Format comparisons, functions, and redirections to be consistent (#1201) * Format comparisons and functions to be consistent * Add `checkstyle.py` script and add check to CI * Fix Ruff lints --- .github/workflows/ci.yml | 2 + bin/git-abort | 6 +- bin/git-bulk | 26 ++-- bin/git-clear | 4 +- bin/git-clear-soft | 2 +- bin/git-ignore | 20 +-- bin/git-merge-into | 2 +- bin/git-paste | 2 +- bin/git-psykorebase | 2 +- bin/git-pull-request | 2 +- bin/git-rebase-patch | 2 +- bin/git-release | 2 +- bin/git-setup | 2 +- bin/git-standup | 2 +- bin/git-summary | 10 +- scripts/checkstyle.py | 262 +++++++++++++++++++++++++++++++++++++++ 16 files changed, 306 insertions(+), 42 deletions(-) create mode 100755 scripts/checkstyle.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 348c4ed..0d5c32f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,8 @@ jobs: env: # NOTE: use env to pass the output in order to avoid possible injection attacks FILES: "${{ steps.files.outputs.added_modified }}" + - name: checkstyle + run: ./scripts/checkstyle.py - name: Shellcheck run: shellcheck --severity=error bin/* ./*.sh - name: Lint and format Python with Ruff diff --git a/bin/git-abort b/bin/git-abort index 201fa48..af1712b 100755 --- a/bin/git-abort +++ b/bin/git-abort @@ -2,7 +2,7 @@ set -euo pipefail -function discover_op() { +discover_op() { local gitdir # git rev-parse emits an error if not in a git repo so only need to bail out gitdir="$(git rev-parse --git-dir)" || exit @@ -14,7 +14,7 @@ function discover_op() { done } -function validate_op() { +validate_op() { local op="$1" if [ -z "$op" ]; then echo "No active operation found" >&2 @@ -26,7 +26,7 @@ function validate_op() { fi } -function discover_action() { +discover_action() { local action=${1/git-/} if [ "$action" != "abort" ] && [ "$action" != "continue" ]; then echo "Invalid action: $1" >&2 diff --git a/bin/git-bulk b/bin/git-bulk index 1f414ea..06eecd2 100755 --- a/bin/git-bulk +++ b/bin/git-bulk @@ -30,7 +30,7 @@ cdfail() { } # add another workspace to global git config -function addworkspace { +addworkspace() { git config --global bulkworkspaces."$wsname" "$wsdir"; if [ -n "$source" ]; then if [ ! -d "$wsdir" ]; then echo 1>&2 "Path of workspace doesn't exist, make it first."; exit 1; fi @@ -59,19 +59,19 @@ function addworkspace { } # add current directory -function addcurrent { git config --global bulkworkspaces."$wsname" "$PWD"; } +addcurrent() { git config --global bulkworkspaces."$wsname" "$PWD"; } # remove workspace from global git config -function removeworkspace { checkWSName && git config --global --unset bulkworkspaces."$wsname"; } +removeworkspace() { checkWSName && git config --global --unset bulkworkspaces."$wsname"; } # remove workspace from global git config -function purge { git config --global --remove-section bulkworkspaces; } +purge() { git config --global --remove-section bulkworkspaces; } # list all current workspace locations defined -function listall { git config --global --get-regexp bulkworkspaces; } +listall() { git config --global --get-regexp bulkworkspaces; } # guarded execution of a git command in one specific repository -function guardedExecution () { +guardedExecution () { if [ "${quiet?}" != "true" ] || $guardedmode; then echo 1>&2 "${bldred}->${reset} executing ${inverse}git $gitcommand${reset} in repository ${leadingpath%/*}/${bldred}${curdir##*/}${reset}" fi @@ -88,7 +88,7 @@ function guardedExecution () { } # check if the passed command is known as a core git command -function checkGitCommand () { +checkGitCommand () { if git help -a | grep -o -q "\b${corecommand}\b"; then echo 1>&2 "Core command \"$corecommand\" accepted." else @@ -101,7 +101,7 @@ function checkGitCommand () { } # check if workspace name is registered -function checkWSName () { +checkWSName () { while read -r workspace; do parseWsName "$workspace" if [[ $rwsname == "$wsname" ]]; then return; fi @@ -111,7 +111,7 @@ function checkWSName () { } # parse out wsname from workspacespec -function parseWsName () { +parseWsName () { local wsspec="$1" # Get the workspace value from its specification in the `.gitconfig`. # May be an absolute path or a variable name of the form: `$VARNAME` @@ -128,7 +128,7 @@ function parseWsName () { } # detects the wsname of the current directory -function wsnameToCurrent () { +wsnameToCurrent () { while read -r workspace; do if [ -z "$workspace" ]; then continue; fi parseWsName "$workspace" @@ -140,7 +140,7 @@ function wsnameToCurrent () { } # helper to check number of arguments. -function allowedargcount () { +allowedargcount () { if [ "$paramcount" -ne "${1:-0}" ] && [ "$paramcount" -ne "${2:-0}" ]; then echo 1>&2 "error: wrong number of arguments" && usage; exit 1; @@ -148,7 +148,7 @@ function allowedargcount () { } # execute the bulk operation -function executBulkOp () { +executBulkOp () { checkGitCommand if ! $allwsmode && ! $singlemode; then wsnameToCurrent; fi # by default git bulk works within the 'current' workspace listall | while read -r workspacespec; do @@ -194,7 +194,7 @@ while [ "${#}" -ge 1 ] ; do --listall|--purge) butilcommand="${1:2}" && break ;; --removeworkspace|--addcurrent|--addworkspace) - butilcommand="${1:2}" && wsname="$2" && wsdir="$3" && if [ "$4" == "--from" ]; then source="$5"; fi && break ;; + butilcommand="${1:2}" && wsname="$2" && wsdir="$3" && if [ "$4" = "--from" ]; then source="$5"; fi && break ;; --no-follow-symlinks) no_follow_symlinks=true ;; --no-follow-hidden) diff --git a/bin/git-clear b/bin/git-clear index 0c8cbd1..6fd3ae8 100755 --- a/bin/git-clear +++ b/bin/git-clear @@ -2,7 +2,7 @@ PROGNAME="git-clear" FORCE=0 -function _usage() { +_usage() { cat << EOF usage: $PROGNAME options usage: $PROGNAME -h|help|? @@ -38,6 +38,6 @@ else clean=y fi -if [ "$clean" == "y" ]; then +if [ "$clean" = "y" ]; then git clean -d -f -x && git reset --hard fi diff --git a/bin/git-clear-soft b/bin/git-clear-soft index 0473b57..1274d21 100755 --- a/bin/git-clear-soft +++ b/bin/git-clear-soft @@ -2,6 +2,6 @@ echo -n "Sure? - This command may delete files that cannot be recovered. Files and directories in .gitignore will be preserved [y/N]: " read -r answer -if [ "$answer" == "y" ] +if [ "$answer" = "y" ] then git clean -d -f && git reset --hard fi diff --git a/bin/git-ignore b/bin/git-ignore index e380a2e..34a1791 100755 --- a/bin/git-ignore +++ b/bin/git-ignore @@ -2,7 +2,7 @@ GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) -function show_contents { +show_contents() { local file="${2/#~/$HOME}" if [ -f "$file" ]; then echo "$1 gitignore: $2" && cat "$file" @@ -11,7 +11,7 @@ function show_contents { fi } -function cd_to_git_root { +cd_to_git_root() { local error_level="$1" if ! git rev-parse --git-dir &>/dev/null; then @@ -29,7 +29,7 @@ function cd_to_git_root { fi } -function global_ignore() { +global_ignore() { if ! git config --global core.excludesFile 2>/dev/null; then if [ -f "$HOME/.gitignore" ]; then echo "$HOME/.gitignore" @@ -39,11 +39,11 @@ function global_ignore() { fi } -function show_global { +show_global() { show_contents Global "$(global_ignore)" } -function add_global { +add_global() { local global_gitignore global_gitignore="$(global_ignore)" if [ -z "$global_gitignore" ]; then @@ -56,28 +56,28 @@ function add_global { fi } -function show_local { +show_local() { cd_to_git_root --warn show_contents Local .gitignore } -function add_local { +add_local() { cd_to_git_root --warn add_patterns .gitignore "$@" } -function show_private { +show_private() { cd_to_git_root --error show_contents Private "${GIT_DIR}/info/exclude" } -function add_private { +add_private() { cd_to_git_root --error test -d "${GIT_DIR}/info" || mkdir -p "${GIT_DIR}/info" add_patterns "${GIT_DIR}/info/exclude" "$@" } -function add_patterns { +add_patterns() { echo "Adding pattern(s) to: $1" local file="${1/#~/$HOME}" dir_name=$(dirname "$file") diff --git a/bin/git-merge-into b/bin/git-merge-into index 0561a8d..e547555 100755 --- a/bin/git-merge-into +++ b/bin/git-merge-into @@ -18,7 +18,7 @@ then git stash fi -if [ "${!#}" == '--ff-only' ]; then +if [ "${!#}" = '--ff-only' ]; then case $# in 2 ) # dest --ff git push "$(git rev-parse --show-toplevel)" "$cur_branch":"$1";; diff --git a/bin/git-paste b/bin/git-paste index 33643dd..173367c 100755 --- a/bin/git-paste +++ b/bin/git-paste @@ -2,7 +2,7 @@ set -e set -o pipefail -if ! command -v pastebinit >/dev/null 2>&1; then +if ! command -v pastebinit &>/dev/null; then echo >&2 "To run 'git paste', you need to install pastebinit in your system" exit 1 fi diff --git a/bin/git-psykorebase b/bin/git-psykorebase index b3e1a7c..a5e7577 100755 --- a/bin/git-psykorebase +++ b/bin/git-psykorebase @@ -5,7 +5,7 @@ SECONDARY_BRANCH="" FF="--ff" CONTINUE="no" -function current_branch() { +current_branch() { git rev-parse --abbrev-ref HEAD } diff --git a/bin/git-pull-request b/bin/git-pull-request index 3bd2060..e31de4f 100755 --- a/bin/git-pull-request +++ b/bin/git-pull-request @@ -43,7 +43,7 @@ if [ -z "$remote" ]; then echo 'no upstream found, push to origin as default' remote="origin" fi -[ "$remote" == "." ] && abort "the upstream should be a remote branch." +[ "$remote" = "." ] && abort "the upstream should be a remote branch." # make sure it's pushed diff --git a/bin/git-rebase-patch b/bin/git-rebase-patch index 395304e..521d5fd 100755 --- a/bin/git-rebase-patch +++ b/bin/git-rebase-patch @@ -30,7 +30,7 @@ do GIT_INDEX_FILE=$index git read-tree "$rev" # Try to apply the patch. - GIT_INDEX_FILE=$index git apply --cached "$1" >/dev/null 2>&1 + GIT_INDEX_FILE=$index git apply --cached "$1" &>/dev/null patch_failed=$? # Do it again, but show the error, if the problem is the patch itself. diff --git a/bin/git-release b/bin/git-release index 676495d..8df102b 100755 --- a/bin/git-release +++ b/bin/git-release @@ -119,7 +119,7 @@ if test $# -gt 0; then fi declare -a sign_args - if [ "$sign" == true ]; then + if [ "$sign" = true ]; then sign_args=("-s") fi diff --git a/bin/git-setup b/bin/git-setup index d1319ec..2a8c82d 100755 --- a/bin/git-setup +++ b/bin/git-setup @@ -2,7 +2,7 @@ COMMIT_MESSAGE='Initial commit' -if [ "$1" == "-m" ]; then +if [ "$1" = "-m" ]; then COMMIT_MESSAGE=$2 shift; shift fi diff --git a/bin/git-standup b/bin/git-standup index c0787f5..89f8afe 100755 --- a/bin/git-standup +++ b/bin/git-standup @@ -35,7 +35,7 @@ in_git_repo=$? # Use colors, but only if connected to a terminal, and that terminal # supports them. -if command -v tput >/dev/null 2>&1; then +if command -v tput &>/dev/null; then ncolors=$(tput colors) fi if [[ -t 1 ]] && [[ -n "$ncolors" ]] && [[ "$ncolors" -ge 8 ]] ; then diff --git a/bin/git-summary b/bin/git-summary index b69a9ae..3dbe1b6 100755 --- a/bin/git-summary +++ b/bin/git-summary @@ -206,10 +206,10 @@ COLUMN_CMD_DELIMTER="¬" # Hopefully, this symbol is not used in branch names... SP="$COLUMN_CMD_DELIMTER|" print_summary_by_line() { - if [ "$OUTPUT_STYLE" == "tabular" ]; then + if [ "$OUTPUT_STYLE" = "tabular" ]; then tabular_headers="# Repo $SP Lines" echo -e "$tabular_headers\n$project $SP $(line_count "${paths[@]}")" | column -t -s "$COLUMN_CMD_DELIMTER" - elif [ "$OUTPUT_STYLE" == "oneline" ]; then + elif [ "$OUTPUT_STYLE" = "oneline" ]; then echo "$project / lines: $(line_count "${paths[@]}")" elif [ -n "$SUMMARY_BY_LINE" ]; then echo @@ -221,10 +221,10 @@ print_summary_by_line() { } print_summary() { - if [ "$OUTPUT_STYLE" == "tabular" ]; then + if [ "$OUTPUT_STYLE" = "tabular" ]; then tabular_headers="# Repo $SP Age $SP Last active $SP Active on $SP Commits $SP Uncommitted $SP Branch" echo -e "$tabular_headers\n$project $SP $(repository_age) $SP $(last_active) $SP $(active_days "$commit") days $SP $(commit_count "$commit") $SP $(uncommitted_changes_count) $SP $(current_branch_name)" | column -t -s "$COLUMN_CMD_DELIMTER" - elif [ "$OUTPUT_STYLE" == "oneline" ]; then + elif [ "$OUTPUT_STYLE" = "oneline" ]; then echo "$project / age: $(repository_age) / last active: $(last_active) / active on $(active_days "$commit") days / commits: $(commit_count "$commit") / uncommitted: $(uncommitted_changes_count) / branch: $(current_branch_name)" else echo @@ -236,7 +236,7 @@ print_summary() { echo " commits : $(commit_count "$commit")" # The file count doesn't support passing a git ref so ignore it if a ref is given - if [ "$commit" == "HEAD" ]; then + if [ "$commit" = "HEAD" ]; then echo " files : $(file_count)" fi echo " uncommitted : $(uncommitted_changes_count)" diff --git a/scripts/checkstyle.py b/scripts/checkstyle.py new file mode 100755 index 0000000..6e2d3dd --- /dev/null +++ b/scripts/checkstyle.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 +import re +import os +import argparse +from pathlib import Path +from typing import Callable, List, Dict, Any # compat + +# This file checks Bash and Shell scripts for violations not found with +# shellcheck or existing methods. You can use it in several ways: +# +# Lint all .bash, .sh, .bats files along with 'bin/asdf' and print out violations: +# $ ./scripts/checkstyle.py +# +# The former, but also fix all violations. This must be ran until there +# are zero violations since any line can have more than one violation: +# $ ./scripts/checkstyle.py --fix +# +# Lint a particular file: +# $ ./scripts/checkstyle.py ./lib/functions/installs.bash +# +# Check to ensure all regular expressions are working as intended: +# $ ./scripts/checkstyle.py --internal-test-regex + +Rule = Dict[str, Any] + +class c: + RED = '\033[91m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + BLUE = '\033[94m' + MAGENTA = '\033[95m' + CYAN = '\033[96m' + RESET = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + LINK: Callable[[str, str], str] = lambda href, text: f'\033]8;;{href}\a{text}\033]8;;\a' + +def utilGetStrs(line: Any, m: Any): + return ( + line[0:m.start('match')], + line[m.start('match'):m.end('match')], + line[m.end('match'):] + ) + +# Before: printf '%s\\n' '^w^' +# After: printf '%s\n' '^w^' +def noDoubleBackslashFixer(line: str, m: Any) -> str: + prestr, midstr, poststr = utilGetStrs(line, m) + + return f'{prestr}{midstr[1:]}{poststr}' + +# Before: $(pwd) +# After: $PWD +def noPwdCaptureFixer(line: str, m: Any) -> str: + prestr, _, poststr = utilGetStrs(line, m) + + return f'{prestr}$PWD{poststr}' + +# Before: [ a == b ] +# After: [ a = b ] +def noTestDoubleEqualsFixer(line: str, m: Any) -> str: + prestr, _, poststr = utilGetStrs(line, m) + + return f'{prestr}={poststr}' + +# Before: function fn() { ... +# After: fn() { ... +# --- +# Before: function fn { ... +# After fn() { ... +def noFunctionKeywordFixer(line: str, m: Any) -> str: + prestr, midstr, poststr = utilGetStrs(line, m) + + midstr = midstr.strip() + midstr = midstr[len('function'):] + midstr = midstr.strip() + + parenIdx = midstr.find('(') + if parenIdx != -1: + midstr = midstr[:parenIdx] + + return f'{prestr}{midstr}() {poststr}' + +# Before: >/dev/null 2>&1 +# After: &>/dev/null +# --- +# Before: 2>/dev/null 1>&2 +# After: &>/dev/null +def noVerboseRedirectionFixer(line: str, m: Any) -> str: + prestr, _, poststr = utilGetStrs(line, m) + + return f'{prestr}&>/dev/null{poststr}' + +def lintfile(file: Path, rules: List[Rule], options: Dict[str, Any]): + content_arr = file.read_text().split('\n') + + for line_i, line in enumerate(content_arr): + if 'checkstyle-ignore' in line: + continue + + for rule in rules: + should_run = False + if 'sh' in rule['fileTypes']: + if file.name.endswith('.sh'): + should_run = True + if 'bash' in rule['fileTypes']: + if file.name.endswith('.bash') or file.name.endswith('.bats') or file.name.startswith('git-'): + should_run = True + + if options['verbose']: + print(f'{str(file)}: {should_run}') + + if not should_run: + continue + + m = re.search(rule['regex'], line) + if m is not None and m.group('match') is not None: + dir = os.path.relpath(file.resolve(), Path.cwd()) + prestr = line[0:m.start('match')] + midstr = line[m.start('match'):m.end('match')] + poststr = line[m.end('match'):] + + print(f'{c.CYAN}{dir}{c.RESET}:{line_i + 1}') + print(f'{c.MAGENTA}{rule["name"]}{c.RESET}: {rule["reason"]}') + print(f'{prestr}{c.RED}{midstr}{c.RESET}{poststr}') + print() + + if options['fix']: + content_arr[line_i] = rule['fixerFn'](line, m) + + rule['found'] += 1 + + if options['fix']: + file.write_text('\n'.join(content_arr)) + +def main(): + rules: List[Rule] = [ + { + 'name': 'no-pwd-capture', + 'regex': '(?P\\$\\(pwd\\))', + 'reason': '$PWD is essentially equivalent to $(pwd) without the overhead of a subshell', + 'fileTypes': ['bash', 'sh'], + 'fixerFn': noPwdCaptureFixer, + 'testPositiveMatches': [ + '$(pwd)' + ], + 'testNegativeMatches': [ + '$PWD' + ], + }, + { + 'name': 'no-test-double-equals', + 'regex': '(?==).*?]', + 'reason': 'Disallow double equals in places where they are not necessary for consistency', + 'fileTypes': ['bash', 'sh'], + 'fixerFn': noTestDoubleEqualsFixer, + 'testPositiveMatches': [ + '[ a == b ]', + '[ "${lines[0]}" == blah ]', + ], + 'testNegativeMatches': [ + '[ a = b ]', + '[[ a = b ]]', + '[[ a == b ]]', + '[ a = b ] || [[ a == b ]]', + '[[ a = b ]] || [[ a == b ]]', + '[[ "${lines[0]}" == \'usage: \'* ]]', + '[ "${lines[0]}" = blah ]', + ], + }, + { + 'name': 'no-function-keyword', + 'regex': '^[ \\t]*(?Pfunction .*?(?:\\([ \\t]*\\))?[ \\t]*){', + 'reason': 'Only allow functions declared like `fn_name() {{ :; }}` for consistency (see ' + c.LINK('https://www.shellcheck.net/wiki/SC2113', 'ShellCheck SC2113') + ')', + 'fileTypes': ['bash', 'sh'], + 'fixerFn': noFunctionKeywordFixer, + 'testPositiveMatches': [ + 'function fn() { :; }', + 'function fn { :; }', + ], + 'testNegativeMatches': [ + 'fn() { :; }', + ], + }, + { + 'name': 'no-verbose-redirection', + 'regex': '(?P(>/dev/null 2>&1|2>/dev/null 1>&2))', + 'reason': 'Use `&>/dev/null` instead of `>/dev/null 2>&1` or `2>/dev/null 1>&2` for consistency', + 'fileTypes': ['bash'], + 'fixerFn': noVerboseRedirectionFixer, + 'testPositiveMatches': [ + 'echo woof >/dev/null 2>&1', + 'echo woof 2>/dev/null 1>&2', + ], + 'testNegativeMatches': [ + 'echo woof &>/dev/null', + 'echo woof >&/dev/null', + ], + }, + ] + [rule.update({ 'found': 0 }) for rule in rules] + + parser = argparse.ArgumentParser() + parser.add_argument('files', metavar='FILES', nargs='*') + parser.add_argument('--fix', action='store_true') + parser.add_argument('--verbose', action='store_true') + parser.add_argument('--internal-test-regex', action='store_true') + args = parser.parse_args() + + if args.internal_test_regex: + for rule in rules: + for positiveMatch in rule['testPositiveMatches']: + m: Any = re.search(rule['regex'], positiveMatch) + if m is None or m.group('match') is None: + print(f'{c.MAGENTA}{rule["name"]}{c.RESET}: Failed {c.CYAN}positive{c.RESET} test:') + print(f'=> {positiveMatch}') + print() + + for negativeMatch in rule['testNegativeMatches']: + m: Any = re.search(rule['regex'], negativeMatch) + if m is not None and m.group('match') is not None: + print(f'{c.MAGENTA}{rule["name"]}{c.RESET}: Failed {c.YELLOW}negative{c.RESET} test:') + print(f'=> {negativeMatch}') + print() + print('Done.') + return + + options = { + 'fix': args.fix, + 'verbose': args.verbose, + } + + # parse files and print matched lints + if len(args.files) > 0: + for file in args.files: + p = Path(file) + if p.is_file(): + lintfile(p, rules, options) + else: + for file in Path.cwd().glob('**/*'): + if '.git' in str(file.absolute()): + continue + + if file.is_file(): + lintfile(file, rules, options) + + # print final results + print(f'{c.UNDERLINE}TOTAL ISSUES{c.RESET}') + for rule in rules: + print(f'{c.MAGENTA}{rule["name"]}{c.RESET}: {rule["found"]}') + + grand_total = sum([rule['found'] for rule in rules]) + print(f'GRAND TOTAL: {grand_total}') + print(f'{c.BOLD}{c.YELLOW}NOTE:{c.RESET} Run "./scripts/checkstyle.py --fix" to automatically fix all issues (may need to run multiple times)') + + # exit + if grand_total == 0: + exit(0) + else: + exit(2) + +main() From 13cbdfc51e22e635939d07af86b33fca78c45411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E6=B3=BD=E8=BD=A9?= Date: Fri, 20 Jun 2025 11:12:58 +0800 Subject: [PATCH 39/62] Version 7.4.0 (#1206) Signed-off-by: spacewander --- AUTHORS | 6 +++++- History.md | 30 ++++++++++++++++++++++++++++++ bin/git-extras | 2 +- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index a53a2c8..63915c5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -40,11 +40,14 @@ Patches and Suggestions - Luke Childs - Sasha Khamkov - equt +- oikarinen - vyas - Don Harper +- Pierre Ayoub - Robin Winslow - Ross Smith II - Yi EungJun +- dependabot[bot] - grindhold - wyattscarpenter - Aggelos Orfanakos @@ -109,7 +112,6 @@ Patches and Suggestions - Wil Moore III - William Montgomery - Ye Lin Aung -- dependabot[bot] - luozexuan - roxchgt - soffolk @@ -129,6 +131,7 @@ Patches and Suggestions - Andrew Griffiths - Andrew Marcinkevičius - Andrew Starr-Bochicchio +- Andrew Sullivan Cant - Andrey Elizarov - Angel Aguilera - Antoine Beaupré @@ -165,6 +168,7 @@ Patches and Suggestions - George Crabtree - Gerrit-K - Greg Allen +- Guilhem Saurel - Guillermo Rauch - Gunnlaugur Thor Briem - Hasse Ramlev Hansen diff --git a/History.md b/History.md index 8046bf2..39e0235 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,34 @@ +7.4.0 / 2025-06-19 +================== + + * Format comparisons, functions, and redirections to be consistent (#1201) + * add git-wip and git-unwip (#669) + * chore(deps): bump bats-core/bats-action from 3.0.0 to 3.0.1 + * Mostly finish pytest to Bats conversion (#1200) + * Implement half of tests in Bats (#1187) + * Add stale bot for old PRs (#1186) + * feat(git-bulk): add new option to not follow hidden directories (#1195) + * Feat: allow git-summary showing full path of repository (#1193) + * feat(git-bulk): add new option to no follow symlinks (#1194) + * docs(git-bulk): Add zsh completion (#1190) + * fix(git-bulk): fix workspace selection when cd fails (#1197) + * fix(git-bulk): quiet find errors by default (#1196) + * fix(git-bulk): fix a bad integer expression (#1198) + * chore(deps): bump astral-sh/ruff-action from 2 to 3 (#1189) + * Fix all ShellCheck errors and add to CI (#1179) + * chore(deps): bump astral-sh/ruff-action from 1 to 2 (#1188) + * fix(ci): use poetry (#1183) + * Delete etc/test.fish (#1185) + * feat: add git-continue (#1176) + * feat: add ruff linter with ci check (#1182) + * fix(ci): missing dollar sign (#1184) + * fix(github-actions): changed files output for editorcondig-checker (#1180) + * Revert "feat: add ruff linter with ci check (#1178)" (#1181) + * feat: add ruff linter with ci check (#1178) + * Support `GITHUB_TOKEN` var for `git-fork` and `git-pull-request` (#1177) + * Bump version to 7.4.0-dev (#1175) + 7.3.0 / 2024-10-20 ================== diff --git a/bin/git-extras b/bin/git-extras index bda96a6..686ed81 100755 --- a/bin/git-extras +++ b/bin/git-extras @@ -1,6 +1,6 @@ #!/usr/bin/env bash -VERSION="7.4.0-dev" +VERSION="7.4.0" INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/main/install.sh" update() { From 215382b12d0ece199224300ec8513b658093aa8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E6=B3=BD=E8=BD=A9?= Date: Mon, 23 Jun 2025 11:08:27 +0800 Subject: [PATCH 40/62] Bump version to 7.5.0-dev (#1207) Signed-off-by: spacewander --- bin/git-extras | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/git-extras b/bin/git-extras index 686ed81..2ada627 100755 --- a/bin/git-extras +++ b/bin/git-extras @@ -1,6 +1,6 @@ #!/usr/bin/env bash -VERSION="7.4.0" +VERSION="7.5.0-dev" INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/main/install.sh" update() { From 5c9a7a2533d138360609a7a47c5bb65675e2d2bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 11:15:38 +0800 Subject: [PATCH 41/62] chore(deps): bump actions/checkout from 4 to 5 (#1209) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d5c32f..8f975f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code. - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: 'Get Changed Files' @@ -47,7 +47,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code. - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install poetry run: pip install poetry - name: Set up Python @@ -71,7 +71,7 @@ jobs: name: 'Test with Pytest' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: recursive - name: Install poetry @@ -96,7 +96,7 @@ jobs: name: 'Test with Bats' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: recursive - name: Setup Bats @@ -120,7 +120,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - name: Check out code. - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Linux Install if: matrix.platform == 'ubuntu-latest' run: sudo apt-get install -y bsdmainutils From 14586c7da045db6b100022e54278e9a49657d5bf Mon Sep 17 00:00:00 2001 From: Edwin Kofler Date: Fri, 29 Aug 2025 03:27:55 -0700 Subject: [PATCH 42/62] Fix use of `git-whatchanged` to `git-log` (#1212) --- bin/git-utimes | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/git-utimes b/bin/git-utimes index 9955a9d..f0dc1a4 100755 --- a/bin/git-utimes +++ b/bin/git-utimes @@ -34,10 +34,10 @@ fi status_opts=(--porcelain --short) # %ct: committer date, UNIX timestamp / %at: author date, UNIX timestamp -whatchanged_opts=(--format='%ct') +log_opts=(--format='%ct') if git status --help 2>&1 | grep -q -- "--no-renames"; then status_opts+=(--no-renames) - whatchanged_opts+=(--no-renames) + log_opts+=(--no-renames) fi if git status --help 2>&1 | grep -q -- "--untracked-files"; then status_opts+=(--untracked-files=no) @@ -126,6 +126,6 @@ git --no-pager status "${status_opts[@]}" . \ | cut -c 4- >"${tmpfile}" # prefix is not stripped: -git --no-pager whatchanged "${whatchanged_opts[@]}" . \ +git --no-pager log --raw --no-merges "${log_opts[@]}" . \ | awk "${awk_flags[@]}" "${awk_script}" "${tmpfile}" - \ | BASH_ENV='' bash "${bash_opts[@]}" - From cf6d40d446e73c1047a26b907e2f82f9e545c6fd Mon Sep 17 00:00:00 2001 From: Daniele Paolella <53744340+danpaolella@users.noreply.github.com> Date: Fri, 29 Aug 2025 12:29:31 +0200 Subject: [PATCH 43/62] Improvements to Bash completion (#1210) * Sort Bash completion functions * feat: add Bash completion for `git rename-branch` * feat: add Bash completion for `git rename-remote` * feat: add Bash completion for `git rename-tag` * fix: use default completion for `git rename-file` * Refactor rename completion functions common logic --- etc/bash_completion.sh | 70 +++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/etc/bash_completion.sh b/etc/bash_completion.sh index 7a6360a..3139c57 100644 --- a/etc/bash_completion.sh +++ b/etc/bash_completion.sh @@ -1,6 +1,22 @@ # shellcheck shell=bash # bash completion support for git-extras. +_git_authors(){ + __gitcomp "-l --list --no-email" +} + +_git_browse(){ + __git_complete_remote_or_refspec +} + +_git_browse_ci(){ + __git_complete_remote_or_refspec +} + +_git_brv(){ + __gitcomp "-r --reverse" +} + _git_changelog(){ local s_opts=( '-a' '-l' '-t' '-f' '-s' '-n' '-p' '-x' '-h' '?' ) local l_opts=( @@ -22,14 +38,6 @@ _git_changelog(){ __gitcomp "$merged_opts_str" } -_git_authors(){ - __gitcomp "-l --list --no-email" -} - -_git_brv(){ - __gitcomp "-r --reverse" -} - _git_coauthor(){ local oldIfs=$IFS IFS=$'\n' @@ -136,6 +144,10 @@ _git_ignore(){ esac } +_git_info(){ + __gitcomp "--color -c --no-config" +} + _git_missing(){ # Suggest all known refs __gitcomp "$(git for-each-ref --format='%(refname:short)')" @@ -158,38 +170,40 @@ _git_reauthor(){ __gitcomp "${comp}" } -_git_scp(){ - __git_complete_remote_or_refspec +__git_extras_rename(){ + if ((COMP_CWORD == 2 || COMP_CWORD == 3)); then + __gitcomp "$1" + fi } -_git_stamp(){ - __gitcomp '--replace -r' +_git_rename_branch(){ + __git_extras_rename "$(__git_heads)" +} + +_git_rename_remote(){ + __git_extras_rename "$(__git_remotes)" +} + +_git_rename_tag(){ + __git_extras_rename "$(__git_tags)" } _git_rscp(){ __git_complete_remote_or_refspec } +_git_scp(){ + __git_complete_remote_or_refspec +} + _git_squash(){ __gitcomp "$(__git_heads)" } +_git_stamp(){ + __gitcomp '--replace -r' +} + _git_undo(){ __gitcomp "--hard --soft -h -s" } - -_git_info(){ - __gitcomp "--color -c --no-config" -} - -_git_browse(){ - __git_complete_remote_or_refspec -} - -_git_browse_ci(){ - __git_complete_remote_or_refspec -} - -_git_rename_file() { - __gitcomp "-h --help" -} From b57eb8b92a5e58d25f8a2b700edec4e2bab517a4 Mon Sep 17 00:00:00 2001 From: Daniele Paolella <53744340+danpaolella@users.noreply.github.com> Date: Thu, 4 Sep 2025 04:51:48 +0200 Subject: [PATCH 44/62] Fix minor typo in `git rename-branch` man page (#1211) * docs: fix symbols in `git rename-branch` man page * Format doc page after others and rebuild artifacts --- man/git-rename-branch.1 | 23 ++++++++--------------- man/git-rename-branch.html | 16 +++++++--------- man/git-rename-branch.md | 10 +++++----- 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/man/git-rename-branch.1 b/man/git-rename-branch.1 index 8ada6b5..6129b43 100644 --- a/man/git-rename-branch.1 +++ b/man/git-rename-branch.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "GIT\-RENAME\-BRANCH" "1" "July 2019" "" "Git Extras" +.TH "GIT\-RENAME\-BRANCH" "1" "September 2025" "" "Git Extras" . .SH "NAME" \fBgit\-rename\-branch\fR \- rename local branch and push to remote @@ -10,26 +10,19 @@ \fBgit\-rename\-branch\fR . .SH "DESCRIPTION" -. -.nf - Rename local branch and push the new branch to remote . -.fi -. .SH "OPTIONS" + . -.nf - -<old\-branch> - +.P Old branch whose has to be renamed\. This is an optional parameter\. If no value is supplied then the current branch will be renamed\. - -<new\-branch> - -New branch name . -.fi +.P + +. +.P +New branch name . .SH "EXAMPLES" . diff --git a/man/git-rename-branch.html b/man/git-rename-branch.html index f757aa4..5e22b78 100644 --- a/man/git-rename-branch.html +++ b/man/git-rename-branch.html @@ -80,19 +80,17 @@

    DESCRIPTION

    -
    Rename local branch and push the new branch to remote
    -
    +

    Rename local branch and push the new branch to remote

    OPTIONS

    -
    &lt;old-branch&gt;
    +

    <old-branch>

    -Old branch whose has to be renamed. This is an optional parameter. If no value is supplied then the current branch will be renamed. +

    Old branch whose has to be renamed. This is an optional parameter. If no value is supplied then the current branch will be renamed.

    -&lt;new-branch&gt; +

    <new-branch>

    -New branch name -
    +

    New branch name

    EXAMPLES

    @@ -103,7 +101,7 @@ $ git rename-branch new-name

    AUTHOR

    -

    Written by Hozefa Jodiawalla <hozefarules@gmail.com>

    +

    Written by Hozefa Jodiawalla <hozefarules@gmail.com>

    REPORTING BUGS

    @@ -116,7 +114,7 @@ $ git rename-branch new-name
    1. -
    2. July 2019
    3. +
    4. September 2025
    5. git-rename-branch(1)
    diff --git a/man/git-rename-branch.md b/man/git-rename-branch.md index 69e2d76..a85dbc4 100644 --- a/man/git-rename-branch.md +++ b/man/git-rename-branch.md @@ -7,17 +7,17 @@ git-rename-branch(1) -- rename local branch and push to remote ## DESCRIPTION - Rename local branch and push the new branch to remote + Rename local branch and push the new branch to remote ## OPTIONS - <old-branch> + <old-branch> - Old branch whose has to be renamed. This is an optional parameter. If no value is supplied then the current branch will be renamed. + Old branch whose has to be renamed. This is an optional parameter. If no value is supplied then the current branch will be renamed. - <new-branch> + <new-branch> - New branch name + New branch name ## EXAMPLES From 4d05955bc583d433a71e808d889a5a29eff6e060 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:28:51 +0800 Subject: [PATCH 45/62] chore(deps): bump actions/setup-python from 5 to 6 (#1216) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f975f3..7d8afa4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: - name: Install poetry run: pip install poetry - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: cache: 'poetry' cache-dependency-path: "tests/pyproject.toml" @@ -77,7 +77,7 @@ jobs: - name: Install poetry run: pip install poetry - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.12' cache: 'poetry' From 1f1320f4d6aa079fb5954cc7e4329f81a15b6d12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:29:28 +0800 Subject: [PATCH 46/62] chore(deps): bump actions/setup-go from 5 to 6 (#1215) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d8afa4..7a53c5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} BEFORE_SHA: "${{ github.event.before }}" - - uses: 'actions/setup-go@v5' + - uses: 'actions/setup-go@v6' with: go-version: '1.20' - name: 'Install EditorConfig Lint' From eb34655aea91c4fcd282797d16aa46fc23cd63c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:29:50 +0800 Subject: [PATCH 47/62] chore(deps): bump actions/stale from 9 to 10 (#1214) Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v9...v10) --- updated-dependencies: - dependency-name: actions/stale dependency-version: '10' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 3c6a1d2..ff2fc42 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -11,7 +11,7 @@ jobs: runs-on: "ubuntu-latest" if: github.repository_owner == 'tj' steps: - - uses: "actions/stale@v9" + - uses: "actions/stale@v10" with: close-pr-message: "This PR was closed because it has been stalled for 365 days with no activity. Feel free to make a new PR if you wish to continue" days-before-pr-stale: 350 From daf9d4148e8a97418a1fc1b9f22d2a500bdf254d Mon Sep 17 00:00:00 2001 From: Hugo Ruiz-Mireles Date: Tue, 21 Oct 2025 20:49:27 -0700 Subject: [PATCH 48/62] Made Git Magic use `--force-with-lease` (#1218) This change simply overwrites the usage of `-f` with `--force-with-lease` in git-magic because `--force-with-lease` is a safer alternative. --- bin/git-magic | 2 +- man/git-magic.1 | 173 ++++++++++------------- man/git-magic.html | 334 ++++++++++++++++++++++----------------------- man/git-magic.md | 2 +- 4 files changed, 240 insertions(+), 271 deletions(-) diff --git a/bin/git-magic b/bin/git-magic index 8bc159a..de1dbaf 100755 --- a/bin/git-magic +++ b/bin/git-magic @@ -30,7 +30,7 @@ while getopts "m:eapfh" arg; do PUSH=true ;; f) - FORCE='-f' + FORCE='--force-with-lease' ;; h) echo "$USAGE" diff --git a/man/git-magic.1 b/man/git-magic.1 index f2b1b48..fae6717 100644 --- a/man/git-magic.1 +++ b/man/git-magic.1 @@ -1,102 +1,71 @@ -.\" generated with Ronn/v0.7.3 -.\" http://github.com/rtomayko/ronn/tree/0.7.3 -. -.TH "GIT\-MAGIC" "1" "May 2023" "" "Git Extras" -. -.SH "NAME" -\fBgit\-magic\fR \- Automate add/commit/push routines -. -.SH "SYNOPSIS" -\fBgit\-magic\fR [\-a] [\-m \fImsg\fR] [\-e] [\-p] [\-f] -. -.SH "DESCRIPTION" -Produces summary of changes for commit message from \fBgit status \-\-porcelain\fR output\. Commits staged changes with the generated commit message and opens editor to modify generated commit message optionally\. Also staging and pushing can be automated optionally\. -. -.SH "OPTIONS" -\-a -. -.P -Adds everything including untracked files\. -. -.P -\-m \fImsg\fR -. -.P -Use the given \fImsg\fR as the commit message\. If multiple \-m options are given, their values are concatenated as separate paragraphs\. Passed to git commit command\. The generated is appended to user\-given messages\. -. -.P -\-e -. -.P -This option lets you further edit the generated message\. Passed to git commit command\. -. -.P -\-p -. -.P -Runs \fBgit push\fR after commit\. -. -.P -\-f -. -.P -Adds \fB\-f\fR option to \fBgit push\fR command\. -. -.P -\-h -. -.P -Prints synopsis\. -. -.SH "EXAMPLES" -This example stages all changes then commits with automatic commit message\. -. -.IP "" 4 -. -.nf - -$ git magic \-a -[feature/magic dc2a11e] A man/git\-magic\.md - 1 file changed, 37 insertions(+) - create mode 100644 man/git\-auto\.md -# git log -Author: overengineer <54alpersaid@gmail\.com> -Date: Thu Sep 30 20:14:22 2021 +0300 - - M man/git\-magic\.md -. -.fi -. -.IP "" 0 -. -.P -\fB\-m\fR option PREPENDS generated message\. -. -.IP "" 4 -. -.nf - -$ git magic \-am "Added documentation for git magic" -[feature/magic dc2a11e] Added documentation for git magic - 1 file changed, 42 insertions(+), 0 deletions(\-) - create mode 100644 A man/git\-auto\.md -$ git log -Author: overengineer <54alpersaid@gmail\.com> -Date: Thu Sep 30 20:14:22 2021 +0300 - - Added documentation for git magic - - M man/git\-magic\.md -. -.fi -. -.IP "" 0 -. -.SH "AUTHOR" -Written by Alper S\. Soylu <54alpersaid@gmail\.com> -. -.SH "REPORTING BUGS" -<\fIhttps://github\.com/tj/git\-extras/issues\fR> -. -.SH "SEE ALSO" -<\fIhttps://github\.com/tj/git\-extras\fR> +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "GIT\-MAGIC" "1" "September 2025" "" "Git Extras" +.SH "NAME" +\fBgit\-magic\fR \- Automate add/commit/push routines +.SH "SYNOPSIS" +\fBgit\-magic\fR [\-a] [\-m \fImsg\fR] [\-e] [\-p] [\-f] +.SH "DESCRIPTION" +Produces summary of changes for commit message from \fBgit status \-\-porcelain\fR output\. Commits staged changes with the generated commit message and opens editor to modify generated commit message optionally\. Also staging and pushing can be automated optionally\. +.SH "OPTIONS" +\-a +.P +Adds everything including untracked files\. +.P +\-m \fImsg\fR +.P +Use the given \fImsg\fR as the commit message\. If multiple \-m options are given, their values are concatenated as separate paragraphs\. Passed to git commit command\. The generated is appended to user\-given messages\. +.P +\-e +.P +This option lets you further edit the generated message\. Passed to git commit command\. +.P +\-p +.P +Runs \fBgit push\fR after commit\. +.P +\-f +.P +Adds \fB\-\-force\-with\-lease\fR option to \fBgit push\fR command for safer force pushing\. +.P +\-h +.P +Prints synopsis\. +.SH "EXAMPLES" +This example stages all changes then commits with automatic commit message\. +.IP "" 4 +.nf +$ git magic \-a +[feature/magic dc2a11e] A man/git\-magic\.md + 1 file changed, 37 insertions(+) + create mode 100644 man/git\-auto\.md +# git log +Author: overengineer <54alpersaid@gmail\.com> +Date: Thu Sep 30 20:14:22 2021 +0300 + + M man/git\-magic\.md +.fi +.IP "" 0 +.P +\fB\-m\fR option PREPENDS generated message\. +.IP "" 4 +.nf +$ git magic \-am "Added documentation for git magic" +[feature/magic dc2a11e] Added documentation for git magic + 1 file changed, 42 insertions(+), 0 deletions(\-) + create mode 100644 A man/git\-auto\.md +$ git log +Author: overengineer <54alpersaid@gmail\.com> +Date: Thu Sep 30 20:14:22 2021 +0300 + + Added documentation for git magic + + M man/git\-magic\.md +.fi +.IP "" 0 +.SH "AUTHOR" +Written by Alper S\. Soylu \fI54alpersaid@gmail\.com\fR +.SH "REPORTING BUGS" +<\fIhttps://github\.com/tj/git\-extras/issues\fR> +.SH "SEE ALSO" +<\fIhttps://github\.com/tj/git\-extras\fR> diff --git a/man/git-magic.html b/man/git-magic.html index 90d668e..a9506e7 100644 --- a/man/git-magic.html +++ b/man/git-magic.html @@ -1,167 +1,167 @@ - - - - - - git-magic(1) - Automate add/commit/push routines - - - - -
    - - - -
      -
    1. git-magic(1)
    2. -
    3. Git Extras
    4. -
    5. git-magic(1)
    6. -
    - -

    NAME

    -

    - git-magic - Automate add/commit/push routines -

    - -

    SYNOPSIS

    - -

    git-magic [-a] [-m msg] [-e] [-p] [-f]

    - -

    DESCRIPTION

    - -

    Produces summary of changes for commit message from git status --porcelain output. -Commits staged changes with the generated commit message and -opens editor to modify generated commit message optionally. -Also staging and pushing can be automated optionally.

    - -

    OPTIONS

    - -

    -a

    - -

    Adds everything including untracked files.

    - -

    -m msg

    - -

    Use the given msg as the commit message. If multiple -m options are given, their values are concatenated as separate paragraphs. -Passed to git commit command. The generated is appended to user-given messages.

    - -

    -e

    - -

    This option lets you further edit the generated message. -Passed to git commit command.

    - -

    -p

    - -

    Runs git push after commit.

    - -

    -f

    - -

    Adds -f option to git push command.

    - -

    -h

    - -

    Prints synopsis.

    - -

    EXAMPLES

    - -

    This example stages all changes then commits with automatic commit message.

    - -
    $ git magic -a
    -[feature/magic dc2a11e] A  man/git-magic.md
    - 1 file changed, 37 insertions(+)
    - create mode 100644 man/git-auto.md
    -# git log
    -Author: overengineer <54alpersaid@gmail.com>
    -Date:   Thu Sep 30 20:14:22 2021 +0300
    -
    -    M  man/git-magic.md
    -
    - -

    -m option PREPENDS generated message.

    - -
    $ git magic -am "Added documentation for git magic"
    -[feature/magic dc2a11e] Added documentation for git magic
    - 1 file changed, 42 insertions(+), 0 deletions(-)
    - create mode 100644 A man/git-auto.md
    -$ git log
    -Author: overengineer <54alpersaid@gmail.com>
    -Date:   Thu Sep 30 20:14:22 2021 +0300
    -
    -    Added documentation for git magic
    -
    -    M  man/git-magic.md
    -
    - -

    AUTHOR

    - -

    Written by Alper S. Soylu <54alpersaid@gmail.com>

    - -

    REPORTING BUGS

    - -

    <https://github.com/tj/git-extras/issues>

    - -

    SEE ALSO

    - -

    <https://github.com/tj/git-extras>

    - - -
      -
    1. -
    2. May 2023
    3. -
    4. git-magic(1)
    5. -
    - -
    - - + + + + + + git-magic(1) - Automate add/commit/push routines + + + + +
    + + + +
      +
    1. git-magic(1)
    2. +
    3. Git Extras
    4. +
    5. git-magic(1)
    6. +
    + + + +

    NAME

    +

    + git-magic - Automate add/commit/push routines +

    +

    SYNOPSIS

    + +

    git-magic [-a] [-m msg] [-e] [-p] [-f]

    + +

    DESCRIPTION

    + +

    Produces summary of changes for commit message from git status --porcelain output. +Commits staged changes with the generated commit message and +opens editor to modify generated commit message optionally. +Also staging and pushing can be automated optionally.

    + +

    OPTIONS

    + +

    -a

    + +

    Adds everything including untracked files.

    + +

    -m msg

    + +

    Use the given msg as the commit message. If multiple -m options are given, their values are concatenated as separate paragraphs. +Passed to git commit command. The generated is appended to user-given messages.

    + +

    -e

    + +

    This option lets you further edit the generated message. +Passed to git commit command.

    + +

    -p

    + +

    Runs git push after commit.

    + +

    -f

    + +

    Adds --force-with-lease option to git push command for safer force pushing.

    + +

    -h

    + +

    Prints synopsis.

    + +

    EXAMPLES

    + +

    This example stages all changes then commits with automatic commit message.

    + +
    $ git magic -a
    +[feature/magic dc2a11e] A  man/git-magic.md
    + 1 file changed, 37 insertions(+)
    + create mode 100644 man/git-auto.md
    +# git log
    +Author: overengineer <54alpersaid@gmail.com>
    +Date:   Thu Sep 30 20:14:22 2021 +0300
    +
    +    M  man/git-magic.md
    +
    + +

    -m option PREPENDS generated message.

    + +
    $ git magic -am "Added documentation for git magic"
    +[feature/magic dc2a11e] Added documentation for git magic
    + 1 file changed, 42 insertions(+), 0 deletions(-)
    + create mode 100644 A man/git-auto.md
    +$ git log
    +Author: overengineer <54alpersaid@gmail.com>
    +Date:   Thu Sep 30 20:14:22 2021 +0300
    +
    +    Added documentation for git magic
    +    
    +    M  man/git-magic.md
    +
    + +

    AUTHOR

    + +

    Written by Alper S. Soylu 54alpersaid@gmail.com

    + +

    REPORTING BUGS

    + +

    <https://github.com/tj/git-extras/issues>

    + +

    SEE ALSO

    + +

    <https://github.com/tj/git-extras>

    + +
      +
    1. +
    2. September 2025
    3. +
    4. git-magic(1)
    5. +
    + +
    + + diff --git a/man/git-magic.md b/man/git-magic.md index 8da5878..d640d66 100644 --- a/man/git-magic.md +++ b/man/git-magic.md @@ -34,7 +34,7 @@ Runs `git push` after commit. -f -Adds `-f` option to `git push` command. +Adds `--force-with-lease` option to `git push` command for safer force pushing. -h From f5c48e484281374f0276f82baef8eaaaae1e59ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:42:06 +0800 Subject: [PATCH 49/62] chore(deps): bump actions/checkout from 5 to 6 (#1219) Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a53c5b..d6f419c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code. - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: 'Get Changed Files' @@ -47,7 +47,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code. - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install poetry run: pip install poetry - name: Set up Python @@ -71,7 +71,7 @@ jobs: name: 'Test with Pytest' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: submodules: recursive - name: Install poetry @@ -96,7 +96,7 @@ jobs: name: 'Test with Bats' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: submodules: recursive - name: Setup Bats @@ -120,7 +120,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - name: Check out code. - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Linux Install if: matrix.platform == 'ubuntu-latest' run: sudo apt-get install -y bsdmainutils From 6f4cf0c1bb23b39d58c9ff638e97269c31544fa8 Mon Sep 17 00:00:00 2001 From: johnpyp Date: Thu, 18 Dec 2025 15:40:24 -0500 Subject: [PATCH 50/62] feat: `delete-branch` multiple unique branch names completions (#1221) --- etc/bash_completion.sh | 9 ++++++++- etc/git-extras-completion.zsh | 12 ++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/etc/bash_completion.sh b/etc/bash_completion.sh index 3139c57..fa561ef 100644 --- a/etc/bash_completion.sh +++ b/etc/bash_completion.sh @@ -1,6 +1,13 @@ # shellcheck shell=bash # bash completion support for git-extras. +__gitex_heads_unique() { + local branch specified=("${COMP_WORDS[@]:2}") + for branch in $(__git_heads); do + [[ " ${specified[*]} " == *" $branch "* ]] || printf '%s\n' "$branch" + done +} + _git_authors(){ __gitcomp "-l --list --no-email" } @@ -84,7 +91,7 @@ __git_cp(){ } _git_delete_branch(){ - __gitcomp "$(__git_heads)" + __gitcomp "$(__gitex_heads_unique)" } _git_delete_squashed_branches(){ diff --git a/etc/git-extras-completion.zsh b/etc/git-extras-completion.zsh index 13957f0..0ab03d2 100644 --- a/etc/git-extras-completion.zsh +++ b/etc/git-extras-completion.zsh @@ -72,6 +72,15 @@ __gitex_branch_names() { _wanted branch-names expl branch-name compadd $* - $branch_names } +__gitex_branch_names_unique() { + local expl + declare -a branch_names already_specified + branch_names=(${${(f)"$(_call_program branchrefs git for-each-ref --format='"%(refname)"' refs/heads 2>/dev/null)"}#refs/heads/}) + __gitex_command_successful || return + already_specified=(${words[2,-1]}) + _wanted branch-names expl branch-name compadd -F already_specified $* - $branch_names +} + __gitex_specific_branch_names() { local expl declare -a branch_names @@ -196,8 +205,7 @@ _git-create-branch() { } _git-delete-branch() { - _arguments \ - ':branch-name:__gitex_branch_names' + __gitex_branch_names_unique } _git-delete-squashed-branches() { From b9bd309a87ddacbc4baef3954255f30d20420f80 Mon Sep 17 00:00:00 2001 From: John Bachir Date: Mon, 19 Jan 2026 16:15:42 -0800 Subject: [PATCH 51/62] Improve `git-repl` prompt (#1224) * repl config ideas * documentation * Update bin/git-repl Co-authored-by: Edwin Kofler * separator * man page and web docs * always use dir for project name --------- Co-authored-by: Edwin Kofler --- Commands.md | 17 +++++++++++++++++ bin/git-repl | 15 ++++++++++++++- man/git-repl.1 | 16 +++++++++++++--- man/git-repl.html | 22 +++++++++++++++++++--- man/git-repl.md | 15 +++++++++++++++ 5 files changed, 78 insertions(+), 7 deletions(-) diff --git a/Commands.md b/Commands.md index fa59614..1b04250 100644 --- a/Commands.md +++ b/Commands.md @@ -392,6 +392,23 @@ Type `exit`, `quit`, or `q` to end the repl session. Any arguments to git repl will be taken as the first command to execute in the repl. +You can configure which character is used at the end of the prompt: (default `>`): + +```bash +git config --global git-extras.repl.prompt-character "±" +``` + +You can specify the prefix for the prompt (default `git`): +```bash +git config --global git-extras.repl.prefix "" +``` + +You can have the name of the current git repo shown in the prompt (default `false`): + +```bash +git config --global git-extras.repl.show-project-name "true" +``` + ```bash $ git repl git version 2.34.1 diff --git a/bin/git-repl b/bin/git-repl index a706de3..b95d9d2 100755 --- a/bin/git-repl +++ b/bin/git-repl @@ -19,7 +19,20 @@ while true; do else es_string="" fi - prompt="git$cur_string$es_string> " + prompt_character=$(git config --get --default '>' git-extras.repl.prompt-character) + + prefix=$(git config --get --default 'git' git-extras.repl.prefix) + + show_project_name=$(git config --get --default 'false' git-extras.repl.show-project-name) + if [[ "$show_project_name" == "true" ]]; then + project_name=" $(basename "$(git rev-parse --show-toplevel)" .git)" + else + project_name="" + fi + + prompt_base="$prefix$project_name$cur_string$es_string$prompt_character" + prompt_base_stripped=$(echo "$prompt_base" | awk '{$1=$1};1') + prompt="$prompt_base_stripped " # Use arguments as a command if any are provided. if [ $# -ne 0 ]; then diff --git a/man/git-repl.1 b/man/git-repl.1 index b3721f4..162b0de 100644 --- a/man/git-repl.1 +++ b/man/git-repl.1 @@ -1,6 +1,6 @@ -.\" generated with Ronn-NG/v0.9.1 -.\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "GIT\-REPL" "1" "September 2024" "" "Git Extras" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "GIT\-REPL" "1" "January 2026" "" "Git Extras" .SH "NAME" \fBgit\-repl\fR \- git read\-eval\-print\-loop .SH "SYNOPSIS" @@ -31,6 +31,16 @@ Equivalent of 'git ls\-files'\. exit|quit|q .P Ends the repl session\. +.SH "CONFIGURATION" +You can configure which character is used at the end of the prompt (default \fB>\fR): +.P +\fBgit config \-\-global git\-extras\.repl\.prompt\-character "±"\fR +.P +You can specify the prefix for the prompt (default \fBgit\fR): \fBgit config \-\-global git\-extras\.repl\.prefix ""\fR +.P +You can have the name of the current git repo shown in the prompt (default \fBfalse\fR): +.P +\fBgit config \-\-global git\-extras\.repl\.show\-project\-name "true"\fR .SH "EXAMPLES" .nf $ git repl diff --git a/man/git-repl.html b/man/git-repl.html index 3f9750e..d67ba2c 100644 --- a/man/git-repl.html +++ b/man/git-repl.html @@ -1,8 +1,8 @@ - - + + git-repl(1) - git read-eval-print-loop