├── .editorconfig ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Changes ├── INSTALL ├── Makefile.PL ├── README.md ├── bin ├── git-got ├── got └── got-complete ├── dist.ini ├── lib └── App │ ├── GitGot.pm │ └── GitGot │ ├── Command.pm │ ├── Command │ ├── add.pm │ ├── chdir.pm │ ├── checkout.pm │ ├── clone.pm │ ├── do.pm │ ├── fetch.pm │ ├── fork.pm │ ├── gc.pm │ ├── lib.pm │ ├── list.pm │ ├── milk.pm │ ├── move.pm │ ├── mux.pm │ ├── push.pm │ ├── readd.pm │ ├── remove.pm │ ├── status.pm │ ├── tag.pm │ ├── that.pm │ ├── this.pm │ ├── update.pm │ └── update_status.pm │ ├── Outputter.pm │ ├── Outputter │ ├── dark.pm │ └── light.pm │ ├── Repo.pm │ ├── Repo │ └── Git.pm │ ├── Repositories.pm │ └── Types.pm ├── releases ├── App-GitGot-0.2.tar.gz ├── App-GitGot-0.3.tar.gz ├── App-GitGot-0.4.tar.gz ├── App-GitGot-0.5.tar.gz ├── App-GitGot-0.6.tar.gz ├── App-GitGot-0.7.tar.gz ├── App-GitGot-0.8.tar.gz ├── App-GitGot-0.9.1.tar.gz ├── App-GitGot-0.9.2.tar.gz ├── App-GitGot-0.9.tar.gz ├── App-GitGot-1.0.tar.gz ├── App-GitGot-1.01.tar.gz ├── App-GitGot-1.02.tar.gz ├── App-GitGot-1.03.tar.gz ├── App-GitGot-1.04.tar.gz ├── App-GitGot-1.05.tar.gz ├── App-GitGot-1.06.tar.gz ├── App-GitGot-1.07.tar.gz ├── App-GitGot-1.08.tar.gz ├── App-GitGot-1.09.tar.gz ├── App-GitGot-1.10.tar.gz ├── App-GitGot-1.11.tar.gz ├── App-GitGot-1.12.tar.gz ├── App-GitGot-1.13.tar.gz ├── App-GitGot-1.14.tar.gz ├── App-GitGot-1.15.tar.gz ├── App-GitGot-1.16.tar.gz ├── App-GitGot-1.17.tar.gz ├── App-GitGot-1.18.tar.gz ├── App-GitGot-1.19.tar.gz ├── App-GitGot-1.20.tar.gz ├── App-GitGot-1.21.tar.gz ├── App-GitGot-1.22.tar.gz ├── App-GitGot-1.23-TRIAL.tar.gz ├── App-GitGot-1.24.tar.gz ├── App-GitGot-1.25.tar.gz ├── App-GitGot-1.26-TRIAL.tar.gz ├── App-GitGot-1.27-TRIAL.tar.gz ├── App-GitGot-1.28-TRIAL.tar.gz ├── App-GitGot-1.29-TRIAL.tar.gz ├── App-GitGot-1.30.tar.gz ├── App-GitGot-1.31-TRIAL.tar.gz ├── App-GitGot-1.32-TRIAL.tar.gz ├── App-GitGot-1.330.tar.gz ├── App-GitGot-1.331-TRIAL.tar.gz ├── App-GitGot-1.332.tar.gz ├── App-GitGot-1.333.tar.gz ├── App-GitGot-1.334.tar.gz ├── App-GitGot-1.335.tar.gz ├── App-GitGot-1.336.tar.gz ├── App-GitGot-1.337.tar.gz ├── App-GitGot-1.338.tar.gz └── App-GitGot-1.339.tar.gz └── t ├── 01-run.t ├── 02-add.t ├── 03-chdir.t ├── 04-clone.t ├── 05-fork.t ├── 06-list.t ├── 07-remove.t ├── 08-status.t ├── 09-update.t ├── 10-gc.t ├── 11-push.t ├── 12-fetch.t ├── 13-do.t ├── 14-move.t ├── 15-tags.t ├── 16-checkout.t └── lib └── Test ├── App └── GitGot │ ├── Repo.pm │ └── Repo │ └── Git.pm └── BASE.pm /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | extlib 3 | App-GitGot-* 4 | !releases/*.tar.gz 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | perl: 3 | - "5.14" 4 | - "5.16" 5 | - "5.18" 6 | - "5.20" 7 | matrix: 8 | include: 9 | - perl: 5.20 10 | env: COVERAGE=1 11 | before_install: 12 | - eval $(curl https://travis-perl.github.io/init) --auto 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to App::GitGot 2 | 3 | We appreciate your desire to contribute to GitGot. 4 | 5 | Please note, App::GitGot is stable, mature software that many people 6 | depend on for their day-to-day workflows. Any contribution needs to 7 | either fix an outstanding bug, or to add a useful new feature to the 8 | project. In particular, changes that are "lateral upgrades" of 9 | dependencies are unlikely to be accepted. Changes that alter existing 10 | interfaces will _NOT_ be accepted. 11 | 12 | The ideal workflow is to open a new issue proposing your feature, or 13 | describing the bug you'd like to fix, along with a rough outline of 14 | how you propose to implement your change. Doing that prior to starting 15 | to code will likely save time and frustration for all. 16 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Changes file for {{$dist->name}} 2 | 3 | {{$NEXT}} 4 | 5 | 1.339 2020-10-29 20:36:17-07:00 America/Los_Angeles 6 | 7 | * s/main/master/g in tests and documentation [#74] (genehack) 8 | 9 | 1.338 2020-10-26 18:46:10-07:00 America/Los_Angeles 10 | 11 | * switch JSON backend to JSON::MaybeXS (Karen Etheridge) 12 | * s/master/main/g in tests and documentation [#74] (genehack) 13 | 14 | 1.337 2020-04-25 15:33:17-07:00 America/Los_Angeles 15 | 16 | * Add 'readd' command to update repo entries in ~/.gitgot based on 17 | what is in working trees (genehack) 18 | * Add .editorconfig (genehack) 19 | * Add `--recursive` option to `clone` command (genehack, 20 | reimplementing yanick's code from #69) [#68] 21 | * switch from File::HomeDir to File::HomeDir::Tiny, for fewer 22 | dependencies (Karen Etheridge) [#70] 23 | 24 | 1.336 2018-04-13 15:38:00-07:00 America/Los_Angeles 25 | 26 | * Add 'git-got' binary to allow running as `git got` (genehack) 27 | 28 | 1.335 2018-03-19 21:10:19-07:00 America/Los_Angeles 29 | 30 | * Don't let the user pick a directory that already exists for 31 | got-clone; [#66] (Yanick) 32 | 33 | 1.334 2017-11-05 11:55:34-08:00 America/Los_Angeles 34 | * switch from using List::AllUtils to List::Util (Karen Etheridge) 35 | 36 | * Add `got milk` subcommand (Lucy Wyman) 37 | 38 | * Tweak detection of clean repos since git 2.15.0 changed the output. 39 | 40 | 1.333 2016-04-22 09:47:11-07:00 America/Los_Angeles 41 | 42 | * Add `--show-branch` option to `update-status` too 43 | 44 | 1.332 2016-04-17 13:53:37-04:00 America/New_York 45 | 46 | * Fix Path::Tiny dep in got-complete. Thanks @perlpunk [#58] 47 | * Add `--show-branch` option for `got status` [@perlpunk] [#59] 48 | 49 | 1.331 2016-01-03 20:30:14-08:00 America/Los_Angeles (TRIAL RELEASE) 50 | 51 | * Depend on Path::Tiny 0.072. Thx to SawyerX for pointing out this issue. 52 | 53 | 1.330 2015-04-14 21:27:19-07:00 America/Los_Angeles 54 | 55 | * No changes from last trial release. 56 | * No new features since last stable release. 57 | 58 | 1.32 2015-04-10 11:23:50-07:00 America/Los_Angeles (TRIAL RELEASE) 59 | 60 | * Update a number of tests to pass under Windows 61 | * Replace ENV{HOME} with File::HomeDir everywhere 62 | * Depend on a version of Git::Wrapper that doesn't use File::pushd 63 | 64 | 1.31 2015-04-05 21:42:56-07:00 America/Los_Angeles (TRIAL RELEASE) 65 | 66 | * Tweak some test code to maybe not blow up on Windows (John 67 | Anderson) 68 | * Set a minimum version of MooX::HandlesVia to avoid test failures 69 | (John Anderson) 70 | 71 | 1.30 2015-03-27 21:25:15-07:00 America/Los_Angeles 72 | 73 | * Make 'got fork' add the forked repo as remote 'upstream' unless 74 | -N/--noremoteadd option is given. (John Anderson) 75 | * See also changes for dev releases v1.25 through v1.29 76 | 77 | 1.29 2015-03-26 11:58:24-07:00 America/Los_Angeles (TRIAL RELEASE) 78 | 79 | * Docs for 'clone' and 'fork'. (John Anderson) 80 | * Be a bit more lenient in parsing GitHub URLs (John Anderson) 81 | * Make 'fork' more verbose [#51] (John Anderson) 82 | * Tweak 'clone' to be a bit better (John Anderson) 83 | 84 | 1.28 2015-03-23 22:32:27-07:00 America/Los_Angeles (TRIAL RELEASE) 85 | 86 | * Tweak missing directory error detection regex (John Anderson) 87 | 88 | 1.27 2015-03-22 19:46:53-07:00 America/Los_Angeles (TRIAL RELEASE) 89 | 90 | * Add some safety code to prevent mux from opening errything (John Anderson) 91 | * Add '-e' flag to 'tmux' subcommand [#41] (John Anderson) 92 | 93 | 1.26 2015-03-18 22:04:39-07:00 America/Los_Angeles (TRIAL RELEASE) 94 | 95 | * Lock Moo to version 2 or greater (John Anderson) 96 | * Report Git version being used in test suite (John Anderson) 97 | 98 | 1.25 2015-03-17 20:51:55-07:00 America/Los_Angeles 99 | 100 | * Fix failing tests. (Thanks Travis CI!) (John Anderson) 101 | 102 | 1.24 2015-03-16 16:56:55-07:00 America/Los_Angeles 103 | 104 | * Update copyright to 2015 (John Anderson) 105 | * Lock Net::GitHub to at least v0.74 [#47] (John Anderson) 106 | * Various testing improvements (John Anderson) 107 | * Turn off IO::Page in 'got all' (John Anderson) 108 | * Get rid of Term::Readline in favor of IO::Prompt::Simple (John Anderson) 109 | * Move towards using Path::Tiny for errything (John Anderson) 110 | * Whitespace cleanup (John Anderson) 111 | * Clean up 'use' stanzas (John Anderson) 112 | * delete 'version' command [#48] (John Anderson) 113 | 114 | 1.23 2015-03-15 01:50:42-07:00 America/Los_Angeles (TRIAL RELEASE) 115 | 116 | * Port to Moo (John Anderson) 117 | * Silence warning if PERL5LIB is not defined (Yanick Champoux) 118 | * Specify minimum version on List::AllUtils (John Anderson) 119 | 120 | 1.22 2015-02-26 07:40:05-08:00 America/Los_Angeles 121 | 122 | * Really fixed test failures (John Anderson) 123 | 124 | 1.21 2015-02-24 19:15:58-08:00 America/Los_Angeles 125 | 126 | * Fixed test failures (John Anderson) 127 | 128 | 1.20 2015-02-23 17:51:14-08:00 America/Los_Angeles 129 | 130 | * Add 'lib' subcommand (Yanick Champoux) 131 | * Add 'tag' subcommand (Yanick Champoux) 132 | * Add 'do' subcommand (Yanick Champoux) 133 | * Add '--json' option to 'got list' (Yanick Champoux) 134 | * Extend the 'add' subcommand in a number of ways: 135 | * Allow to pass more than one directory as arguments. 136 | * Add '--recursive', to hunt down git repos under a main 137 | directory. 138 | * Make '--tag X' set the default tag(s) to X 139 | * If there's only one remote, don't ask, use that one. 140 | * Instead of asking the user to type the remote URI, give the 141 | choice between the remote name/URL pairs. 142 | 143 | 1.19 2014-08-29 22:07:03-07:00 America/Los_Angeles 144 | 145 | * Make 'mux' work with multiple repos instead of just one 146 | * Add 'dirty' flag to 'mux' to make it open all repos with unclear 147 | status 148 | 149 | 1.18 2014-08-12 22:03:34-05:00 America/Chicago 150 | 151 | * Documentation updates for 'fetch' and 'gc' (Dan McCracken) 152 | 153 | 1.17 2014-07-24 10:03:54-07:00 America/Los_Angeles 154 | 155 | * Modify dist.ini to pull the INSTALL file from the build back into 156 | the repo (addresses #28) 157 | 158 | 1.16 2014-07-23 16:19:42-07:00 America/Los_Angeles 159 | 160 | * Load Net::GitHub on the fly in the 'fork' command. Make it an 161 | optional dep. Provide nice error message if it is not installed 162 | when the 'fork' command is used. 163 | 164 | 1.15 2014-05-08 21:19:33-07:00 America/Los_Angeles 165 | 166 | * Fix stupid bugs because File::Slurp::Tiny does things _just_ 167 | slightly differently on some versions of Perl because we don't get 168 | to have nice things ever. 169 | 170 | 1.14 2014-05-08 18:06:22-07:00 America/Los_Angeles 171 | 172 | * Move from File::Slurp to File::Slurp::Tiny (#25) 173 | 174 | 1.13 2014-04-26 10:40:00-07:00 America/Los_Angeles 175 | 176 | * __sigh__ 177 | 178 | 1.12 2014-04-22 20:54:41-07:00 America/Los_Angeles 179 | 180 | 1.11 2014-04-22 18:53:24-07:00 America/Los_Angeles 181 | 182 | * Add 'fetch' subcommand (Rolando Pereira) 183 | 184 | 1.10 2013-12-15 20:28:02 America/Los_Angeles 185 | 186 | * Add 'that' subcommand; refactor 'this' (Mike Greb) 187 | * Add '-s' (session) support to 'mux' (Chris Prather) 188 | 189 | 1.09 2013-10-06 11:01:46 America/New_York 190 | 191 | * Add --skip-tags for skipping repos with given tag(s) (Mike Greb) 192 | 193 | 1.08 2013-07-04 14:37:24 America/Los_Angeles 194 | 195 | * Remove given/when constructs (which will silence the warnings on newer Perls) 196 | (Thanks to Heikki Lehvaslaiho for raising this issue.) 197 | 198 | 1.07 2013-04-24 20:27:45 America/Los_Angeles 199 | 200 | [BUGFIX] 201 | 202 | * Fix mocking so that test doesn't fail on older Perls 203 | 204 | 1.06 2013-04-14 17:28:20 America/Los_Angeles 205 | 206 | [BUGFIXES] 207 | 208 | * Make 'got mux' more resilant with re-attaching (Github issue #7) (John SJ Anderson) 209 | * Update for Net::GitHub API chaneg (Github issue #4) (John SJ Anderson) 210 | 211 | [ENHANCEMENT] 212 | 213 | * Add 'got move' command (Yanick Champoux) 214 | 215 | 1.05 2013-03-15 09:29:24 America/Los_Angeles 216 | 217 | [ENHANCEMENTS] 218 | 219 | * Add 'tmux' command to open tmux window in repo (Yanick Champoux) 220 | * Add 'got-complete' bash-complete helper (Yanick Champoux) 221 | * Use 'bin/got' POD for dist abstract 222 | 223 | 1.04 2011-12-12 21:27:40 America/New_York 224 | 225 | [BUGFIX] 226 | 227 | * And another broken test... 228 | 229 | 1.03 2011-12-11 22:16:13 America/New_York 230 | 231 | [BUGFIX] 232 | 233 | * Fix broken tests; d'oh. 234 | 235 | 1.02 2011-12-07 09:36:01 America/New_York 236 | 237 | [ENHANCEMENTS] 238 | 239 | * Add 'this' command to show if current repo is known to got (Yanick) 240 | * Add '--origin' option for 'add' command (Yanick) 241 | * Convert to MouseX::App::Cmd. (Thanks to Ingy for pointing that 242 | this module exists...) 243 | 244 | 1.01 2011-07-08 21:42:32 America/New_York 245 | 246 | [BUGFIX] 247 | 248 | * Add specific dep on Git::Wrapper version. (RT#69342) 249 | 250 | 1.0 2011-05-12 16:39:16 America/New_York 251 | 252 | [ENHANCEMENTS] 253 | 254 | * Add 'update_status' command (alias 'upst') which first does 'got 255 | update' followed by 'got status'. 256 | 257 | * Add 'push' command (as suggested by Brock Wilcox and Chas Owens) 258 | to do batch pushes of selected repos. 259 | 260 | * Add 'gc' command to trigger garbage collection in managed Git repos. 261 | 262 | * Tests, tests, tests 263 | 264 | [BUG FIXES] 265 | * Tweak $ENV{LESS} to work better with 256 color terms. 266 | 267 | * Remove '_bright_' from various colors in the dark-themed 268 | outputter. 269 | 270 | OLDER: 271 | * Hadn't been maintaining Changes file. Bad coder, no cookie. 272 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | This is the Perl distribution App-GitGot. 2 | 3 | Installing App-GitGot is straightforward. 4 | 5 | ## Installation with cpanm 6 | 7 | If you have cpanm, you only need one line: 8 | 9 | % cpanm App::GitGot 10 | 11 | If it does not have permission to install modules to the current perl, cpanm 12 | will automatically set up and install to a local::lib in your home directory. 13 | See the local::lib documentation (https://metacpan.org/pod/local::lib) for 14 | details on enabling it in your environment. 15 | 16 | ## Installing with the CPAN shell 17 | 18 | Alternatively, if your CPAN shell is set up, you should just be able to do: 19 | 20 | % cpan App::GitGot 21 | 22 | ## Manual installation 23 | 24 | As a last resort, you can manually install it. Download the tarball, untar it, 25 | install configure prerequisites (see below), then build it: 26 | 27 | % perl Makefile.PL 28 | % make && make test 29 | 30 | Then install it: 31 | 32 | % make install 33 | 34 | On Windows platforms, you should use `dmake` or `nmake`, instead of `make`. 35 | 36 | If your perl is system-managed, you can create a local::lib in your home 37 | directory to install modules to. For details, see the local::lib documentation: 38 | https://metacpan.org/pod/local::lib 39 | 40 | The prerequisites of this distribution will also have to be installed manually. The 41 | prerequisites are listed in one of the files: `MYMETA.yml` or `MYMETA.json` generated 42 | by running the manual build process described above. 43 | 44 | ## Configure Prerequisites 45 | 46 | This distribution requires other modules to be installed before this 47 | distribution's installer can be run. They can be found under the 48 | "configure_requires" key of META.yml or the 49 | "{prereqs}{configure}{requires}" key of META.json. 50 | 51 | ## Other Prerequisites 52 | 53 | This distribution may require additional modules to be installed after running 54 | Makefile.PL. 55 | Look for prerequisites in the following phases: 56 | 57 | * to run make, PHASE = build 58 | * to use the module code itself, PHASE = runtime 59 | * to run tests, PHASE = test 60 | 61 | They can all be found in the "PHASE_requires" key of MYMETA.yml or the 62 | "{prereqs}{PHASE}{requires}" key of MYMETA.json. 63 | 64 | ## Documentation 65 | 66 | App-GitGot documentation is available as POD. 67 | You can run `perldoc` from a shell to read the documentation: 68 | 69 | % perldoc App::GitGot 70 | 71 | For more information on installing Perl modules via CPAN, please see: 72 | https://www.cpan.org/modules/INSTALL.html 73 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.015. 2 | use strict; 3 | use warnings; 4 | 5 | use 5.014; 6 | 7 | use ExtUtils::MakeMaker; 8 | 9 | my %WriteMakefileArgs = ( 10 | "ABSTRACT" => "A tool to make it easier to manage multiple git repositories.", 11 | "AUTHOR" => "John SJ Anderson ", 12 | "CONFIGURE_REQUIRES" => { 13 | "ExtUtils::MakeMaker" => 0 14 | }, 15 | "DISTNAME" => "App-GitGot", 16 | "EXE_FILES" => [ 17 | "bin/git-got", 18 | "bin/got", 19 | "bin/got-complete" 20 | ], 21 | "LICENSE" => "perl", 22 | "MIN_PERL_VERSION" => "5.014", 23 | "NAME" => "App::GitGot", 24 | "PREREQ_PM" => { 25 | "App::Cmd::Setup" => 0, 26 | "Capture::Tiny" => 0, 27 | "Class::Load" => 0, 28 | "Config::INI::Reader" => 0, 29 | "Cwd" => 0, 30 | "Data::Dumper" => 0, 31 | "File::Copy::Recursive" => 0, 32 | "File::HomeDir::Tiny" => 0, 33 | "File::chdir" => 0, 34 | "Git::Wrapper" => "0.042", 35 | "IO::Prompt::Simple" => 0, 36 | "List::Util" => "1.45", 37 | "Moo" => "2.000000", 38 | "MooX::HandlesVia" => "0.001008", 39 | "Path::Tiny" => "0.072", 40 | "PerlX::Maybe" => 0, 41 | "Term::ANSIColor" => 0, 42 | "Test::MockObject" => 0, 43 | "Try::Tiny" => 0, 44 | "Type::Library" => 0, 45 | "Type::Utils" => 0, 46 | "Types::Standard" => 0, 47 | "YAML" => 0, 48 | "autodie" => 0, 49 | "namespace::autoclean" => 0, 50 | "overload" => 0, 51 | "warnings" => 0 52 | }, 53 | "TEST_REQUIRES" => { 54 | "App::Cmd::Tester" => 0, 55 | "Carp" => 0, 56 | "File::Spec" => 0, 57 | "File::Temp" => 0, 58 | "IO::Handle" => 0, 59 | "IPC::Open3" => 0, 60 | "Test::Class" => 0, 61 | "Test::Class::Load" => 0, 62 | "Test::Exception" => 0, 63 | "Test::File" => 0, 64 | "Test::More" => "0.94", 65 | "lib" => 0, 66 | "parent" => 0, 67 | "strict" => 0 68 | }, 69 | "VERSION" => "1.339", 70 | "test" => { 71 | "TESTS" => "t/*.t" 72 | } 73 | ); 74 | 75 | 76 | my %FallbackPrereqs = ( 77 | "App::Cmd::Setup" => 0, 78 | "App::Cmd::Tester" => 0, 79 | "Capture::Tiny" => 0, 80 | "Carp" => 0, 81 | "Class::Load" => 0, 82 | "Config::INI::Reader" => 0, 83 | "Cwd" => 0, 84 | "Data::Dumper" => 0, 85 | "File::Copy::Recursive" => 0, 86 | "File::HomeDir::Tiny" => 0, 87 | "File::Spec" => 0, 88 | "File::Temp" => 0, 89 | "File::chdir" => 0, 90 | "Git::Wrapper" => "0.042", 91 | "IO::Handle" => 0, 92 | "IO::Prompt::Simple" => 0, 93 | "IPC::Open3" => 0, 94 | "List::Util" => "1.45", 95 | "Moo" => "2.000000", 96 | "MooX::HandlesVia" => "0.001008", 97 | "Path::Tiny" => "0.072", 98 | "PerlX::Maybe" => 0, 99 | "Term::ANSIColor" => 0, 100 | "Test::Class" => 0, 101 | "Test::Class::Load" => 0, 102 | "Test::Exception" => 0, 103 | "Test::File" => 0, 104 | "Test::MockObject" => 0, 105 | "Test::More" => "0.94", 106 | "Try::Tiny" => 0, 107 | "Type::Library" => 0, 108 | "Type::Utils" => 0, 109 | "Types::Standard" => 0, 110 | "YAML" => 0, 111 | "autodie" => 0, 112 | "lib" => 0, 113 | "namespace::autoclean" => 0, 114 | "overload" => 0, 115 | "parent" => 0, 116 | "strict" => 0, 117 | "warnings" => 0 118 | ); 119 | 120 | 121 | unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { 122 | delete $WriteMakefileArgs{TEST_REQUIRES}; 123 | delete $WriteMakefileArgs{BUILD_REQUIRES}; 124 | $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; 125 | } 126 | 127 | delete $WriteMakefileArgs{CONFIGURE_REQUIRES} 128 | unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; 129 | 130 | WriteMakefile(%WriteMakefileArgs); 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # got 2 | 3 | A tool to make it easier to manage multiple code repositories using different VCSen 4 | 5 | # VERSION [![CPAN version](https://badge.fury.io/pl/App-GitGot.svg)](http://badge.fury.io/pl/App-GitGot) 6 | 7 | # BUILD INFO 8 | 9 | [![Build Status](https://travis-ci.org/genehack/app-gitgot.svg?branch=main)](https://travis-ci.org/genehack/app-gitgot) 10 | [![Coverage Status](https://coveralls.io/repos/genehack/app-gitgot/badge.svg?branch=main)](https://coveralls.io/r/genehack/app-gitgot?branch=main) 11 | 12 | # USAGE 13 | 14 | See usage information [on CPAN](https://metacpan.org/pod/distribution/App-GitGot/bin/got) 15 | -------------------------------------------------------------------------------- /bin/git-got: -------------------------------------------------------------------------------- 1 | got -------------------------------------------------------------------------------- /bin/got: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env perl 2 | # ABSTRACT: A tool to make it easier to manage multiple code repositories using different VCSen 3 | # PODNAME: got 4 | 5 | =encoding UTF-8 6 | 7 | =head1 DESCRIPTION 8 | 9 | C is a script to make it easier to manage all the version controlled 10 | repositories you have on all the computers you use. It can operate on all, 11 | some, or just one repo at a time, to both check the status of the repo (up to 12 | date, pending changes, dirty, etc.) and sync it with any upstream remote. 13 | 14 | got also supports forking a GitHub repo and adding it to the list of managed 15 | repositories. 16 | 17 | =head1 SYNOPSIS 18 | 19 | cd some/proj/in/a/vcs 20 | 21 | got add 22 | # answer prompts for various information 23 | # or run with '-D' to take all defaults 24 | 25 | # show managed repositories 26 | got list 27 | got ls 28 | 29 | # run a command in selected repositories 30 | got do --tag perl --command "ls t/" 31 | 32 | # show managed repositories sorted by path (default = sort by name) 33 | got ls -p 34 | 35 | # remove repo #1 from the list 36 | got remove 1 37 | 38 | # remove repo named 'bar' from the list 39 | got remove bar 40 | 41 | # remove all repos tagged 'foo' without confirmation prompts 42 | got rm -f -t foo 43 | 44 | # remove repo #3 without confirmation prompts and be noisy about it 45 | got rm -f -v 3 46 | 47 | # show status (up-to-date, dirty, etc.) for all repos 48 | got status 49 | 50 | # show status for repo #3 51 | got st 3 52 | 53 | # fetch upstream for all repositories 54 | got fetch 55 | 56 | # fetch upstream for repo #3 57 | got fetch 3 58 | 59 | # update all repos with configured remotes 60 | got update 61 | 62 | # update repo named 'bar' 63 | got up bar 64 | 65 | # Note: if a repo is in the list but doesn't have a local checkout, 'got 66 | # update' will create directories as needed and do the initial check out. 67 | 68 | # Run the 'git gc' command to garbage collect in git repos 69 | got gc 70 | 71 | # spawn a subshell with working directory set to 'path' of repo #1 72 | got chdir 1 73 | 74 | # spawn a subshell with working directory set to 'path' of repo foo 75 | got cd foo 76 | 77 | # or use 'tmux' subcommand to open a new tmux window instead 78 | got tmux 1 79 | got tmux foo 80 | # N.b., 'tmux' will reuse an existing window if one is open 81 | 82 | # checkout a local working copy of a repo and add it to your list of repos. 83 | # will prompt for info on what to name repo, tags, etc. 84 | got clone 85 | 86 | # As above, but accept defaults for all options without prompting 87 | got clone -D 88 | 89 | # fork a github repo, add it to your list of repos, and check it out in 90 | # the current working directory 91 | got fork https://github.com/somebodies/repo_name 92 | 93 | # note: the default path to a repo added via 'fork' is a directory 94 | # named 'repo_name' in the current working directory 95 | 96 | # if you just want to fork without checking out a working copy: 97 | got fork --noclone https://github.com/somebodies/repo_name 98 | 99 | # finally, please note that you need a C<~/.github-identity> file set up 100 | # with your access token or your username and password in the following key-value 101 | # format: 102 | user username 103 | pass password 104 | 105 | # *OR* 106 | access_token token 107 | 108 | # note that if you specify both, the access_token value will be used 109 | 110 | # show version of got 111 | got version 112 | 113 | =head1 OPTIONS 114 | 115 | In addition to the subcommand-specific options illustrated in the SYNOPSIS, 116 | all the subcommands accept the following options: 117 | 118 | =over 4 119 | 120 | =item * C<--verbose / -v> 121 | 122 | Be more verbose about what is happening behind the scenes 123 | 124 | =item * C<--quiet / -q> 125 | 126 | Be more quiet 127 | 128 | =item * C<--tags / -t> 129 | 130 | Select all repositories that have the given tag. May be given multiple 131 | times. Multiple args are (effectively) 'and'-ed together. 132 | 133 | =item * C<--skip-tags / -T> 134 | 135 | Skip all repositories that have the given tag. May be given multiple 136 | times. Multiple args are (effectively) 'or'-ed together. May be combined with 137 | -t to select all repos with the -t tag except for those with the -T tag. 138 | 139 | =item * C<--no-color / -C> 140 | 141 | Suppress colored output 142 | 143 | =item * C<--color-scheme / -c> 144 | 145 | Specify a color scheme. Defaults to 'dark'. People using light backgrounds may 146 | want to specify "-c light". 147 | 148 | The name given to the option indicates a library to load. By default this 149 | library is assumed to be in the 'App::GitGot::Outputter::' namespace; the 150 | given scheme name will be appended to that namespace. You can load something 151 | from a different namespace by prefacing a '+'. (E.g., '-C +GitGot::pink' will 152 | attempt to load 'GitGot::pink'.) 153 | 154 | If the requested module can't be loaded, the command will exit. 155 | 156 | See COLOR SCHEMES for details on how to write your own custom color scheme. 157 | 158 | =item * repo name, repo number, range 159 | 160 | Commands may be limited to a subset of repositories by giving a combination of 161 | additional arguments, consisting of either repository names, repository 162 | numbers (as reported by the 'C' subcommand), or number ranges (e.g., C<2-4> 163 | will operate on repository numbers 2, 3, and 4). 164 | 165 | Note that if you have a repository whose name is an integer number, bad things 166 | are going probably going to happen. Don't do that. 167 | 168 | =back 169 | 170 | =head1 COLOR SCHEMES 171 | 172 | Color scheme libraries should extend C and need to 173 | define four required attributes: C, C, 174 | C, and C. Each attribute should be a 175 | read-only of type 'Str' with a default value that corresponds to a valid 176 | C color string. 177 | 178 | =head1 SEE ALSO/CREDITS 179 | 180 | =over 181 | 182 | =item L 183 | 184 | Seeing Ingy döt Net speak about AYCABTU at PPW2010 was a major factor in the 185 | development of this script -- earlier (unreleased) versions did not have any way 186 | to limit operations to a subset of managed repositories; they also didn't deal 187 | well managing output. After lifting his interface (virtually wholesale) I 188 | ended up with something that I thought was worth releasing. 189 | 190 | =item L 191 | 192 | drdrang prodded me about making the color configuration more friendly to those 193 | that weren't dark backrgound terminal people. The colors in 194 | C are based on a couple of patches that drdrang 195 | sent me. 196 | 197 | =item L 198 | 199 | =back 200 | 201 | =head2 Similar tools 202 | 203 | =over 204 | 205 | =item L 206 | 207 | Allows to check the status and exec commands on a list of 208 | repository, as well as to sync the local machine's repositories with 209 | another via rsync. Can also manage (and rsync) regular directories. 210 | 211 | =item L 212 | 213 | Allows to run the main commands (C,C,etc) in all registered 214 | repos. Advertised as working with many versioning systems. 215 | Not on CPAN nor GitHub. 216 | 217 | =back 218 | 219 | =head1 LIMITATIONS 220 | 221 | Currently git is the only supported VCS. 222 | 223 | =head1 CONTRIBUTORS 224 | 225 | =over 226 | 227 | =item Yanick Champoux 228 | 229 | =item Michael Greb 230 | 231 | =item Chris Prather 232 | 233 | =item Rolando Pereira 234 | 235 | =item Tina Müller 236 | 237 | =item Karen Etheridge 238 | 239 | =item Lucy Wyman 240 | 241 | =back 242 | 243 | =cut 244 | 245 | use 5.014; # strict, unicode_strings 246 | use warnings; 247 | 248 | use App::GitGot; 249 | App::GitGot->run; 250 | -------------------------------------------------------------------------------- /bin/got-complete: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # ABSTRACT: shell completion helper for got 3 | # PODNAME: got-complete 4 | 5 | use 5.014; ## strict, unicode_strings 6 | use warnings; 7 | 8 | use File::HomeDir::Tiny (); 9 | use Path::Tiny; 10 | use YAML qw/ LoadFile /; 11 | 12 | sub get_command_line { 13 | my $comp = substr $ENV{'COMP_LINE'}, 0, $ENV{'COMP_POINT'}; 14 | return split /[ \t]+/, $comp, -1; # if not good enough, use Text::ParseWords 15 | } 16 | 17 | sub get_project_suggestion { 18 | my $word = shift; 19 | 20 | my $configfile = path( File::HomeDir::Tiny::home() , '.gitgot' ); 21 | 22 | my $config = LoadFile( $configfile ) 23 | or die "$configfile not found\n"; 24 | 25 | return grep { 0 == index $_, $word } 26 | map { $_->{name} } 27 | @$config; 28 | } 29 | 30 | sub usage { 31 | die map "\n$_\n", ( 32 | "To use, issue the following command in bash:", 33 | "\tcomplete -C got-complete -o nospace -o default got", 34 | "You probably want to put that line in your ~/.bashrc file.\n", 35 | ); 36 | } 37 | 38 | usage() if not exists $ENV{'COMP_LINE'}; 39 | 40 | my ( $cmd, @args ) = get_command_line(); 41 | 42 | my $subcommand; 43 | $subcommand = shift @args if $cmd =~ /got$/; 44 | 45 | print "$_\n" for get_project_suggestion( $args[-1] ); 46 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = App-GitGot 2 | author = John SJ Anderson 3 | license = Perl_5 4 | copyright_holder = John SJ Anderson 5 | copyright_year = 2020 6 | 7 | [@GENEHACK] 8 | homepage = http://genehack.github.com/app-gitgot/ 9 | 10 | [Prereqs] 11 | Git::Wrapper=0.042 12 | List::Util=1.45 13 | Moo=2.000000 14 | MooX::HandlesVia=0.001008 15 | Path::Tiny=0.072 16 | 17 | [Prereqs / RuntimeRecommends] 18 | IO::Page=0 19 | JSON=0 20 | Path::Iterator::Rule=0 21 | 22 | [Prereqs / RuntimeSuggests] 23 | Net::GitHub=0.74 24 | -------------------------------------------------------------------------------- /lib/App/GitGot.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot; 2 | 3 | # ABSTRACT: A tool to make it easier to manage multiple git repositories. 4 | use 5.014; 5 | use warnings; 6 | 7 | use App::Cmd::Setup -app; 8 | 9 | =head1 SYNOPSIS 10 | 11 | See C for usage information. 12 | 13 | =cut 14 | 15 | 1; 16 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command; 2 | 3 | # ABSTRACT: Base class for App::GitGot commands 4 | use 5.014; 5 | 6 | use App::Cmd::Setup -command; 7 | use Cwd; 8 | use File::HomeDir::Tiny (); 9 | use List::Util qw/ max first /; 10 | use Path::Tiny; 11 | use Try::Tiny; 12 | use Types::Standard -types; 13 | use YAML qw/ DumpFile LoadFile /; 14 | 15 | use App::GitGot::Repo::Git; 16 | use App::GitGot::Repositories; 17 | use App::GitGot::Types -all; 18 | 19 | use Moo; 20 | use MooX::HandlesVia; 21 | use namespace::autoclean; 22 | 23 | sub opt_spec { 24 | my( $class , $app ) = @_; 25 | 26 | return ( 27 | [ 'all|a' => 'use all available repositories' ] , 28 | [ 'by_path|p' => 'if set, output will be sorted by repo path (default: sort by repo name)' ] , 29 | [ 'color_scheme|c=s' => 'name of color scheme to use' => { default => 'dark' } ] , 30 | [ 'configfile|f=s' => 'path to config file' => { default => path( File::HomeDir::Tiny::home() , '.gitgot') , required => 1 } ] , 31 | [ 'no_color|C' => 'do not use colored output' => { default => 0 } ] , 32 | [ 'quiet|q' => 'keep it down' ] , 33 | [ 'skip_tags|T=s@' => 'select repositories not tagged with these words' ] , 34 | [ 'tags|t=s@' => 'select repositories tagged with these words' ] , 35 | [ 'verbose|v' => 'bring th\' noise'] , 36 | $class->options($app) 37 | ); 38 | } 39 | 40 | sub options {} 41 | 42 | has active_repo_list => ( 43 | is => 'lazy', 44 | isa => ArrayRef[GotRepo] , 45 | handles_via => 'Array' , 46 | handles => { 47 | active_repos => 'elements' , 48 | } , 49 | ); 50 | 51 | sub _build_active_repo_list { 52 | my ( $self ) = @_; 53 | 54 | return $self->full_repo_list 55 | if $self->all or ! $self->tags and ! $self->skip_tags and ! @{ $self->args }; 56 | 57 | my $list = _expand_arg_list( $self->args ); 58 | 59 | my @repos; 60 | REPO: foreach my $repo ( $self->all_repos ) { 61 | if ( grep { $_ eq $repo->number or $_ eq $repo->name } @$list ) { 62 | push @repos, $repo; 63 | next REPO; 64 | } 65 | 66 | if ( $self->skip_tags ) { 67 | foreach my $tag ( @{ $self->skip_tags } ) { 68 | next REPO if grep { $repo->tags =~ /\b$_\b/ } $tag; 69 | } 70 | } 71 | 72 | if ( $self->tags ) { 73 | foreach my $tag ( @{ $self->tags } ) { 74 | if ( grep { $repo->tags =~ /\b$_\b/ } $tag ) { 75 | push @repos, $repo; 76 | next REPO; 77 | } 78 | } 79 | } 80 | push @repos, $repo unless $self->tags or @$list; 81 | } 82 | 83 | return \@repos; 84 | } 85 | 86 | has args => ( 87 | is => 'rwp' , 88 | isa => ArrayRef , 89 | ); 90 | 91 | has full_repo_list => ( 92 | is => 'lazy', 93 | isa => ArrayRef[GotRepo] , 94 | writer => 'set_full_repo_list' , 95 | handles_via => 'Array' , 96 | handles => { 97 | add_repo => 'push' , 98 | all_repos => 'elements' , 99 | } , 100 | ); 101 | 102 | sub _build_full_repo_list { 103 | my $self = shift; 104 | 105 | my $config = _read_config( $self->configfile ); 106 | 107 | my $repo_count = 1; 108 | 109 | my $sort_key = $self->by_path ? 'path' : 'name'; 110 | 111 | my @parsed_config; 112 | 113 | foreach my $entry ( sort { $a->{$sort_key} cmp $b->{$sort_key} } @$config ) { 114 | 115 | # a completely empty entry is okay (this will happen when there's no 116 | # config at all...) 117 | keys %$entry or next; 118 | 119 | ### FIXME unnecessarily git specific 120 | push @parsed_config , App::GitGot::Repo::Git->new({ 121 | label => ( $self->by_path ) ? $entry->{path} : $entry->{name} , 122 | entry => $entry , 123 | count => $repo_count++ , 124 | }); 125 | } 126 | 127 | return \@parsed_config; 128 | } 129 | 130 | has opt => ( 131 | is => 'rwp' , 132 | isa => Object , 133 | handles => [ qw/ 134 | all 135 | by_path 136 | color_scheme 137 | configfile 138 | no_color 139 | quiet 140 | skip_tags 141 | tags 142 | verbose 143 | / ] 144 | ); 145 | 146 | has outputter => ( 147 | is => 'lazy' , 148 | isa => GotOutputter , 149 | handles => [ 150 | 'error' , 151 | 'warning' , 152 | 'major_change' , 153 | 'minor_change' , 154 | ] , 155 | ); 156 | 157 | sub _build_outputter { 158 | my $self = shift; 159 | 160 | my $scheme = $self->color_scheme; 161 | 162 | if ( $scheme =~ /^\+/ ) { 163 | $scheme =~ s/^\+//; 164 | } 165 | else { 166 | $scheme = "App::GitGot::Outputter::$scheme" 167 | } 168 | 169 | try { 170 | eval "use $scheme"; 171 | die $@ if $@; 172 | } 173 | catch { 174 | say "Failed to load color scheme '$scheme'.\nExitting now.\n"; 175 | exit(5); 176 | }; 177 | 178 | return $scheme->new({ no_color => $self->no_color }); 179 | } 180 | 181 | sub execute { 182 | my( $self , $opt , $args ) = @_; 183 | $self->_set_args( $args ); 184 | $self->_set_opt( $opt ); 185 | 186 | # set up colored output if we page thru less 187 | # also exit pager immediately if <1 page of output 188 | $ENV{LESS} = 'RFX'; 189 | 190 | # don't catch any errors here; if this fails we just output stuff like 191 | # normal and nobody is the wiser. 192 | eval 'use IO::Page' if $self->_use_io_page; 193 | 194 | $self->_execute($opt,$args); 195 | } 196 | 197 | =method local_repo 198 | 199 | Checks to see if $CWD is inside a Git repo managed by Got, and returns the 200 | corresponding L object if it is. 201 | 202 | =cut 203 | 204 | sub local_repo { 205 | my $self = shift; 206 | 207 | my $dir = $self->_find_repo_root( getcwd() ); 208 | 209 | return first { $_->path eq $dir->absolute } $self->all_repos; 210 | } 211 | 212 | =method max_length_of_an_active_repo_label 213 | 214 | Returns the length of the longest name in the active repo list. 215 | 216 | =cut 217 | 218 | sub max_length_of_an_active_repo_label { 219 | my( $self ) = @_; 220 | 221 | my $sort_key = $self->by_path ? 'path' : 'name'; 222 | 223 | return max ( map { length $_->$sort_key } $self->active_repos); 224 | } 225 | 226 | =method prompt_yn 227 | 228 | Takes a message argument and uses it to prompt for a yes/no response. 229 | 230 | Response defaults to 'no'. 231 | 232 | =cut 233 | 234 | sub prompt_yn { 235 | my( $self , $message ) = @_; 236 | printf '%s [y/N]: ' , $message; 237 | chomp( my $response = ); 238 | return lc($response) eq 'y'; 239 | } 240 | 241 | =method search_repos 242 | 243 | Returns a L object containing all repos managed by 244 | Got. 245 | 246 | =cut 247 | 248 | sub search_repos { 249 | my $self = shift; 250 | 251 | return App::GitGot::Repositories->new( 252 | repos => [ $self->all_repos ] 253 | ); 254 | } 255 | 256 | =method write_config 257 | 258 | Dumps configuration out to disk. 259 | 260 | =cut 261 | 262 | sub write_config { 263 | my ($self) = @_; 264 | 265 | DumpFile( 266 | $self->configfile, 267 | [ 268 | sort { $a->{name} cmp $b->{name} } 269 | map { $_->in_writable_format } $self->all_repos 270 | ] , 271 | ); 272 | } 273 | 274 | sub _expand_arg_list { 275 | my $args = shift; 276 | 277 | ## no critic 278 | 279 | return [ 280 | map { 281 | s!/$!!; 282 | if (/^(\d+)-(\d+)?$/) { ( $1 .. $2 ) } 283 | else { ($_) } 284 | } @$args 285 | ]; 286 | 287 | ## use critic 288 | } 289 | 290 | sub _fetch { 291 | my( $self , @repos ) = @_; 292 | 293 | my $max_len = $self->max_length_of_an_active_repo_label; 294 | 295 | REPO: for my $repo ( @repos ) { 296 | next REPO unless $repo->repo; 297 | 298 | my $name = $repo->name; 299 | 300 | my $msg = sprintf "%3d) %-${max_len}s : ", $repo->number, $repo->label; 301 | 302 | my ( $status, $fxn ); 303 | 304 | my $repo_type = $repo->type; 305 | 306 | if ( $repo_type eq 'git' ) { $fxn = '_git_fetch' } 307 | ### FIXME elsif( $repo_type eq 'svn' ) { $fxn = 'svn_update' } 308 | else { $status = $self->error("ERROR: repo type '$_' not supported") } 309 | 310 | $status = $self->$fxn($repo) if ($fxn); 311 | 312 | next REPO if $self->quiet and !$status; 313 | 314 | say "$msg$status"; 315 | } 316 | } 317 | 318 | sub _find_repo_root { 319 | my( $self , $path ) = @_; 320 | 321 | my $dir = path( $path ); 322 | 323 | # find repo root 324 | while ( ! grep { -d and $_->basename eq '.git' } $dir->children ) { 325 | die "$path doesn't seem to be in a git directory\n" if $dir eq $dir->parent; 326 | $dir = $dir->parent; 327 | } 328 | 329 | return $dir 330 | } 331 | 332 | sub _git_clone_or_callback { 333 | my( $self , $entry , $callback ) = @_ 334 | or die "Need entry and callback"; 335 | 336 | my $msg = ''; 337 | 338 | my $path = $entry->path; 339 | 340 | if ( !-d $path ) { 341 | path($path)->mkpath; 342 | 343 | try { 344 | $entry->clone( $entry->repo , './' ); 345 | $msg .= $self->major_change('Checked out'); 346 | } 347 | catch { $msg .= $self->error('ERROR') . "\n$_" }; 348 | } 349 | elsif ( -d "$path/.git" ) { 350 | try { 351 | $msg .= $callback->($msg , $entry); 352 | } 353 | catch { $msg .= $self->error('ERROR') . "\n$_" }; 354 | } 355 | 356 | return $msg; 357 | 358 | } 359 | 360 | sub _git_fetch { 361 | my ( $self, $entry ) = @_ 362 | or die "Need entry"; 363 | 364 | $self->_git_clone_or_callback( $entry , 365 | sub { 366 | my( $msg , $entry ) = @_; 367 | 368 | my @o = $entry->fetch; 369 | 370 | # "git fetch" doesn't output anything to STDOUT only STDERR 371 | my @err = @{ $entry->_wrapper->ERR }; 372 | 373 | # If something was updated then STDERR should contain something 374 | # similar to: 375 | # 376 | # From git://example.com/link-to-repo 377 | # SHA1___..SHA1___ main -> origin/main 378 | # 379 | # So search for /^From / in STDERR to see if anything was outputed 380 | if ( grep { /^From / } @err ) { 381 | $msg .= $self->major_change('Updated'); 382 | $msg .= "\n" . join("\n",@err) unless $self->quiet; 383 | } 384 | elsif ( scalar @err == 0) { 385 | # No messages to STDERR means repo was already updated 386 | $msg .= $self->minor_change('Up to date') unless $self->quiet; 387 | } 388 | else { 389 | # Something else occured (possibly a warning) 390 | # Print STDERR and move on 391 | $msg .= $self->warning('Problem during fetch'); 392 | $msg .= "\n" . join("\n",@err) unless $self->quiet; 393 | } 394 | 395 | return $msg; 396 | } 397 | ); 398 | } 399 | 400 | sub _git_status { 401 | my ( $self, $entry ) = @_ 402 | or die "Need entry"; 403 | 404 | my( $msg , $verbose_msg ) = $self->_run_git_status( $entry ); 405 | 406 | $msg .= $self->_run_git_cherry( $entry ) 407 | if $entry->current_remote_branch; 408 | if ($self->opt->show_branch and defined $entry->current_branch) { 409 | $msg .= '[' . $entry->current_branch . ']'; 410 | } 411 | 412 | return ( $self->verbose ) ? "$msg$verbose_msg" : $msg; 413 | } 414 | 415 | sub _git_update { 416 | my ( $self, $entry ) = @_ 417 | or die "Need entry"; 418 | 419 | $self->_git_clone_or_callback( $entry , 420 | sub { 421 | my( $msg , $entry ) = @_; 422 | 423 | my @o = $entry->pull; 424 | if ( $o[0] =~ /^Already up.to.date\./ ) { 425 | $msg .= $self->minor_change('Up to date') unless $self->quiet; 426 | } 427 | else { 428 | $msg .= $self->major_change('Updated'); 429 | $msg .= "\n" . join("\n",@o) unless $self->quiet; 430 | } 431 | 432 | return $msg; 433 | } 434 | ); 435 | } 436 | 437 | sub _path_is_managed { 438 | my( $self , $path ) = @_; 439 | 440 | return unless $path; 441 | 442 | my $dir = $self->_find_repo_root( $path ); 443 | my $max_len = $self->max_length_of_an_active_repo_label; 444 | 445 | for my $repo ( $self->active_repos ) { 446 | next unless $repo->path eq $dir->absolute; 447 | 448 | my $repo_remote = ( $repo->repo and -d $repo->path ) ? $repo->repo 449 | : ( $repo->repo ) ? $repo->repo . ' (Not checked out)' 450 | : ( -d $repo->path ) ? 'NO REMOTE' 451 | : 'ERROR: No remote and no repo?!'; 452 | 453 | printf "%3d) ", $repo->number; 454 | 455 | if ( $self->quiet ) { say $repo->label } 456 | else { 457 | printf "%-${max_len}s %-4s %s\n", 458 | $repo->label, $repo->type, $repo_remote; 459 | if ( $self->verbose ) { 460 | printf " tags: %s\n" , $repo->tags if $repo->tags; 461 | } 462 | } 463 | 464 | return 1; 465 | } 466 | 467 | say "repository not in Got list"; 468 | return; 469 | } 470 | 471 | sub _read_config { 472 | my $file = shift; 473 | 474 | my $config; 475 | 476 | if ( -e $file ) { 477 | try { $config = LoadFile( $file ) } 478 | catch { say "Failed to parse config..."; exit }; 479 | } 480 | 481 | # if the config is completely empty, bootstrap _something_ 482 | return $config // [ {} ]; 483 | } 484 | 485 | sub _run_git_cherry { 486 | my( $self , $entry ) = @_; 487 | 488 | my $msg = ''; 489 | 490 | try { 491 | if ( $entry->remote ) { 492 | my $cherry = $entry->cherry; 493 | if ( $cherry > 0 ) { 494 | $msg = $self->major_change("Ahead by $cherry"); 495 | } 496 | } 497 | } 498 | catch { $msg = $self->error('ERROR') . "\n$_" }; 499 | 500 | return $msg 501 | } 502 | 503 | sub _run_git_status { 504 | my( $self , $entry ) = @_; 505 | 506 | my %types = ( 507 | indexed => 'Changes to be committed' , 508 | changed => 'Changed but not updated' , 509 | unknown => 'Untracked files' , 510 | conflict => 'Files with conflicts' , 511 | ); 512 | 513 | my( $msg , $verbose_msg ) = ('',''); 514 | 515 | try { 516 | my $status = $entry->status; 517 | if ( keys %$status ) { 518 | $msg .= $self->warning('Dirty') . ' '; 519 | } else { 520 | $msg .= $self->minor_change('OK ') unless $self->quiet; 521 | } 522 | 523 | if ( $self->verbose ) { 524 | TYPE: for my $type ( keys %types ) { 525 | my @states = $status->get( $type ) or next TYPE; 526 | $verbose_msg .= "\n** $types{$type}:\n"; 527 | for ( @states ) { 528 | $verbose_msg .= sprintf ' %-12s %s' , $_->mode , $_->from; 529 | $verbose_msg .= sprintf ' -> %s' , $_->to if $_->mode eq 'renamed'; 530 | $verbose_msg .= "\n"; 531 | } 532 | } 533 | $verbose_msg = "\n$verbose_msg" if $verbose_msg; 534 | } 535 | } 536 | catch { $msg .= $self->error('ERROR') . "\n$_" }; 537 | 538 | return( $msg , $verbose_msg ); 539 | } 540 | 541 | sub _status { 542 | my( $self , @repos ) = @_; 543 | 544 | my $max_len = $self->max_length_of_an_active_repo_label; 545 | 546 | REPO: for my $repo ( @repos ) { 547 | my $label = $repo->label; 548 | 549 | my $msg = sprintf "%3d) %-${max_len}s : ", $repo->number, $label; 550 | 551 | my ( $status, $fxn ); 552 | 553 | if ( -d $repo->path ) { 554 | my $repo_type = $repo->type; 555 | if ( $repo_type eq 'git' ) { $fxn = '_git_status' } 556 | ### FIXME elsif( $repo_type eq 'svn' ) { $fxn = 'svn_status' } 557 | else { $status = $self->error("ERROR: repo type '$repo_type' not supported") } 558 | 559 | $status = $self->$fxn($repo) if ($fxn); 560 | 561 | next REPO if $self->quiet and !$status; 562 | } 563 | elsif ( $repo->repo ) { $status = 'Not checked out' } 564 | else { $status = $self->error("ERROR: repo '$label' does not exist") } 565 | 566 | say "$msg$status"; 567 | } 568 | } 569 | 570 | sub _update { 571 | my( $self , @repos ) = @_; 572 | 573 | my $max_len = $self->max_length_of_an_active_repo_label; 574 | 575 | REPO: for my $repo ( @repos ) { 576 | next REPO unless $repo->repo; 577 | 578 | my $name = $repo->name; 579 | 580 | my $msg = sprintf "%3d) %-${max_len}s : ", $repo->number, $repo->label; 581 | 582 | my ( $status, $fxn ); 583 | 584 | my $repo_type = $repo->type; 585 | 586 | if ( $repo_type eq 'git' ) { $fxn = '_git_update' } 587 | ### FIXME elsif( $repo_type eq 'svn' ) { $fxn = 'svn_update' } 588 | else { $status = $self->error("ERROR: repo type '$_' not supported") } 589 | 590 | $status = $self->$fxn($repo) if ($fxn); 591 | 592 | next REPO if $self->quiet and !$status; 593 | 594 | say "$msg$status"; 595 | } 596 | } 597 | 598 | # override this in commands that shouldn't use IO::Page -- i.e., ones that 599 | # need to do incremental output 600 | sub _use_io_page { 1 } 601 | 602 | =for Pod::Coverage args opt options 603 | 604 | =cut 605 | 606 | 1; 607 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/add.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::add; 2 | 3 | # ABSTRACT: add a new repo to your config 4 | use 5.014; 5 | 6 | use Class::Load qw/ try_load_class /; 7 | use Config::INI::Reader; 8 | use Cwd; 9 | use IO::Prompt::Simple; 10 | use List::Util qw/ any pairmap /; 11 | use Path::Tiny; 12 | use PerlX::Maybe; 13 | use Types::Standard -types; 14 | 15 | use App::GitGot -command; 16 | use App::GitGot::Repo::Git; 17 | 18 | use Moo; 19 | extends 'App::GitGot::Command'; 20 | use namespace::autoclean; 21 | 22 | sub options { 23 | my( $class , $app ) = @_; 24 | return ( 25 | [ 'defaults|D' => 'FIXME' => { default => 0 } ] , 26 | [ 'origin|o=s' => 'FIXME' => { default => 'origin' } ] , 27 | [ 'recursive' => 'search all sub-directories for repositories' => { default => 0 } ], 28 | ); 29 | } 30 | 31 | sub _use_io_page { 0 } 32 | 33 | sub _execute { 34 | my ( $self, $opt, $args ) = @_; 35 | 36 | my @dirs = @$args; 37 | push @dirs, '.' unless @dirs; # default dir is this one 38 | 39 | if( $self->opt->recursive ) { # hunt for repos 40 | try_load_class( 'Path::Iterator::Rule' ) 41 | or die "--recursive requires module 'Path::Iterator::Rule' to be installed\n"; 42 | 43 | Path::Iterator::Rule->add_helper( 44 | is_git => sub { 45 | return sub { 46 | my $item = shift; 47 | return -d "$item/.git"; 48 | } 49 | } 50 | ); 51 | 52 | @dirs = Path::Iterator::Rule->new->dir->is_git->all(@dirs); 53 | } 54 | 55 | $self->_process_dir($_) for map { path($_)->absolute } @dirs; 56 | } 57 | 58 | sub _build_new_entry_from_user_input { 59 | my( $self, $path ) = @_; 60 | 61 | unless ( -e "$path/.git" ) { 62 | say STDERR "ERROR: Non-git repos not supported at this time."; 63 | exit(1); 64 | } 65 | 66 | my( $repo, $type ) = $self->_init_for_git( $path ); 67 | 68 | # if 'defaults' option is true, tell IO::Prompt::Simple to use default choices 69 | $ENV{PERL_IOPS_USE_DEFAULT} = $self->opt->defaults; 70 | 71 | return unless prompt( "\nAdd repository at '$path'? ", { yn => 1, default => 'y' } ); 72 | 73 | my $name = prompt( 'Name? ', lc path( $path )->basename ); 74 | 75 | my $remote; 76 | if ( 1 == scalar keys %$repo ) { # one remote? No choice 77 | ($remote) = values %$repo; 78 | } 79 | else { 80 | $remote = prompt( 'Tracking remote? ', { 81 | anyone => $repo, 82 | verbose => 1, 83 | maybe default => ( $repo->{$self->opt->origin} && $self->opt->origin ), 84 | }); 85 | } 86 | 87 | return App::GitGot::Repo::Git->new({ entry => { 88 | type => $type, 89 | path => "$path", # Path::Tiny to string coercion 90 | name => $name, 91 | repo => $remote, 92 | maybe tags => ( join ' ', prompt( 'Tags? ', join ' ', @{$self->tags||[]} )), 93 | }}); 94 | } 95 | 96 | sub _init_for_git { 97 | my( $self, $path ) = @_; 98 | 99 | ### FIXME probably should have some error handling here... 100 | my $cfg = Config::INI::Reader->read_file("$path/.git/config"); 101 | 102 | my %remotes = pairmap { $a =~ /remote "(.*?)"/ ? ( $1 => $b->{url} ) : () } %$cfg; 103 | 104 | return ( \%remotes, 'git' ); 105 | } 106 | 107 | sub _process_dir { 108 | my( $self, $dir ) = @_; 109 | 110 | # first thing, do we already "got" it? 111 | return warn "Repository at '$dir' already registered with Got, skipping\n" 112 | if any { $_ eq $dir } map { $_->path } $self->all_repos; 113 | 114 | $self->add_repo( 115 | $self->_build_new_entry_from_user_input($dir) 116 | ); 117 | 118 | $self->write_config; 119 | } 120 | 121 | 1; 122 | 123 | __END__ 124 | 125 | =head1 SYNOPSIS 126 | 127 | # add repository of current directory 128 | $ got add 129 | 130 | # add repository of multiple directories, 131 | # with default tags 132 | $ got add -t bar-things -t moosey Moo-bar Moose-bar 133 | 134 | # recursively find repositories, 135 | # auto-configure with the defaults 136 | # with given tag 137 | $ got add --recursive --tag mine . 138 | 139 | =cut 140 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/chdir.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::chdir; 2 | 3 | # ABSTRACT: open a subshell in a selected project 4 | use 5.014; 5 | 6 | use App::GitGot -command; 7 | 8 | use Moo; 9 | extends 'App::GitGot::Command'; 10 | use namespace::autoclean; 11 | 12 | sub command_names { qw/ chdir cd / } 13 | 14 | sub _execute { 15 | my( $self, $opt, $args ) = @_; 16 | 17 | unless ( $self->active_repos and $self->active_repos == 1 ) { 18 | say STDERR 'ERROR: You need to select a single repo'; 19 | exit(1); 20 | } 21 | 22 | my( $repo ) = $self->active_repos; 23 | 24 | chdir $repo->path 25 | or say STDERR "ERROR: Failed to chdir to repo ($!)" and exit(1); 26 | 27 | exec $ENV{SHELL}; 28 | } 29 | 30 | 1; 31 | 32 | ### FIXME docs 33 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/checkout.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::checkout; 2 | 3 | # ABSTRACT: checkout specific branch for managed repositories 4 | use 5.014; 5 | 6 | use Try::Tiny; 7 | 8 | use App::GitGot -command; 9 | 10 | use Moo; 11 | extends 'App::GitGot::Command'; 12 | use namespace::autoclean; 13 | 14 | sub options { 15 | my( $class , $app ) = @_; 16 | return ( 17 | [ 'branch=s' => 'branch to checkout in the different repos' => { required => 1 } ] , 18 | [ 'create|b' => 'create branch like checkout -b in each of the repos' ], 19 | ); 20 | } 21 | 22 | sub _execute { 23 | my( $self, $opt, $args ) = @_; 24 | 25 | $self->_checkout( $self->opt->branch, $self->active_repos ); 26 | } 27 | 28 | sub _checkout { 29 | my( $self , $branch, @repos ) = @_; 30 | 31 | my $max_len = $self->max_length_of_an_active_repo_label; 32 | 33 | REPO: for my $repo ( @repos ) { 34 | next REPO unless $repo->repo; 35 | 36 | my $name = $repo->name; 37 | 38 | my $msg = sprintf "%3d) %-${max_len}s : ", $repo->number, $repo->label; 39 | 40 | my ( $status, $fxn ); 41 | 42 | my $repo_type = $repo->type; 43 | 44 | if ( $repo_type eq 'git' ) { $fxn = '_git_checkout' } 45 | ### FIXME elsif( $repo_type eq 'svn' ) { $fxn = 'svn_update' } 46 | else { $status = $self->error("ERROR: repo type '$_' not supported") } 47 | 48 | $status = $self->$fxn($repo, $branch) if ($fxn); 49 | 50 | next REPO if $self->quiet and !$status; 51 | 52 | say "$msg$status"; 53 | } 54 | } 55 | 56 | sub _git_checkout { 57 | my ( $self, $entry, $branch ) = @_ 58 | or die "Need entry"; 59 | 60 | # no callback because we need to run checkout even if just cloned 61 | $self->_git_clone_or_callback( $entry , sub { '' } ); 62 | 63 | my $msg = ''; 64 | 65 | my @o = try { $entry->checkout($self->opt->create ? '-b' : (), $branch); } catch { $_->error }; 66 | 67 | my @err = try { @{ $entry->_wrapper->ERR } } catch { $_ }; 68 | 69 | # Typically STDOUT will contain something similar to 70 | # Your branch is up-to-date with 'origin/main'. 71 | # or 72 | # Your branch is ahead of 'origin/main' by 2 commits. 73 | 74 | # Typically STDERR will contain something similar to 75 | # Switched to branch 'beta' 76 | # or 77 | # Already on 'beta' 78 | if ( grep { /(ahead|behind).*?by (\d+) commits./ } @o ) { 79 | # branch checked out but not yet in sync 80 | $msg .= $self->major_change("\u$1\e by $2"); 81 | } 82 | elsif ( grep { /^Switched to/ } @err ) { 83 | # branch checked out and in sync 84 | $msg .= $self->major_change('Checked out'); 85 | #$msg .= "\n" . join("\n",@err) unless $self->quiet; 86 | } 87 | elsif ( grep { /^Already on/ } @err ) { 88 | # already on requested branch 89 | $msg .= $self->minor_change('OK') unless $self->quiet; 90 | } 91 | elsif ( grep { /did not match/ } @o ) { 92 | # branch doesn't exist and was not created 93 | $msg .= $self->error('Unknown branch'); 94 | } 95 | elsif ( scalar @o == 0 && scalar @err == 0 ) { 96 | # No messages to STDERR means repo was already updated (or this is a test) 97 | $msg .= $self->minor_change('OK') unless $self->quiet; 98 | } 99 | else { 100 | # Something else occured (possibly a warning) 101 | # Print STDOUT/STDERR and move on 102 | $msg .= $self->warning('Problem during checkout'); 103 | $msg .= "\n" . join("\n", @o, @err) unless $self->quiet; 104 | return $msg; 105 | } 106 | 107 | return $msg; 108 | } 109 | 110 | 1; 111 | 112 | ### FIXME docs 113 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/clone.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::clone; 2 | 3 | # ABSTRACT: clone a remote repo and add it to your config 4 | use 5.014; 5 | 6 | use Cwd; 7 | use Path::Tiny; 8 | use IO::Prompt::Simple; 9 | use Types::Standard -types; 10 | 11 | use App::GitGot -command; 12 | use App::GitGot::Repo::Git; 13 | 14 | use Moo; 15 | extends 'App::GitGot::Command'; 16 | use namespace::autoclean; 17 | 18 | sub options { 19 | my( $class , $app ) = @_; 20 | return ( 21 | [ 'defaults|D' => 'use the default choices for all prompts' ] , 22 | [ 'recursive|r' => 'clone submodules recursively' => { default => 0 } ], 23 | ); 24 | } 25 | 26 | sub _use_io_page { 0 } 27 | 28 | sub _execute { 29 | my ( $self, $opt, $args ) = @_; 30 | 31 | my ( $repo , $path ) = @$args; 32 | 33 | $repo // ( say STDERR 'ERROR: Need the URL to clone!' and exit(1) ); 34 | 35 | my $cwd = getcwd 36 | or( say STDERR "ERROR: Couldn't determine path" and exit(1) ); 37 | 38 | my $name = path( $repo )->basename; 39 | $name =~ s/.git$//; 40 | 41 | $path //= "$cwd/$name"; 42 | $path = path( $path )->absolute; 43 | 44 | my $tags; 45 | 46 | unless ( $self->opt->defaults ) { 47 | $name = prompt( 'Name: ' , $name ); 48 | while() { 49 | $path = prompt( 'Path: ' , $path ); 50 | last if not path($path)->exists; 51 | say "can't clone into '$path': directory already exists"; 52 | } 53 | $tags = prompt( 'Tags: ' , $tags ); 54 | } 55 | 56 | my $new_entry = App::GitGot::Repo::Git->new({ entry => { 57 | repo => $repo, 58 | name => $name, 59 | type => 'git', 60 | path => $path, 61 | }}); 62 | $new_entry->{tags} = $tags if $tags; 63 | 64 | say "Cloning into '$path'..." unless $self->quiet; 65 | $new_entry->clone( 66 | { recursive => $self->opt->recursive }, 67 | $repo , $path 68 | ); 69 | 70 | $self->add_repo( $new_entry ); 71 | $self->write_config; 72 | } 73 | 74 | 1; 75 | 76 | __END__ 77 | 78 | =head1 SYNOPSIS 79 | 80 | # clone repository and add to got config 81 | $ got clone 82 | # prompts for name, path, tags, etc. 83 | 84 | # clone repository and add to got config 85 | # using defaults for all prompts 86 | $ got clone -D 87 | 88 | # recursively clone the submodules as well 89 | $ got clone -r 90 | 91 | =cut 92 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/do.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::do; 2 | 3 | # ABSTRACT: run command in many repositories 4 | use 5.014; 5 | 6 | use Capture::Tiny qw/ capture_stdout /; 7 | use File::chdir; 8 | use Types::Standard -types; 9 | 10 | use App::GitGot -command; 11 | 12 | use Moo; 13 | extends 'App::GitGot::Command'; 14 | use namespace::autoclean; 15 | 16 | sub options { 17 | my( $class , $app ) = @_; 18 | return ( 19 | [ 'command|e=s' => 'command to execute in the different repos' => { required => 1 } ] , 20 | [ 'with_repo' => 'prepend all output lines with the repo name' => { default => 0 } ] , 21 | ); 22 | } 23 | 24 | sub _execute { 25 | my $self = shift; 26 | 27 | for my $repo ( $self->active_repos ) { 28 | $self->_run_in_repo( $repo => $self->opt->command ); 29 | } 30 | } 31 | 32 | sub _run_in_repo { 33 | my( $self, $repo, $cmd ) = @_; 34 | 35 | if ( not -d $repo->path ) { 36 | printf "repo %s: no repository found at path '%s'\n", 37 | $repo->label, $repo->path; 38 | return; 39 | } 40 | 41 | say "\n## repo ", $repo->label, "\n" unless $self->opt->with_repo; 42 | 43 | my $prefix = $self->opt->with_repo ? $repo->label . ': ' : ''; 44 | 45 | say $prefix, $_ for split "\n", capture_stdout { 46 | $CWD = $repo->path; 47 | system $cmd; 48 | }; 49 | } 50 | 51 | 1; 52 | 53 | ### FIXME docs 54 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/fetch.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::fetch; 2 | 3 | # ABSTRACT: fetch remotes for managed repositories 4 | use 5.014; 5 | 6 | use App::GitGot -command; 7 | 8 | use Moo; 9 | extends 'App::GitGot::Command'; 10 | use namespace::autoclean; 11 | 12 | sub _execute { 13 | my ( $self, $opt, $args ) = @_; 14 | 15 | $self->_fetch( $self->active_repos ); 16 | } 17 | 18 | 1; 19 | 20 | ### FIXME docs 21 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/fork.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::fork; 2 | 3 | # ABSTRACT: fork a github repo 4 | use 5.014; 5 | 6 | use autodie; 7 | use Class::Load 'try_load_class'; 8 | use Cwd; 9 | use File::HomeDir::Tiny (); 10 | use Path::Tiny; 11 | use Types::Standard -types; 12 | 13 | use App::GitGot -command; 14 | use App::GitGot::Repo::Git; 15 | 16 | use Moo; 17 | extends 'App::GitGot::Command'; 18 | use namespace::autoclean; 19 | 20 | sub options { 21 | my( $class , $app ) = @_; 22 | return ( 23 | [ 'noclone|n' => 'If set, do not check out a local working copy of the forked repo' ] , 24 | [ 'noremoteadd|N' => 'If set, do not add the forked repo as the "upstream" repo in the new working copy' ] , 25 | ); 26 | } 27 | 28 | sub _execute { 29 | my( $self, $opt, $args ) = @_; 30 | 31 | try_load_class('Net::GitHub') or 32 | say "Sorry, Net::GitHub is required for 'got fork'. Please install it." 33 | and exit(1); 34 | 35 | my $github_url = shift @$args 36 | or say STDERR "ERROR: Need the URL of a repo to fork!" and exit(1); 37 | 38 | my( $owner , $repo_name ) = _parse_github_url( $github_url ); 39 | 40 | my %gh_args = _parse_github_identity(); 41 | 42 | say "Forking '$owner/$repo_name'..." unless $self->quiet; 43 | 44 | my $resp = Net::GitHub->new( %gh_args )->repos->create_fork( $owner , $repo_name ); 45 | 46 | my $path = cwd() . "/$repo_name"; 47 | 48 | my $new_repo = App::GitGot::Repo::Git->new({ entry => { 49 | name => $repo_name , 50 | path => $path , 51 | repo => $resp->{ssh_url} , 52 | type => 'git' , 53 | }}); 54 | 55 | if ( ! $self->opt->noclone ) { 56 | say "Cloning into $path" unless $self->quiet; 57 | $new_repo->clone( $resp->{ssh_url} ); 58 | 59 | if ( ! $self->opt->noremoteadd ) { 60 | say "Adding '$github_url' as remote 'upstream'..." 61 | unless $self->quiet; 62 | $new_repo->remote( add => upstream => $github_url ); 63 | } 64 | } 65 | 66 | $self->add_repo( $new_repo ); 67 | $self->write_config; 68 | } 69 | 70 | sub _parse_github_identity { 71 | my $file = path( File::HomeDir::Tiny::home() , '.github-identity' ); 72 | 73 | $file->exists or 74 | say STDERR "ERROR: Can't find $file" and exit(1); 75 | 76 | my @lines = $file->lines; 77 | 78 | my %config; 79 | foreach ( @lines ) { 80 | chomp; 81 | next unless $_; 82 | my( $k , $v ) = split /\s/; 83 | $config{$k} = $v; 84 | } 85 | 86 | if ( defined $config{access_token} ) { 87 | return ( access_token => $config{access_token} ) 88 | } 89 | elsif ( defined $config{pass} and defined $config{user} ) { 90 | return ( login => $config{user} , pass => $config{pass} ) 91 | } 92 | else { 93 | say STDERR "Couldn't parse password or access_token info from ~/.github-identity" 94 | and exit(1); 95 | } 96 | } 97 | 98 | sub _parse_github_url { 99 | my $url = shift; 100 | 101 | my( $owner , $repo ) = $url =~ m|/github.com/([^/]+)/([^/]+?)(?:\.git)?$| 102 | or say STDERR "ERROR: Can't parse '$url'.\nURL needs to be of the form 'github.com/OWNER/REPO'.\n" 103 | and exit(1); 104 | 105 | return( $owner , $repo ); 106 | } 107 | 108 | 1; 109 | 110 | __END__ 111 | 112 | =head1 SYNOPSIS 113 | 114 | # fork repo on GitHub, then clone repository and add to got config 115 | $ got fork github.com/owner/repo 116 | 117 | # fork repo on GitHub, add to got config, but do _not_ clone locally 118 | $ got fork -n github.com/owner/repo 119 | $ got fork --noclone github.com/owner/repo 120 | 121 | =cut 122 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/gc.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::gc; 2 | 3 | # ABSTRACT: Run the 'gc' command to garbage collect in git repos 4 | use 5.014; 5 | 6 | use Data::Dumper; 7 | use Try::Tiny; 8 | 9 | use Moo; 10 | extends 'App::GitGot::Command'; 11 | use namespace::autoclean; 12 | 13 | # incremental output looks nicer for this command... 14 | STDOUT->autoflush(1); 15 | sub _use_io_page { 0 } 16 | 17 | sub _execute { 18 | my( $self, $opt, $args ) = @_; 19 | 20 | my $max_len = $self->max_length_of_an_active_repo_label; 21 | 22 | REPO: for my $repo ( $self->active_repos ) { 23 | next REPO unless $repo->type eq 'git'; 24 | try { 25 | printf "%3d) %-${max_len}s : ", $repo->number , $repo->label unless $self->quiet; 26 | # really wish this gave _some_ kind of output... 27 | $repo->gc; 28 | printf "%s\n", $self->major_change( 'COLLECTED' ) unless $self->quiet; 29 | } 30 | catch { 31 | say STDERR $self->error( 'ERROR: Problem with GC on repo ' , $repo->label ); 32 | say STDERR "\n" , Dumper $_; 33 | }; 34 | } 35 | } 36 | 37 | 1; 38 | 39 | ## FIXME docs 40 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/lib.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::lib; 2 | 3 | # ABSTRACT: Generate a lib listing off a .gotlib file 4 | use 5.014; 5 | 6 | use List::Util qw/ uniq /; 7 | use Path::Tiny; 8 | use Types::Standard -types; 9 | 10 | use App::GitGot -command; 11 | 12 | use Moo; 13 | extends 'App::GitGot::Command'; 14 | use namespace::autoclean; 15 | 16 | sub options { 17 | my( $class , $app ) = @_; 18 | return ( 19 | [ 'gotlib=s' => 'gotlib file' => { default => '.gotlib' } ] , 20 | [ 'libvar=s' => 'library environment variable' => { default => 'PERL5LIB' } ] , 21 | [ 'separator=s' => 'library path separator' => { default => ':' } ] , 22 | ); 23 | } 24 | 25 | sub _execute { 26 | my( $self, $opt, $args ) = @_; 27 | 28 | my @libs = map { $self->_expand_lib($_) } $self->_raw_libs( $args ); 29 | 30 | no warnings; # $ENV{$self->opt->libvar} can be undefined 31 | say join $self->opt->separator, uniq @libs, split ':', $ENV{$self->opt->libvar}; 32 | 33 | } 34 | 35 | sub _expand_lib { 36 | my( $self, $lib ) = @_; 37 | 38 | return path($lib)->absolute if $lib =~ m#^(?:\.|/)#; 39 | 40 | if ( $lib =~ s/^\@(\w+)// ) { 41 | # it's a tag 42 | return map { $_->path . $lib } $self->search_repos->tags($1)->all; 43 | } 44 | 45 | # it's a repo name 46 | $lib =~ s#^([^/]+)## or return; 47 | return map { $_->path . $lib } $self->search_repos->name($1)->all; 48 | 49 | } 50 | 51 | sub _raw_libs { 52 | my( $self, $args ) = @_; 53 | 54 | my $file = path( $self->opt->gotlib ); 55 | 56 | return @$args, 57 | # remove comments and clean whitespaces 58 | grep { $_ } 59 | map { s/^\s+|#.*|\s+$//gr } 60 | ( -f $file ) ? $file->lines({ chomp => 1 }) : (); 61 | } 62 | 63 | 1; 64 | 65 | __END__ 66 | 67 | =head1 SYNOPSIS 68 | 69 | $ echo '@dancer/lib' > .gotlib 70 | $ export PERL5LIB=`got lib yet_another_repo_name/lib` 71 | 72 | # PERL5LIB will now hold the path to the 'lib' 73 | # subdirectory of 'yet_another_repo_name', followed 74 | # by the 'lib' directories of all repos tagged with 'dancer', 75 | # followed by the original paths of PERL5LIB 76 | 77 | 78 | =head1 DESCRIPTION 79 | 80 | Got's C subcommand is a Got-aware answer to L and L, 81 | and provides an easy way to alter the I environment variable (or 82 | indeed any other env variable) with local libraries in got-managed repos. 83 | 84 | The subcommand will merge any library passed on the command line and found in 85 | the the I file (if present), and will generate a library listing 86 | where those directories are prepended to I (command-line entries 87 | first, then the ones from the I file). 88 | 89 | Libraries can be given in three different ways: 90 | 91 | =over 92 | 93 | =item absolute or relative path 94 | 95 | If the value begins with a '/' or a '.', it is assumed to be a straight path. 96 | It will be expanded to its absolute value, but otherwise left untouched. 97 | 98 | For example './lib' will be expanded to '/path/to/current/directory/lib' 99 | 100 | 101 | =item tag 102 | 103 | If the value begins with I<@>, it is assumed to be a tag, and will be replaced 104 | by the path to all repositories having that tag. 105 | 106 | For example '@dancer/lib' will be expanded to 107 | '/path/to/dancer/project1/lib:/path/to/dancer/project2/lib:...' 108 | 109 | =item project name 110 | 111 | If not a path nor a tag, the value is assumed to be a project name. 112 | 113 | For example 'vim-x/lib' will be expanded to 114 | '/path/to/vim-x/lib' 115 | 116 | =back 117 | 118 | =head1 OPTIONS 119 | 120 | =head2 --separator 121 | 122 | Separator printed between library directories in the output. 123 | Defaults to ':' (colon). 124 | 125 | =head2 --libvar 126 | 127 | Environment variable containing the directories to include at the end of 128 | the library listing. Defaults to I. 129 | 130 | =head2 --gotlib 131 | 132 | File containing the list of directories to include. Defaults to I<.gotlib>. 133 | 134 | =head1 SEE ALSO 135 | 136 | =over 137 | 138 | =item L 139 | 140 | =item L 141 | 142 | =back 143 | 144 | =cut 145 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/list.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::list; 2 | 3 | # ABSTRACT: list managed repositories 4 | use 5.014; 5 | 6 | use Class::Load 'try_load_class'; 7 | 8 | use App::GitGot -command; 9 | 10 | use Moo; 11 | extends 'App::GitGot::Command'; 12 | use namespace::autoclean; 13 | 14 | sub command_names { qw/ list ls / } 15 | 16 | sub options { 17 | my( $class , $app ) = @_; 18 | return ( 19 | [ 'json|j' => 'stream output as JSON' ] , 20 | ); 21 | } 22 | 23 | sub _execute { 24 | my( $self, $opt, $args ) = @_; 25 | 26 | if ( $self->opt->json ) { 27 | try_load_class( 'JSON::MaybeXS' ) 28 | or die "json serializing requires the module 'JSON::MaybeXS' to be installed\n"; 29 | 30 | my @data = map { {%$_} } $self->active_repos; 31 | 32 | say JSON::MaybeXS->new(pretty => 1)->encode( \@data ); 33 | return; 34 | } 35 | 36 | for my $repo ( $self->active_repos ) { 37 | my $repo_remote = ( $repo->repo and -d $repo->path ) ? $repo->repo 38 | : ( $repo->repo ) ? $repo->repo . ' (Not checked out)' 39 | : ( -d $repo->path ) ? 'NO REMOTE' 40 | : 'ERROR: No remote and no repo?!'; 41 | 42 | printf "%3d) ", $repo->number; 43 | 44 | if ( $self->quiet ) { say $repo->label } 45 | else { 46 | my $max_len = $self->max_length_of_an_active_repo_label; 47 | 48 | printf "%-${max_len}s %-4s %s\n", $repo->label, $repo->type, $repo_remote; 49 | 50 | if ( $self->verbose and $repo->tags ) { 51 | printf " tags: %s\n" , $repo->tags 52 | } 53 | } 54 | } 55 | } 56 | 57 | 1; 58 | 59 | ## FIXME docs 60 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/milk.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::milk; 2 | 3 | use 5.014; 4 | 5 | # ABSTRACT: well, do you? 6 | use App::GitGot -command; 7 | 8 | use Moo; 9 | extends 'App::GitGot::Command'; 10 | use namespace::autoclean; 11 | 12 | sub command_names { qw/ milk / } 13 | 14 | sub _execute { 15 | # Doesn't use 'cowsay' in case it's not installed 16 | print " ___________\n"; 17 | print "< got milk? >\n"; 18 | print " -----------\n"; 19 | print " \\ ^__^\n"; 20 | print " \\ (oo)\\_______\n"; 21 | print " (__)\\ )\\/\\ \n"; 22 | print " ||----w |\n"; 23 | print " || ||\n"; 24 | print "\n"; 25 | } 26 | 27 | 28 | 29 | 1; 30 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/move.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::move; 2 | 3 | # ABSTRACT: move repo to new location 4 | use 5.014; 5 | 6 | use Cwd; 7 | use File::Copy::Recursive qw/ dirmove /; 8 | use Path::Tiny; 9 | 10 | use App::GitGot -command; 11 | 12 | use Moo; 13 | extends 'App::GitGot::Command'; 14 | use namespace::autoclean; 15 | 16 | sub command_names { qw/ move mv / } 17 | 18 | sub options { 19 | my( $class , $app ) = @_; 20 | return ( 21 | [ 'destination=s' => 'FIXME' => { required => 1 } ] , 22 | ); 23 | } 24 | 25 | sub _execute { 26 | my( $self, $opt, $args ) = @_; 27 | 28 | my @repos = $self->active_repos; 29 | 30 | my $dest = $self->opt->destination; 31 | 32 | path($dest)->mkpath if @repos > 1; 33 | 34 | for my $repo ( @repos ) { 35 | my $target_dir = -d $dest 36 | ? path($dest)->child( path($repo->path)->basename ) 37 | : $dest; 38 | 39 | dirmove( $repo->path => $target_dir ) 40 | or die "couldn't move ", $repo->name, " to '$target_dir': $!"; 41 | 42 | $repo->{path} = "$target_dir"; 43 | $self->write_config; 44 | 45 | say sprintf '%s moved to %s', $repo->name, $target_dir; 46 | } 47 | } 48 | 49 | 1; 50 | 51 | ## FIXME docs 52 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/mux.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::mux; 2 | 3 | # ABSTRACT: open a tmux window for a selected project 4 | use 5.014; 5 | 6 | use IO::Prompt::Simple; 7 | 8 | use App::GitGot -command; 9 | 10 | use Moo; 11 | extends 'App::GitGot::Command'; 12 | use namespace::autoclean; 13 | 14 | sub command_names { qw/ mux tmux / } 15 | 16 | sub options { 17 | my( $class , $app ) = @_; 18 | return ( 19 | [ 'dirty|D' => 'open session or window for all dirty repos' ] , 20 | [ 'exec|e=s' => 'pass a command to the `tmux new-window` command (not valid in combination with -s option)'] , 21 | [ 'session|s' => 'use tmux-sessions (default: tmux windows)' ] , 22 | ); 23 | } 24 | 25 | sub _use_io_page { 0 } 26 | 27 | sub _execute { 28 | my( $self, $opt, $args ) = @_; 29 | 30 | die "-e and -s are mutually exclusive" 31 | if $self->opt->exec and $self->opt->session; 32 | 33 | my @repos = $self->opt->dirty ? $self->_get_dirty_repos() : $self->active_repos(); 34 | 35 | my $target = $self->opt->session ? 'session' : 'window'; 36 | 37 | if ( @repos >= 25 ) { 38 | my $repo_count = scalar @repos; 39 | return unless 40 | prompt( "\nYou're about to open $repo_count ${target}s - you sure about that? ", { yn => 1, default => 'n' } ); 41 | } 42 | 43 | REPO: foreach my $repo ( @repos ) { 44 | # is it already opened? 45 | my %windows = reverse map { /^(\d+):::(\S+)/ } 46 | split "\n", `tmux list-$target -F"#I:::#W"`; 47 | 48 | if( my $window = $windows{$repo->name} ) { 49 | if ($self->opt->session) { 50 | system 'tmux', 'switch-client', '-t' => $window; 51 | } 52 | else { 53 | system 'tmux', 'select-window', '-t' => $window; 54 | } 55 | next REPO; 56 | } 57 | 58 | chdir $repo->path; 59 | 60 | if ($self->opt->session) { 61 | delete local $ENV{TMUX}; 62 | system 'tmux', 'new-session', '-d', '-s', $repo->name; 63 | system 'tmux', 'switch-client', '-t' => $repo->name;} 64 | else { 65 | my @args = (qw/ tmux new-window -n / , $repo->name ); 66 | push( @args , $self->opt->exec ) if $self->opt->exec; 67 | system @args; 68 | } 69 | } 70 | } 71 | 72 | sub _get_dirty_repos { 73 | my $self = shift; 74 | 75 | my @dirty_repos; 76 | foreach my $repo ( @{ $self->full_repo_list } ) { 77 | my $status = $repo->status(); 78 | 79 | unless ( ref( $status )) { 80 | die "You need at least Git version 1.7 to use the --dirty flag.\n"; 81 | } 82 | 83 | push @dirty_repos , $repo 84 | if $status->is_dirty; 85 | } 86 | 87 | return @dirty_repos; 88 | } 89 | 90 | 1; 91 | 92 | ## FIXME docs 93 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/push.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::push; 2 | 3 | # ABSTRACT: Push local changes to the default remote in git repos 4 | use 5.014; 5 | 6 | use Data::Dumper; 7 | use Try::Tiny; 8 | 9 | use App::GitGot -command; 10 | 11 | use Moo; 12 | extends 'App::GitGot::Command'; 13 | use namespace::autoclean; 14 | 15 | # incremental output looks nicer for this command... 16 | STDOUT->autoflush(1); 17 | sub _use_io_page { 0 } 18 | 19 | sub _execute { 20 | my( $self, $opt, $args ) = @_; 21 | 22 | my $max_len = $self->max_length_of_an_active_repo_label; 23 | 24 | REPO: for my $repo ( $self->active_repos ) { 25 | next REPO unless $repo->type eq 'git'; 26 | 27 | unless ( $repo->current_remote_branch and $repo->cherry ) { 28 | printf "%3d) %-${max_len}s : Nothing to push\n", 29 | $repo->number , $repo->label unless $self->quiet; 30 | next REPO; 31 | } 32 | 33 | try { 34 | printf "%3d) %-${max_len}s : ", $repo->number , $repo->label; 35 | # really wish this gave _some_ kind of output... 36 | my @output = $repo->push; 37 | printf "%s\n", $self->major_change( 'PUSHED' ); 38 | } 39 | catch { 40 | say STDERR $self->error( 'ERROR: Problem with push on repo ' , $repo->label ); 41 | say STDERR "\n" , Dumper $_; 42 | }; 43 | } 44 | } 45 | 46 | 1; 47 | 48 | ## FIXME docs 49 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/readd.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::readd; 2 | 3 | # ABSTRACT: update config metadata to match repo 4 | use 5.014; 5 | 6 | use App::GitGot -command; 7 | use App::GitGot::Repo::Git; 8 | 9 | use Moo; 10 | extends 'App::GitGot::Command'; 11 | use namespace::autoclean; 12 | 13 | sub options { 14 | my( $class , $app ) = @_; 15 | return (); 16 | } 17 | 18 | sub _use_io_page { 0 } 19 | 20 | sub _execute { 21 | my ( $self, $opt, $args ) = @_; 22 | 23 | my $max_len = $self->max_length_of_an_active_repo_label(); 24 | my $updated_config = 0; 25 | 26 | REPO: for my $repo ( $self->active_repos ) { 27 | next unless $repo->type eq 'git'; 28 | 29 | my $configuration_url = $repo->repo; 30 | my( $repo_url ) = $repo->config("remote.origin.url"); 31 | 32 | if( $configuration_url ne $repo_url ) { 33 | # do as i say, not as i do... 34 | $repo->{repo} = $repo_url; 35 | $updated_config = 1; 36 | 37 | printf "Updated repo url for %-${max_len}s to %s\n", $repo->name, $repo->repo 38 | if $self->verbose; 39 | } 40 | } 41 | 42 | $self->write_config() 43 | if $updated_config; 44 | } 45 | 46 | 1; 47 | 48 | __END__ 49 | 50 | =head1 SYNOPSIS 51 | 52 | # update ~/.gitgot to reflect current remotes 53 | $ got readd 54 | 55 | =cut 56 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/remove.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::remove; 2 | 3 | # ABSTRACT: remove a managed repository from your config 4 | use 5.014; 5 | 6 | use List::Util qw/ any /; 7 | 8 | use App::GitGot -command; 9 | 10 | use Moo; 11 | extends 'App::GitGot::Command'; 12 | use namespace::autoclean; 13 | 14 | sub command_names { qw/ remove rm / } 15 | 16 | sub options { 17 | my( $class , $app ) = @_; 18 | return ( 19 | [ 'force' => 'FIXME' ] , 20 | ); 21 | } 22 | 23 | sub _use_io_page { 0 } 24 | 25 | sub _execute { 26 | my( $self, $opt, $args ) = @_; 27 | 28 | unless ( $self->active_repos and @$args or $self->tags) { 29 | say STDERR "ERROR: You need to select one or more repos to remove"; 30 | exit(1); 31 | } 32 | 33 | my @new_repo_list; 34 | 35 | REPO: for my $repo ( $self->all_repos ) { 36 | my $number = $repo->number; 37 | 38 | if ( any { $number == $_->number } $self->active_repos ) { 39 | my $name = $repo->label; 40 | 41 | if ( $self->opt->force or $self->prompt_yn( "got rm: remove '$name'?" )) { 42 | say "Removed repo '$name'" if $self->verbose; 43 | next REPO; 44 | } 45 | } 46 | push @new_repo_list , $repo; 47 | } 48 | 49 | $self->set_full_repo_list( \@new_repo_list ); 50 | $self->write_config(); 51 | } 52 | 53 | 1; 54 | 55 | ## FIXME docs 56 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/status.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::status; 2 | 3 | # ABSTRACT: print status info about repos 4 | use 5.014; 5 | 6 | use Moo; 7 | extends 'App::GitGot::Command'; 8 | use namespace::autoclean; 9 | 10 | sub command_names { qw/ status st / } 11 | 12 | sub options { 13 | my( $class , $app ) = @_; 14 | return ( 15 | [ 'show-branch' => 'show which branch' => { default => 0 } ] , 16 | ); 17 | } 18 | 19 | 20 | sub _execute { 21 | my ( $self, $opt, $args ) = @_; 22 | 23 | $self->_status( $self->active_repos ); 24 | } 25 | 26 | 1; 27 | 28 | ## FIXME docs 29 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/tag.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::tag; 2 | 3 | # ABSTRACT: list/add/remove tags for the current repository 4 | use 5.014; 5 | 6 | use Moo; 7 | extends 'App::GitGot::Command'; 8 | use namespace::autoclean; 9 | 10 | sub options { 11 | my( $class , $app ) = @_; 12 | return ( 13 | [ 'add|a' => 'assign tags to the current repository' => { default => 0 } ] , 14 | [ 'all|A' => 'print out tags of all repositories' => { default => 0 } ] , 15 | [ 'remove|rm' => 'remove tags from the current repository' => { default => 0 } ] , 16 | ); 17 | } 18 | 19 | sub _execute { 20 | my( $self, $opt, $args ) = @_; 21 | 22 | return say "not in a got-monitored repo" unless $self->local_repo; 23 | 24 | return say "can't --add and --remove at the same time" 25 | if $self->opt->add and $self->opt->remove; 26 | 27 | if( $self->opt->add ) { 28 | return $self->_add_tags( @$args ); 29 | } 30 | 31 | if( $self->opt->remove ) { 32 | return $self->_remove_tags( @$args ); 33 | } 34 | 35 | $self->_print_tags; 36 | } 37 | 38 | sub _add_tags { 39 | my( $self, @tags ) = @_; 40 | 41 | $self->local_repo->add_tags( @tags ); 42 | 43 | $self->write_config; 44 | 45 | say "tags added"; 46 | } 47 | 48 | sub _print_tags { 49 | my $self = shift; 50 | 51 | my %tags = map { $_ => 1 } split ' ', $self->local_repo->tags; 52 | 53 | if ( $self->opt->all ) { 54 | $tags{$_} ||= 0 for map { split ' ', $_->tags } $self->all_repos 55 | } 56 | 57 | for my $t ( sort keys %tags ) { 58 | say $t, ' *' x ( $self->opt->all and $tags{$t} ); 59 | } 60 | 61 | } 62 | 63 | sub _remove_tags { 64 | my( $self, @tags ) = @_; 65 | 66 | $self->local_repo->remove_tags(@tags); 67 | 68 | $self->write_config; 69 | 70 | say "tags removed"; 71 | } 72 | 73 | 1; 74 | 75 | __END__ 76 | 77 | =head1 SYNOPSIS 78 | 79 | $ got tag 80 | dancer 81 | perl 82 | private 83 | 84 | $ got tag --all 85 | dancer * 86 | perl * 87 | private * 88 | other 89 | 90 | $ got tag --add new_tag another_new_tag 91 | 92 | $ got tag --rm new_tag 93 | 94 | =head1 DESCRIPTION 95 | 96 | C manages tags for the current repository. 97 | 98 | =head1 OPTIONS 99 | 100 | =head2 --all 101 | 102 | Shows all tags. Tags that are associated with the current repository are 103 | marked with an '*'. 104 | 105 | =head2 --add tag1 tag2 ... 106 | 107 | Adds tags to the current repository. 108 | 109 | =head2 --remove tag1 tag2 ... 110 | 111 | Removes tags from the current repository. 112 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/that.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::that; 2 | 3 | # ABSTRACT: check if a given repository is managed 4 | use 5.014; 5 | 6 | use Moo; 7 | extends 'App::GitGot::Command'; 8 | use namespace::autoclean; 9 | 10 | sub _execute { 11 | my( $self, $opt, $args ) = @_; 12 | my $path = pop @$args; 13 | 14 | defined $path and -d $path 15 | or say STDERR 'ERROR: You must provide a path to a repo to check' and exit 1; 16 | 17 | $self->_path_is_managed( $path ) or exit 1; 18 | } 19 | 20 | 1; 21 | 22 | ## FIXME docs 23 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/this.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::this; 2 | 3 | # ABSTRACT: check if the current repository is managed 4 | use 5.014; 5 | 6 | use Cwd; 7 | 8 | use Moo; 9 | extends 'App::GitGot::Command'; 10 | use namespace::autoclean; 11 | 12 | sub _execute { 13 | my( $self, $opt, $args ) = @_; 14 | 15 | $self->_path_is_managed( getcwd() ) or exit 1; 16 | } 17 | 18 | 1; 19 | 20 | ## FIXME docs 21 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/update.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::update; 2 | 3 | # ABSTRACT: update managed repositories 4 | use 5.014; 5 | 6 | use Moo; 7 | extends 'App::GitGot::Command'; 8 | use namespace::autoclean; 9 | 10 | sub command_names { qw/ update up / } 11 | 12 | sub _execute { 13 | my ( $self, $opt, $args ) = @_; 14 | 15 | $self->_update( $self->active_repos ); 16 | } 17 | 18 | 1; 19 | 20 | ## FIXME docs 21 | -------------------------------------------------------------------------------- /lib/App/GitGot/Command/update_status.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Command::update_status; 2 | 3 | # ABSTRACT: update managed repositories then display their status 4 | use 5.014; 5 | 6 | use Moo; 7 | extends 'App::GitGot::Command'; 8 | use namespace::autoclean; 9 | 10 | sub command_names { qw/ update_status upst / } 11 | 12 | sub options { 13 | my( $class , $app ) = @_; 14 | return ( 15 | [ 'show-branch' => 'show which branch' => { default => 0 } ] , 16 | ); 17 | } 18 | 19 | sub _execute { 20 | my ( $self, $opt, $args ) = @_; 21 | 22 | say "UPDATE"; 23 | $self->_update( $self->active_repos ); 24 | 25 | say "\nSTATUS"; 26 | $self->_status( $self->active_repos ); 27 | } 28 | 29 | 1; 30 | 31 | ## FIXME docs 32 | -------------------------------------------------------------------------------- /lib/App/GitGot/Outputter.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Outputter; 2 | 3 | # ABSTRACT: Generic base class for outputting formatted messages. 4 | use 5.014; 5 | 6 | use Term::ANSIColor qw/ colored /; 7 | use Types::Standard -types; 8 | 9 | use App::GitGot::Types; 10 | 11 | use Moo; 12 | use namespace::autoclean; 13 | 14 | =attr no_color 15 | 16 | Boolean indicating whether color messages should be output at all. 17 | 18 | =cut 19 | 20 | has no_color => ( 21 | is => 'ro' , 22 | isa => Bool , 23 | default => 0 , 24 | documentation => 'boolean indicating whether color messages should be output at all' , 25 | ); 26 | 27 | =method error 28 | 29 | Display a message using the 'color_error' color settings. 30 | 31 | =cut 32 | 33 | sub error { 34 | my( $self , $message ) = @_; 35 | return $self->_colored( $message , $self->color_error ); 36 | } 37 | 38 | =method major_change 39 | 40 | Display a message using the 'color_major_change' color settings. 41 | 42 | =cut 43 | 44 | sub major_change { 45 | my( $self , $message ) = @_; 46 | return $self->_colored( $message , $self->color_major_change ); 47 | } 48 | 49 | =method minor_change 50 | 51 | Display a message using the 'color_minor_change' color settings. 52 | 53 | =cut 54 | 55 | sub minor_change { 56 | my( $self , $message ) = @_; 57 | return $self->_colored( $message , $self->color_minor_change ); 58 | } 59 | 60 | =method warning 61 | 62 | Display a message using the 'color_warning' color settings. 63 | 64 | =cut 65 | 66 | sub warning { 67 | my( $self , $message ) = @_; 68 | return $self->_colored( $message , $self->color_warning ); 69 | } 70 | 71 | sub _colored { 72 | my( $self , $message , $color_string ) = @_; 73 | 74 | return ( $self->no_color || $color_string eq 'uncolored' ) ? $message 75 | : colored( $message , $color_string ); 76 | } 77 | 78 | 1; 79 | -------------------------------------------------------------------------------- /lib/App/GitGot/Outputter/dark.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Outputter::dark; 2 | 3 | # ABSTRACT: Color scheme appropriate for dark terminal backgrounds 4 | use 5.014; 5 | 6 | use Types::Standard -types; 7 | 8 | use App::GitGot::Types; 9 | 10 | use Moo; 11 | extends 'App::GitGot::Outputter'; 12 | use namespace::autoclean; 13 | 14 | has color_error => ( 15 | is => 'ro' , 16 | isa => Str , 17 | default => 'bold white on_red' 18 | ); 19 | 20 | has color_major_change => ( 21 | is => 'ro' , 22 | isa => Str , 23 | default => 'bold black on_green' 24 | ); 25 | 26 | has color_minor_change => ( 27 | is => 'ro' , 28 | isa => Str , 29 | default => 'green' 30 | ); 31 | 32 | has color_warning => ( 33 | is => 'ro' , 34 | isa => Str , 35 | default => 'bold black on_yellow' 36 | ); 37 | 38 | =for Pod::Coverage color_error color_major_change color_minor_change color_warning 39 | 40 | =cut 41 | 42 | 1; 43 | -------------------------------------------------------------------------------- /lib/App/GitGot/Outputter/light.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Outputter::light; 2 | 3 | # ABSTRACT: Color scheme appropriate for dark terminal backgrounds 4 | use 5.014; 5 | 6 | use Types::Standard -types; 7 | 8 | use App::GitGot::Types; 9 | 10 | use Moo; 11 | extends 'App::GitGot::Outputter'; 12 | use namespace::autoclean; 13 | 14 | has color_error => ( 15 | is => 'ro' , 16 | isa => Str , 17 | default => 'bold red' 18 | ); 19 | 20 | # Color choices by drdrang based on a conversation that started with 21 | # 22 | 23 | has color_major_change => ( 24 | is => 'ro' , 25 | isa => Str , 26 | default => 'blue' 27 | ); 28 | 29 | has color_minor_change => ( 30 | is => 'ro' , 31 | isa => Str , 32 | default => 'uncolored' 33 | ); 34 | 35 | has color_warning => ( 36 | is => 'ro' , 37 | isa => Str , 38 | default => 'bold magenta' 39 | ); 40 | 41 | =for Pod::Coverage color_error color_major_change color_minor_change color_warning 42 | 43 | =cut 44 | 45 | 1; 46 | -------------------------------------------------------------------------------- /lib/App/GitGot/Repo.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Repo; 2 | 3 | # ABSTRACT: Base repository objects 4 | use 5.014; 5 | 6 | use List::Util qw/ uniq /; 7 | use Types::Standard -types; 8 | 9 | use App::GitGot::Types; 10 | 11 | use Moo; 12 | use namespace::autoclean; 13 | 14 | =attr label 15 | 16 | Optional label for the repo. 17 | 18 | =cut 19 | 20 | has label => ( 21 | is => 'ro' , 22 | isa => Str , 23 | ); 24 | 25 | =attr name 26 | 27 | The name of the repo. 28 | 29 | =cut 30 | 31 | has name => ( 32 | is => 'ro', 33 | isa => Str, 34 | required => 1 , 35 | ); 36 | 37 | =attr number 38 | 39 | The number of the repo. 40 | 41 | =cut 42 | 43 | has number => ( 44 | is => 'ro', 45 | isa => Int, 46 | required => 1 , 47 | ); 48 | 49 | =attr path 50 | 51 | The path to the repo. 52 | 53 | =cut 54 | 55 | has path => ( 56 | is => 'ro', 57 | isa => Str, 58 | required => 1 , 59 | coerce => sub { ref $_[0] && $_[0]->isa('Path::Tiny') ? "$_[0]" : $_[0] } , 60 | ); 61 | 62 | =attr repo 63 | 64 | =cut 65 | 66 | has repo => ( 67 | is => 'ro', 68 | isa => Str, 69 | ); 70 | 71 | =attr tags 72 | 73 | Space-separated list of tags for the repo 74 | 75 | =cut 76 | 77 | has tags => ( 78 | is => 'rw', 79 | isa => Str, 80 | ); 81 | 82 | =attr type 83 | 84 | The type of the repo (git, svn, etc.). 85 | 86 | =cut 87 | 88 | has type => ( 89 | is => 'ro', 90 | isa => Str, 91 | required => 1 , 92 | ); 93 | 94 | sub BUILDARGS { 95 | my( $class , $args ) = @_; 96 | 97 | my $count = $args->{count} || 0; 98 | 99 | die "Must provide entry" unless 100 | my $entry = $args->{entry}; 101 | 102 | my $repo = $entry->{repo} //= ''; 103 | 104 | if ( ! defined $entry->{name} ) { 105 | ### FIXME this is unnecessarily Git-specific 106 | $entry->{name} = ( $repo =~ m|([^/]+).git$| ) ? $1 : ''; 107 | } 108 | 109 | $entry->{tags} //= ''; 110 | 111 | my $return = { 112 | number => $count , 113 | name => $entry->{name} , 114 | path => $entry->{path} , 115 | repo => $repo , 116 | type => $entry->{type} , 117 | tags => $entry->{tags} , 118 | }; 119 | 120 | $return->{label} = $args->{label} if $args->{label}; 121 | 122 | return $return; 123 | } 124 | 125 | =method add_tags 126 | 127 | Given a list of tags, add them to the current repo object. 128 | 129 | =cut 130 | 131 | sub add_tags { 132 | my( $self, @tags ) = @_; 133 | 134 | $self->tags( join ' ', uniq sort @tags, split ' ', $self->tags ); 135 | } 136 | 137 | =method in_writable_format 138 | 139 | Returns a serialized representation of the repository for writing out in a 140 | config file. 141 | 142 | =cut 143 | 144 | sub in_writable_format { 145 | my $self = shift; 146 | 147 | my $writeable = { 148 | name => $self->name , 149 | path => $self->path , 150 | }; 151 | 152 | foreach ( qw/ repo tags type /) { 153 | $writeable->{$_} = $self->$_ if $self->$_; 154 | } 155 | 156 | return $writeable; 157 | } 158 | 159 | =method remove_tags 160 | 161 | Given a list of tags, remove them from the current repo object. 162 | 163 | Passing a tag that is not on the current repo object will silently no-op. 164 | 165 | =cut 166 | 167 | sub remove_tags { 168 | my( $self, @tags ) = @_; 169 | 170 | my %verboten = map { $_ => 1 } @tags; 171 | 172 | $self->tags( join ' ', grep { !$verboten{$_} } split ' ', $self->tags ); 173 | } 174 | 175 | =for Pod::Coverage BUILDARGS 176 | 177 | =cut 178 | 179 | 1; 180 | -------------------------------------------------------------------------------- /lib/App/GitGot/Repo/Git.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Repo::Git; 2 | 3 | # ABSTRACT: Git repo objects 4 | use 5.014; 5 | 6 | use Git::Wrapper; 7 | use Test::MockObject; 8 | use Try::Tiny; 9 | use Types::Standard -types; 10 | 11 | use App::GitGot::Types qw/ GitWrapper /; 12 | 13 | use Moo; 14 | extends 'App::GitGot::Repo'; 15 | use namespace::autoclean; 16 | 17 | has '+type' => ( default => 'git' ); 18 | 19 | has '_wrapper' => ( 20 | is => 'lazy' , 21 | isa => GitWrapper , 22 | handles => [ qw/ 23 | checkout 24 | cherry 25 | clone 26 | config 27 | fetch 28 | gc 29 | pull 30 | push 31 | remote 32 | status 33 | symbolic_ref 34 | / ] , 35 | ); 36 | 37 | sub _build__wrapper { 38 | my $self = shift; 39 | 40 | # for testing... 41 | if ( $ENV{GITGOT_FAKE_GIT_WRAPPER} ) { 42 | my $mock = Test::MockObject->new; 43 | $mock->set_isa( 'Git::Wrapper' ); 44 | foreach my $method ( qw/ cherry clone fetch gc pull 45 | remote symbolic_ref / ) { 46 | $mock->mock( $method => sub { return( '1' )}); 47 | } 48 | $mock->mock( 'checkout' => sub { } ); 49 | $mock->mock( 'status' => sub { package 50 | MyFake; sub get { return () }; return bless {} , 'MyFake' } ); 51 | $mock->mock( 'config' => sub { 0 }); 52 | $mock->mock( 'ERR' => sub { [ ] }); 53 | 54 | return $mock 55 | } 56 | else { 57 | return Git::Wrapper->new( $self->path ) 58 | || die "Can't make Git::Wrapper"; 59 | } 60 | } 61 | 62 | 63 | =method current_branch 64 | 65 | Returns the current branch checked out by this repository object. 66 | 67 | =cut 68 | 69 | sub current_branch { 70 | my $self = shift; 71 | 72 | my $branch; 73 | 74 | try { 75 | ( $branch ) = $self->symbolic_ref( 'HEAD' ); 76 | $branch =~ s|^refs/heads/|| if $branch; 77 | } 78 | catch { 79 | die $_ unless $_ && $_->isa('Git::Wrapper::Exception') 80 | && $_->error eq "fatal: ref HEAD is not a symbolic ref\n" 81 | }; 82 | 83 | return $branch; 84 | } 85 | 86 | =method current_remote_branch 87 | 88 | Returns the remote branch for the branch currently checked out by this repo 89 | object, or 0 if that information can't be extracted (if, for example, the 90 | branch doesn't have a remote.) 91 | 92 | =cut 93 | 94 | sub current_remote_branch { 95 | my( $self ) = shift; 96 | 97 | my $remote = 0; 98 | 99 | if ( my $branch = $self->current_branch ) { 100 | try { 101 | ( $remote ) = $self->config( "branch.$branch.remote" ); 102 | } 103 | catch { 104 | ## not the most informative return.... 105 | return 0 if $_ && $_->isa('Git::Wrapper::Exception') && $_->{status} eq '1'; 106 | }; 107 | } 108 | 109 | return $remote; 110 | } 111 | 112 | 1; 113 | -------------------------------------------------------------------------------- /lib/App/GitGot/Repositories.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Repositories; 2 | 3 | # ABSTRACT: Object holding a collection of repositories 4 | use 5.014; 5 | 6 | use Types::Standard -types; 7 | 8 | use App::GitGot::Types qw/ GotRepo /; 9 | 10 | use Moo; 11 | use MooX::HandlesVia; 12 | use namespace::autoclean; 13 | 14 | use overload '@{}' => sub { $_[0]->all }; 15 | 16 | =attr repos 17 | 18 | Array of the C objects in the collection. 19 | 20 | =cut 21 | 22 | has repos => ( 23 | is => 'ro', 24 | isa => ArrayRef[GotRepo], 25 | default => sub { [] }, 26 | required => 1, 27 | handles_via => 'Array' , 28 | handles => { 29 | all => 'elements' 30 | } 31 | ); 32 | 33 | =method name 34 | 35 | Given a repo name, will return a L object 36 | containing the subset of repos from the current object that have that name. 37 | 38 | =cut 39 | 40 | sub name { 41 | my( $self, $name ) = @_; 42 | 43 | return App::GitGot::Repositories->new( repos => [ 44 | grep { $_->{name} eq $name } $self->all 45 | ]); 46 | } 47 | 48 | =method tags 49 | 50 | Given a list of tag names, returns a L object 51 | containing the subset of repos from the current object that have one or more 52 | of those tags. 53 | 54 | =cut 55 | 56 | sub tags { 57 | my( $self, @tags ) = @_; 58 | 59 | my @repos = $self->all; 60 | 61 | for my $tag ( @tags ) { 62 | @repos = grep { $_->tags =~ /\b$tag\b/ } @repos; 63 | } 64 | 65 | return App::GitGot::Repositories->new( repos => \@repos ); 66 | } 67 | 68 | 1; 69 | -------------------------------------------------------------------------------- /lib/App/GitGot/Types.pm: -------------------------------------------------------------------------------- 1 | package App::GitGot::Types; 2 | 3 | # ABSTRACT: GitGot type library 4 | use 5.014; ## strict, unicode_strings 5 | use warnings; 6 | 7 | use Type::Library 8 | -base , 9 | -declare => qw/ 10 | GitWrapper 11 | GotOutputter 12 | GotRepo 13 | /; 14 | use Type::Utils -all; 15 | use Types::Standard -types; 16 | 17 | class_type GitWrapper , { class => "Git::Wrapper" }; 18 | class_type GotOutputter , { class => "App::GitGot::Outputter" }; 19 | class_type GotRepo , { class => "App::GitGot::Repo" }; 20 | 21 | 1; 22 | -------------------------------------------------------------------------------- /releases/App-GitGot-0.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-0.2.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-0.3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-0.3.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-0.4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-0.4.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-0.5.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-0.5.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-0.6.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-0.6.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-0.7.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-0.7.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-0.8.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-0.8.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-0.9.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-0.9.1.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-0.9.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-0.9.2.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-0.9.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-0.9.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.0.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.01.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.01.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.02.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.02.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.03.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.03.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.04.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.04.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.05.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.05.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.06.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.06.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.07.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.07.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.08.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.08.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.09.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.09.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.10.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.10.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.11.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.11.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.12.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.12.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.13.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.13.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.14.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.14.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.15.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.15.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.16.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.16.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.17.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.17.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.18.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.18.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.19.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.19.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.20.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.20.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.21.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.21.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.22.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.22.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.23-TRIAL.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.23-TRIAL.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.24.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.24.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.25.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.25.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.26-TRIAL.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.26-TRIAL.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.27-TRIAL.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.27-TRIAL.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.28-TRIAL.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.28-TRIAL.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.29-TRIAL.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.29-TRIAL.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.30.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.30.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.31-TRIAL.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.31-TRIAL.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.32-TRIAL.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.32-TRIAL.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.330.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.330.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.331-TRIAL.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.331-TRIAL.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.332.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.332.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.333.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.333.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.334.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.334.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.335.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.335.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.336.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.336.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.337.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.337.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.338.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.338.tar.gz -------------------------------------------------------------------------------- /releases/App-GitGot-1.339.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genehack/app-gitgot/70808fe0c106f1797fd91d719610cabfda3542b4/releases/App-GitGot-1.339.tar.gz -------------------------------------------------------------------------------- /t/01-run.t: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env perl 2 | use 5.014; # strict, unicode_strings 3 | use warnings; 4 | use Test::Class::Load qw; 5 | use Test::More; 6 | 7 | use File::Temp qw/ tempdir /; 8 | use Git::Wrapper; 9 | 10 | my $dir = tempdir(CLEANUP => 1); 11 | my $git = Git::Wrapper->new($dir); 12 | 13 | my $version = $git->version; 14 | diag( "Testing with git version: " . $version ); 15 | -------------------------------------------------------------------------------- /t/02-add.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::File; 10 | use Test::More; 11 | 12 | use App::Cmd::Tester; 13 | use App::GitGot; 14 | use App::GitGot::Command::add; 15 | use Class::Load qw/ try_load_class /; 16 | use Cwd; 17 | use YAML qw/ LoadFile /; 18 | 19 | my $dir = Test::BASE::create_tempdir_and_chdir(); 20 | 21 | { 22 | my $result = test_app( 'App::GitGot' => [ 'add' ]); 23 | 24 | is $result->stdout , '' , 'empty STDOUT'; 25 | like $result->stderr , qr/Non-git repos not supported/ , 'expected error on STDERR'; 26 | is $result->exit_code , 1 , 'exit with 1'; 27 | } 28 | 29 | my $config = "$dir/gitgot"; 30 | file_not_exists_ok $config , 'no config'; 31 | 32 | Test::BASE::build_fake_git_repo(); 33 | 34 | { 35 | my $result = test_app( 'App::GitGot' => [ 'add' , '-f' , $config , '-D' ]); 36 | 37 | like $result->stdout, qr/ 38 | Add \s repository \s at \s '.*?'\? \s+ \(y\/n\) \s \[y\]: \s y \s+ 39 | Name\? \s+ \[foo.git\]: \s+ foo.git \s+ 40 | Tracking \s remote\? \s+ : \s+ 41 | Tags\? \s+ \[\]: 42 | /x, 'interaction auto-filled'; 43 | 44 | my $err = $result->stderr; 45 | $err =~ s/-\w on unopened filehandle STDOUT.*?\n//g; # test_app mess with STDOUT 46 | is $err , '' , 'nothing on stderr'; 47 | is $result->exit_code , 0 , 'exit with 0'; 48 | 49 | file_exists_ok $config , 'config exists'; 50 | 51 | my $entry = LoadFile( $config ); 52 | is( $entry->[0]{name} , 'foo.git' , 'expected name' ); 53 | is( $entry->[0]{type} , 'git' , 'expected type' ); 54 | is( $entry->[0]{path} , getcwd() , 'expected path' ); 55 | } 56 | 57 | { 58 | my $result = test_app( 'App::GitGot' => [ 'add' , '-f' , $config , '-D' ]); 59 | 60 | is $result->stdout , '' , 'empty STDOUT'; 61 | like $result->stderr , 62 | qr/Repository at '.+' already registered with Got, skipping/, 63 | 'msg that cannot add same repo twice on STDERR'; 64 | is $result->exit_code , 0, 'exit with 0'; 65 | } 66 | 67 | chdir('/'); ## clean up tempfiles 68 | 69 | subtest 'recursive behavior' => sub { 70 | SKIP: 71 | { 72 | skip 'Test requires Path::Iterator::Rule' , 1 73 | unless try_load_class( 'Path::Iterator::Rule' ); 74 | 75 | my $dir = Test::BASE::create_tempdir_and_chdir(); 76 | 77 | for my $repo ( qw/ alpha beta / ) { 78 | Test::BASE::build_fake_git_repo( $repo ); 79 | chdir '..'; 80 | } 81 | my $config = "$dir/gitgot"; 82 | 83 | my $result = test_app( 'App::GitGot' => [ 'add' , '-f' , $config , '-D', '--recursive' ]); 84 | $result->error 85 | and diag("App::GitGot add -f $config -D --recursive failed: " . $result->error); 86 | 87 | is_deeply [ sort map { $_->{name} } @{ LoadFile($config) } ] => [ 88 | qw/ alpha beta / 89 | ], 'all repositores detected'; 90 | 91 | chdir(); 92 | } 93 | }; 94 | 95 | done_testing(); 96 | -------------------------------------------------------------------------------- /t/03-chdir.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::More; 10 | 11 | use App::Cmd::Tester; 12 | use App::GitGot; 13 | 14 | my( $config , $dir ) = Test::BASE::write_fake_config(); 15 | 16 | { 17 | my $result = test_app( 'App::GitGot' => [ 'chdir' , '-f' , $config ]); 18 | 19 | is $result->stdout , '' , 'nothing on STDOUT'; 20 | like $result->stderr , 21 | qr/ERROR: You need to select a single repo/ , 22 | 'need to select a repo'; 23 | is $result->exit_code , 1 , 'exit with 1'; 24 | } 25 | 26 | { 27 | my $result = test_app( 'App::GitGot' => [ 'chdir' , '-f' , $config , 2 ]); 28 | 29 | is $result->stdout , '' , 'no output'; 30 | like $result->stderr , qr/Failed to chdir to repo/ , 'msg about non-existant dir'; 31 | is $result->exit_code , 1 , 'exit with 1'; 32 | } 33 | 34 | chdir('/'); ## clean up tempfiles 35 | done_testing(); 36 | -------------------------------------------------------------------------------- /t/04-clone.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::File; 10 | use Test::More; 11 | 12 | use App::Cmd::Tester; 13 | use App::GitGot; 14 | use Path::Tiny; 15 | use YAML qw/ LoadFile /; 16 | 17 | my $dir = Test::BASE::create_tempdir_and_chdir(); 18 | my $config = path( "$dir/gitgot" ); 19 | file_not_exists_ok $config , 'config does not exist'; 20 | 21 | $ENV{GITGOT_FAKE_GIT_WRAPPER} = 1; 22 | 23 | { 24 | my $result = test_app( 'App::GitGot' => [ 'clone' , '-f' , $config ]); 25 | 26 | is $result->stdout , '' , 'nothing on STDOUT'; 27 | like $result->stderr , 28 | qr/ERROR: Need the URL to clone/ , 29 | 'need to give a URL'; 30 | is $result->exit_code , 1 , 'exit with 1'; 31 | 32 | file_not_exists_ok $config , 'failed command does not create config'; 33 | } 34 | 35 | { 36 | my $result = test_app( 'App::GitGot' => [ 'clone' , '-f' , $config , '-Dq' , 37 | 'http://genehack.org/fake-git-repo.git' ]); 38 | 39 | is $result->stdout , '' , 'no output'; 40 | is $result->stderr , '' , 'nothing on STDERR'; 41 | is $result->exit_code , 0 , 'exit with 0'; 42 | 43 | file_exists_ok $config , 'now config exists'; 44 | 45 | my $entry = LoadFile( $config ); 46 | is( $entry->[0]{name} , 'fake-git-repo' , 'expected name' ); 47 | is( $entry->[0]{type} , 'git' , 'expected type' ); 48 | is( $entry->[0]{path} , path( "$dir/fake-git-repo" ) , 'expected path' ); 49 | } 50 | 51 | chdir('/'); ## clean up temp files 52 | done_testing(); 53 | -------------------------------------------------------------------------------- /t/05-fork.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::File; 10 | use Test::MockObject; 11 | use Test::More; 12 | 13 | BEGIN { 14 | my $mock = Test::MockObject->new(); 15 | $mock->fake_module( 16 | 'Net::GitHub::V3::Repositories' , 17 | 'fork' => sub { 1 } , 18 | ); 19 | $mock->fake_new( 'Net::GitHub' ); 20 | $mock->mock( repos => sub { $mock } ); 21 | $mock->mock( create_fork => sub {{ ssl_url => 'yeahboi' }} ); 22 | } 23 | 24 | use App::Cmd::Tester; 25 | use App::GitGot; 26 | use Path::Tiny; 27 | use YAML qw/ LoadFile /; 28 | 29 | my $dir = Test::BASE::create_tempdir_and_chdir(); 30 | my $config = path( "$dir/gitgot" ); 31 | file_not_exists_ok $config , 'config does not exist'; 32 | 33 | $ENV{HOME} = $dir; 34 | 35 | { 36 | my $result = test_app( 'App::GitGot' => [ 'fork' , '-f' , $config ]); 37 | 38 | is $result->stdout , '' , 'nothing on STDOUT'; 39 | like $result->stderr , 40 | qr/ERROR: Need the URL of a repo to fork/ , 41 | 'need to give a URL'; 42 | is $result->exit_code , 1 , 'exit with 1'; 43 | 44 | file_not_exists_ok $config , 'failed command does not create config'; 45 | } 46 | 47 | { 48 | my $result = test_app( 'App::GitGot' => [ 'fork' , '-f' , $config , 49 | 'http://not.github.org/' ]); 50 | is $result->stdout , '' , 'nothing on STDOUT'; 51 | like $result->stderr , 52 | qr|ERROR: Can't parse 'http://not.github.org/'| , 53 | 'need repo URL'; 54 | is $result->exit_code , 1 , 'exit with 1'; 55 | 56 | file_not_exists_ok $config , 'failed command does not create config'; 57 | } 58 | 59 | Test::BASE::create_github_identity_file(); 60 | 61 | { 62 | my $result = test_app( 'App::GitGot' => [ 'fork' , '-f' , $config , 'http://not.github.org/' ]); 63 | 64 | is $result->stdout , '' , 'nothing on STDOUT'; 65 | like $result->stderr , 66 | qr|ERROR: Can't parse 'http://not.github.org| , 67 | 'need to give a *github* URL'; 68 | is $result->exit_code , 1 , 'exit with 1'; 69 | 70 | file_not_exists_ok $config , 'failed command does not create config'; 71 | } 72 | 73 | { 74 | my $result = test_app( 'App::GitGot' => [ 'fork' , '-f' , $config , '--noclone' , '-q' , 75 | 'http://github.com/genehack/fake-git-repo.git' ]); 76 | 77 | is $result->stdout , '' , 'no output'; 78 | is $result->stderr , '' , 'nothing on STDERR'; 79 | is $result->exit_code , 0 , 'exit with 0'; 80 | 81 | file_exists_ok $config , 'now config exists'; 82 | 83 | my $entry = LoadFile( $config ); 84 | is( $entry->[0]{name} , 'fake-git-repo' , 'expected name' ); 85 | is( $entry->[0]{type} , 'git' , 'expected type' ); 86 | is( $entry->[0]{path} , path( "$dir/fake-git-repo" ) , 'expected path' ); 87 | } 88 | 89 | chdir('/'); ## clean up temp files 90 | done_testing(); 91 | -------------------------------------------------------------------------------- /t/06-list.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::More; 10 | 11 | use App::Cmd::Tester; 12 | use App::GitGot; 13 | use YAML qw/ DumpFile /; 14 | 15 | my( $config , $dir ) = Test::BASE::write_fake_config(); 16 | 17 | { 18 | my $result = test_app( 'App::GitGot' => [ 'list' , '-f' , $config ]); 19 | 20 | like $result->stdout , 21 | qr|1\)\s*bar\.git\s*git\s*github\@github.com:genehack/bar.git| , 22 | 'first repo'; 23 | 24 | like $result->stdout , 25 | qr|2\)\s*bargle\.git\s*git\s*github\@github\.com:genehack/bargle\.git\s*\(Not checked out\)| , 26 | 'second repo'; 27 | 28 | 29 | like $result->stdout , 30 | qr|3\)\s*foo\.git\s*git\s*ERROR: No remote and no repo\?\!| , 31 | 'second repo'; 32 | 33 | is $result->stderr , '' , 'nothing on STDERR'; 34 | is $result->exit_code , 0 , 'exit with 0'; 35 | } 36 | 37 | { 38 | my $result = test_app( 'App::GitGot' => [ 'list' , '-f' , $config , '-q' ]); 39 | 40 | like $result->stdout , qr|1\)\s*bar\.git| , 'first repo'; 41 | like $result->stdout , qr|2\)\s*bargle\.git| , 'second repo'; 42 | like $result->stdout , qr|3\)\s*foo\.git| , 'third repo'; 43 | 44 | is $result->stderr , '' , 'nothing on STDERR'; 45 | is $result->exit_code , 0 , 'exit with 0'; 46 | } 47 | 48 | { 49 | my $result = test_app( 'App::GitGot' => [ 'list' , '-f' , $config , '-v' ]); 50 | 51 | like $result->stdout , 52 | qr|1\)\s*bar\.git\s*git\s*github\@github.com:genehack/bar.git|, 53 | 'first repo'; 54 | 55 | like $result->stdout , 56 | qr|3\)\s*foo\.git\s*git\s*ERROR: No remote and no repo\?\!| , 57 | 'third repo'; 58 | 59 | is $result->stderr , '' , 'nothing on STDERR'; 60 | is $result->exit_code , 0 , 'exit with 0'; 61 | } 62 | 63 | chdir('/'); ## clean up temp files 64 | done_testing(); 65 | -------------------------------------------------------------------------------- /t/07-remove.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::More; 10 | 11 | use App::Cmd::Tester; 12 | use App::GitGot; 13 | use YAML qw/ DumpFile LoadFile /; 14 | 15 | my( $config , $dir ) = Test::BASE::write_fake_config(); 16 | 17 | { 18 | my $result = test_app( 'App::GitGot' => [ 'remove' , '-f' , $config ]); 19 | 20 | is $result->stdout , '' , 'nothing on STDOUT'; 21 | like $result->stderr , 22 | qr/ERROR: You need to select one or more repos to remove/ , 23 | 'need to give some repos'; 24 | is $result->exit_code , 1 , 'exit with 1'; 25 | } 26 | 27 | { 28 | my $result = test_app( 'App::GitGot' => [ 'remove' , '-f' , $config , 1 , '--force' ]); 29 | 30 | is $result->stdout , '' , 'nothing on STDOUT'; 31 | is $result->stderr , '' , 'nothing on STDERR'; 32 | is $result->exit_code , 0 , 'exit with 0'; 33 | 34 | my $config = LoadFile( $config ); 35 | my $expected = [{ 36 | name => 'bargle.git' , 37 | path => "$dir/bargle.git" , 38 | repo => 'github@github.com:genehack/bargle.git' , 39 | type => 'git' , 40 | },{ 41 | name => 'foo.git' , 42 | path => "$dir/foo.git" , 43 | type => 'git' , 44 | tags => 'foo' , 45 | },{ 46 | name => 'xxx.git' , 47 | path => "$dir/xxx.git" , 48 | type => 'git' 49 | }]; 50 | is_deeply( $config , $expected , 'deleted repo' ); 51 | } 52 | 53 | { 54 | my $result = test_app( 'App::GitGot' => [ 'remove' , '-f' , $config , 2 , '--force' , '-v' ]); 55 | 56 | like $result->stdout , qr/^Removed repo 'foo\.git'/ , 'expected on STDOUT'; 57 | is $result->stderr , '' , 'nothing on STDERR'; 58 | is $result->exit_code , 0 , 'exit with 0'; 59 | 60 | my $config = LoadFile( $config ); 61 | my $expected = [{ 62 | name => 'bargle.git' , 63 | path => "$dir/bargle.git" , 64 | repo => 'github@github.com:genehack/bargle.git' , 65 | type => 'git' , 66 | },{ 67 | name => 'xxx.git' , 68 | path => "$dir/xxx.git" , 69 | type => 'git' 70 | }]; 71 | is_deeply( $config , $expected , 'deleted repo' ); 72 | } 73 | 74 | chdir('/'); # clean up temp files 75 | done_testing(); 76 | -------------------------------------------------------------------------------- /t/08-status.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::More; 10 | 11 | use App::Cmd::Tester; 12 | use App::GitGot; 13 | 14 | my( $config , $dir ) = Test::BASE::write_fake_config(); 15 | 16 | $ENV{GITGOT_FAKE_GIT_WRAPPER} = 1; 17 | 18 | { 19 | my $result = test_app( 'App::GitGot' => [ 'status' , '-f' , $config , '-C' ]); 20 | 21 | like $result->stdout , qr|1\)\s+bar\.git\s+\:\s+OK| , 'repo 1'; 22 | like $result->stdout , qr|3\)\s+foo\.git\s+\:\s+ERROR: repo 'foo.git' does not exist| , 'repo 3'; 23 | like $result->stdout , qr|4\)\s+xxx\.git\s+\:\s+OK| , 'repo 4'; 24 | 25 | is $result->stderr , '' , 'nothing on STDERR'; 26 | is $result->exit_code , 0 , 'exit with 0'; 27 | } 28 | 29 | { 30 | my $result = test_app( 'App::GitGot' => [ 'status' , '-f' , $config , '-C' , '-v' ]); 31 | 32 | like $result->stdout , qr|1\)\s+bar\.git\s+\:\s+OK| , 'repo 1'; 33 | like $result->stdout , qr|3\)\s+foo\.git\s+\:\s+ERROR: repo 'foo.git' does not exist| , 'repo 3'; 34 | like $result->stdout , qr|4\)\s+xxx\.git\s+\:\s+OK| , 'repo 4'; 35 | 36 | is $result->stderr , '' , 'nothing on STDERR'; 37 | is $result->exit_code , 0 , 'exit with 0'; 38 | } 39 | 40 | { 41 | my $result = test_app( 'App::GitGot' => [ 'status' , '-f' , $config , '-C' , '--show-branch' ]); 42 | 43 | like $result->stdout , qr|1\)\s+bar\.git\s+\:\s+OK\s+\[1\]| , 'repo 1'; 44 | like $result->stdout , qr|3\)\s+foo\.git\s+\:\s+ERROR: repo 'foo.git' does not exist| , 'repo 3'; 45 | like $result->stdout , qr|4\)\s+xxx\.git\s+\:\s+OK\s\[1\]| , 'repo 4'; 46 | 47 | is $result->stderr , '' , 'nothing on STDERR'; 48 | is $result->exit_code , 0 , 'exit with 0'; 49 | } 50 | 51 | chdir('/'); ## clean up temp files 52 | done_testing(); 53 | -------------------------------------------------------------------------------- /t/09-update.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::More; 10 | 11 | use App::Cmd::Tester; 12 | use App::GitGot; 13 | 14 | my( $config , $dir ) = Test::BASE::write_fake_config(); 15 | 16 | $ENV{GITGOT_FAKE_GIT_WRAPPER} = 1; 17 | 18 | { 19 | my $result = test_app( 'App::GitGot' => [ 'update' , '-f' , $config , '-C' ]); 20 | 21 | like $result->stdout , qr|1\)\s+bar\.git\s+\:\s+Updated| , 'repo 1'; 22 | like $result->stdout , qr|2\)\s+bargle\.git\s+\:\s+Checked out| , 'repo 2'; 23 | is $result->stderr , '' , 'nothing on STDERR'; 24 | is $result->exit_code , 0 , 'exit with 0'; 25 | } 26 | 27 | chdir('/'); ## clean up temp files 28 | done_testing(); 29 | -------------------------------------------------------------------------------- /t/10-gc.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::More; 10 | 11 | use App::Cmd::Tester; 12 | use App::GitGot; 13 | 14 | my( $config , $dir ) = Test::BASE::write_fake_config(); 15 | 16 | $ENV{GITGOT_FAKE_GIT_WRAPPER} = 1; 17 | 18 | { 19 | my $result = test_app( 'App::GitGot' => [ 'gc' , '-f' , $config , '-C' ]); 20 | 21 | like $result->stdout , qr|1\)\s+bar\.git\s+\:\s+COLLECTED| , 'repo 1'; 22 | like $result->stdout , qr|2\)\s+bargle\.git\s+\:\s+COLLECTED| , 'repo 2'; 23 | is $result->stderr , '' , 'nothing on STDERR'; 24 | is $result->exit_code , 0 , 'exit with 0'; 25 | } 26 | 27 | { 28 | my $result = test_app( 'App::GitGot' => [ 'gc' , '-f' , $config , '-C' , '-q' ]); 29 | 30 | is $result->stdout , '' , 'nothing on STDOUT'; 31 | is $result->stderr , '' , 'nothing on STDERR'; 32 | is $result->exit_code , 0 , 'exit with 0'; 33 | } 34 | 35 | chdir('/'); ## clean up temp files 36 | done_testing(); 37 | -------------------------------------------------------------------------------- /t/11-push.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::More; 10 | 11 | use App::Cmd::Tester; 12 | use App::GitGot; 13 | 14 | my( $config , $dir ) = Test::BASE::write_fake_config(); 15 | 16 | $ENV{GITGOT_FAKE_GIT_WRAPPER} = 1; 17 | 18 | { 19 | my $result = test_app( 'App::GitGot' => [ 'push' , '-f' , $config , '-C' ]); 20 | 21 | like $result->stdout , qr|1\)\s+bar\.git\s+\:\s+Nothing to push| , 'repo 1'; 22 | like $result->stdout , qr|2\)\s+bargle\.git\s+\:\s+Nothing to push| , 'repo 2'; 23 | is $result->stderr , '' , 'nothing on STDERR'; 24 | is $result->exit_code , 0 , 'exit with 0'; 25 | } 26 | 27 | { 28 | my $result = test_app( 'App::GitGot' => [ 'push' , '-f' , $config , '-C' , '-q' ]); 29 | 30 | is $result->stdout , '' , 'nothing on STDOUT'; 31 | is $result->stderr , '' , 'nothing on STDERR'; 32 | is $result->exit_code , 0 , 'exit with 0'; 33 | } 34 | 35 | chdir('/'); ## clean up temp files 36 | done_testing(); 37 | -------------------------------------------------------------------------------- /t/12-fetch.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::More; 10 | 11 | use App::Cmd::Tester; 12 | use App::GitGot; 13 | 14 | my( $config , $dir ) = Test::BASE::write_fake_config(); 15 | 16 | $ENV{GITGOT_FAKE_GIT_WRAPPER} = 1; 17 | 18 | { 19 | my $result = test_app( 'App::GitGot' => [ 'fetch' , '-f' , $config , '-C' ]); 20 | 21 | like $result->stdout , qr|1\)\s+bar\.git\s+\:\s+Up to date| , 'repo 1'; 22 | like $result->stdout , qr|2\)\s+bargle\.git\s+\:\s+Checked out| , 'repo 2'; 23 | is $result->stderr , '' , 'nothing on STDERR'; 24 | is $result->exit_code , 0 , 'exit with 0'; 25 | } 26 | 27 | chdir('/'); ## clean up temp files 28 | done_testing(); 29 | -------------------------------------------------------------------------------- /t/13-do.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::File; 10 | use Test::More; 11 | 12 | use App::Cmd::Tester; 13 | 14 | use App::GitGot; 15 | use App::GitGot::Command::add; 16 | use App::GitGot::Command::do; 17 | 18 | use Capture::Tiny qw/ capture /; 19 | 20 | use Cwd; 21 | 22 | my $dir = Test::BASE::create_tempdir_and_chdir(); 23 | my $config = "$dir/gitgot"; 24 | 25 | for my $repo ( qw/ alpha beta / ) { 26 | Test::BASE::build_fake_git_repo($repo); 27 | my $result = test_app( 'App::GitGot' => [ 'add' , '-f' , $config , '-D' ]); 28 | $result->error 29 | and diag("App::GitGot add -f $config -D failed: " . $result->error); 30 | open my $fh, '>', "$repo.txt"; 31 | print $fh "test"; 32 | chdir '..'; 33 | } 34 | 35 | my $cmd = $^O eq 'MSWin32' ? 'dir' : 'ls'; 36 | 37 | @ARGV = ( qw/ do -f /, $config, '--command' , $cmd , '--all' ); 38 | 39 | my( $stdout, $stderr, $exit ) = capture { 40 | App::GitGot->run; 41 | }; 42 | 43 | like $stdout , qr/##.*alpha.*alpha\.txt/s, 'alpha is listed'; 44 | like $stdout , qr/##.*beta.*beta\.txt/s, 'beta is listed'; 45 | is $stderr , '' , 'nothing on stderr'; 46 | 47 | @ARGV = ( qw/ do -f /, $config, '--with_repo' , '--command' , $cmd , '--all' ); 48 | 49 | ( $stdout, $stderr, $exit ) = capture { 50 | App::GitGot->run; 51 | }; 52 | 53 | like $stdout , qr/alpha: .*alpha\.txt/, 'output preprended with repo name'; 54 | 55 | 56 | chdir('/'); ## clean up temp files 57 | done_testing(); 58 | -------------------------------------------------------------------------------- /t/14-move.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::File; 10 | use Test::More; 11 | 12 | use App::Cmd::Tester; 13 | 14 | use App::GitGot; 15 | use App::GitGot::Command::add; 16 | use App::GitGot::Command::move; 17 | 18 | use Cwd; 19 | use YAML; 20 | 21 | my $dir = Test::BASE::create_tempdir_and_chdir(); 22 | my $config = "$dir/gitgot"; 23 | 24 | Test::BASE::build_fake_git_repo('alpha'); 25 | 26 | my $one = test_app( 'App::GitGot' => [ 'add' , '-f' , $config , '-D' ]); 27 | $one->error 28 | and diag("App::GitGot add -f $config -D failed: " . $one->error); 29 | chdir '..'; 30 | 31 | my $two = test_app( 'App::GitGot' => [ 'move', '-f', $config, '--dest', "$dir/gamma", 'alpha' ] ); 32 | $two->error 33 | and diag("App::GitGot move -f $config --dest $dir/gamma alpha failed: " . $two->error); 34 | 35 | ok ! -d 'alpha', 'alpha is gone'; 36 | ok -d 'gamma', '...and replaced by gamma'; 37 | 38 | $config = YAML::LoadFile( $config ); 39 | 40 | is $config->[0]->{name} => 'alpha', 'right repo'; 41 | like $config->[0]->{path} => qr#.*/gamma#, 'moved to its new location'; 42 | 43 | chdir('/'); ## clean up temp files 44 | done_testing(); 45 | -------------------------------------------------------------------------------- /t/15-tags.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::File; 10 | use Test::More; 11 | 12 | use App::Cmd::Tester; 13 | 14 | use App::GitGot; 15 | use App::GitGot::Command::add; 16 | use App::GitGot::Command::tag; 17 | 18 | use Cwd; 19 | use YAML; 20 | 21 | my $dir = Test::BASE::create_tempdir_and_chdir(); 22 | my $config = "$dir/gitgot"; 23 | 24 | Test::BASE::build_fake_git_repo(); 25 | my $result = test_app( 'App::GitGot' => [ 'add' , '-f' , $config , '-D' ]); 26 | $result->error 27 | and diag("App::GitGot add -f $config -D failed: " . $result->error); 28 | 29 | { 30 | my $result = test_app( 'App::GitGot' => [ 'tag', '-f', $config, ] ); 31 | 32 | is $result->stdout => '', 'no tag to begin with'; 33 | } 34 | 35 | subtest 'add tags' => sub { 36 | my $result = test_app( 'App::GitGot' => [ 'tag', '-f', $config, '--add', qw/ perl git / ] ); 37 | is $result->stdout => "tags added\n", 'added tags'; 38 | 39 | $result = test_app( 'App::GitGot' => [ 'tag', '-f', $config, ] ); 40 | like $result->stdout => qr/git\s*perl/m, 'tags are listed'; 41 | }; 42 | 43 | subtest 'remove tags' => sub { 44 | my $result = test_app( 'App::GitGot' => [ 'tag', '-f', $config, '--remove', qw/ git / ] ); 45 | is $result->stdout => "tags removed\n", 'remove tags'; 46 | 47 | $result = test_app( 'App::GitGot' => [ 'tag', '-f', $config, ] ); 48 | like $result->stdout => qr/^\s*perl\s*$/m, 'no more git' 49 | }; 50 | 51 | my $yaml = YAML::LoadFile( $config ); 52 | 53 | is $yaml->[0]->{tags} => 'perl', 'config holds the tags'; 54 | 55 | $result = test_app( 'App::GitGot' => [ 'tag', '-f', $config, '--remove', '--add', qw/ git / ] ); 56 | 57 | is $result->stdout => "can't --add and --remove at the same time\n", 'simultaneous add/remove'; 58 | 59 | chdir '..'; 60 | my $current_dir = getcwd(); 61 | 62 | $result = test_app( 'App::GitGot' => [ 'tag', '-f', $config, '--add', qw/ perl git / ] ); 63 | 64 | is $result->error => "$current_dir doesn't seem to be in a git directory\n", 'outside of repo'; 65 | 66 | chdir('/'); ## clean up temp files 67 | done_testing(); 68 | -------------------------------------------------------------------------------- /t/16-checkout.t: -------------------------------------------------------------------------------- 1 | #! perl 2 | 3 | use 5.014; # strict, unicode_strings 4 | use warnings; 5 | use autodie; 6 | 7 | use lib 't/lib'; 8 | use Test::BASE; 9 | use Test::More; 10 | 11 | use App::Cmd::Tester; 12 | use App::GitGot; 13 | 14 | my( $config , $dir ) = Test::BASE::write_fake_config(); 15 | 16 | $ENV{GITGOT_FAKE_GIT_WRAPPER} = 1; 17 | 18 | { 19 | my $result = test_app( 'App::GitGot' => [ 'checkout' , '-f' , $config , '-C', '--branch', 'master' ]); 20 | 21 | like $result->stdout , qr|1\)\s+bar\.git\s+\:\s+OK| , 'repo 1'; 22 | like $result->stdout , qr|2\)\s+bargle\.git\s+\:\s+OK| , 'repo 2'; 23 | is $result->stderr , '' , 'nothing on STDERR'; 24 | is $result->exit_code , 0 , 'exit with 0'; 25 | } 26 | 27 | chdir('/'); ## clean up temp files 28 | done_testing(); 29 | -------------------------------------------------------------------------------- /t/lib/Test/App/GitGot/Repo.pm: -------------------------------------------------------------------------------- 1 | package Test::App::GitGot::Repo; 2 | use parent 'Test::BASE'; 3 | 4 | use 5.014; # strict, unicode_strings 5 | use warnings; 6 | 7 | use App::GitGot::Repo; 8 | use File::Temp qw/ tempdir /; 9 | use Path::Tiny; 10 | use Test::Exception; 11 | use Test::More; 12 | 13 | sub fixtures :Test(startup) { 14 | my $test = shift; 15 | 16 | $test->{lib} = 'App::GitGot::Repo'; 17 | 18 | $test->make_base_fixtures; 19 | } 20 | 21 | sub test_constructor :Test(3) { 22 | my $test = shift; 23 | my $lib = $test->{lib}; 24 | my $entry = $test->{entry}; 25 | 26 | new_ok( $lib , [{ entry => $entry }] ); 27 | 28 | dies_ok { $lib->new({}) } 'has req args' ; 29 | like( $@ , qr/Must provide entry/ , 'expected error message' ); 30 | } 31 | 32 | sub test_accessors :Tests(8) { 33 | my $test = shift; 34 | 35 | my $full = $test->{full}; 36 | my $min = $test->{min}; 37 | 38 | # only test the ones that get munged in BUILDALL... 39 | 40 | is( $full->name , 'my-full-repo' , 'full name' ); 41 | is( $min->name , 'my-repo' , 'min name' ); 42 | 43 | is( $full->number , 1 , 'full number' ); 44 | is( $min->number , 0 , 'min number' ); 45 | 46 | is( $full->repo , 'git@github:/luser/my-full-repo.git' , 'full repo' ); 47 | is( $min->repo , '' , 'min repo' ); 48 | 49 | is( $full->tags , 'tag1,tag2' , 'full tags' ); 50 | is( $min->tags , '' , 'min tags' ); 51 | 52 | } 53 | 54 | sub test_in_writable_format :Tests(2) { 55 | my $test = shift; 56 | 57 | { 58 | my $entry = { 59 | name => 'my-repo' , 60 | path => $test->{minpath} , 61 | type => 'git' , 62 | }; 63 | my $min = $test->{min}; 64 | 65 | is_deeply( $min->in_writable_format , $entry , 'min serialized properly' ); 66 | } 67 | { 68 | my $entry = { 69 | name => 'my-full-repo' , 70 | path => '/home/luser/proj/my-full-repo' , 71 | type => 'git' , 72 | repo => 'git@github:/luser/my-full-repo.git' , 73 | tags => 'tag1,tag2' , 74 | }; 75 | my $full = $test->{full}; 76 | 77 | is_deeply( $full->in_writable_format , $entry , 'full serialized properly' ); 78 | } 79 | } 80 | 81 | sub cleanup :Test(shutdown) { chdir('/') } 82 | 83 | sub make_base_fixtures { 84 | my $test = shift; 85 | my $lib = $test->{lib}; 86 | 87 | $test->{minpath} = _make_git_repo(); 88 | 89 | $test->{entry} = { 90 | name => 'my-repo' , 91 | path => $test->{minpath} , 92 | type => 'git' , 93 | }; 94 | 95 | $test->{full} = $lib->new({ 96 | count => 1 , 97 | entry => { 98 | path => '/home/luser/proj/my-full-repo' , 99 | type => 'git' , 100 | repo => 'git@github:/luser/my-full-repo.git' , 101 | tags => 'tag1,tag2' , 102 | label => 'testlabel' , 103 | } , 104 | }); 105 | 106 | $test->{min} = $lib->new({ entry => $test->{entry} }); 107 | } 108 | 109 | sub _make_git_repo { 110 | my $dir = tempdir(CLEANUP=>1); 111 | chdir( $dir ); 112 | `git -c init.defaultBranch="master" init`; 113 | path('foo')->touch; 114 | `git add foo`; 115 | `git commit -m"mu"`; 116 | return $dir; 117 | } 118 | 119 | 1; 120 | -------------------------------------------------------------------------------- /t/lib/Test/App/GitGot/Repo/Git.pm: -------------------------------------------------------------------------------- 1 | package Test::App::GitGot::Repo::Git; 2 | use parent 'Test::App::GitGot::Repo'; 3 | 4 | use 5.014; # strict, unicode_strings 5 | use warnings; 6 | 7 | use Test::Exception; 8 | use Test::More; 9 | 10 | use App::GitGot::Repo::Git; 11 | 12 | sub fixtures :Test(startup) { 13 | my $test = shift; 14 | 15 | $test->{lib} = 'App::GitGot::Repo::Git'; 16 | 17 | $test->make_base_fixtures; 18 | } 19 | 20 | sub test_current_branch :Tests(3) { 21 | my $test = shift; 22 | 23 | dies_ok { $test->{full}->current_branch } 'will die'; 24 | like( $@ , qr/(?:Can't locate|Failed to change) directory/ , 'expected error message' ); 25 | 26 | is( $test->{min}->current_branch , 'master' , 'expected answer' ); 27 | } 28 | 29 | sub test_current_remote_branch :Tests(3) { 30 | my $test = shift; 31 | 32 | dies_ok { $test->{full}->current_branch } 'will die'; 33 | like( $@ , qr/(?:Can't locate|Failed to change) directory/ , 'expected error message' ); 34 | 35 | is( $test->{min}->current_remote_branch , 0 , 'get 0 without real remote' ); 36 | } 37 | 38 | sub cleanup :Test(shutdown) { chdir('/') } 39 | 40 | 1; 41 | -------------------------------------------------------------------------------- /t/lib/Test/BASE.pm: -------------------------------------------------------------------------------- 1 | package Test::BASE; 2 | use parent 'Test::Class'; 3 | 4 | use 5.014; # strict, unicode_strings 5 | use warnings; 6 | 7 | use Carp; 8 | use File::chdir; 9 | use File::Temp qw/ tempdir tempfile /; 10 | use Path::Tiny; 11 | use YAML qw/ DumpFile /; 12 | 13 | INIT { 14 | my $config = tempfile(UNLINK => 1); 15 | $ENV{GIT_CONFIG} = $config; 16 | Test::Class->runtests; 17 | } 18 | 19 | sub build_fake_git_repo { 20 | my $repo = shift || 'foo.git'; 21 | path($repo)->mkpath; 22 | $CWD = $repo; 23 | `git init`; 24 | `git config user.name "Boo"`; 25 | `git config user.email "radley\@example.com"`; 26 | foreach my $x ( qw/ foo bar / ) { 27 | path($x)->touch; 28 | `git add $x`; 29 | `git commit -m"$x"`; 30 | } 31 | chdir $repo; 32 | } 33 | 34 | sub create_github_identity_file { 35 | path( '.github-identity')->spew(<1); 43 | chdir $dir; 44 | return path($dir)->realpath; 45 | } 46 | 47 | sub write_fake_config { 48 | my $dir = create_tempdir_and_chdir(); 49 | 50 | my $config = [{ 51 | name => 'foo.git' , 52 | path => "$dir/foo.git" , 53 | type => 'git', 54 | tags => 'foo' , 55 | },{ 56 | name => 'bar.git' , 57 | path => "$dir/bar.git" , 58 | repo => 'github@github.com:genehack/bar.git' , 59 | type => 'git' 60 | },{ 61 | name => 'xxx.git' , 62 | path => "$dir/xxx.git" , 63 | type => 'git' 64 | },{ 65 | name => 'bargle.git' , 66 | path => "$dir/bargle.git" , 67 | repo => 'github@github.com:genehack/bargle.git' , 68 | type => 'git' 69 | }]; 70 | 71 | build_fake_git_repo( 'xxx.git' ); 72 | chdir('..'); 73 | 74 | build_fake_git_repo( 'bar.git' ); 75 | chdir('..'); 76 | 77 | my( undef , $name ) = tempfile(UNLINK=>1); 78 | DumpFile( $name , $config ); 79 | 80 | return( $name , $dir ); 81 | } 82 | 83 | 1; 84 | --------------------------------------------------------------------------------