| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 8dd51f1 commit a17c43d
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,21 +2,44 @@ | |||
| 2 | 2 | Changelog | |
| 3 | 3 | ========= | |
| 4 | 4 | ||
| 5 | - 0.3.2 Beta 2 | ||
| 5 | + 0.3.1 Beta 2 | ||
| 6 | 6 | ============ | |
| 7 | - * Added reflog support ( reading and writing ) | ||
| 7 | + * Added **reflog support** ( reading and writing ) | ||
| 8 | + | ||
| 9 | + * New types: ``RefLog`` and ``RefLogEntry`` | ||
| 10 | + * Reflog is maintained automatically when creating references and deleting them | ||
| 11 | + * Non-intrusive changes to ``SymbolicReference``, these don't require your code to change. They allow to append messages to the reflog. | ||
| 12 | + | ||
| 13 | + * ``abspath`` property added, similar to ``abspath`` of Object instances | ||
| 14 | + * ``log()`` method added | ||
| 15 | + * ``log_append(...)`` method added | ||
| 16 | + * ``set_reference(...)`` method added (reflog support) | ||
| 17 | + * ``set_commit(...)`` method added (reflog support) | ||
| 18 | + | ||
| 19 | + * Intrusive Changes to ``Head`` type | ||
| 20 | + | ||
| 21 | + * ``create(...)`` method now supports the reflog, but will not raise ``GitCommandError`` anymore as it is a pure python implementation now. Instead, it raises ``OSError``. | ||
| 22 | + | ||
| 8 | 23 | * Repo.rev_parse now supports the [ref]@{n} syntax, where n is the number of steps to look into the reference's past | |
| 9 | 24 | ||
| 10 | - 0.3.2 Beta 1 | ||
| 11 | - ============ | ||
| 12 | - * Flattened directory structure to make development more convenient. | ||
| 25 | + * **BugFixes** | ||
| 26 | + | ||
| 27 | + * Removed incorrect ORIG_HEAD handling | ||
| 28 | + | ||
| 29 | + * **Flattened directory** structure to make development more convenient. | ||
| 13 | 30 | ||
| 14 | 31 | * .. note:: This alters the way projects using git-python as a submodule have to adjust their sys.path to be able to import git-python successfully. | |
| 32 | + * Misc smaller changes and bugfixes | ||
| 15 | 33 | ||
| 16 | 34 | 0.3.1 Beta 1 | |
| 17 | 35 | ============ | |
| 18 | 36 | * Full Submodule-Support | |
| 19 | 37 | * Added unicode support for author names. Commit.author.name is now unicode instead of string. | |
| 38 | + * Head Type changes | ||
| 39 | + | ||
| 40 | + * config_reader() & config_writer() methods added for access to head specific options. | ||
| 41 | + * tracking_branch() & set_tracking_branch() methods addded for easy configuration of tracking branches. | ||
| 42 | + | ||
| 20 | 43 | ||
| 21 | 44 | 0.3.0 Beta 2 | |
| 22 | 45 | ============ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -112,38 +112,6 @@ class Head(Reference): | |||
| 112 | 112 | k_config_remote = "remote" | |
| 113 | 113 | k_config_remote_ref = "merge" # branch to merge from remote | |
| 114 | 114 | ||
| 115 | - @classmethod | ||
| 116 | - def create(cls, repo, path, commit='HEAD', force=False, **kwargs): | ||
| 117 | - """Create a new head. | ||
| 118 | - :param repo: Repository to create the head in | ||
| 119 | - :param path: | ||
| 120 | - The name or path of the head, i.e. 'new_branch' or | ||
| 121 | - feature/feature1. The prefix refs/heads is implied. | ||
| 122 | - | ||
| 123 | - :param commit: | ||
| 124 | - Commit to which the new head should point, defaults to the | ||
| 125 | - current HEAD | ||
| 126 | - | ||
| 127 | - :param force: | ||
| 128 | - if True, force creation even if branch with that name already exists. | ||
| 129 | - | ||
| 130 | - :param kwargs: | ||
| 131 | - Additional keyword arguments to be passed to git-branch, i.e. | ||
| 132 | - track, no-track, l | ||
| 133 | - | ||
| 134 | - :return: Newly created Head | ||
| 135 | - :note: This does not alter the current HEAD, index or Working Tree""" | ||
| 136 | - if cls is not Head: | ||
| 137 | - raise TypeError("Only Heads can be created explicitly, not objects of type %s" % cls.__name__) | ||
| 138 | - | ||
| 139 | - args = ( path, commit ) | ||
| 140 | - if force: | ||
| 141 | - kwargs['f'] = True | ||
| 142 | - | ||
| 143 | - repo.git.branch(*args, **kwargs) | ||
| 144 | - return cls(repo, "%s/%s" % ( cls._common_path_default, path)) | ||
| 145 | - | ||
| 146 | - | ||
| 147 | 115 | @classmethod | |
| 148 | 116 | def delete(cls, repo, *heads, **kwargs): | |
| 149 | 117 | """Delete the given heads | |
@@ -157,7 +125,6 @@ def delete(cls, repo, *heads, **kwargs): | |||
| 157 | 125 | flag = "-D" | |
| 158 | 126 | repo.git.branch(flag, *heads) | |
| 159 | 127 | ||
| 160 | - | ||
| 161 | 128 | def set_tracking_branch(self, remote_reference): | |
| 162 | 129 | """ | |
| 163 | 130 | Configure this branch to track the given remote reference. This will alter | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,6 +18,7 @@ class Reference(SymbolicReference, LazyMixin, Iterable): | |||
| 18 | 18 | """Represents a named reference to any object. Subclasses may apply restrictions though, | |
| 19 | 19 | i.e. Heads can only point to commits.""" | |
| 20 | 20 | __slots__ = tuple() | |
| 21 | + _resolve_ref_on_create = True | ||
| 21 | 22 | _common_path_default = "refs" | |
| 22 | 23 | ||
| 23 | 24 | def __init__(self, repo, path): | |
@@ -52,7 +53,7 @@ def _set_object(self, ref): | |||
| 52 | 53 | ||
| 53 | 54 | :note: | |
| 54 | 55 | TypeChecking is done by the git command""" | |
| 55 | - abs_path = self._abs_path() | ||
| 56 | + abs_path = self.abspath | ||
| 56 | 57 | existed = True | |
| 57 | 58 | if not isfile(abs_path): | |
| 58 | 59 | existed = False | |
@@ -81,31 +82,7 @@ def name(self): | |||
| 81 | 82 | return self.path # could be refs/HEAD | |
| 82 | 83 | return '/'.join(tokens[2:]) | |
| 83 | 84 | ||
| 84 | - | ||
| 85 | 85 | @classmethod | |
| 86 | - def create(cls, repo, path, commit='HEAD', force=False ): | ||
| 87 | - """Create a new reference. | ||
| 88 | - | ||
| 89 | - :param repo: Repository to create the reference in | ||
| 90 | - :param path: | ||
| 91 | - The relative path of the reference, i.e. 'new_branch' or | ||
| 92 | - feature/feature1. The path prefix 'refs/' is implied if not | ||
| 93 | - given explicitly | ||
| 94 | - | ||
| 95 | - :param commit: | ||
| 96 | - Commit to which the new reference should point, defaults to the | ||
| 97 | - current HEAD | ||
| 98 | - | ||
| 99 | - :param force: | ||
| 100 | - if True, force creation even if a reference with that name already exists. | ||
| 101 | - Raise OSError otherwise | ||
| 102 | - | ||
| 103 | - :return: Newly created Reference | ||
| 104 | - | ||
| 105 | - :note: This does not alter the current HEAD, index or Working Tree""" | ||
| 106 | - return cls._create(repo, path, True, commit, force) | ||
| 107 | - | ||
| 108 | - @classmethod | ||
| 109 | 86 | def iter_items(cls, repo, common_path = None): | |
| 110 | 87 | """Equivalent to SymbolicReference.iter_items, but will return non-detached | |
| 111 | 88 | references as well.""" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -56,3 +56,8 @@ def delete(cls, repo, *refs, **kwargs): | |||
| 56 | 56 | except OSError: | |
| 57 | 57 | pass | |
| 58 | 58 | # END for each ref | |
| 59 | + | ||
| 60 | + @classmethod | ||
| 61 | + def create(cls, *args, **kwargs): | ||
| 62 | + """Used to disable this method""" | ||
| 63 | + raise TypeError("Cannot explicitly create remote references") | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,7 +3,8 @@ | |||
| 3 | 3 | from git.util import ( | |
| 4 | 4 | join_path, | |
| 5 | 5 | join_path_native, | |
| 6 | - to_native_path_linux | ||
| 6 | + to_native_path_linux, | ||
| 7 | + assure_directory_exists | ||
| 7 | 8 | ) | |
| 8 | 9 | ||
| 9 | 10 | from gitdb.util import ( | |
@@ -28,6 +29,7 @@ class SymbolicReference(object): | |||
| 28 | 29 | ||
| 29 | 30 | A typical example for a symbolic reference is HEAD.""" | |
| 30 | 31 | __slots__ = ("repo", "path") | |
| 32 | + _resolve_ref_on_create = False | ||
| 31 | 33 | _common_path_default = "" | |
| 32 | 34 | _id_attribute_ = "name" | |
| 33 | 35 | ||
@@ -58,7 +60,8 @@ def name(self): | |||
| 58 | 60 | is the path itself.""" | |
| 59 | 61 | return self.path | |
| 60 | 62 | ||
| 61 | - def _abs_path(self): | ||
| 63 | + @property | ||
| 64 | + def abspath(self): | ||
| 62 | 65 | return join_path_native(self.repo.git_dir, self.path) | |
| 63 | 66 | ||
| 64 | 67 | @classmethod | |
@@ -116,7 +119,7 @@ def _get_ref_info(self): | |||
| 116 | 119 | point to, or None""" | |
| 117 | 120 | tokens = None | |
| 118 | 121 | try: | |
| 119 | - fp = open(self._abs_path(), 'r') | ||
| 122 | + fp = open(self.abspath, 'r') | ||
| 120 | 123 | value = fp.read().rstrip() | |
| 121 | 124 | fp.close() | |
| 122 | 125 | tokens = value.split(" ") | |
@@ -158,37 +161,48 @@ def _get_commit(self): | |||
| 158 | 161 | ||
| 159 | 162 | return self.from_path(self.repo, target_ref_path).commit | |
| 160 | 163 | ||
| 161 | - def _set_commit(self, commit): | ||
| 164 | + def set_commit(self, commit, msg = None): | ||
| 162 | 165 | """Set our commit, possibly dereference our symbolic reference first. | |
| 163 | - If the reference does not exist, it will be created""" | ||
| 166 | + If the reference does not exist, it will be created | ||
| 167 | + | ||
| 168 | + :param msg: If not None, the message will be used in the reflog entry to be | ||
| 169 | + written. Otherwise the reflog is not altered""" | ||
| 164 | 170 | is_detached = True | |
| 165 | 171 | try: | |
| 166 | 172 | is_detached = self.is_detached | |
| 167 | 173 | except ValueError: | |
| 168 | 174 | pass | |
| 169 | 175 | # END handle non-existing ones | |
| 176 | + | ||
| 170 | 177 | if is_detached: | |
| 171 | - return self._set_reference(commit) | ||
| 178 | + return self.set_reference(commit, msg) | ||
| 172 | 179 | ||
| 173 | 180 | # set the commit on our reference | |
| 174 | - self._get_reference().commit = commit | ||
| 181 | + self._get_reference().set_commit(commit, msg) | ||
| 175 | 182 | ||
| 176 | - commit = property(_get_commit, _set_commit, doc="Query or set commits directly") | ||
| 183 | + commit = property(_get_commit, set_commit, doc="Query or set commits directly") | ||
| 177 | 184 | ||
| 178 | 185 | def _get_reference(self): | |
| 179 | - """:return: Reference Object we point to""" | ||
| 186 | + """:return: Reference Object we point to | ||
| 187 | + :raise TypeError: If this symbolic reference is detached, hence it doesn't point | ||
| 188 | + to a reference, but to a commit""" | ||
| 180 | 189 | sha, target_ref_path = self._get_ref_info() | |
| 181 | 190 | if target_ref_path is None: | |
| 182 | 191 | raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha)) | |
| 183 | 192 | return self.from_path(self.repo, target_ref_path) | |
| 184 | 193 | ||
| 185 | - def _set_reference(self, ref, msg = None): | ||
| 194 | + def set_reference(self, ref, msg = None): | ||
| 186 | 195 | """Set ourselves to the given ref. It will stay a symbol if the ref is a Reference. | |
| 187 | - Otherwise we try to get a commit from it using our interface. | ||
| 196 | + Otherwise a commmit, given as Commit object or refspec, is assumed and if valid, | ||
| 197 | + will be set which effectively detaches the refererence if it was a purely | ||
| 198 | + symbolic one. | ||
| 188 | 199 | ||
| 189 | - Strings are allowed but will be checked to be sure we have a commit | ||
| 200 | + :param ref: SymbolicReference instance, Commit instance or refspec string | ||
| 190 | 201 | :param msg: If set to a string, the message will be used in the reflog. | |
| 191 | - Otherwise, a reflog entry is not written for the changed reference""" | ||
| 202 | + Otherwise, a reflog entry is not written for the changed reference. | ||
| 203 | + The previous commit of the entry will be the commit we point to now. | ||
| 204 | + | ||
| 205 | + See also: log_append()""" | ||
| 192 | 206 | write_value = None | |
| 193 | 207 | if isinstance(ref, SymbolicReference): | |
| 194 | 208 | write_value = "ref: %s" % ref.path | |
@@ -207,33 +221,31 @@ def _set_reference(self, ref, msg = None): | |||
| 207 | 221 | raise ValueError("Could not extract object from %s" % ref) | |
| 208 | 222 | # END end try string | |
| 209 | 223 | # END try commit attribute | |
| 224 | + oldbinsha = None | ||
| 225 | + if msg is not None: | ||
| 226 | + try: | ||
| 227 | + oldhexsha = self.commit.binsha | ||
| 228 | + except ValueError: | ||
| 229 | + oldbinsha = Commit.NULL_BIN_SHA | ||
| 230 | + #END handle non-existing | ||
| 231 | + #END retrieve old hexsha | ||
| 232 | + | ||
| 233 | + fpath = self.abspath | ||
| 234 | + assure_directory_exists(fpath, is_file=True) | ||
| 235 | + | ||
| 236 | + lfd = LockedFD(fpath) | ||
| 237 | + fd = lfd.open(write=True, stream=True) | ||
| 238 | + fd.write(write_value) | ||
| 239 | + lfd.commit() | ||
| 240 | + | ||
| 241 | + # Adjust the reflog | ||
| 242 | + if msg is not None: | ||
| 243 | + self.log_append(oldbinsha, msg) | ||
| 244 | + #END handle reflog | ||
| 210 | 245 | ||
| 211 | - # if we are writing a ref, use symbolic ref to get the reflog and more | ||
| 212 | - # checking | ||
| 213 | - # Otherwise we detach it and have to do it manually. Besides, this works | ||
| 214 | - # recursively automaitcally, but should be replaced with a python implementation | ||
| 215 | - # soon | ||
| 216 | - if write_value.startswith('ref:'): | ||
| 217 | - self.repo.git.symbolic_ref(self.path, write_value[5:]) | ||
| 218 | - return | ||
| 219 | - # END non-detached handling | ||
| 220 | - | ||
| 221 | - path = self._abs_path() | ||
| 222 | - directory = dirname(path) | ||
| 223 | - if not isdir(directory): | ||
| 224 | - os.makedirs(directory) | ||
| 225 | - | ||
| 226 | - # TODO: Write using LockedFD | ||
| 227 | - fp = open(path, "wb") | ||
| 228 | - try: | ||
| 229 | - fp.write(write_value) | ||
| 230 | - finally: | ||
| 231 | - fp.close() | ||
| 232 | - # END writing | ||
| 233 | - | ||
| 234 | 246 | ||
| 235 | 247 | # aliased reference | |
| 236 | - reference = property(_get_reference, _set_reference, doc="Returns the Reference we point to") | ||
| 248 | + reference = property(_get_reference, set_reference, doc="Returns the Reference we point to") | ||
| 237 | 249 | ref = reference | |
| 238 | 250 | ||
| 239 | 251 | def is_valid(self): | |
@@ -255,7 +267,7 @@ def is_detached(self): | |||
| 255 | 267 | True if we are a detached reference, hence we point to a specific commit | |
| 256 | 268 | instead to another reference""" | |
| 257 | 269 | try: | |
| 258 | - self.reference | ||
| 270 | + self.ref | ||
| 259 | 271 | return False | |
| 260 | 272 | except TypeError: | |
| 261 | 273 | return True | |
@@ -343,11 +355,18 @@ def delete(cls, repo, path): | |||
| 343 | 355 | open(pack_file_path, 'w').writelines(new_lines) | |
| 344 | 356 | # END open exception handling | |
| 345 | 357 | # END handle deletion | |
| 358 | + | ||
| 359 | + # delete the reflog | ||
| 360 | + reflog_path = RefLog.path(cls(repo, full_ref_path)) | ||
| 361 | + if os.path.isfile(reflog_path): | ||
| 362 | + os.remove(reflog_path) | ||
| 363 | + #END remove reflog | ||
| 364 | + | ||
| 346 | 365 | ||
| 347 | 366 | @classmethod | |
| 348 | - def _create(cls, repo, path, resolve, reference, force): | ||
| 367 | + def _create(cls, repo, path, resolve, reference, force, msg=None): | ||
| 349 | 368 | """internal method used to create a new symbolic reference. | |
| 350 | - If resolve is False,, the reference will be taken as is, creating | ||
| 369 | + If resolve is False, the reference will be taken as is, creating | ||
| 351 | 370 | a proper symbolic reference. Otherwise it will be resolved to the | |
| 352 | 371 | corresponding object and a detached symbolic reference will be created | |
| 353 | 372 | instead""" | |
@@ -365,16 +384,17 @@ def _create(cls, repo, path, resolve, reference, force): | |||
| 365 | 384 | target_data = target.path | |
| 366 | 385 | if not resolve: | |
| 367 | 386 | target_data = "ref: " + target_data | |
| 368 | - if open(abs_ref_path, 'rb').read().strip() != target_data: | ||
| 369 | - raise OSError("Reference at %s does already exist" % full_ref_path) | ||
| 387 | + existing_data = open(abs_ref_path, 'rb').read().strip() | ||
| 388 | + if existing_data != target_data: | ||
| 389 | + raise OSError("Reference at %r does already exist, pointing to %r, requested was %r" % (full_ref_path, existing_data, target_data)) | ||
| 370 | 390 | # END no force handling | |
| 371 | 391 | ||
| 372 | 392 | ref = cls(repo, full_ref_path) | |
| 373 | - ref.reference = target | ||
| 393 | + ref.set_reference(target, msg) | ||
| 374 | 394 | return ref | |
| 375 | 395 | ||
| 376 | 396 | @classmethod | |
| 377 | - def create(cls, repo, path, reference='HEAD', force=False ): | ||
| 397 | + def create(cls, repo, path, reference='HEAD', force=False, msg=None): | ||
| 378 | 398 | """Create a new symbolic reference, hence a reference pointing to another reference. | |
| 379 | 399 | ||
| 380 | 400 | :param repo: | |
@@ -391,14 +411,18 @@ def create(cls, repo, path, reference='HEAD', force=False ): | |||
| 391 | 411 | if True, force creation even if a symbolic reference with that name already exists. | |
| 392 | 412 | Raise OSError otherwise | |
| 393 | 413 | ||
| 414 | + :param msg: | ||
| 415 | + If not None, the message to append to the reflog. Otherwise no reflog | ||
| 416 | + entry is written. | ||
| 417 | + | ||
| 394 | 418 | :return: Newly created symbolic Reference | |
| 395 | 419 | ||
| 396 | 420 | :raise OSError: | |
| 397 | 421 | If a (Symbolic)Reference with the same name but different contents | |
| 398 | 422 | already exists. | |
| 399 | 423 | ||
| 400 | 424 | :note: This does not alter the current HEAD, index or Working Tree""" | |
| 401 | - return cls._create(repo, path, False, reference, force) | ||
| 425 | + return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, msg) | ||
| 402 | 426 | ||
| 403 | 427 | def rename(self, new_path, force=False): | |
| 404 | 428 | """Rename self to a new path | |
| Back | FazBrowse Home | New Git URL |
0 commit comments