Global activate git path and ref (#3356)

diff --git a/lib/src/command/global_activate.dart b/lib/src/command/global_activate.dart
index a21386a..dc67f29 100644
--- a/lib/src/command/global_activate.dart
+++ b/lib/src/command/global_activate.dart
@@ -27,6 +27,11 @@
         allowed: ['git', 'hosted', 'path'],
         defaultsTo: 'hosted');
 
+    argParser.addOption('git-path', help: 'Path of git package in repository');
+
+    argParser.addOption('git-ref',
+        help: 'Git branch or commit to be retrieved');
+
     argParser.addMultiOption('features',
         abbr: 'f', help: 'Feature(s) to enable.', hide: true);
 
@@ -102,13 +107,24 @@
       usageException('Unexpected $arguments ${toSentence(unexpected)}.');
     }
 
+    if (argResults['source'] != 'git' &&
+        (argResults['git-path'] != null || argResults['git-ref'] != null)) {
+      usageException(
+          'Options `--git-path` and `--git-ref` can only be used with --source=git.');
+    }
+
     switch (argResults['source']) {
       case 'git':
         var repo = readArg('No Git repository given.');
-        // TODO(rnystrom): Allow passing in a Git ref too.
         validateNoExtraArgs();
-        return globals.activateGit(repo, executables,
-            features: features, overwriteBinStubs: overwrite);
+        return globals.activateGit(
+          repo,
+          executables,
+          features: features,
+          overwriteBinStubs: overwrite,
+          path: argResults['git-path'],
+          ref: argResults['git-ref'],
+        );
 
       case 'hosted':
         var package = readArg('No package to activate given.');
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index 773f2ec..b8ec3a9 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -85,23 +85,35 @@
   /// If [overwriteBinStubs] is `true`, any binstubs that collide with
   /// existing binstubs in other packages will be overwritten by this one's.
   /// Otherwise, the previous ones will be preserved.
-  Future<void> activateGit(String repo, List<String>? executables,
-      {Map<String, FeatureDependency>? features,
-      required bool overwriteBinStubs}) async {
+  Future<void> activateGit(
+    String repo,
+    List<String>? executables, {
+    Map<String, FeatureDependency>? features,
+    required bool overwriteBinStubs,
+    String? path,
+    String? ref,
+  }) async {
     var name = await cache.git.getPackageNameFromRepo(repo);
 
     // TODO(nweiz): Add some special handling for git repos that contain path
     // dependencies. Their executables shouldn't be cached, and there should
     // be a mechanism for redoing dependency resolution if a path pubspec has
     // changed (see also issue 20499).
-    PackageRef ref;
+    PackageRef packageRef;
     try {
-      ref = cache.git.source.parseRef(name, {'url': repo}, containingPath: '.');
+      packageRef = cache.git.source.parseRef(
+          name,
+          {
+            'url': repo,
+            if (path != null) 'path': path,
+            if (ref != null) 'ref': ref,
+          },
+          containingPath: '.');
     } on FormatException catch (e) {
       throw ApplicationException(e.message);
     }
     await _installInCache(
-        ref
+        packageRef
             .withConstraint(VersionConstraint.any)
             .withFeatures(features ?? const {}),
         executables,
diff --git a/test/global/activate/git_package_test.dart b/test/global/activate/git_package_test.dart
index ee88de7..1fa6beb 100644
--- a/test/global/activate/git_package_test.dart
+++ b/test/global/activate/git_package_test.dart
@@ -2,6 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -26,4 +27,66 @@
                 'Built foo:foo.\n'
                 'Activated foo 1.0.0 from Git repository "../foo.git".')));
   });
+
+  test('activates a package from a Git repo with path and ref', () async {
+    ensureGit();
+
+    await d.git('foo.git', [
+      d.libPubspec('foo', '0.0.0'),
+      d.dir('bin', [d.file('foo.dart', "main() => print('0');")]),
+      d.dir(
+        'sub',
+        [
+          d.libPubspec('foo', '1.0.0'),
+          d.dir('bin', [d.file('foo.dart', "main() => print('1');")])
+        ],
+      ),
+    ]).create();
+    await d.git('foo.git', [
+      d.dir(
+        'sub',
+        [
+          d.libPubspec('foo', '2.0.0'),
+          d.dir('bin', [d.file('foo.dart', "main() => print('2');")])
+        ],
+      ),
+    ]).commit();
+    await d.git('foo.git', [
+      d.dir(
+        'sub',
+        [
+          d.libPubspec('foo', '3.0.0'),
+          d.dir('bin', [d.file('foo.dart', "main() => print('3');")])
+        ],
+      ),
+    ]).commit();
+
+    await runPub(
+      args: [
+        'global',
+        'activate',
+        '-sgit',
+        '../foo.git',
+        '--git-ref=HEAD~',
+        '--git-path=sub/',
+      ],
+      output: allOf(
+        startsWith('Resolving dependencies...\n'
+            '+ foo 2.0.0 from git ../foo.git at'),
+        // Specific revision number goes here.
+        contains('in sub'),
+        endsWith('Building package executables...\n'
+            'Built foo:foo.\n'
+            'Activated foo 2.0.0 from Git repository "..${p.separator}foo.git".'),
+      ),
+    );
+    await runPub(
+      args: [
+        'global',
+        'run',
+        'foo',
+      ],
+      output: contains('2'),
+    );
+  });
 }
diff --git a/test/testdata/goldens/help_test/pub global activate --help.txt b/test/testdata/goldens/help_test/pub global activate --help.txt
index 5341266..29aab49 100644
--- a/test/testdata/goldens/help_test/pub global activate --help.txt
+++ b/test/testdata/goldens/help_test/pub global activate --help.txt
@@ -8,6 +8,8 @@
 -h, --help              Print this usage information.
 -s, --source            The source used to find the package.
                         [git, hosted (default), path]
+    --git-path          Path of git package in repository
+    --git-ref           Git branch or commit to be retrieved
     --no-executables    Do not put executables on PATH.
 -x, --executable        Executable(s) to place on PATH.
     --overwrite         Overwrite executables from other packages with the same