├── .gitignore ├── Gitpad.csproj ├── Gitpad.sln ├── LICENSE ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── README.md └── app.config /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | bin 3 | obj 4 | _Resharper* 5 | *.user 6 | -------------------------------------------------------------------------------- /Gitpad.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {EA1CD7BA-6DE9-4DDC-B594-31B40789A88E} 9 | Exe 10 | Properties 11 | Gitpad 12 | Gitpad 13 | v2.0 14 | 15 | 16 | 512 17 | 18 | 19 | x86 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | true 28 | 29 | 30 | AnyCPU 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | true 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 59 | -------------------------------------------------------------------------------- /Gitpad.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 11 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gitpad", "Gitpad.csproj", "{EA1CD7BA-6DE9-4DDC-B594-31B40789A88E}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|x86 = Debug|x86 9 | Release|x86 = Release|x86 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {EA1CD7BA-6DE9-4DDC-B594-31B40789A88E}.Debug|x86.ActiveCfg = Debug|x86 13 | {EA1CD7BA-6DE9-4DDC-B594-31B40789A88E}.Debug|x86.Build.0 = Debug|x86 14 | {EA1CD7BA-6DE9-4DDC-B594-31B40789A88E}.Release|x86.ActiveCfg = Release|x86 15 | {EA1CD7BA-6DE9-4DDC-B594-31B40789A88E}.Release|x86.Build.0 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 GitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Reflection; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | using System.Windows.Forms; 9 | 10 | namespace Gitpad 11 | { 12 | public enum LineEndingType 13 | { 14 | Windows, /*CR+LF*/ 15 | Posix, /*LF*/ 16 | MacOS9, /*CR*/ 17 | Unsure, 18 | } 19 | 20 | public class Program 21 | { 22 | public static int Main(string[] args) 23 | { 24 | if (args.Length == 0) 25 | { 26 | if (IsProcessElevated()) 27 | { 28 | MessageBox.Show("Run this application as a normal user (not as Elevated Administrator)", 29 | "App is Elevated", MessageBoxButtons.OK, MessageBoxIcon.Error ); 30 | return -1; 31 | } 32 | 33 | if (MessageBox.Show( 34 | "Do you want to use your default text editor as your commit editor?", 35 | "Installing GitPad", MessageBoxButtons.YesNo) 36 | != DialogResult.Yes) 37 | { 38 | return -1; 39 | } 40 | 41 | var target = new DirectoryInfo(Environment.ExpandEnvironmentVariables(@"%AppData%\GitPad")); 42 | if (!target.Exists) 43 | { 44 | target.Create(); 45 | } 46 | 47 | var dest = new FileInfo(Environment.ExpandEnvironmentVariables(@"%AppData%\GitPad\GitPad.exe")); 48 | File.Copy(Assembly.GetExecutingAssembly().Location, dest.FullName, true); 49 | 50 | Environment.SetEnvironmentVariable("EDITOR", "~/AppData/Roaming/GitPad/GitPad.exe", EnvironmentVariableTarget.User); 51 | return 0; 52 | } 53 | 54 | int ret = 0; 55 | string fileData; 56 | string path = null; 57 | try 58 | { 59 | fileData = File.ReadAllText(args[0], Encoding.UTF8); 60 | path = Path.GetRandomFileName() + ".txt"; 61 | WriteStringToFile(path, fileData, LineEndingType.Windows, true); 62 | } 63 | catch (Exception ex) 64 | { 65 | Console.Error.WriteLine(ex); 66 | ret = -1; 67 | goto bail; 68 | } 69 | 70 | var psi = new ProcessStartInfo(path) 71 | { 72 | WindowStyle = ProcessWindowStyle.Normal, 73 | UseShellExecute = true, 74 | }; 75 | 76 | Process proc; 77 | 78 | try 79 | { 80 | proc = Process.Start(psi); 81 | } 82 | catch 83 | { 84 | Console.Error.WriteLine("Could not launch the default text editor, falling back to notepad."); 85 | 86 | psi.FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "notepad.exe"); 87 | psi.Arguments = path; 88 | 89 | proc = Process.Start(psi); 90 | } 91 | 92 | // See http://stackoverflow.com/questions/3456383/process-start-returns-null 93 | // In case of editor reuse (think VS) we can't block on the process so we only have two options. Either try 94 | // to be clever and monitor the file for changes but it's quite possible that users save their file before 95 | // being done with them so we'll go with the semi-sucky method of showing a message on the console 96 | if (proc == null) 97 | { 98 | Console.CancelKeyPress += (s, e) => File.Delete(path); 99 | 100 | Console.WriteLine("Press enter when you're done editing your commit message, or CTRL+C to abort"); 101 | Console.ReadLine(); 102 | } 103 | else 104 | { 105 | proc.WaitForExit(); 106 | if (proc.ExitCode != 0) 107 | { 108 | ret = proc.ExitCode; 109 | goto bail; 110 | } 111 | } 112 | 113 | try 114 | { 115 | fileData = File.ReadAllText(path, Encoding.UTF8); 116 | WriteStringToFile(args[0], fileData, LineEndingType.Posix, false); 117 | } 118 | catch (Exception ex) 119 | { 120 | Console.Error.WriteLine(ex); 121 | ret = -1; 122 | } 123 | 124 | bail: 125 | if (path != null && File.Exists(path)) 126 | File.Delete(path); 127 | return ret; 128 | } 129 | 130 | static void WriteStringToFile(string path, string fileData, LineEndingType lineType, bool emitUTF8Preamble) 131 | { 132 | using(var of = File.Open(path, FileMode.Create)) 133 | { 134 | var buf = Encoding.UTF8.GetBytes(ForceLineEndings(fileData, lineType)); 135 | if (emitUTF8Preamble) 136 | of.Write(Encoding.UTF8.GetPreamble(), 0, Encoding.UTF8.GetPreamble().Length); 137 | of.Write(buf, 0, buf.Length); 138 | } 139 | } 140 | 141 | public static string ForceLineEndings(string fileData, LineEndingType type) 142 | { 143 | var ret = new StringBuilder(fileData.Length); 144 | 145 | string ending; 146 | switch(type) 147 | { 148 | case LineEndingType.Windows: 149 | ending = "\r\n"; 150 | break; 151 | case LineEndingType.Posix: 152 | ending = "\n"; 153 | break; 154 | case LineEndingType.MacOS9: 155 | ending = "\r"; 156 | break; 157 | default: 158 | throw new Exception("Specify an explicit line ending type"); 159 | } 160 | 161 | foreach (var line in fileData.Split('\n')) 162 | { 163 | var fixedLine = line.Replace("\r", ""); 164 | ret.Append(fixedLine); 165 | ret.Append(ending); 166 | } 167 | 168 | // Don't add new lines to the end of the file. 169 | string str = ret.ToString(); 170 | return str.Substring(0, str.Length - ending.Length); 171 | } 172 | 173 | public static bool IsProcessElevated() 174 | { 175 | if (Environment.OSVersion.Version < new Version(6,0,0,0)) 176 | { 177 | // Elevation is not a thing. 178 | return false; 179 | } 180 | 181 | IntPtr tokenHandle; 182 | if (!NativeMethods.OpenProcessToken(NativeMethods.GetCurrentProcess(), NativeMethods.TOKEN_QUERY, out tokenHandle)) 183 | { 184 | throw new Exception("OpenProcessToken failed", new Win32Exception()); 185 | } 186 | 187 | try 188 | { 189 | TOKEN_ELEVATION_TYPE elevationType; 190 | uint dontcare; 191 | if (!NativeMethods.GetTokenInformation(tokenHandle, TOKEN_INFORMATION_CLASS.TokenElevationType, out elevationType, (uint)sizeof(TOKEN_ELEVATION_TYPE), out dontcare)) 192 | { 193 | throw new Exception("GetTokenInformation failed", new Win32Exception()); 194 | } 195 | 196 | return (elevationType == TOKEN_ELEVATION_TYPE.TokenElevationTypeFull); 197 | } 198 | finally 199 | { 200 | NativeMethods.CloseHandle(tokenHandle); 201 | } 202 | } 203 | } 204 | 205 | public enum TOKEN_INFORMATION_CLASS 206 | { 207 | /// 208 | /// The buffer receives a TOKEN_USER structure that contains the user account of the token. 209 | /// 210 | TokenUser = 1, 211 | 212 | /// 213 | /// The buffer receives a TOKEN_GROUPS structure that contains the group accounts associated with the token. 214 | /// 215 | TokenGroups, 216 | 217 | /// 218 | /// The buffer receives a TOKEN_PRIVILEGES structure that contains the privileges of the token. 219 | /// 220 | TokenPrivileges, 221 | 222 | /// 223 | /// The buffer receives a TOKEN_OWNER structure that contains the default owner security identifier (SID) for newly created objects. 224 | /// 225 | TokenOwner, 226 | 227 | /// 228 | /// The buffer receives a TOKEN_PRIMARY_GROUP structure that contains the default primary group SID for newly created objects. 229 | /// 230 | TokenPrimaryGroup, 231 | 232 | /// 233 | /// The buffer receives a TOKEN_DEFAULT_DACL structure that contains the default DACL for newly created objects. 234 | /// 235 | TokenDefaultDacl, 236 | 237 | /// 238 | /// The buffer receives a TOKEN_SOURCE structure that contains the source of the token. TOKEN_QUERY_SOURCE access is needed to retrieve this information. 239 | /// 240 | TokenSource, 241 | 242 | /// 243 | /// The buffer receives a TOKEN_TYPE value that indicates whether the token is a primary or impersonation token. 244 | /// 245 | TokenType, 246 | 247 | /// 248 | /// The buffer receives a SECURITY_IMPERSONATION_LEVEL value that indicates the impersonation level of the token. If the access token is not an impersonation token, the function fails. 249 | /// 250 | TokenImpersonationLevel, 251 | 252 | /// 253 | /// The buffer receives a TOKEN_STATISTICS structure that contains various token statistics. 254 | /// 255 | TokenStatistics, 256 | 257 | /// 258 | /// The buffer receives a TOKEN_GROUPS structure that contains the list of restricting SIDs in a restricted token. 259 | /// 260 | TokenRestrictedSids, 261 | 262 | /// 263 | /// The buffer receives a DWORD value that indicates the Terminal Services session identifier that is associated with the token. 264 | /// 265 | TokenSessionId, 266 | 267 | /// 268 | /// The buffer receives a TOKEN_GROUPS_AND_PRIVILEGES structure that contains the user SID, the group accounts, the restricted SIDs, and the authentication ID associated with the token. 269 | /// 270 | TokenGroupsAndPrivileges, 271 | 272 | /// 273 | /// Reserved. 274 | /// 275 | TokenSessionReference, 276 | 277 | /// 278 | /// The buffer receives a DWORD value that is nonzero if the token includes the SANDBOX_INERT flag. 279 | /// 280 | TokenSandBoxInert, 281 | 282 | /// 283 | /// Reserved. 284 | /// 285 | TokenAuditPolicy, 286 | 287 | /// 288 | /// The buffer receives a TOKEN_ORIGIN value. 289 | /// 290 | TokenOrigin, 291 | 292 | /// 293 | /// The buffer receives a TOKEN_ELEVATION_TYPE value that specifies the elevation level of the token. 294 | /// 295 | TokenElevationType, 296 | 297 | /// 298 | /// The buffer receives a TOKEN_LINKED_TOKEN structure that contains a handle to another token that is linked to this token. 299 | /// 300 | TokenLinkedToken, 301 | 302 | /// 303 | /// The buffer receives a TOKEN_ELEVATION structure that specifies whether the token is elevated. 304 | /// 305 | TokenElevation, 306 | 307 | /// 308 | /// The buffer receives a DWORD value that is nonzero if the token has ever been filtered. 309 | /// 310 | TokenHasRestrictions, 311 | 312 | /// 313 | /// The buffer receives a TOKEN_ACCESS_INFORMATION structure that specifies security information contained in the token. 314 | /// 315 | TokenAccessInformation, 316 | 317 | /// 318 | /// The buffer receives a DWORD value that is nonzero if virtualization is allowed for the token. 319 | /// 320 | TokenVirtualizationAllowed, 321 | 322 | /// 323 | /// The buffer receives a DWORD value that is nonzero if virtualization is enabled for the token. 324 | /// 325 | TokenVirtualizationEnabled, 326 | 327 | /// 328 | /// The buffer receives a TOKEN_MANDATORY_LABEL structure that specifies the token's integrity level. 329 | /// 330 | TokenIntegrityLevel, 331 | 332 | /// 333 | /// The buffer receives a DWORD value that is nonzero if the token has the UIAccess flag set. 334 | /// 335 | TokenUIAccess, 336 | 337 | /// 338 | /// The buffer receives a TOKEN_MANDATORY_POLICY structure that specifies the token's mandatory integrity policy. 339 | /// 340 | TokenMandatoryPolicy, 341 | 342 | /// 343 | /// The buffer receives the token's logon security identifier (SID). 344 | /// 345 | TokenLogonSid, 346 | 347 | /// 348 | /// The maximum value for this enumeration 349 | /// 350 | MaxTokenInfoClass 351 | } 352 | 353 | public enum TOKEN_ELEVATION_TYPE 354 | { 355 | TokenElevationTypeDefault = 1, 356 | TokenElevationTypeFull, 357 | TokenElevationTypeLimited 358 | } 359 | 360 | public static class NativeMethods 361 | { 362 | public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000; 363 | public const UInt32 STANDARD_RIGHTS_READ = 0x00020000; 364 | public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x0001; 365 | public const UInt32 TOKEN_DUPLICATE = 0x0002; 366 | public const UInt32 TOKEN_IMPERSONATE = 0x0004; 367 | public const UInt32 TOKEN_QUERY = 0x0008; 368 | public const UInt32 TOKEN_QUERY_SOURCE = 0x0010; 369 | public const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x0020; 370 | public const UInt32 TOKEN_ADJUST_GROUPS = 0x0040; 371 | public const UInt32 TOKEN_ADJUST_DEFAULT = 0x0080; 372 | public const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100; 373 | public const UInt32 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY); 374 | public const UInt32 TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY | 375 | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | 376 | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT | 377 | TOKEN_ADJUST_SESSIONID); 378 | 379 | [DllImport("advapi32.dll", SetLastError = true)] 380 | [return: MarshalAs(UnmanagedType.Bool)] 381 | public static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle); 382 | 383 | [DllImport("kernel32.dll")] 384 | public static extern IntPtr GetCurrentProcess(); 385 | 386 | [DllImport("advapi32.dll", SetLastError = true)] 387 | public static extern bool GetTokenInformation( 388 | IntPtr TokenHandle, 389 | TOKEN_INFORMATION_CLASS TokenInformationClass, 390 | out TOKEN_ELEVATION_TYPE TokenInformation, 391 | uint TokenInformationLength, 392 | out uint ReturnLength); 393 | 394 | [DllImport("kernel32.dll", SetLastError = true)] 395 | [return: MarshalAs(UnmanagedType.Bool)] 396 | public static extern bool CloseHandle(IntPtr hObject); 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Gitpad")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("GitHub")] 12 | [assembly: AssemblyProduct("Gitpad")] 13 | [assembly: AssemblyCopyright("Copyright © GitHub 2011-2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("ea27d850-aefe-4def-95eb-b0009789c585")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.4.0.0")] 36 | [assembly: AssemblyFileVersion("1.4.0.0")] 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitPad - Use Notepad as your Git commit editor 2 | 3 | This single executable allows you to use Notepad as your editor any time Git 4 | requires one (commits, interactive rebase, etc). 5 | 6 | ![Notepad editing a commit](http://f.cl.ly/items/3A3Y3P3B3Y3P1B0e2Q0Y/Grab.png) 7 | 8 | ## How to install (short version) 9 | 10 | [Click This Link](https://github.com/github/GitPad/releases/download/v1.4.0/Gitpad.zip) 11 | 12 | ## Notepad sucks! What about $FAVORITE\_EDITOR instead? 13 | 14 | Good news! As of GitPad 1.2, the default editor will be whatever editor is 15 | associated with .txt files. Normally, that's Notepad, but if you like a different 16 | editor, you can now use that instead. 17 | 18 | ## How to install (long version) 19 | 20 | Just copy GitPad.exe to a folder and double-click on it. It will install 21 | itself into your Application Data directory (%AppData%) - if your default 22 | editor is not set, GitPad.exe will set itself to EDITOR via setting an 23 | Environment Variable on your user profile. 24 | 25 | ## What do I need to use this? 26 | 27 | GitPad requires .NET 2.0 or higher. 28 | 29 | ## How to uninstall 30 | 31 | Remove HKEY_CURRENT_USER\Environment\EDITOR from the registry and reboot your 32 | system. 33 | -------------------------------------------------------------------------------- /app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | --------------------------------------------------------------------------------