├── .clang-format ├── .gitignore ├── LICENSE ├── README ├── colours.ans ├── new.bat ├── out └── cmdEx.exe ├── sng.py ├── sng_ansicon.py ├── sng_libgit2.py ├── src ├── cmdEx │ ├── command_history.cc │ ├── command_history.h │ ├── command_history_test.cc │ ├── completion.cc │ ├── completion.h │ ├── completion_test.cc │ ├── directory_history.cc │ ├── directory_history.h │ ├── directory_history_test.cc │ ├── line_editor.cc │ ├── line_editor.h │ ├── line_editor_test.cc │ ├── string_util.cc │ ├── string_util.h │ ├── string_util_test.cc │ ├── subprocess.cc │ ├── subprocess.h │ └── subprocess_test.cc ├── cmdEx_dll_x64_dll │ └── dll.cc ├── cmdEx_exe │ ├── main.cc │ ├── resource.h │ └── resource.rc ├── cmdEx_x64_exe │ └── inject.cc └── common │ ├── util.cc │ └── util.h ├── test.py └── test_repos ├── conflict_rebase ├── _git │ ├── COMMIT_EDITMSG │ ├── FETCH_HEAD │ ├── HEAD │ ├── ORIG_HEAD │ ├── config │ ├── description │ ├── hooks │ │ ├── applypatch-msg.sample │ │ ├── commit-msg.sample │ │ ├── post-commit.sample │ │ ├── post-receive.sample │ │ ├── post-update.sample │ │ ├── pre-applypatch.sample │ │ ├── pre-commit.sample │ │ ├── pre-rebase.sample │ │ ├── prepare-commit-msg.sample │ │ └── update.sample │ ├── index │ ├── info │ │ └── exclude │ ├── logs │ │ ├── HEAD │ │ └── refs │ │ │ └── heads │ │ │ ├── child │ │ │ └── master │ ├── objects │ │ ├── 47 │ │ │ └── 73b88416ef5e51ada321eeab0117527e580004 │ │ ├── 62 │ │ │ └── 7a7f1274c1c8e8cc302bfbd60cf952db53c1d5 │ │ ├── 75 │ │ │ └── 4fc3ff54decd45cc0b670a0be5a606cccd5537 │ │ ├── 83 │ │ │ └── 75d6dfc8740e050f4f1e27d5046338ec90c07f │ │ ├── 02 │ │ │ └── 0b44b9d5ae90dac24535b6644745c96ea17a4b │ │ ├── 1a │ │ │ └── 5f6df420b0ea488694f90c5bffd99187509ece │ │ ├── 2b │ │ │ └── 6498555ba1bf52308e77aa36d2f3634470a32b │ │ ├── aa │ │ │ └── 1bab5a4fd1a72b125092ef1f677c4fe36bb485 │ │ ├── c5 │ │ │ └── 07c95bff70294cf024569ed3de5bca6d678ab6 │ │ └── e5 │ │ │ └── ecfe5a67c14206b895bac6311501c87647bb34 │ └── refs │ │ └── heads │ │ ├── child │ │ └── master └── a_file ├── empty └── _git │ ├── HEAD │ ├── config │ ├── description │ ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── post-commit.sample │ ├── post-receive.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ ├── prepare-commit-msg.sample │ └── update.sample │ └── info │ └── exclude ├── four_linear_commits ├── _git │ ├── COMMIT_EDITMSG │ ├── HEAD │ ├── config │ ├── description │ ├── hooks │ │ ├── applypatch-msg.sample │ │ ├── commit-msg.sample │ │ ├── post-commit.sample │ │ ├── post-receive.sample │ │ ├── post-update.sample │ │ ├── pre-applypatch.sample │ │ ├── pre-commit.sample │ │ ├── pre-rebase.sample │ │ ├── prepare-commit-msg.sample │ │ └── update.sample │ ├── index │ ├── info │ │ └── exclude │ ├── logs │ │ ├── HEAD │ │ └── refs │ │ │ └── heads │ │ │ └── master │ ├── objects │ │ ├── 46 │ │ │ └── 6c3e9907b6b0d4e6a5050dad3b966900b71ec8 │ │ ├── 56 │ │ │ └── f3b36e2773a56fe267643b6b1678e06c9584eb │ │ ├── 66 │ │ │ └── 0a045b054741044cc4009bcd1a80ac04afd201 │ │ ├── 09 │ │ │ └── 80abf5c2fcc3ffca32131a6601990018c4bd41 │ │ ├── 1c │ │ │ └── dcbf37e36f1b7c2cdd2c72fafb404be84b33a5 │ │ ├── 7b │ │ │ └── 4f1aedeb255f3e44cd0c2cb29da2d568771938 │ │ ├── 9c │ │ │ └── 4842597acfc9fc5ea49adeae53e73e86aef4f1 │ │ ├── ae │ │ │ └── 26c34d05647d6298e11a8426dcfab600d06d42 │ │ ├── cd │ │ │ └── 85ecc46860b4fe3b7981e4a0d5f808f9d90455 │ │ ├── da │ │ │ └── acd152eb89cc687ff06a6befdbcbefeba552fc │ │ ├── f1 │ │ │ └── a5903ec22f221f352044b28cd921cf44aa3a09 │ │ └── fc │ │ │ └── 0385bb9f2d03d7e0991f9e1a61ac1e0f5e633e │ └── refs │ │ └── heads │ │ └── master └── a_file └── single_commit ├── _git ├── COMMIT_EDITMSG ├── HEAD ├── config ├── description ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── post-commit.sample │ ├── post-receive.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-rebase.sample │ ├── prepare-commit-msg.sample │ └── update.sample ├── index ├── info │ └── exclude ├── logs │ ├── HEAD │ └── refs │ │ └── heads │ │ └── master ├── objects │ ├── 56 │ │ └── f3b36e2773a56fe267643b6b1678e06c9584eb │ ├── 7b │ │ └── 926d27cfb65dc0a2eef5defbe8d17998594915 │ └── cd │ │ └── 85ecc46860b4fe3b7981e4a0d5f808f9d90455 └── refs │ └── heads │ └── master └── a_file /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.ilk 2 | *.obj 3 | *.pdb 4 | *.res 5 | build.ninja 6 | csearchindex 7 | out/ 8 | tags 9 | third_party/ansicon 10 | third_party/googletest 11 | third_party/libgit2 12 | third_party/ninja 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following disclaimer 11 | // in the documentation and/or other materials provided with the 12 | // distribution. 13 | // * Neither the name of Google Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | 30 | Portions of this code based on code from http://code.google.com/p/clink/ which 31 | was distributed with the following license. 32 | 33 | // Copyright (c) 2012 Martin Ridgers 34 | // 35 | // Permission is hereby granted, free of charge, to any person obtaining a copy 36 | // of this software and associated documentation files (the "Software"), to deal 37 | // in the Software without restriction, including without limitation the rights 38 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 39 | // copies of the Software, and to permit persons to whom the Software is 40 | // furnished to do so, subject to the following conditions: 41 | // 42 | // The above copyright notice and this permission notice shall be included in 43 | // all copies or substantial portions of the Software. 44 | // 45 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 46 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 47 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 48 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 49 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 50 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 51 | // SOFTWARE. 52 | 53 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | cmdEx: Fixes for cmd.exe 2 | Scott Graham 3 | ------------------------------------- 4 | 5 | cmdEx makes some improvements to cmd.exe. Currently: 6 | 7 | - In PROMPT, $M displays git branch information instead of its normal function. 8 | - Improvements to input handling (set CMDEX_NOREADCONSOLE=1 to disable). 9 | Keybindings: 10 | - Alt-Up does "cd ..", maintaining current command. 11 | - Ctrl-W deletes back a word, Ctrl-Backspace deletes back a path 12 | component. 13 | - Ctrl-V pastes, with confirmation if there's \n in the text. 14 | - Alt-Left/Right or browser back/forward navigates like a web browser in 15 | previously visited directories, maintaining current command. 16 | - Improved tab completion, in addition to file and directory 17 | (contextually), built-ins and commands in path are completed, git 18 | subcommands are completed, and some git subcommands have arguments and 19 | context-sensitive completion (e.g. "git checkout o" might complete 20 | to "git checkout origin/main"). Target names are also completed for 21 | the ninja build tool. Environment variables are completed after "set". 22 | - Ctrl-Enter opens an Explorer window in the current directory. 23 | - Ctrl-L clears the console and puts the current command at the top of the 24 | window. 25 | - Ctrl-D at an empty prompt exits the shell. 26 | - Escape clears the current line 27 | - Ctrl-A/Ctrl-E are the same as Home/End. 28 | - Ctrl-Left/Right jump by word 29 | - Last 1000 commands are saved to %USERPROFILE%\_cmdex_history on 'exit', 30 | and loaded on startup. 31 | - Up, Down to move through history, PgUp/F8, PgDown to complete from 32 | history based on current prefix. 33 | - Ctrl-U/Ctrl-Home delete to beginning of line, Ctrl-K/Ctrl-End delete to 34 | end of line. 35 | 36 | 37 | TODO: 38 | 39 | - There's a crash in Alt-Right sometimes. :( Not sure of repro. 40 | - ninja && somethi tries to complete targets, instead of commands. 41 | - Ctrl-V multiline doesn't work properly, and needs tests. 42 | - I keep wanting Ctrl-Del to do something, but I'm not sure what. I think 43 | delete word to the right. 44 | - Save history more often, and maybe share between instances somehow. 45 | - Dir completion not excluding files? 46 | - Some of the completer code in dll.cc can move to the lib and be tested. 47 | - Add an option to automate adding/removing registry entry, and speed up 48 | startup so that it's fast enough to be reasonable to do so. 49 | 50 | 51 | cmdEx is currently only known to work on Windows 11 and using the x64 cmd.exe. 52 | This is on the "main" branch which is the current development branch. 53 | 54 | The last x86 version (any time before 2024) worked on Windows 7, 8, 8.1, and 10, 55 | and only when using the x86 cmd.exe. This is still available on the old 56 | "master" branch. 57 | 58 | 59 | Run: 60 | 61 | C:\...\cmdEx>out\cmdEx 62 | [main]C:\...cmdEx> 63 | 64 | 65 | Build: 66 | 67 | Requires: git, cmake, devenv, python in path. 68 | 69 | With VS2022 vars: 70 | C:\...\cmdEx>python sng.py -u # -u pulls third_party dependencies 71 | C:\...\cmdEx>third_party\ninja\ninja 72 | 73 | 74 | Test: 75 | 76 | C:\...\cmdEx>out\tests 77 | C:\...\cmdEx>python test.py 78 | C:\...\cmdEx>new 79 | [main]C:\...\cmdEx>exit 80 | 81 | 82 | 83 | 84 | Acknowledgements: 85 | 86 | - src/cmdEx/subprocess* adapted from http://github.com/martine/ninja/. 87 | 88 | - Similar project, but readline based: http://code.google.com/p/clink/. Some 89 | of cmdEx's injection code is based on Clink's. 90 | -------------------------------------------------------------------------------- /colours.ans: -------------------------------------------------------------------------------- 1 |          (colors 0..7) 2 |          (colors 8..15 or underline) 3 | -------------------------------------------------------------------------------- /new.bat: -------------------------------------------------------------------------------- 1 | :: Copyright 2013 The Chromium Authors. All rights reserved. 2 | :: Use of this source code is governed by a BSD-style license that can be 3 | :: found in the LICENSE file. 4 | @echo off 5 | %WINDIR%\System32\cmd.exe /k out\cmdex.exe 6 | -------------------------------------------------------------------------------- /out/cmdEx.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/out/cmdEx.exe -------------------------------------------------------------------------------- /sng.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 The Chromium Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can be 3 | # found in the LICENSE file. 4 | 5 | """A ninja generator for simple projects. No configuration files, replaced by 6 | ridiculously regimented project structure that you probably won't like. 7 | """ 8 | 9 | import glob 10 | import optparse 11 | import os 12 | import re 13 | import subprocess 14 | import sys 15 | 16 | 17 | def Run(args, cwd=None): 18 | assert isinstance(args, list) 19 | subprocess.check_call(args, shell=True, cwd=cwd) 20 | 21 | 22 | def GitPullOrClone(remote): 23 | base = os.path.splitext(os.path.basename(remote))[0] 24 | if not os.path.exists('third_party/%s/.git' % base): 25 | Run(['git', 'clone', remote], cwd='third_party') 26 | else: 27 | Run(['git', 'pull'], cwd='third_party/%s' % base) 28 | 29 | 30 | def DEP_ninja(action): 31 | if action == 'sync': 32 | GitPullOrClone('https://github.com/martine/ninja.git') 33 | if os.path.exists('third_party/ninja/ninja.bootstrap.exe'): 34 | Run(['ninja.bootstrap.exe'], cwd='third_party/ninja') 35 | elif os.path.exists('third_party/ninja/ninja.exe'): 36 | Run(['ninja.exe'], cwd='third_party/ninja') 37 | elif os.path.exists('third_party/ninja/ninja'): 38 | Run(['ninja'], cwd='third_party/ninja') 39 | else: 40 | Run([sys.executable, 'configure.py', '--bootstrap'], 41 | cwd='third_party/ninja') 42 | 43 | 44 | def DEP_googletest(action): 45 | if action == 'sync': 46 | GitPullOrClone('https://github.com/google/googletest.git') 47 | 48 | 49 | CFLAGS = [ 50 | '/Zi', '/W4', '/WX', '/GR-', 51 | '/wd4100', 52 | '/wd4530', 53 | '/wd4800', 54 | '/D_WIN32_WINNT=0x0600', 55 | '/D_CRT_SECURE_NO_WARNINGS', 56 | '/DNOMINMAX', 57 | '/Isrc', 58 | ] 59 | CFLAGS_DEBUG = [ 60 | '/D_DEBUG', 61 | '/MDd', 62 | ] 63 | CFLAGS_RELEASE = [ 64 | '/Ox', 65 | '/GL', 66 | '/DNDEBUG', 67 | '/MD', 68 | ] 69 | ARFLAGS = [ 70 | ] 71 | ARFLAGS_DEBUG = [ 72 | ] 73 | ARFLAGS_RELEASE = [ 74 | '/LTCG', 75 | ] 76 | LDFLAGS = [ 77 | '/DEBUG', 78 | ] 79 | LDFLAGS_DEBUG = [ 80 | ] 81 | LDFLAGS_RELEASE = [ 82 | '/LTCG', 83 | ] 84 | 85 | 86 | ALL_AUX_SNG = glob.glob('sng_*.py') 87 | for helper in ALL_AUX_SNG: 88 | exec(open(helper).read(), globals()) 89 | 90 | 91 | def UpdateDeps(): 92 | if not os.path.exists('third_party'): 93 | os.makedirs('third_party') 94 | for name, func in globals().items(): 95 | if name.startswith('DEP_'): 96 | func('sync') 97 | 98 | 99 | def GetFromDeps(into, action): 100 | for name, func in globals().items(): 101 | if name.startswith('DEP_'): 102 | result = func(action) 103 | if result: 104 | into.extend(result) 105 | 106 | 107 | def ForwardSlash(path): 108 | return os.path.normpath(path).replace('\\', '/') 109 | 110 | 111 | def ScanForIncludeAndUpdateLibs(src_path, libs, libtags): 112 | with open(src_path, 'r') as f: 113 | contents = f.read() 114 | for libtag, libname in libtags: 115 | if libtag in contents: 116 | libs.add(libname) 117 | 118 | 119 | def ScanForRCDATA(src_path): 120 | deps = [] 121 | with open(src_path, 'r') as f: 122 | contents = f.read() 123 | for mo in re.finditer(r'RCDATA "(.*)"', contents): 124 | deps.append(ForwardSlash(mo.group(1))) 125 | return deps 126 | 127 | 128 | def Compile(n, target, libtags): 129 | objs = [] 130 | libs = set() 131 | for topdir, dirnames, filenames in os.walk(os.path.join('src', target)): 132 | for filename in filenames: 133 | ext = os.path.splitext(filename)[1] 134 | src_path = ForwardSlash(os.path.join(topdir, filename)) 135 | if ext in ('.cc', '.cpp', '.cxx'): 136 | ScanForIncludeAndUpdateLibs(src_path, libs, libtags) 137 | assert src_path.startswith('src') 138 | obj_path = '$builddir/' + ForwardSlash( 139 | src_path[4:] + '.obj').replace('/', '.') 140 | variables = None 141 | if '_test' in filename: 142 | variables = (('cflags', '$cflags_test'),) 143 | n.build(obj_path, 'cxx', src_path, variables=variables) 144 | objs.append(obj_path) 145 | elif ext == '.rc': 146 | obj_path = '$builddir/' + ForwardSlash( 147 | src_path[4:] + '.res').replace('/', '.') 148 | used = ScanForRCDATA(src_path) 149 | n.build(obj_path, 'rc', src_path, implicit=used) 150 | objs.append(obj_path) 151 | return objs, list(libs) 152 | 153 | 154 | def BinaryAndRuleForTarget(target): 155 | if target.endswith('_dll'): 156 | return '$builddir/' + target[:-4] + '.dll', 'linkdll' 157 | elif target.endswith('_exe'): 158 | return '$builddir/' + target[:-4] + '.exe', 'link' 159 | else: 160 | return '$builddir/' + target + '.lib', 'lib' 161 | 162 | 163 | def BuildLibTags(target, targets): 164 | libtags = [] 165 | for t in targets: 166 | if target == t: 167 | continue 168 | if not t.endswith('_dll') and not t.endswith('_exe'): 169 | libtags.append(('#include "%s/' % t, '$builddir/%s.lib' % t)) 170 | return libtags 171 | 172 | 173 | def Generate(is_debug): 174 | if not os.path.exists('out'): 175 | os.makedirs('out') 176 | sys.path.append('third_party/ninja/misc') 177 | import ninja_syntax 178 | with open('build.ninja', 'w') as output_file: 179 | n = ninja_syntax.Writer(output_file) 180 | n.comment('Generated by sng.py') 181 | n.newline() 182 | 183 | n.variable('ninja_required_version', '1.3') 184 | n.newline() 185 | 186 | n.comment('The arguments passed to configure.py, for rerunning it.') 187 | n.variable('configure_args', ' '.join(sys.argv[1:])) 188 | 189 | assert sys.platform == 'win32' 190 | n.variable('builddir', 'out') 191 | 192 | cflags = CFLAGS 193 | have_common = os.path.exists('src/common') 194 | GetFromDeps(cflags, 'cflags') 195 | if is_debug: 196 | cflags += CFLAGS_DEBUG 197 | GetFromDeps(cflags, 'cflags_debug') 198 | else: 199 | cflags += CFLAGS_RELEASE 200 | GetFromDeps(cflags, 'cflags_release') 201 | n.variable('cflags', cflags) 202 | n.variable('rcflags', [x for x in cflags 203 | if x.startswith('/I') or x.startswith('/D')] + [ 204 | '/I.']) 205 | 206 | cflags_test = cflags[:] 207 | cflags_test += [ 208 | '/Ithird_party/googletest/googletest/include', 209 | '/Ithird_party/googletest/googletest', 210 | '/D_VARIADIC_MAX=10', 211 | ] 212 | GetFromDeps(cflags_test, 'cflags_test') 213 | n.variable('cflags_test', cflags_test) 214 | n.newline() 215 | 216 | arflags = ARFLAGS 217 | GetFromDeps(arflags, 'arflags') 218 | if is_debug: 219 | arflags += ARFLAGS_DEBUG 220 | GetFromDeps(arflags, 'arflags_debug') 221 | else: 222 | arflags += ARFLAGS_RELEASE 223 | GetFromDeps(arflags, 'arflags_release') 224 | n.variable('arflags', arflags) 225 | n.newline() 226 | 227 | ldflags = LDFLAGS 228 | GetFromDeps(ldflags, 'ldflags') 229 | if is_debug: 230 | ldflags += LDFLAGS_DEBUG 231 | GetFromDeps(ldflags, 'ldflags_debug') 232 | else: 233 | ldflags += LDFLAGS_RELEASE 234 | GetFromDeps(ldflags, 'ldflags_release') 235 | n.variable('ldflags', ldflags) 236 | n.newline() 237 | 238 | n.rule('cxx', 239 | command=('cl /nologo /showIncludes $cflags -c $in ' 240 | '/Fo$out /Fd$out.pdb'), 241 | description='CXX $out', 242 | deps='msvc') 243 | n.rule('lib', 244 | command='lib /nologo $arflags /out:$out $in', 245 | description='LIB $out') 246 | n.rule('link', 247 | command='link /nologo $in $libs $ldflags /out:$out /pdb:$out.pdb', 248 | description='LINK $out') 249 | n.rule('linkdll', 250 | command=('link /nologo $in $libs $ldflags /DLL ' 251 | '/out:$out /pdb:$out.pdb'), 252 | description='LINK DLL $out') 253 | n.rule('rc', 254 | command='rc /nologo /r /fo$out $rcflags $in', 255 | description='RC $out') 256 | n.newline() 257 | 258 | targets = [os.path.basename(x) for x in glob.glob('src/*')] 259 | all_binaries = [] 260 | all_test_objs = [] 261 | all_test_libs = [] 262 | for target in targets: 263 | objs, libs = Compile(n, target, BuildLibTags(target, targets)) 264 | no_test_objs = [x for x in objs if '_test' not in x] 265 | test_objs = [x for x in objs if '_test' in x] 266 | all_test_objs += test_objs 267 | name, rule = BinaryAndRuleForTarget(target) 268 | n.build(name, rule, no_test_objs + libs) 269 | if test_objs: 270 | all_test_libs.append(name) 271 | all_binaries.append(name) 272 | 273 | if all_test_objs: 274 | gtest_obj = '$builddir/gtest-all.obj' 275 | gtest_main = '$builddir/gtest_main.obj' 276 | gtest_cflags = '$cflags_test /wd4100 /Ithird_party/googletest' 277 | n.build(gtest_obj, 'cxx', 278 | 'third_party/googletest/googletest/src/gtest-all.cc', 279 | variables=(('cflags', gtest_cflags),)) 280 | n.build(gtest_main, 'cxx', 281 | 'third_party/googletest/googletest/src/gtest_main.cc', 282 | variables=(('cflags', gtest_cflags),)) 283 | test_binary = '$builddir/tests.exe' 284 | n.build(test_binary, 'link', 285 | all_test_objs + [gtest_obj, gtest_main] + all_test_libs) 286 | all_binaries.append(test_binary) 287 | n.newline() 288 | 289 | n.comment('Regenerate build file if build script changes.') 290 | n.rule('configure', 291 | command=sys.executable + ' sng.py ' + '$configure_args', 292 | generator=1) 293 | build_files = ['sng.py', 'third_party/ninja/misc/ninja_syntax.py'] + \ 294 | ALL_AUX_SNG 295 | n.build('build.ninja', 'configure', implicit=build_files) 296 | n.newline() 297 | 298 | n.build('all', 'phony', all_binaries) 299 | 300 | 301 | def main(): 302 | parser = optparse.OptionParser() 303 | parser.add_option("-d", "--debug", 304 | action="store_true", dest="debug", default=False, 305 | help="build Debug build (default Release)") 306 | parser.add_option("-u", "--update-deps", 307 | action="store_true", dest="update_deps", default=False, 308 | help="Pull or update third party dependencies") 309 | 310 | (options, args) = parser.parse_args() 311 | if len(args) != 0: 312 | parser.error('unexpected args') 313 | 314 | if options.update_deps: 315 | UpdateDeps() 316 | Generate(options.debug) 317 | return 0 318 | 319 | 320 | if __name__ == '__main__': 321 | sys.exit(main()) 322 | -------------------------------------------------------------------------------- /sng_ansicon.py: -------------------------------------------------------------------------------- 1 | def DEP_ansicon(action): 2 | if action == 'sync': 3 | #GitPullOrClone('https://github.com/adoxa/ansicon.git') 4 | #Run(['nmake', '-f', 'makefile.vc'], cwd='third_party/ansicon') 5 | pass 6 | -------------------------------------------------------------------------------- /sng_libgit2.py: -------------------------------------------------------------------------------- 1 | def DEP_libgit2(action): 2 | if action == 'sync': 3 | GitPullOrClone('https://github.com/libgit2/libgit2.git') 4 | Run(['cmake', '-G', 'Visual Studio 17 2022', '.'], cwd='third_party/libgit2') 5 | # PDB collision with git2.exe, have to build only dll. 6 | Run(['devenv.com', 'libgit2.sln', '/Build', 'Release', '/Project', 'libgit2package'], 7 | cwd='third_party/libgit2') 8 | elif action == 'reftag': 9 | return '#include "git2.h', None # We don't actually want to link against it. 10 | elif action == 'cflags': 11 | return ['/Ithird_party/libgit2/include'] 12 | -------------------------------------------------------------------------------- /src/cmdEx/command_history.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "cmdEx/command_history.h" 6 | 7 | #include "common/util.h" 8 | 9 | CommandHistory::CommandHistory() : position_(-1) {} 10 | 11 | void CommandHistory::Populate(const vector& commands) { 12 | commands_ = commands; 13 | position_ = static_cast(commands_.size()); 14 | } 15 | 16 | vector CommandHistory::GetListForSaving() { 17 | return commands_; 18 | } 19 | 20 | void CommandHistory::AddCommand(const wstring& command) { 21 | // TODO: Maybe remove from old location so there's only one copy of it 22 | // closest to the end? 23 | commands_.push_back(command); 24 | position_ = static_cast(commands_.size()); 25 | } 26 | 27 | bool CommandHistory::MoveInHistory(int direction, const wstring& prefix, wstring* result) { 28 | if (commands_.empty()) 29 | return false; 30 | CHECK(direction == -1 || direction == 1); 31 | int original_position = position_ % commands_.size(); 32 | position_ += direction; 33 | for (;;) { 34 | if (position_ < 0) 35 | position_ = static_cast(commands_.size()) - 1; 36 | if (position_ >= static_cast(commands_.size())) 37 | position_ = 0; 38 | if (prefix.empty()) 39 | break; 40 | if (commands_[position_].substr(0, prefix.size()) == prefix) 41 | break; 42 | if (position_ == original_position) 43 | return false; 44 | position_ += direction; 45 | } 46 | *result = commands_[position_]; 47 | return true; 48 | } 49 | -------------------------------------------------------------------------------- /src/cmdEx/command_history.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef CMDEX_COMMAND_HISTORY_H_ 6 | #define CMDEX_COMMAND_HISTORY_H_ 7 | 8 | #include 9 | #include 10 | using namespace std; 11 | 12 | class CommandHistory { 13 | public: 14 | CommandHistory(); 15 | 16 | void Populate(const vector& commands); 17 | vector GetListForSaving(); 18 | 19 | void AddCommand(const wstring& command); 20 | 21 | bool MoveInHistory(int direction, const wstring& prefix, wstring* result); 22 | 23 | private: 24 | // Most recent are at the end. 25 | vector commands_; 26 | int position_; 27 | }; 28 | 29 | #endif // CMDEX_COMMAND_HISTORY_H_ 30 | -------------------------------------------------------------------------------- /src/cmdEx/command_history_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "cmdEx/command_history.h" 6 | #include "gtest/gtest.h" 7 | 8 | TEST(CommandHistoryTest, NotFound) { 9 | CommandHistory ch; 10 | vector entries; 11 | entries.push_back(L"abc"); 12 | entries.push_back(L"def"); 13 | entries.push_back(L"ghi"); 14 | ch.Populate(entries); 15 | wstring result; 16 | EXPECT_FALSE(ch.MoveInHistory(-1, L"py", &result)); 17 | } 18 | 19 | TEST(CommandHistoryTest, FoundSimple) { 20 | CommandHistory ch; 21 | vector entries; 22 | entries.push_back(L"abc"); 23 | entries.push_back(L"def"); 24 | entries.push_back(L"ghi"); 25 | ch.Populate(entries); 26 | wstring result; 27 | EXPECT_TRUE(ch.MoveInHistory(-1, L"g", &result)); 28 | EXPECT_EQ(L"ghi", result); 29 | } 30 | 31 | TEST(CommandHistoryTest, FoundSimpleFarther) { 32 | CommandHistory ch; 33 | vector entries; 34 | entries.push_back(L"abc"); 35 | entries.push_back(L"def"); 36 | entries.push_back(L"ghi"); 37 | ch.Populate(entries); 38 | wstring result; 39 | EXPECT_TRUE(ch.MoveInHistory(-1, L"d", &result)); 40 | EXPECT_EQ(L"def", result); 41 | } 42 | 43 | TEST(CommandHistoryTest, FoundSimpleFirst) { 44 | CommandHistory ch; 45 | vector entries; 46 | entries.push_back(L"abc"); 47 | entries.push_back(L"def"); 48 | entries.push_back(L"ghi"); 49 | ch.Populate(entries); 50 | wstring result; 51 | EXPECT_TRUE(ch.MoveInHistory(-1, L"a", &result)); 52 | EXPECT_EQ(L"abc", result); 53 | } 54 | -------------------------------------------------------------------------------- /src/cmdEx/completion.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "cmdEx/completion.h" 6 | 7 | #include 8 | 9 | #include "common/util.h" 10 | 11 | #pragma comment(lib, "shell32.lib") 12 | 13 | static void CopyArg(const wchar_t* line_start, 14 | const wchar_t* arg_start, 15 | const wchar_t* arg_end, 16 | vector* word_data) { 17 | WordData wd; 18 | wd.original_offset = static_cast(arg_start - line_start); 19 | wd.original_word.reserve(arg_end - arg_start); 20 | for (const wchar_t* i = arg_start; i != arg_end; ++i) 21 | wd.original_word.push_back(*i); 22 | word_data->push_back(wd); 23 | } 24 | 25 | static void SkipWhitespace(const wchar_t** p) { 26 | while (**p == L' ' || **p == L'\t') 27 | ++(*p); 28 | } 29 | // We want to use CommandLineToArgvW here, but we need to be able to say in 30 | // which word and offset the given |position| in source falls into, and we 31 | // don't want to de-escape anything for completion. So, we need to reimplement 32 | // the somewhat subtle and complex splitting behaviour of CommandLineToArgvW 33 | // for quote escaping and quote doubling: 34 | // - " groups over space and tab 35 | // - \" -> " 36 | // - multiple \ before a quote (only; otherwise backslashes aren't magic): 37 | // 2n -> n backslashes, quote as argument delimiter 38 | // 2n+1 -> n backslashes, quote is literal 39 | // - consecutive quotes are / 3: 40 | // 3n -> n 41 | // 3n+1 -> n, and close string 42 | // 3n+2 -> n+1 and close string 43 | 44 | void CompletionBreakIntoWords(const wstring& line, 45 | vector* word_data) { 46 | if (line.empty()) { 47 | // See comment about appending empty arg at end of function. 48 | CopyArg(NULL, NULL, NULL, word_data); 49 | return; 50 | } 51 | 52 | const wchar_t* p = line.c_str(); 53 | const wchar_t* line_start = p; 54 | const wchar_t* arg_start = p; 55 | // The executable gets special rules per CreateProcess docs: no quote 56 | // escaping if it's quoted, and no space escaping if it's not. 57 | if (*p == L'"') { 58 | ++p; 59 | while (*p) 60 | if (*p++ == L'"') 61 | break; 62 | } else { 63 | while (*p && *p != L' ' && *p != L'\t') 64 | ++p; 65 | } 66 | CopyArg(line_start, arg_start, p, word_data); 67 | 68 | // Skip to the first argument. 69 | SkipWhitespace(&p); 70 | arg_start = p; 71 | 72 | int num_active_quotes = 0; 73 | int num_backslashes = 0; 74 | while (*p) { 75 | if ((*p == L' ' || *p == L'\t') && num_active_quotes == 0) { 76 | // We hit a space and aren't in an active quote, skip to the next 77 | // argument. 78 | CopyArg(line_start, arg_start, p, word_data); 79 | SkipWhitespace(&p); 80 | arg_start = p; 81 | num_backslashes = 0; 82 | } else if (*p == L'\\') { 83 | ++num_backslashes; 84 | ++p; 85 | } else if (*p == L'"') { 86 | if (num_backslashes % 2 == 0) // Unescaped quote. 87 | ++num_active_quotes; 88 | ++p; 89 | num_backslashes = 0; 90 | while (*p == L'"') { 91 | ++num_active_quotes; 92 | ++p; 93 | } 94 | num_active_quotes = num_active_quotes % 3; 95 | if (num_active_quotes == 2) 96 | num_active_quotes = 0; 97 | } else { 98 | num_backslashes = 0; 99 | ++p; 100 | } 101 | } 102 | if (arg_start != p) 103 | CopyArg(line_start, arg_start, p, word_data); 104 | 105 | int num_args; 106 | LPWSTR* escaped = CommandLineToArgvW(line_start, &num_args); 107 | CHECK(num_args == static_cast(word_data->size())); 108 | for (int i = 0; i < num_args; ++i) { 109 | word_data->at(i).deescaped_word = escaped[i]; 110 | } 111 | LocalFree(escaped); 112 | 113 | // If the cursor was spaced past the end of the last argument, add an empty 114 | // argument so that calling code knows we're not on any word, past the end 115 | // of existing arguments. Similarly if the command line is completely empty. 116 | if (p - line_start > 117 | static_cast(word_data->at(num_args - 1).original_offset + 118 | word_data->at(num_args - 1).original_word.size())) { 119 | CopyArg(line_start, p, p, word_data); 120 | } 121 | 122 | // Hack for completing 123 | // "C:\Program Files (x86)"\ 124 | // CommandLineToArgvW will split the trailing \ into a second argument. In 125 | // order to match cmd's completion behaviour, merge an argument that's 126 | // immediately attached and starts with a \ back into the one before it. 127 | for (size_t i = 0; i < word_data->size() - 1; ++i) { 128 | WordData* first = &word_data->at(i); 129 | WordData* second = &word_data->at(i + 1); 130 | if (second->original_word[0] == L'\\' && 131 | first->original_offset + first->original_word.size() == 132 | static_cast(second->original_offset)) { 133 | // Merge second onto first and remove second. 134 | first->original_word += second->original_word; 135 | first->deescaped_word += second->deescaped_word; 136 | word_data->erase(word_data->begin() + i + 1); 137 | } 138 | } 139 | } 140 | 141 | int CompletionWordIndex(const vector& word_data, int position) { 142 | int result = -1; 143 | for (const auto& i : word_data) { 144 | if (position < i.original_offset) 145 | break; 146 | ++result; 147 | } 148 | return result; 149 | } 150 | 151 | // ref: http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx 152 | // This only does the CommandLineToArgvW part, because when you're editing a 153 | // cmd command, it's not clear when you want metachar escaping. 154 | wstring QuoteWord(const std::wstring& argument) { 155 | // Don't quote unless we actually need to. 156 | if (!argument.empty() && 157 | argument.find_first_of(L" \t\n\v\"") == argument.npos) { 158 | return argument; 159 | } else { 160 | wstring result; 161 | result.push_back(L'"'); 162 | for (std::wstring::const_iterator it = argument.begin();; ++it) { 163 | int num_backslashes = 0; 164 | while (it != argument.end() && *it == L'\\') { 165 | ++it; 166 | ++num_backslashes; 167 | } 168 | if (it == argument.end()) { 169 | // Escape all backslashes, but let the terminating double quotation 170 | // mark we add below be interpreted as a metacharacter. 171 | result.append(num_backslashes * 2, L'\\'); 172 | break; 173 | } else if (*it == L'"') { 174 | // Escape all backslashes and the following double quotation mark. 175 | result.append(num_backslashes * 2 + 1, L'\\'); 176 | result.push_back(*it); 177 | } else { 178 | // Backslashes aren't special here. 179 | result.append(num_backslashes, L'\\'); 180 | result.push_back(*it); 181 | } 182 | } 183 | result.push_back(L'"'); 184 | return result; 185 | } 186 | } 187 | 188 | vector> CompletionBreakWordsIntoCommands( 189 | const vector& words) { 190 | vector> result; 191 | vector current; 192 | for (const auto& i : words) { 193 | if (i.original_word == L"&&" || i.original_word == L"||" || 194 | i.original_word == L"&") { 195 | result.push_back(current); 196 | current = vector(); 197 | continue; 198 | } 199 | current.push_back(i); 200 | } 201 | result.push_back(current); 202 | return result; 203 | } 204 | -------------------------------------------------------------------------------- /src/cmdEx/completion.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef CMDEX_COMPLETION_H_ 6 | #define CMDEX_COMPLETION_H_ 7 | 8 | #include 9 | #include 10 | using namespace std; 11 | 12 | // |original_word| will not have any of its escape characters interpreted. 13 | struct WordData { 14 | wstring original_word; 15 | int original_offset; 16 | wstring deescaped_word; 17 | }; 18 | 19 | struct CompleterInput { 20 | vector word_data; 21 | int word_index; 22 | int position_in_word; 23 | }; 24 | 25 | struct CompleterOutput { 26 | vector results; 27 | bool trailing_space; 28 | void Reset() { 29 | results.clear(); 30 | trailing_space = true; 31 | } 32 | }; 33 | 34 | // Return false if can't complete for |input|. Otherwise, fill out |results|. 35 | typedef bool (*Completer)(const CompleterInput& input, 36 | CompleterOutput* output); 37 | 38 | void CompletionBreakIntoWords(const wstring& line, 39 | vector* word_data); 40 | 41 | // Breaks up by subcommands (&&, ||, &). 42 | vector> CompletionBreakWordsIntoCommands( 43 | const vector& words); 44 | 45 | int CompletionWordIndex(const vector& word_data, int position); 46 | 47 | wstring QuoteWord(const wstring& argument); 48 | 49 | #endif // CMDEX_COMPLETION_H_ 50 | -------------------------------------------------------------------------------- /src/cmdEx/completion_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "cmdEx/completion.h" 6 | 7 | #include "gtest/gtest.h" 8 | 9 | // Some tests based on: 10 | // http://source.winehq.org/git/wine.git/blob/HEAD:/dlls/shell32/tests/shlexec.c 11 | 12 | TEST(CompletionTest, BreakIntoWordsNoArgs) { 13 | vector words; 14 | CompletionBreakIntoWords(L"exe", &words); 15 | EXPECT_EQ(1u, words.size()); 16 | EXPECT_EQ(L"exe", words[0].original_word); 17 | } 18 | 19 | TEST(CompletionTest, BreakIntoWordsBasicQuotes) { 20 | vector words; 21 | CompletionBreakIntoWords(L"exe a b \"c d\" 'e f` g\\ h)", &words); 22 | EXPECT_EQ(8u, words.size()); 23 | EXPECT_EQ(L"exe", words[0].original_word); 24 | EXPECT_EQ(L"a", words[1].original_word); 25 | EXPECT_EQ(L"b", words[2].original_word); 26 | EXPECT_EQ(L"\"c d\"", words[3].original_word); 27 | EXPECT_EQ(L"'e", words[4].original_word); 28 | EXPECT_EQ(L"f`", words[5].original_word); 29 | EXPECT_EQ(L"g\\", words[6].original_word); 30 | EXPECT_EQ(L"h)", words[7].original_word); 31 | } 32 | 33 | TEST(CompletionTest, BreakIntoWordsQuotesInArgs) { 34 | vector words; 35 | CompletionBreakIntoWords(L"exe a\"b\" \"c\"d e", &words); 36 | EXPECT_EQ(4u, words.size()); 37 | EXPECT_EQ(L"exe", words[0].original_word); 38 | EXPECT_EQ(L"a\"b\"", words[1].original_word); 39 | EXPECT_EQ(L"\"c\"d", words[2].original_word); 40 | EXPECT_EQ(L"e", words[3].original_word); 41 | } 42 | 43 | TEST(CompletionTest, BreakIntoWordsUnclosedQuotes) { 44 | vector words; 45 | CompletionBreakIntoWords(L"exe a \"b", &words); 46 | EXPECT_EQ(3u, words.size()); 47 | EXPECT_EQ(L"exe", words[0].original_word); 48 | EXPECT_EQ(L"a", words[1].original_word); 49 | EXPECT_EQ(L"\"b", words[2].original_word); 50 | } 51 | 52 | TEST(CompletionTest, BreakIntoWordsStandaloneQuote) { 53 | vector words; 54 | CompletionBreakIntoWords(L"exe a \"", &words); 55 | EXPECT_EQ(3u, words.size()); 56 | EXPECT_EQ(L"exe", words[0].original_word); 57 | EXPECT_EQ(L"a", words[1].original_word); 58 | EXPECT_EQ(L"\"", words[2].original_word); 59 | } 60 | 61 | TEST(CompletionTest, BreakIntoWordsOnlySpaceAndTab) { 62 | vector words; 63 | CompletionBreakIntoWords(L"exe a\tb\rc\nd\ve\ff", &words); 64 | EXPECT_EQ(3u, words.size()); 65 | EXPECT_EQ(L"exe", words[0].original_word); 66 | EXPECT_EQ(L"a", words[1].original_word); 67 | EXPECT_EQ(L"b\rc\nd\ve\ff", words[2].original_word); 68 | } 69 | 70 | TEST(CompletionTest, BreakIntoWordsBackslashNoQuote) { 71 | vector words; 72 | CompletionBreakIntoWords(L"exe o\\ne t\\\\wo t\\\\\\hree f\\\\\\\\our", 73 | &words); 74 | EXPECT_EQ(5u, words.size()); 75 | EXPECT_EQ(L"exe", words[0].original_word); 76 | EXPECT_EQ(L"o\\ne", words[1].original_word); 77 | EXPECT_EQ(L"t\\\\wo", words[2].original_word); 78 | EXPECT_EQ(L"t\\\\\\hree", words[3].original_word); 79 | EXPECT_EQ(L"f\\\\\\\\our", words[4].original_word); 80 | } 81 | 82 | TEST(CompletionTest, BreakIntoWordsBackslashNoQuoteInQuotes) { 83 | vector words; 84 | CompletionBreakIntoWords( 85 | L"exe \"o\\ne\" \"t\\\\wo\" \"t\\\\\\hree\" \"f\\\\\\\\our\"", &words); 86 | EXPECT_EQ(5u, words.size()); 87 | EXPECT_EQ(L"exe", words[0].original_word); 88 | EXPECT_EQ(L"\"o\\ne\"", words[1].original_word); 89 | EXPECT_EQ(L"\"t\\\\wo\"", words[2].original_word); 90 | EXPECT_EQ(L"\"t\\\\\\hree\"", words[3].original_word); 91 | EXPECT_EQ(L"\"f\\\\\\\\our\"", words[4].original_word); 92 | } 93 | 94 | TEST(CompletionTest, BreakIntoWordsBackslashQuote) { 95 | vector words; 96 | CompletionBreakIntoWords( 97 | L"exe \\\"one \\\\\"two\" \\\\\\\"three \\\\\\\\\"four\" after", &words); 98 | EXPECT_EQ(6u, words.size()); 99 | EXPECT_EQ(L"exe", words[0].original_word); 100 | EXPECT_EQ(L"\\\"one", words[1].original_word); 101 | EXPECT_EQ(L"\\\\\"two\"", words[2].original_word); 102 | EXPECT_EQ(L"\\\\\\\"three", words[3].original_word); 103 | EXPECT_EQ(L"\\\\\\\\\"four\"", words[4].original_word); 104 | EXPECT_EQ(L"after", words[5].original_word); 105 | } 106 | 107 | TEST(CompletionTest, BreakIntoWordsBackslashQuoteSpaced) { 108 | vector words; 109 | CompletionBreakIntoWords( 110 | L"exe \"one\\\" still\" \"two\\\\\" \"three\\\\\\\" still\" " \ 111 | L"\"four\\\\\\\\\" after", 112 | &words); 113 | EXPECT_EQ(6u, words.size()); 114 | EXPECT_EQ(L"exe", words[0].original_word); 115 | EXPECT_EQ(L"\"one\\\" still\"", words[1].original_word); 116 | EXPECT_EQ(L"\"two\\\\\"", words[2].original_word); 117 | EXPECT_EQ(L"\"three\\\\\\\" still\"", words[3].original_word); 118 | EXPECT_EQ(L"\"four\\\\\\\\\"", words[4].original_word); 119 | EXPECT_EQ(L"after", words[5].original_word); 120 | } 121 | 122 | TEST(CompletionTest, BreakIntoWordsDoubleQuotes) { 123 | vector words; 124 | CompletionBreakIntoWords(L"exe two\"\"quotes after", &words); 125 | EXPECT_EQ(3u, words.size()); 126 | EXPECT_EQ(L"exe", words[0].original_word); 127 | EXPECT_EQ(L"two\"\"quotes", words[1].original_word); 128 | EXPECT_EQ(L"twoquotes", words[1].deescaped_word); 129 | EXPECT_EQ(L"after", words[2].original_word); 130 | } 131 | 132 | TEST(CompletionTest, BreakIntoWordsTripleQuotes) { 133 | vector words; 134 | CompletionBreakIntoWords(L"exe three\"\"\"quotes after", &words); 135 | EXPECT_EQ(3u, words.size()); 136 | EXPECT_EQ(L"exe", words[0].original_word); 137 | EXPECT_EQ(L"three\"\"\"quotes", words[1].original_word); 138 | EXPECT_EQ(L"three\"quotes", words[1].deescaped_word); 139 | EXPECT_EQ(L"after", words[2].original_word); 140 | } 141 | 142 | TEST(CompletionTest, BreakIntoWordsQuadrupleQuotes) { 143 | vector words; 144 | CompletionBreakIntoWords(L"exe four\"\"\"\" quotes\" after", &words); 145 | EXPECT_EQ(3u, words.size()); 146 | EXPECT_EQ(L"exe", words[0].original_word); 147 | EXPECT_EQ(L"four\"\"\"\" quotes\"", words[1].original_word); 148 | EXPECT_EQ(L"four\" quotes", words[1].deescaped_word); 149 | EXPECT_EQ(L"after", words[2].original_word); 150 | } 151 | 152 | TEST(CompletionTest, BreakIntoWordsQuintupleQuotes) { 153 | vector words; 154 | CompletionBreakIntoWords(L"exe five\"\"\"\"\"quotes after", &words); 155 | EXPECT_EQ(3u, words.size()); 156 | EXPECT_EQ(L"exe", words[0].original_word); 157 | EXPECT_EQ(L"five\"\"\"\"\"quotes", words[1].original_word); 158 | EXPECT_EQ(L"five\"quotes", words[1].deescaped_word); 159 | EXPECT_EQ(L"after", words[2].original_word); 160 | } 161 | 162 | TEST(CompletionTest, BreakIntoWordsHextupleQuotes) { 163 | vector words; 164 | CompletionBreakIntoWords(L"exe six\"\"\"\"\"\"quotes after", &words); 165 | EXPECT_EQ(3u, words.size()); 166 | EXPECT_EQ(L"exe", words[0].original_word); 167 | EXPECT_EQ(L"six\"\"\"\"\"\"quotes", words[1].original_word); 168 | EXPECT_EQ(L"six\"\"quotes", words[1].deescaped_word); 169 | EXPECT_EQ(L"after", words[2].original_word); 170 | } 171 | 172 | TEST(CompletionTest, BreakIntoWordsSextupleQuotes) { 173 | vector words; 174 | CompletionBreakIntoWords(L"exe six\"\"\"\"\"\"quotes after", &words); 175 | EXPECT_EQ(3u, words.size()); 176 | EXPECT_EQ(L"exe", words[0].original_word); 177 | EXPECT_EQ(L"six\"\"\"\"\"\"quotes", words[1].original_word); 178 | EXPECT_EQ(L"six\"\"quotes", words[1].deescaped_word); 179 | EXPECT_EQ(L"after", words[2].original_word); 180 | } 181 | 182 | TEST(CompletionTest, BreakIntoWordsSeptupleQuotes) { 183 | vector words; 184 | CompletionBreakIntoWords(L"exe seven\"\"\"\"\"\"\" quotes\" after", &words); 185 | EXPECT_EQ(3u, words.size()); 186 | EXPECT_EQ(L"exe", words[0].original_word); 187 | EXPECT_EQ(L"seven\"\"\"\"\"\"\" quotes\"", words[1].original_word); 188 | EXPECT_EQ(L"seven\"\" quotes", words[1].deescaped_word); 189 | EXPECT_EQ(L"after", words[2].original_word); 190 | } 191 | 192 | TEST(CompletionTest, BreakIntoWordsDuodectupleQuotes) { 193 | vector words; 194 | CompletionBreakIntoWords(L"exe twelve\"\"\"\"\"\"\"\"\"\"\"\"quotes after", 195 | &words); 196 | EXPECT_EQ(3u, words.size()); 197 | EXPECT_EQ(L"exe", words[0].original_word); 198 | EXPECT_EQ(L"twelve\"\"\"\"\"\"\"\"\"\"\"\"quotes", words[1].original_word); 199 | EXPECT_EQ(L"twelve\"\"\"\"quotes", words[1].deescaped_word); 200 | EXPECT_EQ(L"after", words[2].original_word); 201 | } 202 | 203 | // Had to bust out Wikipedia for that name. 204 | TEST(CompletionTest, BreakIntoWordsTredectupleQuotes) { 205 | vector words; 206 | CompletionBreakIntoWords( 207 | L"exe thirteen\"\"\"\"\"\"\"\"\"\"\"\"\" quotes\" after", &words); 208 | EXPECT_EQ(3u, words.size()); 209 | EXPECT_EQ(L"exe", words[0].original_word); 210 | EXPECT_EQ(L"thirteen\"\"\"\"\"\"\"\"\"\"\"\"\" quotes\"", 211 | words[1].original_word); 212 | EXPECT_EQ(L"thirteen\"\"\"\" quotes", words[1].deescaped_word); 213 | EXPECT_EQ(L"after", words[2].original_word); 214 | } 215 | 216 | TEST(CompletionTest, BreakIntoWordsQuotedContainingTwoQuotes) { 217 | vector words; 218 | CompletionBreakIntoWords(L"exe \"two\"\"quotes after", &words); 219 | EXPECT_EQ(3u, words.size()); 220 | EXPECT_EQ(L"exe", words[0].original_word); 221 | EXPECT_EQ(L"\"two\"\"quotes", words[1].original_word); 222 | EXPECT_EQ(L"two\"quotes", words[1].deescaped_word); 223 | EXPECT_EQ(L"after", words[2].original_word); 224 | EXPECT_EQ(L"after", words[2].deescaped_word); 225 | } 226 | 227 | TEST(CompletionTest, BreakIntoWordsEscapedConsecutiveQuotes) { 228 | vector words; 229 | CompletionBreakIntoWords(L"exe \"the crazy \\\\\"\"\"\\\\\" quotes", &words); 230 | EXPECT_EQ(3u, words.size()); 231 | EXPECT_EQ(L"exe", words[0].original_word); 232 | EXPECT_EQ(L"\"the crazy \\\\\"\"\"\\\\\"", words[1].original_word); 233 | EXPECT_EQ(L"the crazy \\\"\\", words[1].deescaped_word); 234 | EXPECT_EQ(L"quotes", words[2].original_word); 235 | EXPECT_EQ(L"quotes", words[2].deescaped_word); 236 | } 237 | 238 | TEST(CompletionTest, MergeStartingSlashWithTrailingQuote) { 239 | vector words; 240 | CompletionBreakIntoWords(L"\"C:\\Program Files (x86)\"\\", &words); 241 | EXPECT_EQ(1u, words.size()); 242 | EXPECT_EQ(L"\"C:\\Program Files (x86)\"\\", words[0].original_word); 243 | } 244 | 245 | TEST(CompletionTest, Quoting) { 246 | EXPECT_EQ(L"stuff", QuoteWord(L"stuff")); 247 | EXPECT_EQ(L"\"st uff\"", QuoteWord(L"st uff")); 248 | EXPECT_EQ(L"\"\\\"st uff\\\"\"", QuoteWord(L"\"st uff\"")); 249 | EXPECT_EQ(L"\"st uff\\\\\"", QuoteWord(L"st uff\\")); 250 | } 251 | 252 | TEST(CompletionTest, WordsToCommands) { 253 | vector words; 254 | CompletionBreakIntoWords(L"git && ninja && stuff", &words); 255 | vector> by_command = CompletionBreakWordsIntoCommands(words); 256 | EXPECT_EQ(3u, by_command.size()); 257 | EXPECT_EQ(1u, by_command[0].size()); 258 | EXPECT_EQ(L"git", by_command[0][0].original_word); 259 | EXPECT_EQ(1u, by_command[1].size()); 260 | EXPECT_EQ(L"ninja", by_command[1][0].original_word); 261 | EXPECT_EQ(1u, by_command[2].size()); 262 | EXPECT_EQ(L"stuff", by_command[2][0].original_word); 263 | } 264 | 265 | TEST(CompletionTest, WordsToCommandsQuoted) { 266 | vector words; 267 | CompletionBreakIntoWords(L"git && echo \"ninja with && stuff\"", &words); 268 | vector> by_command = CompletionBreakWordsIntoCommands(words); 269 | EXPECT_EQ(2u, by_command.size()); 270 | EXPECT_EQ(1u, by_command[0].size()); 271 | EXPECT_EQ(L"git", by_command[0][0].original_word); 272 | EXPECT_EQ(2u, by_command[1].size()); 273 | EXPECT_EQ(L"echo", by_command[1][0].original_word); 274 | EXPECT_EQ(L"\"ninja with && stuff\"", by_command[1][1].original_word); 275 | } 276 | 277 | TEST(CompletionTest, WordsToCommandsVariousSeparators) { 278 | vector words; 279 | CompletionBreakIntoWords(L"git || ninja & stuff && wee", &words); 280 | vector> by_command = CompletionBreakWordsIntoCommands(words); 281 | EXPECT_EQ(4u, by_command.size()); 282 | EXPECT_EQ(1u, by_command[0].size()); 283 | EXPECT_EQ(L"git", by_command[0][0].original_word); 284 | EXPECT_EQ(1u, by_command[1].size()); 285 | EXPECT_EQ(L"ninja", by_command[1][0].original_word); 286 | EXPECT_EQ(1u, by_command[2].size()); 287 | EXPECT_EQ(L"stuff", by_command[2][0].original_word); 288 | EXPECT_EQ(1u, by_command[3].size()); 289 | EXPECT_EQ(L"wee", by_command[3][0].original_word); 290 | } 291 | 292 | // TODO 293 | // - special executable handling 294 | // - ^ escape not handled 295 | -------------------------------------------------------------------------------- /src/cmdEx/directory_history.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "cmdEx/directory_history.h" 6 | 7 | #include 8 | 9 | #include "common/util.h" 10 | 11 | DirectoryHistory::DirectoryHistory(WorkingDirectoryInterface* working_dir) 12 | : working_dir_(working_dir), position_(0) { 13 | last_known_ = working_dir_->Get(); 14 | } 15 | 16 | void DirectoryHistory::StartingEdit() { 17 | string current = working_dir_->Get(); 18 | if (last_known_ != current) { 19 | CommitLastKnown(); 20 | last_known_ = current; 21 | } 22 | } 23 | 24 | bool DirectoryHistory::NavigateInHistory(int direction) { 25 | CHECK(direction == 1 || direction == -1); 26 | if (position_ == static_cast(dirs_.size()) && direction == -1) { 27 | CommitLastKnown(); 28 | position_--; 29 | } 30 | int original = position_; 31 | position_ += direction; 32 | position_ = max(position_, 0); 33 | position_ = min(position_, static_cast(dirs_.size() - 1)); 34 | // TODO: If a directory has since been removed, remove from history list, 35 | // and either go to the next or say something maybe? 36 | working_dir_->Set(dirs_[position_]); 37 | last_known_ = dirs_[position_]; 38 | return original != position_; 39 | } 40 | 41 | bool DirectoryHistory::CommitLastKnown() { 42 | for (vector::const_iterator i(dirs_.begin()); i != dirs_.end(); ++i) { 43 | if (*i == last_known_) { 44 | dirs_.erase(i); 45 | break; 46 | } 47 | } 48 | dirs_.push_back(last_known_); 49 | position_ = static_cast(dirs_.size()); 50 | return true; 51 | } 52 | -------------------------------------------------------------------------------- /src/cmdEx/directory_history.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef CMDEX_DIRECTORY_HISTORY_H_ 6 | #define CMDEX_DIRECTORY_HISTORY_H_ 7 | 8 | #include 9 | #include 10 | using namespace std; 11 | 12 | class WorkingDirectoryInterface { 13 | public: 14 | virtual bool Set(const string& dir) = 0; 15 | virtual string Get() = 0; 16 | }; 17 | 18 | class DirectoryHistory { 19 | public: 20 | DirectoryHistory(WorkingDirectoryInterface* working_dir); 21 | 22 | // Called when we resume editing again. If the directory isn't the same as 23 | // the last known, then we jumped: add last known to the list. 24 | void StartingEdit(); 25 | 26 | bool NavigateInHistory(int direction); 27 | 28 | WorkingDirectoryInterface* GetWorkingDirectoryInterface() { 29 | return working_dir_; 30 | } 31 | 32 | private: 33 | bool CommitLastKnown(); 34 | 35 | WorkingDirectoryInterface* working_dir_; 36 | vector dirs_; 37 | string last_known_; 38 | int position_; 39 | }; 40 | 41 | #endif // CMDEX_DIRECTORY_HISTORY_H_ 42 | -------------------------------------------------------------------------------- /src/cmdEx/directory_history_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "gtest/gtest.h" 6 | #include "cmdEx/directory_history.h" 7 | 8 | namespace { 9 | 10 | class MockWorkingDirectory : public WorkingDirectoryInterface { 11 | public: 12 | bool Set(const string& dir) override { 13 | dir_ = dir; 14 | return true; 15 | } 16 | string Get() override { 17 | return dir_; 18 | } 19 | 20 | private: 21 | string dir_; 22 | }; 23 | 24 | } // namespace 25 | 26 | TEST(DirectoryHistoryTest, Basic) { 27 | MockWorkingDirectory wd; 28 | wd.Set("c:\\x"); 29 | 30 | DirectoryHistory dh(&wd); 31 | dh.StartingEdit(); 32 | 33 | wd.Set("c:\\y"); 34 | dh.StartingEdit(); 35 | 36 | wd.Set("c:\\z"); 37 | dh.StartingEdit(); 38 | 39 | dh.NavigateInHistory(-1); 40 | dh.StartingEdit(); 41 | EXPECT_EQ("c:\\y", wd.Get()); 42 | 43 | dh.NavigateInHistory(-1); 44 | dh.StartingEdit(); 45 | EXPECT_EQ("c:\\x", wd.Get()); 46 | 47 | dh.NavigateInHistory(1); 48 | dh.StartingEdit(); 49 | EXPECT_EQ("c:\\y", wd.Get()); 50 | 51 | dh.NavigateInHistory(1); 52 | dh.StartingEdit(); 53 | EXPECT_EQ("c:\\z", wd.Get()); 54 | } 55 | 56 | TEST(DirectoryHistoryTest, WithSetAfterNavigate) { 57 | MockWorkingDirectory wd; 58 | wd.Set("c:\\x"); 59 | 60 | DirectoryHistory dh(&wd); 61 | dh.StartingEdit(); 62 | 63 | wd.Set("c:\\y"); 64 | dh.StartingEdit(); 65 | 66 | wd.Set("c:\\z"); 67 | dh.StartingEdit(); 68 | 69 | dh.NavigateInHistory(-1); 70 | dh.StartingEdit(); 71 | EXPECT_EQ("c:\\y", wd.Get()); 72 | 73 | wd.Set("c:\\a"); 74 | dh.StartingEdit(); 75 | EXPECT_EQ("c:\\a", wd.Get()); 76 | 77 | dh.NavigateInHistory(-1); 78 | dh.StartingEdit(); 79 | EXPECT_EQ("c:\\y", wd.Get()); 80 | 81 | dh.NavigateInHistory(-1); 82 | dh.StartingEdit(); 83 | EXPECT_EQ("c:\\z", wd.Get()); 84 | 85 | dh.NavigateInHistory(-1); 86 | dh.StartingEdit(); 87 | EXPECT_EQ("c:\\x", wd.Get()); 88 | } 89 | 90 | // TODO 91 | /* 92 | TEST(DirectoryHistory, SetFails) { 93 | } 94 | */ 95 | -------------------------------------------------------------------------------- /src/cmdEx/line_editor.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "cmdEx/line_editor.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "cmdEx/command_history.h" 12 | #include "cmdEx/directory_history.h" 13 | #include "common/util.h" 14 | 15 | #pragma comment(lib, "user32.lib") 16 | 17 | void LineEditor::Init(ConsoleInterface* console, 18 | DirectoryHistory* directory_history, 19 | CommandHistory* command_history) { 20 | console_ = console; 21 | console->GetCursorLocation(&start_x_, &start_y_); 22 | largest_y_ = start_y_; 23 | directory_history_ = directory_history; 24 | directory_history_->StartingEdit(); 25 | command_history_ = command_history; 26 | RedrawConsole(); 27 | } 28 | 29 | bool IsModifierKey(int vk) { 30 | // We don't want to do much when these are actually pressed, just when the 31 | // other key they're pressed with is pressed. TODO: I'm sure there's some 32 | // missing, not sure what the better way to do this is. 33 | switch (vk) { 34 | case VK_APPS: 35 | case VK_CAPITAL: 36 | case VK_CONTROL: 37 | case VK_LCONTROL: 38 | case VK_LMENU: 39 | case VK_LSHIFT: 40 | case VK_LWIN: 41 | case VK_MENU: 42 | case VK_NUMLOCK: 43 | case VK_PAUSE: 44 | case VK_RCONTROL: 45 | case VK_RMENU: 46 | case VK_RSHIFT: 47 | case VK_RWIN: 48 | case VK_SCROLL: 49 | case VK_SHIFT: 50 | return true; 51 | } 52 | return false; 53 | } 54 | 55 | LineEditor::HandleAction LineEditor::HandleKeyEvent(bool pressed, 56 | bool ctrl_down, 57 | bool alt_down, 58 | bool shift_down, 59 | unsigned char ascii_char, 60 | unsigned short unicode_char, 61 | int vk) { 62 | if (pressed) { 63 | if (IsModifierKey(vk)) 64 | return kIncomplete; 65 | // Assume that it's not going to continue, and let tab handling put it 66 | // back on if it did continue. TODO: We hang on to results until next 67 | // completion which is kind of dumb. 68 | int previous_completion_begin = completion_word_begin_; 69 | completion_word_begin_ = -1; 70 | 71 | bool second_ctrl_v_was_pending = 72 | second_ctrl_v_pending_saved_position_ != -1; 73 | if (second_ctrl_v_was_pending) { 74 | line_ = second_ctrl_v_pending_saved_line_; 75 | position_ = second_ctrl_v_pending_saved_position_; 76 | } 77 | second_ctrl_v_pending_saved_line_.clear(); 78 | second_ctrl_v_pending_saved_position_ = -1; 79 | 80 | if (alt_down && !ctrl_down && vk == VK_UP) { 81 | fake_command_ = L"cd..\x0d\x0a"; 82 | return kReturnToCmdThenResume; 83 | } else if (alt_down && !ctrl_down && 84 | (vk == VK_LEFT || vk == VK_RIGHT || 85 | vk == VK_BROWSER_BACK || vk == VK_BROWSER_FORWARD)) { 86 | // Navigate back or forward. 87 | bool changed = directory_history_->NavigateInHistory( 88 | (vk == VK_LEFT || vk == VK_BROWSER_BACK) ? -1 : 1); 89 | if (changed) { 90 | // cd . is necessary to get a newline. 91 | fake_command_ = L"cd .\x0d\x0a"; 92 | return kReturnToCmdThenResume; 93 | } 94 | return kIncomplete; 95 | } else if (!alt_down && ctrl_down && vk == 'L') { 96 | fake_command_ = L"cls\x0d\x0a"; 97 | return kReturnToCmdThenResume; 98 | } else if (!alt_down && ctrl_down && vk == 'D') { 99 | if (line_.empty() && position_ == 0) { 100 | line_ = L"exit"; 101 | RedrawConsole(); 102 | fake_command_ = L"exit\x0d\x0a"; 103 | return kReturnToCmdThenResume; 104 | } 105 | return kIncomplete; 106 | } else if (!alt_down && !ctrl_down && vk == VK_RETURN) { 107 | if (!line_.empty()) 108 | command_history_->AddCommand(line_); 109 | line_ += L"\x0d\x0a"; 110 | int x, y; 111 | console_->GetCursorLocation(&x, &y); 112 | start_y_ += console_->SetCursorLocation(0, y + 1); 113 | return kReturnToCmd; 114 | } else if (!alt_down && !ctrl_down && vk == VK_ESCAPE) { 115 | // During prompt, Escape cancels. 116 | if (!second_ctrl_v_was_pending) { 117 | line_ = L""; 118 | position_ = 0; 119 | } 120 | } else if (!alt_down && !ctrl_down && vk == VK_BACK) { 121 | if (position_ == 0 || line_.empty()) 122 | return kIncomplete; 123 | position_--; 124 | line_.erase(position_, 1); 125 | } else if (!alt_down && ctrl_down && vk == 'W') { 126 | int from = FindBackwards(max(0, position_ - 1), " "); 127 | line_.erase(from, position_ - from); 128 | position_ = from; 129 | } else if (!alt_down && !ctrl_down && vk == VK_TAB) { 130 | // We're continuing completion, keep it on. 131 | completion_word_begin_ = previous_completion_begin; 132 | TabComplete(!shift_down); 133 | } else if (!alt_down && ctrl_down && vk == VK_BACK) { 134 | int from = FindBackwards(max(0, position_ - 1), " /\\"); 135 | line_.erase(from, position_ - from); 136 | position_ = from; 137 | } else if (!alt_down && !ctrl_down && vk == VK_DELETE) { 138 | if (position_ == static_cast(line_.size()) || line_.empty()) 139 | return kIncomplete; 140 | line_.erase(position_, 1); 141 | } else if (!alt_down && ctrl_down && (vk == VK_END || vk == 'K')) { 142 | line_.erase(position_, line_.size() - position_); 143 | } else if (!alt_down && ctrl_down && (vk == VK_HOME || vk == 'U')) { 144 | line_.erase(0, position_); 145 | position_ = 0; 146 | } else if (!alt_down && !ctrl_down && vk == VK_LEFT) { 147 | position_ = max(0, position_ - 1); 148 | } else if (!alt_down && !ctrl_down && vk == VK_RIGHT) { 149 | position_ = min(static_cast(line_.size()), position_ + 1); 150 | } else if (!alt_down && ctrl_down && vk == VK_LEFT) { 151 | position_ = FindBackwards(max(0, position_ - 1), " "); 152 | } else if (!alt_down && ctrl_down && vk == VK_RIGHT) { 153 | position_ = min(static_cast(line_.size() - 1), 154 | FindForwards(position_, " ") + 1); 155 | while (position_ < static_cast(line_.size()) - 1 && 156 | line_[position_] == L' ') 157 | position_++; 158 | } else if ((!alt_down && !ctrl_down && vk == VK_HOME) || 159 | (!alt_down && ctrl_down && vk == 'A')) { 160 | position_ = 0; 161 | } else if ((!alt_down && !ctrl_down && vk == VK_END) || 162 | (!alt_down && ctrl_down && vk == 'E')) { 163 | position_ = static_cast(line_.size()); 164 | } else if (!alt_down && !ctrl_down && vk == VK_UP) { 165 | if (command_history_->MoveInHistory(-1, L"", &line_)) 166 | position_ = static_cast(line_.size()); 167 | } else if (!alt_down && !ctrl_down && vk == VK_DOWN) { 168 | if (command_history_->MoveInHistory(1, L"", &line_)) 169 | position_ = static_cast(line_.size()); 170 | } else if (!alt_down && !ctrl_down && (vk == VK_PRIOR || vk == VK_F8)) { 171 | command_history_->MoveInHistory(-1, line_.substr(0, position_), &line_); 172 | } else if (!alt_down && !ctrl_down && vk == VK_NEXT) { 173 | command_history_->MoveInHistory(1, line_.substr(0, position_), &line_); 174 | } else if (!alt_down && ctrl_down && vk == 'V') { 175 | wstring text; 176 | if (console_->GetClipboardText(&text)) { 177 | if (std::count(text.begin(), text.end(), L'\n') > 0 && 178 | !second_ctrl_v_was_pending) { 179 | second_ctrl_v_pending_saved_line_ = line_; 180 | second_ctrl_v_pending_saved_position_ = position_; 181 | line_ = L""; 182 | position_ = 0; 183 | } else { 184 | //vector to_paste = StringSplit(text, L'\n'); 185 | line_.insert(position_, text); 186 | position_ += static_cast(text.size()); 187 | } 188 | } 189 | } else if (isprint(ascii_char)) { 190 | line_.insert(position_, 1, ascii_char); 191 | position_++; 192 | } 193 | RedrawConsole(); 194 | } 195 | return kIncomplete; 196 | } 197 | 198 | void LineEditor::ToCmdBuffer(wchar_t* buffer, 199 | unsigned long buffer_size, 200 | unsigned long* num_chars) { 201 | if (!fake_command_.empty()) { 202 | wcscpy_s(buffer, buffer_size, fake_command_.c_str()); 203 | *num_chars = static_cast(fake_command_.size()); 204 | fake_command_.clear(); 205 | } else { 206 | wcscpy_s(buffer, buffer_size, line_.c_str()); 207 | *num_chars = static_cast(line_.size()); 208 | line_.clear(); 209 | position_ = 0; 210 | } 211 | } 212 | 213 | void LineEditor::RegisterCompleter(Completer completer) { 214 | completers_.push_back(completer); 215 | } 216 | 217 | bool LineEditor::IsCompleting() const { 218 | return !completion_output_.results.empty() && completion_word_begin_ != -1; 219 | } 220 | 221 | // This code is redonk. Instead: 222 | // - break line_ into N chunks given width, and initial X 223 | // - draw them at initial X and subsequent Y at X=0 224 | // - position the cursor based 225 | // - clear to the end of line of the last Y we previously wrote to 226 | // - save the new Y range 227 | 228 | struct LineChunk { 229 | LineChunk(int offset, int start_x, int start_y) 230 | : start_offset(offset), start_x(start_x), start_y(start_y) {} 231 | 232 | int start_offset; 233 | wstring contents; 234 | int start_x; 235 | int start_y; 236 | }; 237 | 238 | vector BreakIntoLineChunks(const wstring& line, 239 | int startx, 240 | int starty, 241 | int width) { 242 | int x = startx; 243 | int y = starty; 244 | int offset = 0; 245 | vector chunks; 246 | LineChunk chunk(offset, x, y); 247 | // TODO: This could be better. 248 | for (const auto c : line) { 249 | chunk.contents.push_back(c); 250 | ++offset; 251 | ++x; 252 | if (x == width) { 253 | x = 0; 254 | ++y; 255 | chunks.push_back(chunk); 256 | chunk = LineChunk(offset, x, y); 257 | } 258 | } 259 | if (!chunk.contents.empty()) 260 | chunks.push_back(chunk); 261 | return chunks; 262 | } 263 | 264 | void LineEditor::ScrollByOneLine() { 265 | // This is a bit of a silly way to scroll, but we set the cursor one 266 | // past the end, causing scrolling, and then reset it. 267 | int height = console_->GetHeight(); 268 | int cursor_x, cursor_y; 269 | console_->GetCursorLocation(&cursor_x, &cursor_y); 270 | console_->SetCursorLocation(cursor_x, height); 271 | console_->SetCursorLocation(cursor_x, cursor_y); 272 | } 273 | 274 | void LineEditor::RedrawConsole() { 275 | int width = console_->GetWidth(); 276 | int height = console_->GetHeight(); 277 | CHECK(width > 0); 278 | bool cursor_past_end = position_ == static_cast(line_.size()); 279 | vector chunks = BreakIntoLineChunks( 280 | line_ + (cursor_past_end ? wstring(L"\x01") : wstring(L"")), 281 | start_x_, 282 | start_y_, 283 | width); 284 | while (chunks[chunks.size() - 1].start_y >= height) { 285 | ScrollByOneLine(); 286 | for (auto& chunk : chunks) 287 | --chunk.start_y; 288 | --start_y_; 289 | } 290 | int last_drawn_x = start_x_; 291 | int last_drawn_y = start_y_; 292 | int modify_start_y_by = 0; 293 | for (size_t i = 0; i < chunks.size(); ++i) { 294 | const LineChunk& chunk = chunks[i]; 295 | size_t to_draw = chunk.contents.size(); 296 | if (i == chunks.size() - 1 && cursor_past_end) 297 | --to_draw; 298 | console_->DrawString(chunk.contents.c_str(), static_cast(to_draw), 299 | chunk.start_x, chunk.start_y); 300 | last_drawn_x = static_cast(chunk.start_x + to_draw); 301 | last_drawn_y = chunk.start_y; 302 | if (position_ >= chunk.start_offset && 303 | position_ < 304 | chunk.start_offset + static_cast(chunk.contents.size())) { 305 | modify_start_y_by += console_->SetCursorLocation( 306 | chunk.start_x + (position_ - chunk.start_offset), 307 | chunk.start_y + modify_start_y_by); 308 | } 309 | } 310 | start_y_ += modify_start_y_by; 311 | largest_y_ += modify_start_y_by; 312 | 313 | int clear_from = last_drawn_x; 314 | for (int y = last_drawn_y; y <= largest_y_; ++y) { 315 | console_->FillChar(L' ', width - clear_from, clear_from, y); 316 | clear_from = 0; 317 | } 318 | largest_y_ = 319 | chunks.empty() ? start_y_ : chunks.back().start_y + modify_start_y_by; 320 | } 321 | 322 | int LineEditor::FindBackwards(int start_at, const char* until) { 323 | int result = start_at; 324 | while (result >= 0 && strchr(until, line_[result]) != NULL) 325 | result--; 326 | while (result >= 0 && strchr(until, line_[result]) == NULL) 327 | result--; 328 | result++; 329 | return result; 330 | } 331 | 332 | int LineEditor::FindForwards(int start_at, const char* until) { 333 | int result = start_at; 334 | while (result < static_cast(line_.size()) && 335 | strchr(until, line_[result]) != NULL) 336 | result++; 337 | while (result < static_cast(line_.size()) && 338 | strchr(until, line_[result]) == NULL) 339 | result++; 340 | result--; 341 | return result; 342 | } 343 | 344 | void LineEditor::TabComplete(bool forward_cycle) { 345 | bool started = false; 346 | if (!IsCompleting()) { 347 | vector words; 348 | CompletionBreakIntoWords(line_, &words); 349 | CompleterInput input; 350 | input.word_data = words; // TODO 351 | input.word_index = CompletionWordIndex(input.word_data, position_); 352 | input.position_in_word = 353 | position_ - input.word_data[input.word_index].original_offset; 354 | for (vector::const_iterator i(completers_.begin()); 355 | i != completers_.end(); 356 | ++i) { 357 | completion_output_.Reset(); 358 | if ((*i)(input, &completion_output_)) { 359 | // We'll be completing from begin_ to position_ subbing in results_. 360 | // position_ is updated over time, so old end isn't saved. 361 | started = true; 362 | completion_word_begin_ = 363 | input.word_data[input.word_index].original_offset; 364 | completion_word_end_ = static_cast( 365 | completion_word_begin_ + 366 | input.word_data[input.word_index].original_word.size()); 367 | break; 368 | } 369 | } 370 | } 371 | 372 | if (!IsCompleting()) 373 | return; 374 | 375 | if (started) { 376 | completion_index_ = 377 | forward_cycle ? 0 378 | : static_cast(completion_output_.results.size()) - 1; 379 | } else { 380 | completion_index_ += forward_cycle ? 1 : -1; 381 | if (completion_index_ < 0) 382 | completion_index_ = static_cast(completion_output_.results.size()) - 1; 383 | else if (completion_index_ >= 384 | static_cast(completion_output_.results.size())) 385 | completion_index_ = 0; 386 | } 387 | 388 | // Remove the old one (or the stub of one if we just started). 389 | line_.erase(completion_word_begin_, 390 | completion_word_end_ - completion_word_begin_); 391 | position_ = completion_word_begin_; 392 | 393 | // Insert the new one. 394 | wstring quoted = QuoteWord(completion_output_.results[completion_index_]); 395 | if (completion_output_.trailing_space) 396 | quoted += L" "; 397 | line_.insert(completion_word_begin_, quoted); 398 | position_ = static_cast(completion_word_begin_ + quoted.size()); 399 | completion_word_end_ = position_; 400 | RedrawConsole(); 401 | } 402 | -------------------------------------------------------------------------------- /src/cmdEx/line_editor.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef CMDEX_LINE_EDITOR_H_ 6 | #define CMDEX_LINE_EDITOR_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include "cmdEx/completion.h" 12 | 13 | class CommandHistory; 14 | class DirectoryHistory; 15 | 16 | class ConsoleInterface { 17 | public: 18 | virtual void GetCursorLocation(int* x, int* y) = 0; 19 | virtual int GetWidth() = 0; 20 | virtual int GetHeight() = 0; 21 | // |str| not null terminated. 22 | virtual void DrawString(const wchar_t* str, int count, int x, int y) = 0; 23 | virtual void FillChar(wchar_t ch, int count, int x, int y) = 0; 24 | // Return is amount adjust start_y (when console has been scrolled). 25 | virtual int SetCursorLocation(int x, int y) = 0; 26 | virtual bool GetClipboardText(wstring* text) = 0; 27 | }; 28 | 29 | class LineEditor { 30 | public: 31 | LineEditor() 32 | : console_(NULL), start_x_(0), start_y_(0), position_(0), 33 | directory_history_(NULL), command_history_(NULL), 34 | completion_index_(-1), second_ctrl_v_pending_saved_position_(-1) {} 35 | 36 | // Called initially and on each editing resumption. |directory_history| and 37 | // |command_history| are not owned. 38 | void Init(ConsoleInterface* console, 39 | DirectoryHistory* directory_history, 40 | CommandHistory* command_history); 41 | 42 | enum HandleAction { 43 | kIncomplete, 44 | kReturnToCmd, 45 | kReturnToCmdThenResume, 46 | }; 47 | 48 | // Returns whether a command is now ready for return to cmd. 49 | HandleAction HandleKeyEvent(bool pressed, 50 | bool ctrl_down, 51 | bool alt_down, 52 | bool shift_down, 53 | unsigned char ascii_char, 54 | unsigned short unicode_char, 55 | int vk); 56 | 57 | void ToCmdBuffer(wchar_t* buffer, 58 | unsigned long buffer_size, 59 | unsigned long* num_chars); 60 | 61 | // So tests can inject non-filesystem ones. More specific ones should be 62 | // registered first. 63 | void RegisterCompleter(Completer completer); 64 | 65 | bool IsCompleting() const; 66 | 67 | private: 68 | void RedrawConsole(); 69 | int FindBackwards(int start_at, const char* until); 70 | int FindForwards(int start_at, const char* until); 71 | void TabComplete(bool forward_cycle); 72 | void ScrollByOneLine(); 73 | 74 | ConsoleInterface* console_; 75 | int start_x_; 76 | int start_y_; 77 | int largest_y_; // The farthest down line wrapping has gotten us. 78 | wstring line_; 79 | int position_; 80 | wstring fake_command_; 81 | DirectoryHistory* directory_history_; // Weak. 82 | CommandHistory* command_history_; // Weak. 83 | 84 | vector completers_; 85 | 86 | int completion_word_begin_; 87 | int completion_word_end_; 88 | int completion_index_; 89 | CompleterOutput completion_output_; 90 | wstring second_ctrl_v_pending_saved_line_; 91 | int second_ctrl_v_pending_saved_position_; 92 | }; 93 | 94 | #endif // CMDEX_LINE_EDITOR_H_ 95 | -------------------------------------------------------------------------------- /src/cmdEx/string_util.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "cmdEx/string_util.h" 6 | 7 | vector StringSplit(const wstring& str, wchar_t break_at) { 8 | vector result; 9 | wstring current; 10 | for (const auto& i : str) { 11 | if (i == break_at) { 12 | result.push_back(current); 13 | current = wstring(); 14 | continue; 15 | } 16 | current.push_back(i); 17 | } 18 | result.push_back(current); 19 | return result; 20 | } 21 | -------------------------------------------------------------------------------- /src/cmdEx/string_util.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef CMDEX_STRING_UTIL_H_ 6 | #define CMDEX_STRING_UTIL_H_ 7 | 8 | #include 9 | #include 10 | using namespace std; 11 | 12 | vector StringSplit(const wstring& str, wchar_t break_at); 13 | 14 | #endif // CMDEX_STRING_UTIL_H_ 15 | -------------------------------------------------------------------------------- /src/cmdEx/string_util_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "cmdEx/string_util.h" 6 | #include "gtest/gtest.h" 7 | 8 | TEST(StringUtil, StringSplitStandard) { 9 | vector s = StringSplit(L"a;bcd;;efghi", L';'); 10 | EXPECT_EQ(4u, s.size()); 11 | EXPECT_EQ(L"a", s[0]); 12 | EXPECT_EQ(L"bcd", s[1]); 13 | EXPECT_EQ(L"", s[2]); 14 | EXPECT_EQ(L"efghi", s[3]); 15 | } 16 | 17 | TEST(StringUtil, StringSplitEndingEmpty) { 18 | vector s = StringSplit(L"a;", L';'); 19 | EXPECT_EQ(2u, s.size()); 20 | EXPECT_EQ(L"a", s[0]); 21 | EXPECT_EQ(L"", s[1]); 22 | } 23 | 24 | TEST(StringUtil, StringSplitStartingEmpty) { 25 | vector s = StringSplit(L";b", L';'); 26 | EXPECT_EQ(2u, s.size()); 27 | EXPECT_EQ(L"", s[0]); 28 | EXPECT_EQ(L"b", s[1]); 29 | } 30 | 31 | TEST(StringUtil, StringSplitNoSplits) { 32 | vector s = StringSplit(L"blorpy", L';'); 33 | EXPECT_EQ(1u, s.size()); 34 | EXPECT_EQ(L"blorpy", s[0]); 35 | } 36 | -------------------------------------------------------------------------------- /src/cmdEx/subprocess.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Adapted from http://github.com/martine/ninja/ 16 | 17 | #include "cmdEx/subprocess.h" 18 | 19 | #include 20 | 21 | #include 22 | 23 | #include "common/util.h" 24 | 25 | Subprocess::Subprocess() : child_(NULL), overlapped_(), is_reading_(false) {} 26 | 27 | Subprocess::~Subprocess() { 28 | if (pipe_) { 29 | if (!CloseHandle(pipe_)) 30 | Fatal("CloseHandle"); 31 | } 32 | // Reap child if forgotten. 33 | if (child_) 34 | Finish(); 35 | } 36 | 37 | HANDLE Subprocess::SetupPipe(HANDLE ioport) { 38 | char pipe_name[100]; 39 | _snprintf(pipe_name, 40 | sizeof(pipe_name), 41 | "\\\\.\\pipe\\cmdex_pid%lu_sp%p", 42 | GetCurrentProcessId(), 43 | this); 44 | 45 | pipe_ = ::CreateNamedPipeA(pipe_name, 46 | PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, 47 | PIPE_TYPE_BYTE, 48 | PIPE_UNLIMITED_INSTANCES, 49 | 0, 50 | 0, 51 | INFINITE, 52 | NULL); 53 | if (pipe_ == INVALID_HANDLE_VALUE) 54 | Fatal("CreateNamedPipe"); 55 | 56 | if (!CreateIoCompletionPort(pipe_, ioport, (ULONG_PTR) this, 0)) 57 | Fatal("CreateIoCompletionPort"); 58 | 59 | memset(&overlapped_, 0, sizeof(overlapped_)); 60 | if (!ConnectNamedPipe(pipe_, &overlapped_) && 61 | GetLastError() != ERROR_IO_PENDING) { 62 | Fatal("ConnectNamedPipe"); 63 | } 64 | 65 | // Get the write end of the pipe as a handle inheritable across processes. 66 | HANDLE output_write_handle = 67 | CreateFile(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); 68 | HANDLE output_write_child; 69 | if (!DuplicateHandle(GetCurrentProcess(), 70 | output_write_handle, 71 | GetCurrentProcess(), 72 | &output_write_child, 73 | 0, 74 | TRUE, 75 | DUPLICATE_SAME_ACCESS)) { 76 | Fatal("DuplicateHandle"); 77 | } 78 | CloseHandle(output_write_handle); 79 | 80 | return output_write_child; 81 | } 82 | 83 | bool Subprocess::Start(SubprocessSet* set, const wstring& command) { 84 | HANDLE child_pipe = SetupPipe(set->ioport_); 85 | 86 | SECURITY_ATTRIBUTES security_attributes; 87 | memset(&security_attributes, 0, sizeof(SECURITY_ATTRIBUTES)); 88 | security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); 89 | security_attributes.bInheritHandle = TRUE; 90 | // Must be inheritable so subprocesses can dup to children. 91 | HANDLE nul = 92 | CreateFile("NUL", 93 | GENERIC_READ, 94 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 95 | &security_attributes, 96 | OPEN_EXISTING, 97 | 0, 98 | NULL); 99 | if (nul == INVALID_HANDLE_VALUE) 100 | Fatal("couldn't open nul"); 101 | 102 | STARTUPINFOW startup_info; 103 | memset(&startup_info, 0, sizeof(startup_info)); 104 | startup_info.cb = sizeof(STARTUPINFO); 105 | startup_info.dwFlags = STARTF_USESTDHANDLES; 106 | startup_info.hStdInput = nul; 107 | startup_info.hStdOutput = child_pipe; 108 | startup_info.hStdError = child_pipe; 109 | 110 | PROCESS_INFORMATION process_info; 111 | memset(&process_info, 0, sizeof(process_info)); 112 | 113 | // Do not prepend 'cmd /c' on Windows, this breaks command 114 | // lines greater than 8,191 chars. 115 | if (!CreateProcessW(NULL, 116 | (wchar_t*)command.c_str(), 117 | NULL, 118 | NULL, 119 | /* inherit handles */ TRUE, 120 | CREATE_NEW_PROCESS_GROUP, 121 | NULL, 122 | NULL, 123 | &startup_info, 124 | &process_info)) { 125 | DWORD error = GetLastError(); 126 | if (error == ERROR_FILE_NOT_FOUND) { 127 | // File (program) not found error is treated as a normal build 128 | // action failure. 129 | if (child_pipe) 130 | CloseHandle(child_pipe); 131 | CloseHandle(pipe_); 132 | CloseHandle(nul); 133 | pipe_ = NULL; 134 | // child_ is already NULL; 135 | buf_ = L"CreateProcess failed: " 136 | L"The system cannot find the file specified.\n"; 137 | return true; 138 | } else { 139 | Fatal("CreateProcess"); // pass all other errors to Fatal 140 | } 141 | } 142 | 143 | // Close pipe channel only used by the child. 144 | if (child_pipe) 145 | CloseHandle(child_pipe); 146 | CloseHandle(nul); 147 | 148 | CloseHandle(process_info.hThread); 149 | child_ = process_info.hProcess; 150 | 151 | return true; 152 | } 153 | 154 | void Subprocess::OnPipeReady() { 155 | DWORD bytes; 156 | if (!GetOverlappedResult(pipe_, &overlapped_, &bytes, TRUE)) { 157 | if (GetLastError() == ERROR_BROKEN_PIPE) { 158 | CloseHandle(pipe_); 159 | pipe_ = NULL; 160 | return; 161 | } 162 | Fatal("GetOverlappedResult"); 163 | } 164 | 165 | if (is_reading_ && bytes) { 166 | // TODO: What are we actually receiving here? 167 | for (size_t i = 0; i < bytes; ++i) 168 | buf_.push_back(overlapped_buf_[i]); 169 | } 170 | 171 | memset(&overlapped_, 0, sizeof(overlapped_)); 172 | is_reading_ = true; 173 | if (!::ReadFile(pipe_, 174 | overlapped_buf_, 175 | sizeof(overlapped_buf_), 176 | &bytes, 177 | &overlapped_)) { 178 | if (GetLastError() == ERROR_BROKEN_PIPE) { 179 | CloseHandle(pipe_); 180 | pipe_ = NULL; 181 | return; 182 | } 183 | if (GetLastError() != ERROR_IO_PENDING) 184 | Fatal("ReadFile"); 185 | } 186 | 187 | // Even if we read any bytes in the readfile call, we'll enter this 188 | // function again later and get them at that point. 189 | } 190 | 191 | ExitStatus Subprocess::Finish() { 192 | if (!child_) 193 | return ExitFailure; 194 | 195 | // TODO: add error handling for all of these. 196 | WaitForSingleObject(child_, INFINITE); 197 | 198 | DWORD exit_code = 0; 199 | GetExitCodeProcess(child_, &exit_code); 200 | 201 | CloseHandle(child_); 202 | child_ = NULL; 203 | 204 | return exit_code == 0 ? ExitSuccess : exit_code == CONTROL_C_EXIT 205 | ? ExitInterrupted 206 | : ExitFailure; 207 | } 208 | 209 | bool Subprocess::Done() const { return pipe_ == NULL; } 210 | 211 | const wstring& Subprocess::GetOutput() const { return buf_; } 212 | 213 | HANDLE SubprocessSet::ioport_; 214 | 215 | SubprocessSet::SubprocessSet() { 216 | ioport_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1); 217 | if (!ioport_) 218 | Fatal("CreateIoCompletionPort"); 219 | if (!SetConsoleCtrlHandler(NotifyInterrupted, TRUE)) 220 | Fatal("SetConsoleCtrlHandler"); 221 | } 222 | 223 | SubprocessSet::~SubprocessSet() { 224 | Clear(); 225 | 226 | SetConsoleCtrlHandler(NotifyInterrupted, FALSE); 227 | CloseHandle(ioport_); 228 | } 229 | 230 | BOOL WINAPI SubprocessSet::NotifyInterrupted(DWORD dwCtrlType) { 231 | if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) { 232 | if (!PostQueuedCompletionStatus(ioport_, 0, 0, NULL)) 233 | Fatal("PostQueuedCompletionStatus"); 234 | return TRUE; 235 | } 236 | 237 | return FALSE; 238 | } 239 | 240 | Subprocess* SubprocessSet::Add(const wstring& command) { 241 | Subprocess* subprocess = new Subprocess; 242 | if (!subprocess->Start(this, command)) { 243 | delete subprocess; 244 | return 0; 245 | } 246 | if (subprocess->child_) 247 | running_.push_back(subprocess); 248 | else 249 | finished_.push(subprocess); 250 | return subprocess; 251 | } 252 | 253 | bool SubprocessSet::DoWork() { 254 | DWORD bytes_read; 255 | Subprocess* subproc; 256 | OVERLAPPED* overlapped; 257 | 258 | if (!GetQueuedCompletionStatus(ioport_, 259 | &bytes_read, 260 | (PULONG_PTR) & subproc, 261 | &overlapped, 262 | INFINITE)) { 263 | if (GetLastError() != ERROR_BROKEN_PIPE) 264 | Fatal("GetQueuedCompletionStatus"); 265 | } 266 | 267 | if (!subproc) // A NULL subproc indicates that we were interrupted and is 268 | // delivered by NotifyInterrupted above. 269 | return true; 270 | 271 | subproc->OnPipeReady(); 272 | 273 | if (subproc->Done()) { 274 | vector::iterator end = 275 | std::remove(running_.begin(), running_.end(), subproc); 276 | if (running_.end() != end) { 277 | finished_.push(subproc); 278 | running_.resize(end - running_.begin()); 279 | } 280 | } 281 | 282 | return false; 283 | } 284 | 285 | Subprocess* SubprocessSet::NextFinished() { 286 | if (finished_.empty()) 287 | return NULL; 288 | Subprocess* subproc = finished_.front(); 289 | finished_.pop(); 290 | return subproc; 291 | } 292 | 293 | void SubprocessSet::Clear() { 294 | for (vector::iterator i = running_.begin(); i != running_.end(); 295 | ++i) { 296 | if ((*i)->child_) { 297 | if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 298 | GetProcessId((*i)->child_))) { 299 | Fatal("GenerateConsoleCtrlEvent"); 300 | } 301 | } 302 | } 303 | for (vector::iterator i = running_.begin(); i != running_.end(); 304 | ++i) 305 | delete *i; 306 | running_.clear(); 307 | } 308 | -------------------------------------------------------------------------------- /src/cmdEx/subprocess.h: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Adapted from http://github.com/martine/ninja/ 16 | 17 | #ifndef CMDEX_SUBPROCESS_H_ 18 | #define CMDEX_SUBPROCESS_H_ 19 | 20 | #include 21 | #include 22 | #include 23 | using namespace std; 24 | 25 | #include 26 | 27 | enum ExitStatus { 28 | ExitSuccess, 29 | ExitFailure, 30 | ExitInterrupted 31 | }; 32 | 33 | // Subprocess wraps a single async subprocess. It is entirely 34 | // passive: it expects the caller to notify it when its fds are ready 35 | // for reading, as well as call Finish() to reap the child once done() 36 | // is true. 37 | class Subprocess { 38 | public: 39 | ~Subprocess(); 40 | 41 | // Returns ExitSuccess on successful process exit, ExitInterrupted if 42 | // the process was interrupted, ExitFailure if it otherwise failed. 43 | ExitStatus Finish(); 44 | 45 | bool Done() const; 46 | 47 | const wstring& GetOutput() const; 48 | 49 | private: 50 | Subprocess(); 51 | bool Start(class SubprocessSet* set, const wstring& command); 52 | void OnPipeReady(); 53 | 54 | wstring buf_; 55 | 56 | // Set up pipe_ as the parent-side pipe of the subprocess; return the 57 | // other end of the pipe, usable in the child process. 58 | HANDLE SetupPipe(HANDLE ioport); 59 | 60 | HANDLE child_; 61 | HANDLE pipe_; 62 | OVERLAPPED overlapped_; 63 | char overlapped_buf_[4 << 10]; 64 | bool is_reading_; 65 | 66 | friend class SubprocessSet; 67 | }; 68 | 69 | // SubprocessSet runs a ppoll/pselect() loop around a set of Subprocesses. 70 | // DoWork() waits for any state change in subprocesses; finished_ 71 | // is a queue of subprocesses as they finish. 72 | class SubprocessSet { 73 | public: 74 | SubprocessSet(); 75 | ~SubprocessSet(); 76 | 77 | Subprocess* Add(const wstring& command); 78 | bool DoWork(); 79 | Subprocess* NextFinished(); 80 | void Clear(); 81 | 82 | vector running_; 83 | queue finished_; 84 | 85 | static BOOL WINAPI NotifyInterrupted(DWORD dwCtrlType); 86 | static HANDLE ioport_; 87 | }; 88 | 89 | #endif // CMDEX_SUBPROCESS_H_ 90 | -------------------------------------------------------------------------------- /src/cmdEx/subprocess_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Adapted from http://github.com/martine/ninja/ 16 | 17 | #include "cmdEx/subprocess.h" 18 | 19 | #include "gtest/gtest.h" 20 | 21 | namespace { 22 | 23 | const wchar_t* kSimpleCommand = L"cmd /c dir \\"; 24 | 25 | struct SubprocessTest : public testing::Test { 26 | SubprocessSet subprocs_; 27 | }; 28 | 29 | } // anonymous namespace 30 | 31 | // Run a command that fails and emits to stderr. 32 | TEST_F(SubprocessTest, BadCommandStderr) { 33 | Subprocess* subproc = subprocs_.Add(L"cmd /c cmdex_no_such_command"); 34 | ASSERT_NE((Subprocess *) 0, subproc); 35 | 36 | while (!subproc->Done()) { 37 | // Pretend we discovered that stderr was ready for writing. 38 | subprocs_.DoWork(); 39 | } 40 | 41 | EXPECT_EQ(ExitFailure, subproc->Finish()); 42 | EXPECT_NE(L"", subproc->GetOutput()); 43 | } 44 | 45 | // Run a command that does not exist 46 | TEST_F(SubprocessTest, NoSuchCommand) { 47 | Subprocess* subproc = subprocs_.Add(L"cmdex_no_such_command"); 48 | ASSERT_NE((Subprocess *) 0, subproc); 49 | 50 | while (!subproc->Done()) { 51 | // Pretend we discovered that stderr was ready for writing. 52 | subprocs_.DoWork(); 53 | } 54 | 55 | EXPECT_EQ(ExitFailure, subproc->Finish()); 56 | EXPECT_NE(L"", subproc->GetOutput()); 57 | ASSERT_EQ( 58 | L"CreateProcess failed: The system cannot find the file specified.\n", 59 | subproc->GetOutput()); 60 | } 61 | 62 | TEST_F(SubprocessTest, SetWithSingle) { 63 | Subprocess* subproc = subprocs_.Add(kSimpleCommand); 64 | ASSERT_NE((Subprocess *) 0, subproc); 65 | 66 | while (!subproc->Done()) { 67 | subprocs_.DoWork(); 68 | } 69 | ASSERT_EQ(ExitSuccess, subproc->Finish()); 70 | ASSERT_NE(L"", subproc->GetOutput()); 71 | 72 | ASSERT_EQ(1u, subprocs_.finished_.size()); 73 | } 74 | 75 | TEST_F(SubprocessTest, SetWithMulti) { 76 | Subprocess* processes[3]; 77 | const wchar_t* kCommands[3] = { 78 | kSimpleCommand, 79 | L"cmd /c echo hi", 80 | L"cmd /c time /t", 81 | }; 82 | 83 | for (int i = 0; i < 3; ++i) { 84 | processes[i] = subprocs_.Add(kCommands[i]); 85 | ASSERT_NE((Subprocess *) 0, processes[i]); 86 | } 87 | 88 | ASSERT_EQ(3u, subprocs_.running_.size()); 89 | for (int i = 0; i < 3; ++i) { 90 | ASSERT_FALSE(processes[i]->Done()); 91 | ASSERT_EQ(L"", processes[i]->GetOutput()); 92 | } 93 | 94 | while (!processes[0]->Done() || !processes[1]->Done() || 95 | !processes[2]->Done()) { 96 | ASSERT_GT(subprocs_.running_.size(), 0u); 97 | subprocs_.DoWork(); 98 | } 99 | 100 | ASSERT_EQ(0u, subprocs_.running_.size()); 101 | ASSERT_EQ(3u, subprocs_.finished_.size()); 102 | 103 | for (int i = 0; i < 3; ++i) { 104 | ASSERT_EQ(ExitSuccess, processes[i]->Finish()); 105 | ASSERT_NE(L"", processes[i]->GetOutput()); 106 | delete processes[i]; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/cmdEx_exe/main.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "cmdEx_exe/resource.h" 11 | #include "common/util.h" 12 | 13 | namespace { 14 | 15 | typedef LONG( 16 | WINAPI* NtQueryInformationProcessType)(HANDLE, ULONG, PVOID, ULONG, PULONG); 17 | 18 | // Retrieves the pid of the parent of the current process. In our case, this 19 | // should be the cmd.exe that ran this program. 20 | DWORD GetParentPid() { 21 | NtQueryInformationProcessType function_pointer = 22 | reinterpret_cast(GetProcAddress( 23 | LoadLibraryA("ntdll.dll"), "NtQueryInformationProcess")); 24 | if (function_pointer) { 25 | ULONG_PTR pbi[6]; 26 | ULONG size; 27 | LONG ret = 28 | function_pointer(GetCurrentProcess(), 0, &pbi, sizeof(pbi), &size); 29 | if (ret >= 0 && size == sizeof(pbi)) 30 | return static_cast(pbi[5]); 31 | } 32 | 33 | return 0; 34 | } 35 | 36 | void ChdirToTemp(char* into, size_t size) { 37 | if (!GetTempPath(static_cast(size), into)) 38 | return; 39 | #ifndef NDEBUG 40 | fprintf(stderr, "chdir to %s\n", into); 41 | #endif 42 | _chdir(into); 43 | // TODO: Possibly make a subdir, but then we need to be able to clean up. 44 | } 45 | 46 | bool GetFileResource(int resource_id, unsigned char** data, size_t* size) { 47 | HRSRC res = FindResource(NULL, MAKEINTRESOURCE(resource_id), RT_RCDATA); 48 | if (!res) 49 | return false; 50 | HGLOBAL res_handle = LoadResource(NULL, res); 51 | if (!res_handle) 52 | return false; 53 | if (data) 54 | *data = reinterpret_cast(LockResource(res_handle)); 55 | if (size) 56 | *size = SizeofResource(NULL, res); 57 | return true; 58 | } 59 | 60 | FILETIME GetMtimeOfFile(const char* filename) { 61 | HANDLE file = CreateFile( 62 | filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 63 | FILETIME ft_write; 64 | if (!GetFileTime(file, NULL, NULL, &ft_write)) { 65 | FILETIME none = {0}; 66 | return none; 67 | } 68 | CloseHandle(file); 69 | return ft_write; 70 | } 71 | 72 | void SetMtimeOfFile(const char* filename, FILETIME mtime) { 73 | HANDLE file = CreateFile( 74 | filename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); 75 | if (!SetFileTime(file, NULL, NULL, &mtime)) 76 | Fatal("Couldn't SetFileTime of '%s'\n", filename); 77 | CloseHandle(file); 78 | } 79 | 80 | FILETIME GetOwnMtime() { 81 | char self_name[_MAX_PATH]; 82 | if (!GetModuleFileName(NULL, self_name, sizeof(self_name))) 83 | Fatal("Couldn't GetModuleFileName\n"); 84 | return GetMtimeOfFile(self_name); 85 | } 86 | 87 | bool MtimeMatchesSelf(const char* filename) { 88 | FILETIME own = GetOwnMtime(); 89 | FILETIME file = GetMtimeOfFile(filename); 90 | return own.dwLowDateTime == file.dwLowDateTime && 91 | own.dwHighDateTime == file.dwHighDateTime; 92 | } 93 | 94 | void ExtractFileResource(int resource_id, const char* filename) { 95 | unsigned char* data; 96 | size_t size; 97 | #ifndef NDEBUG 98 | fprintf(stderr, "Extract: %d %s\n", resource_id, filename); 99 | #endif 100 | if (MtimeMatchesSelf(filename)) { 101 | #ifndef NDEBUG 102 | fprintf(stderr, " mtime unchanged, skipping\n"); 103 | #endif 104 | return; 105 | } else { 106 | #ifndef NDEBUG 107 | fprintf(stderr, " mtime doesn't match, trying extract\n"); 108 | #endif 109 | } 110 | if (GetFileResource(resource_id, &data, &size)) { 111 | #ifndef NDEBUG 112 | fprintf( 113 | stderr, "writing %d to %s (%zd bytes)\n", resource_id, filename, size); 114 | #endif 115 | FILE* fp = fopen(filename, "wb"); 116 | if (!fp) { 117 | const size_t kBufSize = 10<<20; 118 | void* buf = malloc(kBufSize); 119 | FILE* readfp = fopen(filename, "rb"); 120 | if (readfp) { 121 | size_t bytes_read = fread(buf, 1, kBufSize, readfp); 122 | fclose(readfp); 123 | if (bytes_read == size && memcmp(data, buf, size) == 0) { 124 | // It's locked (in use), but it's the same as what we have in 125 | // our binary, so we're OK. 126 | free(buf); 127 | return; 128 | } 129 | } else { 130 | Fatal("couldn't open %s for read", filename); 131 | } 132 | Fatal("couldn't open %s, and it's out of date", filename); 133 | } 134 | 135 | fwrite(data, 1, size, fp); 136 | fclose(fp); 137 | SetMtimeOfFile(filename, GetOwnMtime()); 138 | } else { 139 | Fatal("couldn't get %s as %d", filename, resource_id); 140 | } 141 | } 142 | 143 | void RunProcess(char* command, const char* in_dir) { 144 | STARTUPINFO si = {0}; 145 | si.cb = sizeof(si); 146 | PROCESS_INFORMATION pi = {0}; 147 | if (!CreateProcess( 148 | NULL, command, NULL, NULL, TRUE, 0, NULL, in_dir, &si, &pi)) 149 | Fatal("CreateProcess '%s' failed: %d", command, GetLastError()); 150 | WaitForSingleObject(pi.hProcess, INFINITE); 151 | CloseHandle(pi.hProcess); 152 | CloseHandle(pi.hThread); 153 | } 154 | 155 | } // namespace 156 | 157 | int main() { 158 | DWORD parent_pid = GetParentPid(); 159 | 160 | // TODO: I think this doesn't work at all because this program is 161 | // compiled as x86 or x64, and PROCESSOR_ARCHITECTURE is modified from 162 | // the parent. We need to retrieve ParentPid's arch. 163 | char* processor_architecture = getenv("PROCESSOR_ARCHITECTURE"); 164 | if (!processor_architecture) 165 | Fatal("couldn't determine architecture"); 166 | const char* arch = "x64"; 167 | if (_stricmp(processor_architecture, "x86") == 0) 168 | arch = "x86"; 169 | char buf[1024]; 170 | 171 | // If we have embedded resources, extract them to a temporary folder and run 172 | // from there, otherwise, try to launch in a reasonable way from the same 173 | // directory as we're in. 174 | if (GetFileResource(CMDEX_X64_EXE, NULL, NULL)) { 175 | char temp_dir[1024]; 176 | ChdirToTemp(temp_dir, sizeof(temp_dir)); 177 | if (strcmp(arch, "x64") == 0) { 178 | //ULONGLONG start_time = GetTickCount64(); 179 | ExtractFileResource(CMDEX_X64_EXE, "cmdEx_x64.exe"); 180 | ExtractFileResource(CMDEX_DLL_X64_DLL, "cmdEx_dll_x64.dll"); 181 | ExtractFileResource(GIT2_X64_DLL, "git2.dll"); 182 | sprintf(buf, "%s\\cmdEx_x64.exe %d", temp_dir, parent_pid); 183 | RunProcess(buf, temp_dir); 184 | } else { 185 | Fatal("todo; extract x86"); 186 | } 187 | } else { 188 | char module_location[1024]; 189 | if (GetModuleFileName(NULL, module_location, sizeof(module_location)) == 0) 190 | Fatal("couldn't GetModuleFileName"); 191 | char* slash = strrchr(module_location, '\\'); 192 | if (!slash) 193 | Fatal("exe name seems malformed"); 194 | *slash = 0; 195 | #ifndef NDEBUG 196 | fprintf(stderr, "loc: %s\n", module_location); 197 | #endif 198 | 199 | sprintf(buf, "%s\\cmdEx_%s.exe %d", module_location, arch, parent_pid); 200 | return system(buf); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/cmdEx_exe/resource.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #define CMDEX_X64_EXE 10000 6 | #define CMDEX_DLL_X64_DLL 10001 7 | #define GIT2_X64_DLL 10002 8 | #define DBGHELP_X64_DLL 10003 9 | #define SYMSRV_X64_DLL 10004 10 | -------------------------------------------------------------------------------- /src/cmdEx_exe/resource.rc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "cmdEx_exe/resource.h" 6 | 7 | CMDEX_X64_EXE RCDATA "out\\cmdEx_x64.exe" 8 | CMDEX_DLL_X64_DLL RCDATA "out\\cmdEx_dll_x64.dll" 9 | GIT2_X64_DLL RCDATA "third_party\\libgit2\\Release\\git2.dll" 10 | -------------------------------------------------------------------------------- /src/cmdEx_x64_exe/inject.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "common/util.h" 11 | 12 | #if defined _M_X64 13 | const char g_main_dll_name[] = "cmdEx_dll_x64.dll"; 14 | #else 15 | #error 16 | #endif 17 | 18 | namespace { 19 | 20 | // Either SuspendThread or ResumeThread's all thread in the process identified 21 | // by |pid|. 22 | void SetThreadsRunning(DWORD pid, bool run) { 23 | HANDLE th32 = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, pid); 24 | THREADENTRY32 te; 25 | BOOL ok = Thread32First(th32, &te); 26 | while (ok) { 27 | HANDLE thread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID); 28 | // These are refcounted, so we won't wake ones that were already suspended. 29 | if (run) 30 | ResumeThread(thread); 31 | else 32 | SuspendThread(thread); 33 | CloseHandle(thread); 34 | Thread32Next(th32, &te); 35 | } 36 | CloseHandle(th32); 37 | } 38 | 39 | // Injects our helper DLL into the process identified by |target_pid|. 40 | void Inject(DWORD target_pid, const char* dll_name) { 41 | char dll_path[1024]; 42 | GetModuleFileName(NULL, dll_path, sizeof(dll_path)); 43 | char* slash = strrchr(dll_path, '\\'); 44 | if (slash) 45 | strcpy(slash + 1, dll_name); 46 | 47 | // Get a handle to the target process. 48 | HANDLE target_process = 49 | OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | 50 | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, 51 | FALSE, 52 | target_pid); 53 | if (!target_process) 54 | Fatal("failed to target process"); 55 | 56 | // Make sure it matches our architecture. 57 | BOOL parent_is_wow64, current_is_wow64; 58 | IsWow64Process(target_process, &parent_is_wow64); 59 | IsWow64Process(GetCurrentProcess(), ¤t_is_wow64); 60 | if (parent_is_wow64 != current_is_wow64) 61 | Fatal("x86/x64 mismatch, use loader that matches parent architecture"); 62 | 63 | // Allocate a buffer in the target to contain the DLL path, and store the 64 | // DLL we want to load into it. 65 | LPVOID buffer = VirtualAllocEx( 66 | target_process, NULL, sizeof(dll_path), MEM_COMMIT, PAGE_READWRITE); 67 | if (!WriteProcessMemory( 68 | target_process, buffer, dll_path, sizeof(dll_path), NULL)) 69 | Fatal("WriteProcessMemory failed"); 70 | 71 | SetThreadsRunning(target_pid, false); 72 | 73 | // Get LoadLibrary as the entry point for the remote thread (it takes one 74 | // argument -- the DLL path buffer we created above). 75 | void* thread_proc = 76 | GetProcAddress(LoadLibraryA("kernel32.dll"), "LoadLibraryA"); 77 | if (!thread_proc) 78 | Fatal("couldn't get LoadLibraryA"); 79 | DWORD thread_id; 80 | HANDLE remote_thread = 81 | CreateRemoteThread(target_process, 82 | NULL, 83 | 0, 84 | reinterpret_cast(thread_proc), 85 | buffer, 86 | 0, 87 | &thread_id); 88 | if (!remote_thread) 89 | Fatal("CreateRemoteThread failed"); 90 | 91 | // Wait until the LoadLibrary has completed (which means injection is done). 92 | WaitForSingleObject(remote_thread, INFINITE); 93 | 94 | SetThreadsRunning(target_pid, true); 95 | 96 | CloseHandle(remote_thread); 97 | VirtualFreeEx(target_process, buffer, 0, MEM_RELEASE); 98 | CloseHandle(target_process); 99 | } 100 | 101 | } // namespace 102 | 103 | int main(int argc, char** argv) { 104 | if (argc != 2) 105 | Fatal("no target injection pid specified"); 106 | char* cwd = _getcwd(NULL, 0); 107 | Log("cwd: %s\n", cwd); 108 | free(cwd); 109 | DWORD target_pid = atoi(argv[1]); 110 | if (target_pid == 0) 111 | Fatal("argv[1] didn't look like pid"); 112 | Log("injecting into %d", target_pid); 113 | Inject(target_pid, g_main_dll_name); 114 | return 0; 115 | } 116 | -------------------------------------------------------------------------------- /src/common/util.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "common/util.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | void Fatal(const char* msg, ...) { 12 | va_list ap; 13 | fprintf(stderr, "cmdEx: FATAL: "); 14 | va_start(ap, msg); 15 | vfprintf(stderr, msg, ap); 16 | va_end(ap); 17 | fprintf(stderr, "\n"); 18 | #ifdef _WIN32 19 | __debugbreak(); 20 | #endif 21 | exit(1); 22 | } 23 | 24 | void Error(const char* msg, ...) { 25 | va_list ap; 26 | fprintf(stderr, "cmdEx: ERROR: "); 27 | va_start(ap, msg); 28 | vfprintf(stderr, msg, ap); 29 | va_end(ap); 30 | fprintf(stderr, "\n"); 31 | } 32 | 33 | void Log(const char* msg, ...) { 34 | #ifndef NDEBUG 35 | va_list ap; 36 | fprintf(stderr, "cmdEx: "); 37 | va_start(ap, msg); 38 | vfprintf(stderr, msg, ap); 39 | va_end(ap); 40 | fprintf(stderr, "\n"); 41 | #else 42 | (void)msg; 43 | #endif 44 | } 45 | -------------------------------------------------------------------------------- /src/common/util.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef CMDEX_COMMON_UTIL_H_ 6 | #define CMDEX_COMMON_UTIL_H_ 7 | 8 | void Fatal(const char* msg, ...); 9 | void Error(const char* msg, ...); 10 | void Log(const char* msg, ...); 11 | 12 | #define CHECK(c) \ 13 | if (!(c)) \ 14 | Fatal("CHECK '" #c "' failed at %s, line %d", __FILE__, __LINE__); 15 | 16 | #define PCHECK(c) \ 17 | if (!(c)) \ 18 | Fatal("PCHECK '" #c "' failed at %s, line %d, GetLastError=%d", \ 19 | __FILE__, \ 20 | __LINE__, \ 21 | GetLastError()); 22 | 23 | #endif // CMDEX_COMMON_UTIL_H_ 24 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 The Chromium Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can be 3 | # found in the LICENSE file. 4 | 5 | import ctypes 6 | import os 7 | import shutil 8 | import subprocess 9 | import sys 10 | import tempfile 11 | import time 12 | 13 | 14 | BASE_DIR = os.path.abspath(os.path.dirname(__file__)) 15 | CMD_EX_PATH = os.path.join(BASE_DIR, 'out\\cmdEx.exe') 16 | 17 | 18 | def Run(args, quiet=False): 19 | stdout = None 20 | if quiet: 21 | stdout = open(os.devnull, 'w') 22 | subprocess.check_call(args, shell=True, stdout=stdout) 23 | 24 | 25 | def EnsureDirExists(dir_name): 26 | if not os.path.exists(dir_name): 27 | os.makedirs(dir_name) 28 | 29 | 30 | class Test(object): 31 | """Creates a temporary directory, initializes a git repo there, and starts 32 | cmdEx in that directory.""" 33 | 34 | tests_started_ = 0 35 | tests_passed_ = 0 36 | tests_failed_ = 0 37 | cmd_binary_ = None 38 | 39 | def __init__(self, name, repo): 40 | self.name = name 41 | self.orig_dir = os.getcwd() 42 | self.temp_dir = tempfile.mkdtemp() 43 | shutil.copytree(os.path.join(BASE_DIR, 'test_repos', repo), 44 | os.path.join(self.temp_dir, repo)) 45 | os.rename(os.path.join(self.temp_dir, repo, '_git'), 46 | os.path.join(self.temp_dir, repo, '.git')) 47 | # The empty repo won't have a refs/heads dir, because, ironically git 48 | # can't store empty dirs but relies on the existence of .git/refs/heads to 49 | # determine if it's in a no-initial-commit repo. So, make that dir if it 50 | # doesn't exist. 51 | EnsureDirExists(os.path.join(self.temp_dir, repo, '.git', 'refs', 'heads')) 52 | EnsureDirExists(os.path.join(self.temp_dir, repo, '.git', 'objects')) 53 | os.chdir(os.path.join(self.temp_dir, repo)) 54 | 55 | def Interact(self, commands, expect, callback_before_communicate): 56 | # depot_tools does some wacky wrapping, so we need to spawn 'git' in a 57 | # separate sub-shell. 58 | commands = ['%COMSPEC% /c ' + c if c.startswith('git ') else c 59 | for c in commands] 60 | with tempfile.NamedTemporaryFile(suffix='.bat', delete=False) as bat: 61 | bat.write(CMD_EX_PATH + '\n') 62 | for command in commands: 63 | bat.write(command + '\n') 64 | bat.write('ver >nul\n') 65 | env = os.environ.copy() 66 | env['PROMPT'] = '###' 67 | popen = subprocess.Popen([Test.cmd_binary_, '/c', bat.name], 68 | stdout=subprocess.PIPE, 69 | stderr=subprocess.STDOUT, 70 | env=env) 71 | if callback_before_communicate: 72 | callback_before_communicate(popen) 73 | out, _ = popen.communicate() 74 | os.unlink(bat.name) 75 | outlines = out.splitlines() 76 | # Ignore the first two respones, they're cmdEx being installed and running 77 | # the first command (which will contain a $P which we don't want). 78 | outlines = [line for i, line in enumerate(outlines) if i % 2 == 1] 79 | assert outlines[0].startswith('###') 80 | outlines = outlines[2:] 81 | def fail(msg): 82 | print 'FAILED', self.name, '->', msg 83 | print 'commands:', commands 84 | print 'expect:', expect 85 | print 'outlines:', outlines 86 | print '' 87 | Test.tests_failed_ += 1 88 | if len(outlines) != len(expect): 89 | fail('length of output and expected don\'t match') 90 | return 91 | for line, (i, exp) in zip(outlines, enumerate(expect)): 92 | if exp is None: 93 | continue 94 | if '#' in line: 95 | line = line[:line.index('#')] 96 | if '#' in exp: 97 | exp = exp[:exp.index('#')] 98 | if line != exp: 99 | fail("got '%s' vs expected '%s'" % (line, exp)) 100 | return 101 | print 'ok - ' + self.name 102 | Test.tests_passed_ += 1 103 | 104 | def __enter__(self): 105 | Test.tests_started_ += 1 106 | return self 107 | 108 | def __exit__(self, type, value, traceback): 109 | os.chdir(self.orig_dir) 110 | Run(['rmdir', '/s', '/q', self.temp_dir]) 111 | 112 | @staticmethod 113 | def Report(): 114 | assert Test.tests_started_ == Test.tests_passed_ + Test.tests_failed_ 115 | print '%d/%d passed.' % (Test.tests_passed_, Test.tests_started_) 116 | 117 | @staticmethod 118 | def SetCmdBinary(binary): 119 | Test.cmd_binary_ = binary 120 | 121 | 122 | def Interact(name, repo, commands, expect, callback_before_communicate=None): 123 | with Test(name, repo) as t: 124 | t.Interact(commands, expect, callback_before_communicate) 125 | 126 | 127 | def PromptTests(): 128 | Interact( 129 | 'before initial commit', 130 | 'empty', 131 | commands=[ 132 | 'prompt $M#', 133 | ], 134 | expect=[ 135 | '[(no head)] #', 136 | ]) 137 | 138 | Interact( 139 | 'single commit on master', 140 | 'single_commit', 141 | commands=[ 142 | 'prompt $M#', 143 | ], 144 | expect=[ 145 | '[master] #', 146 | ]) 147 | 148 | Interact( 149 | 'prompt completely empty if not in working dir', 150 | 'empty', 151 | commands=[ 152 | 'prompt $M#', 153 | 'cd ..', 154 | ], 155 | expect=[ 156 | None, 157 | ' ', 158 | ]) 159 | 160 | Interact( 161 | 'detached head', 162 | 'four_linear_commits', 163 | commands=[ 164 | 'prompt $M#', 165 | 'git checkout HEAD~2 >nul 2>nul', 166 | ], 167 | expect=[ 168 | '[master] #', 169 | '[7b4f1ae...] #', 170 | ]) 171 | 172 | Interact( 173 | 'rebase in progress', 174 | 'conflict_rebase', 175 | commands=[ 176 | 'prompt $M#', 177 | 'git rebase master >nul 2>nul', 178 | ], 179 | expect=[ 180 | '[child] #', 181 | '[child 1/1|REBASE] #', 182 | ]) 183 | 184 | 185 | def TerminateBatchJobTests(): 186 | # TODO: Test this somehow. I think it doesn't work because cmd's in 187 | # non-interactive mode (?) so it ignores the Ctrl-C. 188 | return 189 | # Run a batch file containing "pause\npause\n", wait a bit, send a Ctrl-C, 190 | # and make sure that it exits cleanly, and that the output doesn't contain 191 | # the prompt we don't want. 192 | def send_ctrl_c(proc): 193 | time.sleep(1) # Just to get to the first pause, probably not necessary. 194 | ctypes.windll.kernel32.GenerateConsoleCtrlEvent(0, proc.pid) # 0 => Ctrl-C 195 | Interact( 196 | 'no terminate batch job prompt', 197 | 'empty', # Don't actually need any. 198 | commands=[ 199 | 'prompt #', 200 | 'pause', 201 | 'pause', 202 | ], 203 | expect=[ 204 | '', 205 | '', 206 | ], 207 | callback_before_communicate=send_ctrl_c) 208 | 209 | 210 | def DoTests(cmd_binary): 211 | Test.SetCmdBinary(cmd_binary) 212 | PromptTests() 213 | TerminateBatchJobTests() 214 | 215 | 216 | def main(): 217 | print 'x86:' 218 | DoTests('C:\\Windows\\SysWOW64\\cmd.exe') 219 | isx64python = sys.maxsize > 2**32 220 | if isx64python: 221 | print 'x64:' 222 | DoTests('C:\\Windows\\System32\\cmd.exe') 223 | else: 224 | print 'not running on x64 python, can\'t test x64 cmd.' 225 | Test.Report() 226 | 227 | 228 | if __name__ == '__main__': 229 | main() 230 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/COMMIT_EDITMSG: -------------------------------------------------------------------------------- 1 | change in master 2 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/FETCH_HEAD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/conflict_rebase/_git/FETCH_HEAD -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/child 2 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/ORIG_HEAD: -------------------------------------------------------------------------------- 1 | aa1bab5a4fd1a72b125092ef1f677c4fe36bb485 2 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/config: -------------------------------------------------------------------------------- 1 | [core] 2 | repositoryformatversion = 0 3 | filemode = false 4 | bare = false 5 | logallrefupdates = true 6 | symlinks = false 7 | ignorecase = true 8 | hideDotFiles = dotGitOnly 9 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/description: -------------------------------------------------------------------------------- 1 | Unnamed repository; edit this file 'description' to name the repository. 2 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/hooks/applypatch-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message taken by 4 | # applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. The hook is 8 | # allowed to edit the commit message file. 9 | # 10 | # To enable this hook, rename this file to "applypatch-msg". 11 | 12 | . git-sh-setup 13 | test -x "$GIT_DIR/hooks/commit-msg" && 14 | exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} 15 | : 16 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/hooks/commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message. 4 | # Called by "git commit" with one argument, the name of the file 5 | # that has the commit message. The hook should exit with non-zero 6 | # status after issuing an appropriate message if it wants to stop the 7 | # commit. The hook is allowed to edit the commit message file. 8 | # 9 | # To enable this hook, rename this file to "commit-msg". 10 | 11 | # Uncomment the below to add a Signed-off-by line to the message. 12 | # Doing this in a hook is a bad idea in general, but the prepare-commit-msg 13 | # hook is more suited to it. 14 | # 15 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 16 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 17 | 18 | # This example catches duplicate Signed-off-by lines. 19 | 20 | test "" = "$(grep '^Signed-off-by: ' "$1" | 21 | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { 22 | echo >&2 Duplicate Signed-off-by lines. 23 | exit 1 24 | } 25 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/hooks/post-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script that is called after a successful 4 | # commit is made. 5 | # 6 | # To enable this hook, rename this file to "post-commit". 7 | 8 | : Nothing 9 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/hooks/post-receive.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script for the "post-receive" event. 4 | # 5 | # The "post-receive" script is run after receive-pack has accepted a pack 6 | # and the repository has been updated. It is passed arguments in through 7 | # stdin in the form 8 | # 9 | # For example: 10 | # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master 11 | # 12 | # see contrib/hooks/ for a sample, or uncomment the next line and 13 | # rename the file to "post-receive". 14 | 15 | #. /usr/share/doc/git-core/contrib/hooks/post-receive-email 16 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/hooks/post-update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare a packed repository for use over 4 | # dumb transports. 5 | # 6 | # To enable this hook, rename this file to "post-update". 7 | 8 | exec git update-server-info 9 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/hooks/pre-applypatch.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed 4 | # by applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. 8 | # 9 | # To enable this hook, rename this file to "pre-applypatch". 10 | 11 | . git-sh-setup 12 | test -x "$GIT_DIR/hooks/pre-commit" && 13 | exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} 14 | : 15 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/hooks/pre-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | if git rev-parse --verify HEAD >/dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 16 | fi 17 | 18 | # If you want to allow non-ascii filenames set this variable to true. 19 | allownonascii=$(git config hooks.allownonascii) 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | # Cross platform projects tend to avoid non-ascii filenames; prevent 25 | # them from being added to the repository. We exploit the fact that the 26 | # printable range starts at the space character and ends with tilde. 27 | if [ "$allownonascii" != "true" ] && 28 | # Note that the use of brackets around a tr range is ok here, (it's 29 | # even required, for portability to Solaris 10's /usr/bin/tr), since 30 | # the square bracket bytes happen to fall in the designated range. 31 | test $(git diff --cached --name-only --diff-filter=A -z $against | 32 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 33 | then 34 | echo "Error: Attempt to add a non-ascii file name." 35 | echo 36 | echo "This can cause problems if you want to work" 37 | echo "with people on other platforms." 38 | echo 39 | echo "To be portable it is advisable to rename the file ..." 40 | echo 41 | echo "If you know what you are doing you can disable this" 42 | echo "check using:" 43 | echo 44 | echo " git config hooks.allownonascii true" 45 | echo 46 | exit 1 47 | fi 48 | 49 | # If there are whitespace errors, print the offending file names and fail. 50 | exec git diff-index --check --cached $against -- 51 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/hooks/pre-rebase.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2006, 2008 Junio C Hamano 4 | # 5 | # The "pre-rebase" hook is run just before "git rebase" starts doing 6 | # its job, and can prevent the command from running by exiting with 7 | # non-zero status. 8 | # 9 | # The hook is called with the following parameters: 10 | # 11 | # $1 -- the upstream the series was forked from. 12 | # $2 -- the branch being rebased (or empty when rebasing the current branch). 13 | # 14 | # This sample shows how to prevent topic branches that are already 15 | # merged to 'next' branch from getting rebased, because allowing it 16 | # would result in rebasing already published history. 17 | 18 | publish=next 19 | basebranch="$1" 20 | if test "$#" = 2 21 | then 22 | topic="refs/heads/$2" 23 | else 24 | topic=`git symbolic-ref HEAD` || 25 | exit 0 ;# we do not interrupt rebasing detached HEAD 26 | fi 27 | 28 | case "$topic" in 29 | refs/heads/??/*) 30 | ;; 31 | *) 32 | exit 0 ;# we do not interrupt others. 33 | ;; 34 | esac 35 | 36 | # Now we are dealing with a topic branch being rebased 37 | # on top of master. Is it OK to rebase it? 38 | 39 | # Does the topic really exist? 40 | git show-ref -q "$topic" || { 41 | echo >&2 "No such branch $topic" 42 | exit 1 43 | } 44 | 45 | # Is topic fully merged to master? 46 | not_in_master=`git rev-list --pretty=oneline ^master "$topic"` 47 | if test -z "$not_in_master" 48 | then 49 | echo >&2 "$topic is fully merged to master; better remove it." 50 | exit 1 ;# we could allow it, but there is no point. 51 | fi 52 | 53 | # Is topic ever merged to next? If so you should not be rebasing it. 54 | only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` 55 | only_next_2=`git rev-list ^master ${publish} | sort` 56 | if test "$only_next_1" = "$only_next_2" 57 | then 58 | not_in_topic=`git rev-list "^$topic" master` 59 | if test -z "$not_in_topic" 60 | then 61 | echo >&2 "$topic is already up-to-date with master" 62 | exit 1 ;# we could allow it, but there is no point. 63 | else 64 | exit 0 65 | fi 66 | else 67 | not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` 68 | /usr/bin/perl -e ' 69 | my $topic = $ARGV[0]; 70 | my $msg = "* $topic has commits already merged to public branch:\n"; 71 | my (%not_in_next) = map { 72 | /^([0-9a-f]+) /; 73 | ($1 => 1); 74 | } split(/\n/, $ARGV[1]); 75 | for my $elem (map { 76 | /^([0-9a-f]+) (.*)$/; 77 | [$1 => $2]; 78 | } split(/\n/, $ARGV[2])) { 79 | if (!exists $not_in_next{$elem->[0]}) { 80 | if ($msg) { 81 | print STDERR $msg; 82 | undef $msg; 83 | } 84 | print STDERR " $elem->[1]\n"; 85 | } 86 | } 87 | ' "$topic" "$not_in_next" "$not_in_master" 88 | exit 1 89 | fi 90 | 91 | exit 0 92 | 93 | ################################################################ 94 | 95 | This sample hook safeguards topic branches that have been 96 | published from being rewound. 97 | 98 | The workflow assumed here is: 99 | 100 | * Once a topic branch forks from "master", "master" is never 101 | merged into it again (either directly or indirectly). 102 | 103 | * Once a topic branch is fully cooked and merged into "master", 104 | it is deleted. If you need to build on top of it to correct 105 | earlier mistakes, a new topic branch is created by forking at 106 | the tip of the "master". This is not strictly necessary, but 107 | it makes it easier to keep your history simple. 108 | 109 | * Whenever you need to test or publish your changes to topic 110 | branches, merge them into "next" branch. 111 | 112 | The script, being an example, hardcodes the publish branch name 113 | to be "next", but it is trivial to make it configurable via 114 | $GIT_DIR/config mechanism. 115 | 116 | With this workflow, you would want to know: 117 | 118 | (1) ... if a topic branch has ever been merged to "next". Young 119 | topic branches can have stupid mistakes you would rather 120 | clean up before publishing, and things that have not been 121 | merged into other branches can be easily rebased without 122 | affecting other people. But once it is published, you would 123 | not want to rewind it. 124 | 125 | (2) ... if a topic branch has been fully merged to "master". 126 | Then you can delete it. More importantly, you should not 127 | build on top of it -- other people may already want to 128 | change things related to the topic as patches against your 129 | "master", so if you need further changes, it is better to 130 | fork the topic (perhaps with the same name) afresh from the 131 | tip of "master". 132 | 133 | Let's look at this example: 134 | 135 | o---o---o---o---o---o---o---o---o---o "next" 136 | / / / / 137 | / a---a---b A / / 138 | / / / / 139 | / / c---c---c---c B / 140 | / / / \ / 141 | / / / b---b C \ / 142 | / / / / \ / 143 | ---o---o---o---o---o---o---o---o---o---o---o "master" 144 | 145 | 146 | A, B and C are topic branches. 147 | 148 | * A has one fix since it was merged up to "next". 149 | 150 | * B has finished. It has been fully merged up to "master" and "next", 151 | and is ready to be deleted. 152 | 153 | * C has not merged to "next" at all. 154 | 155 | We would want to allow C to be rebased, refuse A, and encourage 156 | B to be deleted. 157 | 158 | To compute (1): 159 | 160 | git rev-list ^master ^topic next 161 | git rev-list ^master next 162 | 163 | if these match, topic has not merged in next at all. 164 | 165 | To compute (2): 166 | 167 | git rev-list master..topic 168 | 169 | if this is empty, it is fully merged to "master". 170 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/hooks/prepare-commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare the commit log message. 4 | # Called by "git commit" with the name of the file that has the 5 | # commit message, followed by the description of the commit 6 | # message's source. The hook's purpose is to edit the commit 7 | # message file. If the hook fails with a non-zero status, 8 | # the commit is aborted. 9 | # 10 | # To enable this hook, rename this file to "prepare-commit-msg". 11 | 12 | # This hook includes three examples. The first comments out the 13 | # "Conflicts:" part of a merge commit. 14 | # 15 | # The second includes the output of "git diff --name-status -r" 16 | # into the message, just before the "git status" output. It is 17 | # commented because it doesn't cope with --amend or with squashed 18 | # commits. 19 | # 20 | # The third example adds a Signed-off-by line to the message, that can 21 | # still be edited. This is rarely a good idea. 22 | 23 | case "$2,$3" in 24 | merge,) 25 | /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; 26 | 27 | # ,|template,) 28 | # /usr/bin/perl -i.bak -pe ' 29 | # print "\n" . `git diff --cached --name-status -r` 30 | # if /^#/ && $first++ == 0' "$1" ;; 31 | 32 | *) ;; 33 | esac 34 | 35 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 36 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 37 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/hooks/update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to blocks unannotated tags from entering. 4 | # Called by "git receive-pack" with arguments: refname sha1-old sha1-new 5 | # 6 | # To enable this hook, rename this file to "update". 7 | # 8 | # Config 9 | # ------ 10 | # hooks.allowunannotated 11 | # This boolean sets whether unannotated tags will be allowed into the 12 | # repository. By default they won't be. 13 | # hooks.allowdeletetag 14 | # This boolean sets whether deleting tags will be allowed in the 15 | # repository. By default they won't be. 16 | # hooks.allowmodifytag 17 | # This boolean sets whether a tag may be modified after creation. By default 18 | # it won't be. 19 | # hooks.allowdeletebranch 20 | # This boolean sets whether deleting branches will be allowed in the 21 | # repository. By default they won't be. 22 | # hooks.denycreatebranch 23 | # This boolean sets whether remotely creating branches will be denied 24 | # in the repository. By default this is allowed. 25 | # 26 | 27 | # --- Command line 28 | refname="$1" 29 | oldrev="$2" 30 | newrev="$3" 31 | 32 | # --- Safety check 33 | if [ -z "$GIT_DIR" ]; then 34 | echo "Don't run this script from the command line." >&2 35 | echo " (if you want, you could supply GIT_DIR then run" >&2 36 | echo " $0 )" >&2 37 | exit 1 38 | fi 39 | 40 | if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then 41 | echo "Usage: $0 " >&2 42 | exit 1 43 | fi 44 | 45 | # --- Config 46 | allowunannotated=$(git config --bool hooks.allowunannotated) 47 | allowdeletebranch=$(git config --bool hooks.allowdeletebranch) 48 | denycreatebranch=$(git config --bool hooks.denycreatebranch) 49 | allowdeletetag=$(git config --bool hooks.allowdeletetag) 50 | allowmodifytag=$(git config --bool hooks.allowmodifytag) 51 | 52 | # check for no description 53 | projectdesc=$(sed -e '1q' "$GIT_DIR/description") 54 | case "$projectdesc" in 55 | "Unnamed repository"* | "") 56 | echo "*** Project description file hasn't been set" >&2 57 | exit 1 58 | ;; 59 | esac 60 | 61 | # --- Check types 62 | # if $newrev is 0000...0000, it's a commit to delete a ref. 63 | zero="0000000000000000000000000000000000000000" 64 | if [ "$newrev" = "$zero" ]; then 65 | newrev_type=delete 66 | else 67 | newrev_type=$(git cat-file -t $newrev) 68 | fi 69 | 70 | case "$refname","$newrev_type" in 71 | refs/tags/*,commit) 72 | # un-annotated tag 73 | short_refname=${refname##refs/tags/} 74 | if [ "$allowunannotated" != "true" ]; then 75 | echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 76 | echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 77 | exit 1 78 | fi 79 | ;; 80 | refs/tags/*,delete) 81 | # delete tag 82 | if [ "$allowdeletetag" != "true" ]; then 83 | echo "*** Deleting a tag is not allowed in this repository" >&2 84 | exit 1 85 | fi 86 | ;; 87 | refs/tags/*,tag) 88 | # annotated tag 89 | if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 90 | then 91 | echo "*** Tag '$refname' already exists." >&2 92 | echo "*** Modifying a tag is not allowed in this repository." >&2 93 | exit 1 94 | fi 95 | ;; 96 | refs/heads/*,commit) 97 | # branch 98 | if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then 99 | echo "*** Creating a branch is not allowed in this repository" >&2 100 | exit 1 101 | fi 102 | ;; 103 | refs/heads/*,delete) 104 | # delete branch 105 | if [ "$allowdeletebranch" != "true" ]; then 106 | echo "*** Deleting a branch is not allowed in this repository" >&2 107 | exit 1 108 | fi 109 | ;; 110 | refs/remotes/*,commit) 111 | # tracking branch 112 | ;; 113 | refs/remotes/*,delete) 114 | # delete tracking branch 115 | if [ "$allowdeletebranch" != "true" ]; then 116 | echo "*** Deleting a tracking branch is not allowed in this repository" >&2 117 | exit 1 118 | fi 119 | ;; 120 | *) 121 | # Anything else (is there anything else?) 122 | echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 123 | exit 1 124 | ;; 125 | esac 126 | 127 | # --- Finished 128 | exit 0 129 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/conflict_rebase/_git/index -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/info/exclude: -------------------------------------------------------------------------------- 1 | # git ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | # *~ 7 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/logs/HEAD: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 4773b88416ef5e51ada321eeab0117527e580004 Scott Graham 1374375235 -0700 commit (initial): initial commit 2 | 4773b88416ef5e51ada321eeab0117527e580004 4773b88416ef5e51ada321eeab0117527e580004 Scott Graham 1374375275 -0700 checkout: moving from master to child 3 | 4773b88416ef5e51ada321eeab0117527e580004 aa1bab5a4fd1a72b125092ef1f677c4fe36bb485 Scott Graham 1374375301 -0700 commit: change in child 4 | aa1bab5a4fd1a72b125092ef1f677c4fe36bb485 4773b88416ef5e51ada321eeab0117527e580004 Scott Graham 1374375305 -0700 checkout: moving from child to master 5 | 4773b88416ef5e51ada321eeab0117527e580004 8375d6dfc8740e050f4f1e27d5046338ec90c07f Scott Graham 1374375318 -0700 commit: change in master 6 | 8375d6dfc8740e050f4f1e27d5046338ec90c07f aa1bab5a4fd1a72b125092ef1f677c4fe36bb485 Scott Graham 1374375319 -0700 checkout: moving from master to child 7 | aa1bab5a4fd1a72b125092ef1f677c4fe36bb485 8375d6dfc8740e050f4f1e27d5046338ec90c07f Scott Graham 1374375371 -0700 checkout: moving from child to 8375d6dfc8740e050f4f1e27d5046338ec90c07f^0 8 | 8375d6dfc8740e050f4f1e27d5046338ec90c07f aa1bab5a4fd1a72b125092ef1f677c4fe36bb485 Scott Graham 1374375384 -0700 rebase: aborting 9 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/logs/refs/heads/child: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 4773b88416ef5e51ada321eeab0117527e580004 Scott Graham 1374375275 -0700 branch: Created from master 2 | 4773b88416ef5e51ada321eeab0117527e580004 aa1bab5a4fd1a72b125092ef1f677c4fe36bb485 Scott Graham 1374375301 -0700 commit: change in child 3 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/logs/refs/heads/master: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 4773b88416ef5e51ada321eeab0117527e580004 Scott Graham 1374375235 -0700 commit (initial): initial commit 2 | 4773b88416ef5e51ada321eeab0117527e580004 8375d6dfc8740e050f4f1e27d5046338ec90c07f Scott Graham 1374375318 -0700 commit: change in master 3 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/objects/02/0b44b9d5ae90dac24535b6644745c96ea17a4b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/conflict_rebase/_git/objects/02/0b44b9d5ae90dac24535b6644745c96ea17a4b -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/objects/1a/5f6df420b0ea488694f90c5bffd99187509ece: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/conflict_rebase/_git/objects/1a/5f6df420b0ea488694f90c5bffd99187509ece -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/objects/2b/6498555ba1bf52308e77aa36d2f3634470a32b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/conflict_rebase/_git/objects/2b/6498555ba1bf52308e77aa36d2f3634470a32b -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/objects/47/73b88416ef5e51ada321eeab0117527e580004: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/conflict_rebase/_git/objects/47/73b88416ef5e51ada321eeab0117527e580004 -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/objects/62/7a7f1274c1c8e8cc302bfbd60cf952db53c1d5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/conflict_rebase/_git/objects/62/7a7f1274c1c8e8cc302bfbd60cf952db53c1d5 -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/objects/75/4fc3ff54decd45cc0b670a0be5a606cccd5537: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/conflict_rebase/_git/objects/75/4fc3ff54decd45cc0b670a0be5a606cccd5537 -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/objects/83/75d6dfc8740e050f4f1e27d5046338ec90c07f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/conflict_rebase/_git/objects/83/75d6dfc8740e050f4f1e27d5046338ec90c07f -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/objects/aa/1bab5a4fd1a72b125092ef1f677c4fe36bb485: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/conflict_rebase/_git/objects/aa/1bab5a4fd1a72b125092ef1f677c4fe36bb485 -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/objects/c5/07c95bff70294cf024569ed3de5bca6d678ab6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/conflict_rebase/_git/objects/c5/07c95bff70294cf024569ed3de5bca6d678ab6 -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/objects/e5/ecfe5a67c14206b895bac6311501c87647bb34: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/conflict_rebase/_git/objects/e5/ecfe5a67c14206b895bac6311501c87647bb34 -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/refs/heads/child: -------------------------------------------------------------------------------- 1 | aa1bab5a4fd1a72b125092ef1f677c4fe36bb485 2 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/_git/refs/heads/master: -------------------------------------------------------------------------------- 1 | 8375d6dfc8740e050f4f1e27d5046338ec90c07f 2 | -------------------------------------------------------------------------------- /test_repos/conflict_rebase/a_file: -------------------------------------------------------------------------------- 1 | child change 2 | 3 | -------------------------------------------------------------------------------- /test_repos/empty/_git/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /test_repos/empty/_git/config: -------------------------------------------------------------------------------- 1 | [core] 2 | repositoryformatversion = 0 3 | filemode = false 4 | bare = false 5 | logallrefupdates = true 6 | symlinks = false 7 | ignorecase = true 8 | hideDotFiles = dotGitOnly 9 | -------------------------------------------------------------------------------- /test_repos/empty/_git/description: -------------------------------------------------------------------------------- 1 | Unnamed repository; edit this file 'description' to name the repository. 2 | -------------------------------------------------------------------------------- /test_repos/empty/_git/hooks/applypatch-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message taken by 4 | # applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. The hook is 8 | # allowed to edit the commit message file. 9 | # 10 | # To enable this hook, rename this file to "applypatch-msg". 11 | 12 | . git-sh-setup 13 | test -x "$GIT_DIR/hooks/commit-msg" && 14 | exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} 15 | : 16 | -------------------------------------------------------------------------------- /test_repos/empty/_git/hooks/commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message. 4 | # Called by "git commit" with one argument, the name of the file 5 | # that has the commit message. The hook should exit with non-zero 6 | # status after issuing an appropriate message if it wants to stop the 7 | # commit. The hook is allowed to edit the commit message file. 8 | # 9 | # To enable this hook, rename this file to "commit-msg". 10 | 11 | # Uncomment the below to add a Signed-off-by line to the message. 12 | # Doing this in a hook is a bad idea in general, but the prepare-commit-msg 13 | # hook is more suited to it. 14 | # 15 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 16 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 17 | 18 | # This example catches duplicate Signed-off-by lines. 19 | 20 | test "" = "$(grep '^Signed-off-by: ' "$1" | 21 | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { 22 | echo >&2 Duplicate Signed-off-by lines. 23 | exit 1 24 | } 25 | -------------------------------------------------------------------------------- /test_repos/empty/_git/hooks/post-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script that is called after a successful 4 | # commit is made. 5 | # 6 | # To enable this hook, rename this file to "post-commit". 7 | 8 | : Nothing 9 | -------------------------------------------------------------------------------- /test_repos/empty/_git/hooks/post-receive.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script for the "post-receive" event. 4 | # 5 | # The "post-receive" script is run after receive-pack has accepted a pack 6 | # and the repository has been updated. It is passed arguments in through 7 | # stdin in the form 8 | # 9 | # For example: 10 | # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master 11 | # 12 | # see contrib/hooks/ for a sample, or uncomment the next line and 13 | # rename the file to "post-receive". 14 | 15 | #. /usr/share/doc/git-core/contrib/hooks/post-receive-email 16 | -------------------------------------------------------------------------------- /test_repos/empty/_git/hooks/post-update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare a packed repository for use over 4 | # dumb transports. 5 | # 6 | # To enable this hook, rename this file to "post-update". 7 | 8 | exec git update-server-info 9 | -------------------------------------------------------------------------------- /test_repos/empty/_git/hooks/pre-applypatch.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed 4 | # by applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. 8 | # 9 | # To enable this hook, rename this file to "pre-applypatch". 10 | 11 | . git-sh-setup 12 | test -x "$GIT_DIR/hooks/pre-commit" && 13 | exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} 14 | : 15 | -------------------------------------------------------------------------------- /test_repos/empty/_git/hooks/pre-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | if git rev-parse --verify HEAD >/dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 16 | fi 17 | 18 | # If you want to allow non-ascii filenames set this variable to true. 19 | allownonascii=$(git config hooks.allownonascii) 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | # Cross platform projects tend to avoid non-ascii filenames; prevent 25 | # them from being added to the repository. We exploit the fact that the 26 | # printable range starts at the space character and ends with tilde. 27 | if [ "$allownonascii" != "true" ] && 28 | # Note that the use of brackets around a tr range is ok here, (it's 29 | # even required, for portability to Solaris 10's /usr/bin/tr), since 30 | # the square bracket bytes happen to fall in the designated range. 31 | test $(git diff --cached --name-only --diff-filter=A -z $against | 32 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 33 | then 34 | echo "Error: Attempt to add a non-ascii file name." 35 | echo 36 | echo "This can cause problems if you want to work" 37 | echo "with people on other platforms." 38 | echo 39 | echo "To be portable it is advisable to rename the file ..." 40 | echo 41 | echo "If you know what you are doing you can disable this" 42 | echo "check using:" 43 | echo 44 | echo " git config hooks.allownonascii true" 45 | echo 46 | exit 1 47 | fi 48 | 49 | # If there are whitespace errors, print the offending file names and fail. 50 | exec git diff-index --check --cached $against -- 51 | -------------------------------------------------------------------------------- /test_repos/empty/_git/hooks/pre-push.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # An example hook script to verify what is about to be pushed. Called by "git 4 | # push" after it has checked the remote status, but before anything has been 5 | # pushed. If this script exits with a non-zero status nothing will be pushed. 6 | # 7 | # This hook is called with the following parameters: 8 | # 9 | # $1 -- Name of the remote to which the push is being done 10 | # $2 -- URL to which the push is being done 11 | # 12 | # If pushing without using a named remote those arguments will be equal. 13 | # 14 | # Information about the commits which are being pushed is supplied as lines to 15 | # the standard input in the form: 16 | # 17 | # 18 | # 19 | # This sample shows how to prevent push of commits where the log message starts 20 | # with "WIP" (work in progress). 21 | 22 | remote="$1" 23 | url="$2" 24 | 25 | z40=0000000000000000000000000000000000000000 26 | 27 | IFS=' ' 28 | while read local_ref local_sha remote_ref remote_sha 29 | do 30 | if [ "$local_sha" = $z40 ] 31 | then 32 | # Handle delete 33 | else 34 | if [ "$remote_sha" = $z40 ] 35 | then 36 | # New branch, examine all commits 37 | range="$local_sha" 38 | else 39 | # Update to existing branch, examine new commits 40 | range="$remote_sha..$local_sha" 41 | fi 42 | 43 | # Check for WIP commit 44 | commit=`git rev-list -n 1 --grep '^WIP' "$range"` 45 | if [ -n "$commit" ] 46 | then 47 | echo "Found WIP commit in $local_ref, not pushing" 48 | exit 1 49 | fi 50 | fi 51 | done 52 | 53 | exit 0 54 | -------------------------------------------------------------------------------- /test_repos/empty/_git/hooks/pre-rebase.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2006, 2008 Junio C Hamano 4 | # 5 | # The "pre-rebase" hook is run just before "git rebase" starts doing 6 | # its job, and can prevent the command from running by exiting with 7 | # non-zero status. 8 | # 9 | # The hook is called with the following parameters: 10 | # 11 | # $1 -- the upstream the series was forked from. 12 | # $2 -- the branch being rebased (or empty when rebasing the current branch). 13 | # 14 | # This sample shows how to prevent topic branches that are already 15 | # merged to 'next' branch from getting rebased, because allowing it 16 | # would result in rebasing already published history. 17 | 18 | publish=next 19 | basebranch="$1" 20 | if test "$#" = 2 21 | then 22 | topic="refs/heads/$2" 23 | else 24 | topic=`git symbolic-ref HEAD` || 25 | exit 0 ;# we do not interrupt rebasing detached HEAD 26 | fi 27 | 28 | case "$topic" in 29 | refs/heads/??/*) 30 | ;; 31 | *) 32 | exit 0 ;# we do not interrupt others. 33 | ;; 34 | esac 35 | 36 | # Now we are dealing with a topic branch being rebased 37 | # on top of master. Is it OK to rebase it? 38 | 39 | # Does the topic really exist? 40 | git show-ref -q "$topic" || { 41 | echo >&2 "No such branch $topic" 42 | exit 1 43 | } 44 | 45 | # Is topic fully merged to master? 46 | not_in_master=`git rev-list --pretty=oneline ^master "$topic"` 47 | if test -z "$not_in_master" 48 | then 49 | echo >&2 "$topic is fully merged to master; better remove it." 50 | exit 1 ;# we could allow it, but there is no point. 51 | fi 52 | 53 | # Is topic ever merged to next? If so you should not be rebasing it. 54 | only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` 55 | only_next_2=`git rev-list ^master ${publish} | sort` 56 | if test "$only_next_1" = "$only_next_2" 57 | then 58 | not_in_topic=`git rev-list "^$topic" master` 59 | if test -z "$not_in_topic" 60 | then 61 | echo >&2 "$topic is already up-to-date with master" 62 | exit 1 ;# we could allow it, but there is no point. 63 | else 64 | exit 0 65 | fi 66 | else 67 | not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` 68 | /usr/bin/perl -e ' 69 | my $topic = $ARGV[0]; 70 | my $msg = "* $topic has commits already merged to public branch:\n"; 71 | my (%not_in_next) = map { 72 | /^([0-9a-f]+) /; 73 | ($1 => 1); 74 | } split(/\n/, $ARGV[1]); 75 | for my $elem (map { 76 | /^([0-9a-f]+) (.*)$/; 77 | [$1 => $2]; 78 | } split(/\n/, $ARGV[2])) { 79 | if (!exists $not_in_next{$elem->[0]}) { 80 | if ($msg) { 81 | print STDERR $msg; 82 | undef $msg; 83 | } 84 | print STDERR " $elem->[1]\n"; 85 | } 86 | } 87 | ' "$topic" "$not_in_next" "$not_in_master" 88 | exit 1 89 | fi 90 | 91 | exit 0 92 | 93 | ################################################################ 94 | 95 | This sample hook safeguards topic branches that have been 96 | published from being rewound. 97 | 98 | The workflow assumed here is: 99 | 100 | * Once a topic branch forks from "master", "master" is never 101 | merged into it again (either directly or indirectly). 102 | 103 | * Once a topic branch is fully cooked and merged into "master", 104 | it is deleted. If you need to build on top of it to correct 105 | earlier mistakes, a new topic branch is created by forking at 106 | the tip of the "master". This is not strictly necessary, but 107 | it makes it easier to keep your history simple. 108 | 109 | * Whenever you need to test or publish your changes to topic 110 | branches, merge them into "next" branch. 111 | 112 | The script, being an example, hardcodes the publish branch name 113 | to be "next", but it is trivial to make it configurable via 114 | $GIT_DIR/config mechanism. 115 | 116 | With this workflow, you would want to know: 117 | 118 | (1) ... if a topic branch has ever been merged to "next". Young 119 | topic branches can have stupid mistakes you would rather 120 | clean up before publishing, and things that have not been 121 | merged into other branches can be easily rebased without 122 | affecting other people. But once it is published, you would 123 | not want to rewind it. 124 | 125 | (2) ... if a topic branch has been fully merged to "master". 126 | Then you can delete it. More importantly, you should not 127 | build on top of it -- other people may already want to 128 | change things related to the topic as patches against your 129 | "master", so if you need further changes, it is better to 130 | fork the topic (perhaps with the same name) afresh from the 131 | tip of "master". 132 | 133 | Let's look at this example: 134 | 135 | o---o---o---o---o---o---o---o---o---o "next" 136 | / / / / 137 | / a---a---b A / / 138 | / / / / 139 | / / c---c---c---c B / 140 | / / / \ / 141 | / / / b---b C \ / 142 | / / / / \ / 143 | ---o---o---o---o---o---o---o---o---o---o---o "master" 144 | 145 | 146 | A, B and C are topic branches. 147 | 148 | * A has one fix since it was merged up to "next". 149 | 150 | * B has finished. It has been fully merged up to "master" and "next", 151 | and is ready to be deleted. 152 | 153 | * C has not merged to "next" at all. 154 | 155 | We would want to allow C to be rebased, refuse A, and encourage 156 | B to be deleted. 157 | 158 | To compute (1): 159 | 160 | git rev-list ^master ^topic next 161 | git rev-list ^master next 162 | 163 | if these match, topic has not merged in next at all. 164 | 165 | To compute (2): 166 | 167 | git rev-list master..topic 168 | 169 | if this is empty, it is fully merged to "master". 170 | -------------------------------------------------------------------------------- /test_repos/empty/_git/hooks/prepare-commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare the commit log message. 4 | # Called by "git commit" with the name of the file that has the 5 | # commit message, followed by the description of the commit 6 | # message's source. The hook's purpose is to edit the commit 7 | # message file. If the hook fails with a non-zero status, 8 | # the commit is aborted. 9 | # 10 | # To enable this hook, rename this file to "prepare-commit-msg". 11 | 12 | # This hook includes three examples. The first comments out the 13 | # "Conflicts:" part of a merge commit. 14 | # 15 | # The second includes the output of "git diff --name-status -r" 16 | # into the message, just before the "git status" output. It is 17 | # commented because it doesn't cope with --amend or with squashed 18 | # commits. 19 | # 20 | # The third example adds a Signed-off-by line to the message, that can 21 | # still be edited. This is rarely a good idea. 22 | 23 | case "$2,$3" in 24 | merge,) 25 | /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; 26 | 27 | # ,|template,) 28 | # /usr/bin/perl -i.bak -pe ' 29 | # print "\n" . `git diff --cached --name-status -r` 30 | # if /^#/ && $first++ == 0' "$1" ;; 31 | 32 | *) ;; 33 | esac 34 | 35 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 36 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 37 | -------------------------------------------------------------------------------- /test_repos/empty/_git/hooks/update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to blocks unannotated tags from entering. 4 | # Called by "git receive-pack" with arguments: refname sha1-old sha1-new 5 | # 6 | # To enable this hook, rename this file to "update". 7 | # 8 | # Config 9 | # ------ 10 | # hooks.allowunannotated 11 | # This boolean sets whether unannotated tags will be allowed into the 12 | # repository. By default they won't be. 13 | # hooks.allowdeletetag 14 | # This boolean sets whether deleting tags will be allowed in the 15 | # repository. By default they won't be. 16 | # hooks.allowmodifytag 17 | # This boolean sets whether a tag may be modified after creation. By default 18 | # it won't be. 19 | # hooks.allowdeletebranch 20 | # This boolean sets whether deleting branches will be allowed in the 21 | # repository. By default they won't be. 22 | # hooks.denycreatebranch 23 | # This boolean sets whether remotely creating branches will be denied 24 | # in the repository. By default this is allowed. 25 | # 26 | 27 | # --- Command line 28 | refname="$1" 29 | oldrev="$2" 30 | newrev="$3" 31 | 32 | # --- Safety check 33 | if [ -z "$GIT_DIR" ]; then 34 | echo "Don't run this script from the command line." >&2 35 | echo " (if you want, you could supply GIT_DIR then run" >&2 36 | echo " $0 )" >&2 37 | exit 1 38 | fi 39 | 40 | if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then 41 | echo "usage: $0 " >&2 42 | exit 1 43 | fi 44 | 45 | # --- Config 46 | allowunannotated=$(git config --bool hooks.allowunannotated) 47 | allowdeletebranch=$(git config --bool hooks.allowdeletebranch) 48 | denycreatebranch=$(git config --bool hooks.denycreatebranch) 49 | allowdeletetag=$(git config --bool hooks.allowdeletetag) 50 | allowmodifytag=$(git config --bool hooks.allowmodifytag) 51 | 52 | # check for no description 53 | projectdesc=$(sed -e '1q' "$GIT_DIR/description") 54 | case "$projectdesc" in 55 | "Unnamed repository"* | "") 56 | echo "*** Project description file hasn't been set" >&2 57 | exit 1 58 | ;; 59 | esac 60 | 61 | # --- Check types 62 | # if $newrev is 0000...0000, it's a commit to delete a ref. 63 | zero="0000000000000000000000000000000000000000" 64 | if [ "$newrev" = "$zero" ]; then 65 | newrev_type=delete 66 | else 67 | newrev_type=$(git cat-file -t $newrev) 68 | fi 69 | 70 | case "$refname","$newrev_type" in 71 | refs/tags/*,commit) 72 | # un-annotated tag 73 | short_refname=${refname##refs/tags/} 74 | if [ "$allowunannotated" != "true" ]; then 75 | echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 76 | echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 77 | exit 1 78 | fi 79 | ;; 80 | refs/tags/*,delete) 81 | # delete tag 82 | if [ "$allowdeletetag" != "true" ]; then 83 | echo "*** Deleting a tag is not allowed in this repository" >&2 84 | exit 1 85 | fi 86 | ;; 87 | refs/tags/*,tag) 88 | # annotated tag 89 | if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 90 | then 91 | echo "*** Tag '$refname' already exists." >&2 92 | echo "*** Modifying a tag is not allowed in this repository." >&2 93 | exit 1 94 | fi 95 | ;; 96 | refs/heads/*,commit) 97 | # branch 98 | if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then 99 | echo "*** Creating a branch is not allowed in this repository" >&2 100 | exit 1 101 | fi 102 | ;; 103 | refs/heads/*,delete) 104 | # delete branch 105 | if [ "$allowdeletebranch" != "true" ]; then 106 | echo "*** Deleting a branch is not allowed in this repository" >&2 107 | exit 1 108 | fi 109 | ;; 110 | refs/remotes/*,commit) 111 | # tracking branch 112 | ;; 113 | refs/remotes/*,delete) 114 | # delete tracking branch 115 | if [ "$allowdeletebranch" != "true" ]; then 116 | echo "*** Deleting a tracking branch is not allowed in this repository" >&2 117 | exit 1 118 | fi 119 | ;; 120 | *) 121 | # Anything else (is there anything else?) 122 | echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 123 | exit 1 124 | ;; 125 | esac 126 | 127 | # --- Finished 128 | exit 0 129 | -------------------------------------------------------------------------------- /test_repos/empty/_git/info/exclude: -------------------------------------------------------------------------------- 1 | # git ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | # *~ 7 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/COMMIT_EDITMSG: -------------------------------------------------------------------------------- 1 | four 2 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/config: -------------------------------------------------------------------------------- 1 | [core] 2 | repositoryformatversion = 0 3 | filemode = false 4 | bare = false 5 | logallrefupdates = true 6 | symlinks = false 7 | ignorecase = true 8 | hideDotFiles = dotGitOnly 9 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/description: -------------------------------------------------------------------------------- 1 | Unnamed repository; edit this file 'description' to name the repository. 2 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/hooks/applypatch-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message taken by 4 | # applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. The hook is 8 | # allowed to edit the commit message file. 9 | # 10 | # To enable this hook, rename this file to "applypatch-msg". 11 | 12 | . git-sh-setup 13 | test -x "$GIT_DIR/hooks/commit-msg" && 14 | exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} 15 | : 16 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/hooks/commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message. 4 | # Called by "git commit" with one argument, the name of the file 5 | # that has the commit message. The hook should exit with non-zero 6 | # status after issuing an appropriate message if it wants to stop the 7 | # commit. The hook is allowed to edit the commit message file. 8 | # 9 | # To enable this hook, rename this file to "commit-msg". 10 | 11 | # Uncomment the below to add a Signed-off-by line to the message. 12 | # Doing this in a hook is a bad idea in general, but the prepare-commit-msg 13 | # hook is more suited to it. 14 | # 15 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 16 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 17 | 18 | # This example catches duplicate Signed-off-by lines. 19 | 20 | test "" = "$(grep '^Signed-off-by: ' "$1" | 21 | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { 22 | echo >&2 Duplicate Signed-off-by lines. 23 | exit 1 24 | } 25 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/hooks/post-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script that is called after a successful 4 | # commit is made. 5 | # 6 | # To enable this hook, rename this file to "post-commit". 7 | 8 | : Nothing 9 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/hooks/post-receive.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script for the "post-receive" event. 4 | # 5 | # The "post-receive" script is run after receive-pack has accepted a pack 6 | # and the repository has been updated. It is passed arguments in through 7 | # stdin in the form 8 | # 9 | # For example: 10 | # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master 11 | # 12 | # see contrib/hooks/ for a sample, or uncomment the next line and 13 | # rename the file to "post-receive". 14 | 15 | #. /usr/share/doc/git-core/contrib/hooks/post-receive-email 16 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/hooks/post-update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare a packed repository for use over 4 | # dumb transports. 5 | # 6 | # To enable this hook, rename this file to "post-update". 7 | 8 | exec git update-server-info 9 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/hooks/pre-applypatch.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed 4 | # by applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. 8 | # 9 | # To enable this hook, rename this file to "pre-applypatch". 10 | 11 | . git-sh-setup 12 | test -x "$GIT_DIR/hooks/pre-commit" && 13 | exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} 14 | : 15 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/hooks/pre-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | if git rev-parse --verify HEAD >/dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 16 | fi 17 | 18 | # If you want to allow non-ascii filenames set this variable to true. 19 | allownonascii=$(git config hooks.allownonascii) 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | # Cross platform projects tend to avoid non-ascii filenames; prevent 25 | # them from being added to the repository. We exploit the fact that the 26 | # printable range starts at the space character and ends with tilde. 27 | if [ "$allownonascii" != "true" ] && 28 | # Note that the use of brackets around a tr range is ok here, (it's 29 | # even required, for portability to Solaris 10's /usr/bin/tr), since 30 | # the square bracket bytes happen to fall in the designated range. 31 | test $(git diff --cached --name-only --diff-filter=A -z $against | 32 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 33 | then 34 | echo "Error: Attempt to add a non-ascii file name." 35 | echo 36 | echo "This can cause problems if you want to work" 37 | echo "with people on other platforms." 38 | echo 39 | echo "To be portable it is advisable to rename the file ..." 40 | echo 41 | echo "If you know what you are doing you can disable this" 42 | echo "check using:" 43 | echo 44 | echo " git config hooks.allownonascii true" 45 | echo 46 | exit 1 47 | fi 48 | 49 | # If there are whitespace errors, print the offending file names and fail. 50 | exec git diff-index --check --cached $against -- 51 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/hooks/pre-rebase.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2006, 2008 Junio C Hamano 4 | # 5 | # The "pre-rebase" hook is run just before "git rebase" starts doing 6 | # its job, and can prevent the command from running by exiting with 7 | # non-zero status. 8 | # 9 | # The hook is called with the following parameters: 10 | # 11 | # $1 -- the upstream the series was forked from. 12 | # $2 -- the branch being rebased (or empty when rebasing the current branch). 13 | # 14 | # This sample shows how to prevent topic branches that are already 15 | # merged to 'next' branch from getting rebased, because allowing it 16 | # would result in rebasing already published history. 17 | 18 | publish=next 19 | basebranch="$1" 20 | if test "$#" = 2 21 | then 22 | topic="refs/heads/$2" 23 | else 24 | topic=`git symbolic-ref HEAD` || 25 | exit 0 ;# we do not interrupt rebasing detached HEAD 26 | fi 27 | 28 | case "$topic" in 29 | refs/heads/??/*) 30 | ;; 31 | *) 32 | exit 0 ;# we do not interrupt others. 33 | ;; 34 | esac 35 | 36 | # Now we are dealing with a topic branch being rebased 37 | # on top of master. Is it OK to rebase it? 38 | 39 | # Does the topic really exist? 40 | git show-ref -q "$topic" || { 41 | echo >&2 "No such branch $topic" 42 | exit 1 43 | } 44 | 45 | # Is topic fully merged to master? 46 | not_in_master=`git rev-list --pretty=oneline ^master "$topic"` 47 | if test -z "$not_in_master" 48 | then 49 | echo >&2 "$topic is fully merged to master; better remove it." 50 | exit 1 ;# we could allow it, but there is no point. 51 | fi 52 | 53 | # Is topic ever merged to next? If so you should not be rebasing it. 54 | only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` 55 | only_next_2=`git rev-list ^master ${publish} | sort` 56 | if test "$only_next_1" = "$only_next_2" 57 | then 58 | not_in_topic=`git rev-list "^$topic" master` 59 | if test -z "$not_in_topic" 60 | then 61 | echo >&2 "$topic is already up-to-date with master" 62 | exit 1 ;# we could allow it, but there is no point. 63 | else 64 | exit 0 65 | fi 66 | else 67 | not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` 68 | /usr/bin/perl -e ' 69 | my $topic = $ARGV[0]; 70 | my $msg = "* $topic has commits already merged to public branch:\n"; 71 | my (%not_in_next) = map { 72 | /^([0-9a-f]+) /; 73 | ($1 => 1); 74 | } split(/\n/, $ARGV[1]); 75 | for my $elem (map { 76 | /^([0-9a-f]+) (.*)$/; 77 | [$1 => $2]; 78 | } split(/\n/, $ARGV[2])) { 79 | if (!exists $not_in_next{$elem->[0]}) { 80 | if ($msg) { 81 | print STDERR $msg; 82 | undef $msg; 83 | } 84 | print STDERR " $elem->[1]\n"; 85 | } 86 | } 87 | ' "$topic" "$not_in_next" "$not_in_master" 88 | exit 1 89 | fi 90 | 91 | exit 0 92 | 93 | ################################################################ 94 | 95 | This sample hook safeguards topic branches that have been 96 | published from being rewound. 97 | 98 | The workflow assumed here is: 99 | 100 | * Once a topic branch forks from "master", "master" is never 101 | merged into it again (either directly or indirectly). 102 | 103 | * Once a topic branch is fully cooked and merged into "master", 104 | it is deleted. If you need to build on top of it to correct 105 | earlier mistakes, a new topic branch is created by forking at 106 | the tip of the "master". This is not strictly necessary, but 107 | it makes it easier to keep your history simple. 108 | 109 | * Whenever you need to test or publish your changes to topic 110 | branches, merge them into "next" branch. 111 | 112 | The script, being an example, hardcodes the publish branch name 113 | to be "next", but it is trivial to make it configurable via 114 | $GIT_DIR/config mechanism. 115 | 116 | With this workflow, you would want to know: 117 | 118 | (1) ... if a topic branch has ever been merged to "next". Young 119 | topic branches can have stupid mistakes you would rather 120 | clean up before publishing, and things that have not been 121 | merged into other branches can be easily rebased without 122 | affecting other people. But once it is published, you would 123 | not want to rewind it. 124 | 125 | (2) ... if a topic branch has been fully merged to "master". 126 | Then you can delete it. More importantly, you should not 127 | build on top of it -- other people may already want to 128 | change things related to the topic as patches against your 129 | "master", so if you need further changes, it is better to 130 | fork the topic (perhaps with the same name) afresh from the 131 | tip of "master". 132 | 133 | Let's look at this example: 134 | 135 | o---o---o---o---o---o---o---o---o---o "next" 136 | / / / / 137 | / a---a---b A / / 138 | / / / / 139 | / / c---c---c---c B / 140 | / / / \ / 141 | / / / b---b C \ / 142 | / / / / \ / 143 | ---o---o---o---o---o---o---o---o---o---o---o "master" 144 | 145 | 146 | A, B and C are topic branches. 147 | 148 | * A has one fix since it was merged up to "next". 149 | 150 | * B has finished. It has been fully merged up to "master" and "next", 151 | and is ready to be deleted. 152 | 153 | * C has not merged to "next" at all. 154 | 155 | We would want to allow C to be rebased, refuse A, and encourage 156 | B to be deleted. 157 | 158 | To compute (1): 159 | 160 | git rev-list ^master ^topic next 161 | git rev-list ^master next 162 | 163 | if these match, topic has not merged in next at all. 164 | 165 | To compute (2): 166 | 167 | git rev-list master..topic 168 | 169 | if this is empty, it is fully merged to "master". 170 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/hooks/prepare-commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare the commit log message. 4 | # Called by "git commit" with the name of the file that has the 5 | # commit message, followed by the description of the commit 6 | # message's source. The hook's purpose is to edit the commit 7 | # message file. If the hook fails with a non-zero status, 8 | # the commit is aborted. 9 | # 10 | # To enable this hook, rename this file to "prepare-commit-msg". 11 | 12 | # This hook includes three examples. The first comments out the 13 | # "Conflicts:" part of a merge commit. 14 | # 15 | # The second includes the output of "git diff --name-status -r" 16 | # into the message, just before the "git status" output. It is 17 | # commented because it doesn't cope with --amend or with squashed 18 | # commits. 19 | # 20 | # The third example adds a Signed-off-by line to the message, that can 21 | # still be edited. This is rarely a good idea. 22 | 23 | case "$2,$3" in 24 | merge,) 25 | /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; 26 | 27 | # ,|template,) 28 | # /usr/bin/perl -i.bak -pe ' 29 | # print "\n" . `git diff --cached --name-status -r` 30 | # if /^#/ && $first++ == 0' "$1" ;; 31 | 32 | *) ;; 33 | esac 34 | 35 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 36 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 37 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/hooks/update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to blocks unannotated tags from entering. 4 | # Called by "git receive-pack" with arguments: refname sha1-old sha1-new 5 | # 6 | # To enable this hook, rename this file to "update". 7 | # 8 | # Config 9 | # ------ 10 | # hooks.allowunannotated 11 | # This boolean sets whether unannotated tags will be allowed into the 12 | # repository. By default they won't be. 13 | # hooks.allowdeletetag 14 | # This boolean sets whether deleting tags will be allowed in the 15 | # repository. By default they won't be. 16 | # hooks.allowmodifytag 17 | # This boolean sets whether a tag may be modified after creation. By default 18 | # it won't be. 19 | # hooks.allowdeletebranch 20 | # This boolean sets whether deleting branches will be allowed in the 21 | # repository. By default they won't be. 22 | # hooks.denycreatebranch 23 | # This boolean sets whether remotely creating branches will be denied 24 | # in the repository. By default this is allowed. 25 | # 26 | 27 | # --- Command line 28 | refname="$1" 29 | oldrev="$2" 30 | newrev="$3" 31 | 32 | # --- Safety check 33 | if [ -z "$GIT_DIR" ]; then 34 | echo "Don't run this script from the command line." >&2 35 | echo " (if you want, you could supply GIT_DIR then run" >&2 36 | echo " $0 )" >&2 37 | exit 1 38 | fi 39 | 40 | if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then 41 | echo "Usage: $0 " >&2 42 | exit 1 43 | fi 44 | 45 | # --- Config 46 | allowunannotated=$(git config --bool hooks.allowunannotated) 47 | allowdeletebranch=$(git config --bool hooks.allowdeletebranch) 48 | denycreatebranch=$(git config --bool hooks.denycreatebranch) 49 | allowdeletetag=$(git config --bool hooks.allowdeletetag) 50 | allowmodifytag=$(git config --bool hooks.allowmodifytag) 51 | 52 | # check for no description 53 | projectdesc=$(sed -e '1q' "$GIT_DIR/description") 54 | case "$projectdesc" in 55 | "Unnamed repository"* | "") 56 | echo "*** Project description file hasn't been set" >&2 57 | exit 1 58 | ;; 59 | esac 60 | 61 | # --- Check types 62 | # if $newrev is 0000...0000, it's a commit to delete a ref. 63 | zero="0000000000000000000000000000000000000000" 64 | if [ "$newrev" = "$zero" ]; then 65 | newrev_type=delete 66 | else 67 | newrev_type=$(git cat-file -t $newrev) 68 | fi 69 | 70 | case "$refname","$newrev_type" in 71 | refs/tags/*,commit) 72 | # un-annotated tag 73 | short_refname=${refname##refs/tags/} 74 | if [ "$allowunannotated" != "true" ]; then 75 | echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 76 | echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 77 | exit 1 78 | fi 79 | ;; 80 | refs/tags/*,delete) 81 | # delete tag 82 | if [ "$allowdeletetag" != "true" ]; then 83 | echo "*** Deleting a tag is not allowed in this repository" >&2 84 | exit 1 85 | fi 86 | ;; 87 | refs/tags/*,tag) 88 | # annotated tag 89 | if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 90 | then 91 | echo "*** Tag '$refname' already exists." >&2 92 | echo "*** Modifying a tag is not allowed in this repository." >&2 93 | exit 1 94 | fi 95 | ;; 96 | refs/heads/*,commit) 97 | # branch 98 | if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then 99 | echo "*** Creating a branch is not allowed in this repository" >&2 100 | exit 1 101 | fi 102 | ;; 103 | refs/heads/*,delete) 104 | # delete branch 105 | if [ "$allowdeletebranch" != "true" ]; then 106 | echo "*** Deleting a branch is not allowed in this repository" >&2 107 | exit 1 108 | fi 109 | ;; 110 | refs/remotes/*,commit) 111 | # tracking branch 112 | ;; 113 | refs/remotes/*,delete) 114 | # delete tracking branch 115 | if [ "$allowdeletebranch" != "true" ]; then 116 | echo "*** Deleting a tracking branch is not allowed in this repository" >&2 117 | exit 1 118 | fi 119 | ;; 120 | *) 121 | # Anything else (is there anything else?) 122 | echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 123 | exit 1 124 | ;; 125 | esac 126 | 127 | # --- Finished 128 | exit 0 129 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/index -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/info/exclude: -------------------------------------------------------------------------------- 1 | # git ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | # *~ 7 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/logs/HEAD: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 1cdcbf37e36f1b7c2cdd2c72fafb404be84b33a5 Scott Graham 1370967932 -0700 commit (initial): yolo 2 | 1cdcbf37e36f1b7c2cdd2c72fafb404be84b33a5 7b4f1aedeb255f3e44cd0c2cb29da2d568771938 Scott Graham 1370967941 -0700 commit: two 3 | 7b4f1aedeb255f3e44cd0c2cb29da2d568771938 fc0385bb9f2d03d7e0991f9e1a61ac1e0f5e633e Scott Graham 1370967949 -0700 commit: three 4 | fc0385bb9f2d03d7e0991f9e1a61ac1e0f5e633e f1a5903ec22f221f352044b28cd921cf44aa3a09 Scott Graham 1370967954 -0700 commit: four 5 | f1a5903ec22f221f352044b28cd921cf44aa3a09 7b4f1aedeb255f3e44cd0c2cb29da2d568771938 Scott Graham 1370968039 -0700 checkout: moving from master to 7b4f1aedeb255f3e44cd0c2cb29da2d568771938 6 | 7b4f1aedeb255f3e44cd0c2cb29da2d568771938 f1a5903ec22f221f352044b28cd921cf44aa3a09 Scott Graham 1370968078 -0700 checkout: moving from 7b4f1aedeb255f3e44cd0c2cb29da2d568771938 to master 7 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/logs/refs/heads/master: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 1cdcbf37e36f1b7c2cdd2c72fafb404be84b33a5 Scott Graham 1370967932 -0700 commit (initial): yolo 2 | 1cdcbf37e36f1b7c2cdd2c72fafb404be84b33a5 7b4f1aedeb255f3e44cd0c2cb29da2d568771938 Scott Graham 1370967941 -0700 commit: two 3 | 7b4f1aedeb255f3e44cd0c2cb29da2d568771938 fc0385bb9f2d03d7e0991f9e1a61ac1e0f5e633e Scott Graham 1370967949 -0700 commit: three 4 | fc0385bb9f2d03d7e0991f9e1a61ac1e0f5e633e f1a5903ec22f221f352044b28cd921cf44aa3a09 Scott Graham 1370967954 -0700 commit: four 5 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/objects/09/80abf5c2fcc3ffca32131a6601990018c4bd41: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/objects/09/80abf5c2fcc3ffca32131a6601990018c4bd41 -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/objects/1c/dcbf37e36f1b7c2cdd2c72fafb404be84b33a5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/objects/1c/dcbf37e36f1b7c2cdd2c72fafb404be84b33a5 -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/objects/46/6c3e9907b6b0d4e6a5050dad3b966900b71ec8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/objects/46/6c3e9907b6b0d4e6a5050dad3b966900b71ec8 -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/objects/56/f3b36e2773a56fe267643b6b1678e06c9584eb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/objects/56/f3b36e2773a56fe267643b6b1678e06c9584eb -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/objects/66/0a045b054741044cc4009bcd1a80ac04afd201: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/objects/66/0a045b054741044cc4009bcd1a80ac04afd201 -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/objects/7b/4f1aedeb255f3e44cd0c2cb29da2d568771938: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/objects/7b/4f1aedeb255f3e44cd0c2cb29da2d568771938 -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/objects/9c/4842597acfc9fc5ea49adeae53e73e86aef4f1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/objects/9c/4842597acfc9fc5ea49adeae53e73e86aef4f1 -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/objects/ae/26c34d05647d6298e11a8426dcfab600d06d42: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/objects/ae/26c34d05647d6298e11a8426dcfab600d06d42 -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/objects/cd/85ecc46860b4fe3b7981e4a0d5f808f9d90455: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/objects/cd/85ecc46860b4fe3b7981e4a0d5f808f9d90455 -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/objects/da/acd152eb89cc687ff06a6befdbcbefeba552fc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/objects/da/acd152eb89cc687ff06a6befdbcbefeba552fc -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/objects/f1/a5903ec22f221f352044b28cd921cf44aa3a09: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/objects/f1/a5903ec22f221f352044b28cd921cf44aa3a09 -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/objects/fc/0385bb9f2d03d7e0991f9e1a61ac1e0f5e633e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/four_linear_commits/_git/objects/fc/0385bb9f2d03d7e0991f9e1a61ac1e0f5e633e -------------------------------------------------------------------------------- /test_repos/four_linear_commits/_git/refs/heads/master: -------------------------------------------------------------------------------- 1 | f1a5903ec22f221f352044b28cd921cf44aa3a09 2 | -------------------------------------------------------------------------------- /test_repos/four_linear_commits/a_file: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/COMMIT_EDITMSG: -------------------------------------------------------------------------------- 1 | yolo 2 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/config: -------------------------------------------------------------------------------- 1 | [core] 2 | repositoryformatversion = 0 3 | filemode = false 4 | bare = false 5 | logallrefupdates = true 6 | symlinks = false 7 | ignorecase = true 8 | hideDotFiles = dotGitOnly 9 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/description: -------------------------------------------------------------------------------- 1 | Unnamed repository; edit this file 'description' to name the repository. 2 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/hooks/applypatch-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message taken by 4 | # applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. The hook is 8 | # allowed to edit the commit message file. 9 | # 10 | # To enable this hook, rename this file to "applypatch-msg". 11 | 12 | . git-sh-setup 13 | test -x "$GIT_DIR/hooks/commit-msg" && 14 | exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} 15 | : 16 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/hooks/commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message. 4 | # Called by "git commit" with one argument, the name of the file 5 | # that has the commit message. The hook should exit with non-zero 6 | # status after issuing an appropriate message if it wants to stop the 7 | # commit. The hook is allowed to edit the commit message file. 8 | # 9 | # To enable this hook, rename this file to "commit-msg". 10 | 11 | # Uncomment the below to add a Signed-off-by line to the message. 12 | # Doing this in a hook is a bad idea in general, but the prepare-commit-msg 13 | # hook is more suited to it. 14 | # 15 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 16 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 17 | 18 | # This example catches duplicate Signed-off-by lines. 19 | 20 | test "" = "$(grep '^Signed-off-by: ' "$1" | 21 | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { 22 | echo >&2 Duplicate Signed-off-by lines. 23 | exit 1 24 | } 25 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/hooks/post-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script that is called after a successful 4 | # commit is made. 5 | # 6 | # To enable this hook, rename this file to "post-commit". 7 | 8 | : Nothing 9 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/hooks/post-receive.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script for the "post-receive" event. 4 | # 5 | # The "post-receive" script is run after receive-pack has accepted a pack 6 | # and the repository has been updated. It is passed arguments in through 7 | # stdin in the form 8 | # 9 | # For example: 10 | # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master 11 | # 12 | # see contrib/hooks/ for a sample, or uncomment the next line and 13 | # rename the file to "post-receive". 14 | 15 | #. /usr/share/doc/git-core/contrib/hooks/post-receive-email 16 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/hooks/post-update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare a packed repository for use over 4 | # dumb transports. 5 | # 6 | # To enable this hook, rename this file to "post-update". 7 | 8 | exec git update-server-info 9 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/hooks/pre-applypatch.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed 4 | # by applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. 8 | # 9 | # To enable this hook, rename this file to "pre-applypatch". 10 | 11 | . git-sh-setup 12 | test -x "$GIT_DIR/hooks/pre-commit" && 13 | exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} 14 | : 15 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/hooks/pre-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | if git rev-parse --verify HEAD >/dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 16 | fi 17 | 18 | # If you want to allow non-ascii filenames set this variable to true. 19 | allownonascii=$(git config hooks.allownonascii) 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | # Cross platform projects tend to avoid non-ascii filenames; prevent 25 | # them from being added to the repository. We exploit the fact that the 26 | # printable range starts at the space character and ends with tilde. 27 | if [ "$allownonascii" != "true" ] && 28 | # Note that the use of brackets around a tr range is ok here, (it's 29 | # even required, for portability to Solaris 10's /usr/bin/tr), since 30 | # the square bracket bytes happen to fall in the designated range. 31 | test $(git diff --cached --name-only --diff-filter=A -z $against | 32 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 33 | then 34 | echo "Error: Attempt to add a non-ascii file name." 35 | echo 36 | echo "This can cause problems if you want to work" 37 | echo "with people on other platforms." 38 | echo 39 | echo "To be portable it is advisable to rename the file ..." 40 | echo 41 | echo "If you know what you are doing you can disable this" 42 | echo "check using:" 43 | echo 44 | echo " git config hooks.allownonascii true" 45 | echo 46 | exit 1 47 | fi 48 | 49 | # If there are whitespace errors, print the offending file names and fail. 50 | exec git diff-index --check --cached $against -- 51 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/hooks/pre-rebase.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2006, 2008 Junio C Hamano 4 | # 5 | # The "pre-rebase" hook is run just before "git rebase" starts doing 6 | # its job, and can prevent the command from running by exiting with 7 | # non-zero status. 8 | # 9 | # The hook is called with the following parameters: 10 | # 11 | # $1 -- the upstream the series was forked from. 12 | # $2 -- the branch being rebased (or empty when rebasing the current branch). 13 | # 14 | # This sample shows how to prevent topic branches that are already 15 | # merged to 'next' branch from getting rebased, because allowing it 16 | # would result in rebasing already published history. 17 | 18 | publish=next 19 | basebranch="$1" 20 | if test "$#" = 2 21 | then 22 | topic="refs/heads/$2" 23 | else 24 | topic=`git symbolic-ref HEAD` || 25 | exit 0 ;# we do not interrupt rebasing detached HEAD 26 | fi 27 | 28 | case "$topic" in 29 | refs/heads/??/*) 30 | ;; 31 | *) 32 | exit 0 ;# we do not interrupt others. 33 | ;; 34 | esac 35 | 36 | # Now we are dealing with a topic branch being rebased 37 | # on top of master. Is it OK to rebase it? 38 | 39 | # Does the topic really exist? 40 | git show-ref -q "$topic" || { 41 | echo >&2 "No such branch $topic" 42 | exit 1 43 | } 44 | 45 | # Is topic fully merged to master? 46 | not_in_master=`git rev-list --pretty=oneline ^master "$topic"` 47 | if test -z "$not_in_master" 48 | then 49 | echo >&2 "$topic is fully merged to master; better remove it." 50 | exit 1 ;# we could allow it, but there is no point. 51 | fi 52 | 53 | # Is topic ever merged to next? If so you should not be rebasing it. 54 | only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` 55 | only_next_2=`git rev-list ^master ${publish} | sort` 56 | if test "$only_next_1" = "$only_next_2" 57 | then 58 | not_in_topic=`git rev-list "^$topic" master` 59 | if test -z "$not_in_topic" 60 | then 61 | echo >&2 "$topic is already up-to-date with master" 62 | exit 1 ;# we could allow it, but there is no point. 63 | else 64 | exit 0 65 | fi 66 | else 67 | not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` 68 | /usr/bin/perl -e ' 69 | my $topic = $ARGV[0]; 70 | my $msg = "* $topic has commits already merged to public branch:\n"; 71 | my (%not_in_next) = map { 72 | /^([0-9a-f]+) /; 73 | ($1 => 1); 74 | } split(/\n/, $ARGV[1]); 75 | for my $elem (map { 76 | /^([0-9a-f]+) (.*)$/; 77 | [$1 => $2]; 78 | } split(/\n/, $ARGV[2])) { 79 | if (!exists $not_in_next{$elem->[0]}) { 80 | if ($msg) { 81 | print STDERR $msg; 82 | undef $msg; 83 | } 84 | print STDERR " $elem->[1]\n"; 85 | } 86 | } 87 | ' "$topic" "$not_in_next" "$not_in_master" 88 | exit 1 89 | fi 90 | 91 | exit 0 92 | 93 | ################################################################ 94 | 95 | This sample hook safeguards topic branches that have been 96 | published from being rewound. 97 | 98 | The workflow assumed here is: 99 | 100 | * Once a topic branch forks from "master", "master" is never 101 | merged into it again (either directly or indirectly). 102 | 103 | * Once a topic branch is fully cooked and merged into "master", 104 | it is deleted. If you need to build on top of it to correct 105 | earlier mistakes, a new topic branch is created by forking at 106 | the tip of the "master". This is not strictly necessary, but 107 | it makes it easier to keep your history simple. 108 | 109 | * Whenever you need to test or publish your changes to topic 110 | branches, merge them into "next" branch. 111 | 112 | The script, being an example, hardcodes the publish branch name 113 | to be "next", but it is trivial to make it configurable via 114 | $GIT_DIR/config mechanism. 115 | 116 | With this workflow, you would want to know: 117 | 118 | (1) ... if a topic branch has ever been merged to "next". Young 119 | topic branches can have stupid mistakes you would rather 120 | clean up before publishing, and things that have not been 121 | merged into other branches can be easily rebased without 122 | affecting other people. But once it is published, you would 123 | not want to rewind it. 124 | 125 | (2) ... if a topic branch has been fully merged to "master". 126 | Then you can delete it. More importantly, you should not 127 | build on top of it -- other people may already want to 128 | change things related to the topic as patches against your 129 | "master", so if you need further changes, it is better to 130 | fork the topic (perhaps with the same name) afresh from the 131 | tip of "master". 132 | 133 | Let's look at this example: 134 | 135 | o---o---o---o---o---o---o---o---o---o "next" 136 | / / / / 137 | / a---a---b A / / 138 | / / / / 139 | / / c---c---c---c B / 140 | / / / \ / 141 | / / / b---b C \ / 142 | / / / / \ / 143 | ---o---o---o---o---o---o---o---o---o---o---o "master" 144 | 145 | 146 | A, B and C are topic branches. 147 | 148 | * A has one fix since it was merged up to "next". 149 | 150 | * B has finished. It has been fully merged up to "master" and "next", 151 | and is ready to be deleted. 152 | 153 | * C has not merged to "next" at all. 154 | 155 | We would want to allow C to be rebased, refuse A, and encourage 156 | B to be deleted. 157 | 158 | To compute (1): 159 | 160 | git rev-list ^master ^topic next 161 | git rev-list ^master next 162 | 163 | if these match, topic has not merged in next at all. 164 | 165 | To compute (2): 166 | 167 | git rev-list master..topic 168 | 169 | if this is empty, it is fully merged to "master". 170 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/hooks/prepare-commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare the commit log message. 4 | # Called by "git commit" with the name of the file that has the 5 | # commit message, followed by the description of the commit 6 | # message's source. The hook's purpose is to edit the commit 7 | # message file. If the hook fails with a non-zero status, 8 | # the commit is aborted. 9 | # 10 | # To enable this hook, rename this file to "prepare-commit-msg". 11 | 12 | # This hook includes three examples. The first comments out the 13 | # "Conflicts:" part of a merge commit. 14 | # 15 | # The second includes the output of "git diff --name-status -r" 16 | # into the message, just before the "git status" output. It is 17 | # commented because it doesn't cope with --amend or with squashed 18 | # commits. 19 | # 20 | # The third example adds a Signed-off-by line to the message, that can 21 | # still be edited. This is rarely a good idea. 22 | 23 | case "$2,$3" in 24 | merge,) 25 | /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; 26 | 27 | # ,|template,) 28 | # /usr/bin/perl -i.bak -pe ' 29 | # print "\n" . `git diff --cached --name-status -r` 30 | # if /^#/ && $first++ == 0' "$1" ;; 31 | 32 | *) ;; 33 | esac 34 | 35 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 36 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 37 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/hooks/update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to blocks unannotated tags from entering. 4 | # Called by "git receive-pack" with arguments: refname sha1-old sha1-new 5 | # 6 | # To enable this hook, rename this file to "update". 7 | # 8 | # Config 9 | # ------ 10 | # hooks.allowunannotated 11 | # This boolean sets whether unannotated tags will be allowed into the 12 | # repository. By default they won't be. 13 | # hooks.allowdeletetag 14 | # This boolean sets whether deleting tags will be allowed in the 15 | # repository. By default they won't be. 16 | # hooks.allowmodifytag 17 | # This boolean sets whether a tag may be modified after creation. By default 18 | # it won't be. 19 | # hooks.allowdeletebranch 20 | # This boolean sets whether deleting branches will be allowed in the 21 | # repository. By default they won't be. 22 | # hooks.denycreatebranch 23 | # This boolean sets whether remotely creating branches will be denied 24 | # in the repository. By default this is allowed. 25 | # 26 | 27 | # --- Command line 28 | refname="$1" 29 | oldrev="$2" 30 | newrev="$3" 31 | 32 | # --- Safety check 33 | if [ -z "$GIT_DIR" ]; then 34 | echo "Don't run this script from the command line." >&2 35 | echo " (if you want, you could supply GIT_DIR then run" >&2 36 | echo " $0 )" >&2 37 | exit 1 38 | fi 39 | 40 | if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then 41 | echo "Usage: $0 " >&2 42 | exit 1 43 | fi 44 | 45 | # --- Config 46 | allowunannotated=$(git config --bool hooks.allowunannotated) 47 | allowdeletebranch=$(git config --bool hooks.allowdeletebranch) 48 | denycreatebranch=$(git config --bool hooks.denycreatebranch) 49 | allowdeletetag=$(git config --bool hooks.allowdeletetag) 50 | allowmodifytag=$(git config --bool hooks.allowmodifytag) 51 | 52 | # check for no description 53 | projectdesc=$(sed -e '1q' "$GIT_DIR/description") 54 | case "$projectdesc" in 55 | "Unnamed repository"* | "") 56 | echo "*** Project description file hasn't been set" >&2 57 | exit 1 58 | ;; 59 | esac 60 | 61 | # --- Check types 62 | # if $newrev is 0000...0000, it's a commit to delete a ref. 63 | zero="0000000000000000000000000000000000000000" 64 | if [ "$newrev" = "$zero" ]; then 65 | newrev_type=delete 66 | else 67 | newrev_type=$(git cat-file -t $newrev) 68 | fi 69 | 70 | case "$refname","$newrev_type" in 71 | refs/tags/*,commit) 72 | # un-annotated tag 73 | short_refname=${refname##refs/tags/} 74 | if [ "$allowunannotated" != "true" ]; then 75 | echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 76 | echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 77 | exit 1 78 | fi 79 | ;; 80 | refs/tags/*,delete) 81 | # delete tag 82 | if [ "$allowdeletetag" != "true" ]; then 83 | echo "*** Deleting a tag is not allowed in this repository" >&2 84 | exit 1 85 | fi 86 | ;; 87 | refs/tags/*,tag) 88 | # annotated tag 89 | if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 90 | then 91 | echo "*** Tag '$refname' already exists." >&2 92 | echo "*** Modifying a tag is not allowed in this repository." >&2 93 | exit 1 94 | fi 95 | ;; 96 | refs/heads/*,commit) 97 | # branch 98 | if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then 99 | echo "*** Creating a branch is not allowed in this repository" >&2 100 | exit 1 101 | fi 102 | ;; 103 | refs/heads/*,delete) 104 | # delete branch 105 | if [ "$allowdeletebranch" != "true" ]; then 106 | echo "*** Deleting a branch is not allowed in this repository" >&2 107 | exit 1 108 | fi 109 | ;; 110 | refs/remotes/*,commit) 111 | # tracking branch 112 | ;; 113 | refs/remotes/*,delete) 114 | # delete tracking branch 115 | if [ "$allowdeletebranch" != "true" ]; then 116 | echo "*** Deleting a tracking branch is not allowed in this repository" >&2 117 | exit 1 118 | fi 119 | ;; 120 | *) 121 | # Anything else (is there anything else?) 122 | echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 123 | exit 1 124 | ;; 125 | esac 126 | 127 | # --- Finished 128 | exit 0 129 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/single_commit/_git/index -------------------------------------------------------------------------------- /test_repos/single_commit/_git/info/exclude: -------------------------------------------------------------------------------- 1 | # git ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | # *~ 7 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/logs/HEAD: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 7b926d27cfb65dc0a2eef5defbe8d17998594915 Scott Graham 1370967860 -0700 commit (initial): yolo 2 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/logs/refs/heads/master: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 7b926d27cfb65dc0a2eef5defbe8d17998594915 Scott Graham 1370967860 -0700 commit (initial): yolo 2 | -------------------------------------------------------------------------------- /test_repos/single_commit/_git/objects/56/f3b36e2773a56fe267643b6b1678e06c9584eb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/single_commit/_git/objects/56/f3b36e2773a56fe267643b6b1678e06c9584eb -------------------------------------------------------------------------------- /test_repos/single_commit/_git/objects/7b/926d27cfb65dc0a2eef5defbe8d17998594915: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/single_commit/_git/objects/7b/926d27cfb65dc0a2eef5defbe8d17998594915 -------------------------------------------------------------------------------- /test_repos/single_commit/_git/objects/cd/85ecc46860b4fe3b7981e4a0d5f808f9d90455: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgraham/cmdEx/521a23faf7209d87ae87c062513df167bc5880b4/test_repos/single_commit/_git/objects/cd/85ecc46860b4fe3b7981e4a0d5f808f9d90455 -------------------------------------------------------------------------------- /test_repos/single_commit/_git/refs/heads/master: -------------------------------------------------------------------------------- 1 | 7b926d27cfb65dc0a2eef5defbe8d17998594915 2 | -------------------------------------------------------------------------------- /test_repos/single_commit/a_file: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------