├── .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 | 
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 |
--------------------------------------------------------------------------------